pax_global_header00006660000000000000000000000064147636311120014516gustar00rootroot0000000000000052 comment=5169a428d51c3ae8ff7b0897e8a687d8e05e37b5 zvbi-0.2.44/000077500000000000000000000000001476363111200125575ustar00rootroot00000000000000zvbi-0.2.44/.gitignore000066400000000000000000000035551476363111200145570ustar00rootroot00000000000000# Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf # autotools/m4 cache autom4te.cache/ # backup files created by autotools **/*~ # file generated by gettext po/*.gmo # folders created by libtool **/.deps/ **/.libs/ libtool # autogenerated files aclocal.m4 Makefile Makefile.in po/Makefile.in po/remove-potcdate.sed src/Makefile.in config.h config.h.in config.log config.status configure doc/Doxyfile doc/Makefile.in test/Makefile.in test/*.log test/*.trs test/capture test/ctest-gnu89 test/date test/glyph test/cpptest test/decode test/cpptest-cxx98 test/ttxfilter test/ctest-c89 test/test-dvb_mux test/test-packet-830 test/sliced2pes test/ctest-c94 test/test-vps test/explist test/proxy-test test/test-pdc test/ctest-c99 test/caption test/test-dvb_demux test/unicode test/test-raw_decoder test/ctest test/osc test/test-hamm test/ctest-gnu99 test/cpptest-gnuxx98 test/export zvbi-0.2.pc zvbi.spec examples/pdc2 examples/network examples/rawout examples/wss examples/pdc1 examples/Makefile examples/Makefile.in examples/*.log examples/*.trs contrib/Makefile.in contrib/x11font contrib/zvbi-ntsc-cc contrib/zvbi-atsc-cc daemon/Makefile.in daemon/zvbi-chains daemon/zvbid daemon/zvbid.init doc/html/ doc/html-build.stamp po/Makevars.template po/POTFILES po/stamp-po po/zvbi.pot site_def.h stamp-h1 test/unicode-out.txt COPYING INSTALL build-aux/compile build-aux/config.guess build-aux/config.sub build-aux/depcomp build-aux/install-sh build-aux/ltmain.sh build-aux/missing build-aux/test-driver m4/*.m4 m4/Makefile.in zvbi-0.2.44/ABOUT-NLS000066400000000000000000000001031476363111200140000ustar00rootroot00000000000000 zvbi-0.2.44/AUTHORS000066400000000000000000000155631476363111200136410ustar00rootroot00000000000000Ileana Dumitrescu -----BEGIN PGP PUBLIC KEY BLOCK----- mQINBGFMu5ABEACpFrPCKpfsTSl4svqi91Hsf8gGtdKwndgXqMPJNqBXEJCCwiiU PnS68wNWae54So04zVAcXewFdM36GypUGep5bhdgvbVKaDCrhNRdAoZ0VAywgU9C DCAa3v8eXUSrlGonk/ygjLIMnkOSjIMls4+z0FOpvsd1IcgcBDU5S6DSAF/Sb8w9 bF2yD7f5RaLN6++EJEO2Bp+8v4qCJEUGzi5QJKXHVUTGiTirx50eLIkw0HseLVOi JoU0NRRgzK/q04+X/NuOAPnZm5K3GOJUmKmG7M2tdMhhGT7UjF3XiI0MwydGIrPU 1T1OdPBnXv6ajRYzLgIZl0GsGeFo5qFaFmRtNO7nCGi/5XtivM1WvbqXIQmsAmpm 8N/uEcPcuP+0+7s1o0JC+c4nbHlQyvUFSZVgbZQ+mSn6GXRPNfL7AeDSINXXvXDv 5vkHN+FbFggx5nWg8J5a33hxbnZoR/qTfDBQHF3mJMF3lesXibN+oLvwOVtlIffK c4jwjLKL40644eQfbhHjCE1AXGQjUGCE5vAkCxEqWH2nQbXIedijQD/5mufaCQX3 Rl314FBfyV1b9rIHxJYRLXHT83+om7y5ncYI5sdoY7/g/Ggmi0PuuUicax/ejOx4 nNtDgDxlhCgOVm6qpmX9kyEZj0+vAodQjMrx3JKgojBdbusl4C5bWIgeRwARAQAB tC9JbGVhbmEgRHVtaXRyZXNjdSA8aWxlYW5hZHVtaTk1QHByb3Rvbm1haWwuY29t PokCVAQTAQoAPhYhBPomynhL4YiSfyK5n2Vw6gEUb3NUBQJhTLuQAhsDBQkDwmcA BQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEGVw6gEUb3NUCC4P/AiRUDzkEm8E WdvGQ9CkUYPAOARr19w04+N+86XZU8owULTkys81Wv80Wz48Q6IA3RASjHuyNtOQ a3TmoGsRYovIqKWQY6hIWBY7radPldSnbqXDp0mbwxSFVsCV2m2YqZKQpnKTR7b5 N6KgKKDXDLK0ES5CO1DAdvTg33WOonSNVpP+14R1bg9L685nOckK+TP1kQq91W+0 QUeEfS7BqdU/Znv39sVVMUkXQiWK441rQ1wcHvD32iiSoqnFQxtrdTwaglpv1/Y6 MDsnnwrLX3Bsq0vIL8CYVwVqy309/rtq3tpL1dw9lWaEA0sBNBMfOvBBJ1GOUpnE f6k6dlhHSoDDndbODXBEAgXnbz6JKqPA+NAJfnccnvcb7G2KnWtvG/GbWQia7S1Q lRi18GTDfX143uApm++/bFkGy/m3UjocGxyx9xh/wpzKuTlqBvxAX/cSR7hw3imC 5t2t6fmgIL9ZTED5FEyEgM1+zi/OfrPyqxKs/Qo8ZxoqMuZMHN4n9pJCtOvLT72H PUNxfRKiqjTj9hgQUm+sc8vfYXGapLY7Ybi4VrNPtGUxH/iSRf29sYc7bQlIoXiW KqcPjPuI/IZ8qMrNSloBgGeMuJ4iQn5shIZWbbgnJf5LcSO3SaPsLH80tnfimhkK tj6+MMe9afss26DcNcw4mT3IljkSRjLxtDBJbGVhbmEgRHVtaXRyZXNjdSA8aWxl YW5hZHVtaXRyZXNjdTk1QGdtYWlsLmNvbT6JAlQEEwEKAD4WIQT6Jsp4S+GIkn8i uZ9lcOoBFG9zVAUCYvzlDwIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIX gAAKCRBlcOoBFG9zVJxyD/4+FtSISH3aCu2aDztoRCyQtL3AiRgemtJlT94N8OhG pShkLAOD/G7SgzXY62aj+jrDffUVnpwgmAEp3ShqvUpmCHuwZ66/fMQedvBtwaNu 9TggCk9SGridu9jbTL6ss74DVV07WcsyjP7F/pd+kEZUW1YuVcaXVZZe9GtVO2Mb rL8wbRXlq3W1We7wGN5Eq1+7V42fDY/At1IjyXQoLjwVPhY83LOoHye54HENUB64 6JDBQRlPJy3B8iq4q9W9pbWdNn1vk5HtRFYkkFyLxfGvOTqpJVcSgxbvRKWRINZW aPOFDRmQyaW5aUm0Z30KAUMW5kqeLuJ1UZWFTasyjZH/+8PsbpBNfC8RWds6nlbd VjTmrg23E1pqDOJkMu9Ku/bok+qU4xIf5ltUe+Pr217ekCksYVdWEw8mHm7pjBrv eJvRuasE4Ov8NFWRp8le8sKsCi87ShzgScMz+ntI4UuNW00oTJUbmKPJToAHpyrl /9BCnU6J396Ppqfqq0jpZFQXhoko4b/07KgInmzoSRNbrcNYkKRxMymRp5uNH+u0 YEgFIComWsApFT7UHJ9rwwH8W/69PP3XyjQnF5BiLCED3JUWkoQiRQWjlxTuhtY1 RNo8o924F8VMv8fTvBdzHm2nCjs0UTqC+A0jaeyk4pfJCauBKKsUiSUDJ4Ve6G0p t7kCDQRhTLuQARAAxH2RgDZFA5q9G2wfKzsig/Dz/Kx9H9MGLayJEs5MNIJv17dG +mMmgjRk4O0QwhGzmgD8nBe1AJXqE6hm6K2MpXajb/B9/vIFNgNQ9KIaTtIehkG2 rwXwPDLfvgPYLRw+fH0gAVbS1mDDRro7RJr8pl7m8mi63UEZQxkqF3IZ1pD7uyfC cPl0V3b5dWwo5Uky7hJPEFvj8zJaBS6YdnZ8WteIxIR8eHMPwi2WQLJwn8LUqG2O DMIMkpQo71f2dCopCSq1UU2BY/JMagbpUXek3FIjNIKp9KUj3FFkUFvlqKif+kB9 M6P+llBVY0nDCidK617V6NJwaJUZzRgGimiuW2Qx9QwWHYcBbiaK6EHaew5gkVwP lMJJJhwwFAlPfYT0ThsVl/kpaOjptyDbRWxyGLac+nLXvVai6ElJM7797ZWbwdZh a7TYUA6Y0JPr1ciVcFpipslXkOKzq0GjPPSuQ9+Q57LGWoJX0Z1rravAS7uzFhNb NMgQebnJ5efvMRO8DCDUWiIn22VBR3seL3lBS8sf0Pj+lRCBHJ8usJf/MkfWZiAu QyQ1/EnDao/3wPD0prOBgx096bFMWnuA+YfBYcnb6SASpYwYGTqZU/vp6M1ORWnv xdXvEYEfeq+RabaqvZ4MN0eD75X7K+nbhWhlWuOjVd59E57UN018gdC2DWMAEQEA AYkCPAQYAQoAJhYhBPomynhL4YiSfyK5n2Vw6gEUb3NUBQJhTLuQAhsMBQkDwmcA AAoJEGVw6gEUb3NUSrwQAIP/teiFN0X+N9vzmWHv/q8UPVdGHRRtKD1yb5v1t3sL e59siXcB0j31Jc9aSJG/4nysxW5bvzC05Vr+cVzEfyWoL7PWulDFeQthJqlWzHt2 fDCLN7WGD2pdHKyX6JioZddpGkPJFKgQwlqKUu/dPpRGh9rioQndE6lYF+qZX8FQ GzSyoIpexpE9nTT8I+IkzJs/mXIGc519iONhxBLbo0Rdcvqqv+4dyzsicA3XHGg5 wI5gTiN+yDZ5cmhvBZznusYcSQBHhy8CHA8+0Q0eGQrRF34QgUyuzKMC2FQmyaBJ JpcUija5sEgjhV0JsapVPj6zN9NUfFRu4LUhNQ6CCo+5fLVtlxYdTJb9WFut8GNt U3pAXm/Yn4//YorlscK6lunLGB4mTjkFNZ2uLFZ0Uzfr1kT0mHUsXzXUT47RUCWd Y8ckrNsPp4oWO9Nn9Vv1Grn6yM7DZTzvcCErEfnFJ+YbI+uTMMjoozmUt8ZUbWof 41lhExZTlYM/5U1ER2ZXG9PCcJWitrCdrBW9+KR8vlgWiC+6YkeXVntJmWaqOsvj JCScJ0W1YxO5myMH3rvVfQTri7aULeEbHKr7zU4dS1orXWSevxdxgFGScqxUDZS8 aUvO5JVee+at/SpXPo+KMQI4JgxhOxlYmAw48ALmhMD6NhKiJPKTRlhw2sfnnRuF =CX7T -----END PGP PUBLIC KEY BLOCK----- Edgar Toernig Iñaki García Etxebarria -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org mQGiBD2XPugRBAD+XjKGe0Nks35boWJNc4ouoXd8kvpCAgb+9KK+yDRMtwkX3gx6 Sjwy3STJ4t5PWEvms4yulsICUnJGGDEMZVHbNoWdAKStSGy9/jKHOcH2esVP6qt5 wvd0ITeS/ZLumZTxc8XqhW4KOvUhle8KkzEubrB6MFv6xGQ/wiN+3DXP/wCgrfbr tr+lmHLsPytAHRCBKVjC+R8D/0d2enQ/Aj0Br5Bh2m+X2a8/OAzisIisKOEM+oQi GbPyIxnvLTwDT9oBEYHBrU0xuJ2UmgixsaDWH7QTGcUD495KGH8PuAvrDFgQBENV DDxVJLSSBwB+/eb4oCoIVdIgQ36Gc4yeaFKT4xbHJkV7Co1sEJ/+Qlfck8CX3QAO fRksBADim1EH9To+LGlC5Z9T9+Ic4b7pSuJWFRfsyD8HIcuKGh5lg5bxJna836hP 1KhvWAaSBedoHfSn+FY8lOfqHwxEHcF1fWGtWzgX7H2vqzEKjVW1lWW+BWCo7+j9 2lQ6HpuAL/doc69YNiMRE1YIcaijmqm89sAp4PLpchyWtcuX1rQrScOxYWtpIEdh cmPDrWEgRXR4ZWJhcnJpYSA8Z2FyZXR4ZUBnbXgubmV0PohXBBMRAgAXBQI9lz7o BQsHCgMEAxUDAgMWAgECF4AACgkQF0Bm+G3HG/2GyQCffKIZlsvQcD+ny9a5cbR5 xWzTzokAoJkGxq0R0PGgRdfA8DH28UhJ26iCuQENBD2XPusQBAC97oqKo+v6bIrl won6CtLSuCcgm6XHY/rOEpTqbwckz5MiM23kisP8uc4mq1xqA84K8JcUu60LLCuy w7EmyAg9CSjN0MdACLPeMjmSP29afgwDjSNDVpeJ9MyNFWxF67OtOu955hzxK0Kt 6bjR4CeXDAhJBtoUfDrMrmmfOylA0wADBQP+L7f96E9zfK01WceOPwsQG7i493Qu CDSniRGdMKz9mjxIHBy2l8zZ8EDwHil2AEadyLHM9iWPt1t0ijN+Gy+7wfhuTxsM 3axt7kBQvPHeuKj1hKIJt8oLLd2r7dRFV4gY13wpQSYy6j/NsWu/7/Pf5Jwn5fIo BjTw2/12aBGeDeeIRgQYEQIABgUCPZc+6wAKCRAXQGb4bccb/U6cAJ9w+wem13KY BcX0Bl723wDsv2co4gCeOsGzFWIojZ28HoeKLuhRabFkvRM= =pU7d -----END PGP PUBLIC KEY BLOCK----- Michael H. Schimek -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.0.7 (GNU/Linux) mQGiBD37IFERBAD0O7OMo2FYlaZLZW2/Se6h2Tnw3YiqRYrg4H/BXMB0hWjd4Zvs 6DIXXoMVfMrmP3/y7JT2F9J+V48S/9Z8JmyPCpu3+0lhDWmOdypkJ1YMxSB8ntOk jX79yZhTuPyRV0/ylJizE7sKzSDU/EdEvOnwrHD/Sq+5vOmj1OAPFZgCIwCglTcw fn30Z/kcKNWHstOf36aKG/kEAMUyuM041x/zUDWDPwDxD4XZVEcInwwuB859vqx+ 1GcQB0ySGC1bKMmjZzGnbYJXdCeUPm8AwRaO/c3AiAZrKpWeQX85toroNZdsaw9l CxawPCb1NNc9N1G4IIcjeNnNytoj402FMn/JPP/NhL7OIZ5pJyJ/LvxQ7UFozbDT sTsgA/4r9f9M0XTJl5iA3sMO3vS9Rlsl5ymV5jszYm2dRm+FHB4R1vFaHQ1h3gaH HTaSZyYm9ZumaybMeTr/QPmaF6RtutJVxrIl1ZX42HRaYIeX4CDtdWxOmGN7upon ZzQTE+UqZyTDnvF6fefJVyBJTl5CLP+WcLTQHCgKWLHjFJaWsrQzTWljaGFlbCBI LiBTY2hpbWVrIDxtc2NoaW1la0B1c2Vycy5zb3VyY2Vmb3JnZS5uZXQ+iFkEExEC ABkFAj37IFEECwcDAgMVAgMDFgIBAh4BAheAAAoJEC7lEHu9dNho1wMAnR35l+pg yBmdzYI5j9Esna+gozpkAKCCjYqjiMtb54qtNuo5jXRDb62apLkBDQQ9+yBfEAQA kea1haOdrwx8eEfSQ1gbEci2/w90UaINAC/LtQJBSyYSe6keCyXD2FpHt7Vmhl0S sOn3BoKjKZrDYLCYPAINziOIxLToXgtd3MZWgpPZTFQay7ZTJlvx3R7k1Kmb53vX pluEQchPe4xR619OumLXUrVxpbD6Q9d3ktFKyIAe83sAAwYD+wYeymullsVcpIJ9 UcKcWzQ56wSv3iwFTI9HNgzYfa5Z1Efb40Ro1za6uq/XQqxq7uCYNNI3rOoH5lLB IxIcG5Uxy2ARPUksWa956KLg07I4cm+ihkLteN7dLLtTxcVhiHJFCoprhTJT8SMM xfhbbLEwBC1+SeyyQy4gYxTzK4N0iEYEGBECAAYFAj37IF8ACgkQLuUQe7102GhA CACeMkBxKvu59js7H+T+s6Z23uEPUDoAoJCRRWwibRau+hAQz6JWOxUl9NJJ =PQod -----END PGP PUBLIC KEY BLOCK----- Tom Zoerner Local Variables: coding: utf-8 End: zvbi-0.2.44/BUGS000066400000000000000000000016771476363111200132550ustar00rootroot00000000000000$Id: BUGS,v 1.31 2008-08-19 10:04:36 mschimek Exp $ * v4l and v4l2 i/o can ignore timeout parameter > maybe a sleep() loop works, but what about timestamping? * vbi_page.text[1056] < 26 * 41. D'oh! (fixed in 0.3) * timestamp routines assume timeval.tv_sec and .tv_usec are signed. * Doxumentation needs compile instructions and more examples. * Make sure test apps flush their output buffers once per frame for real time output. * strerror() isn't thread-safe. * In src/raw_decoder.c/decode_pattern() and the stupidly named vbi3_raw_decoder_decode() if the driver does not provide line numbers but we find exactly one CC line per field we should make up line numbers to distinguish fields. * test: decode output differs from sliced2pes | decode output. Probably have to flush the PES demux. * dvb-t/de-Berlin/773-551-VOX-20041103.pes.bz2: libzvbi:dvb_demux:line_address: Illegal line order: 10 <= 333 ? * Sometimes src/version.h isn't updated. zvbi-0.2.44/COPYING.md000066400000000000000000002315471476363111200142250ustar00rootroot00000000000000**zvbi/libzvbi, https://github.com/zapping-vbi/zvbi** ======================================================================= **Unless otherwise specified below, zvbi files have the following copyright and license:** Copyright (c) 1995-1997 Martin Buck \ Copyright (c) 1998-1999 Edgar Toernig \ Copyright (c) 1999 Paul Ortyl \ Copyright (c) 2000-2013 Michael H. Schimek Copyright (c) 2002 Dave Chapman Copyright (c) 2002 Gerd Knorr Copyright (c) 2002-2004 Tom Zoerner \ Copyright (c) 2003 James Mastros Copyright (c) 2003-2007 Mark K. Kim \ Copyright (c) 2003-2007 Mike Baker \ Copyright (c) 2003-2007 Timecop \ Copyright (c) 2022-2025 Ileana Dumitrescu \ License: GPL-2+ ======================================================================= **All files in examples/\* have the follwing copyright and license:** Copyright (c) 2005-2009 Michael H. Schimek \ License: BSD-2-Clause ======================================================================= **Files in po/\* have the follwing copyright and license:** po/de.po Copyright (c) 2000-2002 Michael H. Schimek po/es.po Copyright (c) 2000 Joseba García Etxebarria \ Copyright (c) 2000-2001 Iñaki García Etxebarria \ po/fr.po Copyright (c) 2001-2002 Christian Marillat \ po/it.po Copyright (c) 2002-2004 Pino Toscano \ po/nl.po Copyright (c) 2001 Ime Smits \ Copyright (c) 2001 Reinout van Schouwen \ Copyright (c) 2001 Guus Bonnema \ po/pl.po Copyright (c) 2000 Paweł Sakowski \ po/sv.po Copyright (C) 2001 Henrik Isacsson \ License: GPL-2+ ======================================================================= **All files in src/\* have the follwing copyright and license:** Copyright (c) 1998 Manish Singh \ Copyright (c) 1999-2008 Michael H. Schimek Copyright (c) 2000 Stefan Westerfeld \ Copyright (c) 2000-2003 Iñaki G. Etxebarria \ Copyright (c) 2003-2004 Tom Zoerner \ License: LGPL-2+ ======================================================================= **The files src/dvb/dmx.h and src/dvb/frontend.h have the following copyright and license:** Copyright (c) 2000 Andre Draszik \ Copyright (c) 2000 Holger Waechtler \ Copyright (c) 2000 Marcus Metzler \ Copyright (c) 2000 Ralph Metzler \ License: LGPL-2.1+ ======================================================================= **The files src/packet-830.\* and src/pdc.\* have the following copyright and license:** Copyright (c) 2000-2004 Michael H. Schimek \ License: GPL-2 ======================================================================= **The files src/ure.\* have the following copyright and license:** Copyright (c) 1997-1999 Computing Research Labs, New Mexico State University Copyright (c) 1997-1999 Iñaki García Etxebarrria \ Copyright (c) 1997-1999 Michael H. Schimek License: MIT ======================================================================= **The file src/videodev2k.h has the following copyright and license:** Copyright (c) 1999-2007 Bill Dirks \ Copyright (c) 1999-2007 Justin Schoeman Copyright (c) 1999-2007 Hans Verkuil \ License: GPL-2+ or BSD-3-Clause ======================================================================= **The files src/strptime.\* have the following license:** License: LGPL v2.1+ =======================================================================

GNU GENERAL PUBLIC LICENSE

Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow.

GNU GENERAL PUBLIC LICENSE

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w` and `show c` should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w` and `show c`; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. \, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. =======================================================================

GNU LIBRARY GENERAL PUBLIC LICENSE

Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.]

Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one.

GNU LIBRARY GENERAL PUBLIC LICENSE

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. \, 1 April 1990 Ty Coon, President of Vice That's all there is to it! =======================================================================

GNU LESSER GENERAL PUBLIC LICENSE

Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.]

Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run.

GNU LESSER GENERAL PUBLIC LICENSE

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. \, 1 April 1990 Ty Coon, President of Vice That's all there is to it! =======================================================================

The 2-Clause BSD License Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =======================================================================

The 3-Clause BSD License Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =======================================================================

The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. zvbi-0.2.44/ChangeLog000066400000000000000000003270361476363111200143440ustar00rootroot000000000000002025-03-10 Ileana Dumitrescu * Release 0.2.44 * configure.ac: Add comments to help eith debugging gettext. * doc/Doxyfile.in: Bump version from 1.9.4 to 1.9.8. * src/conv.c: Check src_length to avoid an unitinialized heap read. * src/conv.c, src/io-sim.c, src/search.c: Avoid integer overflow leading to heap overflow. * src/export.c, src/misc.*: Use standard va_copy(), not GNU __va_copy(). * src/teletext.c: Fix accidental G3 character modification. * src/vbi.c: Add support for a larger range of framerates, from 12.5fps to 60fps. 2024-12-03 Ileana Dumitrescu * Release 0.2.43 * configure.ac: Add options to disable tests and examples. Move sincos function check to AC_CHECK_FUNCS. Check for Windows host_os. Check if we have winsock2.h. Remove generated files from check-in. * contrib/ntsc-cc.c: Only use fd_set if needed. * src/Makefile.am: Replace strptime implementation for Windows platforms. * src/io: Rename to inout to prevent conflict with Window's io.h, and update for Windows builds. * src/dvb_mux.c: Fix invalid array subscript in encode_stuffing. * src/format.h, src/vbi.c: Fix UB in vbi_transp_colormap. * src/idl_demux.c: Correct the behaviour of dummy byte removal. * src/lang.c: Fix signed integer overflow in vbi_teletext_unicode. * src/misc.h: Do not redefine strncpy for Windows platforms. * src/page_table.c: Add fallbacks in case ffs is missing. * src/pdc.c, examples/pdc2.c, test/test-pdc.h: Use _mkgmtime instead of timegm on Windows. * src/pdc.c: Add wrappers for un/setenv for Windows. Use _POSIX_C_SOURCE. * src/pdc2.c, test/date.c: Use localtime_s instead of localtime_r for Windows. * src/pdc.h, examples/pdc2.c, test/proxy-test.c: Use __STDC_WANT_LIB_EXT1__ for Windows platforms. * src/strptime.c: Add FFmpeg striptime definition. * test/Makefile.am, examples/Makefile.am: Make testsuite available for Windows platforms. * test/test-common.cc, test/test-hamm.cc: Define function mrand48 for Windows platforms. * test/date.c: Convert Unix time to Windows system time. * test/decode.c, test/test-pdc.cc, examples/pdc2.c: Use gmtime_s instead of gmtime_r for Windows platforms. * test/*: Update and add new test files for Windows platforms. * test_windows.sh: Add testing script for Windows platforms. 2023-08-25 Ileana Dumitrescu * Release 0.2.42 * configure.ac: Release 0.2.42. * NEWS: Add 0.2.42 release info. * po/*.po: Update Project-Id-Version. * po/Makefile.in: Release 0.2.42. * src/libzvbi.h: Update version number macros. * src/version.h: New version number. * src/io-v4l.c: Fix truncated string compiler warning. * daemon/proxyd.c: Remove warning 'warn_unused_result' for dup(). * src/proxy-msg.c: Remove warning 'warn_unused_result' for write(). * examples/wss.c: Remove warning 'warn_unused_result' for write(). * contrib/ntsc-cc.c: Remove warning 'warn_unused_result' for fread(). * src/packet.c: Apply patch to consider ERASE_PAGE flag with single page transmissions. * src/export.c: Fix build warning for multiple param documentation sections. 2023-02-13 Ileana Dumitrescu * Release 0.2.41 * configure.ac: Release 0.2.41. * NEWS: Add 0.2.41 release info. * po/*.po: Update Project-Id-Version. * po/Makefile.in: Release 0.2.41. * src/libzvbi.h: In libzvbi.h, remove #include version.h and replace with version number macros (Closes Issue #40). * src/version.h: New version number. 2023-02-07 Ileana Dumitrescu * Release 0.2.40 * configure.ac: Release 0.2.40. * COPYING.md: Copyright year 2023. * NEWS: Add 0.2.40 release info. * po/*.po: Update Project-Id-Version. * po/Makefile.in: Release 0.2.40. * README.md: Copyright year 2023. * src/libzvbi.h: Remove generated file comment and version number macros, include version.h (Closes Issue #35). * src/version.h: Fix version number (Closes Issue #34). 2022-12-21 Ileana Dumitrescu * Release 0.2.39 * build-aux/autogen.sh: Prevent autogen.sh from running configure immediately after by default (Closes Issue #32). * configure.ac: Release 0.2.39. * contrib/atsc-cc.c, contrib/ntsc-cc.c: Fix indexing for info struct (Closes Issue #9). * NEWS: Add 0.2.39 release info. * po/ka.po, po/LINGUAS: Add Georgian language translation. * po/*.po: Update Project-Id-Version. * po/Makefile.in: Release 0.2.39. * README.md: Update documentation for running autogen.sh and configure separately. * src/xds_demux.c: Fix indexing for subpacket (Closes Issue #9). * test/test-hamm.cc: Fix narrowing conversion compiler warnings during testing (Closes Issue #31). 2022-11-30 Ileana Dumitrescu * Release 0.2.38 * doc/Doxyfile: Removed - generated during build (Closes Issue #30). * doc/Doxyfile.in: Ran doxygen -u to remove obsolete tags (Closes Issue #29). * src/io-v4l.c: Replace deprecated function readdir_r with readdir (Closes Issue #23). * src/xds_demux.c: Fix upper loop bound (Closes Issue #24). * contrib/atsc-cc.c: Fix vbi_char text array size (Closes Issue #27). Fix upper loop bound (Closes Issue #28). Type cast to uint8_t in buf (Closes Issue #26). * contrib/ntsc-cc.c: Fix upper loop bound (Closes Issue #25). * configure.ac: Release 0.2.38. * NEWS: Add 0.2.38 release info. * po/*.po: Update Project-Id-Version. * po/Makefile.in: Release 0.2.38. 2022-10-11 Ileana Dumitrescu * Release 0.2.37 * configure.ac: During cross-compile, include path to and link with X11 if available or set X_DISPLAY_MISSING=1 if not available (Closes Issue #21), release 0.2.37. * src/libzvbi.h: Change encoding to UTF-8. * src/misc.h: Change encoding to UTF-8. * src/search.h: Change encoding to UTF-8. * src/vbi.h: Change encoding to UTF-8. * NEWS: Add 0.2.37 release info. * po/*.po: Update Project-Id-Version. * po/Makefile.in: Release 0.2.37. * doc/Doxyfile: Release 0.2.37. 2022-09-29 Ileana Dumitrescu * Release 0.2.36 * NEWS: Add 0.2.36 release info. * README.md: Release 0.2.36. * COPYING.md: Update copyright info for po files, spacing. * po/ChangeLog: Removed. * po/de.po: Update Project-Id-Version, Report-Msgid-Bugs-To. * po/en@boldquot.po: Update Project-Id-Version, Report-Msgid-Bugs-To. * po/en@quot.po: Update Project-Id-Version, Report-Msgid-Bugs-To. * po/es.po: Update Project-Id-Version, Report-Msgid-Bugs-To. * po/fr.po: Update Project-Id-Version, Report-Msgid-Bugs-To. * po/it.po: Update Project-Id-Version, Report-Msgid-Bugs-To. * po/nl.po: Update Project-Id-Version, Report-Msgid-Bugs-To. * po/pl.po: Update Project-Id-Version, Report-Msgid-Bugs-To. * po/sv.po: Update Project-Id-Version, Report-Msgid-Bugs-To. 2022-09-21 Ileana Dumitrescu * .gitignore: Unignore some test scripts. * data/networks.dtd: New file. * data/networks.xml: New file to use with network-table.pl. * src/caption.c: Fix array bounds checks in xds_separator (Closes Issue #3 and Issue #16). * src/network-table.h: Update networks table. 2022-09-20 Ileana Dumitrescu * COPYING: Removed. * COPYING.LIB: Removed. * COPYING.md: Update COPYING to markdown format and include all license texts. 2022-09-12 Ileana Dumitrescu * README: Removed. * README.md: Update README to markdown format. 2022-09-09 Ileana Dumitrescu * AUTHORS: Add new maintainer PGP key. 2022-09-07 Ileana Dumitrescu * contrib/zvbi-atsc-cc.1: Escape hyphen characters in contrib man pages. * contrib/zvbi-ntsc-cc.1: Escape hyphen characters in contrib man pages. * daemon/Makefile.am: Change LIBZVBI_CHAINS_PATH to libzvbi-chains.so.0. * daemon/proxyd.c: Fix spelling error in log message. * src/proxy-client.c: Fix spelling error in log message. 2022-09-06 Ileana Dumitrescu * contrib/atsc-cc.c: Fix incomplete file writes by flushing writes. * src/caption.c: Fix XDS debug compile-time error (Closes Issue #17). * src/proxy-msg.c: Declare link_name string later in the function when the size is known. * test/test-packet-830.cc: Fix bug that was setting a pointer instead of a value. 2022-09-02 Ileana Dumitrescu * .gitignore: Update with autogenerated files. * autogen.sh: Use newer gnome autogen script, fix build issue. * configure.ac: Add getopt_long to AC_CHECK_FUNCS. * build-aux/autogen.sh: Use newer gnome autogen script. * m4/autogen.sh: Removed. * src/Makefile.am: Change configure.in to configure.ac. 2022-08-30 Ileana Dumitrescu * .gitignore: New file. * ABOUT-NLS: Link to GNU website. * aclocal.m4: New file from aclocal. * config.h.in: New file generated from configure.ac. * configure.ac (AM_GNU_GETTEXT_VERSION): Bump to 0.21. * configure.in: Replace with configure.ac. * INSTALL: Text updates. * Makefile.in: New file from automake. * build-aux/*: New files after setting AC_CONFIG_AUX_DIR to build-aux. * contrib/Makefile.in: New file generated by automake. * daemon/Makefile.in: New file generated by automake. * doc/Makefile.in: New file generated by automake. * examples/Makefile.am: Change INCLUDES to AM_CPPFLAGS. * examples/Makefile.in: New file generated by automake. * m4/gettext.m4: Upgrade to gettext-0.21. * m4/host-cpu-c-abi.m4: New file, from gettext-0.21. * m4/iconv.m4: Upgrade to gettext-0.21. * m4/intlmacosx.m4: New file, from gettext-0.21. * m4/lib-ld.m4: Upgrade to gettext-0.21. * m4/lib-link.m4: Upgrade to gettext-0.21. * m4/lib-prefix.m4: Upgrade to gettext-0.21. * m4/nls.m4: Upgrade to gettext-0.21. * m4/po.m4: Upgrade to gettext-0.21. * m4/progtest.m4: Upgrade to gettext-0.21. * m4/Makefile.am (EXTRA_DIST): Add the new files. * m4/Makefile.in: New file generated by automake. * m4/aclocal-include.m4: Removed. * m4/codeset.m4: Removed. * m4/glibc2.m4: Removed. * m4/glibc21.m4: Removed. * m4/intdiv0.m4: Removed. * m4/intl.m4: Removed. * m4/intldir.m4: Removed. * m4/intmax.m4: Removed. * m4/inttypes-pri.m4: Removed. * m4/inttypes_h.m4: Removed. * m4/isc-posix.m4: Removed. * m4/lcmessage.m4: Removed. * m4/libtool.m4: New file from autoconf. * m4/lock.m4: Removed. * m4/longdouble.m4: Removed. * m4/longlong.m4: Removed. * m4/ltoptions.m4: New file from autoconf. * m4/ltsugar.m4: New file from autoconf. * m4/ltversion.m4: New file from autoconf. * m4/lt~obsolete.m4: New file from autoconf. * m4/printf-posix.m4: Removed. * m4/size_max.m4: Removed. * m4/stdint_h.m4: Removed. * m4/uintmax_t.m4: Removed. * m4/ulonglong.m4: Removed. * m4/visibility.m4: Removed. * m4/wchar_t.m4: Removed. * m4/wint_t.m4: Removed. * m4/xsize.m4: Removed. * po/Changelog: New file. * po/Makefile.in: New file using GNU gettext. * po/Makefile.in.in: Update using gettext-0.21. * po/Makevars: Update using gettext-0.21. * po/Makevars.template: Removed. * po/Rules-quot: Update using gettext-0.21. * po/de.po: Update using gettext-0.21. * po/en@boldquot.header: Update using gettext-0.21. * po/en@boldquot.po: Update using gettext-0.21. * po/en@quot.header: Update using gettext-0.21. * po/en@quot.po: Update using gettext-0.21. * po/es.po: Update using gettext-0.21. * po/fr.po: Update using gettext-0.21. * po/insert-header.sin: Update using gettext-0.21. * po/it.po: Update using gettext-0.21. * po/nl.po: Update using gettext-0.21. * po/pl.po: Update using gettext-0.21. * po/remove-potcdate.sin: Update using gettext-0.21. * src/Makefile.in: New file generated by automake. * src/dvb/Makefile.in: New file generated by automake. * test/Makefile.in: New file generated by automake. 2017-03-18 * test/test-dvb_mux.cc: Silence gcc 6 warnings, SF patch #16 by Pyro. 2016-10-17 * test/test-pdc.h: Fixed operator equal, SF bug #202. 2016-06-04 * src/wstfont2.xbm: Fixed the glyph for U+0111 Latin small letter d with stroke, which exceeded its bounding box, applying SF patch #18 by Stefan Pöschel. Also fixed the italicized glyphs for the same character, U+00A9 Copyright sign, U+00AE Registered sign, U+00B6 Pilcrow sign, U+00F0 Latin small letter eth, and U+0374 Greek numeral sign. 2016-02-08 * src/lang.c (vbi_teletext_composed_unicode): Bug fix, SF patch #17 by Stefan Pöschel. Display at sign instead of asterisk if invoked by X/26 triplet 0x10 'G0 character without diacritical mark'. 2014-02-18 * src/packet.c (parse_28_29): SF bug #198: Faulty logic in TTX Level 2.5, 3.5 DRCS download page parser. 2013-12-20 * configure.in: Added a replacement for AC_PATH_XTRA when cross compiling. Disable check scripts when cross compiling. New pthread check for MinGW compatibility. Do not build the VBI proxy if we do not build the V4L interface. * src/dvb_demux.c (decode_timestamp): Disabled check to work around invalid mark transmitted on Dantoto Racing found by Devrim Ayyildiz. * src/caption.c (caption_command): SF bug #195: Mid-row codes are set-at spacing attributes. Backgr. Attr. codes ditto. * contrib/zvbi-atsc-cc.1, contrib/atsc-cc.c: Added a stream relative timestamp option. * contrib/atsc-cc.c (cc_format_row): Bug fix: Ignored background attribute codes and printed a zero byte in their place. (cc_feed): Bug fix: Did not separate XDS data from CC channel 3 & 4. (decode_cc_data): Bug fix: Crashed if no data on some frames. 2013-08-28 * Release 0.2.35. 2013-08-28 * test/date.c (set_time): Applied bug fix patch by André Draszik. * contrib/atsc-cc.c: Applied patch by André Draszik, removing stale include aio.h which is not available in uClibc. * test/export.c (vbi_decoder_feed): * test/date.c (decode_function): * test/caption.c: Const cast because vbi_decode() second arg sliced data is incorrectly defined as mutable. * src/ure.c: Added include wchar.h because clang didn't recognize wint_t. (ure_write_dfa): printf format fix. * src/teletext.c (enhance): Replaced nested function flush() and flush_row() for portability. Closes SF patch #16, incompatibility with clang. * src/sampling_par.c (_vbi_sampling_par_valid_log) (_vbi_sampling_par_permit_service) (_vbi_sampling_par_from_services_log): Corrected printf format for videostd_set type from %x to %lx. * src/proxy-msg.c (vbi_proxy_msg_v4l_ioctl) (vbi_proxy_msg_v4l2_ioctl): Changed the ioctl request type to unsigned int because clang pointed out the v4l/v4l2 ioctl codes exceed int range. * src/packet.c (same_header, same_clock): Const arg fix. (parse_28_29): Replaced nested function bits() for portability. Closes SF patch #16, incompatibility with clang. * src/libzvbi.h: * src/io-sim.h: Uncommented vbi_capture_sim_load_caption() declaration because the function is needed for make check. * test/sliced.c (read_more): * src/export.c (vbi_export_mem): (vbi_export_alloc): Const cast clarification. * src/exp-gfx.c (ppm_export): Temp buffer was misdeclared as const. * src/conv.c (strndup_iconv_to_ucs2): Silence iconv mutable src string warning. * src/vbi.c (vbi_classify_page): * examples/network.c (handler): * daemon/proxyd.c (vbi_proxyd_take_message): Strings shouldn't be of type uchar. 2013-07-11 * Release 0.2.34. 2013-07-10 * README: Updated the URLs. * src/macros.h: Documentation improvements. * contrib/zvbi-atsc-cc.1: Corrections and clarifications. * src/dvb_demux.c, src/lang.c, src/hamm.c, src/hamm.h, src/packet-830.c, src/idl_demux.c, src/packet.c, src/pfc_demux.c, src/vbi.c: Documentation improvements. * src/network-table.h: Fixed and updated. * src/Makefile.am: Convert network-table.h to Latin-1 for compatibility with older versions. 2013-07-03 * contrib/atsc-cc.c: Bug fix: Didn't work right with CC data only on one field or streams omitting some PTS. 2013-07-02 * contrib/README: Added dvbsubs. * contrib/dvbsubs.c, contrib/dvbsubs.h: Some compile fixes and new code. * src/teletext.c: Corrected an apparent array overflow, SF bug #183. * contrib/ntsc-cc.c, src/io-v4l.c, src/io-dvb.c: Compile fix: Applied SF bug #188 patch by Alex Chiang to include sys/stat.h to define S_ISCHR on Debian/Ubuntu. * src/exp-gfx.c: Compile fix: Applied SF patch #13 for compatibility with libpng 1.5 and later. * po/pl.po: Applied SF bug #192 patch by Jakub Wilk to fix UTF-8 encoding. * src/exp-txt.c (vbi_print_page_region): Accidentally left a debugging printf enabled. * src/Makefile.am: The network-table.h generator needs a rewrite. * examples/pdc2-test1.sh: Didn't work with dash. * contrib/Makefile.am: Disabled zvbi-dvbsubs due to compile errors. * src/exp-gfx.c, src/misc.h: The cpu target checks need a rewrite. Disabled for now to get rid of annoying compiler warnings. * src/teletext.c (enhance): Bug fix: Flush attributes before we reset the active column for a redundant set-active-row triplet. * src/teletext_decoder.h (raw_page): * src/packet.c (lop_parity_check, vbi_decode_teletext): Bug fix: Handle X/26 fallback characters with even parity. Thanks to Marton Balint for a sample and SF patch #15. 2009-12-14 * contrib/Makefile.am (noinst_PROGRAMS): Added zvbi-dvbsubs. * contrib/dvbsubs.c, contrib/dvbsubs.h: Added. 2009-06-10 * src/io-v4l2k.c (vbi_capture_v4l2k_new): Bug fix: Attempt to log error after deleting capture struct with pointer to log function. * src/io-v4l2k.c (print_vfmt): Bug fix: Missing _vbi_log_printf() parameter. 2009-05-27 * src/cc608_decoder.c: Renamed a few public symbols to reflect the experimental nature of the module. (stream_event, display_event): The event structs changed slightly. (CC608_DECODER_LOG_INPUT): New test switch. * src/cc608_decoder.h: Added experimental _vbi_event_cc608_page and _vbi_event_cc608_stream structs. * src/event.h: Added experimental _VBI_EVENT_CC608 and _VBI_EVENT_CC608_STREAM for test/caption.c. * src/Makefile.am (libzvbi_la_SOURCES): Added cc608_decoder.c, cc608_decoder.h. (libzvbi_la_SOURCES): Added event.c, event-priv.h. * src/event.c, src/event-priv.h: New helper functions for cc608_decoder.c. * src/io-sim.c (vbi_capture_sim_load_caption), test/cc608-test-stream.dtd: Removed the unneeded long element names. Changed channel numbers to base one as in EIA 608. Added a ts entity. * test/cc-test-stream.dtd: Renamed to cc608-test-stream.dtd. * test/sliced.c (capture_stream_sim_load_caption): Now available in libzvbi 0.2 too. * test/caption.c: Rewrote the whole thing and added an option to test the new _vbi_cc608 decoder. * test/decode.c (caption): CC dump code replaced by _vbi_cc608_dump(). 2009-03-21 * src/cc608_decoder.c, src/cc608_decoder.h: New Closed Caption decoder based on contrib/atsc-cc.c added to CVS. 2009-03-13 * src/pdc.h, examples/network.c (main): Cosmetic changes. * test/decode.c: Enabled Teletext packet 8/30/1 decoding because the required low-level functions are in the library now. * test/Makefile.am: Commented out the exoptest on explist and test-unicode on unicode dependencies because of problems with make distcheck. * examples/Makefile.am (noinst_PROGRAMS): Added pdc2. (TESTS): Added pdc2-test1.sh. * src/pdc.c: Doxumented examples/pdc2.c. * examples/pdc2-test1.sh: New test for pdc2.c. * examples/pdc2.c: Renamed from pdc1.c, improved and checked against the examples in EN 300 231. * examples/pdc1.c: Renamed to pdc2.c and replaced by a simpler example just demonstrating how to capture Program IDs. 2009-03-07 * test/decode.c: Corrected usage message, enabled packet 8/30/1 decoding. 2009-03-05 * test/test-pdc.cc: Replace time_min()/time_max() by macros. * test/Makefile.am (TESTS, check_PROGRAMS): Added test-pdc. 2009-03-04 * test/Makefile.am (noinst_PROGRAMS): Added date tool. * configure.in: Added tm_gmtoff check for test/date.c. * test/date.c: New test/demo/example of VBI_EVENT_LOCAL_TIME from branch 0.3. * examples/Makefile.am (noinst_PROGRAMS): Added pdc1. * examples/pdc1.c: New example for VBI_EVENT_PROG_ID. * src/vbi.h (vbi_decoder): Added a vps_pid field to check for PDC transmission errors. * src/vbi.c (vbi_event_enable, vbi_decode): The function now supports VBI_EVENT_LOCAL_TIME and VBI_EVENT_PROG_ID. * src/packet.c (station_lookup): The vbi_cni_type definition moved into network.h. (vbi_decode_vps): The function now sends a VBI_EVENT_PROG_ID if requested. (vbi_decode_teletext, parse_8_30): The function now sends a VBI_EVENT_LOCAL_TIME and VBI_EVENT_PROG_ID if requested. * src/event.h: Added VBI_EVENT_LOCAL_TIME, VBI_EVENT_PROG_ID, enum vbi_dst_state, struct vbi_local_time. Added local_time and prog_id fields to struct vbi_event. * test/decode.c: Permanently enabled VPS PDC decoding with vbi_decode_vps_pdc(). * test/Makefile.am (TESTS, check_PROGRAMS): Added test-packet-830. (test_vps_SOURCES): Replaced test-vps.c by test-vps.cc. * test/test-common.h, test/test-common.cc (memcmp_zero): Function added for test-packet-830.cc. * test/test-vps.cc, test/test-packet-830.cc: New unit tests from branch 0.3. * test/test-vps.c: Replaced by test-vps.cc. * doc/Doxyfile, doc/Doxyfile.in (FILE_PATTERNS): Added packet-830.h, pdc.h. * src/Makefile.am (libzvbi_la_SOURCES): Added network.h (LIBZVBI_HDRS): Added network.h, pdc.h, packet-830.h. * src/vps.h: A few modifications to make Doxygen happy. Renamed _vbi_decode_vps_pdc() to vbi_decode_vps_pdc(). All VPS functions are public now. * src/vps.c: Updated the doxumentation. (vbi_decode_vps_cni): Bug fix: Translation of CNI 0x0DC3 was backwards. (vbi_decode_vps_pdc, vbi_decode_dvb_pdc_descriptor): Removed the PIL check to support unreal dates and times. vbi_program_id.mi flag wasn't initialized. (vbi_encode_vps_pdc, vbi_encode_dvb_pdc_descriptor): vbi_program_id does not contain a broken down date and time anymore. Changed the PIL check to support unreal dates and times. * src/packet-830.h: A few modifications to make Doxygen happy. * src/packet-830.c: Updated the doxumentation and added a brief explanation of Packet 8/30. Replaced a vbi_bcd2bin() call because the function has insufficient precision in libzvbi 0.2. * src/misc.h: Added TIME_MIN and TIME_MAX macros for the PDC helper functions. * src/pdc.h: Updated the doxumentation. vbi_program_id.cni_type is back. * src/pdc.c: Updated the doxumentation. Commented out some code that would return not yet defined error codes. 2009-02-18 * src/sampling_par.c (_vbi_sampling_par_permit_service): Offset check disabled, pending repair. 2009-02-16 * test/decode.c: Enabled VPS and Teletext 8/30-2 PDC decoding. * src/vps.c, src/vps.h: Enabled PDC decoding for tests. * src/packet-830.c, src/packet-830.h, src/pdc.c, src/pdc.h: Added for PDC tests. * src/Makefile.am (libzvbi_la_SOURCES): Added packet-830.c, packet-830.h, pdc.c for tests. 2009-02-11 * test/exp-test.sh: Removed the VTX check because the VTX module was disabled in 0.2.28. * src/exp-txt.c (vbi_print_page_region): A debugging printf was accidentally enabled. * src/caption.c (update): Bug fix: Buffer overflow, patch by Helen Buus. * contrib/atsc-cc.c (init_capture_state): Use posix_memalign(), memalign() or malloc() as available. * configure.in: Added a memalign() and posix_memalign() check. Bumped version to 0.2.34, incremented .so revision. 2008-09-11 * test/unicode-out-ref.txt: Updated. * test/unicode.c: Print two more tables to reveal gaps in the Teletext composed character conversion. * src/lang.c: Bug fix: Teletext composed character table was incomplete, patch by Marian Ďurkovič. 2008-09-03 * Release 0.2.33. 2008-09-03 * contrib/atsc-cc.c: Include our libzvbi.h, not the installed one. * contrib/Makefile.am (bin_PROGRAMS): Compile zvbi-atsc-cc only if the Linux DVB interface is available. * configure.in: Added an ENABLE_DVB conditional to disable zvbi-atsc-cc in contrib/Makefile.am. Bumped version to 0.2.33. 2008-08-20 * Release 0.2.32. 2008-08-19 * contrib/atsc-cc.c: Fix: Segfaulted if no station name was given. * test/capture.c (main): If we output PES or TS, capture only the services we can actually encode, so we don't get an error from vbi_dvb_mux. 2008-08-17 * src/dvb/frontend.h, src/dvb/dmx.h: Updated. * contrib/README: Added atsc-cc info. * contrib/atsc-cc.c: Added. * contrib/Makefile.am (bin_PROGRAMS): Added atsc-cc.c. * configure.in: Bumped version to 0.2.32. 2008-07-26 * Release 0.2.31. 2008-07-22 * src/videodev.h, src/videodev2.h: Indentation pedantry. * src/hammgen.c: Minor typo. * src/ccfont2.xbm: Added a LGPLv2+ notice. * test/cc-test-stream.dtd: Changed the license to GPLv2+. * test/proxy-test.c, daemon/proxyd.c, daemon/chains.c: Changed the license to GPLv2+ with Tom's permission. * test/unicode-out-ref.txt: vbi_caption_unicode() fix. * src/lang.c (vbi_caption_unicode): Fixed conversion of latin small letter i with diaresis. * README: Line feed cosmetics. * configure.in: Bumped version to 0.2.31, incremented .so revision. 2008-03-05 * Release 0.2.30. 2008-03-05 * configure.in: Incremented .so version along with the return of vbi_unref_page(), vbi_is_cached() and vbi_cache_hi_subno(). * src/vbi.c (vbi_is_cached, vbi_cache_hi_subno): Bug fix: Restored these functions which were lost in 0.2.28. * src/cache.h (vbi_is_cached, vbi_cache_hi_subno): Bug fix: Restored these declarations which were lost in 0.2.28. Restored the Public/Private markers. 2008-03-01 * test/.cvsignore: Added ctest*, cpptest*. * test/unicode.c, test/ttxfilter.c, test/test-vps.c: * test/test-raw_decoder.cc, test/test-hamm.cc: * test/test-dvb_mux.cc, test/test-dvb_demux.cc: * test/test-common.h, test/test-common.cc, test/sliced2pes.c: * test/proxy-test.c, test/osc.c, test/glyph.c, test/export.c: * test/explist.c, test/decode.c, test/ctest.c, test/cpptest.cc: * test/cc-test-stream.dtd, test/capture.c, test/caption.c: Line feed cosmetics. * test/sliced.h, test/sliced.c: Resynched with branch 0.3. * src/vt.h: Resynched with branch 0.3, adding ttx_page_function_valid() and ttx_page_coding_valid() helpers. * src/misc.h: Resynched with branch 0.3, adding function attributes. 2008-02-26 * test/test-hamm.cc: Include stdlib.h and string.h to declare rand() and memset(). Refactored to clarify which functions are tested. * src/hamm.h: Include macros.h, not misc.h. * src/cache.c (_vbi_cache_put_page), src/cache.h: Resynched with corrections on branch 0.3. * configure.in: Bumped version to 0.2.30, incremented .so revision. 2008-02-24 * Release 0.2.29. 2008-02-24 * src/xds_demux.h, src/vbi.h, src/macros.h, src/io.h: * src/cache.h: ISO C89 does not permit a comma at the end of an enumerator list. * src/hamm.h (vbi_unpar8): ISO C89 does not permit an #if #cpu test (and it probably didn't work as intended anyway). * test/Makefile.am: Added strict ISO C89, C94, C99, and C++98 checks of the libzvbi header. * configure.in: Added a gcc -std check for test/ctest.c and test/cpptest.cc. * test/test-common.h, src/xds_demux.h, src/vps.h: * src/sliced_filter.h, src/sliced.h, src/sampling_par.h: * src/pfc_demux.h, src/page_table.h, src/misc.h: * src/macros.h, src/io.h, src/io-v4l.c, src/idl_demux.h: * src/hamm.h, src/export.h, src/exp-txt.c, src/exp-gfx.c: * src/dvb_mux.h, src/dvb_demux.h, src/conv.h, src/caption.c: * src/cache.h, src/bit_slicer.h: Rewrote the GCC __attribute__ wrapper macros for compatibility with strict ISO C. * src/exp-gfx.c (draw_row_indexed): Removed an unused parameter. * test/test-common.cc (test_malloc): * src/io-dvb.c: Muffle compiler warnings. * src/exp-html.c: Include teletext_decoder.h instead of vt.h to declare vbi_resolve_link(). * configure.in: Bumped version to 0.2.29, incremented .so revision. 2008-02-22 * Release 0.2.28. 2008-02-22 * src/teletext.c (enhance): Bug fix: Row color transparency toggling by display attribute triplet. * configure.in: Incremented .so version to reflect the src/hamm.c, src/bcd.h and src/exp-vtx.c changes. 2008-02-18 * examples/wss.c, examples/rawout.c, examples/network.c: Changed the license to a 2-clause BSD-style license. * src/xds_demux.h, src/xds_demux.c, src/wstfont2.xbm, src/wss.h, src/wss.c, src/vps.h, src/vps.c, src/vbi.c, src/trigger.h, src/trigger.c, src/teletext.c, src/tables.h, src/tables.c, src/sliced_filter.h, src/sliced_filter.c, src/sliced.h, src/sampling_par.h, src/sampling_par.c, src/raw_decoder.h, src/raw_decoder.c, src/pfc_demux.h, src/pfc_demux.c, src/page_table.h, src/page_table.c, src/packet.c, src/network-table.pl, src/macros.h, src/lang.h, src/lang.c, src/io-v4l2.c, src/io-sim.h, src/io-sim.c, src/io-bktr.c, src/intl-priv.h, src/idl_demux.h, src/idl_demux.c, src/export.h, src/export.c, src/event.h, src/exp-txt.h, src/exp-txt.c, src/exp-html.c, src/exp-gfx.h, src/exp-gfx.c, src/format.h, src/dvb_mux.h, src/dvb_mux.c, src/dvb_demux.h, src/dvb_demux.c, src/dvb.h, src/decoder.h, src/decoder.c, src/conv.h, src/conv.c, src/ccfont2.xbm, src/cc.h, src/caption.c, src/bit_slicer.h, src/bit_slicer.c, src/bcd.h: Changed the license to LGPLv2+. * src/search.h, src/search.c, src/vbi.h, src/vbi.c: Changed the license to LGPLv2+ with Iñaki's permission. Could not contact Edgar Toernig for permission but the file changed a lot and only traces of AleVT remain. * src/misc.h, src/misc.c: Changed the license to LGPLv2+ with Iñaki's permission. * src/proxy-msg.h, src/proxy-msg.c, src/proxy-client.h, src/proxy-client.c, src/io.h, src/io.c, src/io-v4l2k.c, src/io-v4l.c: Changed the license to LGPLv2+ with Tom's permission. * src/export.c: Disabled VTX export module. Improved documentation. * src/exp-vtx.c: Disabled for now because this code is licensed under GPLv2+ and cannot be linked with the rest of libzvbi, which is licensed under LGPLv2+. * src/proxy-msg.c: Include videodev.h because videodev2k.h won't do that anymore. * src/proxy-msg.c, src/chains.c: Define __s64 and __u64 for videodev2.h and videodev2k.h if not defined in asm/types.h. * src/videodev2.h, src/videodev.h: Replaced the file by a new uncopyrighted version because the original was copied from the Linux kernel sources which are, absent other declarations, licensed under GPLv2. * src/io.h: Added "deprecated" attribute to vbi_capture_dvb_new() function. * src/macros.h (_vbi_deprecated): New macro for src/io.h vbi_capture_dvb_new() declaration. * src/io-dvb.c: Rewrote this code and changed license to LGPLv2+. * configure.in: Updated site_def.h defaults. * src/vbi.h, src/vbi.c, src/search.c, src/teletext.c, src/packet.c: src/cache.c, src/vt.h changed. * src/Makefile.am (libzvbi_la_SOURCES): Added cache-priv.h, dlist.h. * src/bcd.h: Added vbi_bin2bcd(), vbi_bcd2bin() and vbi_bcd_digits_greater() for src/cache.c. * src/dlist.h, src/cache-priv.h: Added for src/cache.c. * src/cache.h, src/cache.c: Replaced by new Teletext cache code from branch 0.3 and changed license to LGPLv2+. * src/vt.h: Resynched with branch 0.3. * src/vt.h, src/teletext_decoder.h: Moved some definitions from vt.h to new file teletext_decoder.h, so I can include vt.h in cache-priv.h and cache-priv.h in teletext_decoder.h. Changed the license to LGPLv2+. Could not contact Edgar Toernig for permission but the file changed a lot and only traces of AleVT remain. * src/Makefile.am (libzvbi_la_SOURCES, LIBZVBI_HDRS): Added teletext_decoder.h. * src/hamm.c, src/hamm.h: Replaced the code from AleVT and changed the license to LGPLv2+. Added a new function vbi_ham24p(). * test/test-hamm.cc: Added a test for the new vbi_ham24p() function. * test/hamm.c, test/test-hamm.cc: Replaced hamm.c by test-hamm.cc. * test/Makefile.am (TESTS, check_PROGRAMS): Replaced hamm by test-hamm. Added test_hamm_SOURCES because the source is a C++ file. * src/Makefile.am: Added hammgen and hamm-tables.h rule. (BUILT_SOURCES, EXTRA_DIST, libzvbi_la_SOURCES): Added hamm-tables.h. * test/export.c: The --default-cs option now works with libzvbi 0.2 as well. * m4/autogen.sh: Fixed a typo. * Makefile.am (EXTRA_DIST): Added COPYING.LIB. * COPYING.LIB: Added. * README: Updated licensing information. Added IRC link. 2008-02-17 * test/sliced.c (capture_stream_new): Capturing from a Linux DVB device didn't work because we opened the buggy old interface and a sampling format check in test/sliced.c failed. * configure.in: Bumped version to 0.2.28, incremented .so revision. 2008-02-14 * Release 0.2.27. 2008-02-14 * test/test-unicode: New regression test for the Teletext and Closed Caption to Unicode conversion functions. * test/unicode-out-ref.txt: Reference output of test/unicode for the test-unicode make check. * test/unicode.c (main): Fixed vbi_caption_unicode() calls. Print Closed Caption extended characters. Test vbi_caption_unicode() boundary checks. * test/Makefile.am (TESTS, check_SCRIPTS): Added test-unicode. (EXTRA_DIST): Added unicode-out-ref.txt. * src/network-table.h (vbi_cni_table): Updated from TS 101 231 rev. 2008-02. 2008-02-12 * src/lang.c (vbi_caption_unicode): Bug fix: Did not convert special characters. 2007-12-03 * src/dvb_demux.c (demux_pes_packet): Bug fix: Did not skip start codes with invalid stream_id 0x00 ... 0xBB, looping forever. Discovered by Tom. * test/Makefile.am: Added test-dvb_demux.cc. * test/test-dvb_demux.cc: New regression test for start code bug. * configure.in: Bumped version to 0.2.27, incremented .so revision and added a strerror_r() check. 2007-12-02 * src/proxy-client.c: Fixed nasty bug: STDIN was closed after connect failure due to close() on uninitialized sock_fd. 2007-11-27 * Release 0.2.26. 2007-11-26 * src/xds_demux.h, src/pfc_demux.h, src/pfc_demux.c, src/idl_demux.h: Doxumentation fixes. * doc/Doxyfile.in (FILE_PATTERNS): Added pfc_demux.h. * src/xds_demux.h, src/vt.h, src/vps.h, src/sliced_filter.h, src/sliced.h, src/sampling_par.h, src/pfc_demux.h, src/page_table.h, src/misc.h, src/macros.h, src/io-v4l.c, src/idl_demux.h, src/export.h, src/exp-txt.c, src/exp-gfx.c, src/dvb_mux.h, src/dvb_demux.h, src/conv.h, src/caption.c, src/bit_slicer.h: Use _vbi_attribute macro instead of __attribute__ so we can safely disable it in libzvbi.h if there are compiler problems. * src/macros.h: Changed the dummy definitions of _vbi_nonnull, _vbi_format, _vbi_pure and _vbi_alloc because GCC 2.95 aborts with an error if __attribute__ has no parameters. * src/sampling_par.c, src/raw_decoder.c, src/misc.h, src/macros.h, src/lang.h, src/io-sim.c, src/hamm.h, src/exp-txt.h, src/exp-gfx.h, src/dvb_mux.c, src/dvb_demux.c, src/decoder.h, src/bcd.h: Renamed vbi_inline to _vbi_inline (private macro). * README, BUGS: Updated. 2007-11-25 * test/sliced2pes.c: Must include unistd.h to declare optarg. * src/export.h, src/misc.h: Include sys/types.h to define (s)size_t. * src/misc.h: Define SIZE_MAX if not in limits.h because this is a C99(?) extension. Define __va_copy() if not in stdarg.h because this is a GNU extension. * src/io-sim.c: Added log2() fallback because this is a GNU extension. * configure.in: Fixed sincos() check, added log2() check. 2007-11-24 * src/vps.h, src/vps.c, src/teletext.c, src/tables.h, src/tables.c, src/structpr.pl, src/sliced_filter.h, src/sliced_filter.c, src/sliced.h, src/sampling_par.h, src/sampling_par.c, src/raw_decoder.h, src/raw_decoder.c, src/pfc_demux.h, src/pfc_demux.c, src/page_table.h, src/page_table.c, src/packet.c, src/lang.h, src/lang.c, src/io-v4l2k.c, src/io-v4l2.c, src/io-v4l.c, src/io-sim.h, src/io-sim.c, src/io-bktr.c, src/idl_demux.h, src/idl_demux.c, src/format.h, src/dvb_mux.h, src/dvb_mux.c, src/dvb_demux.h, src/dvb_demux.c, src/decoder.h, src/decoder.c, src/bit_slicer.h, src/bit_slicer.c: Changed license from GPLv2 to GPLv2-or-later and updated the FSF address. * test, src, examples, contrib, README: Updated the FSF address in the copyright notice. * NEWS: Added the xpm_support changes. Merged in from the xpm_support branch: * test/exp-test.sh: Added for a quick export target test. * test/export.c (do_export): Extended to test vbi_export_mem(), vbi_export_alloc() and vbi_export_file(). (export_pdc, export_link): Replaced stdio by vbi_export output functions. (usage): Short form of --list changed from -i to -m. * src/vbi.c (vbi_decoder_delete): Bug fix: Did not free() the event handler structures. * src/misc.h, src/misc.c (_vbi_shrink_vector_capacity) (_vbi_grow_vector_capacity): New helper functions based on the page_table.c code for the vbi_export output buffer functions. * src/macros.h: Added __attribute__ format macro for vbi_export_printf(). * src/exp-txt.c (iconv_formats): Bug fix: Did not free the iconv structure after the endianess check failed. * src/exp-html.c: Bug fix: Did not free the styles list on error. * src/exp-vtx.c (export), src/exp-txt.c (export), src/exp-templ.c (export), src/exp-gfx.c (ppm_export), src/exp-html.c (export): Replaced stdio output by vbi_export buffer. * src/export.h, src/export.c (initialize): New XPM module. (_vbi_export_grow_buffer_space, vbi_export_flush, vbi_export_putc) (vbi_export_write, vbi_export_puts, vbi_export_puts_iconv) (vbi_export_puts_iconv_ucs2, vbi_export_vprintf, vbi_export_printf): New helper functions replacing stdio for export modules. (vbi_export_mem, vbi_export_alloc): New functions to export pages into memory. (vbi_export_stdio, vbi_export_file): Replaced stdio output by vbi_export buffer. (_vbi_export_malloc_error): New helper function. * src/exp-gfx.c: Tom refactored the PNG code, added an XPM export module and new transparency and title options to both modules. * src/decoder.h, src/exp-gfx.c: vbi_draw_cc_page_region() and vbi_draw_vt_page_region() now support a palette format. Contributed by Tom. 2007-11-13 * src/misc.c (_vbi_vasprintf): Bug fix: Save the va_list parameter across vsnprintf() because the function may change it. * src/conv.h, src/conv.c: Renamed strndup_iconv() to _vbi_strndup_iconv() and made the function global for vbi_export_puts_iconv(). 2007-11-09 * src/dvb_demux.c: Updated dox to clarify vbi_dvb_demux_cor() and vbi_dvb_demux_feed() are not interchangeable. 2007-11-05 * src/dvb_demux.c (vbi_dvb_demux_cor): Assert callback == NULL to prevent mixed feed and coroutine calls. * src/pfc_demux.h, src/idl_demux.h: Added function __attributes__. * src/xds_demux.h, src/xds_demux.c: Added vbi_xds_demux_feed_frame(). * src/pfc_demux.h, src/pfc_demux.c: Added vbi_pfc_demux_feed_frame(). * src/idl_demux.h, src/idl_demux.c: Added vbi_idl_demux_feed_frame(). 2007-11-04 * test/osc.c (short_options): Added -4 (proxy interface). * test/capture.c (short_options): Added -x. (usage): Documented -x --proxy option. 2007-11-03 * test/proxy-test.c: Bugfix setup of raw capture handling * test/capture.c, osc.c: Added new command line option --proxy * test/sliced.c, sliced.h: Added support for proxy interface type 2007-11-03 * test/sliced.h, test/sliced.c (write_stream_new, read_stream_new): Added file_name parameter to open a named file instead of standard input or output. * test/ttxfilter.c, test/sliced2pes.c: Added an -i --input and -o --output file name option for debugging purposes. * test/export.c: Added an -i --input file name option for debugging purposes. * test/decode.c: Added an -i --input file name option for debugging purposes. Renamed -i --idl option to -j. * test/capture.c: Added an -o --output file name option. * test/caption.c (main): read_stream_new() changed. * src/dvb_demux.c (decode_timestamp): Print a debug message on marker mismatch. (valid_pes_packet_header): In debug messages say if header_length and data_identifier have the expected value. Print a debug message if the PES header flags mismatch or the PTS is missing. * examples/wss.c (init_decoder): Bug fix: Possible overflow in sampling rate calculation. * test/export.c (parse_output_option): Drop the period from filename_suffix because we add one later. 2007-11-02 * test/decode.c (page_function_clear_cb): Second and third parameter were swapped. (teletext): IDL-A data decoding didn't work because somehow the vbi_idl_demux_feed() call was lost. (usage): Option --idl-ch shortcut is -l, not -c anymore. (main): Use strtol() base zero to permit C syntax numbers. * src/pfc_demux.h (vbi_pfc_demux_cb): Bug fix: Second and third parameter were swapped in the function prototype. Thanks Tom! * contrib/ntsc-cc.c (read_test_stream): Skip raw data in test streams. 2007-10-29 * src/teletext.c (vbi_format_vt_page): Bug fix: Must not store a double width character in the last column. 2007-10-14 * src/dvb_mux.c, src/io-sim.c, src/raw_decoder.c, src/sampling_par.c, test/capture.c, test/decode.c, test/export.c, test/sliced.c, test/sliced2pes.c, test/test-dvb_mux.cc: Resynched with 0.3 branch. * src/sliced_filter.c (decode_teletext_packet_0): Bug fix: Keep the very first page header and its timestamp, which is important for subtitle timing. * test/ttxfilter.c (filter_frame): Did not skip broken sliced VBI lines, looping forever. * test/README: Updated. * test/export.c (main): Page number error message fix. * src/misc.h: Replaced vbi_malloc, vbi_free etc macros by pointers for fault injection during unit tests. * test/test-common.h, test/test-common.cc (xmemdup): Added for test-raw_decoder. * test/test-dvb_mux.cc (test_multiplex_sliced_packet_size_checks): Incorrect buffer pointer check. (test_mr_packet_size): Allocated zero size buffer. * src/raw_decoder.h: Added new functions and changed struct vbi3_raw_decoder. * src/raw_decoder.c: Added support for sampling point recording. (vbi3_raw_decoder_add_services): Inherit log function to bit slicer. * src/io.h (struct vbi_capture): Added sampling_point() and debug() methods for test/osc in 0.3. * src/io-sim.h: Various new functions and flags. * src/io-sim.c (signal_closed_caption): Added a flag to generate the low amplitude signal observed by Rich for tests. (vbi_raw_add_noise): New function to test the improved bit slicer. (_vbi_capture_sim_get_flags, _vbi_capture_sim_set_flags): New functions to modify the simulated VBI signal. (vbi_capture_sim_add_noise): New option to simulate a noisy VBI signal. (sim_parameters, sim_debug): New capture methods to test the bit slicer with simulated data. Used by test/osc in 0.3. * configure.in: Added sincos() check for src/io-sim.c. * test/sliced.h: Removed the old sliced file output functions. (capture_stream_sim_set_flags): New function. * test/sliced.c: Added generic support for sampling point recording to examine the bit slicer. (capture_stream_sim_set_flags): Added to simulate incorrect signals in test tools. * test/capture.c: Added --sim-noise option. (cc_test): Cleaned up and documented the function. * test/Makefile.am: Replaced raw_decoder.c by test-raw_decoder.cc. * src/misc.c (_vbi_strlcpy): Was not BSD compatible. * src/dvb_demux.c: Documentation improvements. * src/Makefile.am (unrename): Exclude decoder.c. 2007-09-19 * src/io-v4l2k.c (restart_stream): Didn't initialize the v4l2_buffer.memory field. Ignore VIDIOC_QBUF errors because the buffer may be already enqueued. (v4l2_stream): Didn't initialize the v4l2_buffer.memory field for VIDIOC_QBUF. Just in case, also do that for VIDIOC_DQBUF. (v4l2_stream_flush): Didn't initialize the v4l2_buffer.memory field for VIDIOC_QBUF. 2007-09-16 * src/bit_slicer.c: Kicked averaging length back up to 16. * test/capture.c: Ignore zero bytes during --cc-test. 2007-09-15 * src/bit_slicer.c: Reduced averaging length from 16 to 8 samples for CC sampling at 27 MHz. Bug fix in sampling point recorder. 2007-09-14 * test/test-common.cc: VBI_VERSION_MINOR was undefined. * test/export.c (usage), test/decode.c (usage), test/capture.c (usage): #if VBI_VERSION within the _() macro is not portable. * src/io-sim.c: Replaced malloc() and free() calls by macros for memory allocation tests. * test/sliced.c: Extended the capture/raw_decoder analysis functions to raw VBI files. * src/raw_decoder.c (decode_pattern): Internal bit slicer interface changed. * src/bit_slicer.c: Added a sample averaging bit slicer for noisy low bit rate signals. 2007-09-12 * test/sliced2pes.c: Moved the output functions into sliced.c. Added --verbose option. * test/sliced.c, test/sliced.h: Integrated capture and file output functions, added support for raw capturing and raw VBI files. More helper functions. * test/export.c: Added --verbose option. * test/capture.c: File helpers changed. Removed VPS decoder, which is now part of the decode tool. Added raw capturing. Added a CC test for Rich. Added, changed and removed a few options, added some standard options. Moved the capture and output functions into sliced.c. * test/export.c, test/decode.c, test/caption.c: File helpers changed. * src/sliced_filter.c, src/pfc_demux.c, src/page_table.c, src/misc.c, src/idl_demux.c, src/dvb_mux.c, src/conv.c: Replaced malloc() and free() calls by macros for memory allocation tests. * src/dvb_mux.c (insert_sliced_data_units): Removed the unused strict option to pass the unit test coverage test. * test/test-dvb_mux.cc: Moved some helper functions into test-common.cc, tried C++ to simplify things. Added a memory allocation test. * test/test-common.cc, test/test-common.h: New unit test helper functions. * test/Makefile.am (test_dvb_mux_SOURCES): Added test-common.cc, test-common.h. * src/sampling_par.c (_vbi_sampling_par_valid_log) (_vbi_sampling_par_permit_service): Changed log level from notice to info. * src/bit_slicer.c: Commented out unused BIT_SLICER RGB8 code. 2007-09-07 * test/sliced.c (write_sliced), test/ttxfilter.c (filter_frame), test/capture.c (binary_sliced): Produced wrong timestamps if a frame did not contain data. 2007-09-02 * src/dvb_mux.c: Fixed typos in doxumentation. * src/dvb_mux.c, src/dvb_demux.c: Added reference to vbi_decode_dvb_pdc_descriptor(), vbi_encode_dvb_pdc_descriptor(). * src/vps.h, src/vps.c: Added vbi_decode_dvb_pdc_descriptor(), vbi_encode_dvb_pdc_descriptor() (not part of the API yet). 2007-09-01 * test/wss.c: Removed. This code went into examples/. * test/README: Updated. * test/ttxfilter.c (filter_frame), test/sliced2pes.c, test/sliced.c: Added write_error_exit helper function. * test/ttxfilter.c (main), test/sliced2pes.c (main), test/sliced.h, test/sliced.c, test/decode.c (main): Moved the End of stream messages back to the tools to allow a customized message in test/export. * test/export.c: Consolidated with its 0.3 counterpart. * test/unicode.c, test/glyph.c: Replaced extern decls by includes. * src/Makefile.am, src/sampling_par.h: Make vbi_videostd_set public for dvb_mux. * test/sliced2pes.c: Fixes. 2007-08-31 * src/dvb_mux.h: Missing markers for inclusion in libzvbi.h. * test/test-vps.c, test/test-dvb_mux.cc: Added GPLv2+ blurb. * test/sliced2pes.c: Use the new helper functions. Added support for DVB PES & TS input streams and the standard options -h -q -V. Added data identifier and min/max PES packet size options. Added an option to generate a TS stream. * test/capture.c: Use the new helper functions. Added PID argument to -t (ts) option. * test/Makefile.am (capture_SOURCES): Use helper functions. * test/sliced.h, test/sliced.c: Fixed option_ts_pid to handle 64 bit result of strtoul(). * src/pfc_demux.c (vbi_pfc_demux_new): Dox fixed. * src/page_table.h, src/page_table.c: Added doxumentation. * test/test-dvb_mux.cc, src/dvb_mux.c, src/dvb_mux.h: On a second thought vbi_dvb_mux_get_min/max_pes_packet_size sounds better. 2007-08-29 * test/Makefile.am (LDADD), contrib/Makefile.am (LDADD), configure.in: Don't require libzvbi.a (bug #1692015). * src/io-sim.c (warning): Missing __FILE__ parameter. 2007-08-27 * src/proxy-msg.c (vbi_proxy_msg_handle_read): printf size_t fix. * src/dvb_mux.c (encode_stuffing): Fixed 64 bit pointer addition. * src/misc.c, src/misc.h, src/intl-priv.h, src/conv.c: Compile fixes. * test/export.c: Replaced read loop etc by new read_stream helper functions. Added support for DVB PES & TS streams and the standard options -h -q -V. * test/decode.c: Replaced read loop etc by new read_stream helper functions. Added support for DVB TS streams. Replaced some other functions by helpers. Added -q (quiet) switch. Renamed -m (metronome) switch to -M, -T (time) to -m because -T is --ts everywhere else. * test/caption.c: Replaced read loop etc by new read_stream helper functions. * test/sliced.c, test/sliced.h: Added new helper functions. Improved the sliced VBI file reading functions. * test/capture.c (main): vbi_dvb_mux interface changed. * test/test-dvb_mux.cc: New unit test for the vbi_dvb_mux module. Phew! * test/Makefile.am (TESTS, check_PROGRAMS): Added test-dvb_mux. * src/sliced.h: Added extern C brackets for inclusion into test-dvb_mux.cc. * src/raw_decoder.c (vbi_sliced_name, vbi_sliced_payload_bits): Returned nothing for VBI_SLICED_TELETEXT_B_L25_625. * src/dvb_mux.c, src/dvb_mux.c: Rewrote this code and improved the interface. Added better support for raw VBI data. Added a minimum and maximum instead of one target PES packet size. * src/dvb_demux.c, src/dvb_demux.h: Added _vbi_dvb_skip_data_unit(), _vbi_dvb_demultiplex_sliced(), _vbi_dvb_ts_demux_new() (experimental). * src/Makefile.am (LIBZVBI_HDRS): Added dvb_mux.h. * doc/Doxyfile.in (FILE_PATTERNS): Added dvb_mux.h. * src/page_table.c, src/page_table.h: New module. * src/misc.c, src/misc.h: Added _vbi_popcnt() for page_table.c. * src/sliced_filter.c: Moved the Teletext page table into a new module page_table.c because the code is useful for other purposes. * src/Makefile.am (libzvbi_la_SOURCES): Added page_table.c, page_table.h. * src/proxy-msg.c (vbi_proxy_msg_logger) (vbi_proxy_msg_accept_connection): Replaced sprintf() by the safer snprintf(). * test/osc.c (decode_ttx, dump_pil, decode_vps): Replaced sprintf() by the safer snprintf(). * src/teletext.c (vbi_format_vt_page): Replaced sprintf() by the safer snprintf(). * src/exp-txt.c (print_char): Replaced sprintf() by the safer snprintf(). * daemon/proxyd.c (vbi_proxyd_signal_handler) (vbi_proxyd_parse_argv): Replaced sprintf() by the safer snprintf(). * src/trigger.c (parse_eacem, parse_atvef): Replaced strncpy() by the faster a safer strlcpy(). * src/proxy-msg.c (vbi_proxy_msg_get_local_socket_addr) (vbi_proxy_msg_accept_connection, vbi_proxy_msg_resolve_symlinks): Replaced strncpy() by the faster a safer strlcpy(). * src/proxy-client.c (proxy_client_start_acq): Replaced strncpy() by the faster a safer strlcpy(). * src/packet.c (vbi_decode_vps, parse_bsd): Replaced strncpy() by the faster a safer strlcpy(). * src/io-v4l.c (v4l_new): Replaced strncpy() by the faster a safer strlcpy(). * daemon/proxyd.c (vbi_proxyd_take_service_req) (vbi_proxyd_take_message, vbi_proxyd_take_message): Replaced strncpy() by the faster a safer strlcpy(). * src/misc.h: Undefined strncpy() and sprintf(). * autogen-maint.sh (CXXFLAGS): Same warnings as in CFLAGS, except those which are not supported in C++. 2007-07-23 * src/sliced_filter.c, src/sliced_filter.h: Move the Teletext filter code from test/ttxfilter.c here and improved it somewhat. This not yet part of the library API. * test/ttxfilter.c (main): Added some debugging code. (main): Added -q (quiet) and -a (abort-on-error) option. (filter_frame): Report parity/hamming errors and continue with the next line instead of discarding the entire frame. * src/misc.h (_vbi_vlog): New variadic counterpart of _vbi_log(). * src/misc.h, src/misc.c (_vbi_log_vprintf, _vbi_log_printf): Context was only the function name. Added a file name argument to make it unique. * src/misc.c (vbi_log_on_stderr): Minor formatting fix. (_vbi_log_vprintf, _vbi_log_printf): * src/lang.c (vbi_caption_unicode): Clarified doxumentation. * autogen-maint.sh: CFLAGS -Ox fixes. 2007-07-04 * test/ttxfilter.c: Added time option and a few other improvements. * src/io-dvb.c (dvb_init): Some drivers fail with O_RDWR. Open with O_RDONLY instead. * src/videodev2k.h: Don't use anonymous union, which is a GCC extension. * src/lang.c, src/export.c: Replaced GCC's __PRETTY_FUNCTION__ by __FUNCTION__. * src/exp-gfx.c (draw_char): Added #if __GNUC__ around #if #cpu conditional. * src/conv.c (strndup_iconv_to_ucs2): Force a const cast in iconv() call. * src/vt.h: Don't typedef enum drcs_mode. Some compilers cannot distinguish btw variable and type of same name. * src/cache.c, src/cache.h: Don't typedef struct list, struct node. Some compilers cannot distinguish btw variable and type of same name. * src/io-v4l2k.c, src/io-v4l2.c, src/io-dvb.c: Define __s64 and __u64 if asm/types.h does not. * configure.in: Check if asm/types.h defines __s64 and __u64 to compile with non-GCC compilers. * m4/autogen.sh (REQUIRED_GETTEXT_VERSION): Bumped to 0.16. * autogen-maint.sh: Added maintainer autogen.sh. 2007-07-04 gettextize * m4/iconv.m4: Upgrade to gettext-0.16.1. * m4/lib-ld.m4: Upgrade to gettext-0.16.1. * m4/lib-link.m4: Upgrade to gettext-0.16.1. * m4/lib-prefix.m4: Upgrade to gettext-0.16.1. * m4/nls.m4: Upgrade to gettext-0.16.1. * m4/po.m4: Upgrade to gettext-0.16.1. * m4/progtest.m4: Upgrade to gettext-0.16.1. * configure.in (AM_GNU_GETTEXT_VERSION): Bump to 0.16.1. 2007-04-02 * src/videodev2k.h: Updated to latest version. * contrib/ntsc-cc.c: Added V4L2 sliced VBI interface for tests. * test/decode.c: Added a --metronome option to examine timestamp errors. * configure.in: Bumped version to 0.2.26. 2007-03-09 * Release 0.2.25. 2007-03-09 * src/chains.c: Don't include videodev.h on GNU/kFreeBSD systems (Debian bug #407621). 2007-02-14 * contrib/ntsc-cc.c (CCdecode): Fixed a buffer overflow. Symptom of this bug may be a segfault on reception errors. 2006-11-29 * Release 0.2.24. 2006-11-21 * contrib/ntsc-cc.c (CCdecode): Fixed channel number check. Symptom of this bug may be a segfault on reception errors. 2006-10-27 * Release 0.2.23. 2006-10-27 * configure.in: Bumped .so revision to 10. * contrib/ntsc-cc.c (main): Added a brief delay before retrying after an error. 2006-10-06 * test/unicode.c (main): Now prints extended caption characters too. * test/decode.c: Replaced by a new version from branch 0.3 with improved caption decoder. * src/lang.h, src/lang.c (vbi_caption_unicode): Replaced by a new version from branch 0.3 which can convert extended characters. Added an option to convert the character to upper case. * test/unicode.c (main), test/glyph.c (main), src/caption.c (caption_command, vbi_decode_caption): vbi_caption_unicode() changed. * src/conv.c, src/conv.h: New Unicode conversion helper functions from branch 0.3. * src/Makefile.am (libzvbi_la_SOURCES): Added conv.c conv.h. (LIBZVBI_HDRS): Added conv.h. * doc/Doxyfile.in (FILE_PATTERNS): Added conv.h. * contrib/ntsc-cc.c: Added parallel decoding of all channels. Added proper conversion from CC to locale character set with automagic uppercasing of accented characters. 2006-09-29 * contrib/ntsc-cc.c: Added a caption channel filter. * contrib/ntsc-cc.c, test/capture.c, test/decode.c: * test/ttxfilter.c, test/sliced2pes.c: Explicitely fflush output buffers to ensure real time output of CC/XDS data. 2006-09-27 * contrib/ntsc-cc.c: Replaced -x, -c optional args by new options for compatibility with earlier versions. Added a more verbose help text. Added an option to suppress WebTV links. 2006-09-26 * contrib/ntsc-cc.c: Added --long options, -x optional arg to filter out XDS packages, -c optional arg to redirect caption to a different file. Don't print % if the XDS package type is unknown. 2006-09-24 * contrib/ntsc-cc.c (XDSdecode): Didn't handle zero bytes. Didn't demultiplex F2 caption. Check for repeated packets didn't compare the packet length. (main): Discard data not from line 21 or 284. Broken drivers can go to hell. 2006-07-22 * src/raw_decoder.c: Modified the Closed Caption 525 parameters to decode the incorrect signal observed by Rich Kandel. * test/raw_decoder.c: Added test of decoder with the incorrect signal observed by Rich. * src/io-sim.c (signal_closed_caption): Added optional simulation of the incorrect signal observed by Rich. * src/io-sim.h, src/io-sim.c (_vbi_raw_vbi_image, _vbi_raw_video_image): New functions with flags parameter instead of swap_fields boolean. 2006-06-17 * src/io.c (device_ioctl): * src/structpr.pl (test_cond): Handle r+w fields. * src/io.c (device_ioctl): Print saved errno. Print-r/w-field flags were reversed. 2006-06-11 * configure.in: CFLAGS changes have no effect after AC_PROG_CC, added AC_GNU_SOURCE instead of -D_GNU_SOURCE. * contrib/ntsc-cc.c: s/RAW/print_raw because RAW is a macro on GNU/kFreeBSD (Debian bug #372302). * configure.in: Bumped version number to 0.2.23. 2006-05-30 * Release 0.2.22. 2006-05-30 * src/vps.c, src/packet.c, src/wss.c, src/packet.c, src/io.c, src/dvb_mux.c, src/caption.c, src/cache.c: Include config.h. * src/io-v4l2k.c (v4l2_update_services): Don't request start[1] line zero if count is zero, may confuse broken drivers. Added work-around for start line bug in older versions of the bttv driver which broke proxy-test vps and wss. * src/raw_decoder.c (lines_containing_data): Did not expect a service completely outside the current sampling parameters. * src/proxy-client.c (proxy_client_alloc_msg_buf): Older gcc/libc do not recognize %zd for size_t. * configure.in: Run function checks with -D_GNU_SOURCE because we also compile with this flag. * src/videodev2k.h: Don't include linux/compiler.h, that's __KERNEL__ stuff and it conflicts with our misc.h. * src/macros.h: Added VBI_LOG_DRIVER to replace device log_fp later. * test/decode.c (usage): --idl-ch correction. 2006-05-29 * src/io-v4l2k.c (v4l2_get_videostd): Limit the number of of videostd enumerations in case the driver is broken. 2006-05-28 * test/osc.c (_vbi_to_ascii): * test/decode.c (_vbi_to_ascii): Removed this redundant function. * src/io.c (device_close): Bug fix: logged only if failed. * test/raw_decoder.c: Enabled VPS tests because a VPS simulation is available now. * src/sampling_par.h: Don't make the sampling_par functions public yet, have to brush up the definition of video standards first. 2006-05-26 * src/misc.h, src/macros.h: Resynched with 0.3 branch. _vbi_log_hook moved from misc.h to macros.h for private declarations in various public headers. * src/sampling_par.c: Resynched with 0.3 branch. * src/io-sim.c (sim_parameters): Resynched with 0.3 branch. * examples/rawout.c: Don't declare vbi_sliced_payload_bits(), is public now. * src/sliced.h (vbi_sliced_payload_bits): Moved here from raw_decoder.h and made public. * src/raw_decoder.h (vbi_sliced_payload_bits): Moved into sliced.h. 2006-05-25 * src/io-sim.c (vbi_raw_vbi_image): Fixed signal level check. (vbi_raw_video_image): Added missing signal level check. * test/sim.c: Replaced by io-sim.c, removed. * test/Makefile.am (EXTRA_DIST): Removed sim.c. * test/osc.c (main, mainloop): Use simulated capture device (io-sim.c) instead of old sim code. * examples/wss.c (init_decoder): Bug fix bytes_per_line *is* bytes per line, not samples per line. * src/sampling_par.c (_vbi_sampling_par_permit_service): Allow tighter samples_per_line if strict = 0, for rawout.c square pixel output. * examples/rawout.c: Added a test of generated images. (convert): Allow 50% PTS delay before assuming a missing frame. * src/macros.h: Added log function definitions to doxumentation Basic types group. * src/vbi.c (vbi_set_log_fn): * src/misc.c (vbi_log_on_stderr): * src/io-sim.h, src/io-sim.c: * src/dvb_demux.c (vbi_dvb_demux_set_log_fn): Added/updated doxumentation. * src/export.c, src/exp-txt.c: Corrected syntax which confused doxygen. * src/dvb_demux.c: Doxygen shall not document the _vbi_dvb_demux wrappers, they exist only for compatibility with an old version of Zapping. 2006-05-24 * doc/Doxyfile.in: Updated to doxygen 1.4.5. (FILE_PATTERNS): Replaced misc.h by macros.h, added io-sim.h. * src/sampling_par.c (_vbi_sampling_par_permit_service): Restored the 0.2.21 line number fix. * src/io-sim.c (vbi_raw_video_image): RGBA32 fixes. * src/misc.h (SWAB32): Fixed. * src/bit_slicer.h, src/bit_slicer.c: Fixed buffer read overflow if the sampling format has more than one byte per sample. 2006-05-23 * src/io-sim.h, src/io-sim.c (vbi_raw_video_image): Added blank_level parameter. * src/dvb_demux.h, src/dvb_demux.c: Replaced log macros by vbi_log_hook. Added vbi_dvb_demux_set_log_fn(). * src/misc.h: Added debug log macros. * src/macros.h: Added two more VBI_LOG debug levels. * src/Makefile.am (libzvbi_la_SOURCES): Added intl-priv.h. (version.h): Overwrite, not append. Sheesh. * src/io.h, src/export.h, src/teletext.c: Gettext macro definitions moved to intl-priv.h. * src/intl-priv.h: New file from branch 0.3. * src/raw_decoder.c (vbi3_raw_decoder_add_services): Bit slicer API changed. * src/bit_slicer.h, src/bit_slicer.c: Resynched with 0.3 branch. Added function to collect sampling points for debugging. * configure.in: Added byte order checks because __BYTE_ORDER is not portable. * src/dvb_demux.c (demux_packet): Callback interface was broken, returning -n_sliced_lines and hanging after first frame. * examples/rawout.c: Replaced DVB demux coroutine by a callback to simplify things. Insert a blank frame if the DVB stream contains no VBI data for a frame. * src/vbi.h, src/vbi.c (vbi_set_log_fn): Added. 2006-05-22 * test/ttxfilter.c, test/sliced2pes.c, test/sliced.h, test/sliced.c, test/osc.c, test/export.c, test/decode.c, test/capture.c, test/caption.c: Include individual headers instead of libzvbi.h to pull in private stuff without conflicts. * src/Makefile.am (libzvbi_la_SOURCES): Added sampling_par.c, sampling_par.h. (LIBZVBI_HDRS): Public macros now in macros.h instead of misc.h. (LIBZVBI_HDRS): Added sampling_par.h, io-sim.h. * src/io-v4l2k.c: Use vbi_log_hook. Replaced vbi_log_printf() calls by log macros from misc.h. (vbi_videostd_set_from_scanning): Moved to sampling_par.c. (v4l2_update_services): Replaced vbi_sampling_par_check_services() call by _vbi_sampling_par_check_services_log(). * src/sampling_par.c, src/sampling_par.h: New files from branch 0.3. Sampling parameters functions are public now. * src/decoder.c (vbi_raw_decoder_check_services): Use vbi_sampling_par_check_services() w/o logging. (vbi_raw_decoder_parameters): Use vbi_sampling_par_from_services() w/o logging. * src/raw_decoder.h, src/raw_decoder.c: Use vbi_log_hook. Replaced vbi_log_printf() calls by log macros from misc.h. Sampling parameters functions moved to sampling_par.c, sampling_par.h. * src/proxy-client.c, src/io-v4l2.c, src/io-v4l.c, src/io-bktr.c, src/export.c, daemon/proxyd.c, daemon/chains.c: s/vbi_asprintf/asprintf. * src/bit_slicer.c: s/vbi_log_printf/_vbi_log_printf. * src/misc.h, src/misc.c: Resynched with 0.3 branch. Public stuff moved to macros.h. Added _vbi_keyword_lookup(), _vbi_log_hook, _vbi_log_vprintf(), logging macros, _vbi_vasprintf(). * src/hamm.h: Replaced vbi_pure attribute. * src/xds_demux.h, src/bit_slicer.h: Replaced vbi_alloc attribute. * src/xds_demux.h, src/pfc_demux.h, src/idl_demux.h: Include macros.h. * src/macros.h: Resynched with 0.3 branch. vbi_log stuff now public. Replaced log level by log mask. * test/osc.c (main, mainloop): Use simulated capture device (io-sim.c) instead of old sim code. * test/raw_decoder.c (create_raw): Functions to create raw VBI images changed. (test_services): vbi_sampling_par_from_services() changed. * examples/rawout.c: New example. * examples/Makefile.am (noinst_PROGRAMS): Added rawout. * src/io-sim.h, src/io-sim.c: Resynched with 0.3 branch. Added VPS simulation and corrected CC simulation. Functions to generate raw VBI images are public now, with a more polished interface. Added a simulated capture device. * src/exp-txt.c (match_color8): Signedness fix. * configure.in: Changed SO_VERSION to 9:0:9 (new interfaces). 2006-05-19 * src/raw_decoder.c: Shifted WSS_625 CRI/FRC left one bit to center sampling points over payload bits. 2006-05-17 * src/io-v4l2k.c (print_vfmt): LF redundant. * test/osc.c, test/decode.c, test/capture.c, test/caption.c, src/xds_demux.c, src/teletext.c, src/search.c, src/packet.c, src/misc.h, src/dvb_demux.c, src/caption.c: s/vbi_printable/vbi_to_ascii for clarity. * src/raw_decoder.c (_vbi_sampling_par_valid): Fixed broken start/count check. * src/pfc_demux.h, src/pfc_demux.c: Cleanups for 0.3 backport. * configure.in: Bumped version number to 0.2.22. 2006-05-10 * Release 0.2.21. 2006-05-10 * examples/wss.c: Include libzvbi.h, not src/libzvbi.h. * src/raw_decoder.c (_vbi_sampling_par_check_service): Line number check required both fields for services which exist only on one field. * src/io-sim.c (signal_u8): Didn't handle sampling parameters with only a single field. (_vbi_test_image_vbi): Enabled warnings. * test/raw_decoder.c (test2): Added regression test for line number check bug. (create_decoder): Enabled warnings. * configure.in: Bumped version number to 0.2.21. 2006-05-08 * Release 0.2.20. 2006-05-07 * test/decode.c: Enabled some VPS decoding. * test/test-vps.c: New test for VPS decoding functions. * test/Makefile.am (TESTS): Added test-vps. (noinst_PROGRAMS): Added test-vps, wss moved into examples dir. * src/vbi.c, src/packet.c, src/event.h, src/caption.c (xds_decoder): Added VBI_EVENT_NETWORK_ID. * src/Makefile.am (libzvbi_la_SOURCES): Added macros.h, pdc.h, vps.c, vps.h. * src/vps.c, src/vps.h: Added new VPS decoding functions. * src/event.h: Added a doxy link to examples/network.c. (struct vbi_network): Improved documentation, renamed unused/misdefined private field cni_x26 to reserved. * src/decoder.c: Added a doxy link to examples/wss.c. * doc/Doxyfile.in (FILE_PATTERNS): New file vps.h. (EXAMPLE_PATH): Added examples dir. * configure.in, examples, Makefile.am (SUBDIRS): Added examples dir. * src/io-v4l2k.c (v4l2_update_services): Added an error message about the NTSC VBI bug in the cx88 driver. * src/structpr.pl: ILP64 fixes. 2006-04-28 * src/io-v4l.c (reverse_lookup): Signedness fix. * test/README: Updated. * test/capture.c: Removed Teletext, CC and XDS decoders. That's now implemented in test/decode.c. * test/decode.c: Resynced with 0.3 version, adding CC and XDS decoder. * configure.in: Use -D_GNU_SOURCE when checking for GNU extensions. Added check for program_invocation_name, for test/decode.c. * test/capture.c: Added --strict option. * test/osc.c: Include misc.h, now required by raw_decoder.h * src/misc.h, src/misc.c: Added logging helper functions. * src/bit_slicer.h, src/bit_slicer.c (vbi3_bit_slicer_slice, _vbi3_bit_slicer_init) (vbi3_bit_slicer_new): Replaced the stderr log macros by a vbi3_bit_slicer.log_fn. * src/raw_decoder.c, src/raw_decoder.h: s/_vbi_sampling_par_verify/_vbi_sampling_par_valid for clarity. * src/raw_decoder.h, src/raw_decoder.c: Replaced the stderr log macros by a vbi3_raw_decoder.log_fn for src/io-v4l2k.c. * src/io-v4l2k.c: Use the new raw_decoder directly, so I can enable its logging functions and won't miss interesting messages. Replaced the stderr log macros by a vbi_capture_v4l2.log_fn. 2006-04-12 * src/io-v4l2k.c: Added a bttv offset bug work-around. 2006-03-17 * test/hamm.c (main): Signedness fix. * test/raw_decoder.c: Added vbi_sampling_par.synchronous tests. * test/sim.c, test/osc.c, test/capture.c: Added --sim --desync option to test vbi_sampling_par.synchronous with a one field delay. * src/raw_decoder.h (_vbi_service_par_flag, _vbi_service_par), * src/raw_decoder.c (_vbi_service_table): Added _VBI_SP_FIELD_NUM, _VBI_SP_LINE_NUM flags to eliminate services which need raw VBI with known field or line numbers. * src/raw_decoder.c (decode_pattern, _vbi_sampling_par_check_service) (vbi3_raw_decoder_add_services): Handle raw VBI with unknown field order (V4L VBI_UNSYNC, V4L2_VBI_UNSYNC flag). * src/io-sim.c (signal_u8): Removed vbi_sampling_par.synchronous check so we can test with this flag cleared. * configure.in: Bumped version number to 0.2.20. 2006-02-23 * Release 0.2.19. 2006-02-23 * contrib/ntsc-cc.c: Did not use libzvbi but its own decoder, fixes Debian bug #354035. * contrib/Makefile.am (zvbi_ntsc_cc_LDADD): Link libzvbi dynamically. 2005-02-11 * Release 0.2.18. 2006-02-07 * test/ttxfilter.c: Didn't work with parallel page transmission. * src/cache.c, src/cache.h: Replaced list type to prevent a pointer aliasing bug. * src, contrib, daemon, test: Cleaned up to avoid unused parameter, signedness and constness warnings, replaced printf format modifier ll? by PRI?64. Patch #1425503 by Diego Pettenò. * configure.in: Modernized and made documentation building optional (patch #1425497 by Diego Pettenò). 2005-10-24 * configure.in: Added AM_MAINTAINER_MODE. * m4/autogen.sh (conf_flags): Don't default to maintainer mode. * src/Makefile.am: BUILT_SOURCES do not belong into CLEANFILES. Rebuild BUILT_SOURCES only in maintainer mode, just in case. 2005-10-07 * Release 0.2.17. 2005-10-07 * src/Makefile.am: Build network-table.h from online networks.xml. * src/tables.c: vbi_cni_table[] now in network-table.h (generated). 2005-10-04 * src/io-v4l.c (open_video_dev): readdir_r() fix. 2005-10-03 * configure.in: Bumped version number to 0.2.17, .so revision to 8. * contrib/README: Added info about ntsc-cc. * contrib/Makefile.am (bin_PROGRAMS): Added zvbi-ntsc-cc. (AM_CPPFLAGS): Added X_CFLAGS for ntsc-cc. (LDADD): Added X_LIBS for ntsc-cc. (man_MANS): Added zvbi-ntsc-cc.1. * contrib: Imported ntsc-cc.c and ntsc-cc.1 from Xawtv CVS. * test/Makefile.am (noinst_PROGRAMS): Added ttxfilter. (ttxfilter_SOURCES): Added. * test/sliced.c, test/sliced.h: New write interface for ttxfilter. * test: Added ttxfilter.c. * src/xds_demux.h: Doxumentation update. 2005-07-10 * src/xds_demux.h, src/xds_demux.c (_vbi_xds_packet_dump): Added missing XDS packet subclasses. 2005-06-30 * src/structpr.pl: Didn't log VIDIOC_G|S_STD. 2005-06-10 * src/dvb_mux.c (_vbi_dvb_multiplex_sliced): Didn't write the correct data_unit_length in compatibility mode (data_identifier in range 0x10 ... 0x1F), breaking test/capture --pes output. * src/dvb_demux.c: Added more log points. * src/misc.h (__builtin_expect, likely, unlikely), src/dvb_demux.c (demux_packet), src/bit_slicer.c (BIT_SLICER): Replaced __builtin_expect() by more readable likely()/unlikely() macros. Thanks to Linux hackers for the idea. * src/dvb_mux.h, src/dvb_mux.c: (_vbi_dvb_mux_mux): Renamed to _vbi_dvb_mux_feed for consistency. * test/README: Added sliced2pes and updated test/capture options. * test/Makefile.am (noinst_PROGRAMS): Added sliced2pes. (caption_SOURCES, capture_SOURCES, decode_SOURCES, export_SOURCES): Added sliced.c and sliced.h which now contain the code to read old test/capture --sliced output. * test/caption.c: Cleaned up and added support for DVB PES input (PAL/SECAM caption). * test/sliced2pes.c: Added to convert old test/capture --sliced output to DVB PES format. * test/decode.c (main): Option -a didn't toggle all decode options as it should and didn't enable/disable XDS. * test/decode.c, test/export.c, test/caption.c, test/sliced.c, test/sliced.h: Moved the code reading old test/capture --sliced output into the new files sliced.c/h. 2005-05-25 * Release 0.2.16. 2005-05-25 * doc/Doxyfile.in (FILE_PATTERNS): Added xds_demux.h. * test/decode.c: Added xds_demux test code. * src/caption.c: Moved the XDS debugging code to xds_demux.c. * src/Makefile.am (libzvbi_la_SOURCES): Added xds_demux.c/.h. (LIBZVBI_HDRS): Added xds_demux.h. * src/xds_demux.c, src/xds_demux.h: New XDS demultiplexer from branch 0.3. * src/io-v4l2k.c: Added a work-around for wrong NTSC line numbers reported by saa7134 drivers before 0.2.13. * src/exp-html.c (export): segv fix by Bernhard Rosenkraenzer. 2005-05-11 * test/wss.c: -d takes an argument. Crashed due to NULL string pointer. * test/osc.c, test/capture.c: (short_options): -d takes an argument, not -e. Crashed due to NULL string pointer. 2005-05-07 * src/io.c (vbi_capture_io_update_timeout): Replaced assertion that time increments between successive gettimeofday calls, which isn't necessarily true, by absolute value of delta. 2005-04-27 * test/caption.c, test/osc.c: vbi_printable() undefined. * test/osc.c (decode_vps): s/vbi_bit_reverse[]/vbi_rev8(). (decode_ttx): s/vbi_hamm16()/vbi_unham16p(). * configure.in: Bumped version number to 0.2.16. HAVE_X conditional was backwards, didn't compile test/osc and test/caption. 2005-03-28 * Release 0.2.15. 2005-03-28 * src/raw_decoder.c (_vbi_sampling_par_verify): Disabled a YUV420 even bytes per line check because it conflicts with the ivtv driver, which returns an odd number of bytes per line using _GREY format, mapped to YUV420 because libzvbi 0.2 has no VBI_PIXFMT_Y8. * configure.in: Bumped version number to 0.2.15, .so version to 6:1:6. 2005-02-28 * Release 0.2.14. 2005-02-25 * src/cache.c (destroy_list): Suppress unused parameter warning. * src/Makefile.am (libzvbi_la_SOURCES): Added pfc_demux.c, pfc_demux.h. (LIBZVBI_HDRS): Added pfc_demux.h. * doc/Doxyfile: Is a built file, removed from CVS. * configure.in: Bumped version number to 0.2.14. 2005-02-20 * test/decode.c: Enabled pfc code. * src/packet.c, src/vbi.h: page_clear code replaced by _vbi_pfc_demux. Disabled until rewrite and test. * src/event.h: struct pfc_block obsolete, removed. * src/idl_demux.c, src/idl_demux.h: New Teletext page format clear demultiplexer from branch 0.3. 2005-02-17 * src: Regrouped doxumentation. * test/decode.c: New low level VBI decoder from branch 0.3. Commented out future stuff, made a few corrections and added vbi_idl_demux routines. * test/README: Added decode blurb. * test/Makefile.am (noinst_PROGRAMS): Added decode. * src/idl_demux.c, src/idl_demux.h: New Teletext packet IDL demultiplexer. * src/Makefile.am (libzvbi_la_SOURCES): Added idl_demux.c, idl_demux.h. (LIBZVBI_HDRS): Added idl_demux.h. * doc/Doxyfile.in (FILE_PATTERNS): Added idl_demux.h. 2005-01-23 * Release 0.2.13. 2005-01-22 * src/io.h: read return type ought to be int, not bool. * src/io-bktr.c (bktr_read): Const pointer parameter fix. * src/io-bktr.c (vbi_capture_bktr_new): Ignored scanning parameter, always assuming 625. * src/dvb_demux.c (demux_samples): Potential deref of uninitialized vbi_sliced pointer. * src/decoder.c (vbi_raw_decoder_resize), src/caption.c (xds_separator, itv_separator): Signedness fix. * m4/autogen.sh: Made required versions changeable for tests. * src/Makefile.am (INCLUDES), daemon/Makefile.am (INCLUDES), contrib/Makefile.am (INCLUDES), test/Makefile.am (INCLUDES): Removed warning options, they belong into CFLAGS. * test/Makefile.am (INCLUDES): Removed unused COMMON_INCLUDES. * src/Makefile.am (INCLUDE): Removed unused X_CFLAGS. * src/hamm.h (vbi_unham8): Must return signed int. 2005-01-20 * src/hamm.c, src/hamm.h: Dox "since" missing. * src/proxy-client.c: Dox update. * src/io-dvb.c: Changed to new version. * configure.in: Replaced uname call by AC_CANONICAL_HOST for proper cross-compiling. * test/wss.c: Compile only if we ENABLE_V4L2. 2005-01-19 * src/lang.c: s/is(blank|full)/is_yadda due to gcc 4.0 built-in name conflict. * daemon, src, test, contrib: gcc 4.0 char pointer signedness warnings. 2005-01-18 * Release 0.2.12. 2005-01-17 * po/fr.po: Updated by Christian Marillat. * src/hamm.c, src/hamm.h: Updated from branch 0.3, parity and Hamming routines are public now. * src/Makefile.am (LIBZVBI_HDRS): Added hamm.h. Added built sources to cleanfiles. * test/Makefile.am: Cleaned up. Added hamm check. * test/hamm.c: New parity and Hamming routines check, ported over from branch 0.3. 2005-01-15 * test/wss.c: New test/demo capturing a WSS signal from video images. * test/README: Added wss. * test/Makefile.am (noinst_PROGRAMS): Added wss. * src/teletext.c (enhance), src/packet.c (parse_28_29), src/exp-gfx.c (png_export): Nested func fix for gcc 4.0, Debian bug #290444. 2005-01-13 * src/raw_decoder.c: VBI_SLICED_TELETEXT_B_L10_625 had incorrect F2 range 319-334, bug compatible with bttv. Corrected to 320-335. * src/io-v4l2k.c: Added bug workaround for bttv < 0.9.15, saa7134 which capture PAL/SECAM F2 line numbers one higher than reported. * src/raw_decoder.h, src/raw_decoder.c: s/uint/int strict for compatibility with ancient libzvbi 0.2 apps. 2005-01-09 * test/capture.c, test/osc.c: Changed strict param from -1 to 0 for proper WSS reception (requires programming of sampling params). 2004-12-31 * Release 0.2.11 2004-12-31 * src/Makefile.am (libzvbi_la_SOURCES): Added dvb_demux.h. 2004-12-30 * Release 0.2.10 2004-12-28 * src/Makefile.am (LIBZVBI_HDRS): Added dvb_demux.h. * src/dvb_demux.c, src/dvb_demux.h: Renamed a few funcs, added missing vbi_dvb_demux_reset(), added documentation, made the interface public. * doc/Doxyfile.in (FILE_PATTERNS): Added dvb_demux.h. 2004-12-23 * src/io-bktr.c, src/io-dvb.c, src/io-v4l.c, src/io-v4l2.c, src/io-v4l2k.c: errorstr fix, 0.2.9 may crash if NULL. * configure.in: Replaced uname call by AC_CANONICAL_HOST for proper cross-compiling. Added HAVE_X conditional. * test/Makefile.am: Compile X programs only if we HAVE_X. * src/structpr.pl: fourcc fix. * src/proxy-msg.c, daemon/proxyd.c: printf ptrdiff_t fixes. 2004-12-12 * src/raw_decoder.c (decode_pattern): Disabled blank line detection. Will be slower now but if the signal inserter is disabled during silent periods for more than 4-5 seconds we may miss caption/subtitles. * src/vbi.c (vbi_event_handler_add, vbi_event_handler_remove): Improved doxumentation. * src/cache.c (vbi_is_cached, vbi_cache_hi_subno): Undoxumented return value. * src/io-v4l2.c: Removed unnecessary includes. * src/io-bktr.c (vbi_capture_bktr_new), src/io-v4l.c (v4l_new), src/io-v4l2k.c (vbi_capture_v4l2k_new): Did not initialize raw_decoder, that worked only by accident. * src/io-bktr.c (bktr_delete): Did not destroy raw_decoder. * src/decoder.h: Added vbi_pixfmt_set macros for raw_decoder test. * test/Makefile.am: Added raw_decoder check. Compile cpptest only for make check. * test/raw_decoder.c: New raw_decoder.c, bit_slicer.c unit test from branch 0.3, modified to compile here. * src/exp-gfx.c (vbi_draw_cc_page_region): Dox completed. * src/exp-txt.c (vbi_print_page_region): Fixed doxumentation of ltr parameter. * src/io-v4l.c (vbi_capture_v4l_sidecar_new): Dox completed. 2004-12-11 * test/osc.c: vbi_service_table definitions removed, now semi-public in raw_decoder.h. * src/decoder.c (vbi_raw_decode): No longer YUV420-only. * src/decoder.c: Raw VBI decoder routines changed to wrappers of new raw_decoder.c, bit_slicer.c. Old bit slicer remains because it lacks a destroy function. * src/sliced.h (VBI_SLICED_): Added new services and updated dox from branch 0.3. * src/Makefile.am (libzvbi_la_SOURCES): Added bit_slicer.c|h, raw_decoder.c|h, io-sim.c|h. * src/bit_slicer.h, src/bit_slicer.c: New bit slicer from branch 0.3, modified to compile here. * src/raw_decoder.h, src/raw_decoder.c: New raw VBI decoder from branch 0.3, modified to compile here. 2004-11-26 * src/misc.h (CONST_PARENT): Added. * src/proxy-client.c (vbi_proxy_client_read), src/io-v4l2k.c (v4l2_stream), src/io-v4l.c (v4l_read), src/io-dvb.c (dvb_read), src/io.h: Internal vbi_capture->read() takes const *timeout. * src/io-dvb.c: (vbi_capture_dvb_filter): perror only if dvb->debug. (vbi_capture_dvb_new, vbi_capture_dvb_filter): Doxified. (vbi_capture_dvb_new2): Replacement for buggy vbi_capture_dvb_new. Removed useless scanning, services, strict parameter, added pid. (vbi_capture_dvb_last_pts): Added to pass out decoded PTS until we have stream_time in the I/O interface. (dvb_read): Handle EINTR, EAGAIN. Skip select() if timeout is zero for efficiency. 2004-11-25 * src/io-dvb.c (dvb_read): Must subtract time waited in select from timeout. 2004-11-11 * Release 0.2.9 2004-11-10 * README, NEWS, TODO, daemon/README: Updated for 0.2.9. * src/io-dvb.c: New version with vbi_dvb_demux still untested, restored previous version for 0.2.9. * configure.in: By default no proxy on FreeBSD. * src/io-bktr.c: Include fix. * src/Makefile.am: Always compile proxy-client.c. * src/proxy-client.c: Moved function documentation down to #ifndef proxy section, or doxygen won't find it. Added missing dummy functions to make the linker happy. (vbi_capture_proxy_new): in no-proxy section, fixed parameter mismatch with header. 2004-11-07 * daemon/proxyd.c (dprintf): s/proxyd/zvbid. * src/decoder.c: Include site_def.h. * src/io-v4l.c (v4l_update_services): bttv has_select fix. Workaround for bttv 0.9.5 VIDIOCGVBIFMT not initializing flags. VIDIOCGVBIFMT scanning guess fix. 2004-11-03 * src/dvb_mux.c (_vbi_dvb_multiplex_sliced, _vbi_dvb_multiplex_samples), src/dvb_demux.c (demux_data_units): D'oh! Got stuffing wrong. * src/dvb_mux.c (_vbi_dvb_multiplex_sliced), src/dvb_demux.c (demux_data_units): Don't reverse VPS bits. * src/dvb_demux.c: Improved data unit loop to handle field packets. * src/io-dvb.c: Ported to new vbi_dvb_demux, untested. * po/de.po, po/fr.po, po/es.po, po/nl.po, po/pl.po, po/sv.po: Converted to UTF-8. 2004-10-31 * src/chains.c: Compile only for V4L/V4L2. * configure.in: Added FreeBSD ioctl request type. * src/proxy-client.c (proxy_client_check_msg): s/EPROTO/EMSGSIZE for FreeBSD. (proxy_client_wait_select): FreeBSD FD_ISSET return type mismatch. 2004-10-27 * src/dvb_mux.c (_vbi_dvb_mux_delete): NULL and CLEAR fix. (_vbi_dvb_mux_pes_new): Fixed data_identifier position. * src/dvb_mux.c, src/dvb_mux.h: Added, experimental. * test/capture.c: Changed PTS source to timestamps. * test/export.c: Extended to consume DVB streams. 2004-10-25 * po/POTFILES.in: Added proxy-client.c, proxy-msg.c. * src/proxy-client.c, src/proxy-msg.c: Massaged error messages. * daemon/Makefile.am: Added zvbi-chains target. * src/Makefile.am: Added libzvbi-chains target. * daemon/chains.c, src/chains.c: Added from proxy-18.bak. * daemon/chains.c (main): Replaced sprintf by asprintf and fixed p_env3. * configure.in: Added ioctl request type check for chains. * test/README: DVB capture update. * test/capture.c: Extended to create DVB streams. * src/Makefile.am (libzvbi_la_SOURCES): Added dvb.h, dvb_mux.c, dvb_mux.h. * src/dvb.h: New definitions for DVB-VBI mux/demux. * src/dvb_mux.c, src/dvb_mux.h: Added, experimental. * src/sliced.h: Added vbi_service_set. * configure.in: Added strndup, strlcpy, asprintf checks. * src/misc.h: Added strndup() and asprintf() fallback macros. * src/Makefile.am (libzvbi_la_SOURCES): Added misc.c. * src/vbi.c, src/vbi.h (vbi_asprintf), src/misc.c (_vbi_asprintf): Moved asprintf() replacement to misc.c and improved the implementation. 2004-10-24 * daemon/proxyd.c: Added handling of norm changes; improved debug level handling. * src/proxy-msg.c: Cleaned up socket I/O interface functions. * src/proxy-client.c: Added handling of norm changes. * text/proxy-test.c: Added test support for norm change handling. 2004-10-14 * src/wstfont2.xbm: Fixed height of Omega character. * src/packet.c (vbi_teletext_set_default_region): Override only primary character set code. * src/teletext.c (vbi_format_vt_page): Fixed ESC decoding. 2004-10-05 * src/intl-priv.h: Added from 0.3 branch. * src/io-v4l2.c: V4L2 0.20 API still recognized for debugging but no longer supported. * src/io-v4l.c, src/io-v4l2.c, src/io-bktr.c: Added ioctl logging. * src/io-v4l2k.c: Log mmap and munmap calls. * src/io.c, src/io.h: Added mmap, munmap log wrappers. 2004-10-04 * m4/autogen.sh: Updated to recognize newer automake. * Makefile.am: Added zvbi-0.2.pc. * zvbi-0.2.pc.in: Added. * configure.in: Restored proxy switch and output files. Added zvbi-0.2.pc output. Removed duplicate -lm in PNG_LIB. * src/io-v4l.c, src/io-v4l2k.c, src/io.c, src/io.h, src/decoder.c: Merged with proxy-18.bak. * daemon/Makefile.am, daemon/zvbid.init.in: Added from proxy branch and updated. * daemon/proxyd.c, daemon/README, daemon/zvbid.1, test/proxy-test.c, src/proxy-client.c, src/proxy-client.h, src/proxy-msg.c, src/proxy-msg.h: Added from proxy-18.bak. Tweaked cvs Log keyword to preserve Tom's comments. * test/Makefile.am: Restored proxy targets. * src/Makefile.am: Merged with proxy-18.bak. 2004-06-12 * test/README: Updated capture and osc tool documentation. * test/capture.c, test/osc.c: Added options to force use of a particular capture interface and to ignore read errors. Changed verbosity option from boolean to multi-level to enable ioctl logging. * src/io-v4l2k.c: Replaced by version from proxy branch (proxy-17.bak). s/signed char/int - only text is char. Interface extensions disabled for now. Added ioctl logging. Added preliminary hack to force read capture for tests. * src/io-bktr.c (vbi_capture_bktr_new): No more warning about unused rcsid. * src/io.c, src/io.h: Added vbi_capture_io_select and vbi_capture_io_update_timeout from proxy branch, ioctl logging from 0.3 branch. * src/Makefile.am: Added ioctl logging. * configure.in: Bumped version number. 2004-05-12 * m4/autogen.sh: Fixed non-Posix-ness of head args, reported by Stéphane Loeuillet. 2004-05-12 * Release 0.2.8 2004-04-25 * src/tables.c: Updated CNI table, with Arte/La Cinquième fix by Stéphane Loeuillet. 2004-04-09 * Release 0.2.7. 2004-04-09 * src/io-v4l2k.c: Incomplete v4l2_buffer initialization, doesn't work with bttv driver 0.9.12. 2004-04-04 * Release 0.2.6. 2004-02-19 * test/capture.c: Don't assert raw vbi data from DVB. 2004-02-18 * src/teletext.c: Fixes in debug code, bug item #893713. * src/Makefile.am: New file io-dvb.c. * src/io-dvb.c: New device interface contributed by Gerd Knorr. * src/dvb: DVB headers from Linux 2.6.1. * test/capture.c: Added PID option and DVB interface. 2004-01-02 * src/test/osc.c: Added patch by James Mastros. 2003-12-03 * src/teletext.c (top_navigation_bar): Segv if vtp->pgno == 0x899. 2003-11-13 * src: New misc.h from 0.3 branch. 2003-10-30 * autogen.sh, m4/autogen.sh: Updated. 2003-10-21 * Release 0.2.5. 2003-10-20 * configure.in, Makefile.am, src/Makefile.am, daemon: Proxy code is not ready for release, moved to a separate branch. * src/io-v4l2.c: No workee. Restored 0.2.4 i/o code. * src/caption.c, src/teletext.c, src/vbi.c, src/io-bktr.c: FreeBSD 5 compile fixes. * Cleanup. 2003-10-16 * src/bcd.h (vbi_dec2bcd, vbi_bcd2dec, vbi_add_bcd, vbi_is_bcd): Corrected documentation. 2003-10-14 * src/packet.c, src/trigger.c: Fixed unsafe use of strncpy. * daemon/zvbid.init.in: Added. Just an example for packagers, I cannot create an init script for each distro out there. * daemon/Makefile.am: Changed target ./proxyd to @sbindir@/zvbid. 'proxyd' was a bit too general. 2003-10-09 * src/exp-txt.c, src/io-v4l2.c, src/io-v4l2k.c: x86-64 fixes by Gwenole Beauchesne, submitted by Thierry Vignaud of MandrakeSoft. 2003-06-07 Periodic check-in for ongoing proxy implementation: * src/proxy-msg.c: Optimized client/server message I/O via socket. * daemon/proxyd.c: Added command line option -kill; Added devfs support (use /dev/v4l/vbi as default device if it exists.) Note: Changes in protocol require re-compilation of proxy clients. 2003-06-01 Periodic check-in for ongoing proxy implementation: * src/io-proxy.c: Redesigned internal message handling, i.e. switched from an event-driven model to a synchronous, RPC-like model. Also added TV channel change RPC. * daemon/proxyd.c: Started implementation of server-side TV channel switching (still incomplete: switching works, but scheduling and notifications are missing.) * src/io-v4l.c, io-v4l2k.c, io.c, io.h: Implemented TV channel switch. * io-v4l2k.c: Added optional support for preliminary ioctl S_CHNPRIO (with #ifdef USE_V4L2K_CHNPRIO) * test/proxy-test.c: Added tests for TV channel switching: new command line options -channel, -freq, -chnprio 2003-05-24 Periodic check-in for ongoing proxy implementation: * daemon/proxyd.c: allow multiple -dev arguments on the command line and serve all the given devices through multiple sockets in /tmp; added support for v4l drivers without select() by using threads to block in read(); handle SERVICE_REQ messages from proxy clients to support add_service() capture interface in io-proxy.c * src/io-proxy.c: Implemented new capture interfaces: add_services() and added get_poll_fd(), prepared flush() * src/io.c, src/io-v4l.c, io-v4l2.c, io-v4l2k.c: Added v4l_get_poll_fd() to return file handle only if driver supports poll() and select() * test/proxy-test.c: Added dynamic service switch to test add_service() interface: new function; added new service closed caption. 2003-05-17 * src/io.c: Added new interface function vbi_capture_add_services(); also prepared for new interface function vbi_capture_flush() * src/io-v4l.c, io-v4l2.c, io-v4l2k.c: Implemented new interface add_services(): add one or more services to an already initialized capture context; large internal changes, but existing interface functions should remain fully backwards compatible; also prepared for new interface function flush() * src/decoder.c: added new interface functions, required by io.c's new add_services(): vbi_raw_decoder_resize() to adapt for VBI geometry changes and vbi_raw_decoder_check_services() to check which of the given services can be decoded with current parameters * daemon/proxyd.c: uses new IO API function vbi_capture_add_services() 2003-05-10 * daemon/proxyd.c: bugfix: busy loop until the first client connect unless -nodetach option was used; also added copying of group and permissions from VBI device onto named socket path * daemon/README: added TODO list * src/io-proxy.c: bugfix proxy_read(): loop around select() until a complete VBI frame is received or timeout expired; before the function returned 0 when only a partial message was received, falsely indicating a timeout to the caller 2003-05-04 * src/caption.c (vbi_decode_caption): s/pthread_mutex_unlock/pthread_mutex_lock. 2003-05-03 * src/proxy-msg.c: follow synlinks in given device paths to allow both /dev/vbi and /dev/vbi0 to work as proxy device args * test/proxy-test.c: use vbi_capture_pull_sliced() instead of vbi_capture_read_sliced() * src/io.h: added declaration of vbi_capture_proxy_new() for inclusion in libzvbi.h 2003-05-02 * src/io-v4l2k.c: Missed one of Tom's fixes. * src/io-v4l2.c: Ported io-v4l2k.c fixes. 2003-04-26 Added proxy daemon by Tom Zoerner: * test/Makefile.am: Added proxy-test target. * test: Added proxy-test.c * src/Makefile.am: Added proxy targets. * src: Added io-proxy.c, proxy-msg.c, proxy-msg.h. * Added daemon dir (since we need a different Makefile.am), added Makefile.am, proxyd.c, README. * Makefile.am: Added daemon subdir. * configure.in: Added --disable-proxy switch and daemon/Makefile. 2003-04-26 * src/decoder.c (vbi_raw_decoder_add_services): There was a bug in the loop across the pattern array which caused heap corruption. Fix by Tom Zoerner. He also added some debug output, for now conditionally compiled in. * src/decoder.c (vbi_raw_decoder_remove_services): In the pattern array job indices were not adapted. Fix by Tom. * src/io-v4l.c (set_parameters): ioctl(VIDIOCSVBIFMT) result EINVAL led to a FALSE result value and regardless of the "strict" level to an abort. Actually EINVAL must be expected. Fix by Tom. * src/io-v4l.c (v4l_new): v->dec.offset default values for scanning == 625 were refused by vbi_raw_decoder_add_services(). Changed to be identical to the 525 case. Fix by Tom. See zapping-misc 2003-04-23 for details. 2003-02-17 * src/vbi.c, src/vbi.h: Added vbi_version(). 2003-02-16 * Release 0.2.4. 2003-02-15 * src/io-v4l2k.c: Fixed video standard detection. 2003-02-12 * src/videodev2k.h: Updated. * src/Makefile.am: Fixed improper linking of unicode library, not listed in libzvbi.la dependencies. 2003-01-05 * po/it.po: Update by Pino Toscano. * po/es.po: Update. 2002-12-14 * src/event.h: Wrong assumption on char signedness. 2002-12-14 * it.po: Italian translation, contributed by Pino Toscano. 2002-11-28 * Release 0.2.3. 2002-11-28 * src/exp-vtx.c: Segv due to excess read of variable size cached page structure. Patch #643211 by Art Pogoda. 2002-10-21 * src: A few char* were not const typed. 2002-10-17 * src/io-v4l2k.c, src/videodev2k.h, src/io.h (vbi_capture_v4l2k_new): Added. V4l2 api revision 2002-10 for Linux 2.5 (untested, have to wait for drivers :-). * src/io-v4l2.c: Added fallback to v4l2k.c. * src/io-bktr.c: Added interface to FreeBSD/OpenBSD/NetBSD bktr driver. Seems to work, more or less (bug or feature?). * src/export.c (vbi_ucs2be): Fixed format name UCS-2 (not UCS2). * test (getopt_long): Added fallback to getopt for non-GNU systems. * configure.in: New *BSD and getopt_long test. 2002-10-15 * src/event.h, src/ure.h: s/stdint.h/inttypes.h/ for BSD. * configure.in, src/Makefile.am, test/Makefile.am: -lpthread only on Linux. * src/io_v4l.c, src/io_v4l2.c: Did not compile when v4l/v4l2 disabled. 2002-10-11 * src/packet.c, test/capture.c: Wrong assumption on char signedness. * src/trigger.c (parse_atvef): Fix in type identification. 2002-10-07 * src/exp-gfx.c (vbi_draw_vt_page_region): Flash fix. Zapping not affected. 2002-10-04 * Release 0.2.2. 2002-10-01 * m4: Removed gtk-doc.m4, no longer needed. * Makefile.am: m4 in the dist. Thought it's unnecessary, but what the heck, it's not that much. * configure.in, test: Added two checks. 2002-09-28 * po/fr.po: Updated by Christian Marillat. 2002-09-26 * src/export.c, src/export.h, src/io.h, src/teletext.c: gettext() fix, should have been dgettext(). Oops. * src/wss.c: Aspect ratio event reported incorrect 16:9 anamorphic aspect 16/9, changed to 3/4. 2002-07-30 * src/cache.c: Fixed buffer overflow (SRTL bug). * src/exp-txt.c: Fixed double spaces and double height row bug in vbi_print_page_region(). * src/lang.c: Prime Hebrew won't fix, they transmit language code 0x00 English. Suggest per page language menu, for now added 0x80 entry in vbi_font_descriptors. * Prime CNI won't fix, they registered one but don't transmit. Another candidate for TODO #011. * src/decoder.c: Increased MAX_WAYS to fix ./osc --sim --pal identification of CC-625. 2002-07-04 * doc, src: Switched to Doxygen. 2002-06-22 * doc/Makefile.am: Modified to permit building libzvbi in a separate directory. * src/Makefile.am: Forgot to escape extern "C". * src/export.h: Removed C++ reserved export identifier. 2002-06-17 * m4, po, config.rpath: Added because cannot use autogen.sh gettextize --force since gettext 0.11. The fine hack insists on updating already updated Makefile.am's and configure.in. * po/Rules-quot: s/PACKAGE VERSION/... because msgfmt complains. 2002-06-17 gettextize * Makefile.am (SUBDIRS): Add m4. (SUBDIRS): Remove intl. (ACLOCAL_AMFLAGS): New variable. (EXTRA_DIST): Add config.rpath. * configure.in (AC_OUTPUT): Add m4/Makefile. 2002-06-14 * doc/zdoc-scan: Fix re zapping-Bugs-568052. 2002-06-13 * src/export.c: vbi_export_info_keyword() cuts option string off the keyword, a convenience. 2002-06-08 * src/packet.c: #if fix. * zvbi.spec.in: Removed libunicode requirement. * Release 0.2.1. 2002-05-23 * macros/autogen.sh: Updated. * Release 0.2. 2002-05-20 * configure.in: Inherit env CFLAGS. * teletext.c: Triggers a GCC 3.1 bug, do export CFLAGS=-V3.0.4 2002-04-28 * src/packet.c, src/vbi.h, src/event.h: Added Page Format - Clear (ETS 300 708) decoder. Future stuff. * src/teletext.c: Some work towards PDC preselection. 2002-04-20 * src/vbi.c, src/event.h: New handler functions identifying handler by func ptr and user data. * src/ure.c: If possible use glibc 2.1 wchar_t instead of libunicode. 2002-04-18 * src/io.c, src/io.h, src/io-v4l.c, src/io-v4l2.c: Added function to retrieve fd. * contrib: Added x11font by Gerd Knorr. * configure.in: Added contrib/Makefile. 2002-04-16 * src/caption.c: Corrected string length assertion in xds_decoder. 2002-04-13 * Corrected a few typographical errors in the docs. 2002-04-11 * src/io-v4l2.c: Gerd Knorr says bttv 0.8.x needs O_RDWR to PROT_WRITE. Nyquist check was missing. * test/capture.c: Gerd found missing timeval init. Miracle how it worked up to this point remains unsolved. 2002-04-09 * src/caption.c: Added ASCII range check before Unicode txl, re zapping-misc 2002-04-09. 2002-04-01 Release 0.1.1 * po/de.po: Updated. * po/es.po: Updated by I? G. Etxebarria. * po/pl.po: Updated by Pawel Sakowski. * Removed the version number from the library name, was a bad idea. 2002-03-19 Christian Marillat * po/fr.po: Updated. 2002-03-19 * src/io-v4l.c: Read loop fix, restored pthread_testcancel(); (still needed despite select()?), ETIME not ignored. * src/io-v4l2.c: Read loop fix, pthread_testcancel(); * Changes suggested by gcc 3.0.4. 2002-03-16 * src/Makefile.am: Automated libzvbi.h version #defines. 2002-03-10 zapping-Bugs-527984 * src/io-v4l2.c: Added mmap PROT_READ | PROT_WRITE for bttv 0.8.x. 2002-03-09 Bugfix * src/search.c: Fixed pattern highlighting, used to still skip gfx although now searchable. Segv in reverse search. * src/export-txt.c: vbi_print_page_region() return TRUE instead of actual bytes written. 2002-03-02 Misc * src/bcd.h: Extended vbi_add_bcd() and vbi_is_bcd() from 3 to 8 digits. * src/export.c: strncpy() fix in vbi_export_invalid_option(). * Dropped the libunicode requirement. Is only needed for ure.c which is needed by search.c. Search is now disabled when unicode is not installed. 2002-02-08 I/O stuff * src/io-v4l.c: Enabled select() for bttv. 2002-01-19 Fixes * src/io.c: vbi_capture_delete() not NULL safe, corrected. * src/search.c: Fixed non-regexp mode escape bug. * src/ure.c: Added character classes :gfx: and :drcs:. * src/exp-gfx.c: Fixed DRCS display. * src/exp-txt.c: Fixed color reset (VT100). 2002-01-17 V4L, build fixes * src/io_v4l.c: Added missing pixfmt initialization. Works great after that, great job. * Makefile.am, configure.in: Some build fixes. 2002-01-14 Restored V4L interface, more test stuff, fixes * src/io_v4l.c: Added, *untested*. * test/osc: Try v4l2, then v4l. * po: Updated. * src/hamm.c: Corrected char types (use char only for text, these are ints). * src/export.c: Bugfix in option_string(), didn't accept '-' and '_' in option keywords. * test/capture.c: Added, from old vbi_decoder(). * test/sim.c: Ditto, plus new Teletext simulation. * test: Updated, misc small improvements. 2002-01-13 Fixes * test/explist.h: Option type check. * doc/tmpl/sliced.sgml: Corrected .gif names. * src/export.c: Fixed vbi_export_option_menu_set(), didn't check for entry < 0. 2002-01-12 Imported libzvbi into Zapping CVS * Renamed to libzvbi to avoid a name conflict. VBI is an ubiquitious acronym and there are at least two other libvbi's around. * libzvbi.h: Now generated at compile time, so we can keep public and private definitions together, autodocs are filtered out. Added version #defines. * Separated bcd.h, event.h, search.h. Removed os.h. * Prefixed vbi_ and VBI_ a few remaining symbols, attr_stuff became vbi_stuff and fmt_page vbi_page. Purpose to avoid name conflicts since we're going public. * New vbi_char (former attr_char) encodes characters as Unicode to improve interoperability. Translation TTX/CC->Unicode in decoder, Unicode->glyph in export functions. This affects TTX combined glyphs, now only those covered by U+00A0 to U+017F can be decoded and displayed. Future Latin Ext-B? * exp_gfx.c: Changed PPM color depth from 4 to 8 bits. PNG export now works with Closed Caption pages. * exp_html.c: Teletext G1/G3 substituting and Network name in title doesn't exist anymore, XXX should be restored. * exp-txt.c: vbi_print_page replaced the string module used for cut&paste. ANSI/ASCII modules dropped, the new text module supports a larger number of character encodings. Improved color and ANSI/VT100 or VT200 sequences. * export.c: Upgraded the api to that used by rte 0.5+, which descended from here, so we have roughly the same everywhere. * teletext.c: NLSed TOP index page. * tables.c: Stripped the country table to what's actually needed, removed the station short names we never used. * cache.c: Added vbi_unref_page(). * v4lx.c: Completely replaced by a more generic version. * Copied libzvbi .po entries from Zapping here. * Added /test with various verification utilities. * Added gtk-doc and wrote some autodocs. 2001-11-01 Standalone libvbi * Extracted libvbi from Zapping , added Makefiles and stuff. Local Variables: mode: change-log coding: utf-8 left-margin: 8 fill-column: 76 End: zvbi-0.2.44/Makefile.am000066400000000000000000000016621476363111200146200ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in if ENABLE_PROXY proxy_dirs = daemon else proxy_dirs = endif EXTRA_DIST = \ BUGS \ COPYING \ COPYING.LIB \ config.rpath \ depcomp \ zvbi.spec.in DISTCLEANFILES = site_def.h # Attn order matters. SUBDIRS = \ m4 \ src \ $(proxy_dirs) \ contrib if ENABLE_EXAMPLES SUBDIRS += examples endif if ENABLE_TESTS SUBDIRS += test endif SUBDIRS += \ po \ doc ACLOCAL_AMFLAGS = -I m4 pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = zvbi-0.2.pc dist-hook: chown -R 500:100 $(distdir) fsf-addr-update: for dir in $(SUBDIRS); do \ cd $$dir ; \ for file in *.c *.h *.pl *.sh README ; do \ old='675 Mass Ave, Cambridge, MA 02139, USA' ; \ new='51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA' ; \ if grep -e "$$old" $$file ; then \ sed "s/$$old/$$new/" <$$file >$$file.tmp && mv $$file.tmp $$file ; \ fi done ; \ cd - ; \ done zvbi-0.2.44/NEWS000066400000000000000000000406621476363111200132660ustar00rootroot00000000000000libzvbi 0.2.44, 2025-03-10 -------------------------- Support a larger range of framerates, from 12.5fps to 60fps. Apply security updates. libzvbi 0.2.43, 2024-12-03 -------------------------- Fix building and enable testing for Windows platforms. Fix building for musl. Add options to disable tests and examples. Fix for static declaration of ‘sincos’ follows non-static declaration. libzvbi 0.2.42, 2023-08-25 -------------------------- Fix several compiler warnings. Apply patch to consider ERASE_PAGE flag with single page transmissions. libzvbi 0.2.41, 2023-02-13 -------------------------- In libzvbi.h, remove include version.h to fix include issue. libzvbi 0.2.40, 2023-02-07 -------------------------- Update version number macros. New copyright year. libzvbi 0.2.39, 2022-12-21 -------------------------- Updates to remove compiler warnings during tests. Allow autogen.sh and configure to run separately by default. Add Georgian language translation po files. libzvbi 0.2.38, 2022-11-30 -------------------------- Updates to remove compiler and Doxygen warnings. libzvbi 0.2.37, 2022-10-11 -------------------------- Fix cross-compile issues from autotools updates. Update header files to UTF-8 encoding. libzvbi 0.2.36, 2022-09-29 -------------------------- Update new maintainer and repository information after moving the project to github. Upgrade build system to new autotools versions. Apply simple patches from SourceForge and debian. libzvbi 0.2.35, 2013-08-28 -------------------------- Some corrections to allow compilation with clang/LLVM and uClibc, and a bug fix in test/time.c. libzvbi 0.2.34, 2013-07-11 -------------------------- This is a surprise bugfix release with improvements in the Teletext decoder and zvbi-atsc-cc. libzvbi 0.2.33, 2008-09-03 -------------------------- Fixes two zvbi-atsc-cc compile problems. libzvbi 0.2.32, 2008-08-20 -------------------------- This version adds a new tool named zvbi-atsc-cc to record Closed Caption from ATSC digital TV. It supports legacy NTSC caption (EIA 608-B) and DTVCC (CEA 708-C). libzvbi 0.2.31, 2008-07-26 -------------------------- The vbi_caption_unicode() function did not correctly translate the small letter i with diaresis. The files daemon/chains.c, daemon/proxyd.c, test/proxy-test.c and test/cc-test-stream.dtd are now licensed under GPLv2 or later as stated in the top README file. libzvbi 0.2.30, 2008-03-05 -------------------------- This is a bugfix release restoring the functions vbi_unref_page(), vbi_is_cached() and vbi_cache_hi_subno() which were accidentally dropped in libzvbi 0.2.28. libzvbi 0.2.29, 2008-02-24 -------------------------- This version fixes incompatibilities with strict ISO C in the libzvbi header file. libzvbi 0.2.28, 2008-02-22 -------------------------- The ZVBI libraries libzvbi and libzvbi-chains are now licensed under the terms of the GNU Library General Public License version 2 or later. Example programs are now licensed under a 2-clause BSD-style license. The VBI proxy daemon, contributed programs, test programs and programs required to build the library remain licensed under GNU General Public License version 2 or later. Bug fixes: - A Teletext Level 2.5 transparency attribute bug was fixed in the teletext decoder. - The capture test tool did not work with DVB devices. Other changes: - The VTX export module has been disabled for licensing reasons. It may return after a rewrite. - New BCD functions and vbi_ham24p(), counterpart of vbi_unham24p(), were added. - The artificial 41st column in Teletext pages is no longer black but continues the previous column if appropriate. - The --default-cs option of the export test tool now works with libzvbi 0.2 as well. libzvbi 0.2.27, 2008-02-14 -------------------------- This is a bugfix release. - The DVB VBI demultiplexer entered an endless loop on some input. - The Closed Caption special character conversion did not work. libzvbi 0.2.26, 2007-11-27 -------------------------- - The experimental DVB VBI multiplexer was improved and support for raw VBI data was added. It is now part of the API. - The bit slicer was improved to give better results with noisy Closed Caption signals and the capture device simulator can now simulate noisy signals. Thanks go to Rich Kadel for testing. - Tom Zoerner contributed an XPM export module and added support for an 8-bit indexed format to the Teletext page rendering functions. The PNG and PPM modules work a little more efficiently now. Two functions were added to export Teletext and Closed Caption pages into memory instead of an i/o stream or file. Bug fixes: - The V4L2 capture interface did not properly initialize the parameters for VIDIOC_QBUF and VIDIOC_DQBUF ioctl calls. It also failed to resume capturing after the bttv driver returned an EIO error. - The DVB capture interface did not work with all drivers because the device file was opened for reading and writing. - The vbi_sliced_name() and vbi_sliced_payload_bits() functions returned nothing for VBI_SLICED_TELETEXT_B_L25_625. - The Teletext decoder permitted double width characters in the last column. - In the vbi_pfc_demux_cb function prototype the second and third parameter were swapped. Fix by Tom Zoerner. - The vbi_decoder_delete function did not free the event handler list. - The text and HTML export modules did not free some internal data on errors. The test tools were improved: - All tools now support a --verbose option for debugging, and --quiet, --help, --usage and --version options. - The capture tool can now generate DVB VBI PES and TS streams with variable packet size to reduce the number of stuffing bytes, and user defined TS PID. An -o output file option was added. New device simulation options were added and raw VBI capturing is now possible. Capturing through the VBI proxy was added by Tom Zoerner. The timestamps in the output were incorrect if a frame contained no data, this was fixed. - The decode tool now accepts DVB VBI PES and TS streams and an -i input file option was added. IDL-A decoding works again. - The export tool now accepts DVB VBI PES and TS streams and an -i input file option was added. It can now export multiple pages, has a new -o output file option, and can list the possible file formats and conversion options. - The sliced2pes tool now also accepts DVB VBI PES and TS streams, has a new -i input and -o output file option, and can produce DVB VBI PES and TS streams with user defined packet size, data_identifier and TS PID. - The ttxfilter tool accepts DVB VBI PES and TS streams, has a new -i input and -o output file option. A time option was added to cut Teletext (esp. subtitle) streams. Various bugs were fixed. - Courtesy of Tom the osc tool can now capture raw data through the VBI proxy. - Raw VBI capturing with the proxy-test tool works again, also fixed by Tom. Build fixes: - The package did not compile with ./configure --disable-static (bug #1692015). libzvbi 0.2.25, 2007-03-09 -------------------------- This version fixes a buffer overflow bug in the zvbi-ntsc-cc tool which may cause a segfault on reception errors. Secondly it fixes a GNU/kFreeBSD compile problem (Debian bug #407621). libzvbi 0.2.24, 2006-11-29 -------------------------- This version fixes a bug in the zvbi-ntsc-cc tool which may cause a segfault on reception errors. libzvbi 0.2.23, 2006-10-27 -------------------------- This versions adds a few helper functions to convert Closed Caption and UCS-2 strings. The raw VBI decoder was modified to handle a CC signal with incorrect clock run-in. The zvbi-ntsc-cc tool can now decode all caption channels simultaneously and convert from caption to locale character encoding for proper display of Spanish captions. It can write caption and XDS information to separate files. Some bugs in the XDS decoder were fixed and a filter was added to discard unwanted XDS information and WebTV links. Fixes: - The library did not compile on GNU/kFreeBSD due to a name space conflict with the RAW macro (Debian bug #372302). libzvbi 0.2.22, 2006-05-30 -------------------------- This version adds a simulated raw and sliced VBI capture device to debug applications. The VBI simulation in the capture and osc tools is more accurate and VPS signals can be simulated. Functions to convert sliced VBI data to raw VBI images are public now and an example application was added. An interface to enable warning and debugging messages was added. Fixes: - Bit slicing of WSS data started a little early. It should work better now with noisy signals. - The WSS capture example used an incorrect bytes-per-line value. - A work-around for a line allocation bug in older versions of the bttv driver was added. Enables capturing of single lines (VPS, WSS, CC). libzvbi 0.2.21, 2006-05-10 -------------------------- This is a bugfix release. - Capturing WSS data from video images failed due to a broken check in the raw VBI decoder. libzvbi 0.2.20, 2006-05-08 -------------------------- This version supports raw VBI capturing with unknown field order, adds a work-around for a VBI offset bug in the bttv driver, enables additional debugging messages and improves some test tools. Network identification now returns CNIs and a network identification example was added. The CNI (networks) table was updated. libzvbi 0.2.19, 2006-02-23 -------------------------- This is a bugfix release. - Due to a preprocessor mishap zvbi-ntsc-cc did not use libzvbi but its own VBI decoder. libzvbi 0.2.18, 2006-02-11 -------------------------- This is a bugfix release. - The configure script has been modernized. Building from CVS now requires automake 1.9, autoconf 2.59 and gettext 0.14. - Diego Pettenò added a configure option to disable documentation building regardless if doxygen is installed. - After make clean src/Makefile tried to rebuild network-table.h from http://zapping.sourceforge.net/zvbi-0.3/networks.xml and other source files. This make rule was only intended for the maintainer. - A pointer aliasing bug in the linked list implementation of the Teletext cache, which may crash applications, has been fixed. - In previous versions GCC may not have inlined some bit slicer and page rendering functions, making them much slower. - The teletext filter added in 0.2.17 did not work with parallel page transmission (e.g. BBC). - The code was cleaned up to muffle compiler warnings (Diego). libzvbi 0.2.17, 2005-10-07 -------------------------- This version adds missing XDS packet subclasses to the XDS demultiplexer. The contrib directory now contains a version of the ntsc-cc Closed Caption decoder from the Xawtv package using libzvbi for driver access and bit slicing. It installs as zvbi-ntsc-cc. The CNI (networks) table was updated. The test tools were cleaned up and a teletext filter was added. Fixes: - The DVB multiplexer didn't work right (used by test tools only, not part of the API yet). - The ioctl logger (test stuff) didn't log VIDIOC_G_STD and VIDIOC_S_STD. - test/decode option -a didn't work. libzvbi 0.2.16, 2005-05-25 -------------------------- This version adds a new demultiplexer to extract XDS data from a caption stream (EIA 608). Fixes: - test/caption, test/osc did not compile due to a bug in the configure script. - Removed a misplaced assertion in vbi_capture_io_update_timeout(). - test/osc, test/capture, test/wss -d option segfaulted. - Fixed NTSC VBI capturing in the Linux saa7134 0.2.13 driver and added a partial work-around here for older versions. - The HTML export module segfaulted. Fix by Bernhard Rosenkraenzer. libzvbi 0.2.15, 2005-03-28 -------------------------- This is a bugfix release. Due to an incorrect bytes per line check previous versions could not capture VBI data from the ivtv driver. libzvbi 0.2.14, 2005-02-28 -------------------------- This version adds new demultiplexers to extract data transmissions from Teletext streams, namely in "Page Format Clear" format (e.g. NexTView EPG) and "Independent Data Line Format A". A new low-level decoding tool was added for tests. libzvbi 0.2.13, 2005-01-23 -------------------------- This version has new, more reliable DVB VBI capture routines. See the documentation of vbi_capture_dvb_new() for details. Fixes: - vbi_capture_bktr_new() ignored the scanning parameter, always assuming 625 line systems (e.g. PAL-BGHI, SECAM-L). - Include fix in test/wss.c. - vbi_unham8() return type signedness fix. - GCC 4.0 compile fixes. libzvbi 0.2.12, 2005-01-18 -------------------------- Various error correction functions, generator and bit reversal functions useful for VBI decoding are public now. A new test app has been added to capture WSS data from video images. French translation update by Christian Marillat. Bttv drivers 0.9.0 - 0.9.15 report wrong PAL/SECAM field 2 line numbers. A work-around has been added, and a bug compatible Teletext data service table entry has been corrected. If you encounter problems using similarly broken drivers please try a fuzzy match of driver capabilities and data service parameters (e.g. vbi_capture_v4l2_new(strict <= 1)) and notify mschimek@users.sf.net. The test/capture and test/osc apps did not capture PAL/SECAM line 23 (WSS), this has been fixed. libzvbi 0.2.11, 2004-12-31 -------------------------- Build fix. Cough. libzvbi 0.2.10, 2004-12-30 -------------------------- The bit slicer and raw VBI decoder have been replaced by improved and better tested versions. Developers please note: memory will leak if you do not call vbi_raw_decoder_destroy(). The raw decoder now supports Teletext system A, C and D and pseudo-VPS on the second field. Blank line detection has been disabled. As a result the decoder will run a bit slower but Robin Imrie pointed out that caption / subtitles may be lost if the signal inserter is disabled during longer periods of silence. The DVB demultiplexer added in 0.2.9 is now public. Other fixes: - In 0.2.9 the I/O routines may crash if passed a NULL errorstr pointer. - Replaced uname call in configure.in by AC_CANONICAL_HOST. - Missing documentation. 0.2.9 ----- This version contains the VBI proxy code by Tom Zoerner. With the proxy multiple applications can share a V4L or V4L2 VBI device. For details see daemon/README. Distro packagers please consider adding the proxy daemon to the system init sequence. Other changes: - Libzvbi now installs a zvbi-0.2.pc (pkg-config) file. - Added ioctl logging to V4L and V4L2 interface for tests. - Extended capture and osc apps for easier i/o testing. - Added experimental DVB mux & demux. Not yet supported by the DVB driver interface due to lack of testing but test/capture and test/export use it. Fixes: - Teletext decoder: switching between primary and secondary character set did not work right. 0.2.8 ----- Updated the network identification table from TR 101 231 EBU (2004-04a). Incorporated corrections by Stephane Loeuillet (bug #942434). 0.2.7 ----- Fixes a bug in the v4l2 interface, didn't work with bttv driver 0.9.12. 0.2.6 ----- Libzvbi goes digital! Gerd Knorr contributed a Linux DVB interface. It's not really finished yet but seems usable. You can try this: test/capture --device /dev/dvb/adapter/demux --pid 123 --dump-ttx Further James Mastros improved test/osc and a segv bug was fixed in the page formatting code. 0.2.5 ----- New this time is a vbi_version() function to query the version of the library at runtime. If your application uses autoconf to configure you can test for its presence with the AC_CHECK_LIB macro. The remaining changes are bug fixes, read about the details in ChangeLog. 0.2.4 ----- V4L2 (Linux 2.5+) devices were not correctly identified because the API changed since the interface was first added in version 0.2.3. This version contains an updated header file. 0.2.3 ----- Bug fixes, mostly compile problems. This version supports the refurbished V4L2 API for Linux 2.5ff and has been ported to FreeBSD 4.6, including a bktr driver interface. Basic tests ok, remains to be seen how well this works in the field. 0.2.2 ----- RH 7.3 build fix (zapping-Bugs-568052) and switch to gettext 0.11. Libzvbi can now build in a separate dir. Several other bug fixes, most notably localization works now. 0.2.1 ----- Bug fixes. 0.2 --- Bug fixes, most notably the character code range check in the Closed Caption decoder, a V4L2 interface update to work with bttv 0.8.x, and new handler registration functions (the old ones were retained for compatibility). 0.1.1 ----- Bug fixes. 0.1 --- Former module of the Zapping TV viewer, this is the first release of the Zapping/Zapzilla VBI decoder as a standalone library. Local Variables: coding: utf-8 End: zvbi-0.2.44/README.md000066400000000000000000000143761476363111200140510ustar00rootroot00000000000000 libzvbi - VBI capture and decoding library ========================================== The zvbi/libzvbi repository was moved here from [Sourceforge](https://sourceforge.net/projects/zapping) in September 2022 for current development. The Zapping TV viewer (and other projects from the Sourceforge) have also been [archived in github](https://github.com/zapping-vbi/vbi-archive). Introduction ============ The vertical blanking interval (VBI) is an interval in an analog television signal that temporarily suspends transmission of the signal for the electron gun to move back up to the first line of the television screen to trace the next screen field. The vertical blanking interval can be used to carry data, since anything sent during the VBI would naturally not be displayed; various test signals, closed captioning, and other digital data can be sent during this time period. The ZVBI library provides functions to read from Linux V4L, V4L2 and FreeBSD BKTR raw VBI capture devices, from Linux DVB devices and from a VBI proxy to share V4L and V4L2 VBI devices between multiple applications. It can demodulate raw to sliced VBI data in software, with support for a wide range of formats, has functions to decode several popular services including Teletext and Closed Caption, a Teletext cache with search function, various text export and rendering functions. Basically ZVBI offers all functions needed by VBI applications except for the user interface. The library was written for the [Zapping TV viewer](https://zapping.sourceforge.net). For details on using the library see the documentation in doc/html (built only if you have [Doxygen](https://www.doxygen.org/) installed) and the files in the test and examples directories. Download ======== See Releases and Tags for the latest version of zvbi and libzvbi. The first release on this repo is 0.2.36. Previous tagged releases can be found [here](https://github.com/zapping-vbi/vbi-archive). Ileana Dumitrescu \ maintains the [Debian package](https://packages.debian.org/source/zvbi). Oliver Lehmann \ maintains a [FreeBSD port](https://www.freebsd.org/cgi/ports.cgi?query=libzvbi). For RPMs please see the repository of your distribution. Building and Installation ========================= Obtain a local copy of this git repository by cloning: git clone https://github.com/zapping-vbi/zvbi.git This project uses GNU autotools. After cloning the repo, this command will create missing configuration files: cd zvbi && ./autogen.sh Next, to generate the Makefiles, run: ./configure Optionally, running `CONFIGURE=1 ./autogen.sh` will run the configure script immediately after the autogen script with a single command. After configuring, to compile and build, run: make If you want to run some tests, run: make check Then to install, run: make install The zvbi/libzvbi package produces the following binaries: * **zvbid** - proxy for VBI devices that forwards VBI data streams to one or more connected clients and manages channel change requests * **zvbi-atc-cc** - command-line utility that captures ATSC TV transmissions using a Linux DVB device and decodes the enclosed closed caption data * **zvbi-chains** - command-line wrapper that executes the VBI application while overloading several C library calls so that the application can be forced to access VBI devices via the VBI proxy instead of device files directly * **zvbi-ntsc-cc** - command-line utility for decoding and capturing closed captioning for NTSC and webtv * **libzvbi.[a, so, la]** - VBI decoding library If Doxygen is installed, `make` will also create library documentation. For additional instructions see the [INSTALL](https://github.com/zapping-vbi/zvbi/blob/main/INSTALL) file. The library has been tested on Linux and FreeBSD. Bindings ======== Tom Zoerner wrote a Perl interface to libzvbi. The ZVBI Perl module covers all exported libzvbi functions. Most of the functions and parameters are exposed nearly identical, or with minor adaptions for the Perl idiom. Find out more at https://search.cpan.org/~TOMZO/Video-ZVBI Contributing ============ Contributions are always welcome. Please feel free to write issues and patches, as well as create pull requests. When submitting pull requests, please create your own local branch first to merge into main. zvbi relevant bugs will be moved from [Sourceforge](https://sourceforge.net/p/zapping/bugs/) to github issues on this repo. zvbi patches from [Sourceforge](https://sourceforge.net/p/zapping/patches/) are being applied to the main branch. zvbi feature requests will also be moved from [Sourceforge](https://sourceforge.net/p/zapping/feature-requests/) to github issues on this repo. You can send an email to the maintainer directly with the tag \[zvbi\] in the subject line. You can browse the Sourceforge email list archives [here](http://sourceforge.net/mailarchive/forum.php?forum_name=zapping-misc). The current author and maintainer of this library is: Ileana Dumitrescu The previous author and maintainer of this library was: Michael H. Schimek Encrypted e-mail is welcome. You can find the authors' PGP/GPG keys in the AUTHORS file: gpg --import &1 | grep -q -e '^tcc version' ; then # If preprocessor output is required use GCC. AUTOGENOPTS="$AUTOGENOPTS CPP=cpp" # Optimizations. if ! echo "$CFLAGS" | grep -q -e '-O[s1-3]'; then CFLAGS="$CFLAGS -g -b -bt 10" fi # Warnings. CFLAGS="$CFLAGS -Wimplicit-function-declaration" # func use before declaration CFLAGS="$CFLAGS -Wunsupported" # unsupported GCC features CFLAGS="$CFLAGS -Wwrite-strings" # char *foo = "blah"; elif $CC -v 2>&1 | grep -q -e '^gcc version [3-9]\.' ; then # Optimizations. if echo "$CFLAGS" | grep -q -e '-O[s1-3]'; then CFLAGS="$CFLAGS -fomit-frame-pointer -pipe" else CFLAGS="$CFLAGS -O0 -g -pipe" fi # Warnings. CFLAGS="$CFLAGS -Wchar-subscripts" # array subscript has char type CFLAGS="$CFLAGS -Wcomment" # nested comments CFLAGS="$CFLAGS -Wformat" # printf format args mismatch CFLAGS="$CFLAGS -Wformat-y2k" # two-digit year strftime format CFLAGS="$CFLAGS -Wformat-nonliteral" # printf format cannot be checked CFLAGS="$CFLAGS -Wformat-security" # printf (var); where user may # supply var CFLAGS="$CFLAGS -Wnonnull" # function __attribute__ says # argument must be non-NULL CFLAGS="$CFLAGS -Wimplicit-int" # func decl without a return type CFLAGS="$CFLAGS -Wimplicit-function-declaration" # func use before declaration CFLAGS="$CFLAGS -Wmain" # wrong main() return type or args CFLAGS="$CFLAGS -Wmissing-braces" # int a[2][2] = { 0, 1, 2, 3 }; CFLAGS="$CFLAGS -Wparentheses" # if if else, or sth like x <= y <=z CFLAGS="$CFLAGS -Wsequence-point" # a = a++; CFLAGS="$CFLAGS -Wreturn-type" # void return in non-void function CFLAGS="$CFLAGS -Wswitch" # missing case in enum switch CFLAGS="$CFLAGS -Wtrigraphs" # suspicious ??x CFLAGS="$CFLAGS -Wunused-function" # defined static but not used CFLAGS="$CFLAGS -Wunused-label" CFLAGS="$CFLAGS -Wunused-parameter" CFLAGS="$CFLAGS -Wunused-variable" # declared but not used CFLAGS="$CFLAGS -Wunused-value" # return x, 0; CFLAGS="$CFLAGS -Wunknown-pragmas" # #pragma whatever CFLAGS="$CFLAGS -Wfloat-equal" # x == 1.000 CFLAGS="$CFLAGS -Wundef" # #undef X, #if X == 3 CFLAGS="$CFLAGS -Wendif-labels" # #endif BLAH #CFLAGS="$CFLAGS -Wshadow" # int foo; bar () { int foo; ... } CFLAGS="$CFLAGS -Wpointer-arith" # void *p = &x; ++p; CFLAGS="$CFLAGS -Wbad-function-cast" CFLAGS="$CFLAGS -Wcast-qual" # const int * -> int * CFLAGS="$CFLAGS -Wcast-align" # char * -> int * CFLAGS="$CFLAGS -Wwrite-strings" # char *foo = "blah"; CFLAGS="$CFLAGS -Wsign-compare" # int foo; unsigned bar; foo < bar #CFLAGS="$CFLAGS -Wconversion" # proto causes implicit type conversion CFLAGS="$CFLAGS -Wmissing-prototypes" # global func not declared in header CFLAGS="$CFLAGS -Wmissing-declarations" # global func not declared in header CFLAGS="$CFLAGS -Wpacked" # __attribute__((packed)) may hurt #CFLAGS="$CFLAGS -Wpadded" # struct fields were padded, different # order may save space CFLAGS="$CFLAGS -Wnested-externs" # extern declaration in function; # use header files, stupid! CFLAGS="$CFLAGS -Winline" # inline function cannot be inlined CFLAGS="$CFLAGS -Wall -W" # other useful warnings if ! echo "$CFLAGS" | grep -q -e '-O0' ; then CFLAGS="$CFLAGS -Wuninitialized" # int i; return i; CFLAGS="$CFLAGS -Wstrict-aliasing" # code may break C rules used for # optimization fi if $CC --version | grep -q -e '(GCC) [4-9]\.' ; then CFLAGS="$CFLAGS -Winit-self" # int i = i; CFLAGS="$CFLAGS -Wdeclaration-after-statement" # int i; i = 1; int j; CFLAGS="$CFLAGS -Wmissing-include-dirs" CFLAGS="$CFLAGS -Wmissing-field-initializers" # int a[2][2] = { 0, 1 }; if ! (echo "$CFLAGS" | grep -q -e '-O0') ; then CFLAGS="$CFLAGS -Wstrict-aliasing=2" fi fi fi # GCC >= 3.x export CFLAGS export CXXFLAGS=`echo $CFLAGS | sed \ -e s/-Wbad-function-cast// \ -e s/-Wdeclaration-after-statement// \ -e s/-Wimplicit-function-declaration// \ -e s/-Wimplicit-int// \ -e s/-Wmain// \ -e s/-Wmissing-declarations// \ -e s/-Wmissing-prototypes// \ -e s/-Wnested-externs// \ -e s/-Wnonnull// \ ` # autopoint extracts the gettext files from an ad-hoc CVS repository. export CVS_SIGN_COMMITS=off export CVS_VERIFY_CHECKOUTS=off ./autogen.sh $@ \ $AUTOGENOPTS \ PATH="$PATH" \ CC="$CC" \ CFLAGS="$CFLAGS" zvbi-0.2.44/autogen.sh000077500000000000000000000007071476363111200145640ustar00rootroot00000000000000#!/bin/sh # Run this to generate all the initial makefiles, etc. srcdir=`dirname $0` test -z "$srcdir" && srcdir=. PACKAGE=libzvbi ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I m4" # automake --copy: no links please (NFS) GETTEXTIZE_FLAGS="--copy --no-changelog" am_opt="--copy" (test -f $srcdir/configure.ac) || { echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" echo " top-level directory" exit 1 } . $srcdir/build-aux/autogen.sh zvbi-0.2.44/build-aux/000077500000000000000000000000001476363111200144515ustar00rootroot00000000000000zvbi-0.2.44/build-aux/autogen.sh000077500000000000000000000277211476363111200164630ustar00rootroot00000000000000#!/bin/sh # Copied from gnome-common 3.18.0-4 /usr/bin/gnome-autogen.sh # Run this to generate all the initial makefiles, etc. # default version requirements ... test "$REQUIRED_AUTOMAKE_VERSION" || REQUIRED_AUTOMAKE_VERSION=1.11.2 test "$REQUIRED_AUTORECONF_VERSION" || REQUIRED_AUTORECONF_VERSION=2.53 test "$REQUIRED_GLIB_GETTEXT_VERSION" || REQUIRED_GLIB_GETTEXT_VERSION=2.2.0 test "$REQUIRED_INTLTOOL_VERSION" || REQUIRED_INTLTOOL_VERSION=0.25 test "$REQUIRED_PKG_CONFIG_VERSION" || REQUIRED_PKG_CONFIG_VERSION=0.14.0 test "$REQUIRED_GTK_DOC_VERSION" || REQUIRED_GTK_DOC_VERSION=1.0 # a list of required m4 macros. Package can set an initial value test "$REQUIRED_M4MACROS" || REQUIRED_M4MACROS= # Not all echo versions allow -n, so we check what is possible. This test is # based on the one in autoconf. ECHO_C= ECHO_N= case `echo -n x` in -n*) case `echo 'x\c'` in *c*) ;; *) ECHO_C='\c';; esac;; *) ECHO_N='-n';; esac # some terminal codes ... if tty 1>/dev/null 2>&1; then boldface="`tput bold 2>/dev/null`" normal="`tput sgr0 2>/dev/null`" else boldface= normal= fi printbold() { echo $ECHO_N "$boldface" $ECHO_C echo "$@" echo $ECHO_N "$normal" $ECHO_C } printerr() { echo "$@" >&2 } check_deprecated() { variable=$1 if set | grep "^$variable=" > /dev/null; then printerr "***Warning*** $1 is deprecated, you may remove it from autogen.sh" fi } check_deprecated REQUIRED_GNOME_DOC_UTILS_VERSION check_deprecated REQUIRED_DOC_COMMON_VERSION check_deprecated USE_COMMON_DOC_BUILD check_deprecated FORBIDDEN_M4MACROS check_deprecated GNOME2_DIR check_deprecated GNOME2_PATH check_deprecated USE_GNOME2_MACROS check_deprecated PKG_NAME if [ -z "$srcdir" ]; then printerr "***Warning*** \$srcdir is not defined, out of dir autogen is broken!" srcdir=. fi PKG_NAME=`autoconf --trace='AC_INIT:$1' "$srcdir/configure.ac"` # Usage: # compare_versions MIN_VERSION ACTUAL_VERSION # returns true if ACTUAL_VERSION >= MIN_VERSION compare_versions() { ch_min_version=$1 ch_actual_version=$2 ch_status=0 IFS="${IFS= }"; ch_save_IFS="$IFS"; IFS="." set $ch_actual_version for ch_min in $ch_min_version; do ch_cur=`echo $1 | sed 's/[^0-9].*$//'`; shift # remove letter suffixes if [ -z "$ch_min" ]; then break; fi if [ -z "$ch_cur" ]; then ch_status=1; break; fi if [ $ch_cur -gt $ch_min ]; then break; fi if [ $ch_cur -lt $ch_min ]; then ch_status=1; break; fi done IFS="$ch_save_IFS" return $ch_status } # Usage: # version_check PACKAGE VARIABLE CHECKPROGS MIN_VERSION SOURCE # checks to see if the package is available version_check() { vc_package=$1 vc_variable=$2 vc_checkprogs=$3 vc_min_version=$4 vc_source=$5 vc_status=1 local ${vc_variable}_VERSION vc_checkprog=`eval echo "\\$$vc_variable"` if [ -n "$vc_checkprog" ]; then printbold "using $vc_checkprog for $vc_package" return 0 fi printbold "checking for $vc_package >= $vc_min_version..." for vc_checkprog in $vc_checkprogs; do echo $ECHO_N " testing $vc_checkprog... " $ECHO_C if $vc_checkprog --version < /dev/null > /dev/null 2>&1; then vc_actual_version=`$vc_checkprog --version | head -n 1 | \ sed 's/^.*[ ]\([0-9.]*[a-z]*\).*$/\1/'` if compare_versions $vc_min_version $vc_actual_version; then echo "found $vc_actual_version" # set variables eval "$vc_variable=$vc_checkprog; \ ${vc_variable}_VERSION=$vc_actual_version" vc_status=0 break else echo "too old (found version $vc_actual_version)" fi else echo "not found." fi done if [ "$vc_status" != 0 ]; then printerr "***Error***: You must have $vc_package >= $vc_min_version installed" printerr " to build $PKG_NAME. Download the appropriate package for" printerr " from your distribution or get the source tarball at" printerr " $vc_source" printerr exit $vc_status fi return $vc_status } # Usage: # require_m4macro filename.m4 # adds filename.m4 to the list of required macros require_m4macro() { case "$REQUIRED_M4MACROS" in $1\ * | *\ $1\ * | *\ $1) ;; *) REQUIRED_M4MACROS="$REQUIRED_M4MACROS $1" ;; esac } # Usage: # add_to_cm_macrodirs dirname # Adds the dir to $cm_macrodirs, if it's not there yet. add_to_cm_macrodirs() { case $cm_macrodirs in "$1 "* | *" $1 "* | *" $1") ;; *) cm_macrodirs="$cm_macrodirs $1";; esac } # Usage: # print_m4macros_error # Prints an error message saying that autoconf macros were misused print_m4macros_error() { printerr "***Error***: some autoconf macros required to build $PKG_NAME" printerr " were not found in your aclocal path, or some forbidden" printerr " macros were found. Perhaps you need to adjust your" printerr " ACLOCAL_PATH?" printerr } # Usage: # check_m4macros # Checks that all the requested macro files are in the aclocal macro path # Uses REQUIRED_M4MACROS and ACLOCAL_PATH variables. check_m4macros() { # construct list of macro directories cm_macrodirs=`aclocal --print-ac-dir` # aclocal also searches a version specific dir, eg. /usr/share/aclocal-1.9 # but it contains only Automake's own macros, so we can ignore it. # Read the dirlist file if [ -s $cm_macrodirs/dirlist ]; then cm_dirlist=`sed 's/[ ]*#.*//;/^$/d' $cm_macrodirs/dirlist` if [ -n "$cm_dirlist" ] ; then for cm_dir in $cm_dirlist; do if [ -d $cm_dir ]; then add_to_cm_macrodirs $cm_dir fi done fi fi # Parse $ACLOCAL_PATH IFS="${IFS= }"; save_IFS="$IFS"; IFS=":" for dir in ${ACLOCAL_PATH}; do add_to_cm_macrodirs "$dir" done IFS="$save_IFS" cm_status=0 if [ -n "$REQUIRED_M4MACROS" ]; then printbold "Checking for required M4 macros..." # check that each macro file is in one of the macro dirs for cm_macro in $REQUIRED_M4MACROS; do cm_macrofound=false for cm_dir in $cm_macrodirs; do if [ -f "$cm_dir/$cm_macro" ]; then cm_macrofound=true break fi # The macro dir in Cygwin environments may contain a file # called dirlist containing other directories to look in. if [ -f "$cm_dir/dirlist" ]; then for cm_otherdir in `cat $cm_dir/dirlist`; do if [ -f "$cm_otherdir/$cm_macro" ]; then cm_macrofound=true break fi done fi done if $cm_macrofound; then : else printerr " $cm_macro not found" cm_status=1 fi done fi if [ "$cm_status" != 0 ]; then print_m4macros_error exit $cm_status fi if [ "$cm_status" != 0 ]; then print_m4macros_error exit $cm_status fi } want_glib_gettext=false want_intltool=false want_pkg_config=false want_gtk_doc=false want_maintainer_mode=false version_check automake AUTOMAKE automake $REQUIRED_AUTOMAKE_VERSION \ "http://ftp.gnu.org/pub/gnu/automake/automake-$REQUIRED_AUTOMAKE_VERSION.tar.xz" version_check autoreconf AUTORECONF autoreconf $REQUIRED_AUTORECONF_VERSION \ "http://ftp.gnu.org/pub/gnu/autoconf/autoconf-$REQUIRED_AUTORECONF_VERSION.tar.xz" find_configure_files() { configure_ac= if test -f "$1/configure.ac"; then configure_ac="$1/configure.ac" elif test -f "$1/configure.in"; then configure_ac="$1/configure.in" fi if test "x$configure_ac" != x; then echo "$configure_ac" autoconf -t 'AC_CONFIG_SUBDIRS:$1' "$configure_ac" | while read dir; do find_configure_files "$1/$dir" done fi } configure_files="`find_configure_files $srcdir`" for configure_ac in $configure_files; do dirname=`dirname $configure_ac` if [ -f $dirname/NO-AUTO-GEN ]; then echo skipping $dirname -- flagged as no auto-gen continue fi if grep "^AM_GLIB_GNU_GETTEXT" $configure_ac >/dev/null; then want_glib_gettext=true fi if grep "^AC_PROG_INTLTOOL" $configure_ac >/dev/null || grep "^IT_PROG_INTLTOOL" $configure_ac >/dev/null; then want_intltool=true fi if grep "^PKG_CHECK_MODULES" $configure_ac >/dev/null; then want_pkg_config=true fi if grep "^GTK_DOC_CHECK" $configure_ac >/dev/null; then want_gtk_doc=true fi # check that AM_MAINTAINER_MODE is used if grep "^AM_MAINTAINER_MODE" $configure_ac >/dev/null; then want_maintainer_mode=true fi if grep "^YELP_HELP_INIT" $configure_ac >/dev/null; then require_m4macro yelp.m4 fi if grep "^APPDATA_XML" $configure_ac >/dev/null; then require_m4macro appdata-xml.m4 fi if grep "^APPSTREAM_XML" $configure_ac >/dev/null; then require_m4macro appstream-xml.m4 fi # check to make sure gnome-common macros can be found ... if grep "^GNOME_COMMON_INIT" $configure_ac >/dev/null || grep "^GNOME_DEBUG_CHECK" $configure_ac >/dev/null || grep "^GNOME_MAINTAINER_MODE_DEFINES" $configure_ac >/dev/null; then require_m4macro gnome-common.m4 fi if grep "^GNOME_COMPILE_WARNINGS" $configure_ac >/dev/null || grep "^GNOME_CXX_WARNINGS" $configure_ac >/dev/null; then require_m4macro gnome-compiler-flags.m4 fi if grep "^GNOME_CODE_COVERAGE" $configure_ac >/dev/null; then require_m4macro gnome-code-coverage.m4 fi done if $want_glib_gettext; then version_check glib-gettext GLIB_GETTEXTIZE glib-gettextize $REQUIRED_GLIB_GETTEXT_VERSION \ "ftp://ftp.gtk.org/pub/gtk/v2.2/glib-$REQUIRED_GLIB_GETTEXT_VERSION.tar.gz" require_m4macro glib-gettext.m4 fi if $want_intltool; then version_check intltool INTLTOOLIZE intltoolize $REQUIRED_INTLTOOL_VERSION \ "http://ftp.gnome.org/pub/GNOME/sources/intltool/" require_m4macro intltool.m4 fi if $want_pkg_config; then version_check pkg-config PKG_CONFIG pkg-config $REQUIRED_PKG_CONFIG_VERSION \ "'http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-$REQUIRED_PKG_CONFIG_VERSION.tar.gz" require_m4macro pkg.m4 fi if $want_gtk_doc; then version_check gtk-doc GTKDOCIZE gtkdocize $REQUIRED_GTK_DOC_VERSION \ "http://ftp.gnome.org/pub/GNOME/sources/gtk-doc/" require_m4macro gtk-doc.m4 fi check_m4macros topdir=`pwd` for configure_ac in $configure_files; do dirname=`dirname $configure_ac` basename=`basename $configure_ac` if [ -f $dirname/NO-AUTO-GEN ]; then echo skipping $dirname -- flagged as no auto-gen elif [ ! -w $dirname ]; then echo skipping $dirname -- directory is read only else printbold "Processing $configure_ac" cd $dirname # if the AC_CONFIG_MACRO_DIR() macro is used, create that directory # This is a automake bug fixed in automake 1.13.2 # See http://debbugs.gnu.org/cgi/bugreport.cgi?bug=13514 m4dir=`autoconf --trace 'AC_CONFIG_MACRO_DIR:$1'` if [ -n "$m4dir" ]; then mkdir -p $m4dir fi if grep "^AM_GLIB_GNU_GETTEXT" $basename >/dev/null; then printbold "Running $GLIB_GETTEXTIZE... Ignore non-fatal messages." echo "no" | $GLIB_GETTEXTIZE --force --copy || exit 1 fi if grep "^GTK_DOC_CHECK" $basename >/dev/null; then printbold "Running $GTKDOCIZE..." $GTKDOCIZE --copy || exit 1 fi if grep "^AC_PROG_INTLTOOL" $basename >/dev/null || grep "^IT_PROG_INTLTOOL" $basename >/dev/null; then printbold "Running $INTLTOOLIZE..." $INTLTOOLIZE --force --copy --automake || exit 1 fi # Now that all the macros are sorted, run autoreconf ... printbold "Running autoreconf..." autoreconf --verbose --force --install -Wno-portability || exit 1 cd "$topdir" fi done conf_flags="" if $want_maintainer_mode; then conf_flags="--enable-maintainer-mode" fi if [ "$#" = 0 -a "$CONFIGURE" = 1 ]; then printerr "**Warning**: I am going to run \`configure' with no arguments." printerr "If you wish to pass any to it, please specify them on the" printerr \`$0\'" command line." printerr fi if test x$CONFIGURE = x1; then printbold Running $srcdir/configure $conf_flags "$@" ... $srcdir/configure $conf_flags "$@" \ && echo Now type \`make\' to compile $PKG_NAME || exit 1 else echo Now type \`./configure\` to generate Makefiles. fi zvbi-0.2.44/build-aux/config.rpath000077500000000000000000000442161476363111200167700ustar00rootroot00000000000000#! /bin/sh # Output a system dependent set of variables, describing how to set the # run time search path of shared libraries in an executable. # # Copyright 1996-2020 Free Software Foundation, Inc. # Taken from GNU libtool, 2001 # Originally by Gordon Matzigkeit , 1996 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # The first argument passed to this file is the canonical host specification, # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld # should be set by the caller. # # The set of defined variables is at the end of this script. # Known limitations: # - On IRIX 6.5 with CC="cc", the run time search patch must not be longer # than 256 bytes, otherwise the compiler driver will dump core. The only # known workaround is to choose shorter directory names for the build # directory and/or the installation directory. # All known linkers require a '.a' archive for static linking (except MSVC, # which needs '.lib'). libext=a shrext=.so host="$1" host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` # Code taken from libtool.m4's _LT_CC_BASENAME. for cc_temp in $CC""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done cc_basename=`echo "$cc_temp" | sed -e 's%^.*/%%'` # Code taken from libtool.m4's _LT_COMPILER_PIC. wl= if test "$GCC" = yes; then wl='-Wl,' else case "$host_os" in aix*) wl='-Wl,' ;; mingw* | cygwin* | pw32* | os2* | cegcc*) ;; hpux9* | hpux10* | hpux11*) wl='-Wl,' ;; irix5* | irix6* | nonstopux*) wl='-Wl,' ;; linux* | k*bsd*-gnu | kopensolaris*-gnu) case $cc_basename in ecc*) wl='-Wl,' ;; icc* | ifort*) wl='-Wl,' ;; lf95*) wl='-Wl,' ;; nagfor*) wl='-Wl,-Wl,,' ;; pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) wl='-Wl,' ;; ccc*) wl='-Wl,' ;; xl* | bgxl* | bgf* | mpixl*) wl='-Wl,' ;; como) wl='-lopt=' ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ F* | *Sun*Fortran*) wl= ;; *Sun\ C*) wl='-Wl,' ;; esac ;; esac ;; newsos6) ;; *nto* | *qnx*) ;; osf3* | osf4* | osf5*) wl='-Wl,' ;; rdos*) ;; solaris*) case $cc_basename in f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) wl='-Qoption ld ' ;; *) wl='-Wl,' ;; esac ;; sunos4*) wl='-Qoption ld ' ;; sysv4 | sysv4.2uw2* | sysv4.3*) wl='-Wl,' ;; sysv4*MP*) ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) wl='-Wl,' ;; unicos*) wl='-Wl,' ;; uts4*) ;; esac fi # Code taken from libtool.m4's _LT_LINKER_SHLIBS. hardcode_libdir_flag_spec= hardcode_libdir_separator= hardcode_direct=no hardcode_minus_L=no case "$host_os" in cygwin* | mingw* | pw32* | cegcc*) # FIXME: the MSVC++ port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++. if test "$GCC" != yes; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++) with_gnu_ld=yes ;; openbsd*) with_gnu_ld=no ;; esac ld_shlibs=yes if test "$with_gnu_ld" = yes; then # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. # Unlike libtool, we use -rpath here, not --rpath, since the documented # option of GNU ld is called -rpath, not --rpath. hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' case "$host_os" in aix[3-9]*) # On AIX/PPC, the GNU linker is very broken if test "$host_cpu" != ia64; then ld_shlibs=no fi ;; amigaos*) case "$host_cpu" in powerpc) ;; m68k) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; beos*) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; cygwin* | mingw* | pw32* | cegcc*) # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec='-L$libdir' if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then : else ld_shlibs=no fi ;; haiku*) ;; interix[3-9]*) hardcode_direct=no hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; netbsd*) ;; solaris*) if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then ld_shlibs=no elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) ld_shlibs=no ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' else ld_shlibs=no fi ;; esac ;; sunos4*) hardcode_direct=yes ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; esac if test "$ld_shlibs" = no; then hardcode_libdir_flag_spec= fi else case "$host_os" in aix3*) # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. hardcode_minus_L=yes if test "$GCC" = yes; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. hardcode_direct=unsupported fi ;; aix[4-9]*) if test "$host_cpu" = ia64; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no else aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # need to do runtime linking. case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) for ld_flag in $LDFLAGS; do if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then aix_use_runtimelinking=yes break fi done ;; esac fi hardcode_direct=yes hardcode_libdir_separator=':' if test "$GCC" = yes; then case $host_os in aix4.[012]|aix4.[012].*) collect2name=`${CC} -print-prog-name=collect2` if test -f "$collect2name" && \ strings "$collect2name" | grep resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct=unsupported hardcode_minus_L=yes hardcode_libdir_flag_spec='-L$libdir' hardcode_libdir_separator= fi ;; esac fi # Begin _LT_AC_SYS_LIBPATH_AIX. echo 'int main () { return 0; }' > conftest.c ${CC} ${LDFLAGS} conftest.c -o conftest aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` if test -z "$aix_libpath"; then aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` fi if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib" fi rm -f conftest.c conftest # End _LT_AC_SYS_LIBPATH_AIX. if test "$aix_use_runtimelinking" = yes; then hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" else if test "$host_cpu" = ia64; then hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' else hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" fi fi ;; amigaos*) case "$host_cpu" in powerpc) ;; m68k) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; bsdi[45]*) ;; cygwin* | mingw* | pw32* | cegcc*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec=' ' libext=lib ;; darwin* | rhapsody*) hardcode_direct=no if { case $cc_basename in ifort*) true;; *) test "$GCC" = yes;; esac; }; then : else ld_shlibs=no fi ;; dgux*) hardcode_libdir_flag_spec='-L$libdir' ;; freebsd2.[01]*) hardcode_direct=yes hardcode_minus_L=yes ;; freebsd* | dragonfly*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; hpux9*) hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; hpux10*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes fi ;; hpux11*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: case $host_cpu in hppa*64*|ia64*) hardcode_direct=no ;; *) hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; netbsd*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; newsos6) hardcode_direct=yes hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; *nto* | *qnx*) ;; openbsd*) if test -f /usr/libexec/ld.so; then hardcode_direct=yes if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then hardcode_libdir_flag_spec='${wl}-rpath,$libdir' else case "$host_os" in openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) hardcode_libdir_flag_spec='-R$libdir' ;; *) hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; esac fi else ld_shlibs=no fi ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; osf3*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; osf4* | osf5*) if test "$GCC" = yes; then hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' else # Both cc and cxx compiler support -rpath directly hardcode_libdir_flag_spec='-rpath $libdir' fi hardcode_libdir_separator=: ;; solaris*) hardcode_libdir_flag_spec='-R$libdir' ;; sunos4*) hardcode_libdir_flag_spec='-L$libdir' hardcode_direct=yes hardcode_minus_L=yes ;; sysv4) case $host_vendor in sni) hardcode_direct=yes # is this really true??? ;; siemens) hardcode_direct=no ;; motorola) hardcode_direct=no #Motorola manual says yes, but my tests say they lie ;; esac ;; sysv4.3*) ;; sysv4*MP*) if test -d /usr/nec; then ld_shlibs=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) ;; sysv5* | sco3.2v5* | sco5v6*) hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' hardcode_libdir_separator=':' ;; uts4*) hardcode_libdir_flag_spec='-L$libdir' ;; *) ld_shlibs=no ;; esac fi # Check dynamic linker characteristics # Code taken from libtool.m4's _LT_SYS_DYNAMIC_LINKER. # Unlike libtool.m4, here we don't care about _all_ names of the library, but # only about the one the linker finds when passed -lNAME. This is the last # element of library_names_spec in libtool.m4, or possibly two of them if the # linker has special search rules. library_names_spec= # the last element of library_names_spec in libtool.m4 libname_spec='lib$name' case "$host_os" in aix3*) library_names_spec='$libname.a' ;; aix[4-9]*) library_names_spec='$libname$shrext' ;; amigaos*) case "$host_cpu" in powerpc*) library_names_spec='$libname$shrext' ;; m68k) library_names_spec='$libname.a' ;; esac ;; beos*) library_names_spec='$libname$shrext' ;; bsdi[45]*) library_names_spec='$libname$shrext' ;; cygwin* | mingw* | pw32* | cegcc*) shrext=.dll library_names_spec='$libname.dll.a $libname.lib' ;; darwin* | rhapsody*) shrext=.dylib library_names_spec='$libname$shrext' ;; dgux*) library_names_spec='$libname$shrext' ;; freebsd[23].*) library_names_spec='$libname$shrext$versuffix' ;; freebsd* | dragonfly*) library_names_spec='$libname$shrext' ;; gnu*) library_names_spec='$libname$shrext' ;; haiku*) library_names_spec='$libname$shrext' ;; hpux9* | hpux10* | hpux11*) case $host_cpu in ia64*) shrext=.so ;; hppa*64*) shrext=.sl ;; *) shrext=.sl ;; esac library_names_spec='$libname$shrext' ;; interix[3-9]*) library_names_spec='$libname$shrext' ;; irix5* | irix6* | nonstopux*) library_names_spec='$libname$shrext' case "$host_os" in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;; *) libsuff= shlibsuff= ;; esac ;; esac ;; linux*oldld* | linux*aout* | linux*coff*) ;; linux* | k*bsd*-gnu | kopensolaris*-gnu) library_names_spec='$libname$shrext' ;; knetbsd*-gnu) library_names_spec='$libname$shrext' ;; netbsd*) library_names_spec='$libname$shrext' ;; newsos6) library_names_spec='$libname$shrext' ;; *nto* | *qnx*) library_names_spec='$libname$shrext' ;; openbsd*) library_names_spec='$libname$shrext$versuffix' ;; os2*) libname_spec='$name' shrext=.dll library_names_spec='$libname.a' ;; osf3* | osf4* | osf5*) library_names_spec='$libname$shrext' ;; rdos*) ;; solaris*) library_names_spec='$libname$shrext' ;; sunos4*) library_names_spec='$libname$shrext$versuffix' ;; sysv4 | sysv4.3*) library_names_spec='$libname$shrext' ;; sysv4*MP*) library_names_spec='$libname$shrext' ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) library_names_spec='$libname$shrext' ;; tpf*) library_names_spec='$libname$shrext' ;; uts4*) library_names_spec='$libname$shrext' ;; esac sed_quote_subst='s/\(["`$\\]\)/\\\1/g' escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"` shlibext=`echo "$shrext" | sed -e 's,^\.,,'` escaped_libname_spec=`echo "X$libname_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_library_names_spec=`echo "X$library_names_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` LC_ALL=C sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' < dnl log2() is a GNU extension (a macro, not a function). dnl If not present we use a replacement. AC_MSG_CHECKING([for log2]) AC_LINK_IFELSE([AC_LANG_SOURCE([ #include #include int main (void) { double x; scanf ("%f", &x); printf ("%f", log2 (x)); return 0; } ])],[ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_LOG2, 1, [Define if the log2() function is available]) ],[ AC_MSG_RESULT([no]) ]) dnl strerror() is not thread safe and there are different versions dnl of strerror_r(). If none of them are present we use a replacement. AC_MSG_CHECKING([for strerror_r]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include #include int main (void) { return *strerror_r (22, malloc (128), 128); } ])],[ AC_MSG_RESULT([yes, GNU version]) AC_DEFINE(HAVE_GNU_STRERROR_R, 1, [Define to 1 if you have the GNU version of the strerror_r() function.]) ],[ AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include #include #include int main (void) { printf ("%f", 1.0 + strerror_r (22, malloc (128), 128)); return 0; } ])],[ AC_MSG_RESULT([yes, SUSV3 version]) AC_DEFINE(HAVE_SUSV3_STRERROR_R, 1, [Define to 1 if you have the SUSV3 version of the strerror_r() function.]) ],[ AC_MSG_RESULT([no]) ]) ]) dnl __BYTE_ORDER is not portable. AC_DEFINE(Z_LITTLE_ENDIAN, 1234, [naidne elttiL]) AC_DEFINE(Z_BIG_ENDIAN, 4321, [Big endian]) AC_C_BIGENDIAN( AC_DEFINE(Z_BYTE_ORDER, 4321, [Byte order]), AC_DEFINE(Z_BYTE_ORDER, 1234, [Byte order])) dnl Initialize libtool dnl ("LT_INIT" replaces "AC_PROG_LIBTOOL") LT_INIT dnl Generate "site_def.h" test -e site_def.h || cat <site_def.h /* Site specific definitions */ #ifndef SITE_DEF_H #define SITE_DEF_H /* #define BIT_SLICER_LOG 1 */ /* #define CACHE_DEBUG 1 */ /* #define CACHE_DEBUG 2 */ /* #define CACHE_STATUS 1 */ /* #define CACHE_CONSISTENCY 1 */ /* #define DVB_DEMUX_LOG 1 */ /* #define DVB_MUX_LOG 1 */ /* #define RAW_DECODER_LOG 1 */ /* #define RAW_DECODER_PATTERN_DUMP 1 */ /* #define TELETEXT_DEBUG 1 */ #endif /* SITE_DEF_H */ EOF dnl option, define/conditional name AC_DEFUN([CHECK_CC_OPTION], [ AC_MSG_CHECKING([if $CC supports $1]) SAVE_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $1" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [return 0;])], [$2=yes], [$2=no]) CFLAGS="$SAVE_CFLAGS" AC_MSG_RESULT($$2) AM_CONDITIONAL($2, [test "x$$2" = "xyes"])]) dnl option, define/conditional name AC_DEFUN([CHECK_CXX_OPTION], [ AC_MSG_CHECKING([if $CXX supports $1]) SAVE_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS $1" AC_LANG_PUSH(C++) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [return 0;])], [$2=yes], [$2=no]) AC_LANG_POP() CXXFLAGS="$SAVE_CXXFLAGS" AC_MSG_RESULT($$2) AM_CONDITIONAL($2, [test "x$$2" = "xyes"])]) dnl See if we can / have to increase inlining limits. CHECK_CC_OPTION([--param inline-unit-growth=3000], HAVE_GCC_LIMITS) dnl For make check. CHECK_CC_OPTION([-std=c89], HAVE_GCC_C89_SUPPORT) CHECK_CC_OPTION([-std=iso9899:199409], HAVE_GCC_C94_SUPPORT) CHECK_CC_OPTION([-std=c99], HAVE_GCC_C99_SUPPORT) CHECK_CXX_OPTION([-std=c++98], HAVE_GXX_CXX98_SUPPORT) dnl dnl Check how to link pthreads functions. dnl (-lpthread on Linux, -lpthreadGC2 [from the pthreads-win32. dnl package] on MinGW, -pthread on FreeBSD). dnl dnl When upgrading to autoconf 2.71, AC_TRY_LINK deprecated and replaced dnl with AC_LINK_IFELSE. dnl AC_CHECK_LIB(pthread, pthread_create,,[ AC_CHECK_LIB(pthreadGC2, pthread_create,,[ AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[pthread_create();]])],[],[ # same as previous, but use '-pthread' instead of '-lpthread' LDFLAGS="$LDFLAGS -pthread" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[pthread_create();]])],[],[ AC_MSG_ERROR([Unable to link pthread functions]) ]) ]) ]) ]) dnl dnl See if struct tm has tm_gmtoff, a BSD/GNU extension. dnl (Used in test/date.) dnl AC_MSG_CHECKING([if struct tm has tm_gmtoff]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include int main (void) { struct tm tm; tm.tm_gmtoff = 0; return 0; } ])],[ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_TM_GMTOFF, 1, [Define if struct tm has a tm_gmtoff field]) ],[ AC_MSG_RESULT([no]) ]) dnl dnl Check for Gnome unicode library or libc 2.1. dnl (Teletext URE search wchar_t ctype.h functions) dnl AC_MSG_CHECKING(whether we are using the GNU C Library 2.1 or newer) AC_EGREP_CPP([GLIBC21],[ #include #ifdef __GNU_LIBRARY__ #if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) || (__GLIBC__ > 2) GLIBC21 #endif #endif ],[ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_GLIBC21, 1, [Honk if you have GNU C lib 2.1+]) ],[ AC_MSG_RESULT([no]) AC_MSG_CHECKING(for unicode library) UNICODE_VERSION=`unicode-config --version` if test $? -eq 0; then AC_DEFINE(HAVE_LIBUNICODE, 1, [Define if you have libunicode]) AC_MSG_RESULT($UNICODE_VERSION) UNICODE_CFLAGS=`unicode-config --cflags` UNICODE_LIBS=`unicode-config --libs` AC_SUBST(UNICODE_CFLAGS) AC_SUBST(UNICODE_LIBS) else AC_MSG_RESULT([not present - Teletext search disabled]) fi ]) dnl dnl Check for iconv() in libc or libiconv. dnl (Unicode conversions) dnl dnl Using m4/iconv.m4 from the gettext package. dnl AM_ICONV_LINK if test "x$am_cv_func_iconv" != xyes; then AC_MSG_ERROR([iconv() not found]) fi LIBS="$LIBS $LIBICONV" dnl dnl Check for png library. dnl (PNG page export) dnl HAVE_PNG="yes" AC_CHECK_LIB(png, png_destroy_write_struct, LIBS="$LIBS -lpng -lz", HAVE_PNG="no", -lz -lm) if test "x$HAVE_PNG" = xyes; then AC_DEFINE(HAVE_LIBPNG, 1, [Define if you have libpng]) fi dnl dnl X libraries. dnl (Test programs) dnl if test "x$build" != "x$host" ; then # AC_PATH_XTRA does not work when cross compiling. if test "x$with_x" = xno || test "x$no_x" = xyes ; then # X_DISPLAY_MISSING is normally set by AC_PATH_XTRA # Set X_DISPLAY_MISSING to 1 if X is disabled or not found no_x=yes AC_DEFINE(X_DISPLAY_MISSING, 1) else # If no --x-includes or --x-libraries, add expected path if test "x$x_includes" != "x" ; then X_CFLAGS="$X_CFLAGS -I$x_includes" else X_CFLAGS="$X_CFLAGS -I/usr/include" fi if test "x$x_libraries" != "x" ; then X_LIBS="$X_LIBS -L$x_libraries" else X_LIBS="$X_LIBS -L/usr/lib/${host_str}" fi # Add includedir/libdir if they exist and were not already added above if test "x$includedir" != "x" && test "x$includedir" != "x/usr/include" ; then X_CFLAGS="$X_CFLAGS -I${includedir}" fi if test "x$libdir" != "x" && test "x$libdir" != "x/usr/lib/${host_str}" ; then X_LIBS="$X_LIBS -L${libdir}" fi SAVE_CPPFLAGS="$CPPFLAGS" SAVE_LDFLAGS="$LDFLAGS" CPPFLAGS="$CPPFLAGS $X_CFLAGS" LDFLAGS="$LDFLAGS $X_LIBS" AC_CHECK_HEADER(X11/Xlib.h,, no_x=yes) AC_CHECK_LIB(X11, XOpenDisplay,, no_x=yes) CPPFLAGS="$SAVE_CPPFLAGS" LDFLAGS="$SAVE_LDFLAGS" fi else AC_PATH_XTRA fi if test "x$no_x" = xyes ; then X_CFLAGS="" X_LIBS="" else X_LIBS="$X_LIBS -lX11" fi AM_CONDITIONAL(HAVE_X, [test "x$no_x" != xyes]) dnl dnl Enable OS dependent device interfaces. dnl (Linux videodev.h, videodev2.h, dvb, bktr dependencies) dnl enable_v4l_auto=no enable_dvb_auto=no enable_bktr_auto=no enable_proxy_auto=no run_check_scripts=no host_windows=no case "$host_os" in linux*) enable_v4l_auto=yes enable_dvb_auto=yes enable_proxy_auto=yes run_check_scripts=yes ;; freebsd* | kfreebsd*-gnu | openbsd* | netbsd*) enable_bktr_auto=yes run_check_scripts=yes ;; cygwin*|mingw*) host_windows=yes ;; *) ;; esac AM_CONDITIONAL([WINDOWS], [test "$host_windows" = "yes"]) build_linux_for_windows=no if test "$host_windows" = "yes"; then case "$build_os" in linux*) build_linux_for_windows=yes ;; *) ;; esac fi AM_CONDITIONAL([LINUX_FOR_WINDOWS], [test "$build_linux_for_windows" = "yes"]) AC_MSG_CHECKING([whether to build the Video4Linux interface]) AC_ARG_ENABLE(v4l, AS_HELP_STRING([--enable-v4l],[Include the V4L and V4L2 interface (auto)]),, enable_v4l=$enable_v4l_auto) AC_MSG_RESULT($enable_v4l) if test "x$enable_v4l" = xyes; then AC_DEFINE(ENABLE_V4L, 1, [Define to build V4L interface]) AC_DEFINE(ENABLE_V4L2, 1, [Define to build V4L2 / V4L2 2.5 interface]) else # The proxy allows sharing of a V4L/V4L2 device and will be # useless without the V4L/V4L2 capture functions if the library. enable_proxy_auto=no fi AM_CONDITIONAL(ENABLE_V4L, [test "x$enable_v4l" = xyes]) AC_MSG_CHECKING([whether to build the Linux DVB interface]) AC_ARG_ENABLE(dvb, AS_HELP_STRING([--enable-dvb],[Include the DVB interface (auto)]),, enable_dvb=$enable_dvb_auto) AC_MSG_RESULT($enable_dvb) if test "x$enable_dvb" = xyes; then AC_DEFINE(ENABLE_DVB, 1, [Define to build DVB interface]) fi AM_CONDITIONAL(ENABLE_DVB, [test "x$enable_dvb" = xyes]) AC_MSG_CHECKING([whether to build the *BSD bktr driver interface]) AC_ARG_ENABLE(bktr, AS_HELP_STRING([--enable-bktr],[Include the *BSD bktr driver interface (auto)]),, enable_bktr=$enable_bktr_auto) AC_MSG_RESULT($enable_bktr) if test "x$enable_bktr" = xyes; then AC_DEFINE(ENABLE_BKTR, 1, [Define to build bktr driver interface]) fi if test "x$enable_v4l" = xyes -o "x$enable_dvb" = xyes; then # Linux 2.6.x asm/types.h defines __s64 and __u64 only # if __GNUC__ is defined. These types are required to compile # videodev2.h and the Linux DVB headers. AC_MSG_CHECKING([if asm/types.h defines __s64 and __u64]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([#include __s64 a = 1; __u64 b = 2; ])], [AC_DEFINE(HAVE_S64_U64, 1, [Define if asm/types.h defines __s64 and __u64]) AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no)]) fi dnl dnl Enable vbi proxy dnl AC_MSG_CHECKING([whether to build the vbi proxy daemon and interface]) AC_ARG_ENABLE(proxy, AS_HELP_STRING([--enable-proxy],[Build the vbi proxy daemon and interface (auto)]),, enable_proxy=$enable_proxy_auto) AC_MSG_RESULT($enable_proxy) if test "x$enable_proxy" = xyes; then AC_DEFINE(ENABLE_PROXY, 1, [Define to build proxy daemon and interface]) case "$host_os" in *-musl*) AC_DEFINE(HAVE_IOCTL_INT_INT_DOTS, 1, [ioctl request type]) ;; linux*) AC_DEFINE(HAVE_IOCTL_INT_ULONG_DOTS, 1, [ioctl request type]) ;; freebsd* | kfreebsd*-gnu | openbsd* | netbsd*) AC_DEFINE(HAVE_IOCTL_INT_ULONG_DOTS, 1, [ioctl request type]) ;; *) ;; esac fi AM_CONDITIONAL(ENABLE_PROXY, [test "x$enable_proxy" = xyes]) AM_CONDITIONAL(RUN_CHECK_SCRIPTS, [test "x$run_check_scripts" = xyes]) dnl dnl Native language support. dnl # If issues found with using a different gettext version than below and # performing a crossbuild, update AM_GNU_GETTEXT_VERSION to match and # run autogen.sh to update necessary files. AM_GNU_GETTEXT_VERSION([0.21]) AM_GNU_GETTEXT([external], [need-ngettext]) LIBS="$LTLIBINTL $LIBS" if test "x${prefix}" = xNONE; then AC_DEFINE_UNQUOTED(PACKAGE_LOCALE_DIR, "${ac_default_prefix}/share/locale", [ld]) else AC_DEFINE_UNQUOTED(PACKAGE_LOCALE_DIR, "${prefix}/share/locale", [ld]) fi dnl dnl Build docs from the sources if Doxygen is installed. dnl AC_ARG_WITH([doxygen], AS_HELP_STRING([--without-doxygen], [Disable building of API documentation]),, [with_doxygen=yes]) if test "x$with_doxygen" = "xyes"; then AC_CHECK_PROG(HAVE_DOXYGEN, doxygen, yes, no) else HAVE_DOXYGEN=no fi AM_CONDITIONAL(HAVE_DOXYGEN, [test "x$HAVE_DOXYGEN" = xyes]) dnl Helps debugging, see test/Makefile.am. AM_CONDITIONAL(BUILD_STATIC_LIB, [test "x$enable_static" = xyes]) AC_ARG_ENABLE([tests], AS_HELP_STRING([--disable-tests], [Disable building of tests programs]),, [enable_tests=yes]) AM_CONDITIONAL(ENABLE_TESTS, [test "x$enable_tests" = xyes]) AC_ARG_ENABLE([examples], AS_HELP_STRING([--disable-examples], [Disable building of example programs]),, [enable_examples=yes]) AM_CONDITIONAL(ENABLE_EXAMPLES, [test "x$enable_examples" = xyes]) AC_CONFIG_FILES([ Makefile contrib/Makefile examples/Makefile daemon/Makefile daemon/zvbid.init doc/Doxyfile doc/Makefile m4/Makefile src/Makefile src/dvb/Makefile test/Makefile po/Makefile.in zvbi.spec zvbi-0.2.pc ]) AC_OUTPUT zvbi-0.2.44/contrib/000077500000000000000000000000001476363111200142175ustar00rootroot00000000000000zvbi-0.2.44/contrib/Makefile.am000066400000000000000000000014041476363111200162520ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in bin_PROGRAMS = zvbi-ntsc-cc zvbi_ntsc_cc_SOURCES = ntsc-cc.c noinst_PROGRAMS = x11font man_MANS = zvbi-ntsc-cc.1 # These programs work only with Linux DVB drivers. if ENABLE_DVB bin_PROGRAMS += zvbi-atsc-cc # noinst_PROGRAMS += zvbi-dvbsubs zvbi_atsc_cc_SOURCES = atsc-cc.c man_MANS += zvbi-atsc-cc.1 # zvbi_dvbsubs_SOURCES = dvbsubs.c dvbsubs.h endif EXTRA_DIST = \ $(man_MANS) \ atsc-cc.c \ dvbsubs.c \ dvbsubs.h \ zvbi-atsc-cc.1 \ README AM_CPPFLAGS = \ -I$(top_srcdir) \ $(X_CFLAGS) \ -D_REENTRANT \ -D_GNU_SOURCE # For easier debugging. if BUILD_STATIC_LIB LDADD = $(top_builddir)/src/.libs/libzvbi.a else LDADD = $(top_builddir)/src/libzvbi.la endif LDADD += \ $(LIBS) \ $(X_LIBS) zvbi-0.2.44/contrib/README000066400000000000000000000026411476363111200151020ustar00rootroot00000000000000Contributions x11font by Gerd Knorr --------------------- Transcodes the libzvbi internal fonts into X11 fonts: teletext.bdf teletexti.bdf caption.bdf captioni.bdf You can install like this (replace /usr/lib/X11/fonts by the name of your fonts directory): for f in *.bdf; do bdftopcf $f | \ gzip >`echo /usr/lib/X11/fonts/misc/$f | sed s/bdf/pcf.gz/g`; done mkfontdir /usr/lib/X11/fonts/misc # Now you may need to run: xset fp rehash /etc/rc.d/xfs restart # Check if installation succeeded: xlsfonts | grep -ets- After you installed you can for example start an xterm with "xterm -u8 -font " and see the output of test/unicode printed in libzvbi fonts. ntsc-cc by various authors -------------------------- This is a copy of the ntsc-cc tool from the Xawtv package using libzvbi for driver access and bit slicing. Can be used to record and display Closed Caption and eXtended Data Service data. atsc-cc by Michael Schimek et al -------------------------------- This is an ATSC (digital TV) version of ntsc-cc. Unlike ntsc-cc this program also tunes in a station, and it can capture data from all stations sharing a transponder frequency at one. The Closed Caption decoder has been greatly improved (this code will be integrated into the library and ntsc-cc after more tests) and a basic DTVCC (CEA 708-C) decoder has been added. dvbsubs by Dave Chapman ----------------------- A DVB subtitles decoder. Work in progress. zvbi-0.2.44/contrib/atsc-cc.c000066400000000000000000006733371476363111200157230ustar00rootroot00000000000000/* * atsc-cc -- ATSC Closed Caption decoder * * Copyright (C) 2008 Michael H. Schimek * * Contains code from zvbi-ntsc-cc closed caption decoder written by * , Mike Baker , * Mark K. Kim . * * Thanks to Karol Zapolski for his support. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_LONG # include #endif #include "src/libzvbi.h" /* Linux DVB driver interface. */ #include "src/dvb/dmx.h" #include "src/dvb/frontend.h" #undef PROGRAM #define PROGRAM "ATSC-CC" #undef VERSION #define VERSION "0.5" #if __GNUC__ < 3 # define likely(expr) (expr) # define unlikely(expr) (expr) #else # define likely(expr) __builtin_expect(expr, 1) # define unlikely(expr) __builtin_expect(expr, 0) #endif #define N_ELEMENTS(array) (sizeof (array) / sizeof ((array)[0])) #define CLEAR(var) memset (&(var), 0, sizeof (var)) /* FIXME __typeof__ is a GCC extension. */ #undef SWAP #define SWAP(x, y) \ do { \ __typeof__ (x) _x = x; \ x = y; \ y = _x; \ } while (0) #undef MIN #define MIN(x, y) ({ \ __typeof__ (x) _x = (x); \ __typeof__ (y) _y = (y); \ (void)(&_x == &_y); /* warn if types do not match */ \ /* return */ (_x < _y) ? _x : _y; \ }) #undef MAX #define MAX(x, y) ({ \ __typeof__ (x) _x = (x); \ __typeof__ (y) _y = (y); \ (void)(&_x == &_y); /* warn if types do not match */ \ /* return */ (_x > _y) ? _x : _y; \ }) #undef PARENT #define PARENT(_ptr, _type, _member) ({ \ __typeof__ (&((_type *) 0)->_member) _p = (_ptr); \ (_p != 0) ? (_type *)(((char *) _p) - offsetof (_type, \ _member)) : (_type *) 0; \ }) /* These should be defined in inttypes.h. */ #ifndef PRId64 # define PRId64 "lld" #endif #ifndef PRIu64 # define PRIu64 "llu" #endif #ifndef PRIx64 # define PRIx64 "llx" #endif /* EIA 608-B decoder. */ enum field_num { FIELD_1 = 0, FIELD_2, MAX_FIELDS }; enum cc_mode { CC_MODE_UNKNOWN, CC_MODE_ROLL_UP, CC_MODE_POP_ON, CC_MODE_PAINT_ON, CC_MODE_TEXT }; /* EIA 608-B Section 4.1. */ #define VBI_CAPTION_CC1 1 /* primary synchronous caption service (F1) */ #define VBI_CAPTION_CC2 2 /* special non-synchronous use captions (F1) */ #define VBI_CAPTION_CC3 3 /* secondary synchronous caption service (F2) */ #define VBI_CAPTION_CC4 4 /* special non-synchronous use captions (F2) */ #define VBI_CAPTION_T1 5 /* first text service (F1) */ #define VBI_CAPTION_T2 6 /* second text service (F1) */ #define VBI_CAPTION_T3 7 /* third text service (F2) */ #define VBI_CAPTION_T4 8 /* fourth text service (F2) */ #define UNKNOWN_CC_CHANNEL 0 #define MAX_CC_CHANNELS 8 /* 47 CFR 15.119 (d) Screen format. */ #define CC_FIRST_ROW 0 #define CC_LAST_ROW 14 #define CC_MAX_ROWS 15 #define CC_FIRST_COLUMN 1 #define CC_LAST_COLUMN 32 #define CC_MAX_COLUMNS 32 #define CC_ALL_ROWS_MASK ((1 << CC_MAX_ROWS) - 1) #define VBI_TRANSLUCENT VBI_SEMI_TRANSPARENT struct cc_timestamp { /* System time when the event occured, zero if no event occured yet. */ struct timeval sys; /* Presentation time stamp of the event. Only the 33 least significant bits are valid. < 0 if no event occured yet. */ int64_t pts; }; struct cc_channel { /** * [0] and [1] are the displayed and non-displayed buffer as * defined in 47 CFR 15.119, and selected by displayed_buffer * below. [2] is a snapshot of the displayed buffer at the * last stream event. * * XXX Text channels don't need buffer[2] and buffer[3], we're * wasting memory. */ uint16_t buffer[3][CC_MAX_ROWS][1 + CC_MAX_COLUMNS]; /** * For buffer[0 ... 2], if bit 1 << row is set this row * contains displayable characters, spacing or non-spacing * attributes. (Special character 0x1139 "transparent space" * is not a displayable character.) This information is * intended to speed up copying, erasing and formatting. */ unsigned int dirty[3]; /** Index of displayed buffer, 0 or 1. */ unsigned int displayed_buffer; /** * Cursor position: FIRST_ROW ... LAST_ROW and * FIRST_COLUMN ... LAST_COLUMN. */ unsigned int curr_row; unsigned int curr_column; /** * Text window height in CC_MODE_ROLL_UP. The first row of the * window is curr_row - window_rows + 1, the last row is * curr_row. * * Note: curr_row - window_rows + 1 may be < FIRST_ROW, this * must be clipped before using window_rows: * * actual_rows = MIN (curr_row - FIRST_ROW + 1, window_rows); * * We won't do that at the RUx command because usually a PAC * follows which may change curr_row. */ unsigned int window_rows; /* Most recently received PAC command. */ unsigned int last_pac; /** * This variable counts successive transmissions of the * letters A to Z. It is reset to zero on reception of any * letter a to z. * * Some stations do not transmit EIA 608-B extended characters * and except for N with tilde the standard and special * character sets contain only lower case accented * characters. We force these characters to upper case if this * variable indicates live caption, which is usually all upper * case. */ unsigned int uppercase_predictor; /** Current caption mode or CC_MODE_UNKNOWN. */ enum cc_mode mode; /** * The time when we last received data for this * channel. Intended to detect if this caption channel is * active. */ struct cc_timestamp timestamp; /** * The time when we received the first (but not necessarily * leftmost) character in the current row. Unless the mode is * CC_MODE_POP_ON the next stream event will carry this * timestamp. */ struct cc_timestamp timestamp_c0; }; struct cc_decoder { /** * Decoder state. We decode all channels in parallel, this way * clients can switch between channels without data loss, or * capture multiple channels with a single decoder instance. * * Also 47 CFR 15.119 and EIA 608-C require us to remember the * cursor position on each channel. */ struct cc_channel channel[MAX_CC_CHANNELS]; /** * Current channel, switched by caption control codes. Can be * one of @c VBI_CAPTION_CC1 ... @c VBI_CAPTION_CC4 or @c * VBI_CAPTION_T1 ... @c VBI_CAPTION_T4 or @c * UNKNOWN_CC_CHANNEL if no channel number was received yet. */ vbi_pgno curr_ch_num[MAX_FIELDS]; /** * Caption control codes (two bytes) may repeat once for error * correction. -1 if no repeated control code can be expected. */ int expect_ctrl[MAX_FIELDS][2]; /** Receiving XDS data, as opposed to caption / ITV data. */ vbi_bool in_xds[MAX_FIELDS]; /** * Pointer into the channel[] array if a display update event * shall be sent at the end of this iteration, %c NULL * otherwise. Purpose is to suppress an event for the first of * two displayable characters in a caption byte pair. */ struct cc_channel * event_pending; /** * Remembers past parity errors: One bit for each call of * cc_feed(), most recent result in lsb. The idea is to * disable the decoder if we detect too many errors. */ unsigned int error_history; /** * The time when we last received data, including NUL bytes. * Intended to detect if the station transmits any data on * line 21 or 284 at all. */ struct cc_timestamp timestamp; }; /* CEA 708-C decoder. */ enum justify { JUSTIFY_LEFT = 0, JUSTIFY_RIGHT, JUSTIFY_CENTER, JUSTIFY_FULL }; enum direction { DIR_LEFT_RIGHT = 0, DIR_RIGHT_LEFT, DIR_TOP_BOTTOM, DIR_BOTTOM_TOP }; enum display_effect { DISPLAY_EFFECT_SNAP = 0, DISPLAY_EFFECT_FADE, DISPLAY_EFFECT_WIPE }; enum opacity { OPACITY_SOLID = 0, OPACITY_FLASH, OPACITY_TRANSLUCENT, OPACITY_TRANSPARENT }; enum edge { EDGE_NONE = 0, EDGE_RAISED, EDGE_DEPRESSED, EDGE_UNIFORM, EDGE_SHADOW_LEFT, EDGE_SHADOW_RIGHT }; enum pen_size { PEN_SIZE_SMALL = 0, PEN_SIZE_STANDARD, PEN_SIZE_LARGE }; enum font_style { FONT_STYLE_DEFAULT = 0, FONT_STYLE_MONO_SERIF, FONT_STYLE_PROP_SERIF, FONT_STYLE_MONO_SANS, FONT_STYLE_PROP_SANS, FONT_STYLE_CASUAL, FONT_STYLE_CURSIVE, FONT_STYLE_SMALL_CAPS }; enum text_tag { TEXT_TAG_DIALOG = 0, TEXT_TAG_SOURCE_ID, TEXT_TAG_DEVICE, TEXT_TAG_DIALOG_2, TEXT_TAG_VOICEOVER, TEXT_TAG_AUDIBLE_TRANSL, TEXT_TAG_SUBTITLE_TRANSL, TEXT_TAG_VOICE_DESCR, TEXT_TAG_LYRICS, TEXT_TAG_EFFECT_DESCR, TEXT_TAG_SCORE_DESCR, TEXT_TAG_EXPLETIVE, TEXT_TAG_NOT_DISPLAYABLE = 15 }; enum offset { OFFSET_SUBSCRIPT = 0, OFFSET_NORMAL, OFFSET_SUPERSCRIPT }; /* RGB 2:2:2 (lsb = B). */ typedef uint8_t dtvcc_color; /* Lsb = window 0, msb = window 7. */ typedef uint8_t dtvcc_window_map; struct dtvcc_pen_style { enum pen_size pen_size; enum font_style font_style; enum offset offset; vbi_bool italics; vbi_bool underline; enum edge edge_type; dtvcc_color fg_color; enum opacity fg_opacity; dtvcc_color bg_color; enum opacity bg_opacity; dtvcc_color edge_color; }; struct dtvcc_pen { enum text_tag text_tag; struct dtvcc_pen_style style; }; struct dtvcc_window_style { enum justify justify; enum direction print_direction; enum direction scroll_direction; vbi_bool wordwrap; enum display_effect display_effect; enum direction effect_direction; unsigned int effect_speed; /* 1/10 sec */ dtvcc_color fill_color; enum opacity fill_opacity; enum edge border_type; dtvcc_color border_color; }; struct dtvcc_window { /* EIA 708-C window state. */ uint16_t buffer[16][42]; vbi_bool visible; /* 0 = highest ... 7 = lowest. */ unsigned int priority; unsigned int anchor_point; unsigned int anchor_horizontal; unsigned int anchor_vertical; vbi_bool anchor_relative; unsigned int row_count; unsigned int column_count; vbi_bool row_lock; vbi_bool column_lock; unsigned int curr_row; unsigned int curr_column; struct dtvcc_pen curr_pen; struct dtvcc_window_style style; /* Our stuff. */ /** * If bit 1 << row is set we already sent a stream event for * this row. */ unsigned int streamed; /** * The time when we received the first (but not necessarily * leftmost) character in the current row. Unless a * DisplayWindow or ToggleWindow command completed the line * the next stream event will carry this timestamp. */ struct cc_timestamp timestamp_c0; }; struct dtvcc_service { /* Interpretation Layer. */ struct dtvcc_window window[8]; struct dtvcc_window * curr_window; dtvcc_window_map created; /* For debugging. */ unsigned int error_line; /* Service Layer. */ uint8_t service_data[128]; unsigned int service_data_in; /** The time when we last received data for this service. */ struct cc_timestamp timestamp; }; struct dtvcc_decoder { struct dtvcc_service service[2]; /* Packet Layer. */ uint8_t packet[128]; unsigned int packet_size; /* Next expected DTVCC packet sequence_number. Only the two most significant bits are valid. < 0 if no sequence_number has been received yet. */ int next_sequence_number; /** The time when we last received data. */ struct cc_timestamp timestamp; }; /* ATSC A/53 Part 4:2007 Closed Caption Data decoder. */ enum cc_type { NTSC_F1 = 0, NTSC_F2 = 1, DTVCC_DATA = 2, DTVCC_START = 3, }; struct cc_data_decoder { /* Test tap. */ const char * option_cc_data_tap_file_name; FILE * cc_data_tap_fp; /* For debugging. */ int64_t last_pts; }; /* Caption recorder. */ enum cc_attr { VBI_UNDERLINE = (1 << 0), VBI_ITALIC = (1 << 2), VBI_FLASH = (1 << 3) }; struct cc_pen { uint8_t attr; uint8_t fg_color; uint8_t fg_opacity; uint8_t bg_color; uint8_t bg_opacity; uint8_t edge_type; uint8_t edge_color; uint8_t edge_opacity; uint8_t pen_size; uint8_t font_style; uint8_t reserved[6]; }; enum caption_format { FORMAT_PLAIN, FORMAT_VT100, FORMAT_NTSC_CC }; struct caption_recorder { /* Caption stream filter: NTSC CC1 ... CC4 (1 << 0 ... 1 << 3), NTSC T1 ... T4 (1 << 4 ... 1 << 7), ATSC Service 1 ... 2 (1 << 8 ... 1 << 9). */ unsigned int option_caption_mask; /* Output file name for caption stream. */ const char * option_caption_file_name[10]; /* Output file name for XDS data. */ const char * option_xds_output_file_name; vbi_bool option_caption_timestamps; vbi_bool option_timestamps_rel; vbi_bool option_ucla_prefix; enum caption_format option_caption_format; /* old options */ char usexds; char usecc; char usesen; char usewebtv; struct cc_data_decoder ccd; struct cc_decoder cc; struct dtvcc_decoder dtvcc; //XDSdecode unsigned int field; struct { char packet[34]; uint8_t length; int print : 1; } info[2][8][25]; char newinfo[2][8][25][34]; char *infoptr; int mode,type; char infochecksum; const char * xds_info_prefix; const char * xds_info_suffix; FILE * xds_fp; uint16_t * ucs_buffer; unsigned int ucs_buffer_length; unsigned int ucs_buffer_capacity; FILE * caption_fp[10]; int minicut_min[10]; }; /* Video recorder. */ enum start_code { PICTURE_START_CODE = 0x00, /* 0x01 ... 0xAF slice_start_code */ /* 0xB0 reserved */ /* 0xB1 reserved */ USER_DATA_START_CODE = 0xB2, SEQUENCE_HEADER_CODE = 0xB3, SEQUENCE_ERROR_CODE = 0xB4, EXTENSION_START_CODE = 0xB5, /* 0xB6 reserved */ SEQUENCE_END_CODE = 0xB7, GROUP_START_CODE = 0xB8, /* 0xB9 ... 0xFF system start codes */ PRIVATE_STREAM_1 = 0xBD, PADDING_STREAM = 0xBE, PRIVATE_STREAM_2 = 0xBF, AUDIO_STREAM_0 = 0xC0, AUDIO_STREAM_31 = 0xDF, VIDEO_STREAM_0 = 0xE0, VIDEO_STREAM_15 = 0xEF, }; enum extension_start_code_identifier { /* 0x0 reserved */ SEQUENCE_EXTENSION_ID = 0x1, SEQUENCE_DISPLAY_EXTENSION_ID = 0x2, QUANT_MATRIX_EXTENSION_ID = 0x3, COPYRIGHT_EXTENSION_ID = 0x4, SEQUENCE_SCALABLE_EXTENSION_ID = 0x5, /* 0x6 reserved */ PICTURE_DISPLAY_EXTENSION_ID = 0x7, PICTURE_CODING_EXTENSION_ID = 0x8, PICTURE_SPATIAL_SCALABLE_EXTENSION_ID = 0x9, PICTURE_TEMPORAL_SCALABLE_EXTENSION_ID = 0xA, /* 0xB ... 0xF reserved */ }; enum picture_coding_type { /* 0 forbidden */ I_TYPE = 1, P_TYPE = 2, B_TYPE = 3, D_TYPE = 4, /* 5 ... 7 reserved */ }; enum picture_structure { /* 0 reserved */ TOP_FIELD = 1, BOTTOM_FIELD = 2, FRAME_PICTURE = 3 }; /* PTSs and DTSs are 33 bits wide. */ #define TIMESTAMP_MASK (((int64_t) 1 << 33) - 1) struct packet { /* Offset in bytes from the buffer start. */ unsigned int offset; /* Packet and payload size in bytes. */ unsigned int size; unsigned int payload; /* Decoding and presentation time stamp and display duration of this packet. */ int64_t dts; int64_t pts; int64_t duration; /* Cut the stream at this packet. */ vbi_bool splice; /* Data is missing before this packet but the producer will correct that later. */ vbi_bool data_lost; }; struct buffer { uint8_t * base; /* Capacity of the buffer in bytes. */ unsigned int capacity; /* Offset in bytes from base where the next data will be stored. */ unsigned int in; /* Offset in bytes from base where the next data will be removed. */ unsigned int out; }; #define MAX_PACKETS 64 struct pes_buffer { uint8_t * base; /* Capacity of the buffer in bytes. */ unsigned int capacity; /* Offset in bytes from base where the next data will be stored. */ unsigned int in; /* Information about the packets in the buffer. packet[0].offset is the offset in bytes from base where the next data will be removed. */ struct packet packet[MAX_PACKETS]; /* Number of packets in the packet[] array. packet[0] must not be removed unless n_packets >= 2. */ unsigned int n_packets; }; struct pes_multiplexer { vbi_bool new_file; unsigned int b_state; time_t minicut_end; FILE * minicut_fp; }; struct audio_es_packetizer { /* Test tap. */ const char * option_audio_es_tap_file_name; FILE * audio_es_tap_fp; struct buffer ac3_buffer; struct pes_buffer pes_buffer; /* Number of bytes we want to examine at pes_buffer.in. */ unsigned int need; /* Estimated PTS of the next/current AC3 frame (pts or pes_packet_pts plus previous frame duration), -1 if no PTS was received yet. */ int64_t pts; /* Next/current AC3 frame is the first received frame. */ vbi_bool first_frame; /* Data may have been lost between the previous and next/current AC3 frame. */ vbi_bool data_lost; uint64_t pes_audio_bit_rate; }; struct video_es_packetizer { struct pes_buffer pes_buffer; int sequence_header_offset; unsigned int packet_filled; uint8_t pes_packet_header_6; uint64_t pes_video_bit_rate; vbi_bool aligned; }; struct audio_pes_decoder { struct buffer buffer; unsigned int need; unsigned int look_ahead; }; struct video_recorder { struct audio_pes_decoder apesd; struct video_es_packetizer vesp; struct audio_es_packetizer aesp; struct pes_multiplexer pm; /* TS recorder */ unsigned int pat_cc; unsigned int pmt_cc; time_t minicut_end; FILE * minicut_fp; }; enum received_blocks { RECEIVED_PES_PACKET = (1 << 0), RECEIVED_PICTURE = (1 << 1), RECEIVED_PICTURE_EXT = (1 << 2), RECEIVED_MPEG_CC_DATA = (1 << 3) }; struct video_es_decoder { /* Test tap. */ const char * option_video_es_all_tap_file_name; const char * option_video_es_tap_file_name; FILE * video_es_tap_fp; /* Video elementary stream buffer. */ struct buffer buffer; unsigned int min_bytes_valid; /* Number of bytes after buffer.out which have already been scanned for a start code prefix. */ unsigned int skip; /* Last received start code, < 0 if none. If valid buffer.out points at the first start code prefix byte. */ enum start_code last_start_code; /* The decoding and presentation time stamp of the current picture. Only the lowest 33 bits are valid. < 0 if no PTS or DTS was received or the PES packet header was malformed. */ int64_t pts; int64_t dts; /* For debugging. */ uint64_t n_pictures_received; /* Parameters of the current picture. */ enum picture_coding_type picture_coding_type; enum picture_structure picture_structure; unsigned int picture_temporal_reference; /* Set of the data blocks we received so far. */ enum received_blocks received_blocks; /* Describes the contents of the reorder_buffer[]: Bit 0 - a top field in reorder_buffer[0], Bit 1 - a bottom field in reorder_buffer[1], Bit 2 - a frame in reorder_buffer[0]. Only the combinations 0, 1, 2, 3, 4 are valid. */ unsigned int reorder_pictures; /* The PTS (as above) of the data in the reorder_buffer. */ int64_t reorder_pts[2]; unsigned int reorder_n_bytes[2]; /* Buffer to convert picture user data from coded order to display order, for the top and bottom field. Maximum size required: 11 + cc_count * 3, where cc_count = 0 ... 31. */ uint8_t reorder_buffer[2][128]; }; struct ts_decoder { /* TS PID of the video [0] and audio [1] stream. */ unsigned int pid[2]; /* Next expected video and audio TS packet continuity counter. Only the lowest four bits are valid. < 0 if no continuity counter has been received yet. */ int next_ts_cc[2]; /* One or more TS packets were lost. */ vbi_bool data_lost; } tsd; struct program { const char * option_station_name; /* Convert the captured video and audio data to PES format, cut the stream into one minute long fragments and save them with file name /yyyymmddhh0000/yyyymmddhhmm00.mpg One minute is measured in real time, yyyymmddhhmm is the system time in the UTC zone when the data was received and decoded. */ const char * option_minicut_dir_name; struct timeval now; struct timeval rel; int64_t first_pts; int64_t first_dts; int64_t cc_pts; struct ts_decoder tsd; struct video_es_decoder vesd; struct video_recorder vr; struct caption_recorder cr; }; struct station { struct station * next; char * name; enum fe_type type; unsigned long frequency; unsigned int video_pid; unsigned int audio_pid; union { struct { enum fe_modulation modulation; } atsc; struct { enum fe_spectral_inversion inversion; enum fe_bandwidth bandwidth; enum fe_code_rate code_rate_HP; enum fe_code_rate code_rate_LP; enum fe_modulation constellation; enum fe_transmit_mode transm_mode; enum fe_guard_interval guard_interval; enum fe_hierarchy hierarchy; } dvb_t; } u; }; enum debug { DEBUG_VESD_START_CODE = (1 << 0), DEBUG_VESD_PES_PACKET = (1 << 1), DEBUG_VESD_PIC_HDR = (1 << 2), DEBUG_VESD_PIC_EXT = (1 << 3), DEBUG_VESD_USER_DATA = (1 << 5), DEBUG_VESD_CC_DATA = (1 << 6), DEBUG_CC_DATA = (1 << 7), DEBUG_CC_F1 = (1 << 8), DEBUG_CC_F2 = (1 << 9), DEBUG_CC_DECODER = (1 << 10), DEBUG_DTVCC_PACKET = (1 << 11), DEBUG_DTVCC_SE = (1 << 12), DEBUG_DTVCC_PUT_CHAR = (1 << 13), DEBUG_DTVCC_STREAM_EVENT = (1 << 14), DEBUG_CONFIG = (1 << 15) }; enum source { SOURCE_DVB_DEVICE = 1, SOURCE_STDIN_TS, /* Not implemented yet. */ SOURCE_STDIN_PES, /* For tests only. */ SOURCE_STDIN_VIDEO_ES, SOURCE_STDIN_CC_DATA }; static const char * my_name; static unsigned int option_verbosity; static unsigned int option_debug; /* Input. */ static enum source option_source; /* DVB device. */ static enum fe_type option_dvb_type; static unsigned long option_dvb_adapter_num; static unsigned long option_dvb_frontend_id; static unsigned long option_dvb_demux_id; static unsigned long option_dvb_dvr_id; static const char * option_channel_conf_file_name; /* Test taps. */ const char * option_ts_all_tap_file_name; const char * option_ts_tap_file_name; static vbi_bool option_minicut_test; static const char * locale_codeset; /* DVB devices. */ static int fe_fd; /* frontend */ static int dvr_fd; /* data stream */ static int dmx_fd; /* demultiplexer */ /* Capture thread. */ static pthread_t capture_thread_id; /* The read buffer of the capture thread. */ static uint8_t * ct_buffer; static unsigned int ct_buffer_capacity; /* For debugging. */ static uint64_t ct_n_bytes_in; /* Transport stream decoder. */ /* static pthread_t demux_thread_id; */ /* Transport stream buffer. The capture thread stores TS packets at ts_buffer_in, the TS decoder removes packets from ts_buffer_out, and increments the respective pointer by the number of bytes transferred. The input and output pointers jump back to the start of the buffer before they would exceed ts_buffer_capacity (wraps at packet, not byte granularity). The buffer is empty if ts_buffer_in equals ts_buffer_out. */ static uint8_t * ts_buffer; static unsigned int ts_buffer_capacity; static volatile unsigned int ts_buffer_in; static volatile unsigned int ts_buffer_out; static uint8_t ts_error; /* For debugging. */ static uint64_t ts_n_packets_in; /* Test tap into the transport stream. */ static FILE * ts_tap_fp; /* Notifies the TS decoder when data is available in the ts_buffer. */ static pthread_mutex_t dx_mutex; static pthread_cond_t dx_cond; /* If pid_map[].program < 0, no TS packet with PID n is needed and the capture thread will drop the packet. Otherwise pid_map[].program is an index into the programs[] table. This information is used by the TS decoder to separate multiple video streams. */ static struct { int8_t program; } pid_map[0x2000]; /* The programs we want to record. */ static struct program program_table[12]; static unsigned int n_programs; /* A list of stations found in the channel.conf file. */ static struct station * station_list; /* Any station on the selected transponder. */ static struct station * station; static void list_stations (void); static void init_cc_decoder (struct cc_decoder * cd); static void init_dtvcc_decoder (struct dtvcc_decoder * dc); static void init_cc_data_decoder (struct cc_data_decoder *cd); #define CASE(x) case x: return #x; static const char * picture_coding_type_name (enum picture_coding_type t) { switch (t) { CASE (I_TYPE) CASE (P_TYPE) CASE (B_TYPE) CASE (D_TYPE) } return "invalid"; } static const char * picture_structure_name (enum picture_structure t) { switch (t) { CASE (TOP_FIELD) CASE (BOTTOM_FIELD) CASE (FRAME_PICTURE) } return "invalid"; } static const char * cc_type_name (enum cc_type t) { switch (t) { CASE (NTSC_F1) CASE (NTSC_F2) CASE (DTVCC_DATA) CASE (DTVCC_START) } return "invalid"; } #undef CASE static int printable (int c) { if ((c & 0x7F) < 0x20) return '.'; else return c & 0x7F; } static void dump (FILE * fp, const uint8_t * buf, unsigned int n_bytes) _vbi_unused; static void dump (FILE * fp, const uint8_t * buf, unsigned int n_bytes) { const unsigned int width = 16; unsigned int i; for (i = 0; i < n_bytes; i += width) { unsigned int end; unsigned int j; end = MIN (i + width, n_bytes); for (j = i; j < end; ++j) fprintf (fp, "%02x ", buf[j]); for (; j < i + width; ++j) fputs (" ", fp); fputc (' ', fp); for (j = i; j < end; ++j) { int c = buf[j]; fputc (printable (c), fp); } fputc ('\n', fp); } } #define log(verb, templ, args...) \ log_message (verb, /* print_errno */ FALSE, templ , ##args) #define log_errno(verb, templ, args...) \ log_message (verb, /* print_errno */ TRUE, templ , ##args) #define bug(templ, args...) \ log_message (1, /* print_errno */ FALSE, "BUG: " templ , ##args) static void log_message (unsigned int verbosity, vbi_bool print_errno, const char * templ, ...) _vbi_format ((printf, 3, 4)); static void log_message (unsigned int verbosity, vbi_bool print_errno, const char * templ, ...) { if (verbosity <= option_verbosity) { va_list ap; va_start (ap, templ); fprintf (stderr, "%s: ", my_name); vfprintf (stderr, templ, ap); if (print_errno) { fprintf (stderr, ": %s.\n", strerror (errno)); } va_end (ap); } } #define error_exit(templ, args...) \ error_message_exit (/* print_errno */ FALSE, templ , ##args) #define errno_exit(templ, args...) \ error_message_exit (/* print_errno */ TRUE, templ , ##args) static void error_message_exit (vbi_bool print_errno, const char * templ, ...) _vbi_format ((printf, 2, 3)); static void error_message_exit (vbi_bool print_errno, const char * templ, ...) { if (option_verbosity > 0) { va_list ap; va_start (ap, templ); fprintf (stderr, "%s: ", my_name); vfprintf (stderr, templ, ap); if (print_errno) { fprintf (stderr, ": %s.\n", strerror (errno)); } va_end (ap); } exit (EXIT_FAILURE); } static void no_mem_exit (void) { error_exit ("Out of memory."); } static void * xmalloc (size_t size) { void *p; p = malloc (size); if (NULL == p) { no_mem_exit (); } return p; } static char * xasprintf (const char * templ, ...) _vbi_format ((printf, 1, 2)); static char * xasprintf (const char * templ, ...) { va_list ap; char *s; int r; va_start (ap, templ); r = vasprintf (&s, templ, ap); if (r < 0 || NULL == s) { no_mem_exit (); } va_end (ap); return s; } static int xioctl_may_fail (int fd, int request, void * arg) { int r; do r = ioctl (fd, request, arg); while (-1 == r && EINTR == errno); return r; } #define xioctl(fd, request, arg) \ do { \ int r; \ \ r = xioctl_may_fail (fd, request, arg); \ if (-1 == r) { \ errno_exit (#request " failed"); \ } \ } while (0) static FILE * open_output_file (const char * name) { FILE *fp; if (NULL == name || 0 == strcmp (name, "-")) { fp = stdout; } else { fp = fopen (name, "a"); if (NULL == fp) { errno_exit ("Cannot open output file '%s'", name); } } return fp; } static FILE * open_test_file (const char * name) { FILE *fp; if (NULL == name || 0 == strcmp (name, "-")) { fp = stdin; } else { fp = fopen (name, "r"); if (NULL == fp) { errno_exit ("Cannot open test file '%s'", name); } } return fp; } static FILE * open_minicut_file (struct program * pr, const struct tm * tm, const char * file_name, const char * extension) { char dir_name[32]; size_t base_len; size_t dir_len; char *buf; struct stat st; unsigned int i; FILE *fp; base_len = strlen (pr->option_minicut_dir_name); dir_len = snprintf (dir_name, sizeof (dir_name), "/%04u%02u%02u%02u0000", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour); buf = xmalloc (base_len + dir_len + strlen (file_name) + 2 + strlen (extension) + 1); strcpy (buf, pr->option_minicut_dir_name); if (0 != stat (buf, &st)) { errno_exit ("Cannot open '%s'", buf); } else if (!S_ISDIR(st.st_mode)) { error_exit ("'%s' is not a directory.\n", buf); } strcpy (buf + base_len, dir_name); if (0 != stat (buf, &st)) { if (ENOENT != errno) errno_exit ("Cannot open '%s'", buf); if (-1 == mkdir (buf, /* mode */ 0777)) errno_exit ("Cannot create '%s'", buf); } else if (!S_ISDIR(st.st_mode)) { error_exit ("'%s' is not a directory.\n", buf); } for (i = 0; i < 100; ++i) { int fd; if (0 == i) { sprintf (buf + base_len + dir_len, "%s%s", file_name, extension); } else { sprintf (buf + base_len + dir_len, "%s-%u%s", file_name, i, extension); } fd = open (buf, (O_CREAT | O_EXCL | O_LARGEFILE | O_WRONLY), 0666); if (fd >= 0) { fp = fdopen (fd, "w"); if (NULL == fp) goto failed; log (2, "Opened '%s'.\n", buf); free (buf); return fp; } if (EEXIST == errno) continue; if (ENOSPC == errno) break; failed: errno_exit ("Cannot open output file '%s'", buf); } free (buf); /* Will try again later. */ log_errno (1, "Cannot open output file '%s'", buf); return NULL; } static unsigned int station_num (struct program * pr) { return (pr - program_table) + 1; } /* Caption recorder */ static const char * cr_file_name_suffix [10] = { "-cc1", "-cc2", "-cc3", "-cc4", "-t1", "-t2", "-t3", "-t4", "-s1", "-s2" }; static void cr_grow_buffer (struct caption_recorder *cr, unsigned int n_chars) { uint16_t *new_buffer; size_t min_size; size_t new_size; if (likely (cr->ucs_buffer_length + n_chars <= cr->ucs_buffer_capacity)) return; min_size = (cr->ucs_buffer_length + n_chars) * 2; min_size = MAX ((size_t) 64, min_size); new_size = MAX (min_size, (size_t) cr->ucs_buffer_capacity * 4); new_buffer = realloc (cr->ucs_buffer, new_size); if (NULL == new_buffer) no_mem_exit (); cr->ucs_buffer = new_buffer; cr->ucs_buffer_capacity = new_size / 2; } static void cr_putuc (struct caption_recorder *cr, uint16_t uc) { cr_grow_buffer (cr, 1); cr->ucs_buffer[cr->ucs_buffer_length++] = uc; } static void cr_puts (struct caption_recorder *cr, const char * s) { while (0 != *s) cr_putuc (cr, *s++); } _vbi_inline void vbi_char_copy_attr (struct vbi_char * cp1, struct vbi_char * cp2, unsigned int attr) { if (attr & VBI_UNDERLINE) cp1->underline = cp2->underline; if (attr & VBI_ITALIC) cp1->italic = cp2->italic; if (attr & VBI_FLASH) cp1->flash = cp2->flash; } _vbi_inline void vbi_char_clear_attr (struct vbi_char * cp, unsigned int attr) { if (attr & VBI_UNDERLINE) cp->underline = 0; if (attr & VBI_ITALIC) cp->italic = 0; if (attr & VBI_FLASH) cp->flash = 0; } _vbi_inline void vbi_char_set_attr (struct vbi_char * cp, unsigned int attr) { if (attr & VBI_UNDERLINE) cp->underline = 1; if (attr & VBI_ITALIC) cp->italic = 1; if (attr & VBI_FLASH) cp->flash = 1; } _vbi_inline unsigned int vbi_char_has_attr (struct vbi_char * cp, unsigned int attr) { attr &= (VBI_UNDERLINE | VBI_ITALIC | VBI_FLASH); if (0 == cp->underline) attr &= ~VBI_UNDERLINE; if (0 == cp->italic) attr &= ~VBI_ITALIC; if (0 == cp->flash) attr &= ~VBI_FLASH; return attr; } _vbi_inline unsigned int vbi_char_xor_attr (struct vbi_char * cp1, struct vbi_char * cp2, unsigned int attr) { attr &= (VBI_UNDERLINE | VBI_ITALIC | VBI_FLASH); if (0 == (cp1->underline ^ cp2->underline)) attr &= ~VBI_UNDERLINE; if (0 == (cp1->italic ^ cp2->italic)) attr &= ~VBI_ITALIC; if (0 == (cp1->flash ^ cp2->flash)) attr &= ~VBI_FLASH; return attr; } static vbi_bool cr_put_attr (struct caption_recorder *cr, vbi_char * prev, vbi_char curr) { uint16_t *d; switch (cr->option_caption_format) { case FORMAT_PLAIN: return TRUE; case FORMAT_NTSC_CC: /* Same output as the [zvbi-]ntsc-cc app. */ curr.opacity = VBI_OPAQUE; /* Use the default foreground and background color of the terminal. */ curr.foreground = -1; curr.background = -1; if (vbi_char_has_attr (&curr, VBI_ITALIC)) curr.foreground = VBI_CYAN; vbi_char_clear_attr (&curr, VBI_ITALIC | VBI_FLASH); break; case FORMAT_VT100: break; } cr_grow_buffer (cr, 32); d = cr->ucs_buffer + cr->ucs_buffer_length; /* Control sequences based on ECMA-48, http://www.ecma-international.org/ */ /* SGR sequence */ d[0] = 27; /* CSI */ d[1] = '['; d += 2; switch (curr.opacity) { case VBI_TRANSPARENT_SPACE: vbi_char_clear_attr (&curr, -1); curr.foreground = -1; curr.background = -1; break; case VBI_TRANSPARENT_FULL: curr.background = -1; break; case VBI_SEMI_TRANSPARENT: case VBI_OPAQUE: break; } if ((prev->foreground != curr.foreground && (uint8_t) -1 == curr.foreground) || (prev->background != curr.background && (uint8_t) -1 == curr.background)) { *d++ = ';'; /* "[0m;" reset */ vbi_char_clear_attr (prev, -1); prev->foreground = -1; prev->background = -1; } if (vbi_char_xor_attr (prev, &curr, VBI_ITALIC)) { if (!vbi_char_has_attr (&curr, VBI_ITALIC)) *d++ = '2'; /* off */ d[0] = '3'; /* italic */ d[1] = ';'; d += 2; } if (vbi_char_xor_attr (prev, &curr, VBI_UNDERLINE)) { if (!vbi_char_has_attr (&curr, VBI_UNDERLINE)) *d++ = '2'; /* off */ d[0] = '4'; /* underline */ d[1] = ';'; d += 2; } if (vbi_char_xor_attr (prev, &curr, VBI_FLASH)) { if (!vbi_char_has_attr (&curr, VBI_FLASH)) *d++ = '2'; /* steady */ d[0] = '5'; /* slowly blinking */ d[1] = ';'; d += 2; } if (prev->foreground != curr.foreground) { d[0] = '3'; d[1] = curr.foreground + '0'; d[2] = ';'; d += 3; } if (prev->background != curr.background) { d[0] = '4'; d[1] = curr.background + '0'; d[2] = ';'; d += 3; } vbi_char_copy_attr (prev, &curr, -1); prev->foreground = curr.foreground; prev->background = curr.background; if ('[' == d[-1]) d -= 2; /* no change, remove CSI */ else d[-1] = 'm'; /* replace last semicolon */ cr->ucs_buffer_length = d - cr->ucs_buffer; return TRUE; } static void cr_timestamp (struct caption_recorder *cr, struct tm * tm, struct timeval * tv, int year_offs, vbi_bool print_msec) { char time_str[32]; const char *format; if (tm->tm_mday <= 0) { time_t sec = tv->tv_sec; if (NULL == gmtime_r (&sec, tm)) { /* Should not happen. */ error_exit ("System time invalid.\n"); } } format = "%04u%02u%02u%02u%02u%02u|"; if (print_msec) format = "%04u%02u%02u%02u%02u%02u.%03u|"; snprintf (time_str, sizeof (time_str), format, tm->tm_year + year_offs, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tv->tv_usec / 1000); cr_puts (cr, time_str); } static void cr_minicut (struct caption_recorder *cr, struct tm * tm, time_t t, vbi_pgno channel) { struct program *pr = PARENT (cr, struct program, cr); if (NULL == pr->option_minicut_dir_name) return; if (NULL != cr->option_caption_file_name[channel - 1]) return; if (tm->tm_mday <= 0) { if (NULL == gmtime_r (&t, tm)) { /* Should not happen. */ error_exit ("System time invalid.\n"); } } if (tm->tm_min != cr->minicut_min[channel - 1]) { char file_name[32]; FILE *fp; fp = cr->caption_fp[channel - 1]; if (NULL != fp) { if (0 != fclose (fp)) { log_errno (1, "Station %u CC file " "write error", station_num (pr)); } } snprintf (file_name, sizeof (file_name), "/%04u%02u%02u%02u%02u00%s", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, cr_file_name_suffix [channel - 1]); /* Note: May be NULL. */ cr->caption_fp[channel - 1] = open_minicut_file (pr, tm, file_name, ".txt"); cr->minicut_min[channel - 1] = tm->tm_min; } } static void cr_stream_time (struct program * pr, struct timeval * tv, int64_t pts) { int64_t long_pts = pts; int64_t usec; if (pr->first_pts >= ((int64_t) 3 << 32) && pts < ((int64_t) 1 << 32)) long_pts += (int64_t) 1 << 33; long_pts -= pr->first_pts; tv->tv_sec = long_pts / 90000 + pr->rel.tv_sec; usec = long_pts % 90000 * 100 / 9 + pr->rel.tv_usec; if (usec > 1000000) { tv->tv_sec += usec / 1000000; usec %= 1000000; } tv->tv_usec = usec; } static void cr_new_line (struct caption_recorder *cr, struct cc_timestamp * ts, vbi_pgno channel, enum cc_mode mode, const vbi_char text[42], unsigned int length) { FILE *fp; if (0 == (ts->sys.tv_sec | ts->sys.tv_usec) || channel >= 10 || length < 32) return; if (0 == (cr->option_caption_mask & (1 << (channel - 1)))) return; cr->ucs_buffer_length = 0; if (cr->usesen) { unsigned int uc[3]; unsigned int column; unsigned int end; unsigned int separator; end = length; /* Eat trailing spaces. */ while (end > 0 && ' ' == text[end - 1].unicode) --end; uc[2] = ' '; separator = 0; for (column = 0; column < end + 1; ++column) { uc[0] = uc[1]; uc[1] = uc[2]; uc[2] = 0; if (column < end) uc[2] = text[column].unicode; if (0 == separator && ' ' == uc[1]) continue; cr_putuc (cr, uc[1]); separator = ' '; switch (uc[1]) { case '"': if ('.' != uc[0] && '!' != uc[0] && '?' != uc[0]) continue; break; case '.': if ('.' == uc[0] || '.' == uc[2]) continue; /* fall through */ case '!': case '?': if ('"' == uc[2]) continue; if (0 != uc[2] && ' ' != uc[2]) continue; break; default: continue; } cr_putuc (cr, '\n'); separator = 0; } if (0 != separator) cr_putuc (cr, separator); } else { struct program *pr = PARENT (cr, struct program, cr); struct timeval tv; struct tm tm; int year_offs; vbi_char prev_char; unsigned int column; tv = ts->sys; year_offs = 1900; if (cr->option_timestamps_rel) { cr_stream_time (pr, &tv, ts->pts); if (0 == pr->rel.tv_sec && 0 == pr->rel.tv_usec) { /* tv.tv_sec is relative to 1970-01-01, and tm.tm_year to 1900. */ year_offs = -69; } } tm.tm_mday = 0; /* not valid */ cr_minicut (cr, &tm, (time_t) tv.tv_sec, channel); if (cr->option_caption_timestamps || cr->option_ucla_prefix) { cr_timestamp (cr, &tm, &tv, year_offs, /* print_msec */ cr->option_ucla_prefix); } if (cr->option_ucla_prefix) { /* Format (borrowed from ccextractor 0.66): YYYYMMDDHHMMSS.MMM|YYYYMMDDHHMMSS.MMM|CCn|mode|text - Roll-Up, Paint-On, Text mode: t1 = capture/ presentation time of first character, t2 = c/p time of carriage return code, rounded down to one millisecond Pop-On mode: t1 = t2 = c/p time of Pop-On code - caption channel CC1 ... CC8 - caption mode RU2, POP, PAI, TXT (we do not distinguish btw RU2, RU3, RU4) */ if (cr->option_timestamps_rel) { if (CC_MODE_POP_ON != mode) cr_stream_time (pr, &tv, pr->cc_pts); } else { tv = pr->now; } tm.tm_mday = 0; /* not valid */ cr_timestamp (cr, &tm, &tv, year_offs, /* print_msec */ cr->option_ucla_prefix); cr_puts (cr, "CC"); cr_putuc (cr, '0' + channel); switch (mode) { case CC_MODE_UNKNOWN: /* Shouldn't happen. Fall through. */ case CC_MODE_ROLL_UP: cr_puts (cr, "|RU2|"); break; case CC_MODE_POP_ON: cr_puts (cr, "|POP|"); break; case CC_MODE_PAINT_ON: cr_puts (cr, "|PAI|"); break; case CC_MODE_TEXT: cr_puts (cr, "|TXT|"); break; } } vbi_char_clear_attr (&prev_char, -1); prev_char.foreground = -1; prev_char.background = -1; for (column = 0; column < length; ++column) { cr_put_attr (cr, &prev_char, text[column]); cr_putuc (cr, text[column].unicode); } if (0 != vbi_char_has_attr (&prev_char, -1) || (uint8_t) -1 != prev_char.foreground || (uint8_t) -1 != prev_char.background) { static const char end_seq[] = { 27, '[', 'm', '\n', 0 }; cr_puts (cr, end_seq); } else { cr_putuc (cr, '\n'); } } fp = cr->caption_fp[channel - 1]; /* May be NULL due to a bug or a temporary failure of open_minicut_file(). */ if (NULL != fp) { vbi_fputs_iconv_ucs2 (fp, locale_codeset, cr->ucs_buffer, cr->ucs_buffer_length, /* repl_char */ '?'); fflush(fp); } } static void init_caption_recorder (struct caption_recorder *cr) { CLEAR (*cr); cr->option_xds_output_file_name = "-"; init_cc_data_decoder (&cr->ccd); init_cc_decoder (&cr->cc); init_dtvcc_decoder (&cr->dtvcc); cr->infoptr = cr->newinfo[0][0][0]; cr->xds_info_prefix = "\33[33m% "; cr->xds_info_suffix = "\33[0m\n"; cr->usewebtv = 1; memset (cr->minicut_min, -1, sizeof (cr->minicut_min)); } /* EIA 608-B Closed Caption decoder. */ static void cc_timestamp_reset (struct cc_timestamp * ts) { CLEAR (ts->sys); ts->pts = -1; } static vbi_bool cc_timestamp_isset (struct cc_timestamp * ts) { return (ts->pts >= 0 || 0 != (ts->sys.tv_sec | ts->sys.tv_usec)); } static const vbi_color cc_color_map [8] = { VBI_WHITE, VBI_GREEN, VBI_BLUE, VBI_CYAN, VBI_RED, VBI_YELLOW, VBI_MAGENTA, VBI_BLACK }; static const int8_t cc_pac_row_map [16] = { /* 0 */ 10, /* 0x1040 */ /* 1 */ -1, /* no function */ /* 2 */ 0, 1, 2, 3, /* 0x1140 ... 0x1260 */ /* 6 */ 11, 12, 13, 14, /* 0x1340 ... 0x1460 */ /* 10 */ 4, 5, 6, 7, 8, 9 /* 0x1540 ... 0x1760 */ }; static void dump_cc (FILE * fp, unsigned int index, unsigned int cc_count, unsigned int cc_valid, unsigned int cc_type, unsigned int c1, unsigned int c2) { uint16_t ucs2_str[2]; unsigned int ch; unsigned int a7; unsigned int f; unsigned int b7; unsigned int u; fprintf (fp, "%s%u/%u %d %s %02X%02X %02X%c%02X%c", (NTSC_F2 == cc_type) ? "\t\t\t\t\t\t\t\t" : "", index, cc_count, !!cc_valid, cc_type_name (cc_type), c1, c2, c1 & 0x7F, vbi_unpar8 (c1) < 0 ? '*' : ' ', c2 & 0x7F, vbi_unpar8 (c2) < 0 ? '*' : ' '); c1 &= 0x7F; c2 &= 0x7F; if (0 == c1) { fputs (" null\n", fp); return; } else if (c1 < 0x10) { fputc ('\n', fp); return; } else if (c1 >= 0x20) { fputs (" '", fp); ucs2_str[0] = vbi_caption_unicode (c1, /* to_upper */ FALSE); ucs2_str[1] = vbi_caption_unicode (c2, /* to_upper */ FALSE); vbi_fputs_iconv_ucs2 (fp, locale_codeset, ucs2_str, 2, /* repl_char */ '?'); fputs ("'\n", fp); return; } else if (c2 < 0x20) { fputs (" INVALID\n", fp); return; } /* Some common bit groups. */ ch = (c1 >> 3) & 1; /* channel */ a7 = c1 & 7; f = c1 & 1; /* field */ b7 = (c2 >> 1) & 7; u = c2 & 1; /* underline */ if (c2 >= 0x40) { unsigned int row; /* Preamble Address Codes -- 001 crrr 1ri bbbu */ row = cc_pac_row_map [a7 * 2 + ((c2 >> 5) & 1)]; if (c2 & 0x10) fprintf (fp, " PAC ch=%u row=%d column=%u u=%u\n", ch, row, b7 * 4, u); else fprintf (fp, " PAC ch=%u row=%d color=%u u=%u\n", ch, row, b7, u); return; } /* Control codes -- 001 caaa 01x bbbu */ switch (a7) { case 0: if (c2 < 0x30) { static const char mnemo [16 * 4] = "BWO\0BWS\0BGO\0BGS\0" "BBO\0BBS\0BCO\0BCS\0" "BRO\0BRS\0BYO\0BYS\0" "BMO\0BMS\0BAO\0BAS"; fprintf (fp, " %s ch=%u\n", mnemo + (c2 & 0xF) * 4, ch); return; } break; case 1: if (c2 < 0x30) { fprintf (fp, " mid-row ch=%u color=%u u=%u\n", ch, b7, u); } else { fprintf (fp, " special character ch=%u '", ch); ucs2_str[0] = vbi_caption_unicode (0x1100 | c2, /* to_upper */ FALSE); vbi_fputs_iconv_ucs2 (fp, locale_codeset, ucs2_str, 1, /* repl_char */ '?'); fputs ("'\n", fp); } return; case 2: /* first group */ case 3: /* second group */ fprintf (fp, " extended character ch=%u '", ch); ucs2_str[0] = vbi_caption_unicode (c1 * 256 + c2, /* to_upper */ FALSE); vbi_fputs_iconv_ucs2 (fp, locale_codeset, ucs2_str, 1, /* repl_char */ '?'); fputs ("'\n", fp); return; case 4: /* f=0 */ case 5: /* f=1 */ if (c2 < 0x30) { static const char mnemo [16 * 4] = "RCL\0BS \0AOF\0AON\0" "DER\0RU2\0RU3\0RU4\0" "FON\0RDC\0TR \0RTD\0" "EDM\0CR \0ENM\0EOC"; fprintf (fp, " %s ch=%u f=%u\n", mnemo + (c2 & 0xF) * 4, ch, f); return; } break; case 6: fprintf (fp, " reserved\n"); return; case 7: switch (c2) { case 0x21 ... 0x23: fprintf (fp, " TO%u ch=%u\n", c2 - 0x20, ch); return; case 0x2D: fprintf (fp, " BT ch=%u\n", ch); return; case 0x2E: fprintf (fp, " FA ch=%u\n", ch); return; case 0x2F: fprintf (fp, " FAU ch=%u\n", ch); return; default: break; } break; } fprintf (fp, " unknown\n"); } static void cc_reset (struct cc_decoder * cd); static vbi_pgno cc_channel_num (struct cc_decoder * cd, struct cc_channel * ch) { return (ch - cd->channel) + 1; } /* Note 47 CFR 15.119 (h) Character Attributes: "(1) Transmission of Attributes. A character may be transmitted with any or all of four attributes: Color, italics, underline, and flash. All of these attributes are set by control codes included in the received data. An attribute will remain in effect until changed by another control code or until the end of the row is reached. Each row begins with a control code which sets the color and underline attributes. (White non-underlined is the default display attribute if no Preamble Address Code is received before the first character on an empty row.) Attributes are not affected by transparent spaces within a row. (i) All Mid-Row Codes and the Flash On command are spacing attributes which appear in the display just as if a standard space (20h) had been received. Preamble Address Codes are non-spacing and will not alter any attributes when used to position the cursor in the midst of a row of characters. (ii) The color attribute has the highest priority and can only be changed by the Mid-Row Code of another color. Italics has the next highest priority. If characters with both color and italics are desired, the italics Mid-Row Code must follow the color assignment. Any color Mid-Row Code will turn off italics. If the least significant bit of a Preamble Address Code or of a color or italics Mid-Row Code is a 1 (high), underlining is turned on. If that bit is a 0 (low), underlining is off. (iii) The flash attribute is transmitted as a Miscellaneous Control Code. The Flash On command will not alter the status of the color, italics, or underline attributes. However, any coloror italics Mid-Row Code will turn off flash. (iv) Thus, for example, if a red, italicized, underlined, flashing character is desired, the attributes must be received in the following order: a red Mid-Row or Preamble Address Code, an italics Mid-Row Code with underline bit, and the Flash On command. The character will then be preceded by three spaces (two if red was assigned via a Preamble Address Code)." EIA 608-B Annex C.7 Preamble Address Codes and Tab Offsets (Regulatory/Preferred): "In general, Preamble Address Codes (PACs) have no immediate effect on the display. A major exception is the receipt of a PAC during roll-up captioning. In that case, if the base row designated in the PAC is not the same as the current base row, the display shall be moved immediately to the new base row. [...] An indenting PAC carries the attributes of white, non-italicized, and it sets underlining on or off. Tab Offset commands do not change these attributes. If an indenting PAC with underline ON is received followed by a Tab Offset and by text, the text shall be underlined (except as noted below). When a displayable character is received, it is deposited at the current cursor position. If there is already a displayable character in the column immediately to the left, the new character assumes the attributes of that character. The new character may be arriving as the result of an indenting PAC (with or without a Tab Offset), and that PAC may designate other attributes, but the new character is forced to assume the attributes of the character immediately to its left, and the PAC's attributes are ignored. If, when a displayable character is received, it overwrites an existing PAC or mid-row code, and there are already characters to the right of the new character, these existing characters shall assume the same attributes as the new character. This adoption can result in a whole caption row suddenly changing color, underline, italics, and/or flash attributes." EIA 608-B Annex C.14 Special Cases Regarding Attributes (Normative): "In most cases, Preamble Address Codes shall set attributes for the caption elements they address. It is theoretically possible for a service provider to use an indenting PAC to start a row at Column 5 or greater, and then to use Backspace to move the cursor to the left of the PAC into an area to which no attributes have been assigned. It is also possible for a roll-up row, having been created by a Carriage Return, to receive characters with no PAC used to set attributes. In these cases, and in any other case where no explicit attributes have been assigned, the display shall be white, non-underlined, non-italicized, and non-flashing. In case new displayable characters are received immediately after a Delete to End of Row (DER), the display attributes of the first deleted character shall remain in effect if there is a displayable character to the left of the cursor; otherwise, the most recently received PAC shall set the display attributes." 47 CFR 15.119 (n) Glossary of terms: "(6) Displayable character: Any letter, number or symbol which is defined for on-screen display, plus the 20h space. [...] (13) Special characters: Displayable characters (except for "transparent space") [...]" */ static void cc_format_row (struct cc_decoder * cd, struct vbi_char * cp, struct cc_channel * ch, unsigned int buffer, unsigned int row, vbi_bool to_upper, vbi_bool padding) { struct vbi_char ac; unsigned int i; cd = cd; /* unused */ /* 47 CFR 15.119 (h)(1). EIA 608-B Section 6.4. */ CLEAR (ac); ac.foreground = VBI_WHITE; ac.background = VBI_BLACK; /* Shortcut. */ if (0 == (ch->dirty[buffer] & (1 << row))) { vbi_char *end; ac.unicode = 0x20; ac.opacity = VBI_TRANSPARENT_SPACE; end = cp + CC_MAX_COLUMNS; if (padding) end += 2; while (cp < end) *cp++ = ac; return; } if (padding) { ac.unicode = 0x20; ac.opacity = VBI_TRANSPARENT_SPACE; *cp++ = ac; } /* EIA 608-B Section 6.4. */ ac.opacity = VBI_OPAQUE; for (i = CC_FIRST_COLUMN - 1; i <= CC_LAST_COLUMN; ++i) { unsigned int color; unsigned int c; ac.unicode = 0x20; c = ch->buffer[buffer][row][i]; if (0 == c) { if (padding && VBI_TRANSPARENT_SPACE != cp[-1].opacity && 0x20 != cp[-1].unicode) { /* Append a space with the same colors and opacity (opaque or transp. backgr.) as the text to the left of it. */ *cp++ = ac; /* We don't underline spaces, see below. */ vbi_char_clear_attr (cp - 1, -1); } else if (i > 0) { *cp++ = ac; cp[-1].opacity = VBI_TRANSPARENT_SPACE; } continue; } else if (c < 0x1020) { if (padding && VBI_TRANSPARENT_SPACE == cp[-1].opacity) { /* Prepend a space with the same colors and opacity (opaque or transp. backgr.) as the text to the right of it. */ cp[-1] = ac; /* We don't underline spaces, see below. */ vbi_char_clear_attr (cp - 1, -1); } if ((c >= 'a' && c <= 'z') || 0x7E == c /* n with tilde */) { /* We do not force these characters to upper case because the standard character set includes upper case versions of these characters and lower case was probably deliberately transmitted. */ ac.unicode = vbi_caption_unicode (c, /* to_upper */ FALSE); } else { ac.unicode = vbi_caption_unicode (c, to_upper); } } else if (c < 0x1040) { unsigned int color; /* Backgr. Attr. Codes -- 001 c000 010 xxxt */ /* EIA 608-B Section 6.2. */ /* This is a set-at spacing attribute. */ color = (c >> 1) & 7; ac.background = cc_color_map[color]; if (c & 0x0001) { ac.opacity = VBI_SEMI_TRANSPARENT; } else { ac.opacity = VBI_OPAQUE; } } else if (c < 0x1120) { /* Preamble Address Codes -- 001 crrr 1ri xxxu */ /* PAC is a non-spacing attribute and only stored in the buffer at the addressed column minus one if it replaces a transparent space (EIA 608-B Annex C.7, C.14). There's always a transparent space to the left of the first column but we show this zeroth column only if padding is enabled. */ if (padding && VBI_TRANSPARENT_SPACE != cp[-1].opacity && 0x20 != cp[-1].unicode) { /* See 0 == c. */ *cp++ = ac; vbi_char_clear_attr (cp - 1, -1); } else if (i > 0) { *cp++ = ac; cp[-1].opacity = VBI_TRANSPARENT_SPACE; } vbi_char_clear_attr (&ac, VBI_UNDERLINE | VBI_ITALIC); if (c & 0x0001) vbi_char_set_attr (&ac, VBI_UNDERLINE); if (c & 0x0010) { ac.foreground = VBI_WHITE; } else { color = (c >> 1) & 7; if (7 == color) { ac.foreground = VBI_WHITE; vbi_char_set_attr (&ac, VBI_ITALIC); } else { ac.foreground = cc_color_map[color]; } } continue; } else if (c < 0x1130) { /* Mid-Row Codes -- 001 c001 010 xxxu */ /* 47 CFR 15.119 Mid-Row Codes table, (h)(1)(ii), (h)(1)(iii). */ /* 47 CFR 15.119 (h)(1)(i), EIA 608-B Section 6.2: Mid-Row codes, FON, BT, FA and FAU are set-at spacing attributes. */ vbi_char_clear_attr (&ac, -1); if (c & 0x0001) vbi_char_set_attr (&ac, VBI_UNDERLINE); color = (c >> 1) & 7; if (7 == color) { vbi_char_set_attr (&ac, VBI_ITALIC); } else { ac.foreground = cc_color_map[color]; } } else if (c < 0x1220) { /* Special Characters -- 001 c001 011 xxxx */ /* 47 CFR 15.119 Character Set Table. */ if (padding && VBI_TRANSPARENT_SPACE == cp[-1].opacity) { cp[-1] = ac; vbi_char_clear_attr (cp - 1, -1); } /* Note we already stored 0 instead of 0x1139 (transparent space) in the ch->buffer. */ ac.unicode = vbi_caption_unicode (c, to_upper); } else if (c < 0x1428) { /* Extended Character Set -- 001 c01x 01x xxxx */ /* EIA 608-B Section 6.4.2 */ if (padding && VBI_TRANSPARENT_SPACE == cp[-1].opacity) { cp[-1] = ac; vbi_char_clear_attr (cp - 1, -1); } /* We do not force these characters to upper case because the extended character set includes upper case versions of all letters and lower case was probably deliberately transmitted. */ ac.unicode = vbi_caption_unicode (c, /* to_upper */ FALSE); } else if (c < 0x172D) { /* FON Flash On -- 001 c10f 010 1000 */ /* 47 CFR 15.119 (h)(1)(iii). */ vbi_char_set_attr (&ac, VBI_FLASH); } else if (c < 0x172E) { /* BT Background Transparent -- 001 c111 010 1101 */ /* EIA 608-B Section 6.4. */ ac.opacity = VBI_TRANSPARENT_FULL; } else if (c <= 0x172F) { /* FA Foreground Black -- 001 c111 010 111u */ /* EIA 608-B Section 6.4. */ if (c & 0x0001) vbi_char_set_attr (&ac, VBI_UNDERLINE); ac.foreground = VBI_BLACK; } *cp++ = ac; /* 47 CFR 15.119 and EIA 608-B are silent about underlined spaces, but considering the example in 47 CFR (h)(1)(iv) which would produce something ugly like "__text" I suppose we should not underline them. For good measure we also clear the invisible italic and flash attribute. */ if (0x20 == ac.unicode) vbi_char_clear_attr (cp - 1, -1); } if (padding) { ac.unicode = 0x20; vbi_char_clear_attr (&ac, -1); if (VBI_TRANSPARENT_SPACE != cp[-1].opacity && 0x20 != cp[-1].unicode) { *cp = ac; } else { ac.opacity = VBI_TRANSPARENT_SPACE; *cp = ac; } } } typedef enum { /** * 47 CFR Section 15.119 requires caption decoders to roll * caption smoothly: Nominally each character cell has a * height of 13 field lines. When this flag is set the current * caption should be displayed with a vertical offset of 12 * field lines, and after every 1001 / 30000 seconds the * caption overlay should move up by one field line until the * offset is zero. The roll rate should be no more than 0.433 * seconds/row for other character cell heights. * * The flag may be set again before the offset returned to * zero. The caption overlay should jump to offset 12 in this * case regardless. */ VBI_START_ROLLING = (1 << 0) } vbi_cc_page_flags; static void cc_display_event (struct cc_decoder * cd, struct cc_channel * ch, vbi_cc_page_flags flags) { cd = cd; /* unused */ ch = ch; flags = flags; } /* This decoder is mainly designed to overlay caption onto live video, but to create transcripts we also offer an event every time a line of caption is complete. The event occurs when certain control codes are received. In POP_ON mode we send the event upon reception of EOC, which swaps the displayed and non-displayed memory. In ROLL_UP and TEXT mode captioners are not expected to display new text by erasing and overwriting a row with PAC, TOx, BS and DER so we ignore these codes. In ROLL_UP mode CR, EDM, EOC, RCL and RDC complete a line. CR moves the cursor to a new row, EDM erases the displayed memory. The remaining codes switch to POP_ON or PAINT_ON mode. In TEXT mode CR and TR are our line completion indicators. CR works as above and TR erases the displayed memory. EDM, EOC, RDC, RCL and RUx have no effect on TEXT buffers. In PAINT_ON mode RDC never erases the displayed memory and CR has no function. Instead captioners can freely position the cursor and erase or overwrite (parts of) rows with PAC, TOx, BS and DER, or erase all rows with EDM. We send an event on PAC, EDM, EOC, RCL and RUx, provided the characters (including spacing attributes) in the current row changed since the last event. PAC is the only control code which can move the cursor to the left and/or to a new row, and likely to introduce a new line. EOC, RCL and RUx switch to POP_ON or ROLL_UP mode. */ static void cc_stream_event (struct cc_decoder * cd, struct cc_channel * ch, unsigned int first_row, unsigned int last_row) { vbi_pgno channel; unsigned int row; channel = cc_channel_num (cd, ch); for (row = first_row; row <= last_row; ++row) { struct vbi_char text[42]; unsigned int end; cc_format_row (cd, text, ch, ch->displayed_buffer, row, /* to_upper */ FALSE, /* padding */ FALSE); for (end = 32; end > 0; --end) { if (VBI_TRANSPARENT_SPACE != text[end - 1].opacity) break; } if (0 == end) continue; { struct program *pr; pr = PARENT (cd, struct program, cr.cc); cr_new_line (&pr->cr, &ch->timestamp_c0, channel, ch->mode, text, /* length */ 32); } } cc_timestamp_reset (&ch->timestamp_c0); } static void cc_put_char (struct cc_decoder * cd, struct cc_channel * ch, int c, vbi_bool displayable, vbi_bool backspace) { uint16_t *text; unsigned int curr_buffer; unsigned int row; unsigned int column; /* 47 CFR Section 15.119 (f)(1), (f)(2), (f)(3). */ curr_buffer = ch->displayed_buffer ^ (CC_MODE_POP_ON == ch->mode); row = ch->curr_row; column = ch->curr_column; if (unlikely (backspace)) { /* 47 CFR 15.119 (f)(1)(vi), (f)(2)(ii), (f)(3)(i). EIA 608-B Section 6.4.2, 7.4. */ if (column > CC_FIRST_COLUMN) --column; } else { /* 47 CFR 15.119 (f)(1)(v), (f)(1)(vi), (f)(2)(ii), (f)(3)(i). EIA 608-B Section 7.4. */ if (column < CC_LAST_COLUMN) ch->curr_column = column + 1; } text = &ch->buffer[curr_buffer][row][0]; text[column] = c; /* Send a display update event when the displayed buffer of the current channel changed, but no more than once for each pair of Closed Caption bytes. */ /* XXX This may not be a visible change, but such cases are rare and we'd need something close to format_row() to be sure. */ if (CC_MODE_POP_ON != ch->mode) { cd->event_pending = ch; } if (likely (displayable)) { /* Note EIA 608-B Annex C.7, C.14. */ if (CC_FIRST_COLUMN == column || 0 == text[column - 1]) { /* Note last_pac may be 0 as well. */ text[column - 1] = ch->last_pac; } if (c >= 'a' && c <= 'z') { ch->uppercase_predictor = 0; } else if (c >= 'A' && c <= 'Z') { unsigned int up; up = ch->uppercase_predictor + 1; if (up > 0) ch->uppercase_predictor = up; } } else if (unlikely (0 == c)) { unsigned int i; /* This is Special Character "Transparent space". */ for (i = CC_FIRST_COLUMN; i <= CC_LAST_COLUMN; ++i) c |= ch->buffer[curr_buffer][row][i]; ch->dirty[curr_buffer] &= ~((0 == c) << row); return; } assert (sizeof (ch->dirty[0]) * 8 - 1 >= CC_MAX_ROWS); ch->dirty[curr_buffer] |= 1 << row; if (ch->timestamp_c0.pts < 0 && 0 == (ch->timestamp_c0.sys.tv_sec | ch->timestamp_c0.sys.tv_usec)) { ch->timestamp_c0 = cd->timestamp; } } static void cc_ext_control_code (struct cc_decoder * cd, struct cc_channel * ch, unsigned int c2) { unsigned int column; switch (c2) { case 0x21: /* TO1 */ case 0x22: /* TO2 */ case 0x23: /* TO3 Tab Offset -- 001 c111 010 00xx */ /* 47 CFR 15.119 (e)(1)(ii). EIA 608-B Section 7.4, Annex C.7. */ column = ch->curr_column + (c2 & 3); ch->curr_column = MIN (column, (unsigned int) CC_LAST_COLUMN); break; case 0x24: /* Select standard character set in normal size */ case 0x25: /* Select standard character set in double size */ case 0x26: /* Select first private character set */ case 0x27: /* Select second private character set */ case 0x28: /* Select character set GB 2312-80 (Chinese) */ case 0x29: /* Select character set KSC 5601-1987 (Korean) */ case 0x2A: /* Select first registered character set. */ /* EIA 608-B Section 6.3 Closed Group Extensions. */ break; case 0x2D: /* BT Background Transparent -- 001 c111 010 1101 */ case 0x2E: /* FA Foreground Black -- 001 c111 010 1110 */ case 0x2F: /* FAU Foregr. Black Underl. -- 001 c111 010 1111 */ /* EIA 608-B Section 6.2. */ cc_put_char (cd, ch, 0x1700 | c2, /* displayable */ FALSE, /* backspace */ TRUE); break; default: /* 47 CFR Section 15.119 (j): Ignore. */ break; } } /* Send a stream event if the current row has changed since the last stream event. This is necessary in paint-on mode where CR has no function and captioners can freely position the cursor to erase or overwrite (parts of) rows. */ static void cc_stream_event_if_changed (struct cc_decoder * cd, struct cc_channel * ch) { unsigned int curr_buffer; unsigned int row; unsigned int i; curr_buffer = ch->displayed_buffer; row = ch->curr_row; if (0 == (ch->dirty[curr_buffer] & (1 << row))) return; for (i = CC_FIRST_COLUMN; i <= CC_LAST_COLUMN; ++i) { unsigned int c1; unsigned int c2; c1 = ch->buffer[curr_buffer][row][i]; if (c1 >= 0x1040) { if (c1 < 0x1120) { c1 = 0; /* PAC -- non-spacing */ } else if (c1 < 0x1130 || c1 >= 0x1428) { /* MR, FON, BT, FA, FAU -- spacing */ c1 = 0x20; } } c2 = ch->buffer[2][row][i]; if (c2 >= 0x1040) { if (c2 < 0x1120) { c2 = 0; } else if (c2 < 0x1130 || c2 >= 0x1428) { c1 = 0x20; } } if (c1 != c2) { cc_stream_event (cd, ch, row, row); memcpy (ch->buffer[2][row], ch->buffer[curr_buffer][row], sizeof (ch->buffer[0][0])); ch->dirty[2] = ch->dirty[curr_buffer]; return; } } } static void cc_end_of_caption (struct cc_decoder * cd, struct cc_channel * ch) { unsigned int curr_buffer; unsigned int row; /* EOC End Of Caption -- 001 c10f 010 1111 */ curr_buffer = ch->displayed_buffer; switch (ch->mode) { case CC_MODE_UNKNOWN: case CC_MODE_POP_ON: break; case CC_MODE_ROLL_UP: row = ch->curr_row; if (0 != (ch->dirty[curr_buffer] & (1 << row))) cc_stream_event (cd, ch, row, row); break; case CC_MODE_PAINT_ON: cc_stream_event_if_changed (cd, ch); break; case CC_MODE_TEXT: /* Not reached. (ch is a caption channel.) */ return; } ch->displayed_buffer = curr_buffer ^= 1; /* 47 CFR Section 15.119 (f)(2). */ ch->mode = CC_MODE_POP_ON; if (0 != ch->dirty[curr_buffer]) { ch->timestamp_c0 = cd->timestamp; cc_stream_event (cd, ch, CC_FIRST_ROW, CC_LAST_ROW); cc_display_event (cd, ch, 0); } } static void cc_carriage_return (struct cc_decoder * cd, struct cc_channel * ch) { unsigned int curr_buffer; unsigned int row; unsigned int window_rows; unsigned int first_row; /* CR Carriage Return -- 001 c10f 010 1101 */ curr_buffer = ch->displayed_buffer; row = ch->curr_row; switch (ch->mode) { case CC_MODE_UNKNOWN: return; case CC_MODE_ROLL_UP: /* 47 CFR Section 15.119 (f)(1)(iii). */ ch->curr_column = CC_FIRST_COLUMN; /* 47 CFR 15.119 (f)(1): "The cursor always remains on the base row." */ /* XXX Spec? */ ch->last_pac = 0; /* No event if the buffer contains only TRANSPARENT_SPACEs. */ if (0 == ch->dirty[curr_buffer]) return; window_rows = MIN (row + 1 - CC_FIRST_ROW, ch->window_rows); break; case CC_MODE_POP_ON: case CC_MODE_PAINT_ON: /* 47 CFR 15.119 (f)(2)(i), (f)(3)(i): No effect. */ return; case CC_MODE_TEXT: /* 47 CFR Section 15.119 (f)(1)(iii). */ ch->curr_column = CC_FIRST_COLUMN; /* XXX Spec? */ ch->last_pac = 0; /* EIA 608-B Section 7.4: "When Text Mode has initially been selected and the specified Text memory is empty, the cursor starts at the topmost row, Column 1, and moves down to Column 1 on the next row each time a Carriage Return is received until the last available row is reached. A variety of methods may be used to accomplish the scrolling, provided that the text is legible while moving. For example, as soon as all of the available rows of text are on the screen, Text Mode switches to the standard roll-up type of presentation." */ if (CC_LAST_ROW != row) { if (0 != (ch->dirty[curr_buffer] & (1 << row))) { cc_stream_event (cd, ch, row, row); } ch->curr_row = row + 1; return; } /* No event if the buffer contains all TRANSPARENT_SPACEs. */ if (0 == ch->dirty[curr_buffer]) return; window_rows = CC_MAX_ROWS; break; } /* 47 CFR Section 15.119 (f)(1)(iii). In roll-up mode: "Each time a Carriage Return is received, the text in the top row of the window is erased from memory and from the display or scrolled off the top of the window. The remaining rows of text are each rolled up into the next highest row in the window, leaving the base row blank and ready to accept new text." */ if (0 != (ch->dirty[curr_buffer] & (1 << row))) { cc_stream_event (cd, ch, row, row); } first_row = row + 1 - window_rows; memmove (ch->buffer[curr_buffer][first_row], ch->buffer[curr_buffer][first_row + 1], (window_rows - 1) * sizeof (ch->buffer[0][0])); ch->dirty[curr_buffer] >>= 1; memset (ch->buffer[curr_buffer][row], 0, sizeof (ch->buffer[0][0])); cc_display_event (cd, ch, VBI_START_ROLLING); } static void cc_erase_memory (struct cc_decoder * cd, struct cc_channel * ch, unsigned int buffer) { if (0 != ch->dirty[buffer]) { CLEAR (ch->buffer[buffer]); ch->dirty[buffer] = 0; if (buffer == ch->displayed_buffer) cc_display_event (cd, ch, 0); } } static void cc_erase_displayed_memory (struct cc_decoder * cd, struct cc_channel * ch) { unsigned int row; /* EDM Erase Displayed Memory -- 001 c10f 010 1100 */ switch (ch->mode) { case CC_MODE_UNKNOWN: /* We have not received EOC, RCL, RDC or RUx yet, but ch is valid. */ break; case CC_MODE_ROLL_UP: row = ch->curr_row; if (0 != (ch->dirty[ch->displayed_buffer] & (1 << row))) cc_stream_event (cd, ch, row, row); break; case CC_MODE_PAINT_ON: cc_stream_event_if_changed (cd, ch); break; case CC_MODE_POP_ON: /* Nothing to do. */ break; case CC_MODE_TEXT: /* Not reached. (ch is a caption channel.) */ return; } /* May send a display event. */ cc_erase_memory (cd, ch, ch->displayed_buffer); } static void cc_text_restart (struct cc_decoder * cd, struct cc_channel * ch) { unsigned int curr_buffer; unsigned int row; /* TR Text Restart -- 001 c10f 010 1010 */ curr_buffer = ch->displayed_buffer; row = ch->curr_row; /* ch->mode is invariably CC_MODE_TEXT. */ if (0 != (ch->dirty[curr_buffer] & (1 << row))) { cc_stream_event (cd, ch, row, row); } /* EIA 608-B Section 7.4. */ /* May send a display event. */ cc_erase_memory (cd, ch, ch->displayed_buffer); /* EIA 608-B Section 7.4. */ ch->curr_row = CC_FIRST_ROW; ch->curr_column = CC_FIRST_COLUMN; } static void cc_resume_direct_captioning (struct cc_decoder * cd, struct cc_channel * ch) { unsigned int curr_buffer; unsigned int row; /* RDC Resume Direct Captioning -- 001 c10f 010 1001 */ /* 47 CFR 15.119 (f)(1)(x), (f)(2)(vi) and EIA 608-B Annex B.7: Does not erase memory, does not move the cursor when resuming after a Text transmission. XXX If ch->mode is unknown, roll-up or pop-on, what shall we do if no PAC is received between RDC and the text? */ curr_buffer = ch->displayed_buffer; row = ch->curr_row; switch (ch->mode) { case CC_MODE_ROLL_UP: if (0 != (ch->dirty[curr_buffer] & (1 << row))) cc_stream_event (cd, ch, row, row); /* fall through */ case CC_MODE_UNKNOWN: case CC_MODE_POP_ON: /* No change since last stream_event(). */ memcpy (ch->buffer[2], ch->buffer[curr_buffer], sizeof (ch->buffer[2])); break; case CC_MODE_PAINT_ON: /* Mode continues. */ break; case CC_MODE_TEXT: /* Not reached. (ch is a caption channel.) */ return; } ch->mode = CC_MODE_PAINT_ON; } static void cc_resize_window (struct cc_decoder * cd, struct cc_channel * ch, unsigned int new_rows) { unsigned int curr_buffer; unsigned int max_rows; unsigned int old_rows; unsigned int row1; curr_buffer = ch->displayed_buffer; /* No event if the buffer contains all TRANSPARENT_SPACEs. */ if (0 == ch->dirty[curr_buffer]) return; row1 = ch->curr_row + 1; max_rows = row1 - CC_FIRST_ROW; old_rows = MIN (ch->window_rows, max_rows); new_rows = MIN (new_rows, max_rows); /* Nothing to do unless the window shrinks. */ if (0 == new_rows || new_rows >= old_rows) return; memset (&ch->buffer[curr_buffer][row1 - old_rows][0], 0, (old_rows - new_rows) * sizeof (ch->buffer[0][0])); ch->dirty[curr_buffer] &= -1 << (row1 - new_rows); cc_display_event (cd, ch, 0); } static void cc_roll_up_caption (struct cc_decoder * cd, struct cc_channel * ch, unsigned int c2) { unsigned int window_rows; /* Roll-Up Captions -- 001 c10f 010 01xx */ window_rows = (c2 & 7) - 3; /* 2, 3, 4 */ switch (ch->mode) { case CC_MODE_ROLL_UP: /* 47 CFR 15.119 (f)(1)(iv). */ /* May send a display event. */ cc_resize_window (cd, ch, window_rows); /* fall through */ case CC_MODE_UNKNOWN: ch->mode = CC_MODE_ROLL_UP; ch->window_rows = window_rows; /* 47 CFR 15.119 (f)(1)(ix): No cursor movements, no memory erasing. */ break; case CC_MODE_PAINT_ON: cc_stream_event_if_changed (cd, ch); /* fall through */ case CC_MODE_POP_ON: ch->mode = CC_MODE_ROLL_UP; ch->window_rows = window_rows; /* 47 CFR 15.119 (f)(1)(ii). */ ch->curr_row = CC_LAST_ROW; ch->curr_column = CC_FIRST_COLUMN; /* 47 CFR 15.119 (f)(1)(x). */ /* May send a display event. */ cc_erase_memory (cd, ch, ch->displayed_buffer); cc_erase_memory (cd, ch, ch->displayed_buffer ^ 1); break; case CC_MODE_TEXT: /* Not reached. (ch is a caption channel.) */ return; } } static void cc_delete_to_end_of_row (struct cc_decoder * cd, struct cc_channel * ch) { unsigned int curr_buffer; unsigned int row; /* DER Delete To End Of Row -- 001 c10f 010 0100 */ /* 47 CFR 15.119 (f)(1)(vii), (f)(2)(iii), (f)(3)(ii) and EIA 608-B Section 7.4: In all caption modes and Text mode "[the] Delete to End of Row command will erase from memory any characters or control codes starting at the current cursor location and in all columns to its right on the same row." */ curr_buffer = ch->displayed_buffer ^ (CC_MODE_POP_ON == ch->mode); row = ch->curr_row; /* No event if the row contains only TRANSPARENT_SPACEs. */ if (0 != (ch->dirty[curr_buffer] & (1 << row))) { unsigned int column; unsigned int i; uint16_t c; column = ch->curr_column; memset (&ch->buffer[curr_buffer][row][column], 0, (CC_LAST_COLUMN - column + 1) * sizeof (ch->buffer[0][0][0])); c = 0; for (i = CC_FIRST_COLUMN; i < column; ++i) c |= ch->buffer[curr_buffer][row][i]; ch->dirty[curr_buffer] &= ~((0 == c) << row); cc_display_event (cd, ch, 0); } } static void cc_backspace (struct cc_decoder * cd, struct cc_channel * ch) { unsigned int curr_buffer; unsigned int row; unsigned int column; /* BS Backspace -- 001 c10f 010 0001 */ /* 47 CFR Section 15.119 (f)(1)(vi), (f)(2)(ii), (f)(3)(i) and EIA 608-B Section 7.4. */ column = ch->curr_column; if (column <= CC_FIRST_COLUMN) return; ch->curr_column = --column; curr_buffer = ch->displayed_buffer ^ (CC_MODE_POP_ON == ch->mode); row = ch->curr_row; /* No event if there's no visible effect. */ if (0 != ch->buffer[curr_buffer][row][column]) { unsigned int i; uint16_t c; /* 47 CFR 15.119 (f), (f)(1)(vi), (f)(2)(ii) and EIA 608-B Section 7.4. */ ch->buffer[curr_buffer][row][column] = 0; c = 0; for (i = CC_FIRST_COLUMN; i <= CC_LAST_COLUMN; ++i) c |= ch->buffer[curr_buffer][row][i]; ch->dirty[curr_buffer] &= ~((0 == c) << row); cc_display_event (cd, ch, 0); } } static void cc_resume_caption_loading (struct cc_decoder * cd, struct cc_channel * ch) { unsigned int row; /* RCL Resume Caption Loading -- 001 c10f 010 0000 */ switch (ch->mode) { case CC_MODE_UNKNOWN: case CC_MODE_POP_ON: break; case CC_MODE_ROLL_UP: row = ch->curr_row; if (0 != (ch->dirty[ch->displayed_buffer] & (1 << row))) cc_stream_event (cd, ch, row, row); break; case CC_MODE_PAINT_ON: cc_stream_event_if_changed (cd, ch); break; case CC_MODE_TEXT: /* Not reached. (ch is a caption channel.) */ return; } /* 47 CFR 15.119 (f)(1)(x): Does not erase memory. (f)(2)(iv): Cursor position remains unchanged. */ ch->mode = CC_MODE_POP_ON; } /* Note curr_ch is invalid if UNKNOWN_CC_CHANNEL == cd->cc.curr_ch_num. */ static struct cc_channel * cc_switch_channel (struct cc_decoder * cd, struct cc_channel * curr_ch, vbi_pgno new_ch_num, enum field_num f) { struct cc_channel *new_ch; if (UNKNOWN_CC_CHANNEL != cd->curr_ch_num[f] && CC_MODE_UNKNOWN != curr_ch->mode) { /* XXX Force a display update if we do not send events on every display change. */ } cd->curr_ch_num[f] = new_ch_num; new_ch = &cd->channel[new_ch_num - VBI_CAPTION_CC1]; return new_ch; } /* Note ch is invalid if UNKNOWN_CC_CHANNEL == cd->cc.curr_ch_num[f]. */ static void cc_misc_control_code (struct cc_decoder * cd, struct cc_channel * ch, unsigned int c2, unsigned int ch_num0, enum field_num f) { unsigned int new_ch_num; /* Misc Control Codes -- 001 c10f 010 xxxx */ /* c = channel (0 -> CC1/CC3/T1/T3, 1 -> CC2/CC4/T2/T4) -- 47 CFR Section 15.119, EIA 608-B Section 7.7. f = field (0 -> F1, 1 -> F2) -- EIA 608-B Section 8.4, 8.5. */ /* XXX The f flag is intended to detect accidential field swapping and we should use it for that purpose. */ switch (c2 & 15) { case 0: /* RCL Resume Caption Loading -- 001 c10f 010 0000 */ /* 47 CFR 15.119 (f)(2) and EIA 608-B Section 7.7. */ new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3); ch = cc_switch_channel (cd, ch, new_ch_num, f); cc_resume_caption_loading (cd, ch); break; case 1: /* BS Backspace -- 001 c10f 010 0001 */ if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f] || CC_MODE_UNKNOWN == ch->mode) break; cc_backspace (cd, ch); break; case 2: /* reserved (formerly AOF Alarm Off) */ case 3: /* reserved (formerly AON Alarm On) */ break; case 4: /* DER Delete To End Of Row -- 001 c10f 010 0100 */ if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f] || CC_MODE_UNKNOWN == ch->mode) break; cc_delete_to_end_of_row (cd, ch); break; case 5: /* RU2 */ case 6: /* RU3 */ case 7: /* RU4 Roll-Up Captions -- 001 c10f 010 01xx */ /* 47 CFR 15.119 (f)(1) and EIA 608-B Section 7.7. */ new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3); ch = cc_switch_channel (cd, ch, new_ch_num, f); cc_roll_up_caption (cd, ch, c2); break; case 8: /* FON Flash On -- 001 c10f 010 1000 */ if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f] || CC_MODE_UNKNOWN == ch->mode) break; /* 47 CFR 15.119 (h)(1)(i): Spacing attribute. */ cc_put_char (cd, ch, 0x1428, /* displayable */ FALSE, /* backspace */ FALSE); break; case 9: /* RDC Resume Direct Captioning -- 001 c10f 010 1001 */ /* 47 CFR 15.119 (f)(3) and EIA 608-B Section 7.7. */ new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3); ch = cc_switch_channel (cd, ch, new_ch_num, f); cc_resume_direct_captioning (cd, ch); break; case 10: /* TR Text Restart -- 001 c10f 010 1010 */ /* EIA 608-B Section 7.4. */ new_ch_num = VBI_CAPTION_T1 + (ch_num0 & 3); ch = cc_switch_channel (cd, ch, new_ch_num, f); cc_text_restart (cd, ch); break; case 11: /* RTD Resume Text Display -- 001 c10f 010 1011 */ /* EIA 608-B Section 7.4. */ new_ch_num = VBI_CAPTION_T1 + (ch_num0 & 3); ch = cc_switch_channel (cd, ch, new_ch_num, f); /* ch->mode is invariably CC_MODE_TEXT. */ break; case 12: /* EDM Erase Displayed Memory -- 001 c10f 010 1100 */ /* 47 CFR 15.119 (f). EIA 608-B Section 7.7 and Annex B.7: "[The] command shall be acted upon as appropriate for caption processing without terminating the Text Mode data stream." */ /* We need not check cd->curr_ch_num because bit 2 is implied, bit 1 is the known field number and bit 0 is coded in the control code. */ ch = &cd->channel[ch_num0 & 3]; cc_erase_displayed_memory (cd, ch); break; case 13: /* CR Carriage Return -- 001 c10f 010 1101 */ if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f]) break; cc_carriage_return (cd, ch); break; case 14: /* ENM Erase Non-Displayed Memory -- 001 c10f 010 1110 */ /* 47 CFR 15.119 (f)(2)(v). EIA 608-B Section 7.7 and Annex B.7: "[The] command shall be acted upon as appropriate for caption processing without terminating the Text Mode data stream." */ /* See EDM. */ ch = &cd->channel[ch_num0 & 3]; cc_erase_memory (cd, ch, ch->displayed_buffer ^ 1); break; case 15: /* EOC End Of Caption -- 001 c10f 010 1111 */ /* 47 CFR 15.119 (f), (f)(2), (f)(3)(iv) and EIA 608-B Section 7.7, Annex C.11. */ new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3); ch = cc_switch_channel (cd, ch, new_ch_num, f); cc_end_of_caption (cd, ch); break; } } static void cc_move_window (struct cc_decoder * cd, struct cc_channel * ch, unsigned int new_base_row) { uint8_t *base; unsigned int curr_buffer; unsigned int bytes_per_row; unsigned int old_max_rows; unsigned int new_max_rows; unsigned int copy_bytes; unsigned int erase_begin; unsigned int erase_end; curr_buffer = ch->displayed_buffer; /* No event if we do not move the window or the buffer contains only TRANSPARENT_SPACEs. */ if (new_base_row == ch->curr_row || 0 == ch->dirty[curr_buffer]) return; base = (void *) &ch->buffer[curr_buffer][CC_FIRST_ROW][0]; bytes_per_row = sizeof (ch->buffer[0][0]); old_max_rows = ch->curr_row + 1 - CC_FIRST_ROW; new_max_rows = new_base_row + 1 - CC_FIRST_ROW; copy_bytes = MIN (MIN (old_max_rows, new_max_rows), ch->window_rows) * bytes_per_row; if (new_base_row < ch->curr_row) { erase_begin = (new_base_row + 1) * bytes_per_row; erase_end = (ch->curr_row + 1) * bytes_per_row; memmove (base + erase_begin - copy_bytes, base + erase_end - copy_bytes, copy_bytes); ch->dirty[curr_buffer] >>= ch->curr_row - new_base_row; } else { erase_begin = (ch->curr_row + 1) * bytes_per_row - copy_bytes; erase_end = (new_base_row + 1) * bytes_per_row - copy_bytes; memmove (base + erase_end, base + erase_begin, copy_bytes); ch->dirty[curr_buffer] <<= new_base_row - ch->curr_row; ch->dirty[curr_buffer] &= CC_ALL_ROWS_MASK; } memset (base + erase_begin, 0, erase_end - erase_begin); cc_display_event (cd, ch, 0); } static void cc_preamble_address_code (struct cc_decoder * cd, struct cc_channel * ch, unsigned int c1, unsigned int c2) { unsigned int row; /* PAC Preamble Address Codes -- 001 crrr 1ri xxxu */ row = cc_pac_row_map[(c1 & 7) * 2 + ((c2 >> 5) & 1)]; if ((int) row < 0) return; switch (ch->mode) { case CC_MODE_UNKNOWN: return; case CC_MODE_ROLL_UP: /* EIA 608-B Annex C.4. */ if (ch->window_rows > row + 1) row = ch->window_rows - 1; /* 47 CFR Section 15.119 (f)(1)(ii). */ /* May send a display event. */ cc_move_window (cd, ch, row); ch->curr_row = row; break; case CC_MODE_PAINT_ON: cc_stream_event_if_changed (cd, ch); /* fall through */ case CC_MODE_POP_ON: /* XXX 47 CFR 15.119 (f)(2)(i), (f)(3)(i): In Pop-on and paint-on mode "Preamble Address Codes can be used to move the cursor around the screen in random order to place captions on Rows 1 to 15." We do not have a limit on the number of displayable rows, but as EIA 608-B Annex C.6 points out, if more than four rows must be displayed they were probably received in error and we should respond accordingly. */ /* 47 CFR Section 15.119 (d)(1)(i) and EIA 608-B Annex C.7. */ ch->curr_row = row; break; case CC_MODE_TEXT: /* 47 CFR 15.119 (e)(1) and EIA 608-B Section 7.4: Does not change the cursor row. */ break; } if (c2 & 0x10) { /* 47 CFR 15.119 (e)(1)(i) and EIA 608-B Table 71. */ ch->curr_column = CC_FIRST_COLUMN + (c2 & 0x0E) * 2; } /* PAC is a non-spacing attribute for the next character, see cc_put_char(). */ ch->last_pac = 0x1000 | c2; } static void cc_control_code (struct cc_decoder * cd, unsigned int c1, unsigned int c2, enum field_num f) { struct cc_channel *ch; unsigned int ch_num0; if (option_debug & DEBUG_CC_DECODER) { fprintf (stderr, "%s %02x %02x %d\n", __FUNCTION__, c1, c2, f); } /* Caption / text, field 1 / 2, primary / secondary channel. */ ch_num0 = (((cd->curr_ch_num[f] - VBI_CAPTION_CC1) & 4) + f * 2 + ((c1 >> 3) & 1)); /* Note ch is invalid if UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f]. */ ch = &cd->channel[ch_num0]; if (c2 >= 0x40) { /* Preamble Address Codes -- 001 crrr 1ri xxxu */ if (UNKNOWN_CC_CHANNEL != cd->curr_ch_num[f]) cc_preamble_address_code (cd, ch, c1, c2); return; } switch (c1 & 7) { case 0: if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f] || CC_MODE_UNKNOWN == ch->mode) break; if (c2 < 0x30) { /* Backgr. Attr. Codes -- 001 c000 010 xxxt */ /* EIA 608-B Section 6.2. */ cc_put_char (cd, ch, 0x1000 | c2, /* displayable */ FALSE, /* backspace */ TRUE); } else { /* Undefined. */ } break; case 1: if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f] || CC_MODE_UNKNOWN == ch->mode) break; if (c2 < 0x30) { /* Mid-Row Codes -- 001 c001 010 xxxu */ /* 47 CFR 15.119 (h)(1)(i): Spacing attribute. */ cc_put_char (cd, ch, 0x1100 | c2, /* displayable */ FALSE, /* backspace */ FALSE); } else { /* Special Characters -- 001 c001 011 xxxx */ if (0x39 == c2) { /* Transparent space. */ cc_put_char (cd, ch, 0, /* displayable */ FALSE, /* backspace */ FALSE); } else { cc_put_char (cd, ch, 0x1100 | c2, /* displayable */ TRUE, /* backspace */ FALSE); } } break; case 2: case 3: /* Extended Character Set -- 001 c01x 01x xxxx */ if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f] || CC_MODE_UNKNOWN == ch->mode) break; /* EIA 608-B Section 6.4.2. */ cc_put_char (cd, ch, (c1 * 256 + c2) & 0x777F, /* displayable */ TRUE, /* backspace */ TRUE); break; case 4: case 5: if (c2 < 0x30) { /* Misc. Control Codes -- 001 c10f 010 xxxx */ cc_misc_control_code (cd, ch, c2, ch_num0, f); } else { /* Undefined. */ } break; case 6: /* reserved */ break; case 7: /* Extended control codes -- 001 c111 01x xxxx */ if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f] || CC_MODE_UNKNOWN == ch->mode) break; cc_ext_control_code (cd, ch, c2); break; } } static vbi_bool cc_characters (struct cc_decoder * cd, struct cc_channel * ch, int c) { if (option_debug & DEBUG_CC_DECODER) { fprintf (stderr, "%s %02x '%c'\n", __FUNCTION__, c, printable (c)); } if (0 == c) { if (CC_MODE_UNKNOWN == ch->mode) return TRUE; /* XXX After x NUL characters (presumably a caption pause), force a display update if we do not send events on every display change. */ return TRUE; } if (c < 0x20) { /* Parity error or invalid data. */ if (c < 0 && CC_MODE_UNKNOWN != ch->mode) { /* 47 CFR Section 15.119 (j)(1). */ cc_put_char (cd, ch, 0x7F, /* displayable */ TRUE, /* backspace */ FALSE); } return FALSE; } if (CC_MODE_UNKNOWN != ch->mode) { cc_put_char (cd, ch, c, /* displayable */ TRUE, /* backspace */ FALSE); } return TRUE; } static vbi_bool cc_feed (struct cc_decoder * cd, const uint8_t buffer[2], unsigned int line, const struct timeval * tv, int64_t pts) { int c1, c2; enum field_num f; vbi_bool all_successful; assert (NULL != cd); if (option_debug & DEBUG_CC_DECODER) { fprintf (stderr, "%s %02x %02x '%c%c' " "%3d %f %" PRId64 "\n", __FUNCTION__, buffer[0] & 0x7F, buffer[1] & 0x7F, printable (buffer[0]), printable (buffer[1]), line, tv->tv_sec + tv->tv_usec * (1 / 1e6), pts); } f = FIELD_1; switch (line) { case 21: /* NTSC */ case 22: /* PAL/SECAM */ break; case 284: /* NTSC */ f = FIELD_2; break; default: return FALSE; } cd->timestamp.sys = *tv; cd->timestamp.pts = pts; /* FIXME deferred reset here */ c1 = vbi_unpar8 (buffer[0]); c2 = vbi_unpar8 (buffer[1]); all_successful = TRUE; /* 47 CFR 15.119 (2)(i)(4): "If the first transmission of a control code pair passes parity, it is acted upon within one video frame. If the next frame contains a perfect repeat of the same pair, the redundant code is ignored. If, however, the next frame contains a different but also valid control code pair, this pair, too, will be acted upon (and the receiver will expect a repeat of this second pair in the next frame). If the first byte of the expected redundant control code pair fails the parity check and the second byte is identical to the second byte in the immediately preceding pair, then the expected redundant code is ignored. If there are printing characters in place of the redundant code, they will be processed normally." EIA 608-B Section 8.3: Caption control codes on field 2 may repeat as on field 1. Section 8.6.2: XDS control codes shall not repeat. */ if (unlikely (c1 < 0)) { goto parity_error; } else if (c1 == cd->expect_ctrl[f][0] && c2 == cd->expect_ctrl[f][1]) { /* Already acted upon. */ cd->expect_ctrl[f][0] = -1; goto finish; } if (c1 >= 0x10 && c1 < 0x20) { /* Caption control code. */ /* There's no XDS on field 1, we just use an array to save a branch. */ cd->in_xds[f] = FALSE; /* 47 CFR Section 15.119 (i)(1), (i)(2). */ if (c2 < 0x20) { /* Parity error or invalid control code. Let's hope it repeats. */ goto parity_error; } cc_control_code (cd, c1, c2, f); if (cd->event_pending) { cc_display_event (cd, cd->event_pending, 0); cd->event_pending = NULL; } cd->expect_ctrl[f][0] = c1; cd->expect_ctrl[f][1] = c2; } else { cd->expect_ctrl[f][0] = -1; if (c1 < 0x10) { if (FIELD_1 == f) { /* 47 CFR Section 15.119 (i)(1): "If the non-printing character in the pair is in the range 00h to 0Fh, that character alone will be ignored and the second character will be treated normally." */ c1 = 0; } else if (0x0F == c1) { /* XDS packet terminator. */ cd->in_xds[FIELD_2] = FALSE; goto finish; } else if (c1 >= 0x01) { /* XDS packet start or continuation. EIA 608-B Section 7.7, 8.5: Also interrupts a Text mode transmission. */ cd->in_xds[FIELD_2] = TRUE; goto finish; } } if (!cd->in_xds[f]) { struct cc_channel *ch; vbi_pgno ch_num; ch_num = cd->curr_ch_num[f]; if (UNKNOWN_CC_CHANNEL == ch_num) goto finish; ch_num = ((ch_num - VBI_CAPTION_CC1) & 5) + f * 2; ch = &cd->channel[ch_num]; all_successful &= cc_characters (cd, ch, c1); all_successful &= cc_characters (cd, ch, c2); if (cd->event_pending) { cc_display_event (cd, cd->event_pending, 0); cd->event_pending = NULL; } } } finish: cd->error_history = cd->error_history * 2 + all_successful; return all_successful; parity_error: cd->expect_ctrl[f][0] = -1; /* XXX Some networks stupidly transmit 0x0000 instead of 0x8080 as filler. Perhaps we shouldn't take that as a serious parity error. */ cd->error_history *= 2; return FALSE; } static void cc_reset (struct cc_decoder * cd) { unsigned int ch_num; assert (NULL != cd); if (option_debug & DEBUG_CC_DECODER) { fprintf (stderr, "%s\n", __FUNCTION__); } for (ch_num = 0; ch_num < MAX_CC_CHANNELS; ++ch_num) { struct cc_channel *ch; ch = &cd->channel[ch_num]; if (ch_num <= 3) { ch->mode = CC_MODE_UNKNOWN; /* Something suitable for roll-up mode. */ ch->curr_row = CC_LAST_ROW; ch->curr_column = CC_FIRST_COLUMN; ch->window_rows = 4; } else { ch->mode = CC_MODE_TEXT; /* invariable */ /* EIA 608-B Section 7.4: "When Text Mode has initially been selected and the specified Text memory is empty, the cursor starts at the topmost row, Column 1." */ ch->curr_row = CC_FIRST_ROW; ch->curr_column = CC_FIRST_COLUMN; ch->window_rows = 0; /* n/a */ } ch->displayed_buffer = 0; ch->last_pac = 0; CLEAR (ch->buffer); CLEAR (ch->dirty); cc_timestamp_reset (&ch->timestamp); cc_timestamp_reset (&ch->timestamp_c0); } cd->curr_ch_num[0] = UNKNOWN_CC_CHANNEL; cd->curr_ch_num[1] = UNKNOWN_CC_CHANNEL; memset (cd->expect_ctrl, -1, sizeof (cd->expect_ctrl)); CLEAR (cd->in_xds); cd->event_pending = NULL; } static void init_cc_decoder (struct cc_decoder * cd) { cc_reset (cd); cd->error_history = 0; cc_timestamp_reset (&cd->timestamp); } /* Some code left over from ntsc-cc, to be replaced. */ const char * const ratings[] = { "(NOT RATED)","TV-Y","TV-Y7","TV-G", "TV-PG","TV-14","TV-MA","(NOT RATED)"}; const char * const modes[]={ "current","future","channel","miscellaneous","public service", "reserved","invalid","invalid","invalid","invalid"}; static void print_xds_info (struct caption_recorder *cr, unsigned int mode, unsigned int type) { const char *infoptr; if (!cr->info[0][mode][type].print) return; infoptr = cr->info[cr->field][mode][type].packet; switch ((mode << 8) + type) { case 0x0101: fprintf (cr->xds_fp, "%sTIMECODE: %d/%02d %d:%02d%s", cr->xds_info_prefix, infoptr[3]&0x0f,infoptr[2]&0x1f, infoptr[1]&0x1f,infoptr[0]&0x3f, cr->xds_info_suffix); case 0x0102: if ((infoptr[1]&0x3f)>5) break; fprintf (cr->xds_fp, "%s LENGTH: %d:%02d:%02d of %d:%02d:00%s", cr->xds_info_prefix, infoptr[3]&0x3f,infoptr[2]&0x3f, infoptr[4]&0x3f,infoptr[1]&0x3f, infoptr[0]&0x3f, cr->xds_info_suffix); break; case 0x0103: fprintf (cr->xds_fp, "%s TITLE: %s%s", cr->xds_info_prefix, infoptr, cr->xds_info_suffix); break; case 0x0105: fprintf (cr->xds_fp, "%s RATING: %s (%d)", cr->xds_info_prefix, ratings[infoptr[0]&0x07],infoptr[0]); if ((infoptr[0]&0x07)>0) { if (infoptr[0]&0x20) fputs (" VIOLENCE", cr->xds_fp); if (infoptr[0]&0x10) fputs (" SEXUAL", cr->xds_fp); if (infoptr[0]&0x08) fputs (" LANGUAGE", cr->xds_fp); } fputs (cr->xds_info_suffix, cr->xds_fp); break; case 0x0501: fprintf (cr->xds_fp, "%s NETWORK: %s%s", cr->xds_info_prefix, infoptr, cr->xds_info_suffix); break; case 0x0502: fprintf (cr->xds_fp, "%s CALL: %s%s", cr->xds_info_prefix, infoptr, cr->xds_info_suffix); break; case 0x0701: fprintf (cr->xds_fp, "%sCUR.TIME: %d:%02d %d/%02d/%04d UTC%s", cr->xds_info_prefix, infoptr[1]&0x1F,infoptr[0]&0x3f, infoptr[3]&0x0f,infoptr[2]&0x1f, (infoptr[5]&0x3f)+1990, cr->xds_info_suffix); break; case 0x0704: //timezone fprintf (cr->xds_fp, "%sTIMEZONE: UTC-%d%s", cr->xds_info_prefix, infoptr[0]&0x1f, cr->xds_info_suffix); break; case 0x0104: //program genere break; case 0x0110: case 0x0111: case 0x0112: case 0x0113: case 0x0114: case 0x0115: case 0x0116: case 0x0117: fprintf (cr->xds_fp, "%s DESC: %s%s", cr->xds_info_prefix, infoptr, cr->xds_info_suffix); break; } fflush (cr->xds_fp); } static int XDSdecode(struct caption_recorder *cr, int data) { static vbi_bool in_xds[2]; int b1, b2, length; if (data == -1) return -1; b1 = data & 0x7F; b2 = (data>>8) & 0x7F; if (0 == b1) { /* Filler, discard. */ return -1; } else if (b1 < 15) // start packet { cr->mode = b1; cr->type = b2; cr->infochecksum = b1 + b2 + 15; if (cr->mode > 8 || cr->type > 20) { // printf("%% Unsupported mode %s(%d) [%d]\n",modes[(mode-1)>>1],mode,type); cr->mode=0; cr->type=0; } cr->infoptr = cr->newinfo[cr->field][cr->mode][cr->type]; in_xds[cr->field] = TRUE; } else if (b1 == 15) // eof (next byte is checksum) { #if 0 //debug if (mode == 0) { length=infoptr - newinfo[cr->field][0][0]; infoptr[1]=0; printf("LEN: %d\n",length); for (y=0;yfield][0][0][y]); printf(" --- %s\n",newinfo[cr->field][0][0]); } #endif if (cr->mode == 0) return 0; if (b2 != 128-((cr->infochecksum%128)&0x7F)) return 0; length = cr->infoptr - cr->newinfo[cr->field][cr->mode][cr->type]; //don't bug the user with repeated data //only parse it if it's different if (cr->info[cr->field][cr->mode][cr->type].length != length || 0 != memcmp (cr->info[cr->field][cr->mode][cr->type].packet, cr->newinfo[cr->field][cr->mode][cr->type], length)) { memcpy (cr->info[cr->field][cr->mode][cr->type].packet, cr->newinfo[cr->field][cr->mode][cr->type], 32); cr->info[cr->field][cr->mode][cr->type].packet[length] = 0; cr->info[cr->field][cr->mode][cr->type].length = length; if (0) fprintf (stderr, "XDS %d %d %d %d %d\n", cr->field, cr->mode, cr->type, length, cr->info[0][cr->mode][cr->type].print); print_xds_info (cr, cr->mode, cr->type); } cr->mode = 0; cr->type = 0; in_xds[cr->field] = FALSE; } else if (b1 <= 31) { /* Caption control code. */ in_xds[cr->field] = FALSE; } else if (in_xds[cr->field]) { if (cr->infoptr >= &cr->newinfo[cr->field][cr->mode][cr->type][32]) { /* Bad packet. */ cr->mode = 0; cr->type = 0; in_xds[cr->field] = 0; } else { cr->infoptr[0] = b1; cr->infoptr++; cr->infoptr[0] = b2; cr->infoptr++; cr->infochecksum += b1 + b2; } } return 0; } #if 0 /* to be replaced */ static int webtv_check(struct caption_recorder *cr, char * buf,int len) { unsigned long sum; unsigned long nwords; unsigned short csum=0; char temp[9]; int nbytes=0; while (buf[0]!='<' && len > 6) //search for the start { buf++; len--; } if (len == 6) //failure to find start return 0; while (nbytes+6 <= len) { //look for end of object checksum, it's enclosed in []'s and there shouldn't be any [' after if (buf[nbytes] == '[' && buf[nbytes+5] == ']' && buf[nbytes+6] != '[') break; else nbytes++; } if (nbytes+6>len) //failure to find end return 0; nwords = nbytes >> 1; sum = 0; //add up all two byte words while (nwords-- > 0) { sum += *buf++ << 8; sum += *buf++; } if (nbytes & 1) { sum += *buf << 8; } csum = (unsigned short)(sum >> 16); while(csum !=0) { sum = csum + (sum & 0xffff); csum = (unsigned short)(sum >> 16); } sprintf(temp,"%04X\n",(int)~sum&0xffff); buf++; if(!strncmp(buf,temp,4)) { buf[5]=0; if (cr->cur_ch[cr->field] >= 0 && cr->cc_fp[cr->cur_ch[cr->field]]) { if (!cr->plain) fprintf(cr->cc_fp[cr->cur_ch[cr->field]], "\33[35mWEBTV: %s\33[0m\n",buf-nbytes-1); else fprintf(cr->cc_fp[cr->cur_ch[cr->field]], "WEBTV: %s\n",buf-nbytes-1); fflush (cr->cc_fp[cr->cur_ch[cr->field]]); } } return 0; } #endif /* 0 */ static void xds_filter_option (struct caption_recorder *cr, const char * optarg) { const char *s; /* Attention: may be called repeatedly. */ if (NULL == optarg || 0 == strcasecmp (optarg, "all")) { unsigned int i, j; for (i = 0; i < N_ELEMENTS (cr->info[0]); ++i) { for (j = 0; j < N_ELEMENTS (cr->info[0][0]); ++j) { cr->info[0][i][j].print = TRUE; } } return; } s = optarg; while (0 != *s) { char buf[16]; unsigned int len; for (;;) { if (0 == *s) return; if (isalnum (*s)) break; ++s; } for (len = 0; len < N_ELEMENTS (buf) - 1; ++len) { if (!isalnum (*s)) break; buf[len] = *s++; } buf[len] = 0; if (0 == strcasecmp (buf, "timecode")) { cr->info[0][1][1].print = TRUE; } else if (0 == strcasecmp (buf, "length")) { cr->info[0][1][2].print = TRUE; } else if (0 == strcasecmp (buf, "title")) { cr->info[0][1][3].print = TRUE; } else if (0 == strcasecmp (buf, "rating")) { cr->info[0][1][5].print = TRUE; } else if (0 == strcasecmp (buf, "network")) { cr->info[0][5][1].print = TRUE; } else if (0 == strcasecmp (buf, "call")) { cr->info[0][5][2].print = TRUE; } else if (0 == strcasecmp (buf, "time")) { cr->info[0][7][1].print = TRUE; } else if (0 == strcasecmp (buf, "timezone")) { cr->info[0][7][4].print = TRUE; } else if (0 == strcasecmp (buf, "desc")) { cr->info[0][1][0x10].print = TRUE; cr->info[0][1][0x11].print = TRUE; cr->info[0][1][0x12].print = TRUE; cr->info[0][1][0x13].print = TRUE; cr->info[0][1][0x14].print = TRUE; cr->info[0][1][0x15].print = TRUE; cr->info[0][1][0x16].print = TRUE; cr->info[0][1][0x17].print = TRUE; } else { fprintf (stderr, "Unknown XDS info '%s'\n", buf); } } } /* CEA 708-C Digital TV Closed Caption decoder. */ static const uint8_t dtvcc_c0_length [4] = { 1, 1, 2, 3 }; static const uint8_t dtvcc_c1_length [32] = { /* 0x80 CW0 ... CW7 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 0x88 CLW */ 2, /* 0x89 DSW */ 2, /* 0x8A HDW */ 2, /* 0x8B TGW */ 2, /* 0x8C DLW */ 2, /* 0x8D DLY */ 2, /* 0x8E DLC */ 1, /* 0x8F RST */ 1, /* 0x90 SPA */ 3, /* 0x91 SPC */ 4, /* 0x92 SPL */ 3, /* CEA 708-C Section 7.1.5.1: 0x93 ... 0x96 are reserved one byte codes. */ 1, 1, 1, 1, /* 0x97 SWA */ 5, /* 0x98 DF0 ... DF7 */ 7, 7, 7, 7, 7, 7, 7, 7 }; static const uint16_t dtvcc_g2 [96] = { /* Note Unicode defines no transparent spaces. */ 0x0020, /* 0x1020 Transparent space */ 0x00A0, /* 0x1021 Non-breaking transparent space */ 0, /* 0x1022 reserved */ 0, 0, 0x2026, /* 0x1025 Horizontal ellipsis */ 0, 0, 0, 0, 0x0160, /* 0x102A S with caron */ 0, 0x0152, /* 0x102C Ligature OE */ 0, 0, 0, /* CEA 708-C Section 7.1.8: "The character (0x30) is a solid block which fills the entire character position with the text foreground color." */ 0x2588, /* 0x1030 Full block */ 0x2018, /* 0x1031 Left single quotation mark */ 0x2019, /* 0x1032 Right single quotation mark */ 0x201C, /* 0x1033 Left double quotation mark */ 0x201D, /* 0x1034 Right double quotation mark */ 0, 0, 0, 0x2122, /* 0x1039 Trademark sign */ 0x0161, /* 0x103A s with caron */ 0, 0x0153, /* 0x103C Ligature oe */ 0x2120, /* 0x103D Service mark */ 0, 0x0178, /* 0x103F Y with diaeresis */ /* Code points 0x1040 ... 0x106F reserved. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x1070 reserved */ 0, 0, 0, 0, 0, 0x215B, /* 0x1076 1/8 */ 0x215C, /* 0x1077 3/8 */ 0x215D, /* 0x1078 5/8 */ 0x215E, /* 0x1079 7/8 */ 0x2502, /* 0x107A Box drawings vertical */ 0x2510, /* 0x107B Box drawings down and left */ 0x2514, /* 0x107C Box drawings up and right */ 0x2500, /* 0x107D Box drawings horizontal */ 0x2518, /* 0x107E Box drawings up and left */ 0x250C /* 0x107F Box drawings down and right */ }; static unsigned int dtvcc_unicode (unsigned int c) { if (unlikely (0 == (c & 0x60))) { /* C0, C1, C2, C3 */ return 0; } else if (likely (c < 0x100)) { /* G0, G1 */ if (unlikely (0x7F == c)) return 0x266A; /* music note */ else return c; } else if (c < 0x1080) { if (unlikely (c < 0x1020)) return 0; else return dtvcc_g2[c - 0x1020]; } else if (0x10A0 == c) { /* We map all G2/G3 characters which are not representable in Unicode to private code U+E900 ... U+E9FF. */ return 0xE9A0; /* caption icon */ } return 0; } static void dump_dtvcc_se (FILE * fp, const uint8_t * buf, unsigned int n_bytes) { uint16_t ucs2_str[1]; unsigned int se_length; unsigned int c; unsigned int i; if (0 == n_bytes) return; c = buf[0]; if (0 != (c & 0x60)) { ucs2_str[0] = dtvcc_unicode (c); fprintf (fp, "G0/G1 0x%02X U+%04X '", c, ucs2_str[0]); vbi_fputs_iconv_ucs2 (fp, locale_codeset, ucs2_str, 1, /* repl_char */ '?'); fputs ("'\n", fp); return; } else if ((int8_t) c < 0) { static const char *mnemo [32] = { "CW0", "CW1", "CW2", "CW3", "CW4", "CW5", "CW6", "CW7", "CLW", "DSW", "HDW", "TGW", "DLW", "DLY", "DLC", "RST", "SPA", "SPC", "SPL", "93", "reserved", "reserved", "reserved", "SWA", "DF0", "DF1", "DF2", "DF3", "DF4", "DF5", "DF6", "DF7" }; static const char *opacity_name [4] = { "Solid", "Flash", "Transl", "Transp" }; static const char *edge_name [8] = { "None", "Raised", "Depressed", "Uniform", "ShadowL", "ShadowR", "INVALID", "INVALID" }; fprintf (fp, "C1 0x%02X %s", c, mnemo[c & 31]); se_length = dtvcc_c1_length[c - 0x80]; if (n_bytes < se_length) { fputs (" incomplete\n", fp); return; } switch (c) { case 0x80 ... 0x87: /* CWx */ case 0x8E: /* DLC */ case 0x8F: /* RST */ case 0x93 ... 0x96: /* reserved */ fputc ('\n', fp); return; case 0x88: /* CLW */ case 0x89: /* DSW */ case 0x8A: /* HDW */ case 0x8B: /* TGW */ case 0x8C: /* DLW */ fputs (" 0b", fp); for (i = 0; i < 8; ++i) { unsigned int bit; bit = !!(buf[1] & (0x80 >> i)); fputc ('0' + bit, fp); } fputc ('\n', fp); return; case 0x8D: /* DLY */ fprintf (fp, " t=%u\n", buf[1]); return; case 0x90: /* SPA */ { static const char *s_name [4] = { "Small", "Std", "Large", "INVALID" }; static const char *fs_name [8] = { "Default", "MonoSerif", "PropSerif", "MonoSans", "PropSans", "Casual", "Cursive", "SmallCaps" }; static const char *tt_name [16] = { "Dialog", "SourceID", "Device", "Dialog2", "Voiceover", "AudTransl", "SubTransl", "VoiceDescr", "Lyrics", "EffectDescr", "ScoreDescr", "Expletive", "INVALID", "INVALID", "INVALID", "NotDisplayable" }; static const char *o_name [4] = { "Sub", "Normal", "Super", "INVALID" }; fprintf (fp, " s=%s fs=%s tt=%s o=%s i=%u " "u=%u et=%s\n", s_name[buf[1] & 3], fs_name[buf[2] & 7], tt_name[(buf[1] >> 4) & 15], o_name[(buf[1] >> 2) & 3], !!(buf[2] & 0x80), !!(buf[2] & 0x40), edge_name[(buf[2] >> 3) & 7]); return; } case 0x91: /* SPC */ { fprintf (fp, " fg=%u%u%u fo=%s bg=%u%u%u bo=%s " "edge=%u%u%u\n", (buf[1] >> 4) & 3, (buf[1] >> 2) & 3, buf[1] & 3, opacity_name[(buf[1] >> 6) & 3], (buf[2] >> 4) & 3, (buf[2] >> 2) & 3, buf[2] & 3, opacity_name[(buf[2] >> 6) & 3], (buf[3] >> 4) & 3, (buf[3] >> 2) & 3, buf[3] & 3); return; } case 0x92: /* SPL */ fprintf (fp, " r=%u c=%u\n", buf[1] & 0x0F, buf[2] & 0x3F); return; case 0x97: /* SWA */ { static const char *j_name [4] = { "L", "R", "C", "F" }; static const char *pd_sd_ed_name [4] = { "LR", "RL", "TB", "BT" }; static const char *de_name [4] = { "Snap", "Fade", "Wipe", "INVALID" }; fprintf (fp, " j=%s pd=%s sd=%s ww=%u de=%s " "ed=%s es=%u fill=%u%u%u fo=%s " "bt=%s border=%u%u%u\n", j_name [buf[3] & 3], pd_sd_ed_name [(buf[3] >> 4) & 3], pd_sd_ed_name [(buf[3] >> 2) & 3], !!(buf[3] & 0x40), de_name [buf[4] & 3], pd_sd_ed_name [(buf[4] >> 2) & 3], (buf[4] >> 4) & 15, (buf[1] >> 4) & 3, (buf[1] >> 2) & 3, buf[1] & 3, opacity_name[(buf[1] >> 6) & 3], edge_name[(buf[2] >> 6) & 3], (buf[2] >> 4) & 3, (buf[2] >> 2) & 3, buf[2] & 3); return; } case 0x98 ... 0x9F: /* DFx */ { static const char *ap_name [16] = { "TL", "TC", "TR", "CL", "C", "CR", "BL", "BC", "BR", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID" }; static const char *ws_name [8] = { "0", "PopUp", "TranspPopUp", "CentPopUp", "RollUp", "TranspRollUp", "CentRollUp", "Ticker" }; static const char *ps_name [8] = { "0", "NTSC", "NTSCMonoSerif", "NTSCPropSerif", "NTSCMonoSans", "NTSCPropSans", "MonoSans", "PropSans" }; fprintf (fp, " p=%u ap=%s rp=%u av=%u ah=%u " "rc=%u cc=%u rl=%u cl=%u v=%u " "ws=%s ps=%s\n", buf[1] & 7, ap_name[(buf[4] >> 4) & 15], !!(buf[2] & 0x80), buf[2] & 0x7F, buf[3], buf[4] & 0x0F, buf[5] & 0x3F, !!(buf[1] & 0x10), !!(buf[1] & 0x08), !!(buf[1] & 0x20), ws_name [(buf[6] >> 3) & 7], ps_name [buf[6] & 7]); return; } } /* switch */ } else { static const char *mnemo [32] = { "NUL", "reserved", "reserved", "ETX", "reserved", "reserved", "reserved", "reserved", "BS", "reserved", "reserved", "reserved", "FF", "CR", "HCR", "reserved", "EXT1", "reserved", "reserved", "reserved", "reserved", "reserved", "reserved", "reserved", "P16", "reserved", "reserved", "reserved", "reserved", "reserved", "reserved", "reserved" }; /* C0 code. */ fprintf (fp, "C0 0x%02X %s", c, mnemo [c]); se_length = dtvcc_c0_length[c >> 3]; if (n_bytes < se_length) { fputs (" incomplete\n", fp); return; } if (0x10 != c) { if (se_length > 1) fprintf (fp, " 0x%02X", buf[1]); if (se_length > 2) fprintf (fp, " 0x%02X", buf[2]); fputc ('\n', fp); return; } } /* Two-byte codes. */ c = buf[1]; if (0 != (c & 0x60)) { ucs2_str[0] = dtvcc_unicode (0x1000 | c); fprintf (fp, "G2/G3 0x10%02X U+%04X '", c, ucs2_str[0]); vbi_fputs_iconv_ucs2 (fp, locale_codeset, ucs2_str, 1, /* repl_char */ '?'); fputs ("'\n", fp); return; } else if ((int8_t) c >= 0) { /* C2 code. */ se_length = (c >> 3) + 2; fprintf (fp, "C2 0x10%02X reserved", c); } else if (c < 0x90) { /* C3 Fixed Length Commands. */ se_length = (c >> 3) - 10; fprintf (fp, "C3 0x10%02X reserved", c); } else { /* C3 Variable Length Commands. */ if (n_bytes < 3) { fprintf (fp, "C3 0x10%02X incomplete\n", c); return; } /* type [2], zero_bit [1], length [5] */ se_length = (buf[2] & 0x1F) + 3; fprintf (fp, "C3 0x10%02X%02X reserved", c, buf[2]); } for (i = 2; i < se_length; ++i) fprintf (fp, " 0x%02X", buf[i]); fputc ('\n', fp); } static void dump_dtvcc_buffer (FILE * fp, struct dtvcc_window * dw) { unsigned int row; unsigned int column; for (row = 0; row < dw->row_count; ++row) { uint16_t ucs2_str[42]; fprintf (fp, "%02u '", row); for (column = 0; column < dw->column_count; ++column) { unsigned int c; c = dw->buffer[row][column]; if (0 == c) { ucs2_str[column] = 0x20; continue; } c = dtvcc_unicode (c); if (0 == c) { ucs2_str[column] = '?'; continue; } ucs2_str[column] = c; } vbi_fputs_iconv_ucs2 (fp, locale_codeset, ucs2_str, dw->column_count, /* repl_char */ '?'); fputs ("'\n", fp); } } static void dtvcc_reset (struct dtvcc_decoder * cd); static void dtvcc_reset_service (struct dtvcc_service * ds); static unsigned int dtvcc_window_id (struct dtvcc_service * ds, struct dtvcc_window * dw) { return dw - ds->window; } static unsigned int dtvcc_service_num (struct dtvcc_decoder * dc, struct dtvcc_service * ds) { return ds - dc->service + 1; } /* Up to eight windows can be visible at once, so which one displays the caption? Let's take a guess. */ static struct dtvcc_window * dtvcc_caption_window (struct dtvcc_service * ds) { struct dtvcc_window *dw; unsigned int max_priority; unsigned int window_id; dw = NULL; max_priority = 8; for (window_id = 0; window_id < 8; ++window_id) { if (0 == (ds->created & (1 << window_id))) continue; if (!ds->window[window_id].visible) continue; if (DIR_BOTTOM_TOP != ds->window[window_id].style.scroll_direction) continue; if (ds->window[window_id].priority < max_priority) { dw = &ds->window[window_id]; max_priority = ds->window[window_id].priority; } } return dw; } static void dtvcc_stream_event (struct dtvcc_decoder * dc, struct dtvcc_service * ds, struct dtvcc_window * dw, unsigned int row) { vbi_char text[48]; vbi_char ac; unsigned int column; if (NULL == dw || dw != dtvcc_caption_window (ds)) return; if (option_debug & DEBUG_DTVCC_STREAM_EVENT) { fprintf (stderr, "%s row=%u streamed=%08x\n", __FUNCTION__, row, dw->streamed); dump_dtvcc_buffer (stderr, dw); } /* Note we only stream windows with scroll direction upwards. */ if (0 != (dw->streamed & (1 << row)) || !cc_timestamp_isset (&dw->timestamp_c0)) return; dw->streamed |= 1 << row; for (column = 0; column < dw->column_count; ++column) { if (0 != dw->buffer[row][column]) break; } /* Row contains only transparent spaces. */ if (column >= dw->column_count) return; /* TO DO. */ CLEAR (ac); ac.foreground = VBI_WHITE; ac.background = VBI_BLACK; ac.opacity = VBI_OPAQUE; for (column = 0; column < dw->column_count; ++column) { unsigned int c; c = dw->buffer[row][column]; if (0 == c) { ac.unicode = 0x20; } else { ac.unicode = dtvcc_unicode (c); if (0 == ac.unicode) { ac.unicode = 0x20; } } text[column] = ac; } { struct program *pr; pr = PARENT (dc, struct program, cr.dtvcc); cr_new_line (&pr->cr, &dw->timestamp_c0, /* channel */ dtvcc_service_num (dc, ds) + 8, /* mode */ CC_MODE_ROLL_UP, /* FIXME */ text, /* length */ dw->column_count); } cc_timestamp_reset (&dw->timestamp_c0); } static vbi_bool dtvcc_put_char (struct dtvcc_decoder * dc, struct dtvcc_service * ds, unsigned int c) { struct dtvcc_window *dw; unsigned int row; unsigned int column; dc = dc; /* unused */ dw = ds->curr_window; if (NULL == dw) { ds->error_line = __LINE__; return FALSE; } row = dw->curr_row; column = dw->curr_column; /* FIXME how should we handle TEXT_TAG_NOT_DISPLAYABLE? */ dw->buffer[row][column] = c; if (option_debug & DEBUG_DTVCC_PUT_CHAR) { fprintf (stderr, "%s row=%u/%u column=%u/%u\n", __FUNCTION__, row, dw->row_count, column, dw->column_count); dump_dtvcc_buffer (stderr, dw); } switch (dw->style.print_direction) { case DIR_LEFT_RIGHT: dw->streamed &= ~(1 << row); if (!cc_timestamp_isset (&dw->timestamp_c0)) dw->timestamp_c0 = ds->timestamp; if (++column >= dw->column_count) return TRUE; break; case DIR_RIGHT_LEFT: dw->streamed &= ~(1 << row); if (!cc_timestamp_isset (&dw->timestamp_c0)) dw->timestamp_c0 = ds->timestamp; if (column-- <= 0) return TRUE; break; case DIR_TOP_BOTTOM: dw->streamed &= ~(1 << column); if (!cc_timestamp_isset (&dw->timestamp_c0)) dw->timestamp_c0 = ds->timestamp; if (++row >= dw->row_count) return TRUE; break; case DIR_BOTTOM_TOP: dw->streamed &= ~(1 << column); if (!cc_timestamp_isset (&dw->timestamp_c0)) dw->timestamp_c0 = ds->timestamp; if (row-- <= 0) return TRUE; break; } dw->curr_row = row; dw->curr_column = column; return TRUE; } static vbi_bool dtvcc_set_pen_location (struct dtvcc_decoder * dc, struct dtvcc_service * ds, const uint8_t * buf) { struct dtvcc_window *dw; unsigned int row; unsigned int column; dw = ds->curr_window; if (NULL == dw) { ds->error_line = __LINE__; return FALSE; } row = buf[1]; /* We check the top four zero bits. */ if (row >= 16) { ds->error_line = __LINE__; return FALSE; } column = buf[2]; /* We also check the top two zero bits. */ if (column >= 42) { ds->error_line = __LINE__; return FALSE; } if (row > dw->row_count) row = dw->row_count - 1; if (column > dw->column_count) column = dw->column_count - 1; if (row != dw->curr_row) { dtvcc_stream_event (dc, ds, dw, dw->curr_row); } /* FIXME there's more. */ dw->curr_row = row; dw->curr_column = column; return TRUE; } static vbi_bool dtvcc_set_pen_color (struct dtvcc_service * ds, const uint8_t * buf) { struct dtvcc_window *dw; unsigned int c; dw = ds->curr_window; if (NULL == dw) { ds->error_line = __LINE__; return FALSE; } c = buf[3]; if (0 != (c & 0xC0)) { ds->error_line = __LINE__; return FALSE; } dw->curr_pen.style.edge_color = c; c = buf[1]; dw->curr_pen.style.fg_opacity = c >> 6; dw->curr_pen.style.fg_color = c & 0x3F; c = buf[2]; dw->curr_pen.style.bg_opacity = c >> 6; dw->curr_pen.style.bg_color = c & 0x3F; return TRUE; } static vbi_bool dtvcc_set_pen_attributes (struct dtvcc_service * ds, const uint8_t * buf) { struct dtvcc_window *dw; unsigned int c; enum pen_size pen_size; enum offset offset; enum edge edge_type; dw = ds->curr_window; if (NULL == dw) { ds->error_line = __LINE__; return FALSE; } c = buf[1]; offset = (c >> 2) & 3; pen_size = c & 3; if ((offset | pen_size) >= 3) { ds->error_line = __LINE__; return FALSE; } c = buf[2]; edge_type = (c >> 3) & 7; if (edge_type >= 6) { ds->error_line = __LINE__; return FALSE; } c = buf[1]; dw->curr_pen.text_tag = c >> 4; dw->curr_pen.style.offset = offset; dw->curr_pen.style.pen_size = pen_size; c = buf[2]; dw->curr_pen.style.italics = c >> 7; dw->curr_pen.style.underline = (c >> 6) & 1; dw->curr_pen.style.edge_type = edge_type; dw->curr_pen.style.font_style = c & 7; return TRUE; } static vbi_bool dtvcc_set_window_attributes (struct dtvcc_service * ds, const uint8_t * buf) { struct dtvcc_window *dw; unsigned int c; enum edge border_type; enum display_effect display_effect; dw = ds->curr_window; if (NULL == dw) return FALSE; c = buf[2]; border_type = ((buf[3] >> 5) & 0x04) | (c >> 6); if (border_type >= 6) return FALSE; c = buf[4]; display_effect = c & 3; if (display_effect >= 3) return FALSE; c = buf[1]; dw->style.fill_opacity = c >> 6; dw->style.fill_color = c & 0x3F; c = buf[2]; dw->style.border_type = border_type; dw->style.border_color = c & 0x3F; c = buf[3]; dw->style.wordwrap = (c >> 6) & 1; dw->style.print_direction = (c >> 4) & 3; dw->style.scroll_direction = (c >> 2) & 3; dw->style.justify = c & 3; c = buf[4]; dw->style.effect_speed = c >> 4; dw->style.effect_direction = (c >> 2) & 3; dw->style.display_effect = display_effect; return TRUE; } static vbi_bool dtvcc_clear_windows (struct dtvcc_decoder * dc, struct dtvcc_service * ds, dtvcc_window_map window_map) { unsigned int i; window_map &= ds->created; for (i = 0; i < 8; ++i) { struct dtvcc_window *dw; if (0 == (window_map & (1 << i))) continue; dw = &ds->window[i]; dtvcc_stream_event (dc, ds, dw, dw->curr_row); memset (dw->buffer, 0, sizeof (dw->buffer)); dw->streamed = 0; /* FIXME CEA 708-C Section 7.1.4 (Form Feed) and 8.10.5.3 confuse me. */ if (0) { dw->curr_column = 0; dw->curr_row = 0; } } return TRUE; } static vbi_bool dtvcc_define_window (struct dtvcc_decoder * dc, struct dtvcc_service * ds, uint8_t * buf) { static const struct dtvcc_window_style window_styles [7] = { { JUSTIFY_LEFT, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP, FALSE, DISPLAY_EFFECT_SNAP, 0, 0, 0, OPACITY_SOLID, EDGE_NONE, 0 }, { JUSTIFY_LEFT, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP, FALSE, DISPLAY_EFFECT_SNAP, 0, 0, 0, OPACITY_TRANSPARENT, EDGE_NONE, 0 }, { JUSTIFY_CENTER, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP, FALSE, DISPLAY_EFFECT_SNAP, 0, 0, 0, OPACITY_SOLID, EDGE_NONE, 0 }, { JUSTIFY_LEFT, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP, TRUE, DISPLAY_EFFECT_SNAP, 0, 0, 0, OPACITY_SOLID, EDGE_NONE, 0 }, { JUSTIFY_LEFT, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP, TRUE, DISPLAY_EFFECT_SNAP, 0, 0, 0, OPACITY_TRANSPARENT, EDGE_NONE, 0 }, { JUSTIFY_CENTER, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP, TRUE, DISPLAY_EFFECT_SNAP, 0, 0, 0, OPACITY_SOLID, EDGE_NONE, 0 }, { JUSTIFY_LEFT, DIR_TOP_BOTTOM, DIR_RIGHT_LEFT, FALSE, DISPLAY_EFFECT_SNAP, 0, 0, 0, OPACITY_SOLID, EDGE_NONE, 0 } }; static const struct dtvcc_pen_style pen_styles [7] = { { PEN_SIZE_STANDARD, 0, OFFSET_NORMAL, FALSE, FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID, 0x00, OPACITY_SOLID, 0 }, { PEN_SIZE_STANDARD, 1, OFFSET_NORMAL, FALSE, FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID, 0x00, OPACITY_SOLID, 0 }, { PEN_SIZE_STANDARD, 2, OFFSET_NORMAL, FALSE, FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID, 0x00, OPACITY_SOLID, 0 }, { PEN_SIZE_STANDARD, 3, OFFSET_NORMAL, FALSE, FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID, 0x00, OPACITY_SOLID, 0 }, { PEN_SIZE_STANDARD, 4, OFFSET_NORMAL, FALSE, FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID, 0x00, OPACITY_SOLID, 0 }, { PEN_SIZE_STANDARD, 3, OFFSET_NORMAL, FALSE, FALSE, EDGE_UNIFORM, 0x3F, OPACITY_SOLID, 0, OPACITY_TRANSPARENT, 0x00 }, { PEN_SIZE_STANDARD, 4, OFFSET_NORMAL, FALSE, FALSE, EDGE_UNIFORM, 0x3F, OPACITY_SOLID, 0, OPACITY_TRANSPARENT, 0x00 } }; struct dtvcc_window *dw; dtvcc_window_map window_map; vbi_bool anchor_relative; unsigned int anchor_vertical; unsigned int anchor_horizontal; unsigned int anchor_point; unsigned int column_count_m1; unsigned int window_id; unsigned int window_style_id; unsigned int pen_style_id; unsigned int c; if (0 != ((buf[1] | buf[6]) & 0xC0)) { ds->error_line = __LINE__; return FALSE; } c = buf[2]; anchor_relative = (c >> 7) & 1; anchor_vertical = c & 0x7F; anchor_horizontal = buf[3]; if (0 == anchor_relative) { if (unlikely (anchor_vertical >= 75 || anchor_horizontal >= 210)) { ds->error_line = __LINE__; return FALSE; } } else { if (unlikely (anchor_vertical >= 100 || anchor_horizontal >= 100)) { ds->error_line = __LINE__; return FALSE; } } c = buf[4]; anchor_point = c >> 4; if (unlikely (anchor_point >= 9)) { ds->error_line = __LINE__; return FALSE; } column_count_m1 = buf[5]; /* We also check the top two zero bits. */ if (unlikely (column_count_m1 >= 41)) { ds->error_line = __LINE__; return FALSE; } window_id = buf[0] & 7; dw = &ds->window[window_id]; window_map = 1 << window_id; ds->curr_window = dw; c = buf[1]; dw->visible = (c >> 5) & 1; dw->row_lock = (c >> 4) & 1; dw->column_lock = (c >> 4) & 1; dw->priority = c & 7; dw->anchor_relative = anchor_relative; dw->anchor_vertical = anchor_vertical; dw->anchor_horizontal = anchor_horizontal; dw->anchor_point = anchor_point; c = buf[4]; dw->row_count = (c & 15) + 1; dw->column_count = column_count_m1 + 1; c = buf[6]; window_style_id = (c >> 3) & 7; pen_style_id = c & 7; if (window_style_id > 0) { dw->style = window_styles[window_style_id]; } else if (0 == (ds->created & window_map)) { dw->style = window_styles[1]; } if (pen_style_id > 0) { dw->curr_pen.style = pen_styles[pen_style_id]; } else if (0 == (ds->created & window_map)) { dw->curr_pen.style = pen_styles[1]; } if (0 != (ds->created & window_map)) return TRUE; /* Has to be something, no? */ dw->curr_pen.text_tag = TEXT_TAG_NOT_DISPLAYABLE; dw->curr_column = 0; dw->curr_row = 0; dw->streamed = 0; cc_timestamp_reset (&dw->timestamp_c0); ds->created |= window_map; return dtvcc_clear_windows (dc, ds, window_map); } static vbi_bool dtvcc_display_windows (struct dtvcc_decoder * dc, struct dtvcc_service * ds, unsigned int c, dtvcc_window_map window_map) { unsigned int i; window_map &= ds->created; for (i = 0; i < 8; ++i) { struct dtvcc_window *dw; vbi_bool was_visible; if (0 == (window_map & (1 << i))) continue; dw = &ds->window[i]; was_visible = dw->visible; switch (c) { case 0x89: /* DSW DisplayWindows */ dw->visible = TRUE; break; case 0x8A: /* HDW HideWindows */ dw->visible = FALSE; break; case 0x8B: /* TGW ToggleWindows */ dw->visible = was_visible ^ TRUE; break; } if (!was_visible) { unsigned int row; dw->timestamp_c0 = ds->timestamp; for (row = 0; row < dw->row_count; ++row) { dtvcc_stream_event (dc, ds, dw, row); } } } return TRUE; } static vbi_bool dtvcc_carriage_return (struct dtvcc_decoder * dc, struct dtvcc_service * ds) { struct dtvcc_window *dw; unsigned int row; unsigned int column; dw = ds->curr_window; if (NULL == dw) { ds->error_line = __LINE__; return FALSE; } dtvcc_stream_event (dc, ds, dw, dw->curr_row); row = dw->curr_row; column = dw->curr_column; switch (dw->style.scroll_direction) { case DIR_LEFT_RIGHT: dw->curr_row = 0; if (column > 0) { dw->curr_column = column - 1; break; } dw->streamed = (dw->streamed << 1) & ~(1 << dw->column_count); for (row = 0; row < dw->row_count; ++row) { for (column = dw->column_count - 1; column > 0; --column) { dw->buffer[row][column] = dw->buffer[row][column - 1]; } dw->buffer[row][column] = 0; } break; case DIR_RIGHT_LEFT: dw->curr_row = 0; if (column + 1 < dw->row_count) { dw->curr_column = column + 1; break; } dw->streamed >>= 1; for (row = 0; row < dw->row_count; ++row) { for (column = 0; column < dw->column_count - 1; ++column) { dw->buffer[row][column] = dw->buffer[row][column + 1]; } dw->buffer[row][column] = 0; } break; case DIR_TOP_BOTTOM: dw->curr_column = 0; if (row > 0) { dw->curr_row = row - 1; break; } dw->streamed = (dw->streamed << 1) & ~(1 << dw->row_count); memmove (&dw->buffer[1], &dw->buffer[0], sizeof (dw->buffer[0]) * (dw->row_count - 1)); memset (&dw->buffer[0], 0, sizeof (dw->buffer[0])); break; case DIR_BOTTOM_TOP: dw->curr_column = 0; if (row + 1 < dw->row_count) { dw->curr_row = row + 1; break; } dw->streamed >>= 1; memmove (&dw->buffer[0], &dw->buffer[1], sizeof (dw->buffer[0]) * (dw->row_count - 1)); memset (&dw->buffer[row], 0, sizeof (dw->buffer[0])); break; } return TRUE; } static vbi_bool dtvcc_form_feed (struct dtvcc_decoder * dc, struct dtvcc_service * ds) { struct dtvcc_window *dw; dtvcc_window_map window_map; dw = ds->curr_window; if (NULL == dw) { ds->error_line = __LINE__; return FALSE; } window_map = 1 << dtvcc_window_id (ds, dw); if (!dtvcc_clear_windows (dc, ds, window_map)) return FALSE; dw->curr_row = 0; dw->curr_column = 0; return TRUE; } static vbi_bool dtvcc_backspace (struct dtvcc_decoder * dc, struct dtvcc_service * ds) { struct dtvcc_window *dw; unsigned int row; unsigned int column; unsigned int mask; dc = dc; /* unused */ dw = ds->curr_window; if (NULL == dw) { ds->error_line = __LINE__; return FALSE; } row = dw->curr_row; column = dw->curr_column; switch (dw->style.print_direction) { case DIR_LEFT_RIGHT: mask = 1 << row; if (column-- <= 0) return TRUE; break; case DIR_RIGHT_LEFT: mask = 1 << row; if (++column >= dw->column_count) return TRUE; break; case DIR_TOP_BOTTOM: mask = 1 << column; if (row-- <= 0) return TRUE; break; case DIR_BOTTOM_TOP: mask = 1 << column; if (++row >= dw->row_count) return TRUE; break; } if (0 != dw->buffer[row][column]) { dw->streamed &= ~mask; dw->buffer[row][column] = 0; } dw->curr_row = row; dw->curr_column = column; return TRUE; } static vbi_bool dtvcc_hor_carriage_return (struct dtvcc_decoder * dc, struct dtvcc_service * ds) { struct dtvcc_window *dw; unsigned int row; unsigned int column; unsigned int mask; dc = dc; /* unused */ dw = ds->curr_window; if (NULL == dw) { ds->error_line = __LINE__; return FALSE; } row = dw->curr_row; column = dw->curr_column; switch (dw->style.print_direction) { case DIR_LEFT_RIGHT: case DIR_RIGHT_LEFT: mask = 1 << row; memset (&dw->buffer[row][0], 0, sizeof (dw->buffer[0])); if (DIR_LEFT_RIGHT == dw->style.print_direction) dw->curr_column = 0; else dw->curr_column = dw->column_count - 1; break; case DIR_TOP_BOTTOM: case DIR_BOTTOM_TOP: mask = 1 << column; for (row = 0; row < dw->column_count; ++row) dw->buffer[row][column] = 0; if (DIR_TOP_BOTTOM == dw->style.print_direction) dw->curr_row = 0; else dw->curr_row = dw->row_count - 1; break; } dw->streamed &= ~mask; return TRUE; } static vbi_bool dtvcc_delete_windows (struct dtvcc_decoder * dc, struct dtvcc_service * ds, dtvcc_window_map window_map) { struct dtvcc_window *dw; dw = ds->curr_window; if (NULL != dw) { unsigned int window_id; window_id = dtvcc_window_id (ds, dw); if (0 != (window_map & (1 << window_id))) { dtvcc_stream_event (dc, ds, dw, dw->curr_row); ds->curr_window = NULL; } } ds->created &= ~window_map; return TRUE; } static vbi_bool dtvcc_command (struct dtvcc_decoder * dc, struct dtvcc_service * ds, unsigned int * se_length, uint8_t * buf, unsigned int n_bytes) { unsigned int c; unsigned int window_id; c = buf[0]; if ((int8_t) c < 0) { *se_length = dtvcc_c1_length[c - 0x80]; } else { *se_length = dtvcc_c0_length[c >> 3]; } if (*se_length > n_bytes) { ds->error_line = __LINE__; return FALSE; } switch (c) { case 0x08: /* BS Backspace */ return dtvcc_backspace (dc, ds); case 0x0C: /* FF Form Feed */ return dtvcc_form_feed (dc, ds); case 0x0D: /* CR Carriage Return */ return dtvcc_carriage_return (dc, ds); case 0x0E: /* HCR Horizontal Carriage Return */ return dtvcc_hor_carriage_return (dc, ds); case 0x80 ... 0x87: /* CWx SetCurrentWindow */ window_id = c & 7; if (0 == (ds->created & (1 << window_id))) { ds->error_line = __LINE__; return FALSE; } ds->curr_window = &ds->window[window_id]; return TRUE; case 0x88: /* CLW ClearWindows */ return dtvcc_clear_windows (dc, ds, buf[1]); case 0x89: /* DSW DisplayWindows */ return dtvcc_display_windows (dc, ds, c, buf[1]); case 0x8A: /* HDW HideWindows */ return dtvcc_display_windows (dc, ds, c, buf[1]); case 0x8B: /* TGW ToggleWindows */ return dtvcc_display_windows (dc, ds, c, buf[1]); case 0x8C: /* DLW DeleteWindows */ return dtvcc_delete_windows (dc, ds, buf[1]); case 0x8F: /* RST Reset */ dtvcc_reset_service (ds); return TRUE; case 0x90: /* SPA SetPenAttributes */ return dtvcc_set_pen_attributes (ds, buf); case 0x91: /* SPC SetPenColor */ return dtvcc_set_pen_color (ds, buf); case 0x92: /* SPL SetPenLocation */ return dtvcc_set_pen_location (dc, ds, buf); case 0x97: /* SWA SetWindowAttributes */ return dtvcc_set_window_attributes (ds, buf); case 0x98 ... 0x9F: /* DFx DefineWindow */ return dtvcc_define_window (dc, ds, buf); default: return TRUE; } } static vbi_bool dtvcc_decode_se (struct dtvcc_decoder * dc, struct dtvcc_service * ds, unsigned int * se_length, uint8_t * buf, unsigned int n_bytes) { unsigned int c; c = buf[0]; if (likely (0 != (c & 0x60))) { /* G0/G1 character. */ *se_length = 1; return dtvcc_put_char (dc, ds, c); } if (0x10 != c) { /* C0/C1 control code. */ return dtvcc_command (dc, ds, se_length, buf, n_bytes); } if (unlikely (n_bytes < 2)) { ds->error_line = __LINE__; return FALSE; } c = buf[1]; if (likely (0 != (c & 0x60))) { /* G2/G3 character. */ *se_length = 2; return dtvcc_put_char (dc, ds, 0x1000 | c); } /* CEA 708-C defines no C2 or C3 commands. */ if ((int8_t) c >= 0) { /* C2 code. */ *se_length = (c >> 3) + 2; } else if (c < 0x90) { /* C3 Fixed Length Commands. */ *se_length = (c >> 3) - 10; } else { /* C3 Variable Length Commands. */ if (unlikely (n_bytes < 3)) { ds->error_line = __LINE__; return FALSE; } /* type [2], zero_bit [1], length [5] */ *se_length = (buf[2] & 0x1F) + 3; } if (unlikely (n_bytes < *se_length)) { ds->error_line = __LINE__; return FALSE; } return TRUE; } static vbi_bool dtvcc_decode_syntactic_elements (struct dtvcc_decoder * dc, struct dtvcc_service * ds, uint8_t * buf, unsigned int n_bytes) { ds->timestamp = dc->timestamp; while (n_bytes > 0) { unsigned int se_length; if (option_debug & DEBUG_DTVCC_SE) { fprintf (stderr, "S%u ", dtvcc_service_num (dc, ds)); dump_dtvcc_se (stderr, buf, n_bytes); } if (0x8D /* DLY */ == *buf || 0x8E /* DLC */ == *buf) { /* FIXME ignored for now. */ ++buf; --n_bytes; continue; } if (!dtvcc_decode_se (dc, ds, &se_length, buf, n_bytes)) { return FALSE; } buf += se_length; n_bytes -= se_length; } return TRUE; } static void dtvcc_decode_packet (struct dtvcc_decoder * dc, const struct timeval * tv, int64_t pts) { unsigned int packet_size_code; unsigned int packet_size; unsigned int i; dc->timestamp.sys = *tv; dc->timestamp.pts = pts; /* Packet Layer. */ /* sequence_number [2], packet_size_code [6], packet_data [n * 8] */ if (dc->next_sequence_number >= 0 && 0 != ((dc->packet[0] ^ dc->next_sequence_number) & 0xC0)) { struct program *pr; pr = PARENT (dc, struct program, cr.dtvcc); log (4, "Station %u DTVCC packet lost.\n", station_num (pr)); dtvcc_reset (dc); return; } dc->next_sequence_number = dc->packet[0] + 0x40; packet_size_code = dc->packet[0] & 0x3F; packet_size = 128; if (packet_size_code > 0) packet_size = packet_size_code * 2; if (option_debug & DEBUG_DTVCC_PACKET) { unsigned int sequence_number; sequence_number = (dc->packet[0] >> 6) & 3; fprintf (stderr, "DTVCC packet packet_size=%u " "(transmitted %u), sequence_number %u\n", packet_size, dc->packet_size, sequence_number); dump (stderr, dc->packet, dc->packet_size); } /* CEA 708-C Section 5: Apparently packet_size need not be equal to the actually transmitted amount of data. */ if (packet_size > dc->packet_size) { struct program *pr; pr = PARENT (dc, struct program, cr.dtvcc); log (4, "Station %u DTVCC packet incomplete (%u/%u).\n", station_num (pr), dc->packet_size, packet_size); dtvcc_reset (dc); return; } /* Service Layer. */ /* CEA 708-C Section 6.2.5, 6.3: Service Blocks and syntactic elements must not cross Caption Channel Packet boundaries. */ for (i = 1; i < packet_size;) { unsigned int service_number; unsigned int block_size; unsigned int header_size; unsigned int c; header_size = 1; /* service_number [3], block_size [5], (null_fill [2], extended_service_number [6]), (Block_data [n * 8]) */ c = dc->packet[i]; service_number = (c & 0xE0) >> 5; /* CEA 708-C Section 6.3: Ignore block_size if service_number is zero. */ if (0 == service_number) { /* NULL Service Block Header, no more data in this Caption Channel Packet. */ break; } /* CEA 708-C Section 6.2.1: Apparently block_size zero is valid, although properly it should only occur in NULL Service Block Headers. */ block_size = c & 0x1F; if (7 == service_number) { if (i + 1 > packet_size) goto service_block_incomplete; header_size = 2; c = dc->packet[i + 1]; /* We also check the null_fill bits. */ if (c < 7 || c > 63) goto invalid_service_block; service_number = c; } if (i + header_size + block_size > packet_size) goto service_block_incomplete; if (service_number <= 2) { struct dtvcc_service *ds; unsigned int in; ds = &dc->service[service_number - 1]; in = ds->service_data_in; memcpy (ds->service_data + in, dc->packet + i + header_size, block_size); ds->service_data_in = in + block_size; } i += header_size + block_size; } for (i = 0; i < 2; ++i) { struct dtvcc_service *ds; struct program *pr; vbi_bool success; ds = &dc->service[i]; if (0 == ds->service_data_in) continue; success = dtvcc_decode_syntactic_elements (dc, ds, ds->service_data, ds->service_data_in); ds->service_data_in = 0; if (success) continue; pr = PARENT (dc, struct program, cr.dtvcc); log (4, "Station %u DTVCC invalid " "syntactic element (%u).\n", station_num (pr), ds->error_line); if (option_debug & DEBUG_DTVCC_PACKET) { fprintf (stderr, "Packet (%d/%d):\n", packet_size, dc->packet_size); dump (stderr, dc->packet, packet_size); fprintf (stderr, "Service Data:\n"); dump (stderr, ds->service_data, ds->service_data_in); } dtvcc_reset_service (ds); } return; invalid_service_block: { struct program *pr; pr = PARENT (dc, struct program, cr.dtvcc); log (4, "Station %u DTVCC invalid " "service block (%u).\n", station_num (pr), i); if (option_debug & DEBUG_DTVCC_PACKET) { fprintf (stderr, "Packet (%d/%d):\n", packet_size, dc->packet_size); dump (stderr, dc->packet, packet_size); } dtvcc_reset (dc); return; } service_block_incomplete: { struct program *pr; pr = PARENT (dc, struct program, cr.dtvcc); log (4, "Station %u DTVCC incomplete " "service block (%u).\n", station_num (pr), i); if (option_debug & DEBUG_DTVCC_PACKET) { fprintf (stderr, "Packet (%d/%d):\n", packet_size, dc->packet_size); dump (stderr, dc->packet, packet_size); } dtvcc_reset (dc); return; } } static void dtvcc_reset_service (struct dtvcc_service * ds) { ds->curr_window = NULL; ds->created = 0; cc_timestamp_reset (&ds->timestamp); } static void dtvcc_reset (struct dtvcc_decoder * dc) { dtvcc_reset_service (&dc->service[0]); dtvcc_reset_service (&dc->service[1]); dc->packet_size = 0; dc->next_sequence_number = -1; } static void init_dtvcc_decoder (struct dtvcc_decoder * dc) { dtvcc_reset (dc); cc_timestamp_reset (&dc->timestamp); } /* ATSC A/53 Part 4:2007 Closed Caption Data decoder */ static void dump_cc_data_pair (FILE * fp, unsigned int index, const uint8_t buf[3]) { unsigned int one_bit; unsigned int reserved; unsigned int cc_valid; enum cc_type cc_type; unsigned int cc_data_1; unsigned int cc_data_2; /* Was marker_bits: "11111". */ one_bit = (buf[0] >> 7) & 1; reserved = (buf[0] >> 3) & 15; cc_valid = (buf[0] >> 2) & 1; cc_type = (enum cc_type)(buf[0] & 3); cc_data_1 = buf[1]; cc_data_2 = buf[2]; fprintf (fp, " %2u '1F'=%u%X%s valid=%u type=%s " "%02x %02x '%c%c'\n", index, one_bit, reserved, (1 != one_bit || 0xF != reserved) ? "*" : "", cc_valid, cc_type_name (cc_type), cc_data_1, cc_data_2, printable (cc_data_1), printable (cc_data_2)); } static void dump_cc_data (FILE * fp, const uint8_t * buf, unsigned int n_bytes, int64_t pts, int64_t last_pts) { unsigned int reserved1; unsigned int process_cc_data_flag; unsigned int zero_bit; unsigned int cc_count; unsigned int reserved2; unsigned int same; unsigned int marker_bits; unsigned int i; /* Was process_em_data_flag: "This flag is set to indicate whether it is necessary to process the em_data. If it is set to 1, the em_data has to be parsed and its meaning has to be processed. When it is set to 0, the em_data can be discarded." */ reserved1 = (buf[9] >> 7) & 1; process_cc_data_flag = (buf[9] >> 6) & 1; /* Was: additional_cc_data. */ zero_bit = (buf[9] >> 5) & 1; cc_count = buf[9] & 0x1F; /* Was em_data: "Eight bits for representing emergency message." */ reserved2 = buf[10]; fprintf (fp, "cc_data pts=%" PRId64 " (%+" PRId64 ") " "'1'=%u%s process_cc_data_flag=%u " "'0'=%u%s cc_count=%u 'FF'=0x%02X%s:\n", pts, pts - last_pts, reserved1, (1 != reserved1) ? "*" : "", process_cc_data_flag, zero_bit, (0 != zero_bit) ? "*" : "", cc_count, reserved2, (0xFF != reserved2) ? "*" : ""); same = 0; for (i = 0; i <= cc_count; ++i) { if (i > 0 && i < cc_count && 0 == memcmp (&buf[11 + i * 3], &buf[ 8 + i * 3], 3)) { ++same; } else { if (same > 1) { fprintf (fp, " %2u-%u as above\n", i - same, i - 1); } else if (same > 0) { dump_cc_data_pair (fp, i - 1, &buf[8 + i * 3]); } if (i < cc_count) dump_cc_data_pair (fp, i, &buf[11 + i * 3]); same = 0; } } marker_bits = buf[11 + cc_count * 3]; fprintf (fp, " marker_bits=0x%02X%s\n", marker_bits, (0xFF != marker_bits) ? "*" : ""); if (n_bytes > 12 + cc_count * 3) { fprintf (fp, " extraneous"); for (i = 12 + cc_count * 3; i < n_bytes; ++i) fprintf (stderr, " %02x", buf[i]); fputc ('\n', stderr); } } /* Note pts may be < 0 if no PTS was received. */ static void decode_cc_data (struct program * pr, int64_t pts, const uint8_t * buf, unsigned int n_bytes) { unsigned int process_cc_data_flag; unsigned int cc_count; unsigned int i; vbi_bool dtvcc; if (NULL == buf || n_bytes < 10) return; if (pts >= 0) pr->cc_pts = pts; if (pr->first_pts < 0) pr->first_pts = pts; if (option_debug & DEBUG_CC_DATA) { static int64_t last_pts = 0; /* XXX */ dump_cc_data (stderr, buf, n_bytes, pts, last_pts); last_pts = pts; } process_cc_data_flag = buf[9] & 0x40; if (!process_cc_data_flag) return; cc_count = buf[9] & 0x1F; dtvcc = FALSE; if (NULL != pr->cr.ccd.cc_data_tap_fp) { static uint8_t output_buffer [8 + 11 + 31 * 3]; unsigned int in; unsigned int out; unsigned int n_bytes; for (in = 0; in < 8; ++in) output_buffer[in] = pts >> (56 - in * 8); n_bytes = 11 + cc_count * 3; memcpy (output_buffer + in, buf, n_bytes); in += n_bytes; out = sizeof (output_buffer); memset (output_buffer + in, 0, out - in); if (out != fwrite (output_buffer, 1, out, pr->cr.ccd.cc_data_tap_fp)) { errno_exit ("cc_data tap write error"); } } for (i = 0; i < cc_count; ++i) { unsigned int b0; unsigned int cc_valid; enum cc_type cc_type; unsigned int cc_data_1; unsigned int cc_data_2; unsigned int j; b0 = buf[11 + i * 3]; cc_valid = b0 & 4; cc_type = (enum cc_type)(b0 & 3); cc_data_1 = buf[12 + i * 3]; cc_data_2 = buf[13 + i * 3]; switch (cc_type) { case NTSC_F1: case NTSC_F2: /* Note CEA 708-C Table 4: Only one NTSC pair will be present in field picture user_data or in progressive video pictures, and up to three can occur if the frame rate < 30 Hz or repeat_first_field = 1. */ if (!cc_valid || i >= 3 || dtvcc) { /* Illegal, invalid or filler. */ break; } if (option_debug & (DEBUG_CC_F1 | DEBUG_CC_F2)) { if ((NTSC_F1 == cc_type && 0 != (option_debug & DEBUG_CC_F1)) || (NTSC_F2 == cc_type && 0 != (option_debug & DEBUG_CC_F2))) dump_cc (stderr, i, cc_count, cc_valid, cc_type, cc_data_1, cc_data_2); } cc_feed (&pr->cr.cc, &buf[12 + i * 3], /* line */ (NTSC_F1 == cc_type) ? 21 : 284, &pr->now, pts); /* XXX replace me. */ if (NTSC_F1 == cc_type) { pr->cr.field = 0; if (pr->cr.usexds) /* fields swapped? */ XDSdecode(&pr->cr, cc_data_1 + cc_data_2 * 256); } else { pr->cr.field = 1; if (pr->cr.usexds) XDSdecode(&pr->cr, cc_data_1 + cc_data_2 * 256); } break; case DTVCC_DATA: j = pr->cr.dtvcc.packet_size; if (j <= 0) { /* Missed packet start. */ break; } else if (!cc_valid) { /* End of DTVCC packet. */ dtvcc_decode_packet (&pr->cr.dtvcc, &pr->now, pr->cc_pts); pr->cr.dtvcc.packet_size = 0; } else if (j >= 128) { /* Packet buffer overflow. */ dtvcc_reset (&pr->cr.dtvcc); pr->cr.dtvcc.packet_size = 0; } else { pr->cr.dtvcc.packet[j] = cc_data_1; pr->cr.dtvcc.packet[j + 1] = cc_data_2; pr->cr.dtvcc.packet_size = j + 2; } break; case DTVCC_START: dtvcc = TRUE; j = pr->cr.dtvcc.packet_size; if (j > 0) { /* End of DTVCC packet. */ dtvcc_decode_packet (&pr->cr.dtvcc, &pr->now, pr->cc_pts); } if (!cc_valid) { /* No new data. */ pr->cr.dtvcc.packet_size = 0; } else { pr->cr.dtvcc.packet[0] = cc_data_1; pr->cr.dtvcc.packet[1] = cc_data_2; pr->cr.dtvcc.packet_size = 2; } break; } } } static void init_cc_data_decoder (struct cc_data_decoder *cd) { CLEAR (*cd); } static void cc_data_test_loop (struct program * pr, const char * test_file_name) { FILE *test_fp; test_fp = open_test_file (test_file_name); for (;;) { static uint8_t buffer[8 + 11 + 31 * 3]; size_t todo; size_t actual; todo = sizeof (buffer); actual = fread (buffer, 1, todo, test_fp); if (likely (actual == todo)) { int64_t pts; unsigned int i; pts = 0; for (i = 0; i < 8; ++i) { pts |= buffer[i] << (56 - i * 8); } decode_cc_data (pr, pts, &buffer[8], actual); continue; } if (ferror (test_fp)) { errno_exit ("CC data file read error"); } else { log (1, "End of CC data file.\n"); fclose (test_fp); return; } } } /* DVB capture functions and transport stream decoding. */ static void init_buffer (struct buffer * b, unsigned int capacity) { b->capacity = capacity; b->base = xmalloc (capacity); b->in = 0; b->out = 0; } static void dump_pes_buffer (FILE * fp, const struct pes_buffer *b, const char * name) _vbi_unused; static void dump_pes_buffer (FILE * fp, const struct pes_buffer *b, const char * name) { unsigned int i; fprintf (fp, "%s PES buffer:\n", name); for (i = 0; i < b->n_packets; ++i) { fprintf (fp, "%2u: offs=%5u size=%u/%u " "dts=%" PRId64 " (%+" PRId64 ") " "duration=%" PRId64 " splice=%d lost=%d\n", i, b->packet[i].offset, b->packet[i].payload, b->packet[i].size, b->packet[i].dts, (i > 0) ? (b->packet[i].dts - b->packet[i - 1].dts) : 0, b->packet[i].duration, b->packet[i].splice, b->packet[i].data_lost); } } static vbi_bool decode_time_stamp (int64_t * ts, const uint8_t * buf, unsigned int marker) { /* ISO 13818-1 Section 2.4.3.6 */ if (0 != ((marker ^ buf[0]) & 0xF1)) return FALSE; if (NULL != ts) { unsigned int a, b, c; /* marker [4], TS [32..30], marker_bit, TS [29..15], marker_bit, TS [14..0], marker_bit */ a = (buf[0] >> 1) & 0x7; b = (buf[1] * 256 + buf[2]) >> 1; c = (buf[3] * 256 + buf[4]) >> 1; *ts = ((int64_t) a << 30) + (b << 15) + (c << 0); } return TRUE; } static void dump_pes_packet_header (FILE * fp, const uint8_t * buf) { unsigned int packet_start_code_prefix; unsigned int stream_id; unsigned int PES_packet_length; unsigned int PES_scrambling_control; unsigned int PES_priority; unsigned int data_alignment_indicator; unsigned int copyright; unsigned int original_or_copy; unsigned int PTS_DTS_flags; unsigned int ESCR_flag; unsigned int ES_rate_flag; unsigned int DSM_trick_mode_flag; unsigned int additional_copy_info_flag; unsigned int PES_CRC_flag; unsigned int PES_extension_flag; unsigned int PES_header_data_length; int64_t ts; /* ISO 13818-1 Section 2.4.3.6. */ packet_start_code_prefix = buf[0] * 65536 + buf[1] * 256 + buf[2]; stream_id = buf[3]; PES_packet_length = buf[4] * 256 + buf[5]; /* '10' */ PES_scrambling_control = (buf[6] & 0x30) >> 4; PES_priority = buf[6] & 0x08; data_alignment_indicator = buf[6] & 0x04; copyright = buf[6] & 0x02; original_or_copy = buf[6] & 0x01; PTS_DTS_flags = (buf[7] & 0xC0) >> 6; ESCR_flag = buf[7] & 0x20; ES_rate_flag = buf[7] & 0x10; DSM_trick_mode_flag = buf[7] & 0x08; additional_copy_info_flag = buf[7] & 0x04; PES_CRC_flag = buf[7] & 0x02; PES_extension_flag = buf[7] & 0x01; PES_header_data_length = buf[8]; fprintf (fp, "PES %06X%02X %5u " "%u%u%u%c%c%c%c%u%c%c%c%c%c%c %u", packet_start_code_prefix, stream_id, PES_packet_length, !!(buf[6] & 0x80), !!(buf[6] & 0x40), PES_scrambling_control, PES_priority ? 'P' : '-', data_alignment_indicator ? 'A' : '-', copyright ? 'C' : '-', original_or_copy ? 'O' : 'C', PTS_DTS_flags, ESCR_flag ? 'E' : '-', ES_rate_flag ? 'E' : '-', DSM_trick_mode_flag ? 'D' : '-', additional_copy_info_flag ? 'A' : '-', PES_CRC_flag ? 'C' : '-', PES_extension_flag ? 'X' : '-', PES_header_data_length); switch (PTS_DTS_flags) { case 0: /* no timestamps */ case 1: /* forbidden */ fputc ('\n', fp); break; case 2: /* PTS only */ if (decode_time_stamp (&ts, &buf[9], 0x21)) fprintf (fp, " %" PRId64 "\n", ts); else fputs (" PTS?\n", fp); break; case 3: /* PTS and DTS */ if (decode_time_stamp (&ts, &buf[9], 0x31)) fprintf (fp, " %" PRId64, ts); else fputs (" PTS?", fp); if (decode_time_stamp (&ts, &buf[14], 0x11)) fprintf (fp, " %" PRId64 "\n", ts); else fputs (" DTS?\n", fp); break; } } static void close_ts_file (struct video_recorder *vr) { if (NULL != vr->minicut_fp) { if (0 != fclose (vr->minicut_fp)) { struct program *pr; pr = PARENT (vr, struct program, vr); log_errno (1, "TS stream %u close error", (unsigned int)(pr - program_table)); } vr->minicut_fp = NULL; } } static unsigned int mpeg2_crc (const uint8_t * buf, unsigned int n_bytes) { static uint32_t crc_table[256]; unsigned int crc; unsigned int i; /* ISO 13818-1 Annex B. */ if (unlikely (0 == crc_table[255])) { const unsigned int poly = ((1 << 26) | (1 << 23) | (1 << 22) | (1 << 16) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 8) | (1 << 7) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | 1); unsigned int c, j; for (i = 0; i < 256; ++i) { c = i << 24; for (j = 0; j < 8; ++j) { if (c & (1 << 31)) c = (c << 1) ^ poly; else c <<= 1; } crc_table[i] = c; } assert (0 != crc_table[255]); } crc = -1; for (i = 0; i < n_bytes; ++i) crc = crc_table[(buf[i] ^ (crc >> 24)) & 0xFF] ^ (crc << 8); return crc & 0xFFFFFFFFUL; } static const unsigned int pmt_pid = 0x5A5; static void init_pmt (uint8_t buf[188], struct video_recorder *vr, const struct ts_decoder *td) { uint32_t CRC_32; /* sync_byte [8], transport_error_indicator, payload_unit_start_indicator, transport_priority, PID [13], transport_scrambling_control [2], adaptation_field_control [2], continuity_counter [4] */ buf[0] = 0x47; buf[1] = 0x40 | ((pmt_pid & 0x1FFF) >> 8); buf[2] = (uint8_t) pmt_pid; buf[3] = 0x10 | (vr->pmt_cc & 0x0F); ++vr->pmt_cc; /* pointer_field */ buf[4] = 0x00; /* table_id [8] */ buf[5] = 0x02; /* TS_program_map_section */ /* section_syntax_indicator, '0', reserved [2], section_length [12] */ buf[6] = 0xB0; buf[7] = 31 - 8; /* program_number [16] */ buf[8] = 0x00; buf[9] = 0x01; /* reserved [2], version_number [5], current_next_indicator */ buf[10] = 0xC1; /* section_number [8], last_section_number [8] */ buf[11] = 0x00; buf[12] = 0x00; /* reserved [3], PCR_PID [13] */ buf[13] = 0xE0 | (td->pid[0] >> 8); buf[14] = td->pid[0]; /* reserved [4], program_info_length [12] */ buf[15] = 0xF0; buf[16] = 0x00; /* stream_type [8], reserved [3], elementary_PID [13], reserved [4], ES_info_length [12] */ buf[17] = 0x02; /* MPEG-2 video */ buf[18] = 0xE0 | (td->pid[0] >> 8); buf[19] = td->pid[0]; buf[20] = 0xF0; buf[21] = 0x00; buf[22] = 0x81; /* AC3 audio */ buf[23] = 0xE0 | (td->pid[1] >> 8); buf[24] = td->pid[1]; buf[25] = 0xF0; buf[26] = 0x00; CRC_32 = mpeg2_crc (buf + 5, 27 - 5); buf[27] = CRC_32 >> 24; buf[28] = CRC_32 >> 16; buf[29] = CRC_32 >> 8; buf[30] = CRC_32; memset (buf + 31, -1, 188 - 31); } static void init_pat (uint8_t buf[188], struct video_recorder *vr) { uint32_t CRC_32; /* sync_byte [8], transport_error_indicator, payload_unit_start_indicator, transport_priority, PID [13], transport_scrambling_control [2], adaptation_field_control [2], continuity_counter [4] */ buf[0] = 0x47; buf[1] = 0x40; buf[2] = 0x00; buf[3] = 0x10 | (vr->pat_cc & 0x0F); ++vr->pat_cc; /* pointer_field [8] */ buf[4] = 0x00; /* table_id [8] */ buf[5] = 0x00; /* program_association_section */ /* section_syntax_indicator, '0', reserved [2], section_length [12] */ buf[6] = 0xB0; buf[7] = 21 - 8; /* transport_stream_id [16] */ buf[8] = 0x00; buf[9] = 0x01; /* reserved [2], version_number [5], current_next_indicator */ buf[10] = 0xC1; /* section_number [8], last_section_number [8] */ buf[11] = 0x00; buf[12] = 0x00; /* program_number [16] */ buf[13] = 0x00; buf[14] = 0x01; /* reserved [3], program_map_PID [13] */ buf[15] = 0xE0 | ((pmt_pid & 0x1FFF) >> 8); buf[16] = (uint8_t) pmt_pid; CRC_32 = mpeg2_crc (buf + 5, 17 - 5); buf[17] = CRC_32 >> 24; buf[18] = CRC_32 >> 16; buf[19] = CRC_32 >> 8; buf[20] = CRC_32; memset (buf + 21, -1, 188 - 21); } static void video_recorder (struct video_recorder *vr, const uint8_t buf[188]) { struct program *pr = PARENT (vr, struct program, vr); size_t actual; if (NULL == pr->option_minicut_dir_name) return; /* no video recording */ /* The TS packet rate is too high to call time() here. We do that when a picture arrives, some 24 to 60 times/second. */ if (0 == (pr->now.tv_sec | pr->now.tv_usec)) return; /* no picture received yet */ /* Note minicut_end is initially zero. */ if (pr->now.tv_sec >= vr->minicut_end) { char file_name[32]; struct tm tm; time_t t; t = pr->now.tv_sec; if (NULL == gmtime_r (&t, &tm)) { /* Should not happen. */ error_exit ("System time invalid.\n"); } vr->minicut_end = t + (60 - tm.tm_sec); if (option_minicut_test) { tm.tm_sec = 0; } else if (1) { tm.tm_sec = 0; } else { if (0 != tm.tm_sec) return; } close_ts_file (vr); snprintf (file_name, sizeof (file_name), "/%04u%02u%02u%02u%02u%02u", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); vr->minicut_fp = open_minicut_file (pr, &tm, file_name, ".ts"); if (NULL != vr->minicut_fp) { uint8_t buf[2 * 188]; init_pat (buf, vr); init_pmt (buf + 188, vr, &pr->tsd); actual = fwrite (buf, 1, 2 * 188, vr->minicut_fp); if (2 * 188 != actual) { log_errno (1, "TS stream %u write error", station_num (pr)); } } } if (NULL == vr->minicut_fp) return; actual = fwrite (buf, 1, 188, vr->minicut_fp); if (188 != actual) { log_errno (1, "TS stream %u write error", station_num (pr)); } } static void init_video_recorder (struct video_recorder *vr) { vr->pat_cc = 0; vr->pmt_cc = 0; vr->minicut_end = 0; vr->minicut_fp = NULL; } /* Video elementary stream decoder. */ static void vesd_reorder_decode_cc_data (struct video_es_decoder *vd, const uint8_t * buf, unsigned int n_bytes) { struct program *pr = PARENT (vd, struct program, vesd); n_bytes = MIN (n_bytes, (unsigned int) sizeof (vd->reorder_buffer[0])); switch (vd->picture_structure) { case FRAME_PICTURE: if (0 != vd->reorder_pictures) { if (vd->reorder_pictures & 5) { /* Top field or top and bottom field. */ decode_cc_data (pr, vd->reorder_pts[0], vd->reorder_buffer[0], vd->reorder_n_bytes[0]); } if (vd->reorder_pictures & 2) { /* Bottom field. */ decode_cc_data (pr, vd->reorder_pts[1], vd->reorder_buffer[1], vd->reorder_n_bytes[1]); } } memcpy (vd->reorder_buffer[0], buf, n_bytes); vd->reorder_n_bytes[0] = n_bytes; vd->reorder_pts[0] = vd->pts; /* We have a frame. */ vd->reorder_pictures = 4; break; case TOP_FIELD: if (vd->reorder_pictures >= 3) { /* Top field or top and bottom field. */ decode_cc_data (pr, vd->reorder_pts[0], vd->reorder_buffer[0], vd->reorder_n_bytes[0]); vd->reorder_pictures &= 2; } else if (1 == vd->reorder_pictures) { /* Apparently we missed a bottom field. */ } memcpy (vd->reorder_buffer[0], buf, n_bytes); vd->reorder_n_bytes[0] = n_bytes; vd->reorder_pts[0] = vd->pts; /* We have a top field. */ vd->reorder_pictures |= 1; break; case BOTTOM_FIELD: if (vd->reorder_pictures >= 3) { if (vd->reorder_pictures >= 4) { /* Top and bottom field. */ decode_cc_data (pr, vd->reorder_pts[0], vd->reorder_buffer[0], vd->reorder_n_bytes[0]); } else { /* Bottom field. */ decode_cc_data (pr, vd->reorder_pts[1], vd->reorder_buffer[1], vd->reorder_n_bytes[1]); } vd->reorder_pictures &= 1; } else if (2 == vd->reorder_pictures) { /* Apparently we missed a top field. */ } memcpy (vd->reorder_buffer[1], buf, n_bytes); vd->reorder_n_bytes[1] = n_bytes; vd->reorder_pts[1] = vd->pts; /* We have a bottom field. */ vd->reorder_pictures |= 2; break; default: /* invalid */ break; } } static void vesd_user_data (struct video_es_decoder *vd, const uint8_t * buf, unsigned int min_bytes_valid) { unsigned int ATSC_identifier; unsigned int user_data_type_code; unsigned int cc_count; /* ATSC A/53 Part 4:2007 Section 6.2.2 */ if (unlikely (option_debug & DEBUG_VESD_USER_DATA)) { unsigned int i; fprintf (stderr, "VES UD: %s %s ref=%u " "dts=%" PRId64 " pts=%" PRId64, picture_coding_type_name (vd->picture_coding_type), picture_structure_name (vd->picture_structure), vd->picture_temporal_reference, vd->dts, vd->pts); for (i = 0; i < min_bytes_valid; ++i) fprintf (stderr, " %02x", buf[i]); fputc (' ', stderr); for (i = 0; i < min_bytes_valid; ++i) fputc (printable (buf[i]), stderr); fputc ('\n', stderr); } /* NB. the PES packet header is optional and we may receive more than one user_data structure. */ if ((RECEIVED_PICTURE | RECEIVED_PICTURE_EXT) != (vd->received_blocks & (RECEIVED_PICTURE | RECEIVED_PICTURE_EXT))) { /* Either sequence or group user_data, or we missed the picture_header. */ vd->received_blocks &= ~RECEIVED_PES_PACKET; return; } if (NULL == buf) { /* No user_data received on this field or frame. */ if (option_debug & DEBUG_VESD_CC_DATA) { fprintf (stderr, "DTVCC coding=%s structure=%s " "pts=%" PRId64 " no data\n", picture_coding_type_name (vd->picture_coding_type), picture_structure_name (vd->picture_structure), vd->pts); } } else { /* start_code_prefix [24], start_code [8], ATSC_identifier [32], user_data_type_code [8] */ if (min_bytes_valid < 9) return; ATSC_identifier = ((buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]); if (0x47413934 != ATSC_identifier) return; user_data_type_code = buf[8]; if (0x03 != user_data_type_code) return; /* ATSC A/53 Part 4:2007 Section 6.2.1: "No more than one user_data() structure using the same user_data_type_code [...] shall be present following any given picture header." */ if (vd->received_blocks & RECEIVED_MPEG_CC_DATA) { /* Too much data lost. */ return; } vd->received_blocks |= RECEIVED_MPEG_CC_DATA; /* reserved, process_cc_data_flag, zero_bit, cc_count [5], reserved [8] */ if (min_bytes_valid < 11) return; /* one_bit, reserved [4], cc_valid, cc_type [2], cc_data_1 [8], cc_data_2 [8] */ cc_count = buf[9] & 0x1F; /* CEA 708-C Section 4.4 permits padding, so we have to see all cc_data elements. */ if (min_bytes_valid < 11 + cc_count * 3) return; if (option_debug & DEBUG_VESD_CC_DATA) { char text[0x1F * 2 + 1]; unsigned int i; vbi_bool ooo; for (i = 0; i < cc_count; ++i) { text[i * 2 + 0] = printable (buf[12 + i * 3]); text[i * 2 + 1] = printable (buf[13 + i * 3]); } text[cc_count * 2] = 0; ooo = (B_TYPE == vd->picture_coding_type && vd->reorder_pictures < 3); fprintf (stderr, "DTVCC coding=%s structure=%s " "pts=%" PRId64 " cc_count=%u " "n_bytes=%u '%s'%s\n", picture_coding_type_name (vd->picture_coding_type), picture_structure_name (vd->picture_structure), vd->pts, cc_count, min_bytes_valid, text, ooo ? " (out of order)" : ""); } } /* CEA 708-C Section 4.4.1.1 */ switch (vd->picture_coding_type) { case I_TYPE: case P_TYPE: vesd_reorder_decode_cc_data (vd, buf, min_bytes_valid); break; case B_TYPE: /* To prevent a gap in the caption stream we must not decode B pictures until we have buffered both fields of the temporally following I or P picture. */ if (vd->reorder_pictures < 3) { vd->reorder_pictures = 0; break; } /* To do: If a B picture appears to have a higher temporal_reference than the picture it forward references we lost that I or P picture. */ { struct program *pr; pr = PARENT (vd, struct program, vesd); decode_cc_data (pr, vd->pts, buf, min_bytes_valid); } break; default: /* invalid */ break; } } static void vesd_extension (struct video_es_decoder *vd, const uint8_t * buf, unsigned int min_bytes_valid) { enum extension_start_code_identifier extension_start_code_identifier; /* extension_start_code [32], extension_start_code_identifier [4], f_code [4][4], intra_dc_precision [2], picture_structure [2], ... */ if (min_bytes_valid < 7) return; extension_start_code_identifier = (enum extension_start_code_identifier)(buf[4] >> 4); if (PICTURE_CODING_EXTENSION_ID != extension_start_code_identifier) return; if (0 == (vd->received_blocks & RECEIVED_PICTURE)) { /* We missed the picture_header. */ vd->received_blocks = 0; return; } vd->picture_structure = (enum picture_structure)(buf[6] & 3); if (option_debug & DEBUG_VESD_PIC_EXT) { fprintf (stderr, "VES PIC EXT structure=%s\n", picture_structure_name (vd->picture_structure)); } vd->received_blocks |= RECEIVED_PICTURE_EXT; } static void vesd_picture_header (struct video_es_decoder *vd, const uint8_t * buf, unsigned int min_bytes_valid) { unsigned int c; /* picture_start_code [32], picture_temporal_reference [10], picture_coding_type [3], ... */ /* XXX consider estimating the PTS if none transmitted. */ if (min_bytes_valid < 6 /* || vd->received_blocks != RECEIVED_PES_PACKET */) { /* Too much data lost. */ vd->received_blocks = 0; return; } c = buf[4] * 256 + buf[5]; vd->picture_temporal_reference = (c >> 6) & 0x3FF; vd->picture_coding_type = (enum picture_coding_type)((c >> 3) & 7); if (option_debug & DEBUG_VESD_PIC_HDR) { fprintf (stderr, "VES PIC HDR ref=%d type=%ss\n", vd->picture_temporal_reference, picture_coding_type_name (vd->picture_coding_type)); } ++vd->n_pictures_received; vd->received_blocks |= RECEIVED_PICTURE; } static void vesd_pes_packet_header (struct video_es_decoder *vd, const uint8_t * buf, unsigned int min_bytes_valid) { unsigned int PES_packet_length; unsigned int PTS_DTS_flags; int64_t pts; if (unlikely (option_debug & DEBUG_VESD_PES_PACKET)) { dump_pes_packet_header (stderr, buf); } vd->pts = -1; vd->dts = -1; vd->received_blocks = 0; /* packet_start_code_prefix [24], stream_id [8], PES_packet_length [16], '10', PES_scrambling_control [2], PES_priority, data_alignment_indicator, copyright, original_or_copy, PTS_DTS_flags [2], ESCR_flag, ES_rate_flag, DSM_trick_mode_flag, additional_copy_info_flag, PES_CRC_flag, PES_extension_flag, PES_header_data_length [8] */ if (min_bytes_valid < 9) return; PES_packet_length = buf[4] * 256 + buf[5]; PTS_DTS_flags = (buf[7] & 0xC0) >> 6; /* ISO 13818-1 Section 2.4.3.7: In transport streams video PES packets do not carry data, they only contain the DTS/PTS of the following picture and PES_packet_length must be zero. */ if (0 != PES_packet_length) return; switch (PTS_DTS_flags) { case 0: /* no timestamps */ return; case 1: /* forbidden */ return; case 2: /* PTS only */ if (min_bytes_valid < 14) return; if (!decode_time_stamp (&vd->pts, &buf[9], 0x21)) return; break; case 3: /* PTS and DTS */ if (min_bytes_valid < 19) return; if (!decode_time_stamp (&pts, &buf[9], 0x31)) return; if (!decode_time_stamp (&vd->dts, &buf[14], 0x11)) return; vd->pts = pts; break; } if (unlikely (option_minicut_test)) { struct program *pr; int64_t dts; pr = PARENT (vd, struct program, vesd); dts = vd->dts; if (dts < 0) dts = vd->pts; if (pr->first_dts < 0) { pr->first_dts = dts; } else if (dts < pr->first_dts) { dts += TIMESTAMP_MASK + 1; } pr->now.tv_sec = (dts - pr->first_dts) / 90000; pr->now.tv_usec = (dts - pr->first_dts) % 90000 * 100 / 9; } else { struct program *pr; pr = PARENT (vd, struct program, vesd); gettimeofday (&pr->now, /* tz */ NULL); } vd->received_blocks = RECEIVED_PES_PACKET; } static void vesd_decode_block (struct video_es_decoder *vd, unsigned int start_code, const uint8_t * buf, unsigned int n_bytes, unsigned int min_bytes_valid, vbi_bool data_lost) { if (unlikely (option_debug & DEBUG_VESD_START_CODE)) { fprintf (stderr, "VES 0x000001%02X %u %u\n", start_code, min_bytes_valid, n_bytes); } /* The CEA 608-C and 708-C Close Caption data is encoded in picture user data fields. ISO 13818-2 requires the start code sequence 0x00, 0xB5/8, (0xB5?, 0xB2?)*. To properly convert from coded order to display order we also need the picture_coding_type and picture_structure fields. */ if (likely (start_code <= 0xAF)) { if (!data_lost && (vd->received_blocks == (RECEIVED_PICTURE | RECEIVED_PICTURE_EXT) || vd->received_blocks == (RECEIVED_PES_PACKET | RECEIVED_PICTURE | RECEIVED_PICTURE_EXT))) { /* No user data received for this picture. */ vesd_user_data (vd, NULL, 0); } if (unlikely (0x00 == start_code) && !data_lost) { vesd_picture_header (vd, buf, min_bytes_valid); } else { /* slice_start_code, or data lost in or after the picture_header. */ /* For all we care the picture data is just useless filler prior to the next PES packet header, and we need an uninterrupted sequence from there to the next picture user_data to ensure the PTS, DTS, picture_temporal_reference, picture_coding_type, picture_structure and cc_data belong together. */ vd->received_blocks = 0; vd->pts = -1; vd->dts = -1; } } else if (USER_DATA_START_CODE == start_code) { vesd_user_data (vd, buf, min_bytes_valid); } else if (data_lost) { /* Data lost in or after this block. */ vd->received_blocks = 0; } else if (EXTENSION_START_CODE == start_code) { vesd_extension (vd, buf, min_bytes_valid); } else if (start_code >= VIDEO_STREAM_0 && start_code <= VIDEO_STREAM_15) { if (!data_lost && (vd->received_blocks == (RECEIVED_PICTURE | RECEIVED_PICTURE_EXT) || vd->received_blocks == (RECEIVED_PES_PACKET | RECEIVED_PICTURE | RECEIVED_PICTURE_EXT))) { /* No user data received for previous picture. */ vesd_user_data (vd, NULL, 0); } /* Start of a new picture. */ vesd_pes_packet_header (vd, buf, min_bytes_valid); } else { /* Should be a sequence_header or group_of_pictures_header. */ vd->received_blocks &= RECEIVED_PES_PACKET; } /* Not all of this data is relevant for caption decoding but we may need it to debug the video ES decoder. Without the actual picture data it should be highly repetitive and compress rather well. */ if (unlikely (NULL != vd->video_es_tap_fp)) { unsigned int n = n_bytes; if (start_code >= 0x01 && start_code <= 0xAF) { if (NULL == vd->option_video_es_all_tap_file_name) n = MIN (n, 8u); } if (n != fwrite (buf, 1, n, vd->video_es_tap_fp)) { errno_exit ("Video ES tap write error"); } } } static unsigned int vesd_make_room (struct video_es_decoder *vd, unsigned int required) { struct buffer *b; unsigned int capacity; unsigned int in; b = &vd->buffer; capacity = b->capacity; in = b->in; if (unlikely (in + required > capacity)) { unsigned int consumed; unsigned int unconsumed; consumed = b->out; unconsumed = in - consumed; if (required > capacity - unconsumed) { /* XXX make this a recoverable error. */ error_exit ("Video ES buffer overflow.\n"); } memmove (b->base, b->base + consumed, unconsumed); in = unconsumed; b->out = 0; } return in; } static void video_es_decoder (struct video_es_decoder *vd, const uint8_t * buf, unsigned int n_bytes, vbi_bool data_lost) { const uint8_t *s; const uint8_t *e; const uint8_t *e_max; unsigned int in; /* This code searches for a start code and then decodes the data between the previous and the current start code. */ in = vesd_make_room (vd, n_bytes); memcpy (vd->buffer.base + in, buf, n_bytes); vd->buffer.in = in + n_bytes; s = vd->buffer.base + vd->buffer.out + vd->skip; e = vd->buffer.base + in + n_bytes - 4; e_max = e; if (unlikely (data_lost)) { if (vd->min_bytes_valid >= UINT_MAX) { vd->min_bytes_valid = in - vd->buffer.out; } /* Data is missing after vd->buffer.base + in, so we must ignore apparent start codes crossing that boundary. */ e -= n_bytes; } for (;;) { const uint8_t *b; enum start_code start_code; unsigned int n_bytes; unsigned int min_bytes_valid; for (;;) { if (s >= e) { /* Need more data. */ if (unlikely (s < e_max)) { /* Skip over the lost data. */ s = e + 4; e = e_max; continue; } /* In the next iteration skip the bytes we already scanned. */ vd->skip = s - vd->buffer.base - vd->buffer.out; return; } if (likely (0 != (s[2] & ~1))) { /* Not 000001 or xx0000 or xxxx00. */ s += 3; } else if (0 != (s[0] | s[1]) || 1 != s[2]) { ++s; } else { break; } } b = vd->buffer.base + vd->buffer.out; n_bytes = s - b; min_bytes_valid = n_bytes; data_lost = FALSE; if (unlikely (vd->min_bytes_valid < UINT_MAX)) { if (n_bytes < vd->min_bytes_valid) { /* We found a new start code before the missing data. */ vd->min_bytes_valid -= n_bytes; } else { min_bytes_valid = vd->min_bytes_valid; vd->min_bytes_valid = UINT_MAX; /* Need a flag in case we lost data just before the next start code. */ data_lost = TRUE; } } start_code = vd->last_start_code; if (likely ((int) start_code >= 0)) { vesd_decode_block (vd, start_code, b, n_bytes, min_bytes_valid, data_lost); } /* Remove the data we just decoded from the buffer. Remember the position of the new start code we found, skip it and continue the search. */ vd->buffer.out = s - vd->buffer.base; vd->last_start_code = (enum start_code) s[3]; s += 4; } } static void reset_video_es_decoder (struct video_es_decoder *vd) { vd->buffer.in = 0; vd->buffer.out = 0; vd->min_bytes_valid = UINT_MAX; vd->skip = 0; vd->last_start_code = (enum start_code) -1; vd->pts = -1; vd->dts = -1; vd->picture_coding_type = (enum picture_coding_type) -1; vd->picture_structure = (enum picture_structure) -1; vd->received_blocks = 0; vd->reorder_pictures = 0; } static void init_video_es_decoder (struct video_es_decoder *vd) { CLEAR (*vd); init_buffer (&vd->buffer, /* capacity */ 1 << 20); reset_video_es_decoder (vd); } static void video_es_test_loop (struct program * pr, const char * test_file_name) { FILE *test_fp; assert (NULL != ts_buffer); test_fp = open_test_file (test_file_name); for (;;) { size_t todo; size_t actual; todo = 4096; actual = fread (ts_buffer, 1, todo, test_fp); if (likely (actual == todo)) { video_es_decoder (&pr->vesd, ts_buffer, /* n_bytes */ actual, /* data_lost */ FALSE); } else if (ferror (test_fp)) { errno_exit ("Video ES read error"); } else { log (1, "End of video ES file.\n"); fclose (test_fp); return; } } } static void dump_ts_packet_header (FILE * fp, const uint8_t buf[188]) { unsigned int sync_byte; unsigned int transport_error_indicator; unsigned int payload_unit_start_indicator; unsigned int transport_priority; unsigned int PID; unsigned int transport_scrambling_control; unsigned int adaptation_field_control; unsigned int continuity_counter; unsigned int header_length; sync_byte = buf[0]; transport_error_indicator = buf[1] & 0x80; payload_unit_start_indicator = buf[1] & 0x40; transport_priority = buf[1] & 0x20; PID = (buf[1] * 256 + buf[2]) & 0x1FFF; transport_scrambling_control = (buf[3] & 0xC0) >> 6; adaptation_field_control = (buf[3] & 0x30) >> 4; continuity_counter = buf[3] & 0x0F; if (adaptation_field_control >= 2) { unsigned int adaptation_field_length; adaptation_field_length = buf[4]; header_length = 5 + adaptation_field_length; } else { header_length = 4; } fprintf (fp, "TS %02x %c%c%c %04x %u%u%x %u\n", sync_byte, transport_error_indicator ? 'E' : '-', payload_unit_start_indicator ? 'S' : '-', transport_priority ? 'P' : '-', PID, transport_scrambling_control, adaptation_field_control, continuity_counter, header_length); } static void tsd_program (struct program * pr, const uint8_t buf[188], unsigned int pid, unsigned int es_num) { unsigned int adaptation_field_control; unsigned int header_length; unsigned int payload_length; vbi_bool data_lost; adaptation_field_control = (buf[3] & 0x30) >> 4; if (likely (1 == adaptation_field_control)) { header_length = 4; } else if (3 == adaptation_field_control) { unsigned int adaptation_field_length; adaptation_field_length = buf[4]; /* Zero length is used for stuffing. */ if (adaptation_field_length > 0) { unsigned int discontinuity_indicator; /* ISO 13818-1 Section 2.4.3.5. Also the code below would be rather upset if header_length > packet_size. */ if (adaptation_field_length > 182) { log (2, "Invalid TS header " "on station %u, stream %u.\n", station_num (pr), pid); /* Possibly. */ pr->tsd.data_lost = TRUE; return; } /* ISO 13818-1 Section 2.4.3.5 */ discontinuity_indicator = buf[5] & 0x80; if (discontinuity_indicator) pr->tsd.next_ts_cc[es_num] = -1; } header_length = 5 + adaptation_field_length; } else { /* 0 == adaptation_field_control: invalid; 2 == adaptation_field_control: no payload. */ /* ISO 13818-1 Section 2.4.3.3: continuity_counter shall not increment. */ return; } payload_length = 188 - header_length; data_lost = pr->tsd.data_lost; if (unlikely (0 != ((pr->tsd.next_ts_cc[es_num] ^ buf[3]) & 0x0F))) { /* Continuity counter mismatch. */ if (pr->tsd.next_ts_cc[es_num] < 0) { /* First TS packet. */ } else if (0 == (((pr->tsd.next_ts_cc[es_num] - 1) ^ buf[3]) & 0x0F)) { /* ISO 13818-1 Section 2.4.3.3: Repeated packet. */ return; } else { log (2, "TS continuity error " "on station %u, stream %u.\n", station_num (pr), pid); data_lost = TRUE; } } pr->tsd.next_ts_cc[es_num] = buf[3] + 1; pr->tsd.data_lost = FALSE; if (NULL != pr->option_minicut_dir_name) video_recorder (&pr->vr, buf); if (0 == es_num) { video_es_decoder (&pr->vesd, buf + header_length, payload_length, data_lost); } } static void ts_decoder (const uint8_t buf[188]) { unsigned int pid; unsigned int i; if (0) { dump_ts_packet_header (stderr, buf); } if (unlikely (NULL != ts_tap_fp)) { if (188 != fwrite (buf, 1, 188, ts_tap_fp)) { errno_exit ("TS tap write error"); } } if (unlikely (buf[1] & 0x80)) { log (2, "TS transmission error.\n"); /* The PID may be wrong, we don't know how much data was lost, and continuity counters match by chance with 1:16 probability. */ for (i = 0; i < n_programs; ++i) { video_recorder (&program_table[i].vr, buf); program_table[i].tsd.data_lost = TRUE; } return; } pid = (buf[1] * 256 + buf[2]) & 0x1FFF; /* Note two or more programs may share one elementary stream (e.g. radio programs with a dummy video stream). */ for (i = 0; i < n_programs; ++i) { struct program *pr; unsigned int es_num; pr = program_table + i; es_num = 0; if (pid == pr->tsd.pid[1]) { es_num = 1; } else if (pid != pr->tsd.pid[0]) { continue; } tsd_program (pr, buf, pid, es_num); } } static void init_ts_decoder (struct ts_decoder * td) { CLEAR (*td); memset (&td->next_ts_cc, -1, sizeof (td->next_ts_cc)); } static void ts_test_loop (const char * test_file_name) { FILE *test_fp; assert (NULL != ts_buffer); test_fp = open_test_file (test_file_name); for (;;) { size_t todo; size_t actual; todo = 188; actual = fread (ts_buffer, 1, todo, test_fp); if (likely (actual == todo)) { ts_decoder (ts_buffer); } else if (ferror (test_fp)) { errno_exit ("TS read error"); } else { log (1, "End of TS file.\n"); fclose (test_fp); return; } } } static void * demux_thread (void * arg) { unsigned int in; unsigned int out; arg = arg; /* unused */ assert (0 == ts_buffer_capacity % 188); out = ts_buffer_out; /* We don't actually need the mutex but pthread_cond_wait() won't work without it. */ pthread_mutex_lock (&dx_mutex); for (;;) { unsigned int avail; in = ts_buffer_in; avail = in - out; if (in < out) avail += ts_buffer_capacity; if (avail <= 0) { /* Yield the CPU if the buffer is empty. */ pthread_cond_wait (&dx_cond, &dx_mutex); continue; } if (0) { fputc (',', stderr); fflush (stderr); } else { ts_decoder (ts_buffer + out); } out += 188; if (out >= ts_buffer_capacity) out = 0; ts_buffer_out = out; } pthread_mutex_unlock (&dx_mutex); return NULL; } static void init_program (struct program * pr) { CLEAR (*pr); pr->first_dts = -1; pr->first_pts = -1; init_ts_decoder (&pr->tsd); init_video_es_decoder (&pr->vesd); init_video_recorder (&pr->vr); init_caption_recorder (&pr->cr); } static void destroy_demux_state (void) { free (ts_buffer); ts_buffer = NULL; ts_buffer_capacity = 0; pthread_cond_destroy (&dx_cond); pthread_mutex_destroy (&dx_mutex); } static void init_demux_state (void) { pthread_mutex_init (&dx_mutex, /* attr */ NULL); pthread_cond_init (&dx_cond, /* attr */ NULL); /* This buffer prevents data loss if disk activity blocks a printf() in the caption decoder. Actually we need to buffer only CC data which trickles in at 9600 bits/s but I don't want to burden the capture thread with TS and PES demultiplexing and avoid the overhead of another thread. */ ts_buffer_capacity = 20000 * 188; ts_buffer = xmalloc (ts_buffer_capacity); /* Copy-on-write and lock the pages. */ memset (ts_buffer, -1, ts_buffer_capacity); ts_error = 0x00; ts_buffer_in = 0; ts_buffer_out = 0; ts_n_packets_in = 0; } /* Capture thread */ static const char * fe_type_name (enum fe_type t) { #undef CASE #define CASE(x) case FE_##x: return #x; switch (t) { CASE (QPSK) CASE (QAM) CASE (OFDM) CASE (ATSC) } return "invalid"; } static const char * fe_spectral_inversion_name (enum fe_spectral_inversion t) { #undef CASE #define CASE(x) case INVERSION_##x: return #x; switch (t) { CASE (OFF) CASE (ON) CASE (AUTO) } return "invalid"; } static const char * fe_code_rate_name (enum fe_code_rate t) { #undef CASE #define CASE(x) case FEC_##x: return #x; switch (t) { CASE (NONE) CASE (1_2) CASE (2_3) CASE (3_4) CASE (4_5) CASE (5_6) CASE (6_7) CASE (7_8) CASE (8_9) CASE (AUTO) } return "invalid"; } static const char * fe_modulation_name (enum fe_modulation t) { #undef CASE #define CASE(x) case x: return #x; switch (t) { CASE (QPSK) CASE (QAM_16) CASE (QAM_32) CASE (QAM_64) CASE (QAM_128) CASE (QAM_256) CASE (QAM_AUTO) CASE (VSB_8) CASE (VSB_16) } return "invalid"; } static const char * fe_transmit_mode_name (enum fe_transmit_mode t) { #undef CASE #define CASE(x) case TRANSMISSION_MODE_##x: return #x; switch (t) { CASE (2K) CASE (8K) CASE (AUTO) } return "invalid"; } static const char * fe_bandwidth_name (enum fe_bandwidth t) { #undef CASE #define CASE(x) case BANDWIDTH_##x: return #x; switch (t) { CASE (8_MHZ) CASE (7_MHZ) CASE (6_MHZ) CASE (AUTO) } return "invalid"; } static const char * fe_guard_interval_name (enum fe_guard_interval t) { #undef CASE #define CASE(x) case GUARD_INTERVAL_##x: return #x; switch (t) { CASE (1_32) CASE (1_16) CASE (1_8) CASE (1_4) CASE (AUTO) } return "invalid"; } static const char * fe_hierarchy_name (enum fe_hierarchy t) { #undef CASE #define CASE(x) case HIERARCHY_##x: return #x; switch (t) { CASE (NONE) CASE (1) CASE (2) CASE (4) CASE (AUTO) } return "invalid"; } #undef CASE static vbi_bool same_transponder (struct station * s1, struct station * s2) { if (s1->frequency != s2->frequency) return FALSE; if (s1->type != s2->type) return FALSE; switch (s1->type) { case FE_ATSC: if (s1->u.atsc.modulation != s1->u.atsc.modulation) return FALSE; break; case FE_OFDM: if (s1->u.dvb_t.inversion != s2->u.dvb_t.inversion || s1->u.dvb_t.bandwidth != s2->u.dvb_t.bandwidth || s1->u.dvb_t.code_rate_HP != s2->u.dvb_t.code_rate_HP || s1->u.dvb_t.code_rate_LP != s2->u.dvb_t.code_rate_LP || s1->u.dvb_t.constellation != s2->u.dvb_t.constellation || s1->u.dvb_t.transm_mode != s2->u.dvb_t.transm_mode || s1->u.dvb_t.guard_interval != s2->u.dvb_t.guard_interval || s1->u.dvb_t.hierarchy != s2->u.dvb_t.hierarchy) return FALSE; break; case FE_QPSK: case FE_QAM: assert (0); } return TRUE; } /* We use a FIFO because the caption decoder may block for extended periods due to disk activity, and buffering in the kernel driver is unreliable in my experience. */ static void ct_filter (const uint8_t buf[188]) { unsigned int in; unsigned int out; unsigned int free; /* Supposedly all modern CPUs read and write ints atomically so we can avoid the mutex locking overhead. */ in = ts_buffer_in; out = ts_buffer_out; assert (in < ts_buffer_capacity); assert (out < ts_buffer_capacity); if (likely (0 == (buf[1] & 0x80))) { unsigned int pid; pid = (buf[1] * 256 + buf[2]) & 0x1FFF; if (0) { fprintf (stderr, "CT TS 0x%04x = %u\n", pid, pid); return; } if (pid_map[pid].program < 0) { if (NULL == option_ts_all_tap_file_name) return; } } free = out - in; if (out <= in) free += ts_buffer_capacity; if (unlikely (free <= 188)) { ts_error = 0x80; return; } memcpy (ts_buffer + in, buf, 188); ts_buffer[in + 1] |= ts_error; ts_error = 0; ++ts_n_packets_in; in += 188; if (in >= ts_buffer_capacity) in = 0; ts_buffer_in = in; /* Hm. This delays the output. */ if (1 || free < (ts_buffer_capacity / 2)) { pthread_cond_signal (&dx_cond); } } static unsigned int ct_resync (const uint8_t buf[2 * 188]) { unsigned int i; for (i = 1; i < 188; ++i) { if (0x47 == buf[i] && 0x47 == buf[i + 188]) return i; } log (1, "Capture thread cannot synchronize.\n"); capture_thread_id = 0; pthread_exit (NULL); return 0; } static vbi_bool ct_read (uint8_t * buffer, ssize_t todo) { unsigned int retry = 100; do { ssize_t actual; actual = read (dvr_fd, buffer, todo); if (likely (actual == todo)) return TRUE; if (actual > 0) { if (unlikely (actual >= todo)) { log (1, "DVB device read size " "error.\n"); return FALSE; } buffer += actual; todo -= actual; continue; } else if (actual < 0) { int saved_errno; saved_errno = errno; if (EINTR == saved_errno) continue; log_errno (1, "DVB device read error"); errno = saved_errno; return FALSE; } else { log (2, "EOF from DVB device (ignored).\n"); return FALSE; } } while (--retry > 0); log (2, "DVB device read error: EINTR or " "read size problem.\n"); errno = EINTR; return FALSE; } static void * capture_thread (void * arg) { uint8_t *start; uint8_t *end; const uint8_t *s; const uint8_t *e; unsigned int left; ssize_t size; arg = arg; /* unused */ log (2, "Capture thread ready.\n"); /* Don't swap out any code or data pages. If the capture thread is delayed we may lose packets. Errors ignored. */ mlockall (MCL_CURRENT | MCL_FUTURE); pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, /* old */ NULL); pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, /* old */ NULL); /* Page aligned reads are not required, but who knows, it may allow some DMA magic or speed up copying. */ start = ct_buffer + 4096; size = ct_buffer_capacity - 4096; left = 0; assert (ct_buffer_capacity > 4096); assert (0 == ct_buffer_capacity % 4096); xioctl (dmx_fd, DMX_START, 0); for (;;) { if (!ct_read (start, size)) continue; ct_n_bytes_in += size; end = start + size; s = start - left; e = end - 4096; while (s < e) { if (0x47 != s[0] || 0x47 != s[188]) { if (ts_n_packets_in > 0) { log (2, "Capture thread " "lost sync.\n"); } s += ct_resync (s); } if (0) { fputc ('.', stderr); fflush (stderr); } else { ct_filter (s); } s += 188; } left = end - s; memcpy (start - left, s, left); } return NULL; } static void destroy_capture_state (void) { /* FIXME this a glibc feature. */ free (ct_buffer); ct_buffer = NULL; ct_buffer_capacity = 0; } #ifdef HAVE_POSIX_MEMALIGN /* posix_memalign() was introduced in POSIX 1003.1d and may not be implemented on all systems. */ static void * my_memalign (size_t boundary, size_t size) { void *p; int err; /* boundary must be a power of two. */ if (0 != (boundary & (boundary - 1))) return malloc (size); err = posix_memalign (&p, boundary, size); if (0 == err) return p; errno = err; return NULL; } #elif defined HAVE_MEMALIGN /* memalign() is a GNU extension. Due to the DVB driver interface this program currently runs on Linux only, but it can't hurt to be prepared. */ # define my_memalign memalign #else # define my_memalign(boundary, size) malloc (size) #endif static void init_capture_state (void) { ct_buffer_capacity = 32 * 1024; ct_buffer = my_memalign (4096, ct_buffer_capacity); if (NULL == ct_buffer) { no_mem_exit (); } /* Copy-on-write and lock the pages. */ memset (ct_buffer, -1, ct_buffer_capacity); ct_n_bytes_in = 0; } static int xopen_device (const char * dev_name, int flags) { struct stat st; int fd; if (-1 == stat (dev_name, &st)) { goto open_failed; } if (!S_ISCHR (st.st_mode)) { error_exit ("'%s' is not a DVB device.\n", dev_name); } fd = open (dev_name, flags, /* mode */ 0); if (-1 == fd) { goto open_failed; } return fd; open_failed: errno_exit ("Cannot open '%s'", dev_name); return -1; /* not reached */ } static void close_device (void) { log (2, "Closing DVB device.\n"); if (0 != capture_thread_id) { pthread_cancel (capture_thread_id); pthread_join (capture_thread_id, NULL); capture_thread_id = 0; } destroy_capture_state (); if (-1 != dmx_fd) { close (dmx_fd); dmx_fd = -1; } if (-1 != dvr_fd) { close (dvr_fd); dvr_fd = -1; } if (-1 != fe_fd) { close (fe_fd); fe_fd = -1; } } static void open_device (void) { struct dvb_frontend_info fe_info; struct dvb_frontend_parameters fe_param; struct dmx_pes_filter_params filter; char *dev_name; unsigned int retry; dev_name = NULL; init_capture_state (); /* Front end. */ log (2, "Opening dvb/adapter%lu.\n", option_dvb_adapter_num); dev_name = xasprintf ("/dev/dvb/adapter%lu/frontend%lu", option_dvb_adapter_num, option_dvb_frontend_id); fe_fd = xopen_device (dev_name, O_RDWR); CLEAR (fe_info); xioctl (fe_fd, FE_GET_INFO, &fe_info); switch (fe_info.type) { case FE_ATSC: case FE_OFDM: if (fe_info.type != station->type) { error_exit ("'%s' is not %s device.\n", dev_name, (FE_ATSC == station->type) ? "an ATSC" : "a DVB-T"); } break; case FE_QPSK: case FE_QAM: error_exit ("'%s' is not an ATSC device.\n", dev_name); break; } CLEAR (fe_param); fe_param.frequency = station->frequency; switch (fe_info.type) { case FE_ATSC: fe_param.u.vsb.modulation = station->u.atsc.modulation; break; case FE_OFDM: fe_param.inversion = station->u.dvb_t.inversion; fe_param.u.ofdm.bandwidth = station->u.dvb_t.bandwidth; fe_param.u.ofdm.code_rate_HP = station->u.dvb_t.code_rate_HP; fe_param.u.ofdm.code_rate_LP = station->u.dvb_t.code_rate_LP; fe_param.u.ofdm.constellation = station->u.dvb_t.constellation; fe_param.u.ofdm.transmission_mode = station->u.dvb_t.transm_mode; fe_param.u.ofdm.guard_interval = station->u.dvb_t.guard_interval; fe_param.u.ofdm.hierarchy_information = station->u.dvb_t.hierarchy; break; case FE_QPSK: case FE_QAM: assert (0); } xioctl (fe_fd, FE_SET_FRONTEND, &fe_param); for (retry = 0;;) { fe_status_t status; xioctl (fe_fd, FE_READ_STATUS, &status); if (status & FE_HAS_LOCK) break; if (++retry > 20) { error_exit ("No signal detected.\n"); } if (7 == (retry & 7)) { log (2, "Waiting for a signal.\n"); } usleep (250000); } log (2, "Signal detected.\n"); free (dev_name); /* DVR. */ dev_name = xasprintf ("/dev/dvb/adapter%lu/dvr%lu", option_dvb_adapter_num, option_dvb_dvr_id); dvr_fd = xopen_device (dev_name, O_RDONLY); /* Not implemented? Let's try anyway. */ xioctl_may_fail (dvr_fd, DMX_SET_BUFFER_SIZE, (void *)(4 << 20)); free (dev_name); /* Demultiplexer. */ dev_name = xasprintf ("/dev/dvb/adapter%lu/demux%lu", option_dvb_adapter_num, option_dvb_demux_id); dmx_fd = xopen_device (dev_name, O_RDWR); xioctl_may_fail (dmx_fd, DMX_SET_BUFFER_SIZE, (void *)(4 << 20)); CLEAR (filter); /* We capture the entire transport multiplex so we can receive all stations on this transponder at once and properly handle transmission errors in the video ES demultiplexer. */ filter.pid = 0x2000; filter.input = DMX_IN_FRONTEND; filter.output = DMX_OUT_TS_TAP; filter.pes_type = DMX_PES_OTHER; xioctl (dmx_fd, DMX_SET_PES_FILTER, &filter); free (dev_name); /* Start capture thread. */ if (0 != pthread_create (&capture_thread_id, /* attr */ NULL, capture_thread, /* arg */ NULL)) { errno_exit ("Cannot start capture thread"); } log (2, "Opened dvb/adapter%lu, tuned to %.3f MHz and " "started capture thread.\n", option_dvb_adapter_num, station->frequency / 1e6); } static enum fe_type device_type (void) { struct dvb_frontend_info fe_info; char *dev_name; int fd; dev_name = xasprintf ("/dev/dvb/adapter%lu/frontend%lu", option_dvb_adapter_num, option_dvb_frontend_id); fd = xopen_device (dev_name, O_RDWR); CLEAR (fe_info); xioctl (fd, FE_GET_INFO, &fe_info); close (fd); switch (fe_info.type) { case FE_ATSC: case FE_OFDM: break; case FE_QPSK: case FE_QAM: error_exit ("'%s' is not an ATSC device.\n", dev_name); break; } free (dev_name); return fe_info.type; } static void list_stations (void) { const struct station *st; size_t max_len; if (NULL == station_list) { printf ("The channel config file is empty.\n"); return; } /* FIXME the encoding of station names is unknown, could be a multi-byte coding like UTF-8. */ max_len = 0; for (st = station_list; NULL != st; st = st->next) { size_t len; len = strlen (st->name); max_len = MAX (max_len, len); } for (st = station_list; NULL != st; st = st->next) { printf ("%-*s %3.3f MHz\n", (int) max_len, st->name, st->frequency / 1e6); } } static struct station * find_station (const char * station_name) { struct station *st; for (st = station_list; NULL != st; st = st->next) { if (0 == strcmp (station_name, st->name)) return st; } return NULL; } static char * parse_station_name (const char ** sp, int delimiter) { const char *s; const char *s_name; char *station_name; size_t len; s = *sp; while (isspace (*s)) ++s; s_name = s; while (0 != *s && delimiter != *s) ++s; *sp = s; while (s > s_name && isspace (s[-1])) --s; len = s - s_name; if (0 == len) return NULL; station_name = xmalloc (len + 1); memcpy (station_name, s_name, len); station_name[len] = 0; return station_name; } struct key_value { const char * key; int value; }; static vbi_bool parse_enum (int * value, const char ** sp, const struct key_value *table) { const char *s; unsigned int i; s = *sp; while (isspace (*s)) ++s; for (i = 0; NULL != table[i].key; ++i) { size_t len = strlen (table[i].key); if (0 == strncmp (s, table[i].key, len)) { s += len; break; } } if (NULL == table[i].key) return FALSE; while (isspace (*s)) ++s; if (':' != *s++) return FALSE; *value = table[i].value; *sp = s; return TRUE; } static void parse_tzap_channel_conf_line (const char * filename, unsigned int line_number, const char * buffer) { static const struct key_value inversion [] = { { "INVERSION_OFF", INVERSION_OFF }, { "INVERSION_ON", INVERSION_ON }, { "INVERSION_AUTO", INVERSION_AUTO }, { NULL, 0 } }; static const struct key_value bandwidth [] = { { "BANDWIDTH_6_MHZ", BANDWIDTH_6_MHZ }, { "BANDWIDTH_7_MHZ", BANDWIDTH_7_MHZ }, { "BANDWIDTH_8_MHZ", BANDWIDTH_8_MHZ }, { NULL, 0 } }; static const struct key_value fec [] = { { "FEC_1_2", FEC_1_2 }, { "FEC_2_3", FEC_2_3 }, { "FEC_3_4", FEC_3_4 }, { "FEC_4_5", FEC_4_5 }, { "FEC_5_6", FEC_5_6 }, { "FEC_6_7", FEC_6_7 }, { "FEC_7_8", FEC_7_8 }, { "FEC_8_9", FEC_8_9 }, { "FEC_AUTO", FEC_AUTO }, { "FEC_NONE", FEC_NONE }, { NULL, 0 } }; static const struct key_value constellation [] = { { "QPSK", QPSK }, { "QAM_16", QAM_16 }, { "QAM_32", QAM_32 }, { "QAM_64", QAM_64 }, { "QAM_128", QAM_128 }, { "QAM_256", QAM_256 }, { NULL, 0 } }; static const struct key_value transmission_mode [] = { { "TRANSMISSION_MODE_2K", TRANSMISSION_MODE_2K }, { "TRANSMISSION_MODE_8K", TRANSMISSION_MODE_8K }, { NULL, 0 } }; static const struct key_value guard_interval [] = { {"GUARD_INTERVAL_1_16", GUARD_INTERVAL_1_16}, {"GUARD_INTERVAL_1_32", GUARD_INTERVAL_1_32}, {"GUARD_INTERVAL_1_4", GUARD_INTERVAL_1_4}, {"GUARD_INTERVAL_1_8", GUARD_INTERVAL_1_8}, { NULL, 0 } }; static const struct key_value hierarchy [] = { { "HIERARCHY_1", HIERARCHY_1 }, { "HIERARCHY_2", HIERARCHY_2 }, { "HIERARCHY_4", HIERARCHY_4 }, { "HIERARCHY_NONE", HIERARCHY_NONE }, { NULL, 0 } }; struct station *st; struct station **stp; const struct station *st2; const char *detail; const char *s; char *s_end; int value; /* TZAP channel config file format: A number of lines with the fields: 1. Station name (encoding?) 2. Transponder frequency in Hz 3. Inversion: INVERSION_(ON|OFF|AUTO) 4. Bandwidth: BANDWIDTH_(6|7|8)_MHZ 5. Code rate HP: FEC_(1_2|2_3|3_4|4_5|5_6|6_7|7_8|8_9|AUTO|NONE) 6. Code rate LP: as above 7. Constellation: QPSK, QAM_(16|32|64|128|256) 8. Transmission mode: TRANSMISSION_MODE_(2K|8K) 9. Guard interval: GUARD_INTERVAL_1_(4|8|16|32) 10. Hierarchy information: HIERARCHY_(1|2|4|NONE) 11. Video stream PID 12. Audio stream PID 13. ? The fields are separated by one colon. We skip whitespace at the beginning of a line, whitespace before and after colons, empty lines, and lines starting with a number sign. */ st = xmalloc (sizeof (*st)); CLEAR (*st); s = buffer; while (isspace (*s)) ++s; if (0 == *s || '#' == *s) return; detail = "station name"; st->name = parse_station_name (&s, /* delimiter */ ':'); if (NULL == st->name) goto invalid; if (':' != *s++) goto invalid; st2 = find_station (st->name); if (NULL != st2) { error_exit ("Duplicate station name '%s' " "in %s line %u.\n", st->name, filename, line_number); } st->type = FE_OFDM; detail = "frequency"; /* NB. strtoul() skips leading whitespace. */ st->frequency = strtoul (s, &s_end, 0); if (s_end == s || st->frequency < 1) goto invalid; s = s_end; while (isspace (*s)) ++s; if (':' != *s++) goto invalid; detail = "inversion"; if (!parse_enum (&value, &s, inversion)) goto invalid; st->u.dvb_t.inversion = value; detail = "bandwidth"; if (!parse_enum (&value, &s, bandwidth)) goto invalid; st->u.dvb_t.bandwidth = value; detail = "code rate HP"; if (!parse_enum (&value, &s, fec)) goto invalid; st->u.dvb_t.code_rate_HP = value; detail = "code rate LP"; if (!parse_enum (&value, &s, fec)) goto invalid; st->u.dvb_t.code_rate_LP = value; detail = "constellation"; if (!parse_enum (&value, &s, constellation)) goto invalid; st->u.dvb_t.constellation = value; detail = "transmission_mode"; if (!parse_enum (&value, &s, transmission_mode)) goto invalid; st->u.dvb_t.transm_mode = value; detail = "guard_interval"; if (!parse_enum (&value, &s, guard_interval)) goto invalid; st->u.dvb_t.guard_interval = value; detail = "hierarchy"; if (!parse_enum (&value, &s, hierarchy)) goto invalid; st->u.dvb_t.hierarchy = value; detail = "video PID"; st->video_pid = strtoul (s, &s_end, 0); if (s_end == s || (unsigned int) st->video_pid > 0x1FFE) goto invalid; s = s_end; while (isspace (*s)) ++s; if (':' != *s++) goto invalid; detail = "audio PID"; st->audio_pid = strtoul (s, &s_end, 0); if (s_end == s || (unsigned int) st->audio_pid > 0x1FFE) goto invalid; s = s_end; while (isspace (*s)) ++s; if (':' != *s++) goto invalid; if (0 == st->video_pid) { if (option_debug & DEBUG_CONFIG) { fprintf (stderr, "Skipping radio station '%s'.\n", st->name); } free (st->name); free (st); return; } if (option_debug & DEBUG_CONFIG) { fprintf (stderr, "%3u: station_name='%s' frequency=%lu " "inversion=%s bandwidth=%s code_rate=%s/%s " "constellation=%s transm_mode=%s " "guard_interval=%s hierarchy=%s " "video_pid=%u audio_pid=%u.\n", line_number, st->name, st->frequency, fe_spectral_inversion_name (st->u.dvb_t.inversion), fe_bandwidth_name (st->u.dvb_t.bandwidth), fe_code_rate_name (st->u.dvb_t.code_rate_HP), fe_code_rate_name (st->u.dvb_t.code_rate_LP), fe_modulation_name (st->u.dvb_t.constellation), fe_transmit_mode_name (st->u.dvb_t.transm_mode), fe_guard_interval_name (st->u.dvb_t.guard_interval), fe_hierarchy_name (st->u.dvb_t.hierarchy), st->video_pid, st->audio_pid); } /* Append to station list. */ for (stp = &station_list; NULL != *stp; stp = &(*stp)->next) ; *stp = st; return; invalid: error_exit ("Invalid %s field in '%s' line %u.\n", detail, filename, line_number); } static void parse_azap_channel_conf_line (const char * filename, unsigned int line_number, const char * buffer) { static const struct key_value modulations [] = { { "8VSB", VSB_8 }, { "16VSB", VSB_16 }, { "QAM_64", QAM_64 }, { "QAM_256", QAM_256 }, { NULL, 0 } }; struct station *st; struct station **stp; const struct station *st2; const char *detail; const char *s; char *s_end; int value; /* AZAP channel config file format: A number of lines with the fields: 1. Station name (encoding?) 2. Transponder frequency in Hz 3. Modulation: 8VSB, 16VSB, QAM_64, QAM_256 4. Video stream PID 5. Audio stream PID (one or more?) 6. Stream ID? The fields are separated by one colon. We skip whitespace at the beginning of a line, whitespace before and after colons, empty lines, and lines starting with a number sign. */ st = xmalloc (sizeof (*st)); CLEAR (*st); s = buffer; while (isspace (*s)) ++s; if (0 == *s || '#' == *s) return; detail = "station name"; st->name = parse_station_name (&s, /* delimiter */ ':'); if (NULL == st->name) goto invalid; if (':' != *s++) goto invalid; st2 = find_station (st->name); if (NULL != st2) { error_exit ("Duplicate station name '%s' " "in %s line %u.\n", st->name, filename, line_number); } st->type = FE_ATSC; detail = "frequency"; /* NB. strtoul() skips leading whitespace. */ st->frequency = strtoul (s, &s_end, 0); if (s_end == s || st->frequency < 1) goto invalid; s = s_end; while (isspace (*s)) ++s; if (':' != *s++) goto invalid; detail = "modulation"; if (!parse_enum (&value, &s, modulations)) goto invalid; st->u.atsc.modulation = value; detail = "video PID"; st->video_pid = strtoul (s, &s_end, 0); if (s_end == s || (unsigned int) st->video_pid > 0x1FFE) goto invalid; s = s_end; while (isspace (*s)) ++s; if (':' != *s++) goto invalid; detail = "audio PID"; st->audio_pid = strtoul (s, &s_end, 0); if (s_end == s || (unsigned int) st->audio_pid > 0x1FFE) goto invalid; s = s_end; while (isspace (*s)) ++s; if (':' != *s++) goto invalid; if (0 == st->video_pid) { if (option_debug & DEBUG_CONFIG) { fprintf (stderr, "Skipping radio station '%s'.\n", st->name); } free (st->name); free (st); return; } if (option_debug & DEBUG_CONFIG) { fprintf (stderr, "%3u: station_name='%s' frequency=%lu " "modulation=%s video_pid=%u audio_pid=%u.\n", line_number, st->name, st->frequency, fe_modulation_name (st->u.atsc.modulation), st->video_pid, st->audio_pid); } /* Append to station list. */ for (stp = &station_list; NULL != *stp; stp = &(*stp)->next) ; *stp = st; return; invalid: error_exit ("Invalid %s field in '%s' line %u.\n", detail, filename, line_number); } static char * get_channel_conf_name (enum fe_type type) { const char *home; const char *s; s = option_channel_conf_file_name; if (NULL == s) { switch (type) { case FE_ATSC: s = "~/.azap/channels.conf"; break; case FE_OFDM: s = "~/.tzap/channels.conf"; break; case FE_QPSK: case FE_QAM: assert (0); break; } } if (0 == strncmp (s, "~/", 2)) { home = getenv ("HOME"); if (NULL == home) { error_exit ("Cannot open '%s' because the " "HOME environment variable " "is unset.\n", s); } ++s; } else { home = ""; } return xasprintf ("%s%s", home, s); } static void read_channel_conf (void) { char buffer[256]; char *channel_conf_name; enum fe_type type; unsigned int line_number; FILE *fp; if (NULL != station_list) return; type = option_dvb_type; if ((enum fe_type) -1 == option_dvb_type) type = device_type (); channel_conf_name = get_channel_conf_name (type); fp = fopen (channel_conf_name, "r"); if (NULL == fp) { errno_exit ("Cannot open '%s'", channel_conf_name); } if (option_debug & DEBUG_CONFIG) { fprintf (stderr, "Opened '%s' (%s):\n", channel_conf_name, fe_type_name (type)); } line_number = 1; while (NULL != fgets (buffer, sizeof (buffer), fp)) { const char *s; s = buffer; while (isspace (*s)) ++s; if (0 == *s || '#' == *s) continue; switch (type) { case FE_ATSC: parse_azap_channel_conf_line (channel_conf_name, line_number, buffer); break; case FE_OFDM: parse_tzap_channel_conf_line (channel_conf_name, line_number, buffer); break; case FE_QPSK: case FE_QAM: assert (0); } ++line_number; } if (ferror (fp) || 0 != fclose (fp)) { errno_exit ("Error while reading '%s'", channel_conf_name); } free (channel_conf_name); } static void usage (FILE * fp) { fprintf (fp, "\ " PROGRAM " " VERSION " -- ATSC Closed Caption and XDS decoder\n\ Copyright (C) 2008 Michael H. Schimek \n\ Based on code by Mike Baker, Mark K. Kim and timecop@japan.co.jp.\n\ This program is licensed under GPL 2 or later. NO WARRANTIES.\n\n\ Usage: %s [options] [-n] station name\n\ Options:\n\ -? | -h | --help | --usage Print this message, then terminate\n\ -1 ... -4 | --cc1-file ... --cc4-file file name\n\ Append CC1 ... CC4 to this file\n\ -5 ... -8 | --t1-file ... --t4-file file name\n\ Append T1 ... T4 to this file\n\ -9 ... -0 | --s1-file ... --s2-file file name\n\ Append DTVCC service 1 ... 2 to this file\n\ -a | --adapter-num number DVB device adapter [%lu]\n\ -b | --no-webtv Do not print WebTV links\n\ -c | --cc Print Closed Caption (includes WebTV)\n\ -d | --demux-id number DVB device demultiplexer [%lu]\n\ -e | --channel-conf file name Channel config. file [~/.azap/channels.conf]\n\ -f | --filter type[,type]* Select XDS info: all, call, desc, length,\n\ network, rating, time, timecode, timezone,\n\ title. Multiple -f options accumulate. [all]\n\ -i | --frontend-id number DVB device frontend [%lu]\n\ -j | --format type Print caption in 'plain' encoding, with\n\ 'vt100' control codes or like the 'ntsc-cc'\n\ tool [ntsc-cc].\n\ -l | --channel number Select caption channel 1 ... 4 [nothing]\n\ -m | --timestamps Prepend timestamps to caption lines\n\ -n | --station name Station name. Usually the -n can be omitted\n\ -q | --quiet Suppress all progress and error messages\n\ -p | --plain Same as -j plain.\n\ -r | --dvr-id number DVB device dvr [%lu]\n\ -s | --sentences Decode caption by sentences\n\ -v | --verbose Increase verbosity\n\ -x | --xds Print XDS info\n\ -C | --cc-file file name Append all caption to this file [stdout]\n\ -L | --list List all TV stations in the channel\n\ configuration file\n\ -S | --stream-time\n\ -Sdate | --stream-time=date Prepend timestamps to caption lines counting\n\ from the beginning of the video stream plus\n\ an optional date in format YYYYMMDDHHMMSS\n\ -T | --ts Decode a DVB Transport Stream on stdin\n\ instead of opening a DVB device\n\ -X | --xds-file file name Append XDS info to this file [stdout]\n\ \n\ To record data from multiple stations sharing a transponder frequency\n\ you can specify caption options and a station name repeatedly.\n\ ", my_name, option_dvb_adapter_num, option_dvb_demux_id, option_dvb_frontend_id, option_dvb_dvr_id); } static const char short_options [] = ("-?1:2:3:4:5:6:7:8:9:0:" "a:bcd:e:f:hi:j:l:mn:pr:svx" "C:DELM:PS::TX:"); #ifdef HAVE_GETOPT_LONG static const struct option long_options [] = { { "help", no_argument, NULL, '?' }, /* From ntsc-cc.c. */ { "cc1-file", required_argument, NULL, '1' }, { "cc2-file", required_argument, NULL, '2' }, { "cc3-file", required_argument, NULL, '3' }, { "cc4-file", required_argument, NULL, '4' }, /* From ntsc-cc.c. */ { "t1-file", required_argument, NULL, '5' }, { "t2-file", required_argument, NULL, '6' }, { "t3-file", required_argument, NULL, '7' }, { "t4-file", required_argument, NULL, '8' }, { "s1-file", required_argument, NULL, '9' }, { "s2-file", required_argument, NULL, '0' }, { "adapter-num", required_argument, NULL, 'a' }, /* From ntsc-cc.c. */ { "no-webtv", no_argument, NULL, 'b' }, /* From ntsc-cc.c. */ { "cc", no_argument, NULL, 'c' }, { "demux-id", required_argument, NULL, 'd' }, { "conf-file", required_argument, NULL, 'e' }, /* From ntsc-cc.c. */ { "filter", required_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { "usage", no_argument, NULL, 'h' }, { "frontend-id", required_argument, NULL, 'i' }, { "format", required_argument, NULL, 'j' }, { "channel", required_argument, NULL, 'l' }, { "timestamps", no_argument, NULL, 'm' }, { "station", required_argument, NULL, 'n' }, /* From ntsc-cc.c. */ { "plain", no_argument, NULL, 'p' }, /* From ntsc-cc.c. Actually the output was never limited to ASCII. */ { "plain-ascii", no_argument, NULL, 'p' }, { "quiet", no_argument, NULL, 'q' }, { "dvr-id", required_argument, NULL, 'r' }, /* From ntsc-cc.c. */ { "sentences", no_argument, NULL, 's' }, { "ucla", no_argument, NULL, 'u' }, { "verbose", no_argument, NULL, 'v' }, /* From ntsc-cc.c. */ { "xds", no_argument, NULL, 'x' }, /* From ntsc-cc.c. */ { "cc-file", required_argument, NULL, 'C' }, /* 'E' - video elementary stream? */ { "list", no_argument, NULL, 'L' }, { "minicut", required_argument, NULL, 'M' }, { "pes", no_argument, NULL, 'P' }, { "stream-time", optional_argument, NULL, 'S' }, { "ts", no_argument, NULL, 'T' }, /* From ntsc-cc.c. */ { "xds-file", required_argument, NULL, 'X' }, /* Test options, may change. */ { "atsc", no_argument, NULL, 301 }, { "dvb-t", no_argument, NULL, 302 }, { "ts-all-tap", required_argument, NULL, 303 }, { "ts-tap", required_argument, NULL, 304 }, { "video-all-tap", required_argument, NULL, 305 }, { "video-tap", required_argument, NULL, 306 }, { "cc-data-tap", required_argument, NULL, 308 }, { "debug", required_argument, NULL, 309 }, { "mtest", no_argument, NULL, 310 }, { "cc-data", no_argument, NULL, 311 }, { "es", no_argument, NULL, 312 }, { 0, 0, 0, 0 } }; #else # define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; static void list_programs (void) { const unsigned int ll = 1; /* log level */ unsigned int i; for (i = 0; i < n_programs; ++i) { struct program *pr; unsigned int j; pr = &program_table[i]; log (ll, "Station %u: '%s'\n", station_num (pr), pr->option_station_name); for (j = 0; j < 10; ++j) { const char *stream_name [10] = { "NTSC CC1", "NTSC CC2", "NTSC CC3", "NTSC CC4", "NTSC T1", "NTSC T2", "NTSC T3", "NTSC T4", "ATSC S1", "ATSC S2" }; const char *file_name; if (0 == (pr->cr.option_caption_mask & (1 << j))) continue; file_name = pr->cr.option_caption_file_name[j]; if (NULL != file_name) { log (ll, " %s -> '%s'\n", stream_name[j], file_name); } else if (NULL != pr->option_minicut_dir_name) { log (ll, " %s -> '%s/" "YYYYMMDDHH0000/" "YYYYMMDDHHMM00%s.txt'\n", stream_name[j], pr->option_minicut_dir_name, cr_file_name_suffix[j]); } } if (pr->cr.usexds) { log (ll, " XDS -> '%s'\n", pr->cr.option_xds_output_file_name); } if (NULL != pr->option_minicut_dir_name) { log (ll, " TS -> '%s/" "YYYYMMDDHH0000/" "YYYYMMDDHHMM00.ts'\n", pr->option_minicut_dir_name); } if (NULL != pr->vesd.video_es_tap_fp) { const char *name; name = pr->vesd.option_video_es_all_tap_file_name; if (NULL == name) name = pr->vesd.option_video_es_tap_file_name; log (ll, " V-ES -> '%s'\n", name); } if (NULL != pr->vr.aesp.audio_es_tap_fp) { log (ll, " A-ES -> '%s'\n", pr->vr.aesp.option_audio_es_tap_file_name); } if (NULL != pr->cr.ccd.cc_data_tap_fp) { log (ll, " cc_data -> '%s'\n", pr->cr.ccd.option_cc_data_tap_file_name); } } } static void cr_open_xds_output_file (struct caption_recorder *cr) { cr->xds_fp = open_output_file (cr->option_xds_output_file_name); } static void cr_open_caption_output_files (struct caption_recorder *cr) { unsigned int i; for (i = 0; i < 10; ++i) { const char *name; name = cr->option_caption_file_name[i]; if (cr->option_caption_mask & (1 << i) && NULL != name) { cr->caption_fp[i] = open_output_file (name); } } } static void open_output_files (void) { unsigned int i; for (i = 0; i < n_programs; ++i) { struct program *pr; pr = &program_table[i]; if (pr->cr.usecc) cr_open_caption_output_files (&pr->cr); if (pr->cr.usexds) cr_open_xds_output_file (&pr->cr); } } static void look_up_station_names (void) { unsigned int i; read_channel_conf (); for (i = 0; i < n_programs; ++i) { struct program *pr; struct station *st; pr = &program_table[i]; st = find_station (pr->option_station_name); if (NULL == st) { error_exit ("Station '%s' is unknown. List " "all stations with the -L option.\n", pr->option_station_name); } if (NULL == station) { station = st; } else if (!same_transponder (st, station)) { error_exit ("To receive multiple programs the " "stations must share one " "transponder frequency.\n"); } pr->tsd.pid[0] = st->video_pid; pr->tsd.pid[1] = st->audio_pid; assert (st->video_pid <= N_ELEMENTS (pid_map)); assert (st->audio_pid <= N_ELEMENTS (pid_map)); pid_map[st->video_pid].program = i; pid_map[st->audio_pid].program = i; } } static void finish_program_setup (struct program * pr, vbi_bool have_cc_filter_option, vbi_bool have_xds_filter_option) { if (NULL != pr->option_minicut_dir_name) { pr->cr.usecc = TRUE; if (0 == pr->cr.option_caption_mask) pr->cr.option_caption_mask = 0x30F; } else { unsigned int i; if (!(pr->cr.usecc | pr->cr.usexds)) { error_exit ("Please give option -c or -x, " "or -h for help.\n"); } if (pr->cr.usecc && !have_cc_filter_option) pr->cr.option_caption_mask = 0x001; for (i = 0; i < 10; ++i) { if (0 != (pr->cr.option_caption_mask & (1 << i)) && NULL == pr->cr.option_caption_file_name[i]) { pr->cr.option_caption_file_name[i] = "-"; } } } if (pr->cr.usexds && !have_xds_filter_option) xds_filter_option (&pr->cr, "all"); if (NULL != pr->vesd.option_video_es_all_tap_file_name) { pr->vesd.video_es_tap_fp = open_output_file (pr->vesd.option_video_es_all_tap_file_name); } else if (NULL != pr->vesd.option_video_es_tap_file_name) { pr->vesd.video_es_tap_fp = open_output_file (pr->vesd.option_video_es_tap_file_name); } if (NULL != pr->vr.aesp.option_audio_es_tap_file_name) { pr->vr.aesp.audio_es_tap_fp = open_output_file (pr->vr.aesp.option_audio_es_tap_file_name); } if (NULL != pr->cr.ccd.option_cc_data_tap_file_name) { pr->cr.ccd.cc_data_tap_fp = open_output_file (pr->cr.ccd.option_cc_data_tap_file_name); } } static unsigned int uint_option (const char * option_name, const char * optarg) { const char *s; char *end; unsigned long ul; s = optarg; while (isspace (*s)) ++s; if (!isdigit (*s)) goto failed; ul = strtoul (optarg, &end, 0); while (isspace (*end)) ++end; if (0 == *end && ul < UINT_MAX) return ul; failed: error_exit ("Invalid %s '%s'.\n", option_name, optarg); return 0; } static void format_option (struct caption_recorder *cr, const char * optarg) { static const struct { const char * name; enum caption_format format; } formats [] = { { "plain", FORMAT_PLAIN }, { "vt100", FORMAT_VT100 }, { "ntsc-cc", FORMAT_NTSC_CC } }; unsigned int i; for (i = 0; i < N_ELEMENTS (formats); ++i) { if (NULL != optarg && 0 == strcmp (optarg, formats[i].name)) { cr->option_caption_format = formats[i].format; break; } } if (i >= N_ELEMENTS (formats)) { error_exit ("Invalid caption format '%s'. " "Try 'plain', 'vt100' or 'ntsc-cc'.\n", optarg); } if (FORMAT_PLAIN == cr->option_caption_format) { cr->xds_info_prefix = "% "; cr->xds_info_suffix = "\n"; } else { cr->xds_info_prefix = "\33[33m% "; cr->xds_info_suffix = "\33[0m\n"; } } static void debug_option (const char * optarg) { static const struct { const char * name; unsigned int flag; } flags [] = { { "all", -1 }, { "ccdata", DEBUG_CC_DATA }, { "ccdec", DEBUG_CC_DECODER }, { "ccf1", DEBUG_CC_F1 }, { "ccf2", DEBUG_CC_F2 }, { "conf", DEBUG_CONFIG }, { "dtvccp", DEBUG_DTVCC_PACKET }, { "dtvccpc", DEBUG_DTVCC_PUT_CHAR }, { "dtvccse", DEBUG_DTVCC_SE }, { "dtvccsev", DEBUG_DTVCC_STREAM_EVENT }, { "vesdcc", DEBUG_VESD_CC_DATA }, { "vesdpe", DEBUG_VESD_PIC_EXT }, { "vesdph", DEBUG_VESD_PIC_HDR }, { "vesdpesp", DEBUG_VESD_PES_PACKET }, { "vesdsc", DEBUG_VESD_START_CODE }, { "vesdud", DEBUG_VESD_USER_DATA }, }; const char *s; if (NULL == optarg) { option_debug = -1; return; } else if (0 == strcmp (optarg, "help")) { unsigned int i; printf ("Debugging switches:\n"); for (i = 0; i < N_ELEMENTS (flags); ++i) { printf (" %s\n", flags[i].name); } exit (EXIT_SUCCESS); } s = optarg; while (isspace (*s)) ++s; while (0 != *s) { unsigned int i; for (i = 0; i < N_ELEMENTS (flags); ++i) { const char *t; const char *u; t = s; u = flags[i].name; for (;;) { if (0 == *t || ',' == *t || ' ' == *t) { if (0 != *u) break; option_debug |= flags[i].flag; s = t; goto next_item; } else if ('-' == *t || '_' == *t) { ++t; continue; } else if (*t++ != *u++) { break; } } } error_exit ("Invalid debugging switch '%s'. " "Try --debug help.\n", optarg); next_item: while (',' == *s || isspace (*s)) ++s; } } static struct program * add_program (void) { struct program *pr; if (n_programs >= N_ELEMENTS (program_table)) { error_exit ("Sorry, too many programs.\n"); } pr = &program_table[n_programs++]; init_program (pr); return pr; } static void parse_time (struct timeval * tv, const char * s) { struct tm tm; time_t sec; tv->tv_sec = 0; tv->tv_usec = 0; if (NULL == s) return; while (isspace (*s)) ++s; if ('0' == s[0] && 0 == s[1]) return; #ifdef HAVE_STRPTIME if (NULL == strptime (s, "%Y%m%d%H%M%S", &tm)) goto invalid; #else goto dangit; #endif sec = timegm (&tm); tv->tv_sec = sec; if (sec >= 0) return; invalid: error_exit ("Invalid date format '%s'\n", s); dangit: error_exit ("Cannot parse dates, sorry.\n"); } static void parse_args (int argc, char ** argv) { struct program *pr; vbi_bool have_cc_filter_option; vbi_bool have_xds_filter_option; unsigned int n_program_options; option_source = SOURCE_DVB_DEVICE; option_dvb_type = -1; /* query device */ option_dvb_adapter_num = 0; option_dvb_frontend_id = 0; option_dvb_demux_id = 0; option_dvb_dvr_id = 0; option_verbosity = 1; pr = add_program (); format_option (&pr->cr, "ntsc-cc"); have_xds_filter_option = FALSE; have_cc_filter_option = FALSE; n_program_options = 0; for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { const char *name; unsigned int i; long ch; case '?': case 'h': usage (stdout); exit (EXIT_SUCCESS); case '0' ... '9': assert (NULL != optarg); if ('0' == c) i = 9; else i = c - '1'; pr->cr.option_caption_file_name[i] = optarg; pr->cr.option_caption_mask |= 1 << i; have_cc_filter_option = TRUE; ++n_program_options; pr->cr.usecc = 1; break; case 'a': assert (NULL != optarg); option_dvb_adapter_num = uint_option ("DVB adapter number", optarg); break; case 'b': pr->cr.usewebtv = 0; /* sic, compatibility */ ++n_program_options; break; case 'c': pr->cr.usecc = 1; ++n_program_options; break; case 'd': assert (NULL != optarg); option_dvb_demux_id = uint_option ("DVB demux device number", optarg); break; case 'e': assert (NULL != optarg); option_channel_conf_file_name = optarg; break; case 'f': pr->cr.usexds = TRUE; xds_filter_option (&pr->cr, optarg); have_xds_filter_option = TRUE; ++n_program_options; break; case 'i': assert (NULL != optarg); option_dvb_frontend_id = uint_option ("DVB frontend device number", optarg); break; case 'j': assert (NULL != optarg); format_option (&pr->cr, optarg); ++n_program_options; break; case 'l': assert (NULL != optarg); ch = strtol (optarg, NULL, 0); if (ch < 1 || ch > 10) { error_exit ("Invalid caption stream " "number %ld. The valid " "range is 1 ... 10.\n", ch); } pr->cr.option_caption_mask |= 1 << (ch - 1); have_cc_filter_option = TRUE; ++n_program_options; pr->cr.usecc = 1; break; case 'm': pr->cr.option_caption_timestamps = TRUE; ++n_program_options; break; case '\1': /* not an option */ /* NB this is a GNU extension, hence the -n option. */ case 'n': assert (NULL != optarg); name = optarg; if (NULL == pr->option_station_name) { pr->option_station_name = name; if (0 == n_program_options) break; name = NULL; } finish_program_setup (pr, have_cc_filter_option, have_xds_filter_option); pr = add_program (); pr->option_station_name = name; have_xds_filter_option = FALSE; have_cc_filter_option = FALSE; n_program_options = 0; break; case 'p': format_option (&pr->cr, "plain"); ++n_program_options; break; case 'q': option_verbosity = 0; break; case 'r': assert (NULL != optarg); option_dvb_dvr_id = uint_option ("DVB DVR device number", optarg); break; case 's': pr->cr.usesen = 1; ++n_program_options; break; case 'u': pr->cr.option_ucla_prefix = TRUE; ++n_program_options; break; case 'v': ++option_verbosity; break; case 'x': pr->cr.usexds = 1; ++n_program_options; break; case 'C': assert (NULL != optarg); for (i = 0; i < 10; ++i) { pr->cr.option_caption_file_name[i] = optarg; } pr->cr.usecc = 1; ++n_program_options; break; case 'L': read_channel_conf (); list_stations (); exit (EXIT_SUCCESS); case 'M': assert (NULL != optarg); pr->option_minicut_dir_name = optarg; ++n_program_options; break; case 'P': option_source = SOURCE_STDIN_PES; break; case 'S': pr->cr.option_caption_timestamps = TRUE; pr->cr.option_timestamps_rel = TRUE; parse_time (&pr->rel, optarg); ++n_program_options; break; case 'T': option_source = SOURCE_STDIN_TS; break; case 'X': assert (NULL != optarg); pr->cr.option_xds_output_file_name = optarg; ++n_program_options; break; /* Test options. */ case 301: option_dvb_type = FE_ATSC; break; case 302: option_dvb_type = FE_OFDM; break; case 303: assert (NULL != optarg); option_ts_all_tap_file_name = optarg; break; case 304: assert (NULL != optarg); option_ts_tap_file_name = optarg; break; case 305: assert (NULL != optarg); pr->vesd.option_video_es_all_tap_file_name = optarg; ++n_program_options; break; case 306: assert (NULL != optarg); pr->vesd.option_video_es_tap_file_name = optarg; ++n_program_options; break; case 307: assert (NULL != optarg); pr->vr.aesp.option_audio_es_tap_file_name = optarg; ++n_program_options; break; case 308: assert (NULL != optarg); pr->cr.ccd.option_cc_data_tap_file_name = optarg; ++n_program_options; break; case 309: assert (NULL != optarg); debug_option (optarg); break; case 310: option_minicut_test = TRUE; break; case 311: option_source = SOURCE_STDIN_CC_DATA; break; case 312: option_source = SOURCE_STDIN_VIDEO_ES; break; default: usage (stderr); exit (EXIT_FAILURE); } } if (pr->cr.usesen && (pr->cr.option_caption_timestamps || NULL != pr->option_minicut_dir_name)) { error_exit ("Sorry, option -s does not " "combine with -m or -M.\n"); } if (NULL == pr->option_station_name) { if (0 == n_program_options) { --n_programs; if (SOURCE_DVB_DEVICE == option_source || SOURCE_STDIN_TS == option_source) { if (0 == n_programs) goto no_station; } } else { if (SOURCE_DVB_DEVICE == option_source || SOURCE_STDIN_TS == option_source) { if (optind == argc) goto no_station; pr->option_station_name = argv[optind]; } finish_program_setup (pr, have_cc_filter_option, have_xds_filter_option); } } else { finish_program_setup (pr, have_cc_filter_option, have_xds_filter_option); } if (SOURCE_DVB_DEVICE == option_source || SOURCE_STDIN_TS == option_source) { look_up_station_names (); } else { if (n_programs > 1) { goto too_many_stations; } else if (NULL != program_table[0].option_station_name) { log (1, "Ignoring station name.\n"); } } return; no_station: error_exit ("Please give a station name. " "List all stations with the -L option.\n"); too_many_stations: error_exit ("Sorry, only one program can be decoded with " "the --cc-data, --es or\n" "--pes option.\n"); } int main (int argc, char ** argv) { my_name = argv[0]; setlocale (LC_ALL, ""); locale_codeset = vbi_locale_codeset (), /* Don't swap out any code or data pages (if we have the privilege). If the capture thread is delayed we may lose packets. Errors ignored. */ mlockall (MCL_CURRENT | MCL_FUTURE); memset (pid_map, -1, sizeof (pid_map)); parse_args (argc, argv); init_demux_state (); if (NULL != option_ts_all_tap_file_name) { ts_tap_fp = open_output_file (option_ts_all_tap_file_name); } else if (NULL != option_ts_tap_file_name) { ts_tap_fp = open_output_file (option_ts_tap_file_name); } switch (option_source) { case SOURCE_DVB_DEVICE: open_device (); open_output_files (); if (n_programs > 1) list_programs (); demux_thread (/* arg */ NULL); close_device (); break; case SOURCE_STDIN_TS: open_output_files (); if (n_programs > 1) list_programs (); ts_test_loop ("-"); break; case SOURCE_STDIN_PES: error_exit ("Sorry, the --pes option " "is not implemented yet.\n"); break; case SOURCE_STDIN_VIDEO_ES: /* For tests only. */ open_output_files (); video_es_test_loop (&program_table[0], "-"); break; case SOURCE_STDIN_CC_DATA: /* For tests only. */ open_output_files (); cc_data_test_loop (&program_table[0], "-"); break; } destroy_demux_state (); exit (EXIT_SUCCESS); } zvbi-0.2.44/contrib/dvbsubs.c000066400000000000000000001734531476363111200160500ustar00rootroot00000000000000/* dvbsubs - a program for decoding DVB subtitles (ETS 300 743) File: dvbsubs.c Old code (C) 2002 Dave Chapman New code (C) 2009 Michael H. Schimek This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Or, point your browser to http://www.gnu.org/copyleft/gpl.html */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dvbsubs.h" #if __GNUC__ < 3 # define likely(expr) (expr) # define unlikely(expr) (expr) #else # define likely(expr) __builtin_expect(expr, 1) # define unlikely(expr) __builtin_expect(expr, 0) #endif #undef TRUE #define TRUE 1 #undef FALSE #define FALSE 0 /* These should be defined in inttypes.h. */ #ifndef PRId64 # define PRId64 "lld" #endif #ifndef PRIu64 # define PRIu64 "llu" #endif #ifndef PRIx64 # define PRIx64 "llx" #endif #define N_ELEMENTS(array) (sizeof (array) / sizeof ((array)[0])) page_t page; region_t regions[MAX_REGIONS]; textsub_t textsub; int y=0; int x=0; int fd_osd; int num_windows=1; int acquired=0; struct timeval start_tv; unsigned int curr_obj; unsigned int curr_reg[64]; unsigned char white[4]={255,255,255,0xff}; unsigned char green[4]={0,255,0,0xdf} ; unsigned char blue[4]={0,0,255,0xbf} ; unsigned char yellow[4]={255,255,0,0xbf} ; unsigned char black[4]={0,0,0,0xff} ; unsigned char red[4]={255,0,0,0xbf} ; unsigned char magenta[4]={255,0,255,0xff}; unsigned char othercol[4]={0,255,255,0xff}; unsigned char transp[4]={0,255,0,0}; //unsigned char buf[100000]; uint8_t * buf;; int i=0; int nibble_flag=0; int in_scanline=0; int sub_idx=0; FILE * outfile; static void output_textsub(FILE * outf) { int temp, h, m, s, ms; int i; temp = textsub.start_pts; h = temp / 3600000; temp %= 3600000; m = temp / 60000; temp %= 60000; s = temp / 1000; temp %= 1000; ms = temp; fprintf(outf, "%d\n%02d:%02d:%02d,%03d --> ", ++sub_idx, h, m, s, ms); temp = textsub.end_pts; h = temp / 3600000; temp %= 3600000; m = temp / 60000; temp %= 60000; s = temp / 1000; temp %= 1000; ms = temp; fprintf(outf, "%02d:%02d:%02d,%03d\n", h, m, s, ms); for (i=0; i 0) fprintf(outf,"%s", textsub.regions[i]); } fprintf(outf, "\n"); fflush(outf); } static void process_gocr_output(const char *const fname, int region) { FILE *file; int read; file = fopen(fname, "r"); if (file == NULL) { perror("fopen failed"); return; } read = fread(textsub.regions[region],sizeof(char), 64, file); if (read <= 0) perror("error reading"); textsub.regions[region][read] = '\0'; if (textsub.regions[region][0] == ',' && textsub.regions[region][1] == '\n') { char tmp[66]; char * eol; strcpy(tmp,textsub.regions[region]+2); eol = rindex(tmp,'\n'); strcpy(eol,",\n"); strcpy(textsub.regions[region], tmp); } fclose(file); } static void output_pgm(FILE *f, int r) { int x, y; fprintf(f, "P5\n" "%d %d\n" "255\n", regions[r].width, regions[r].height); for (y = 0; y < regions[r].height; ++y) { for (x = 0; x < regions[r].width; ++x) { int res = 0; int pix = regions[r].img[y*regions[r].width+x]; if (regions[r].alpha[pix]) res = regions[r].palette[pix] * regions[r].alpha[pix]; else res = 0; res = (65535 - res) >> 8; putc(res&0xff, f); } } putc('\n', f); } #define GOCR_PROGRAM "gocr" static void run_ocr(int region, unsigned long long pts) { FILE *f; char inbuf[128]; char outbuf[128]; char cmd[512]; int cmdres; //const char *const tmpfname = tmpnam(NULL); sprintf(inbuf, "subtitle-%llu-%d.pgm", pts / 90, region); sprintf(outbuf, "tmp.txt"); f = fopen(inbuf, "w"); output_pgm(f, region); fclose(f); //sprintf(cmd, GOCR_PROGRAM" -v 1 -s 7 -d 0 -m 130 -m 256 -m 32 -i %s -o %s", inbuf, outbuf); sprintf(cmd, GOCR_PROGRAM" -s 8 -d 0 -m 130 -i %s -o %s", inbuf, outbuf); cmdres = system(cmd); if (cmdres < 0) { perror("system failed"); exit(EXIT_FAILURE); } else if (cmdres) { fprintf(stderr, GOCR_PROGRAM" returned %d\n", cmdres); exit(cmdres); } process_gocr_output(outbuf,region); unlink(inbuf); unlink(outbuf); } static void init_data() { int i; for (i=0;i= 0) && (y < regions[r].height)) { i=(y*regions[r].width)+x; regions[r].img[i]=pixel; } else { fprintf(stderr,"plot out of region: x=%d, y=%d - r=%d, height=%d\n",x,y,r,regions[r].height); } } static void plot(int r,int run_length, unsigned char pixel) { int x2=x+run_length; // fprintf(stderr,"plot: x=%d,y=%d,length=%d,pixel=%d\n",x,y,run_length,pixel); while (x < x2) { do_plot(r,x,y,pixel); //fprintf(stderr,"%s",pixel==0?" ":pixel==5?"..":pixel==6?"oo":pixel==7?"xx":pixel==8?"OO":"XX"); x++; } // x+=run_length; } static unsigned char next_nibble () { unsigned char x; if (nibble_flag==0) { x=(buf[i]&0xf0)>>4; nibble_flag=1; } else { x=(buf[i++]&0x0f); nibble_flag=0; } return(x); } /* function taken from "dvd2sub.c" in the svcdsubs packages in the vcdimager contribs directory. Author unknown, but released under GPL2. */ /*void set_palette(int region_id,int id,int Y_value, int Cr_value, int Cb_value, int T_value) { int Y,Cr,Cb,R,G,B; unsigned char colour[4]; Y=Y_value; Cr=Cr_value; Cb=Cb_value; B = 1.164*(Y - 16) + 2.018*(Cb - 128); G = 1.164*(Y - 16) - 0.813*(Cr - 128) - 0.391*(Cb - 128); R = 1.164*(Y - 16) + 1.596*(Cr - 128); if (B<0) B=0; if (B>255) B=255; if (G<0) G=0; if (G>255) G=255; if (R<0) R=0; if (R>255) R=255; colour[0]=R; colour[1]=B; colour[2]=G; if (Y==0) { colour[3]=0; } else { colour[3]=255; } //fprintf(stderr,"setting palette: region=%d,id=%d, R=%d,G=%d,B=%d,T=%d\n",region_id,id,R,G,B,T_value); } */ static inline void set_palette(int region_id,int id,int Y_value, int Cr_value, int Cb_value, int T_value) { Cr_value = Cr_value; /* unused */ Cb_value = Cb_value; regions[region_id].palette[id] = Y_value; if (Y_value == 0) T_value = 0; regions[region_id].alpha[id] = T_value; //fprintf(stderr,"setting palette: region=%d,id=%d, val=%d,alpha=%d\n",region_id,id,Y_value,T_value); } static void decode_4bit_pixel_code_string(int r, int object_id, int ofs, int n) { int next_bits, switch_1, switch_2, switch_3, run_length, pixel_code=0; int bits; unsigned int data; int j; object_id = object_id; /* unused */ ofs = ofs; if (in_scanline==0) { // printf("\n"); //fprintf(stderr,"\n"); in_scanline=1; } nibble_flag=0; j=i+n; while(i < j) { // printf("start of loop, i=%d, nibble-flag=%d\n",i,nibble_flag); // printf("buf=%02x %02x %02x %02x\n",buf[i],buf[i+1],buf[i+2],buf[i+3]); pixel_code = 0; bits=0; next_bits=next_nibble(); if (next_bits!=0) { pixel_code=next_bits; // printf("\n",pixel_code); plot(r,1,pixel_code); bits+=4; } else { bits+=4; data=next_nibble(); switch_1=(data&0x08)>>3; bits++; if (switch_1==0) { run_length=(data&0x07); bits+=3; if (run_length!=0) { // printf("\n",run_length+2); plot(r,run_length+2,pixel_code); } else { // printf("end_of_string - run_length=%d\n",run_length); break; } } else { switch_2=(data&0x04)>>2; bits++; if (switch_2==0) { run_length=(data&0x03); bits+=2; pixel_code=next_nibble(); bits+=4; //printf("\n",run_length+4,pixel_code); plot(r,run_length+4,pixel_code); } else { switch_3=(data&0x03); bits+=2; switch (switch_3) { case 0: // printf("\n"); plot(r,1,pixel_code); break; case 1: // printf("\n"); plot(r,2,pixel_code); break; case 2: run_length=next_nibble(); bits+=4; pixel_code=next_nibble(); bits+=4; // printf("\n",run_length+9,pixel_code); plot(r,run_length+9,pixel_code); break; case 3: run_length=next_nibble(); run_length=(run_length<<4)|next_nibble(); bits+=8; pixel_code=next_nibble(); bits+=4; // printf("\n",run_length+25,pixel_code); plot(r,run_length+25,pixel_code); } } } } // printf("used %d bits\n",bits); } if (nibble_flag==1) { i++; nibble_flag=0; } } static void process_pixel_data_sub_block(int r, int o, int ofs, int n) { int data_type; int j; j=i+n; x=(regions[r].object_pos[o])>>16; y=((regions[r].object_pos[o])&0xffff)+ofs; // fprintf(stderr,"process_pixel_data_sub_block: r=%d, x=%d, y=%d\n",r,x,y); // printf("process_pixel_data: %02x %02x %02x %02x %02x %02x\n",buf[i],buf[i+1],buf[i+2],buf[i+3],buf[i+4],buf[i+5]); while (i < j) { data_type=buf[i++]; // printf("%02x\n",data_type); switch(data_type) { case 0x11: decode_4bit_pixel_code_string(r,o,ofs,n-1); break; case 0xf0: // printf("\n"); in_scanline=0; x=(regions[r].object_pos[o])>>16; y+=2; break; default: fprintf(stderr,"unimplemented data_type %02x in pixel_data_sub_block\n",data_type); } } i=j; } static int process_page_composition_segment() { int page_id, segment_length, page_time_out, page_version_number, page_state; int region_id,region_x,region_y; int j; page_id=(buf[i]<<8)|buf[i+1]; i+=2; segment_length=(buf[i]<<8)|buf[i+1]; i+=2; j=i+segment_length; page_time_out=buf[i++]; page_version_number=(buf[i]&0xf0)>>4; page_state=(buf[i]&0x0c)>>2; i++; // printf("\n",page_id); // printf("%d\n",page_time_out); // printf("%d\n",page_version_number); // printf(""); //fprintf(stderr,"page_state=%d (",page_state); switch(page_state) { case 0: //fprintf(stderr,"normal_case)\n"); break ; case 1: //fprintf(stderr,"acquisition_point)\n"); break ; case 2: //fprintf(stderr,"mode_change)\n"); break ; case 3: //fprintf(stderr,"reserved)\n"); break ; } // printf("\n"); if ((acquired==0) && (page_state!=2) && (page_state!=1)) { //fprintf(stderr,"waiting for mode_change\n"); return 1; } else { //fprintf(stderr,"acquired=1\n"); acquired=1; } // printf("\n"); // IF the packet contains no data (i.e. is used to clear a // previous subtitle), do nothing if (i>=j) { //fprintf(stderr,"Empty sub, return\n"); return 1; } while (i\n"); // printf("\n"); return 0; } static void process_region_composition_segment() { int page_id, segment_length, region_id, region_version_number, region_fill_flag, region_width, region_height, region_level_of_compatibility, region_depth, CLUT_id, region_8_bit_pixel_code, region_4_bit_pixel_code, region_2_bit_pixel_code; int object_id, object_type, object_provider_flag, object_x, object_y, foreground_pixel_code, background_pixel_code; int j; int o; page_id=(buf[i]<<8)|buf[i+1]; i+=2; segment_length=(buf[i]<<8)|buf[i+1]; i+=2; j=i+segment_length; region_id=buf[i++]; region_version_number=(buf[i]&0xf0)>>4; region_fill_flag=(buf[i]&0x08)>>3; i++; region_width=(buf[i]<<8)|buf[i+1]; i+=2; region_height=(buf[i]<<8)|buf[i+1]; i+=2; region_level_of_compatibility=(buf[i]&0xe0)>>5; region_depth=(buf[i]&0x1c)>>2; i++; CLUT_id=buf[i++]; region_8_bit_pixel_code=buf[i++]; region_4_bit_pixel_code=(buf[i]&0xf0)>>4; region_2_bit_pixel_code=(buf[i]&0x0c)>>2; i++; if (regions[region_id].win < 0) { // If the region doesn't exist, then open it. create_region(region_id,region_width,region_height,region_depth); regions[region_id].CLUT_id=CLUT_id; } if (region_fill_flag==1) { //fprintf(stderr,"filling region %d with %d\n",region_id,region_4_bit_pixel_code); memset(regions[region_id].img,region_4_bit_pixel_code,sizeof(regions[region_id].img)); } // printf("\n",page_id,region_id); // printf("%d\n",region_version_number); // printf("%d\n",region_fill_flag); // printf("%d\n",region_width); // printf("%d\n",region_height); // printf("%d\n",region_level_of_compatibility); // printf("%d\n",region_depth); // printf("%d\n",CLUT_id); // printf("%d\n",region_8_bit_pixel_code); // printf("%d\n",region_4_bit_pixel_code); // printf("%d\n",region_2_bit_pixel_code); regions[region_id].objects_start=i; regions[region_id].objects_end=j; for (o=0;o<65536;o++) { regions[region_id].object_pos[o]=0xffffffff; } // printf("\n"); while (i < j) { object_id=(buf[i]<<8)|buf[i+1]; i+=2; object_type=(buf[i]&0xc0)>>6; object_provider_flag=(buf[i]&0x30)>>4; object_x=((buf[i]&0x0f)<<8)|buf[i+1]; i+=2; object_y=((buf[i]&0x0f)<<8)|buf[i+1]; i+=2; regions[region_id].object_pos[object_id]=(object_x<<16)|object_y; // printf("\n",object_id,object_type); // printf("%d\n",object_provider_flag); // printf("%d\n",object_x); // printf("%d\n",object_y); if ((object_type==0x01) || (object_type==0x02)) { foreground_pixel_code=buf[i++]; background_pixel_code=buf[i++]; // printf("%d\n",foreground_pixel_code); // printf("%d\n",background_pixel_code); } // printf("\n"); } // printf("\n"); // printf("\n"); } static void process_CLUT_definition_segment() { int page_id, segment_length, CLUT_id, CLUT_version_number; int CLUT_entry_id, CLUT_flag_8_bit, CLUT_flag_4_bit, CLUT_flag_2_bit, full_range_flag, Y_value, Cr_value, Cb_value, T_value; int j; int r; page_id=(buf[i]<<8)|buf[i+1]; i+=2; segment_length=(buf[i]<<8)|buf[i+1]; i+=2; j=i+segment_length; CLUT_id=buf[i++]; CLUT_version_number=(buf[i]&0xf0)>>4; i++; // printf("\n",page_id,CLUT_id); // printf("%d\n",CLUT_version_number); // printf("\n"); while (i < j) { CLUT_entry_id=buf[i++]; // printf("\n",CLUT_entry_id); CLUT_flag_2_bit=(buf[i]&0x80)>>7; CLUT_flag_4_bit=(buf[i]&0x40)>>6; CLUT_flag_8_bit=(buf[i]&0x20)>>5; full_range_flag=buf[i]&1; i++; // printf("%d\n",CLUT_flag_2_bit); // printf("%d\n",CLUT_flag_4_bit); // printf("%d\n",CLUT_flag_8_bit); // printf("%d\n",full_range_flag); if (full_range_flag==1) { Y_value=buf[i++]; Cr_value=buf[i++]; Cb_value=buf[i++]; T_value=buf[i++]; } else { Y_value=(buf[i]&0xfc)>>2; Cr_value=(buf[i]&0x2<<2)|((buf[i+1]&0xc0)>>6); Cb_value=(buf[i+1]&0x2c)>>2; T_value=buf[i+1]&2; i+=2; } // printf("%d\n",Y_value); // printf("%d\n",Cr_value); // printf("%d\n",Cb_value); // printf("%d\n",T_value); // printf("\n"); // Apply CLUT to every region it applies to. for (r=0;r= 0) { if (regions[r].CLUT_id==CLUT_id) { set_palette(r,CLUT_entry_id,Y_value,Cr_value,Cb_value,255-T_value); } } } } // printf("\n"); // printf("\n"); } static void process_object_data_segment() { int page_id, segment_length, object_id, object_version_number, object_coding_method, non_modifying_colour_flag; int top_field_data_block_length, bottom_field_data_block_length; int j; int old_i; int r; page_id=(buf[i]<<8)|buf[i+1]; i+=2; segment_length=(buf[i]<<8)|buf[i+1]; i+=2; j=i+segment_length; object_id=(buf[i]<<8)|buf[i+1]; i+=2; curr_obj=object_id; object_version_number=(buf[i]&0xf0)>>4; object_coding_method=(buf[i]&0x0c)>>2; non_modifying_colour_flag=(buf[i]&0x02)>>1; i++; // printf("\n",page_id,object_id); // printf("%d\n",object_version_number); // printf("%d\n",object_coding_method); // printf("%d\n",non_modifying_colour_flag); // fprintf(stderr,"decoding object %d\n",object_id); old_i=i; for (r=0;r= 0) { //fprintf(stderr,"testing region %d, object_pos=%08x\n",r,regions[r].object_pos[object_id]); if (regions[r].object_pos[object_id]!=0xffffffff) { //fprintf(stderr,"rendering object %d into region %d\n",object_id,r); i=old_i; if (object_coding_method==0) { top_field_data_block_length=(buf[i]<<8)|buf[i+1]; i+=2; bottom_field_data_block_length=(buf[i]<<8)|buf[i+1]; i+=2; process_pixel_data_sub_block(r,object_id,0,top_field_data_block_length); process_pixel_data_sub_block(r,object_id,1,bottom_field_data_block_length); } } } } // Data should be word-aligned, pass the next byte if necessary if (((old_i - i) & 0x1) == 0) i++; } static void process_pes_packet() { int n; unsigned long long PTS; unsigned char PTS_1; unsigned short PTS_2,PTS_3; double PTS_secs; int r; int segment_length, segment_type; int empty_sub = 0; init_data(); gettimeofday(&start_tv,NULL); // printf("\n"); i=6; i++; // Skip some boring PES flags if (buf[i]!=0x80) { fprintf(stdout,"UNEXPECTED PES HEADER: %02x\n",buf[i]); exit(-1); } i++; if (buf[i]!=5) { fprintf(stdout,"UNEXPECTED PES HEADER DATA LENGTH: %d\n",buf[i]); exit(-1); } i++; // Header data length PTS_1=(buf[i++]&0x0e)>>1; // 3 bits PTS_2=(buf[i]<<7)|((buf[i+1]&0xfe)>>1); // 15 bits i+=2; PTS_3=(buf[i]<<7)|((buf[i+1]&0xfe)>>1); // 15 bits i+=2; PTS=PTS_1; PTS=(PTS << 15)|PTS_2; PTS=(PTS << 15)|PTS_3; PTS_secs=(PTS/90000.0); // printf("\n",buf[i++],PTS_secs); i++; // printf("\n",buf[i++]); i++; while (buf[i]==0x0f) { /* SUBTITLING SEGMENT */ i++; // sync_byte segment_type=buf[i++]; /* SEGMENT_DATA_FIELD */ switch(segment_type) { case 0x10: empty_sub = process_page_composition_segment(); break; case 0x11: process_region_composition_segment(); break; case 0x12: process_CLUT_definition_segment(); break; case 0x13: process_object_data_segment(); break; default: segment_length=(buf[i+2]<<8)|buf[i+3]; i+=segment_length+4; // printf("SKIPPING segment %02x, length %d\n",segment_type,segment_length); } } // printf("\n"); // printf("\n"); // fprintf(stderr,"End of PES packet - time=%.2f\n",PTS_secs); /* if (empty_sub) */ { int i; if (textsub.start_pts < 0) /* return */; else { textsub.end_pts = PTS/90; output_textsub(outfile); textsub.end_pts = textsub.start_pts = -1; for(i=0; i= 0) { if (page.regions[r].is_visible) { //int xx,yy; //fprintf(stderr,"displaying region %d at %d,%d width=%d,height=%d PTS = %g\n", //r,page.regions[r].x,page.regions[r].y,regions[r].width,regions[r].height, PTS_secs); /*for(yy=0;yy 0) { fprintf(stderr,"%d regions visible - showing\n",n); } else { fprintf(stderr,"%d regions visible - hiding\n",n); }*/ } // if (acquired) { sleep(1); } } #define PID_MASK_HI 0x1F static uint16_t get_pid(uint8_t *pid) { uint16_t pp = 0; pp = (pid[0] & PID_MASK_HI) << 8; pp |= pid[1]; return pp; } /* From dvb-mpegtools ctools.c, (C) 2000-2002 Marcus Metzler, license GPLv2+. */ static ssize_t save_read(int fd, void *buf, size_t count) { ssize_t neof = 1; size_t re = 0; while(neof >= 0 && re < count){ neof = read(fd, (uint8_t *) buf + re, count - re); if (neof > 0) re += neof; else break; } if (neof < 0 && re == 0) return neof; else return re; } #define TS_SIZE 188 #define IN_SIZE TS_SIZE*10 static uint8_t * get_sub_packets(int fdin, uint16_t pids) { uint8_t buffer[IN_SIZE]; uint8_t mbuf[TS_SIZE]; uint8_t * packet = NULL; uint8_t * next_write = NULL; int packet_current_size = 0; int packet_size = 0; int i; int count = 1; uint16_t pid; // fprintf(stderr,"extract pid %d\n", pids); if ((count = save_read(fdin,mbuf,TS_SIZE))<0) perror("reading"); for ( i = 0; i < 188 ; i++){ if ( mbuf[i] == 0x47 ) break; } if ( i == 188){ fprintf(stderr,"Not a TS\n"); return NULL; } else { memcpy(buffer,mbuf+i,TS_SIZE-i); if ((count = save_read(fdin,mbuf,i))<0) perror("reading"); memcpy(buffer+TS_SIZE-i,mbuf,i); i = 188; } count = 1; while (count > 0){ if ((count = save_read(fdin,buffer+i,IN_SIZE-i)+i)<0) perror("reading"); for( i = 0; i < count; i+= TS_SIZE){ uint8_t off = 0; if ( count - i < TS_SIZE) break; pid = get_pid(buffer+i+1); if (!(buffer[3+i]&0x10)) // no payload? continue; if ( buffer[1+i]&0x80){ fprintf(stderr,"Error in TS for PID: %d\n", pid); } if (pid != pids) continue; if ( buffer[3+i] & 0x20) { // adaptation field? off = buffer[4+i] + 1; } if ( !packet && ! buffer[i+off+4] && ! buffer[i+off+5] && buffer[i+off+6] && buffer[i+off+7] == 0xbd) { packet_size = buffer[i+off+8]<<8 | buffer[i+off+9]; packet_size += 6; // for the prefix, the stream ID and the size field //fprintf(stderr,"Packet start, size = %d\n",packet_size); packet = (uint8_t *)malloc(packet_size); next_write = packet; packet_current_size = 0; } if (packet) { if (packet_current_size + TS_SIZE-4-off <= packet_size) { memcpy(next_write, buffer+4+off+i, TS_SIZE-4-off); next_write+=TS_SIZE-4-off; packet_current_size += TS_SIZE-4-off; } else { fprintf(stderr,"write beyond buffer limit?\n"); free(packet); packet = NULL; next_write = NULL; packet_current_size = 0; packet_size = 0; continue; } if (packet_current_size == packet_size) { // process packet //int j=0; buf = packet; /*for(j=0;j 0) { va_list ap; va_start (ap, templ); fprintf (stderr, "%s: ", my_name); vfprintf (stderr, templ, ap); if (print_errno) { fprintf (stderr, ": %s.\n", strerror (errno)); } va_end (ap); } exit (EXIT_FAILURE); } static void no_mem_exit (void) { error_exit ("Out of memory."); } void hex_dump (const uint8_t * buf, unsigned int n_bytes) { const unsigned int mod = 16; unsigned int i; for (i = 0; i < n_bytes; ++i) { fprintf (stderr, "%02x ", buf[i]); if ((mod - 1) == (i % mod)) { if (1) { unsigned int j; fputc (' ', stderr); for (j = 0; j < mod; ++j) { int c = buf[i - mod + 1 + j]; if ((c & 0x7F) < 0x20) c = '.'; fputc (c, stderr); } } fputc ('\n', stderr); } } if ((mod - 1) != (n_bytes % mod)) fputc ('\n', stderr); } #ifdef HAVE_POSIX_MEMALIGN /* posix_memalign() was introduced in POSIX 1003.1d and may not be available on all systems. */ static void * my_memalign (size_t boundary, size_t size) { void *p; int err; /* boundary must be a power of two. */ if (0 != (boundary & (boundary - 1))) return malloc (size); err = posix_memalign (&p, boundary, size); if (0 == err) return p; errno = err; return NULL; } #elif defined HAVE_MEMALIGN /* memalign() is a GNU extension. */ # define my_memalign memalign #else # define my_memalign(boundary, size) malloc (size) #endif static __inline__ unsigned int get16be (const uint8_t * s) { /* XXX Use movw & xchg if available. */ return s[0] * 256 + s[1]; } static __inline__ unsigned int get32be (const uint8_t * s) { /* XXX Use movl & bswap if available. */ return get16be (s + 0) * 65536 + get16be (s + 2); } struct bit_stream { const uint8_t * data; unsigned int pos; unsigned int end; jmp_buf exit; }; static uint8_t get_bits (struct bit_stream * bs, unsigned int n_bits) { unsigned int pos; unsigned int byte_pos; unsigned int bit_pos; unsigned int value; pos = bs->pos; if (unlikely (pos + n_bits > bs->end)) longjmp (bs->exit, 1); bs->pos = pos + n_bits; byte_pos = pos >> 3; bit_pos = pos & 7; /* assert (n_bits <= 8); */ if (bit_pos + n_bits > 8) { value = get16be (bs->data + byte_pos) << bit_pos; } else { value = bs->data[byte_pos] << (bit_pos + 8); } return (value & 0xFFFF) >> (16 - n_bits); } static __inline__ void realign_bit_stream (struct bit_stream * bs, unsigned int n_bits) { /* assert (is_power_of_two (n_bits)); */ bs->pos = (bs->pos + (n_bits - 1)) & ~(n_bits - 1); } static __inline__ bool init_bit_stream (struct bit_stream * bs, const uint8_t * s, const uint8_t * e) { bs->data = s; bs->pos = 0; bs->end = (e - s) * 8; return (0 == setjmp (bs->exit)); } #define FIELD_DUMP 1 #define begin() \ do { \ if (FIELD_DUMP) \ fprintf (stderr, "%s:\n", __FUNCTION__); \ } while (0) #define uimsbf bslbf #define bslbf(field, val) \ do { \ field = (val); \ if (FIELD_DUMP) \ fprintf (stderr, " " #field " = %u = 0x%x\n", \ (unsigned int) field, (unsigned int) field); \ } while (0) #define bslbf_1(field, val) \ do { \ field = (val); \ if (FIELD_DUMP) \ fprintf (stderr, " " #field " = %u\n", \ (unsigned int) field & 1); \ } while (0) #define bslbf_enum(field, val, names) \ do { \ field = (val); \ if (FIELD_DUMP) \ fprintf (stderr, " " #field " = %u (%s)\n", \ (unsigned int) field, names [field]); \ } while (0) /* EN 300 743 Section 7.2.4.2. See also Section 11, Table 14, 15, 16. */ static void eight_bit_pixel_code_string (struct bit_stream * bs, int r) { r = r; /* unused */ in_scanline=1; begin (); while (bs->pos < bs->end) { unsigned int run_length; unsigned int pixel_code; unsigned int switch_1; unsigned int n; run_length = 1; bslbf (pixel_code, get_bits (bs, 8)); if (0 == pixel_code) { n = get_bits (bs, 8); bslbf_1 (switch_1, n >> 7); uimsbf (run_length, switch_1 & 127); if ((int8_t) n >= 0) { /* 00000000 0LLLLLLL */ if (0 == run_length) return; run_length += 1; } else { /* 00000000 1LLLLLLL CCCCCCCC */ run_length += 3; bslbf (pixel_code, get_bits (bs, 8)); } } /* plot(r,run_length,pixel_code); */ } } static void four_bit_pixel_code_string (struct bit_stream * bs, int r) { r = r; /* unused */ in_scanline=1; begin (); while (bs->pos < bs->end) { unsigned int run_length; unsigned int pixel_code; run_length = 1; bslbf (pixel_code, get_bits (bs, 4)); if (0 == pixel_code) { uimsbf (run_length, get_bits (bs, 4)); switch (run_length) { case 0: /* 0000 0000 */ return; case 1 ... 7: /* 0000 0LLL */ run_length += 2; break; case 8 ... 11: /* 0000 10LL CCCC */ run_length += 4 - 8; bslbf (pixel_code, get_bits (bs, 4)); break; case 12: /* 0000 1100 */ case 13: /* 0000 1101 */ run_length += 1 - 12; break; case 14: /* 0000 1110 LLLL CCCC */ pixel_code = get_bits (bs, 8); uimsbf (run_length, pixel_code >> 4); run_length += 9; bslbf (pixel_code, pixel_code & 15); break; case 15: /* 0000 1111 LLLL LLLL CCCC */ uimsbf (run_length, get_bits (bs, 8)); run_length += 25; bslbf (pixel_code, get_bits (bs, 4)); break; } } /* plot(r,run_length,pixel_code); */ } } static void two_bit_pixel_code_string (struct bit_stream * bs, int r) { r = r; /* unused */ in_scanline=1; begin (); while (bs->pos < bs->end) { unsigned int run_length; unsigned int pixel_code; run_length = 1; bslbf (pixel_code, get_bits (bs, 2)); if (0 == pixel_code) { unsigned int switch_3; uimsbf (run_length, get_bits (bs, 2)); switch (run_length) { case 2 ... 3: /* 00 1L LL CC */ pixel_code = run_length * 16 + get_bits (bs, 4); uimsbf (run_length, (pixel_code >> 2) - 8); run_length += 3; bslbf (pixel_code, pixel_code & 3); break; case 1: /* 00 01 */ break; case 0: /* 00 00 ... */ bslbf (switch_3, get_bits (bs, 2)); switch (switch_3) { case 0: /* 00 00 00 */ return; case 1: /* 00 00 01 */ run_length = 2; break; case 2: /* 00 00 10 LL LL CC */ pixel_code = get_bits (bs, 6); uimsbf (run_length, pixel_code >> 2); run_length += 12; bslbf (pixel_code, pixel_code & 3); break; case 3: /* 00 00 11 LL LL LL LL CC */ uimsbf (run_length, get_bits (bs, 8)); run_length += 29; bslbf (pixel_code, get_bits (bs, 2)); break; } break; } } /* plot(r,run_length,pixel_code); */ } } /* EN 300 743 Section 7.2.4.1. */ static bool pixel_data_sub_block_loop (const uint8_t * s, const uint8_t * end, int r, int o, int ofs) { struct bit_stream bs; x=(regions[r].object_pos[o])>>16; y=((regions[r].object_pos[o])&0xffff)+ofs; // fprintf(stderr,"process_pixel_data_sub_block: r=%d, x=%d, y=%d\n",r,x,y); // printf("process_pixel_data: %02x %02x %02x %02x %02x %02x\n",s[0],s[1],s[2],s[3],s[4],s[5]); if (!init_bit_stream (&bs, s, end)) return FALSE; while (bs.pos < bs.end) { unsigned int data_type; begin (); bslbf (data_type, get_bits (&bs, 8)); switch (data_type) { case 0x10: if (0) { /* Not implemented yet. */ two_bit_pixel_code_string (&bs,r); realign_bit_stream (&bs, 8); } break; case 0x11: four_bit_pixel_code_string (&bs,r); realign_bit_stream (&bs, 8); break; case 0x12: if (0) { /* Not implemented yet. */ eight_bit_pixel_code_string (&bs,r); } break; case 0xf0: /* end of object line code */ in_scanline=0; x=(regions[r].object_pos[o])>>16; y+=2; ++s; break; case 0x20: /* 2 to 4-bit map-table */ case 0x21: /* 2 to 8-bit map-table */ case 0x22: /* 4 to 8-bit map-table */ default: fprintf(stderr,"unimplemented data_type %02x in pixel_data_sub_block\n",data_type); return TRUE; } } return (bs.pos == bs.end); } /* EN 300 743 Section 7.2.4. */ static bool object_data_segment (const uint8_t * s, const uint8_t * end) { static const char *object_coding_method_names [4] = { "coding of pixels", "coded as a string of characters", "reserved", "reserved" }; const uint8_t *old_s; unsigned int object_id; unsigned int object_version_number; unsigned int object_coding_method; unsigned int non_modifying_colour_flag; unsigned int top_field_data_block_length; unsigned int bottom_field_data_block_length; unsigned int total_length; int r; begin (); if (s + 8 >= end) return FALSE; bslbf (object_id, get16be (s + 6)); uimsbf (object_version_number, s[8] >> 4); bslbf_enum (object_coding_method, (s[8] >> 2) & 3, object_coding_method_names); bslbf_1 (non_modifying_colour_flag, s[8] & 2); /* reserved [1] */ curr_obj=object_id; switch (object_coding_method) { case 0: /* coding of pixels */ if (s + 12 >= end) return FALSE; uimsbf (top_field_data_block_length, get16be (s + 9)); uimsbf (bottom_field_data_block_length, get16be (s + 11)); total_length = 13 + top_field_data_block_length + bottom_field_data_block_length; /* 8_stuff_bits for 16 bit alignment. */ total_length += total_length & 1; if (s + total_length != end) return FALSE; old_s = s + 13; for (r=0;r= 0) { //fprintf(stderr,"testing region %d, object_pos=%08x\n",r,regions[r].object_pos[object_id]); if ((int) regions[r].object_pos[object_id]!=-1) { //fprintf(stderr,"rendering object %d into region %d\n",object_id,r); s = old_s; if (!pixel_data_sub_block_loop (s, s + top_field_data_block_length, r,object_id,0)) return FALSE; s += top_field_data_block_length; if (!pixel_data_sub_block_loop (s, s + bottom_field_data_block_length, r,object_id,1)) return FALSE; } } } /* FIXME: "if a segment carries no data for the bottom field, i.e. the bottom_field_data_block_length contains the value '0x0000', then the pixel-data_sub-block for the top field shall apply for the bottom field also." */ break; case 1: /* coded as a string of characters */ fprintf (stderr, "Whoops! Coding as characters " "not supported.\n"); break; case 2 ... 3: /* reserved */ break; } return TRUE; } /* EN 300 743 Section 7.2.3. */ static bool CLUT_definition_segment (const uint8_t * s, const uint8_t * end) { unsigned int CLUT_id; unsigned int CLUT_version_number; begin (); if (s + 7 >= end) return FALSE; bslbf (CLUT_id, s[6]); uimsbf (CLUT_version_number, s[7] >> 4); /* reserved [4] */ while (s + 11 < end) { unsigned int CLUT_entry_id; unsigned int two_bit_entry_CLUT_flag; unsigned int four_bit_entry_CLUT_flag; unsigned int eight_bit_entry_CLUT_flag; unsigned int full_range_flag; unsigned int Y_value; unsigned int Cr_value; unsigned int Cb_value; unsigned int T_value; int r; bslbf (CLUT_entry_id, s[8]); bslbf_1 (two_bit_entry_CLUT_flag, s[9] & 0x80); bslbf_1 (four_bit_entry_CLUT_flag, s[9] & 0x40); bslbf_1 (eight_bit_entry_CLUT_flag, s[9] & 0x20); /* reserved [4] */ bslbf_1 (full_range_flag, s[9] & 1); if (full_range_flag) { if (s + 13 >= end) return FALSE; bslbf (Y_value, s[10]); bslbf (Cr_value, s[11]); bslbf (Cb_value, s[12]); bslbf (T_value, s[13]); s += 6; } else { unsigned int n; fprintf (stderr, "Whoops! CLUT reduced range " "not supported.\n"); return FALSE; n = get16be (s + 10); /* Scale? */ bslbf (Y_value, n >> 10); bslbf (Cr_value, (n >> 6) & 15); bslbf (Cb_value, (n >> 2) & 15); bslbf (T_value, n & 3); s += 4; } // Apply CLUT to every region it applies to. for (r=0;r= 0) { if ((int) regions[r].CLUT_id == (int) CLUT_id) { /* XXX 255-T? */ set_palette(r,CLUT_entry_id,Y_value,Cr_value,Cb_value,255-T_value); } } } } return (s + 8 == end); } /* EN 300 743 Section 7.2.2. */ static bool region_composition_segment (const uint8_t * s, const uint8_t * end) { int o; static const char *region_level_of_compatibility_names [8] = { "reserved", "2 bit/entry CLUT required", "4 bit/entry CLUT required", "8 bit/entry CLUT required", "reserved", "reserved", "reserved", "reserved" }; unsigned int region_id; unsigned int region_version_number; unsigned int region_fill_flag; unsigned int region_width; unsigned int region_height; unsigned int region_level_of_compatibility; unsigned int region_depth; unsigned int CLUT_id; unsigned int region_8_bit_pixel_code; unsigned int region_4_bit_pixel_code; unsigned int region_2_bit_pixel_code; begin (); uimsbf (region_id, s[6]); if (region_id >= MAX_REGIONS) { fprintf (stderr, "Whoops! Too many regions for us.\n"); return FALSE; } uimsbf (region_version_number, s[7] >> 4); bslbf_1 (region_fill_flag, s[7] & 8); /* reserved [3] */ uimsbf (region_width, get16be (s + 8)); uimsbf (region_height, get16be (s + 10)); /* EN 300 743 Section 7.2.2. */ if ((region_width - 1) > 719 || (region_height - 1) > 575) return FALSE; bslbf_enum (region_level_of_compatibility, s[12] >> 5, region_level_of_compatibility_names); region_depth = (s[12] >> 2) & 7; if (FIELD_DUMP) { if (region_depth >= 1 && region_depth <= 3) { fprintf (stderr, " region_depth = %u (%u bits)\n", region_depth, 1 << region_depth); } else { fprintf (stderr, " region_depth = %u (reserved)\n", region_depth); } } if (0xF1 & ((1 << region_level_of_compatibility) | (1 << region_depth))) return FALSE; /* reserved [2] */ bslbf (CLUT_id, s[13]); bslbf (region_8_bit_pixel_code, s[14]); bslbf (region_4_bit_pixel_code, s[15] >> 4); bslbf (region_2_bit_pixel_code, (s[15] >> 2) & 3); /* reserved [2] */ if (regions[region_id].win < 0) { // If the region doesn't exist, then open it. create_region(region_id,region_width,region_height,region_depth); regions[region_id].CLUT_id=CLUT_id; } if (region_fill_flag) { memset(regions[region_id].img,region_4_bit_pixel_code,sizeof(regions[region_id].img)); } for (o=0;o<(int) N_ELEMENTS (regions[0].object_pos);o++) { regions[region_id].object_pos[o]=-1; } while (s + 21 < end) { static const char *object_type_names [4] = { "basic_object, bitmap", "basic_object, character", "composite_object, string of characters", "reserved" }; unsigned int object_id; unsigned int object_type; unsigned int object_horizontal_position; unsigned int object_vertical_position; unsigned int foreground_pixel_code; unsigned int background_pixel_code; unsigned int n; bslbf (object_id, get16be (s + 16)); n = get16be (s + 18); bslbf_enum (object_type, n >> 14, object_type_names); uimsbf (object_horizontal_position, n & 0xFFF); /* reserved [4] */ n = get16be (s + 20); uimsbf (object_vertical_position, n & 0xFFF); switch (object_type) { case 0: /* bitmap */ regions[region_id].object_pos[object_id]= (object_horizontal_position<<16)| object_vertical_position; s += 6; break; case 1: /* character */ case 2: /* character string */ if (s + 23 >= end) return FALSE; bslbf (foreground_pixel_code, s[22]); bslbf (background_pixel_code, s[23]); s += 8; break; case 3: /* reserved */ s += 6; break; } } return (s + 16 == end); } /* EN 300 743 Section 7.2.1. */ static bool page_composition_segment (bool * empty_sub, const uint8_t * s, const uint8_t * end) { static const char *page_state_names [4] = { "normal case", "acquisition point", "mode change", "reserved" }; unsigned int page_time_out; unsigned int page_version_number; unsigned int page_state; *empty_sub = FALSE; begin (); if (s + 7 >= end) return FALSE; uimsbf (page_time_out, s[6]); uimsbf (page_version_number, s[7] >> 4); bslbf_enum (page_state, (s[7] >> 2) & 3, page_state_names); /* reserved [2] */ if (page_state >= 3) return FALSE; if ((acquired==0) && (page_state!=2) && (page_state!=1)) { //fprintf(stderr,"waiting for mode_change\n"); *empty_sub = TRUE; return TRUE; } else { //fprintf(stderr,"acquired=1\n"); acquired=1; } // IF the packet contains no data (i.e. is used to clear a // previous subtitle), do nothing if (s + 8 == end) { //fprintf(stderr,"Empty sub, return\n"); *empty_sub = TRUE; return TRUE; } while (s + 13 < end) { unsigned int region_id; unsigned int region_horizontal_address; unsigned int region_vertical_address; bslbf (region_id, s[8]); /* reserved [8] */ uimsbf (region_horizontal_address, get16be (s + 10)); uimsbf (region_vertical_address, get16be (s + 12)); if (region_id >= MAX_REGIONS) { fprintf (stderr, "Whoops! Too many regions for us.\n"); return FALSE; } page.regions[region_id].x=region_horizontal_address; page.regions[region_id].y=region_vertical_address; page.regions[region_id].is_visible=1; //fprintf(stderr,"page_region id=%02x x=%d y=%d\n",region_id,region_x,region_y); s += 6; } return (s + 8 == end); } /* EN 300 743 Section 7.2. */ static bool subtitling_segment_loop (bool * empty_sub, const uint8_t * s, const uint8_t * end) { for (;;) { unsigned int sync_byte; begin (); bslbf (sync_byte, s[0]); if (0x0F == sync_byte) { unsigned int segment_type; unsigned int page_id; unsigned int segment_length; const uint8_t *segment_end; bool success; /* sync_byte [8], segment_type [8], page_id [16], segment_length [16], segment_data_field [segment_length * 8], end_of_PES_data_field_marker [8]. */ if (s + 6 >= end) return FALSE; bslbf (segment_type, s[1]); bslbf (page_id, get16be (s + 2)); uimsbf (segment_length, get16be (s + 4)); segment_end = s + 6 + segment_length; if (segment_end >= end) return FALSE; switch (segment_type) { case 0x10: success = page_composition_segment (empty_sub, s, segment_end); break; case 0x11: success = region_composition_segment (s, segment_end); break; case 0x12: success = CLUT_definition_segment (s, segment_end); break; case 0x13: success = object_data_segment (s, segment_end); break; default: /* 0x40 ... 0x7F Reserved 0x80 End of display segment 0x81 ... 0xEF Private data 0xFF stuffing */ if (1) { hex_dump (s, segment_end - s); } success = TRUE; break; } if (!success) return FALSE; s = segment_end; } else if (0xFF == sync_byte) { /* end_of_PES_data_field_marker. */ break; } else { return FALSE; } } return TRUE; } /* packet_start_code_prefix [24], stream_id [8], PES_packet_length [16]. */ static const unsigned int MAX_PES_PACKET_SIZE = 6 + 65535; static const unsigned int PTS_BITS = 33; static const uint64_t PTS_MASK = (((int64_t) 1) << 33) - 1; static bool decode_time_stamp (int64_t * ts, const uint8_t * s, unsigned int marker) { /* ISO 13818-1 Section 2.4.3.6 */ if (0 != ((marker ^ s[0]) & 0xF1)) return FALSE; if (NULL != ts) { unsigned int a, b, c; /* marker [4], TS [32..30], marker_bit, TS [29..15], marker_bit, TS [14..0], marker_bit */ a = (s[0] >> 1) & 0x7; b = get16be (s + 1) >> 1; c = get16be (s + 3) >> 1; *ts = ((int64_t) a << 30) + (b << 15) + (c << 0); } return TRUE; } static void dump_pes_packet_header (FILE * fp, const uint8_t * pes_packet, const uint8_t * end) { unsigned int packet_start_code_prefix; unsigned int stream_id; unsigned int PES_packet_length; unsigned int PES_scrambling_control; unsigned int PES_priority; unsigned int data_alignment_indicator; unsigned int copyright; unsigned int original_or_copy; unsigned int PTS_DTS_flags; unsigned int ESCR_flag; unsigned int ES_rate_flag; unsigned int DSM_trick_mode_flag; unsigned int additional_copy_info_flag; unsigned int PES_CRC_flag; unsigned int PES_extension_flag; unsigned int PES_header_data_length; int64_t ts; /* ISO 13818-1 Section 2.4.3.6. */ fputs ("PES packet", fp); if (pes_packet + 9 >= end) goto truncated; packet_start_code_prefix = ((pes_packet[0] << 16) | (pes_packet[1] << 8) | (pes_packet[2] << 0)); stream_id = pes_packet[3]; PES_packet_length = get16be (pes_packet + 4); /* '10' */ PES_scrambling_control = (pes_packet[6] & 0x30) >> 4; PES_priority = pes_packet[6] & 0x08; data_alignment_indicator = pes_packet[6] & 0x04; copyright = pes_packet[6] & 0x02; original_or_copy = pes_packet[6] & 0x01; PTS_DTS_flags = (pes_packet[7] & 0xC0) >> 6; ESCR_flag = pes_packet[7] & 0x20; ES_rate_flag = pes_packet[7] & 0x10; DSM_trick_mode_flag = pes_packet[7] & 0x08; additional_copy_info_flag = pes_packet[7] & 0x04; PES_CRC_flag = pes_packet[7] & 0x02; PES_extension_flag = pes_packet[7] & 0x01; PES_header_data_length = pes_packet[8]; fprintf (fp, " %06X%02X %5u " "%u%u%u%c%c%c%c%u%c%c%c%c%c%c %u", packet_start_code_prefix, stream_id, PES_packet_length, !!(pes_packet[6] & 0x80), !!(pes_packet[6] & 0x40), PES_scrambling_control, PES_priority ? 'P' : '-', data_alignment_indicator ? 'A' : '-', copyright ? 'C' : '-', original_or_copy ? 'O' : 'C', PTS_DTS_flags, ESCR_flag ? 'E' : '-', ES_rate_flag ? 'E' : '-', DSM_trick_mode_flag ? 'D' : '-', additional_copy_info_flag ? 'A' : '-', PES_CRC_flag ? 'C' : '-', PES_extension_flag ? 'X' : '-', PES_header_data_length); switch (PTS_DTS_flags) { case 0: /* no timestamps */ case 1: /* forbidden */ fputc ('\n', fp); break; case 2: /* PTS only */ if (pes_packet + 14 >= end) goto truncated; if (decode_time_stamp (&ts, &pes_packet[9], 0x21)) fprintf (fp, " PTS=%" PRId64 "\n", ts); else fputs (" bad PTS\n", fp); break; case 3: /* PTS and DTS */ if (pes_packet + 19 >= end) goto truncated; if (decode_time_stamp (&ts, &pes_packet[9], 0x31)) fprintf (fp, " PTS=%" PRId64, ts); else fputs (" bad PTS", fp); if (decode_time_stamp (&ts, &pes_packet[14], 0x11)) fprintf (fp, " DTS=%" PRId64 "\n", ts); else fputs (" bad DTS\n", fp); break; } return; truncated: fputs (" truncated\n", fp); } static bool pes_subt_packet (const uint8_t * pes_packet, const uint8_t * end) { double PTS_secs; int r; const uint8_t *s; unsigned int n; unsigned int PES_header_data_length; int64_t pts; unsigned int data_identifier; unsigned int subtitle_stream_id; bool empty_sub; if (0) { dump_pes_packet_header (stderr, pes_packet, end); } /* Minimum PES packet header size is 9 bytes, plus at least 5 bytes for the mandatory PTS (EN 300743 Section 6), plus at least 3 bytes for the PES_data_field (EN 300743 Section 7.1). */ if (pes_packet + 8 + 5 + 3 > end) return FALSE; n = get16be (pes_packet + 6); /* '10', PES_scrambling_control == '00' (not scrambled), data_alignment_indicator == 1 (EN 300743 Section 6), PTS_DTS_flags == '10' (EN 300743 Section 6). */ if (0x8480 != (n & 0xF4C0)) return FALSE; PES_header_data_length = pes_packet[8]; if (PES_header_data_length < 5 || pes_packet + PES_header_data_length + 12 > end) return FALSE; if (!decode_time_stamp (&pts, &pes_packet[9], 0x21)) return FALSE; s = pes_packet + PES_header_data_length + 9; /* EN 300743 Section 7.1: PES_data_field. */ data_identifier = s[0]; if (0x20 != data_identifier) return FALSE; subtitle_stream_id = s[1]; if (0x00 != subtitle_stream_id) { /* Not a DVB subtitling stream. */ return TRUE; } init_data(); gettimeofday(&start_tv,NULL); PTS_secs=(pts/90000.0); empty_sub = FALSE; if (!subtitling_segment_loop (&empty_sub, s + 2, end)) { return FALSE; } // fprintf(stderr,"End of PES packet - time=%.2f\n",PTS_secs); if (empty_sub) { int i; if (textsub.start_pts < 0) return TRUE; textsub.end_pts = pts/90; output_textsub(outfile); textsub.end_pts = textsub.start_pts = -1; for(i=0; i= 0) { int i; textsub.end_pts = pts/90; output_textsub(outfile); textsub.end_pts = textsub.start_pts = -1; for(i=0; i= 0) { if (page.regions[r].is_visible) { //int xx,yy; //fprintf(stderr,"displaying region %d at %d,%d width=%d,height=%d PTS = %g\n", //r,page.regions[r].x,page.regions[r].y,regions[r].width,regions[r].height, PTS_secs); /*for(yy=0;yy 0) { fprintf(stderr,"%d regions visible - showing\n",n); } else { fprintf(stderr,"%d regions visible - hiding\n",n); }*/ } // if (acquired) { sleep(1); } return TRUE; } static void ts_subt_reset (void) { pes_in = 0; pes_packet_end = 0; } static bool ts_subt_packet (const uint8_t ts_packet[188], unsigned int header_length) { unsigned int payload_unit_start_indicator; unsigned int payload_length; unsigned int PES_packet_length; payload_unit_start_indicator = ts_packet[1] & 0x40; /* ISO 13818-1 Section 2.4.3.3. */ if (payload_unit_start_indicator) { if (unlikely (pes_in > 0)) { /* TS packet headers and PES_packet_length disagree about the PES packet size. */ ts_subt_reset (); } } else if (unlikely (0 == pes_in)) { /* Discard remainder of previous PES packet. */ return TRUE; } payload_length = 188 - header_length; memcpy (pes_buffer + pes_in, ts_packet + header_length, payload_length); pes_in += payload_length; if (0 == pes_packet_end) { if (unlikely (pes_in < 6)) return TRUE; /* need more data */ /* EN 300743 Section 6. */ if (0x000001BD != get32be (pes_buffer + 0)) { ts_subt_reset (); return FALSE; } PES_packet_length = get16be (pes_buffer + 4); pes_packet_end = 6 + PES_packet_length; } if (pes_in < pes_packet_end) return TRUE; /* need more data */ if (unlikely (pes_in > pes_packet_end)) { /* TS packet headers and PES_packet_length disagree about the PES packet size. */ ts_subt_reset (); return FALSE; } pes_subt_packet (pes_buffer, pes_buffer + pes_packet_end); ts_subt_reset (); return TRUE; } static const unsigned int TS_PACKET_SIZE = 188; static void dump_ts_packet_header (FILE * fp, const uint8_t ts_packet[188]) { unsigned int sync_byte; unsigned int transport_error_indicator; unsigned int payload_unit_start_indicator; unsigned int transport_priority; unsigned int PID; unsigned int transport_scrambling_control; unsigned int adaptation_field_control; unsigned int continuity_counter; unsigned int header_length; sync_byte = ts_packet[0]; transport_error_indicator = ts_packet[1] & 0x80; payload_unit_start_indicator = ts_packet[1] & 0x40; transport_priority = ts_packet[1] & 0x20; PID = get16be (ts_packet + 1) & 0x1FFF; transport_scrambling_control = (ts_packet[3] & 0xC0) >> 6; adaptation_field_control = (ts_packet[3] & 0x30) >> 4; continuity_counter = ts_packet[3] & 0x0F; if (adaptation_field_control >= 2) { unsigned int adaptation_field_length; adaptation_field_length = ts_packet[4]; header_length = 5 + adaptation_field_length; } else { header_length = 4; } fprintf (fp, "TS packet %02x %c%c%c 0x%04x=%u %u%u%x %u\n", sync_byte, transport_error_indicator ? 'E' : '-', payload_unit_start_indicator ? 'S' : '-', transport_priority ? 'P' : '-', PID, PID, transport_scrambling_control, adaptation_field_control, continuity_counter, header_length); } static bool ts_filter (const uint8_t ts_packet[188]) { unsigned int transport_error_indicator; unsigned int pid; unsigned int adaptation_field_control; unsigned int header_length; if (0) dump_ts_packet_header (stderr, ts_packet); transport_error_indicator = ts_packet[1] & 0x80; if (unlikely (transport_error_indicator)) { log (2, "TS transmission error\n"); ts_subt_reset (); ts_next_cc = -1; return TRUE; } pid = get16be (ts_packet + 1) & 0x1FFF; if (ts_subt_pid != pid) return TRUE; ++ts_n_subt_packets_in; if (0) dump_ts_packet_header (stderr, ts_packet); adaptation_field_control = (ts_packet[3] & 0x30) >> 4; if (likely (1 == adaptation_field_control)) { header_length = 4; } else if (3 == adaptation_field_control) { unsigned int adaptation_field_length; adaptation_field_length = ts_packet[4]; /* Zero length is used for stuffing. */ if (adaptation_field_length > 0) { unsigned int discontinuity_indicator; /* ISO 13818-1 Section 2.4.3.5. Also the code below assumes header_length <= packet_size. */ if (adaptation_field_length > 182) { log (2, "TS AFL error\n"); ts_subt_reset (); ts_next_cc = -1; return FALSE; } /* ISO 13818-1 Section 2.4.3.5. */ discontinuity_indicator = ts_packet[5] & 0x80; if (discontinuity_indicator) { log (2, "TS discontinuity\n"); ts_subt_reset (); } } header_length = 5 + adaptation_field_length; } else if (0 == adaptation_field_control) { log (2, "TS AFC error\n"); ts_subt_reset (); ts_next_cc = -1; return FALSE; } else { /* 2 == adaptation_field_control: no payload. */ /* ISO 13818-1 Section 2.4.3.3: continuity_counter shall not increment. */ return TRUE; } if (unlikely (0 != ((ts_next_cc ^ ts_packet[3]) & 0x0F))) { /* Continuity counter mismatch. */ if (ts_next_cc < 0) { /* First TS packet. */ } else if (0 == (((ts_next_cc - 1) ^ ts_packet[3]) & 0x0F)) { /* ISO 13818-1 Section 2.4.3.3: Repeated packet. */ return TRUE; } else { log (2, "TS continuity error\n"); ts_subt_reset (); } } ts_next_cc = ts_packet[3] + 1; ts_subt_packet (ts_packet, header_length); return TRUE; } static unsigned int ts_sync (const uint8_t * ts, const uint8_t * end) { const uint8_t *ts0 = ts; for (;;) { unsigned int avail; avail = end - ts; if (avail < 188) break; /* need more data */ if (unlikely (0x47 != ts[0])) { unsigned int offset; if (avail < 188 + 187) break; /* need more data */ if (ts_n_subt_packets_in > 0) log (2, "TS sync lost.\n"); for (offset = 0; offset < (avail - 187); ++offset) { if (0x47 == ts[offset] && 0x47 == ts[offset + 188]) break; } if (offset >= (avail - 187)) { ts_subt_reset (); return end - ts0 - 187; } ts += offset; } if (likely (ts_filter (ts))) ts += 188; else ts += 1; } return ts - ts0; /* num. bytes consumed */ } static void file_read_loop (void) { ssize_t in; ssize_t out; assert (0 == (ts_buffer_capacity & 4095)); in = 0; out = 0; for (;;) { ssize_t space; ssize_t actual; ssize_t left; space = ts_buffer_capacity - in; assert (space > 0); for (;;) { actual = read (fd, ts_buffer + in, space); if (actual >= 0) break; if (EINTR == errno) continue; errno_exit ("Read error"); } if (0 == actual) break; /* eof */ in += actual; ts_n_bytes_in += actual; out += ts_sync (ts_buffer + out, ts_buffer + in); left = in - out; if (left > 0) { /* Keep reads page aligned. */ in = out & 4095; memmove (ts_buffer + in, ts_buffer + out, left); } else { assert (0 == left); in = 0; } out = in; in += left; } } static void init (void) { ts_buffer_capacity = 32 * 1024; ts_buffer = my_memalign (4096, ts_buffer_capacity); if (NULL == ts_buffer) { no_mem_exit (); } ts_n_bytes_in = 0; ts_n_subt_packets_in = 0; ts_next_cc = -1; pes_buffer_capacity = MAX_PES_PACKET_SIZE + TS_PACKET_SIZE; pes_buffer = my_memalign (4096, pes_buffer_capacity); if (NULL == pes_buffer) { no_mem_exit (); } ts_subt_reset (); } /* Old code */ int main(int argc, char* argv[]) { int pid; if (argc!=4) { fprintf(stderr,"USAGE: dvbsubs PID input_file output_file\n"); exit(0); } pid=atoi(argv[1]); fd=open(argv[2],O_RDONLY); outfile=fopen(argv[3],"w"); textsub.start_pts = textsub.end_pts = -1; if (1) { get_sub_packets(fd,pid); } else { ts_subt_pid = pid; init (); file_read_loop (); } fclose(outfile); close(fd); return 0; } zvbi-0.2.44/contrib/dvbsubs.h000066400000000000000000000027641476363111200160510ustar00rootroot00000000000000/* dvbsubs - a program for decoding DVB subtitles (ETS 300 743) File: dvbsubs.h Copyright (C) Dave Chapman 2002 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Or, point your browser to http://www.gnu.org/copyleft/gpl.html */ #define MAX_REGIONS 5 typedef struct { int x,y; unsigned char is_visible; } visible_region_t; typedef struct { int acquired; int page_time_out; int page_version_number; int page_state; visible_region_t regions[MAX_REGIONS]; } page_t; typedef struct { int width,height; int depth; int CLUT_id; int win; int objects_start,objects_end; unsigned int object_pos[65536]; unsigned char palette[256]; unsigned char alpha[256]; unsigned char img[720*576]; } region_t; typedef struct { char regions[MAX_REGIONS][64]; int next_region; int64_t start_pts; int64_t end_pts; } textsub_t; zvbi-0.2.44/contrib/ntsc-cc.c000066400000000000000000001203331476363111200157170ustar00rootroot00000000000000/* cc.c -- closed caption decoder * Mike Baker (mbm@linux.com) * (based on code by timecop@japan.co.jp) * Buffer overflow bugfix by Mark K. Kim (dev@cbreak.org), 2003.05.22 * * Libzvbi port, various fixes and improvements * (C) 2005-2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_IOCTL_H # include #endif #include #include #include #ifdef HAVE_GETOPT_LONG # include #endif #define HAVE_ZVBI 1 #include #include "src/libzvbi.h" #ifdef ENABLE_V4L2 # include # include "src/videodev2k.h" #endif #ifndef X_DISPLAY_MISSING # include # include # include # include Display *dpy; Window Win,Root; char dpyname[256] = ""; GC WinGC; GC WinGC0; GC WinGC1; int x; #endif #undef PROGRAM #define PROGRAM "CCDecoder" #undef VERSION #define VERSION "0.13" #define N_ELEMENTS(array) (sizeof (array) / sizeof (*(array))) #define CLEAR(var) memset (&(var), 0, sizeof (var)) #ifndef _ # define _(x) x /* future l18n */ #endif static char * my_name; static int vbifd = -1; static void * io_buffer; static size_t io_size; static unsigned int field; static vbi_bool in_xds[2]; static int cur_ch[2]; //XDSdecode static struct { char packet[34]; uint8_t length; int print : 1; } info[2][8][25]; char newinfo[2][8][25][34]; char *infoptr=newinfo[0][0][0]; int mode,type; char infochecksum; static const char * xds_info_prefix = "\33[33m% "; static const char * xds_info_suffix = "\33[0m\n"; static FILE * xds_fp; //ccdecode const char *ratings[] = {"(NOT RATED)","TV-Y","TV-Y7","TV-G","TV-PG","TV-14","TV-MA","(NOT RATED)"}; int rowdata[] = {11,-1,1,2,3,4,12,13,14,15,5,6,7,8,9,10}; const char *specialchar[] = {"®","°","œ","¿","(TM)","¢","£","o/~ ","à"," ","è","â","ê","î","ô","û"}; const char *modes[]={"current","future","channel","miscellaneous","public service","reserved","invalid","invalid","invalid","invalid"}; int lastcode; int ccmode=1; //cc1 or cc2 char ccbuf[8][3][256]; //cc is 32 columns per row, this allows for extra characters static uint16_t cc_ubuf[8][3][256]; int keywords=0; char *keyword[32]; static int is_upper[8]; static FILE * cc_fp[8]; static vbi_bool opt_v4l2_sliced; //args (this should probably be put into a structure later) char useraw=0; char semirawdata=0; char usexds=0; char usecc=0; char plain=0; char usesen=0; char debugwin=0; char test=0; char usewebtv=1; char rawline=-1; int sen; int inval; static int parityok(int n) /* check parity for 2 bytes packed in n */ { int mask=0; int j, k; for (k = 1, j = 0; j < 7; j++) { if (n & (1<>7)&1)) mask|=0x00FF; for (k = 1, j = 8; j < 15; j++) { if (n & (1<>15)&1)) mask|=0xFF00; return mask; } static int decodebit(unsigned char *data, int threshold) { int i, sum = 0; for (i = 0; i < 23; i++) sum += data[i]; return (sum > threshold*23); } static int decode(unsigned char *vbiline) { int max[7], min[7], val[7], i, clk, tmp, sample, packedbits = 0; for (clk=0; clk<7; clk++) max[clk] = min[clk] = val[clk] = -1; clk = tmp = 0; i=30; while (i < 600 && clk < 7) { /* find and lock all 7 clocks */ sample = vbiline[i]; if (max[clk] < 0) { /* find maximum value before drop */ if (sample > 85 && sample > val[clk]) (val[clk] = sample, tmp = i); /* mark new maximum found */ else if (val[clk] - sample > 30) /* far enough */ (max[clk] = tmp, i = tmp + 10); } else { /* find minimum value after drop */ if (sample < 85 && sample < val[clk]) (val[clk] = sample, tmp = i); /* mark new minimum found */ else if (sample - val[clk] > 30) /* searched far enough */ (min[clk++] = tmp, i = tmp + 10); } i++; } i=min[6]=min[5]-max[5]+max[6]; if (clk != 7 || vbiline[max[3]] - vbiline[min[5]] < 45) /* failure to locate clock lead-in */ return -1; #ifndef X_DISPLAY_MISSING if (debugwin) { for (clk=0;clk<7;clk++) { XDrawLine(dpy,Win,WinGC,min[clk]/2,0,min[clk]/2,128); XDrawLine(dpy,Win,WinGC1,max[clk]/2,0,max[clk]/2,128); } XFlush(dpy); } #endif /* calculate threshold */ for (i=0,sample=0;i<7;i++) sample=(sample + vbiline[min[i]] + vbiline[max[i]])/3; for(i=min[6];vbiline[i]5) break; fprintf (xds_fp, "%s LENGTH: %d:%02d:%02d of %d:%02d:00%s", xds_info_prefix, infoptr[3]&0x3f,infoptr[2]&0x3f, infoptr[4]&0x3f,infoptr[1]&0x3f, infoptr[0]&0x3f, xds_info_suffix); break; case 0x0103: fprintf (xds_fp, "%s TITLE: %s%s", xds_info_prefix, infoptr, xds_info_suffix); break; case 0x0105: fprintf (xds_fp, "%s RATING: %s (%d)", xds_info_prefix, ratings[infoptr[0]&0x07],infoptr[0]); if ((infoptr[0]&0x07)>0) { if (infoptr[0]&0x20) fputs (" VIOLENCE", xds_fp); if (infoptr[0]&0x10) fputs (" SEXUAL", xds_fp); if (infoptr[0]&0x08) fputs (" LANGUAGE", xds_fp); } fputs (xds_info_suffix, xds_fp); break; case 0x0501: fprintf (xds_fp, "%s NETWORK: %s%s", xds_info_prefix, infoptr, xds_info_suffix); break; case 0x0502: fprintf (xds_fp, "%s CALL: %s%s", xds_info_prefix, infoptr, xds_info_suffix); break; case 0x0701: fprintf (xds_fp, "%sCUR.TIME: %d:%02d %d/%02d/%04d UTC%s", xds_info_prefix, infoptr[1]&0x1F,infoptr[0]&0x3f, infoptr[3]&0x0f,infoptr[2]&0x1f, (infoptr[5]&0x3f)+1990, xds_info_suffix); break; case 0x0704: //timezone fprintf (xds_fp, "%sTIMEZONE: UTC-%d%s", xds_info_prefix, infoptr[0]&0x1f, xds_info_suffix); break; case 0x0104: //program genere break; case 0x0110: case 0x0111: case 0x0112: case 0x0113: case 0x0114: case 0x0115: case 0x0116: case 0x0117: fprintf (xds_fp, "%s DESC: %s%s", xds_info_prefix, infoptr, xds_info_suffix); break; } fflush (xds_fp); } static int XDSdecode(int data) { static vbi_bool in_xds[2]; int b1, b2, length; if (data == -1) return -1; b1 = data & 0x7F; b2 = (data>>8) & 0x7F; if (0 == b1) { /* Filler, discard. */ return -1; } else if (b1 < 15) // start packet { mode = b1; type = b2; infochecksum = b1 + b2 + 15; if (mode > 8 || type > 20) { // printf("%% Unsupported mode %s(%d) [%d]\n",modes[(mode-1)>>1],mode,type); mode=0; type=0; } infoptr = newinfo[field][mode][type]; in_xds[field] = TRUE; } else if (b1 == 15) // eof (next byte is checksum) { #if 0 //debug if (mode == 0) { length=infoptr - newinfo[field][0][0]; infoptr[1]=0; printf("LEN: %d\n",length); for (y=0;y= &newinfo[field][mode][type][32]) { /* Bad packet. */ mode = 0; type = 0; in_xds[field] = 0; } else { infoptr[0] = b1; infoptr++; infoptr[0] = b2; infoptr++; infochecksum += b1 + b2; } } return 0; } static int webtv_check(char * buf,int len) { unsigned long sum; unsigned long nwords; unsigned short csum=0; char temp[9]; int nbytes=0; while (buf[0]!='<' && len > 6) //search for the start { buf++; len--; } if (len == 6) //failure to find start return 0; while (nbytes+6 <= len) { //look for end of object checksum, it's enclosed in []'s and there shouldn't be any [' after if (buf[nbytes] == '[' && buf[nbytes+5] == ']' && buf[nbytes+6] != '[') break; else nbytes++; } if (nbytes+6>len) //failure to find end return 0; nwords = nbytes >> 1; sum = 0; //add up all two byte words while (nwords-- > 0) { sum += *buf++ << 8; sum += *buf++; } if (nbytes & 1) { sum += *buf << 8; } csum = (unsigned short)(sum >> 16); while(csum !=0) { sum = csum + (sum & 0xffff); csum = (unsigned short)(sum >> 16); } sprintf(temp,"%04X\n",(int)~sum&0xffff); buf++; if(!strncmp(buf,temp,4)) { buf[5]=0; if (cur_ch[field] >= 0 && cc_fp[cur_ch[field]]) { if (!plain) fprintf(cc_fp[cur_ch[field]], "\33[35mWEBTV: %s\33[0m\n",buf-nbytes-1); else fprintf(cc_fp[cur_ch[field]], "WEBTV: %s\n",buf-nbytes-1); fflush (cc_fp[cur_ch[field]]); } } return 0; } static int unicode (int c) { if (c >= 'a' && c <= 'z') { is_upper[cur_ch[field]] = 0; } else if (c >= 'A' && c <= 'Z') { if (is_upper[cur_ch[field]] < 3) ++is_upper[cur_ch[field]]; } /* The standard character set has no upper case accented characters, so we convert to upper case if that appears to be intended. */ return vbi_caption_unicode (c, (is_upper[cur_ch[field]] >= 3)); } static void append_char (int c, int uc) { unsigned int ch = cur_ch[field]; unsigned int dlen; dlen = strlen (ccbuf[ch][ccmode]); if (dlen < N_ELEMENTS (ccbuf[0][0]) - 1) { ccbuf[ch][ccmode][dlen] = c; ccbuf[ch][ccmode][dlen + 1] = 0; } dlen = vbi_strlen_ucs2 (cc_ubuf[ch][ccmode]); if (dlen < N_ELEMENTS (cc_ubuf[0][0]) - 1) { cc_ubuf[ch][ccmode][dlen] = uc; cc_ubuf[ch][ccmode][dlen + 1] = 0; } } static void append_special_char (int b2) { unsigned int ch = cur_ch[field]; unsigned int dlen; unsigned int slen; slen = strlen (specialchar[b2&0x0f]); dlen = strlen (ccbuf[ch][ccmode]); if (dlen + slen < N_ELEMENTS (ccbuf[0][0]) - 1) { strcpy (&ccbuf[ch][ccmode][dlen], specialchar[b2&0x0f]); } dlen = vbi_strlen_ucs2 (cc_ubuf[ch][ccmode]); if (dlen < N_ELEMENTS (cc_ubuf[0][0]) - 1) { cc_ubuf[ch][ccmode][dlen] = unicode (0x1100 | b2); cc_ubuf[ch][ccmode][dlen + 1] = 0; } } static void append_control_seq (const char * seq) { unsigned int ch = cur_ch[field]; unsigned int slen; unsigned int dlen; if (plain) return; slen = strlen (seq); dlen = strlen (ccbuf[ch][ccmode]); if (dlen + slen < N_ELEMENTS (ccbuf[0][0]) - 1) { strcpy (&ccbuf[ch][ccmode][dlen], seq); } dlen = vbi_strlen_ucs2 (cc_ubuf[ch][ccmode]); if (dlen + slen < N_ELEMENTS (cc_ubuf[0][0]) - 1) { unsigned int i; for (i = 0; 0 != seq[i]; ++i) { /* ASCII -> UCS-2 */ cc_ubuf[ch][ccmode][dlen + i] = seq[i]; } cc_ubuf[ch][ccmode][dlen + i] = 0; } } static int CCdecode(int data) { int b1, b2, row, x,y; if (cur_ch[field] < 0) return -1; if (data == -1) //invalid data. flush buffers to be safe. { CLEAR (ccbuf); CLEAR (cc_ubuf); return -1; } b1 = data & 0x7f; b2 = (data>>8) & 0x7f; if(ccmode >= 3) ccmode = 0; if (b1&0x60 && data != lastcode) // text { append_char (b1, unicode (b1)); if (b2&0x60) { append_char (b2, unicode (b2)); } if ((b1 == ']' || b2 == ']') && usewebtv) webtv_check(ccbuf[cur_ch[field]][ccmode], strlen (ccbuf[cur_ch[field]][ccmode])); } else if ((b1&0x10) && (b2>0x1F) && (data != lastcode)) //codes are always transmitted twice (apparently not, ignore the second occurance) { ccmode=((b1>>3)&1)+1; if (b2 & 0x40) //preamble address code (row & indent) { row=rowdata[((b1<<1)&14)|((b2>>5)&1)]; if (strlen (ccbuf[cur_ch[field]][ccmode]) > 0) { append_char ('\n', '\n'); } if (b2&0x10) //row contains indent flag for (x=0;x<(b2&0x0F)<<1;x++) { append_char (' ', ' '); } } else { switch (b1 & 0x07) { case 0x00: //attribute if (cc_fp[cur_ch[field]]) { // fprintf (cc_fp[cur_ch[field]], "\n",b1,b2); // fflush (cc_fp[cur_ch[field]]); } break; case 0x01: //midrow or char switch (b2&0x70) { case 0x20: //midrow attribute change switch (b2&0x0e) { case 0x00: //italics off append_control_seq ("\33[0m "); break; case 0x0e: //italics on append_control_seq ("\33[36m "); break; } if (b2&0x01) { //underline append_control_seq ("\33[4m"); } else { append_control_seq ("\33[24m"); } break; case 0x30: //special character.. append_special_char (b2); break; } break; case 0x04: //misc case 0x05: //misc + F if (cc_fp[cur_ch[field]]) { // fprintf (cc_fp[cur_ch[field]], "ccmode %d cmd %02x\n",ccmode,b2); } switch (b2) { size_t n; unsigned int dlen; case 0x21: //backspace dlen = strlen (ccbuf[cur_ch[field]][ccmode]); if (dlen > 0) { ccbuf[cur_ch[field]][ccmode][dlen - 1] = 0; } dlen = vbi_strlen_ucs2 (cc_ubuf[cur_ch[field]][ccmode]); if (dlen > 0) { cc_ubuf[cur_ch[field]][ccmode][dlen - 1] = 0; } break; /* these codes are insignifigant if we're ignoring positioning */ case 0x25: //2 row caption case 0x26: //3 row caption case 0x27: //4 row caption case 0x29: //resume direct caption case 0x2B: //resume text display case 0x2C: //erase displayed memory break; case 0x2D: //carriage return if (ccmode==2) break; case 0x2F: //end caption + swap memory case 0x20: //resume caption (new caption) if (!strlen(ccbuf[cur_ch[field]][ccmode])) break; for (n=0;n>8) & 0x7f; if (!semirawdata) { fprintf(stderr,"%c%c",b1,b2); fflush(stderr); return 0; } // semi-raw data output begins here... // a control code. if ( ( b1 >= 0x10 ) && ( b1 <= 0x1F ) ) { if ( ( b2 >= 0x20 ) && ( b2 <= 0x7F ) ) fprintf(stderr,"[%02X-%02X]",b1,b2); fflush(stderr); return 0; } // next two rules: // supposed to be one printable char // and the other char to be discarded if ( ( b1 >= 0x0 ) && ( b1 <= 0xF ) ) { fprintf(stderr,"(%02x)%c",b1,b2); //fprintf(stderr,"%c",b2); //fprintf(stderr,"%c%c",0,b2); fflush(stderr); return 0; } if ( ( b2 >= 0x0 ) && ( b2 <= 0xF ) ) { fprintf(stderr,"%c{%02x}",b1,b2); //fprintf(stderr,"%c",b1); //fprintf(stderr,"%c%c",b1,1); fflush(stderr); return 0; } // just classic two chars to print. fprintf(stderr,"%c%c",b1,b2); fflush(stderr); return 0; } static int sentence(int data) { int b1, b2; if (data == -1) return -1; if (cur_ch[field] < 0 || !cc_fp[cur_ch[field]]) return 0; b1 = data & 0x7f; b2 = (data>>8) & 0x7f; inval++; if (data==lastcode) { if (sen==1) { fprintf (cc_fp[cur_ch[field]], " "); fflush (cc_fp[cur_ch[field]]); sen=0; } if (inval>10 && sen) { fprintf (cc_fp[cur_ch[field]], "\n"); fflush (cc_fp[cur_ch[field]]); sen=0; } return 0; } lastcode=data; if (b1&96) { inval=0; if (sen==2 && b1!='.' && b2!='.' && b1!='!' && b2!='!' && b1!='?' && b2!='?' && b1!=')' && b2!=')') { fprintf (cc_fp[cur_ch[field]], "\n"); sen=1; } else if (b1=='.' || b2=='.' || b1=='!' || b2=='!' || b1=='?' || b2=='?' || b1==')' || b2==')') sen=2; else sen=1; fprintf (cc_fp[cur_ch[field]], "%c%c",tolower(b1),tolower(b2)); fflush (cc_fp[cur_ch[field]]); } return 0; } #ifndef X_DISPLAY_MISSING static unsigned long getColor(const char *colorName, float dim) { XColor Color; XWindowAttributes Attributes; XGetWindowAttributes(dpy, Root, &Attributes); Color.pixel = 0; XParseColor (dpy, Attributes.colormap, colorName, &Color); Color.red=(unsigned short)(Color.red/dim); Color.blue=(unsigned short)(Color.blue/dim); Color.green=(unsigned short)(Color.green/dim); Color.flags=DoRed | DoGreen | DoBlue; XAllocColor (dpy, Attributes.colormap, &Color); return Color.pixel; } #endif static void caption_filter (unsigned int c1, unsigned int c2) { unsigned int p; p = c1 + c2 * 256; p ^= p >> 4; p ^= p >> 2; p ^= p >> 1; c1 &= 0x7F; c2 &= 0x7F; if (0x0101 != (p & 0x0101)) { /* Parity error. */ cur_ch[field] = -1; } else if (0 == c1) { /* Filler. */ } else if (c1 < 0x10) { in_xds[field] = TRUE; } else if (c1 < 0x20) { in_xds[field] = FALSE; if (c2 < 0x20) { /* Invalid. */ } else { cur_ch[field] &= ~1; cur_ch[field] |= (c1 >> 3) & 1; if (c2 < 0x30 && 0x14 == (c1 & 0xF6)) { cur_ch[field] &= ~2; cur_ch[field] |= (c1 << 1) & 2; switch (c2) { case 0x20: /* RCL */ case 0x25: /* RU2 */ case 0x26: /* RU3 */ case 0x27: /* RU4 */ case 0x29: /* RDC */ cur_ch[field] &= 3; break; case 0x2A: /* TR */ case 0x2B: /* RTD */ cur_ch[field] &= 3; /* now >= 0 */ cur_ch[field] |= 4; break; default: break; } } } } if (0) { fprintf (stderr, "in_xds=%d cur_ch=%d\n", in_xds[field], cur_ch[field]); } } #ifdef ENABLE_V4L2 static ssize_t read_v4l2_sliced (vbi_sliced * sliced_out, int * n_lines_out, unsigned int max_lines) { const struct v4l2_sliced_vbi_data *s; unsigned int n_lines; ssize_t size; size = read (vbifd, io_buffer, io_size); if (size <= 0) { return size; } s = (const struct v4l2_sliced_vbi_data *) io_buffer; n_lines = size / sizeof (struct v4l2_sliced_vbi_data); *n_lines_out = 0; while (n_lines > 0) { if ((unsigned int) *n_lines_out >= max_lines) return 1; /* ok */ if (V4L2_SLICED_CAPTION_525 == s->id && 21 == s->line) { sliced_out->id = VBI_SLICED_CAPTION_525; if (0 == s->field) sliced_out->line = 21; else sliced_out->line = 284; memcpy (sliced_out->data, s->data, 2); ++sliced_out; ++*n_lines_out; } ++s; --n_lines; } return 1; /* ok */ } static vbi_bool open_v4l2_sliced (const char * dev_name) { struct stat st; struct v4l2_capability cap; struct v4l2_format fmt; if (-1 == stat (dev_name, &st)) { fprintf (stderr, _("%s: Cannot identify '%s'. %s.\n"), my_name, dev_name, strerror (errno)); exit (EXIT_FAILURE); } if (!S_ISCHR (st.st_mode)) { fprintf (stderr, _("%s: %s is not a character device.\n"), my_name, dev_name); exit (EXIT_FAILURE); } vbifd = open (dev_name, O_RDWR, 0); if (-1 == vbifd) { fprintf (stderr, _("%s: Cannot open %s. %s.\n"), my_name, dev_name, strerror (errno)); exit (EXIT_FAILURE); } if (-1 == ioctl (vbifd, VIDIOC_QUERYCAP, &cap)) { if (EINVAL == errno) { fprintf (stderr, _("%s: %s is not a V4L2 device.\n"), my_name, dev_name); } else { fprintf (stderr, _("%s: VIDIOC_QUERYCAP failed: %s.\n"), my_name, strerror (errno)); } goto failed; } if (0 == (cap.capabilities & V4L2_CAP_SLICED_VBI_CAPTURE)) { fprintf (stderr, _("%s: %s does not support sliced VBI capturing.\n"), my_name, dev_name); goto failed; } if (0 == (cap.capabilities & V4L2_CAP_READWRITE)) { fprintf (stderr, _("%s: %s does not support the read() function.\n"), my_name, dev_name); goto failed; } CLEAR (fmt); fmt.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE; fmt.fmt.sliced.service_set = V4L2_SLICED_CAPTION_525; if (-1 == ioctl (vbifd, VIDIOC_S_FMT, &fmt)) { fprintf (stderr, _("%s: VIDIOC_S_FMT failed: %s.\n"), my_name, strerror (errno)); goto failed; } if (0 == (fmt.fmt.sliced.service_set & V4L2_SLICED_CAPTION_525)) { fprintf (stderr, _("%s: %s cannot capture Closed Caption.\n"), my_name, dev_name); goto failed; } io_size = fmt.fmt.sliced.io_size; io_buffer = malloc (io_size); if (NULL == io_buffer) { fprintf (stderr, _("%s: Cannot allocate %u byte I/O buffer.\n"), my_name, (unsigned int) io_size); goto failed; } return TRUE; failed: if (-1 != vbifd) { close (vbifd); vbifd = -1; } return FALSE; } #else /* !ENABLE_V4L2 */ static ssize_t read_v4l2_sliced (vbi_sliced * sliced, int * n_lines, unsigned int max_lines) { assert (0); /* not reached */ } static vbi_bool open_v4l2_sliced (const char * dev_name) { /* Not supported, fall back to standard i/o. */ return FALSE; } #endif /* !ENABLE_V4L2 */ static void check_fread (unsigned int n_expected_items, unsigned int n_actual_items) { if (n_expected_items != n_actual_items) { if (ferror (stdin)) fprintf (stderr, "Error reading stream\n"); exit (EXIT_FAILURE); } } static ssize_t read_test_stream (vbi_sliced * sliced, int * n_lines, unsigned int max_lines) { char buf[256]; double dt; unsigned int n_items; vbi_sliced *s; if (ferror (stdin) || !fgets (buf, 255, stdin)) { fprintf (stderr, "End of test stream\n"); exit (EXIT_SUCCESS); } dt = strtod (buf, NULL); n_items = fgetc (stdin); assert (n_items < max_lines); s = sliced; while (n_items-- > 0) { int index; unsigned int n_actual_items; index = fgetc (stdin); if (255 == index) { uint8_t buffer[22]; unsigned int count[2]; unsigned int bytes_per_line; unsigned int bytes_per_frame; uint8_t *p; /* Skip raw data. */ memset (buffer, 0, sizeof (buffer)); n_actual_items = fread (buffer, 1, 22, stdin); check_fread (22, n_actual_items); bytes_per_line = buffer[8] | (buffer[9] << 8); count[0] = buffer[18] | (buffer[19] << 8); count[1] = buffer[20] | (buffer[21] << 8); bytes_per_frame = (count[0] + count[1]) * bytes_per_line; assert (bytes_per_frame > 0 && bytes_per_frame < 50 * 2048); p = malloc (bytes_per_frame); assert (NULL != p); /* fseek() works w/pipe? */ n_actual_items = fread (p, 1, bytes_per_frame, stdin); check_fread (bytes_per_frame, n_actual_items); free (p); continue; } s->line = (fgetc (stdin) + 256 * fgetc (stdin)) & 0xFFF; if (index < 0) { fprintf (stderr, "Bad index in test stream\n"); exit (EXIT_FAILURE); } switch (index) { case 0: s->id = VBI_SLICED_TELETEXT_B; n_actual_items = fread (s->data, 1, 42, stdin); check_fread (42, n_actual_items); break; case 1: s->id = VBI_SLICED_CAPTION_625; n_actual_items = fread (s->data, 1, 2, stdin); check_fread (2, n_actual_items); break; case 2: s->id = VBI_SLICED_VPS; n_actual_items = fread (s->data, 1, 13, stdin); check_fread (13, n_actual_items); break; case 3: s->id = VBI_SLICED_WSS_625; n_actual_items = fread (s->data, 1, 2, stdin); check_fread (2, n_actual_items); break; case 4: s->id = VBI_SLICED_WSS_CPR1204; n_actual_items = fread (s->data, 1, 3, stdin); check_fread (3, n_actual_items); break; case 7: s->id = VBI_SLICED_CAPTION_525; n_actual_items = fread (s->data, 1, 2, stdin); check_fread (2, n_actual_items); break; default: fprintf (stderr, "\nUnknown data type %d " "in test stream\n", index); exit (EXIT_FAILURE); } ++s; } *n_lines = s - sliced; return 1; /* success */ } static void xds_filter_option (const char * optarg) { const char *s; /* Attention: may be called repeatedly. */ if (NULL == optarg || 0 == strcasecmp (optarg, "all")) { unsigned int i, j; for (i = 0; i < N_ELEMENTS (info[0]); ++i) { for (j = 0; j < N_ELEMENTS (info[0][0]); ++j) { info[0][i][j].print = TRUE; } } return; } s = optarg; while (0 != *s) { char buf[16]; unsigned int len; for (;;) { if (0 == *s) return; if (isalnum (*s)) break; ++s; } for (len = 0; len < N_ELEMENTS (buf) - 1; ++len) { if (!isalnum (*s)) break; buf[len] = *s++; } buf[len] = 0; if (0 == strcasecmp (buf, "timecode")) { info[0][1][1].print = TRUE; } else if (0 == strcasecmp (buf, "length")) { info[0][1][2].print = TRUE; } else if (0 == strcasecmp (buf, "title")) { info[0][1][3].print = TRUE; } else if (0 == strcasecmp (buf, "rating")) { info[0][1][5].print = TRUE; } else if (0 == strcasecmp (buf, "network")) { info[0][5][1].print = TRUE; } else if (0 == strcasecmp (buf, "call")) { info[0][5][2].print = TRUE; } else if (0 == strcasecmp (buf, "time")) { info[0][7][1].print = TRUE; } else if (0 == strcasecmp (buf, "timezone")) { info[0][7][4].print = TRUE; } else if (0 == strcasecmp (buf, "desc")) { info[0][1][0x10].print = TRUE; info[0][1][0x11].print = TRUE; info[0][1][0x12].print = TRUE; info[0][1][0x13].print = TRUE; info[0][1][0x14].print = TRUE; info[0][1][0x15].print = TRUE; info[0][1][0x16].print = TRUE; info[0][1][0x17].print = TRUE; } else { fprintf (stderr, "Unknown XDS info '%s'\n", buf); } } } static void usage (FILE * fp) { fprintf (fp, "\ " PROGRAM " " VERSION " -- Closed Caption and XDS decoder\n\ Copyright (C) 2003-2007 Mike Baker, Mark K. Kim, Michael H. Schimek\n\ ; Based on code by timecop@japan.co.jp.\n\ This program is licensed under GPL 2 or later. NO WARRANTIES.\n\n\ Usage: %s [options]\n\ Options:\n\ -? | -h | --help | --usage Print this message and exit\n\ -1 ... -4 | --cc1-file ... --cc4-file filename\n\ Append caption channel CC1 ... CC4 to this file\n\ -b | --no-webtv Do not print WebTV links\n\ -c | --cc Print Closed Caption (includes WebTV)\n\ -d | --device filename VBI device [/dev/vbi]\n\ -f | --filter type[,type]* Select XDS info: all, call, desc, length,\n\ network, rating, time, timecode, timezone,\n\ title. Multiple -f options accumulate. [all]\n\ -k | --keyword string Break caption line at this word (broken?).\n\ Multiple -k options accumulate.\n\ -l | --channel number Select caption channel 1 ... 4 [no filter]\n\ -p | --plain-ascii Print plain ASCII, else insert VT.100 color,\n\ italic and underline control codes\n\ -r | --raw line-number Dump raw VBI data\n\ -s | --sentences Decode caption by sentences\n\ -v | --verbose Increase verbosity\n\ -w | --window Open debugging window (with -r option)\n\ -x | --xds Print XDS info\n\ -C | --cc-file filename Append all caption to this file [stdout]\n\ -R | --semi-raw Dump semi-raw VBI data (with -r option)\n" #ifdef ENABLE_V4L2 "-S | --v4l2-sliced Capture sliced (not raw) VBI data [raw]\n" #endif "-X | --xds-file filename Append XDS info to this file [stdout]\n\ ", my_name); } static const char short_options [] = "?1:2:3:4:5:6:7:8:bcd:f:hkl:pr:stvwxC:RSX:"; #ifdef HAVE_GETOPT_LONG static const struct option long_options [] = { { "help", no_argument, NULL, '?' }, { "cc1-file", required_argument, NULL, '1' }, { "cc2-file", required_argument, NULL, '2' }, { "cc3-file", required_argument, NULL, '3' }, { "cc4-file", required_argument, NULL, '4' }, { "t1-file", required_argument, NULL, '5' }, { "t2-file", required_argument, NULL, '6' }, { "t3-file", required_argument, NULL, '7' }, { "t4-file", required_argument, NULL, '8' }, { "no-webtv", no_argument, NULL, 'b' }, { "cc", no_argument, NULL, 'c' }, { "device", required_argument, NULL, 'd' }, { "filter", required_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { "keyword", required_argument, NULL, 'k' }, { "channel", required_argument, NULL, 'l' }, { "plain-ascii", no_argument, NULL, 'p' }, { "raw", required_argument, NULL, 'r' }, { "sentences", no_argument, NULL, 's' }, { "test", no_argument, NULL, 't' }, { "verbose", no_argument, NULL, 'v' }, { "window", no_argument, NULL, 'w' }, { "xds", no_argument, NULL, 'x' }, { "usage", no_argument, NULL, 'u' }, { "cc-file", required_argument, NULL, 'C' }, { "semi-raw", no_argument, NULL, 'R' }, { "v4l2-sliced", no_argument, NULL, 'S' }, { "xds-file", required_argument, NULL, 'X' }, { NULL, 0, 0, 0 } }; #else # define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; static FILE * open_output_file (const char * name) { FILE *fp; if (NULL == name || 0 == strcmp (name, "-")) { fp = stdout; } else { fp = fopen (name, "a"); if (NULL == fp) { fprintf (stderr, "Couldn't open '%s' for appending: %s.\n", name, strerror (errno)); exit (EXIT_FAILURE); } } return fp; } int main(int argc,char **argv) { unsigned char buf[65536]; int arg; int args=0; #ifndef HAVE_ZVBI fd_set rfds; #endif int x; const char *device_file_name; const char *cc_file_name[8]; const char *xds_file_name; int verbose; int have_xds_filter_option; vbi_bool use_cc_filter; unsigned int i; unsigned int channels; #ifdef HAVE_ZVBI vbi_capture *cap; char *errstr; unsigned int services; int scanning; int strict; int ignore_read_error; vbi_raw_decoder *par; unsigned int src_w; unsigned int src_h; uint8_t *raw; vbi_sliced *sliced; struct timeval timeout; #endif my_name = argv[0]; setlocale (LC_ALL, ""); device_file_name = "/dev/vbi"; for (i = 0; i < 8; ++i) cc_file_name[i] = "-"; xds_file_name = "-"; verbose = 0; channels = 0; have_xds_filter_option = FALSE; use_cc_filter = FALSE; for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { case '?': case 'h': usage (stdout); exit (EXIT_SUCCESS); case '1' ... '8': assert (NULL != optarg); cc_file_name[c - '1'] = optarg; channels |= 1 << (c - '1'); use_cc_filter = TRUE; usecc=1; break; case 'b': usewebtv=0; /* sic, compatibility */ break; case 'c': usecc=1; break; case 'd': assert (NULL != optarg); device_file_name = optarg; break; case 'f': usexds = TRUE; xds_filter_option (optarg); have_xds_filter_option = TRUE; break; case 'l': { long ch; assert (NULL != optarg); ch = strtol (optarg, NULL, 0); if (ch < 1 || ch > 8) { fprintf (stderr, "Invalid channel number %ld, " "should be 1 ... 8.\n", ch); exit (EXIT_FAILURE); } channels |= 1 << (ch - 1); use_cc_filter = TRUE; usecc=1; break; } case 'k': keyword[keywords++]=optarg; break; case 'p': plain=1; xds_info_prefix = "% "; xds_info_suffix = "\n"; break; case 'r': assert (NULL != optarg); useraw=1; rawline=atoi(optarg); break; case 's': usesen=1; break; case 't': test=1; break; case 'v': ++verbose; break; case 'w': debugwin=1; break; case 'x': usexds=1; break; case 'C': assert (NULL != optarg); for (i = 0; i < 8; ++i) cc_file_name[i] = optarg; usecc=1; break; case 'R': semirawdata=1; break; case 'S': opt_v4l2_sliced = TRUE; break; case 'X': assert (NULL != optarg); xds_file_name = optarg; break; default: usage (stderr); exit (EXIT_FAILURE); } } if (!(usecc | usexds | useraw)) { fprintf (stderr, "Give one of the -c, -x or -r options " "or -h for help.\n"); exit (EXIT_FAILURE); } if (usecc && 0 == channels) channels = 0x01; if (usexds && !have_xds_filter_option) xds_filter_option ("all"); #ifdef HAVE_ZVBI errstr = NULL; /* What we want. */ services = VBI_SLICED_CAPTION_525; /* This is a hint in case the device can't tell the current video standard. */ scanning = 525; /* Strict sampling parameter matching: 0, 1, 2 */ strict = 1; ignore_read_error = 1; do { if (test) { break; } /* Linux */ if (opt_v4l2_sliced) { if (open_v4l2_sliced (device_file_name)) { break; } else { opt_v4l2_sliced = FALSE; } } /* DVB interface omitted, doesn't support NTSC/ATSC. */ cap = vbi_capture_v4l2_new (device_file_name, /* buffers */ 5, &services, strict, &errstr, !!verbose); if (cap) { break; } fprintf (stderr, "Cannot capture vbi data with v4l2 interface:\n" "%s\nWill try v4l.\n", errstr); free (errstr); cap = vbi_capture_v4l_new (device_file_name, scanning, &services, strict, &errstr, !!verbose); if (cap) break; fprintf (stderr, "Cannot capture vbi data with v4l interface:\n" "%s\n", errstr); /* FreeBSD to do */ free (errstr); exit(EXIT_FAILURE); } while (0); if (test || opt_v4l2_sliced) { src_w = 1440; src_h = 50; } else { par = vbi_capture_parameters (cap); assert (NULL != par); src_w = par->bytes_per_line / 1; src_h = par->count[0] + par->count[1]; if (useraw && (unsigned int) rawline >= src_h) { fprintf (stderr, "-r must be in range 0 ... %u\n", src_h - 1); exit (EXIT_FAILURE); } } raw = calloc (1, src_w * src_h); sliced = malloc (sizeof (vbi_sliced) * src_h); assert (NULL != raw); assert (NULL != sliced); /* How long to wait for a frame. */ timeout.tv_sec = 2; timeout.tv_usec = 0; #else opt_v4l2_sliced = FALSE; if ((vbifd = open(device_file_name, O_RDONLY)) < 0) { perror(vbifile); exit(1); } #endif if (usecc) { for (i = 0; i < 8; ++i) { if (channels & (1 << i)) cc_fp[i] = open_output_file (cc_file_name[i]); } } if (usexds) xds_fp = open_output_file (xds_file_name); for (x=0;x 2) fprintf (stderr, "No data in this frame\n"); for (i = 0; i < n_lines; ++i) { unsigned int c1, c2; c1 = sliced[i].data[0]; c2 = sliced[i].data[1]; if (verbose > 2) fprintf (stderr, "Line %3d %02x %02x\n", sliced[i].line, c1, c2); /* No need to check sliced[i].id because we requested only caption. */ if (21 == sliced[i].line) { field = 0; caption_filter (c1, c2); if (!in_xds[field]) { /* fields swapped? */ if (usecc) CCdecode(c1 + c2 * 256); if (usesen) sentence(c1 + c2 * 256); } if (usexds) /* fields swapped? */ XDSdecode(c1 + c2 * 256); } else if (284 == sliced[i].line) { field = 1; caption_filter (c1, c2); if (!in_xds[field]) { if (usecc) CCdecode(c1 + c2 * 256); if (usesen) sentence(c1 + c2 * 256); } if (usexds) XDSdecode(c1 + c2 * 256); } } #ifndef X_DISPLAY_MISSING if (debugwin) { XFlush(dpy); usleep(100); } #endif } #else /* !HAVE_ZVBI */ //mainloop while(1){ FD_ZERO(&rfds); FD_SET(vbifd, &rfds); select(FD_SETSIZE, &rfds, NULL, NULL, NULL); if (FD_ISSET(vbifd, &rfds)) { if (read(vbifd, buf , 65536)!=65536) printf("read error\n"); if (useraw) { #ifndef X_DISPLAY_MISSING if (debugwin) { XClearArea(dpy,Win,0,0,1024,128,0); XDrawLine(dpy,Win,WinGC1,0,128-85/2,1024,128-85/2); for (x=0;x<1024;x++) if (buf[2048 * rawline+x*2]/2<128 && buf[2048 * rawline+x*2+2]/2 < 128) XDrawLine(dpy,Win,WinGC0,x,128-buf[2048 * rawline+x*2]/2, x+1,128-buf[2048 * rawline+x*2+2]/2); } #endif print_raw(decode(&buf[2048 * rawline])); #ifndef X_DISPLAY_MISSING if (debugwin) { XFlush(dpy); usleep(100); } #endif } if (usexds) XDSdecode(decode(&buf[2048 * 27])); if (usecc) CCdecode(decode(&buf[2048 * 11])); if (usesen) sentence(decode(&buf[2048 * 11])); #ifndef X_DISPLAY_MISSING if (debugwin) { XFlush(dpy); usleep(100); } #endif } } #endif /* !HAVE_ZVBI */ return 0; } zvbi-0.2.44/contrib/x11font.c000066400000000000000000000061621476363111200156700ustar00rootroot00000000000000/* Copyright (C) 2002 Gerd Knorr */ /* $Id: x11font.c,v 1.5 2006-02-10 06:25:36 mschimek Exp $ */ #include "src/exp-gfx.c" static void print_head(FILE *fp, const char *foundry, const char *name, const char *slant, int width, int height) { fprintf(fp, "STARTFONT 2.1\n" "FONT -%s-%s-medium-%s-normal--%d-%d-75-75-c-%d-iso10646-1\n" "SIZE %d 75 75\n" "FONTBOUNDINGBOX 6 13 0 -2\n" "STARTPROPERTIES 25\n" "FONTNAME_REGISTRY \"\"\n" "FOUNDRY \"%s\"\n" "FAMILY_NAME \"%s\"\n" "WEIGHT_NAME \"medium\"\n" "SLANT \"%s\"\n" "SETWIDTH_NAME \"normal\"\n" "ADD_STYLE_NAME \"\"\n" "PIXEL_SIZE %d\n" "POINT_SIZE %d\n" "RESOLUTION_X 75\n" "RESOLUTION_Y 75\n" "SPACING \"c\"\n" "AVERAGE_WIDTH %d\n" "CHARSET_REGISTRY \"iso10646\"\n" "CHARSET_ENCODING \"1\"\n" "COPYRIGHT \"fixme\"\n" "CAP_HEIGHT 9\n" "X_HEIGHT 18\n" "FONT \"-%s-%s-medium-%s-normal--%d-%d-75-75-c-%d-iso10646-1\"\n" "WEIGHT 10\n" "RESOLUTION 103\n" "QUAD_WIDTH %d\n" "DEFAULT_CHAR 0\n" "FONT_ASCENT %d\n" "FONT_DESCENT 0\n" "ENDPROPERTIES\n", foundry,name,slant,height,height*10,width*10, height, foundry,name,slant,height,height*10,width*10, foundry,name,slant,height,height*10,width*10, width,height); } static void print_font(const char *filename, const char *foundry, const char *name, int italic, uint8_t *font, int cw, int ch, int cpl, int count, unsigned int (*map)(unsigned int,int), int invalid) { FILE *fp; int x,y,i,c,on,bit,byte,mask1,mask2; fp = stdout; if (NULL != filename) { fp = fopen(filename,"w"); if (NULL == fp) fprintf(stderr,"open %s: %s\n",filename,strerror(errno)); fprintf(stderr,"writing %s\n",filename); } print_head(fp, foundry, name, italic ? "i" : "r", cw, ch); fprintf(fp,"CHARS %d\n", count); for (i = 0; i < 0xffff; i++) { c = map(i, italic); if (invalid == c) continue; fprintf(fp,"STARTCHAR fixme\n" "ENCODING %d\n" "SWIDTH %d 0\n" "DWIDTH %d 0\n" "BBX %d %d 0 0\n" "BITMAP\n", i,cw*10,cw, cw,ch); for (y = 0; y < ch; y++) { bit = cpl * cw * y + cw * c; byte = 0; for (x = 0; x < cw; x++) { mask1 = 1 << (bit & 7); mask2 = 1 << (7-(x & 7)); on = font[bit >> 3] & mask1; if (on) byte |= mask2; if (7 == (x&7)) { fprintf(fp,"%02x",byte); byte = 0; } bit++; } fprintf(fp,"%02x\n",byte); } fprintf(fp,"ENDCHAR\n"); } fprintf(fp,"ENDFONT\n"); if (NULL != filename) fclose(fp); } int main () { print_font("teletext.bdf","ets","teletext",0,(uint8_t *) wstfont2_bits, TCW,TCH,TCPL,1448,unicode_wstfont2,357); print_font("teletexti.bdf","ets","teletext",1,(uint8_t *) wstfont2_bits, TCW,TCH,TCPL,1449,unicode_wstfont2,357); print_font("caption.bdf","ets","caption",0,(uint8_t *) ccfont2_bits, CCW,CCH,CCPL,120,unicode_ccfont2,15); print_font("captioni.bdf","ets","caption",1,(uint8_t *) ccfont2_bits, CCW,CCH,CCPL,120,unicode_ccfont2,15 + 4 * 32); return 0; } zvbi-0.2.44/contrib/zvbi-atsc-cc.1000066400000000000000000000200551476363111200165700ustar00rootroot00000000000000.TH zvbi-atsc-cc 1 .SH NAME zvbi-atsc-cc - ATSC Closed Caption decoder .SH SYNOPSIS .B zvbi-atsc-cc [ options ] [-n] station name .SH DESCRIPTION .B zvbi\-atsc\-cc captures ATSC TV transmissions using a Linux DVB device and decodes the enclosed Closed Caption data. It can record both NTSC caption (EIA 608-B) and DTVCC caption (CEA 708-C). .SH OPTIONS .IP "\fB\-?\fP \fB\-h\fP \fB\-\-help\fP \fB\-\-usage\fP" Print a short usage message. .IP "\fB\-q\fP \fB\-\-quiet\fP" Suppress all progress and error messages. .IP "\fB\-v\fP \fB\-\-verbose\fP" Increase verbosity. .SS "Device options" .IP "\fB\-a\fP number" .PD 0 .IP "\fB\-\-adapter\-num\fP number" .PD Select a different DVB device to capture the signal (/dev/dvb/adapterN). The default is adapter 0. .IP "\fB\-i\fP number" .PD 0 .IP "\fB\-\-frontend\-id\fP number" .PD Select a different frontend on the DVB device (/dev/dvb/adapterN/frontendM). Most devices have only one frontend. The default is frontend 0. .IP "\fB\-d\fP number" .PD 0 .IP "\fB\-\-demux\-id\fP number" .PD Select a different demultiplexer on the DVB device (/dev/dvb/adapterN/demuxM). Most devices have only one demultiplexer. The default is demultiplexer 0. .IP "\fB\-r\fP number" .PD 0 .IP "\fB\-\-dvr\-id\fP number" .PD Select a different DVR interface on the DVB device (/dev/dvb/adapterN/dvrM). Most devices have only one DVR interface. The default is DVR interface 0. .IP "\fB\-T\fP" .PD 0 .IP "\fB\-\-ts\fP" .PD Decode an MPEG-2 Transport Stream on standard input instead of opening a DVB device. This option is mainly intended for debugging. .IP Since Transport Streams can contain multiple programs you must still specify one or more station names, which .B zvbi-atsc-cc will look up in a channel configuration file to determine the Program ID of the video elementary stream or streams it should extract. You should also add the .B --atsc option to clarify that this is an ATSC TS and the program should expect an .B azap channel configuration file, which is otherwise inferred from the DVB device capabilities. .SS "Tuning options" .IP "\fB\-e\fP file name" .PD 0 .IP "\fB\-\-channel\-conf\fP file name" .PD To tune in a TV station .B zvbi-atsc-cc needs a channel configuration file. We use the config file of the .B azap tuning tool from the linuxtv\-dvb\-apps package. You can create it with the .B scan tool from the same package. .IP The azap channel configuration file is a text file which lists one station per line. Each line contains six fields separated by a colon: The station name, the transponder frequency in Hz, the modulation used (8VSB, 16VSB, QAM_64, QAM_256), the video stream PID, the audio stream PID, and the service ID. Empty lines and lines starting with a number sign will be ignored. .IP This option selects a different channel configuration file. The default is .nf .I $HOME/.azap/channels.conf .fi .IP "\fB\-L\fP \fB\-\-list\fP" List all stations in the channel configuration file and their transponder frequency. .IP "\fB\-n\fP station name" .PD 0 .IP "\fB\-\-station\fP station name" .PD The station to tune in. Usually the \fB\-n\fP can be omitted. .IP .B zvbi-atsc-cc can record caption from multiple stations at once if they share a transponder frequency. Just specify multiple station names on the command line to enable this. .SS "Caption options" With the caption and XDS options you specify which data .B zvbi-atsc-cc should extract. If multiple station names are given these options apply to the succeeding station name. You must specify at least one of these options for each station. .IP "\fB\-c\fP \fB\-\-cc\fP" Print any received caption on standard output. .IP "\fB\-C\fP file name" .PD 0 .IP "\fB\-\-cc\-file\fP file name" .PD Append any received caption to the specified file. .IP "\fB\-l\fP number" .PD 0 .IP "\fB\-\-channel\fP number" .PD Print NTSC Closed Caption channel 1, 2, 3 or 4 on standard output. .IP "\fB\-1\fP ... \fB\-4\fP file name" .PD 0 .IP "\fB\-\-cc1\-file\fP ... \fB\-\-cc4\-file\fP file name" .PD Filter out NTSC Closed Caption channel CC1 ... CC4 and append the text to the specified file. CC1 is the primary, CC3 the secondary caption service. If both services are transmitted CC1 usually carries English, CC3 Spanish caption. .IP "\fB\-5\fP ... \fB\-8\fP file name" .PD 0 .IP "\fB\-\-t1\-file\fP ... \fB\-\-t4\-file\fP file name" .PD Filter out NTSC Text service T1 ... T4 and append the text to the specified file. .IP "\fB\-9\fP ... \fB\-0\fP file name" .PD 0 .IP "\fB\-\-s1\-file\fP ... \fB\-\-s2\-file\fP file name" .PD Filter out DTVCC service 1 or 2 and append the text to the specified file. Service 1 is the primary, service 2 the secondary caption service. If both services are transmitted service 1 usually carries English, service 2 Spanish caption. .IP Digital TV stations are supposed to transmit language codes and other information about the available caption services but these are not presently evaluated. .B zvbi\-atsc\-cc filters out text which does not appear to be caption, such as tickers or vertically printed text. .IP "\fB\-b\fP \fB\-\-no-webtv\fP" Do not print WebTV links. .IP "\fB\-m\fP \fB\-\-timestamps\fP" Prepend timestamps YYYYMMDDHHMMSS to caption lines. This is the system time in the UTC timezone when the text was captured, specifically the Carriage Return or Pop-On code responsible for printing the line. .IP "\fB\-S\fP \fB\-\-stream-time\fP" .PD 0 .IP "\fB\-S\fPdate \fB\-\-stream-time\fP=date" .PD Prepend timestamps YYYYMMDDHHMMSS to caption lines generated from the timestamps transmitted in the video stream. If date is given, also in the format YYYYMMDDHHMMSS, it will be added to the timestamps, otherwise the counter starts at 0001-01-01 00:00:00. .IP "\fB\-s\fP \fB\-\-sentences\fP" Print caption one sentence at a time. .IP "\fB\-j\fP type" .PD 0 .IP "\fB\-\-format\fP type" .PD When type is "plain" .B zvbi\-atsc\-cc prints caption and XDS text without any markup. When type is "vt100" it faithfully reproduces the caption foreground and background color, italic and underline attributes by inserting VT.100 terminal control codes. With type "ntsc-cc" it mimics the output of the .B ntsc-cc and .B zvbi-ntsc-cc tool. The default is "ntsc-cc". .IP .B zvbi\-atsc\-cc supports all Closed Caption character sets and converts to the encoding of the current locale, usually UTF-8. .IP "\fB\-p\fP \fB\-\-plain\fP" Same as \fB\-j\ plain\fP .SS "XDS options" .IP "\fB\-x\fP \fB\-\-xds\fP" Print all received XDS data on standard output. .IP "\fB\-X\fP file name" .PD 0 .IP "\fB\-\-xds\-file\fP file name" .PD Filter out eXtended Data Service data (station name, program name, program rating etc.) and append it as text to the specified file. .IP "\fB\-f\fP type[,type]*" .PD 0 .IP "\fB\-\-filter\fP type[,type]*" .PD Filter out specific XDS information: all, call (station call sign), desc (program synopsis), length, network, rating, time, timecode, timezone, title. Multiple \fB-f\fP options accumulate. The default is "all". .SH EXAMPLES .nf zvbi-atsc-cc \-c NJN-HD .P zvbi-atsc-cc \-\-cc1-file wnyw.txt WNYW-DT \-\-cc1-file wwor.txt WWOR-DT .P (NJN-HD, WNYW-DT and WWOR-DT are TV stations in New York. WNYW-DT and WWOR-DT can be captured simultaneously because they share a transponder frequency.) .P zvbi-atsc-cc \-\-atsc \-\-ts \-c CNN < recorded-from-cnn.ts .fi .SH FILES .I $HOME/.azap/channels.conf .SH SEE ALSO zvbi-ntsc-cc, azap .P http://zapping.sourceforge.net .SH AUTHORS Michael H. Schimek (mschimek AT users.sourceforge.net) .br timecop@japan.co.jp .br Mike Baker .br Mark K. Kim .SH COPYRIGHT This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. zvbi-0.2.44/contrib/zvbi-ntsc-cc.1000066400000000000000000000020031476363111200165760ustar00rootroot00000000000000.TH zvbi-ntsc-cc 1 .SH NAME zvbi-ntsc-cc - closed caption decoder .SH SYNOPSIS .B zvbi-ntsc-cc [ options ] .SH DESCRIPTION .B zvbi-ntsc-cc reads vbi data from /dev/vbi and decodes the enclosed cc data. Start it with '\-h' to get a list of cmd line options. .SH AUTHORS timecop@japan.co.jp .br Mike Baker .br Adam .SH COPYRIGHT This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .P This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. .P You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. zvbi-0.2.44/daemon/000077500000000000000000000000001476363111200140225ustar00rootroot00000000000000zvbi-0.2.44/daemon/Makefile.am000066400000000000000000000006451476363111200160630ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in bin_PROGRAMS = zvbi-chains zvbi_chains_SOURCES = chains.c sbin_PROGRAMS = zvbid zvbid_SOURCES = proxyd.c man_MANS = \ zvbid.1 \ zvbi-chains.1 EXTRA_DIST = \ $(man_MANS) \ zvbid.init.in AM_CPPFLAGS = \ -I$(top_srcdir) \ -DLIBZVBI_CHAINS_PATH=\"libzvbi-chains.so.0\" \ -D_REENTRANT \ -D_GNU_SOURCE LDADD = \ $(top_builddir)/src/libzvbi.la \ $(LIBS) zvbi-0.2.44/daemon/README000066400000000000000000000152341476363111200147070ustar00rootroot00000000000000About Linux "VBI proxy" ----------------------- With "video4linux" drivers only one application at a time can capture VBI data. The 2nd generation "v4l2" API allows multiple clients to open a device, but still only one client may read from the device. If for example the Nxtvepg daemon runs in the background users will not be able to start a Teletext application. The VBI proxy was developed as a solution to this problem. VBI can transport many independent data streams: VPS, WSS, Closed Caption and Teletext, within them PDC, XDS and custom data streams providing such services as Teletext, subtitles, caption, NexTView and other Electronic Programme Guides, channel identification, video recording triggers, information about the current program, wide screen signalling and more. We want to share one VBI stream between for example an application opening the device to update the system clock, Teletext viewers, TV viewers displaying channel names, caption or subtitles, recording applications, a PVR watching channels for recording triggers, and an EPG daemon updating its database. The proxy must assign higher priority to recording and lower priority to background applications. It must coordinate channel changes between background "data harvesting" applications. Such apps would register a channel request with the proxy together with a priority and expected time of use and the proxy will then grant those requests according to priority and previous grants in a round-robin way. How does it work? The VBI proxy is implemented as a daemon "zvbid", which usually will be started during system boot. The daemon remains dormant until the first proxy client connects through a socket, then opens the VBI device and starts capturing and decoding the services requested by the client, e.g. Teletext. It will not pass raw but sliced VBI data, reducing the required bandwidth and saving repeated decoding of data needed by multiple clients. One drawback of this solution is that clients must be adapted, i.e. instead of opening the VBI device as usual, clients have to connect to the daemon (but if the daemon is not running they should fall back to using the device directly). For most applications already using libzvbi the change is minimal: A single function call is enough to create a proxy client and connect to the server, another to start capturing. The API to capture and decode services is identical for v4l2, v4l, bktr, dvb and proxy devices. Legacy applications can often be ported simply by replacing open and read system calls with their libzvbi equivalents. As a work-around for applications which cannot be adapted, a wrapper "zvbi-chains" is available which intercepts access to the VBI device and redirects it to the proxy. In this case however only raw VBI data can be forwarded, resulting in a significantly higher CPU load. Hopefully the solution is elegant enough to convince the authors to invest the necessary effort to switch to libzvbi (or at least implement the proxy protocol) in subsequent releases of their software. Current status -------------- The proxy may still need improvement, see below for a detailed list of open points, but at least the basic service works well, and the API will remain backwards compatible. If you want to try it out, libzvbi is available from http://zapping.sourceforge.net/. Some applications using the proxy: - The libzvbi test directory contains a small program "proxy-test" which can read and dump Teletext and VPS data from the proxy. Try -help for a list of options. - The Nxtevpg EPG viewer (http://nxtvepg.sourceforge.net/) uses the proxy when compiled with -DUSE_LIBZVBI in the Makefile. - The Zapping 0.8 TV viewer and Teletext browser (http://zapping.sourceforge.net) has basic proxy support and will use the proxy automatically when installed and running. - The AleVT 1.6.1 Teletext browser and Xawdecode TV viewer (http://xawdecode.sourceforge.net/download/) use the proxy with the following patches: http://nxtvepg.sourceforge.net/tmp/alevt-1.6.1-libzvbi.diff http://nxtvepg.sourceforge.net/tmp/alevtd-3.91-vbiproxy.diff AleVT is also known to work unmodified with zvbi-chains. With proxy you can run these applications (or multiple instances of them, perhaps only pure VBI apps - YMMV) in parallel. TODO list --------- This is an unordered list of open points (unimplemented or yet undecided) regarding the proxy. Any comments or help appreciated. - Optimize raw buffer forward, possibly based on shared memory to avoid copying -> requires user-space DMA in io-v4l2k (i.e. streaming into buffers allocated by the user) - Device permissions are implemented by copying group id and access mask of the device. This is not portable however according to man socket(7). Possibly a sub-directory should be used and permissions set on the directory. Disadvantage: other people cannot clean up (same for socket file if sticky bit is set). Or an X11 way a key file could be used. - Save errno upon device errors deep down, e.g. in proxy-msg.c into io struct to make sure it's not overwritten higher up in the return path - Log daemon's errors and warnings in syslog (esp. protocol violations in clients, such as not returning token on request) - Channel change handling and scheduling not fully implemented yet; needs more testing - Option: allow user to override or fine-tune channel priority requests (on side of daemon, based on client-names) - Inform clients about video norm changes: e.g. sampling rate may change (only relevant for raw data though), frame rate changes -> must be considered when evaluating time stamps to detect dropped frames; number of VBI lines may change, client may want to change service IDs. Probably only for clients which explicitly enable this; others could simply be starved. - io-bktr.c not supported at all yet for new features. Note bktr is weird(tm), for example one cannot capture VBI without video. The proxy is not officially supported on FreeBSD. - Possibly add a generic capture client config function to: + set video device path (v4l1 for channel changes & norm query) + sequence counter instead of time stamp in vbi_capture_buffer (v4l1 bttv) Alternatively libzvbi 0.3 may provide sampling time and stream time, the latter derived from sequence counters for analog devices. + query time between frames; query timestamp from driver or after read() + dynamically change buffer count (v4l2 only) + optionally use read even if streaming is available - Possible v4l2 API extensions: + channel change event notification + CGUNIT to check if two nodes refer to the same piece of hardware + EIO from DQBUF: timeout, buffer must be requeued zvbi-0.2.44/daemon/chains.c000066400000000000000000000150241476363111200154350ustar00rootroot00000000000000/* * VBI proxy wrapper for proxy-unaware clients * * Copyright (C) 2004 Tom Zoerner * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * * Description: * * This is a small wrapper which executes the VBI application given * on the command line while overloading several C library calls * (such as open(2) and read(2)) so that the application can be forced * to access VBI devices via the VBI proxy instead of device files * directly. * * LD_PRELOAD is used to intercept C library calls and call functions * in the libvbichain shared library instead. Parameters given on the * command line (e.g. device path) are passed to the library by means * of environment variables. * * $Log: not supported by cvs2svn $ * Revision 1.3 2007/11/27 17:39:34 mschimek * *** empty log message *** * * Revision 1.2 2006/05/22 09:02:43 mschimek * s/vbi_asprintf/asprintf. * * Revision 1.1 2004/10/25 16:52:43 mschimek * main: Replaced sprintf by asprintf and fixed p_env3. * Added from proxy-18.bak. * */ static const char rcsid[] = "$Id: chains.c,v 1.4 2008-07-26 06:22:28 mschimek Exp $"; #include "config.h" #ifdef ENABLE_PROXY #include #include #include #include #include #include #include #include #include #include #include #include #include "src/misc.h" /* asprintf() */ #define dprintf1(fmt, arg...) do {if (opt_debug_level >= 1) fprintf(stderr, "proxyd: " fmt, ## arg);} while (0) #define dprintf2(fmt, arg...) do {if (opt_debug_level >= 2) fprintf(stderr, "proxyd: " fmt, ## arg);} while (0) static char * opt_vbi_device = ""; static int opt_debug_level = 0; /* --------------------------------------------------------------------------- ** Print usage and exit */ static void usage_exit( const char *argv0, const char *argvn, const char * reason ) { fprintf(stderr, "%s: %s: %s\n" "Usage: %s [options ...] command ...\n" " -dev : VBI device path (default: any VBI device)\n" " -debug : enable debug output: 1=warnings, 2=all\n" " -help : this message\n" " -- : stop option processing\n", argv0, reason, argvn, argv0); exit(1); } /* --------------------------------------------------------------------------- ** Parse numeric value in command line options */ static int parse_argv_numeric( char * p_number, int * p_value ) { char * p_num_end; if (*p_number != 0) { *p_value = strtol(p_number, &p_num_end, 0); return (*p_num_end == 0); } else return 0; } /* --------------------------------------------------------------------------- ** Parse command line options */ static void parse_argv( int argc, char * argv[], int * p_arg_off ) { struct stat stb; int arg_val; int arg_idx = 1; while (arg_idx < argc) { if (strcasecmp(argv[arg_idx], "-dev") == 0) { if (arg_idx + 1 < argc) { if (stat(argv[arg_idx + 1], &stb) == -1) usage_exit(argv[0], argv[arg_idx +1], strerror(errno)); if (!S_ISCHR(stb.st_mode)) usage_exit(argv[0], argv[arg_idx +1], "not a character device"); if (access(argv[arg_idx + 1], R_OK | W_OK) == -1) usage_exit(argv[0], argv[arg_idx +1], strerror(errno)); opt_vbi_device = argv[arg_idx + 1]; arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing mode keyword after"); } else if (strcasecmp(argv[arg_idx], "-debug") == 0) { if ((arg_idx + 1 < argc) && parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_debug_level = arg_val; arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing debug level after"); } else if (strcasecmp(argv[arg_idx], "-help") == 0) { usage_exit(argv[0], "", "the following options are available"); } else if (strcmp(argv[arg_idx], "--") == 0) { arg_idx += 1; break; } else if (*argv[arg_idx] == '-') { usage_exit(argv[0], argv[arg_idx], "unknown option or argument"); } else break; } if (arg_idx >= argc) { usage_exit(argv[0], "", "name of application to launch is missing"); } * p_arg_off = arg_idx; } /* ---------------------------------------------------------------------------- ** Main */ #define putenv_printf(bp, tmpl, args...) \ do { \ asprintf (&(bp), tmpl ,##args ); \ assert (NULL != (bp)); \ putenv (bp); \ } while (0) int main( int argc, char ** argv ) { int arg_off; char * p_old_preload; char * p_env1; char * p_env2; char * p_env3; char * p_env4; parse_argv(argc, argv, &arg_off); putenv_printf(p_env1, "VBIPROXY_DEVICE=%s", opt_vbi_device); putenv_printf(p_env2, "VBIPROXY_DEBUG=%d", opt_debug_level); putenv_printf(p_env3, "VBIPROXY_CLIENT=%s [vbi-chains]", argv[arg_off]); p_old_preload = getenv("LD_PRELOAD"); if (p_old_preload == NULL) { /* no preload defined yet */ putenv_printf(p_env4, "LD_PRELOAD=%s", LIBZVBI_CHAINS_PATH); } else { /* prepend preload to existing definition */ putenv_printf(p_env4, "LD_PRELOAD=%s:%s", LIBZVBI_CHAINS_PATH, p_old_preload); } if (opt_debug_level > 0) { fprintf(stderr, "vbi-chains: Environment set-up:\n" "\t%s\n\t%s\n\t%s\n\t%s\n", p_env1, p_env2, p_env3, p_env4); } execvp(argv[arg_off], argv + arg_off); fprintf(stderr, "vbi_chains: Failed to start %s: %s\n", argv[arg_off], strerror(errno)); exit(-1); return -1; } #endif /* ENABLE_PROXY */ zvbi-0.2.44/daemon/proxyd.c000066400000000000000000003270371476363111200155270ustar00rootroot00000000000000/* * VBI proxy daemon * * Copyright (C) 2002-2004 Tom Zoerner (and others) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * * Description: * * This is the main module of the VBI proxy daemon. Please refer to * the manual page for information on the daemon's general purpose. * * When started, the daemon will at first only create a named socket in * /tmp for the devices given on the command line and wait for client * connections. When a client connects the VBI device is opened and * configured for the requested services. If no services are requested, * the device is still opened, but acquisition not started. When more * clients connect, the daemon will reset service parameters and add them * newly to the slicer in order of connection times, adjusting VBI device * parameters as required and possible (e.g. enlarging VBI window.) * * Client handling was originally derived from alevtd by Gerd Knorr, then * adapted/extended for nxtvepg and again adapted/reduced for the VBI proxy * by Tom Zoerner. * * * $Log: not supported by cvs2svn $ * Revision 1.19 2008/07/26 06:22:24 mschimek * Changed the license to GPLv2+ with Tom's permission. * * Revision 1.18 2007/11/27 17:39:35 mschimek * *** empty log message *** * * Revision 1.17 2007/08/27 06:45:24 mschimek * vbi_proxyd_take_service_req, vbi_proxyd_take_message, * vbi_proxyd_take_message: Replaced strncpy() by the faster a safer * strlcpy(). * vbi_proxyd_signal_handler, vbi_proxyd_parse_argv: Replaced sprintf() by * the safer snprintf(). * * Revision 1.16 2006/05/22 09:01:53 mschimek * s/vbi_asprintf/asprintf. * * Revision 1.15 2006/02/10 06:25:36 mschimek * *** empty log message *** * * Revision 1.14 2005/01/20 01:39:15 mschimek * gcc 4.0 char pointer signedness warnings. * * Revision 1.13 2004/12/30 02:26:02 mschimek * printf ptrdiff_t fixes. * * Revision 1.12 2004/11/07 10:52:01 mschimek * dprintf: s/proxyd/zvbid. * * Revision 1.11 2004/10/25 16:56:26 mschimek * *** empty log message *** * * Revision 1.10 2004/10/24 18:15:33 tomzo * - added handling of norm changes * - adapted for interface change to proxy-msg.c (split socket read/write func) * - improved debug level handling (command line option -debug) * */ static const char rcsid[] = "$Id: proxyd.c,v 1.20 2013-08-28 14:45:58 mschimek Exp $"; #include "config.h" #ifdef ENABLE_PROXY #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #include #include #include #include #include #include "src/vbi.h" #include "src/inout.h" #include "src/bcd.h" #include "src/proxy-msg.h" #ifdef ENABLE_V4L2 #include #include "src/videodev2k.h" /* for setting device priority */ #endif #define DBG_MSG 1 #define DBG_QU 2 #define DBG_CLNT 4 #define DBG_SCHED 8 #define dprintf(flags, fmt, arg...) \ do { \ if (opt_debug_level & (flags)) \ fprintf (stderr, "zvbid: " fmt, ## arg); \ } while (0) /* Macro to cast (void *) to (int) and backwards without compiler warning ** (note: 64-bit compilers warn when casting a pointer to an int) */ #define PVOID2INT(X) ((int)((long)(X))) #define INT2PVOID(X) ((void *)((long)(X))) /* ---------------------------------------------------------------------------- ** This struct is one element in the slicer data queue */ typedef struct PROXY_QUEUE_s { struct PROXY_QUEUE_s * p_next; unsigned int ref_count; unsigned int use_count; int max_lines; int line_count; double timestamp; void * p_raw_data; vbi_sliced lines[1]; } PROXY_QUEUE; #define QUEUE_ELEM_SIZE(Q,C) (sizeof(PROXY_QUEUE) + (sizeof(vbi_sliced) * ((C) - 1))) /* ---------------------------------------------------------------------------- ** Declaration of types of internal state variables */ /* Note mutex conventions: ** - mutex are only required for v4l devices which do not support select(2), ** because only then a separate thread is started which blocks in read(2) ** - when both the client chain and a slicer queue mutex is required, the ** client mutex is acquired first; order is important to prevent deadlocks ** - the master thread locks the client chain mutex only for write access, ** i.e. if a client is added or removed */ typedef enum { REQ_TOKEN_NONE, /* this client is not allowed to switch channels */ REQ_TOKEN_RECLAIM, /* return of token will be requested */ REQ_TOKEN_RELEASE, /* waiting for client to release token */ REQ_TOKEN_GRANT, /* this client will be sent the token a.s.a.p. */ REQ_TOKEN_GRANTED, /* this client currently holds the token */ REQ_TOKEN_RETURNED /* this client has returned the token, but still 'owns' the channel */ } REQ_TOKEN_STATE; #define REQ_CONTROLS_CHN(X) ((X) >= REQ_TOKEN_GRANTED) /* client channel control scheduler state */ typedef struct { REQ_TOKEN_STATE token_state; vbi_bool is_completed; int cycle_count; time_t last_start; time_t last_duration; } VBIPROXY_CHN_STATE; /* client connection state */ typedef enum { REQ_STATE_WAIT_CON_REQ, REQ_STATE_WAIT_CLOSE, REQ_STATE_FORWARD, REQ_STATE_CLOSED, } REQ_STATE; #define SRV_MAX_DEVICES 4 #define VBI_MAX_BUFFER_COUNT 32 #define VBI_MIN_STRICT -1 #define VBI_MAX_STRICT 2 #define VBI_GET_SERVICE_P(PREQ,STRICT) ((PREQ)->services + (signed)(STRICT) - VBI_MIN_STRICT) #define VBI_RAW_SERVICES(SRV) (((SRV) & (VBI_SLICED_VBI_625 | VBI_SLICED_VBI_525)) != 0) /* this struct holds client-specific state and parameters */ typedef struct PROXY_CLNT_s { struct PROXY_CLNT_s * p_next; REQ_STATE state; VBIPROXY_MSG_STATE io; vbi_bool endianSwap; VBI_PROXY_CLIENT_FLAGS client_flags; int dev_idx; VBIPROXY_MSG msg_buf; unsigned int services[VBI_MAX_STRICT - VBI_MIN_STRICT + 1]; unsigned int all_services; int vbi_start[2]; int vbi_count[2]; int buffer_count; vbi_bool buffer_overflow; PROXY_QUEUE * p_sliced; vbi_channel_profile chn_profile; VBIPROXY_CHN_STATE chn_state; VBI_CHN_PRIO chn_prio; VBI_PROXY_CHN_FLAGS chn_status_ind; } PROXY_CLNT; /* this struct holds the state of a device */ typedef struct { const char * p_dev_name; char * p_sock_path; int pipe_fd; vbi_capture * p_capture; vbi_raw_decoder * p_decoder; int vbi_fd; VBI_DRIVER_API_REV vbi_api; unsigned int all_services; unsigned int scanning; int max_lines; PROXY_QUEUE * p_sliced; PROXY_QUEUE * p_free; PROXY_QUEUE * p_tmp_buf; VBI_CHN_PRIO chn_prio; vbi_bool use_thread; int wr_fd; vbi_bool wait_for_exit; vbi_bool thread_active; pthread_t thread_id; pthread_cond_t start_cond; pthread_mutex_t start_mutex; pthread_mutex_t queue_mutex; } PROXY_DEV; /* this struct holds the global state of the module */ typedef struct { char * listen_ip; char * listen_port; vbi_bool do_tcp_ip; int tcp_ip_fd; int max_conn; vbi_bool should_exit; vbi_bool chn_sched_alarm; PROXY_CLNT * p_clnts; int clnt_count; pthread_mutex_t clnt_mutex; PROXY_DEV dev[4]; int dev_count; } PROXY_SRV; #define SRV_CONNECT_TIMEOUT 60 #define SRV_STALLED_STATS_INTV 15 #define SRV_QUEUE_BUFFER_COUNT 10 #define DEFAULT_MAX_CLIENTS 10 #define DEFAULT_VBI_DEV_PATH "/dev/vbi" #define DEFAULT_VBI_DEVFS_PATH "/dev/v4l/vbi" #define DEFAULT_CHN_PRIO VBI_CHN_PRIO_INTERACTIVE #define DEFAULT_BUFFER_COUNT 8 #define MAX_DEV_ERROR_COUNT 10 /* ---------------------------------------------------------------------------- ** Local variables */ static PROXY_SRV proxy; static char * p_opt_log_name = NULL; static int opt_log_level = -1; static int opt_syslog_level = -1; static vbi_bool opt_no_detach = FALSE; static vbi_bool opt_kill_daemon = FALSE; static unsigned int opt_max_clients = DEFAULT_MAX_CLIENTS; static unsigned int opt_debug_level = 0; static unsigned int opt_buffer_count = DEFAULT_BUFFER_COUNT; /* ---------------------------------------------------------------------------- ** Add one buffer to the tail of a queue ** - slicer queue is organized so that new data is appended to the tail, ** forwarded data is taken from the head ** - note that a buffer is not released from the slicer queue until all ** clients have processed it's data; client structs hold a pointer to ** the first unprocessed (by the respective client) buffer in the queue */ static void vbi_proxy_queue_add_tail( PROXY_QUEUE ** q, PROXY_QUEUE * p_buf ) { PROXY_QUEUE * p_last; dprintf(DBG_QU, "queue_add_tail: buffer 0x%lX\n", (long)p_buf); p_buf->p_next = NULL; if (*q != NULL) { assert(*q != p_buf); p_last = *q; while (p_last->p_next != NULL) p_last = p_last->p_next; p_last->p_next = p_buf; } else *q = p_buf; assert((*q != NULL) && ((*q)->p_next != *q)); } /* ---------------------------------------------------------------------------- ** Retrieve one buffer from the queue of unused buffers ** - checks if the buffer size still matches the current VBI format ** if not, the buffer is re-allocated */ static PROXY_QUEUE * vbi_proxy_queue_get_free( PROXY_DEV * p_proxy_dev ) { PROXY_QUEUE * p_buf; pthread_mutex_lock(&p_proxy_dev->queue_mutex); p_buf = p_proxy_dev->p_free; if (p_buf != NULL) { p_proxy_dev->p_tmp_buf = p_buf; p_proxy_dev->p_free = p_buf->p_next; pthread_mutex_unlock(&p_proxy_dev->queue_mutex); if (p_buf->max_lines != p_proxy_dev->max_lines) { /* max line parameter changed -> re-alloc the buffer */ p_proxy_dev->p_tmp_buf = NULL; if (p_buf->p_raw_data != NULL) free(p_buf->p_raw_data); free(p_buf); p_buf = malloc(QUEUE_ELEM_SIZE(p_buf, p_proxy_dev->max_lines)); p_buf->p_raw_data = NULL; p_buf->max_lines = p_proxy_dev->max_lines; p_proxy_dev->p_tmp_buf = p_buf; } /* add/remove "sub-buffer" for raw data */ if (VBI_RAW_SERVICES(p_proxy_dev->all_services)) { if (p_buf->p_raw_data == NULL) p_buf->p_raw_data = malloc(p_proxy_dev->max_lines * VBIPROXY_RAW_LINE_SIZE); } else { if (p_buf->p_raw_data != NULL) free(p_buf->p_raw_data); p_buf->p_raw_data = NULL; } p_buf->p_next = NULL; p_buf->ref_count = 0; p_buf->use_count = 0; } else pthread_mutex_unlock(&p_proxy_dev->queue_mutex); dprintf(DBG_QU, "queue_get_free: buffer 0x%lX\n", (long)p_buf); return p_buf; } /* ---------------------------------------------------------------------------- ** Add a buffer to the queue of unused buffers ** - there's no ordering between buffers in the free queue, hence we don't ** care if the buffer is inserted at head or tail of the queue */ static void vbi_proxy_queue_add_free( PROXY_DEV * p_proxy_dev, PROXY_QUEUE * p_buf ) { dprintf(DBG_QU, "queue_add_free: buffer 0x%lX\n", (long)p_buf); p_buf->p_next = p_proxy_dev->p_free; p_proxy_dev->p_free = p_buf; } /* ---------------------------------------------------------------------------- ** Decrease reference counter on a buffer, add back to free queue upon zero ** - called when a buffer has been processed for one client */ static void vbi_proxy_queue_release_sliced( PROXY_CLNT * req ) { PROXY_QUEUE * p_buf; PROXY_DEV * p_proxy_dev; p_proxy_dev = proxy.dev + req->dev_idx; p_buf = req->p_sliced; req->p_sliced = p_buf->p_next; if (p_buf->ref_count > 0) p_buf->ref_count -= 1; if (p_buf->ref_count == 0) { assert(p_proxy_dev->p_sliced == p_buf); p_proxy_dev->p_sliced = p_buf->p_next; /* add the buffer to the free queue */ p_buf->p_next = p_proxy_dev->p_free; p_proxy_dev->p_free = p_buf; } } /* ---------------------------------------------------------------------------- ** Free all resources of all buffers in a queue ** - called upon stop of acquisition for all queues */ static void vbi_proxy_queue_release_all( int dev_idx ) { PROXY_DEV * p_proxy_dev; PROXY_CLNT * req; PROXY_QUEUE * p_next; p_proxy_dev = proxy.dev + dev_idx; pthread_mutex_lock(&p_proxy_dev->queue_mutex); while (p_proxy_dev->p_sliced != NULL) { p_next = p_proxy_dev->p_sliced->p_next; vbi_proxy_queue_add_free(p_proxy_dev, p_proxy_dev->p_sliced); p_proxy_dev->p_sliced = p_next; } for (req = proxy.p_clnts; req != NULL; req = req->p_next) { if (req->dev_idx == dev_idx) { req->p_sliced = NULL; } } pthread_mutex_unlock(&p_proxy_dev->queue_mutex); } /* ---------------------------------------------------------------------------- ** Free all resources of all buffers in a queue ** - called upon stop of acquisition for all queues */ static void vbi_proxy_queue_free_all( PROXY_QUEUE ** q ) { PROXY_QUEUE * p_next; while (*q != NULL) { p_next = (*q)->p_next; if ((*q)->p_raw_data != NULL) free((*q)->p_raw_data); free(*q); *q = p_next; } } /* ---------------------------------------------------------------------------- ** Allocate buffers ** - determines number of required buffers and adds or removes buffers from queue ** - buffer count depends on ** (i) minimum which is always allocated (>= number of raw buffers) ** (ii) max. requested buffer count of all connected clients ** (iii) number of clients (one spare for each client) */ static vbi_bool vbi_proxy_queue_allocate( int dev_idx ) { PROXY_DEV * p_proxy_dev; PROXY_CLNT * p_walk; PROXY_QUEUE * p_buf; int client_count; int buffer_count; int buffer_free; int buffer_used; p_proxy_dev = proxy.dev + dev_idx; buffer_count = opt_buffer_count; client_count = 0; for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) { if (p_walk->dev_idx == dev_idx) { client_count += 1; if (buffer_count < p_walk->buffer_count) buffer_count = p_walk->buffer_count; } } buffer_count += client_count; pthread_mutex_lock(&p_proxy_dev->queue_mutex); /* count buffers in sliced data output queue */ buffer_used = 0; for (p_buf = p_proxy_dev->p_sliced; p_buf != NULL; p_buf = p_buf->p_next) { buffer_used += 1; } /* count buffers in free queue */ buffer_free = 0; for (p_buf = p_proxy_dev->p_free; p_buf != NULL; p_buf = p_buf->p_next) { buffer_free += 1; } dprintf(DBG_MSG, "queue_allocate: need %d buffers, have %d+%d (free+used)\n", buffer_count, buffer_free, buffer_used); if (buffer_free + buffer_used > buffer_count) { /* too many buffers: first reclaim from free queue (possibly too many) */ vbi_proxy_queue_free_all(&p_proxy_dev->p_free); buffer_free = 0; } /* XXX we could also force-free more buffers in out queue, but that may be overkill */ while (buffer_free + buffer_used < buffer_count) { p_buf = malloc(QUEUE_ELEM_SIZE(p_buf, p_proxy_dev->max_lines)); if (p_buf != NULL) { p_buf->p_raw_data = NULL; p_buf->max_lines = p_proxy_dev->max_lines; vbi_proxy_queue_add_free(p_proxy_dev, p_buf); buffer_free += 1; } else { dprintf(DBG_MSG, "queue_allocate: failed to allocate buffer (errno %d)\n", errno); break; } } pthread_mutex_unlock(&p_proxy_dev->queue_mutex); return ((unsigned int)(buffer_free + buffer_used) >= opt_buffer_count + client_count); } /* ---------------------------------------------------------------------------- ** Free the first buffer in the output queue by force ** - required if one client is blocked but others still active ** - client(s) will lose this frame's data */ static PROXY_QUEUE * vbi_proxy_queue_force_free( PROXY_DEV * p_proxy_dev ) { PROXY_CLNT * req; pthread_mutex_lock(&proxy.clnt_mutex); pthread_mutex_lock(&p_proxy_dev->queue_mutex); if ((p_proxy_dev->p_free == NULL) && (p_proxy_dev->p_sliced != NULL)) { dprintf(DBG_MSG, "queue_force_free: buffer 0x%lX\n", (long)p_proxy_dev->p_sliced); for (req = proxy.p_clnts; req != NULL; req = req->p_next) { if (req->p_sliced == p_proxy_dev->p_sliced) { vbi_proxy_queue_release_sliced(req); } } } pthread_mutex_unlock(&p_proxy_dev->queue_mutex); pthread_mutex_unlock(&proxy.clnt_mutex); return vbi_proxy_queue_get_free(p_proxy_dev); } /* ---------------------------------------------------------------------------- ** Read sliced data and forward it to all clients */ static void vbi_proxyd_forward_data( int dev_idx ) { PROXY_QUEUE * p_buf; PROXY_CLNT * req; PROXY_DEV * p_proxy_dev; struct timeval timeout; int res; p_proxy_dev = proxy.dev + dev_idx; /* unlink a buffer from the free queue */ p_buf = vbi_proxy_queue_get_free(p_proxy_dev); if (p_buf == NULL) p_buf = vbi_proxy_queue_force_free(p_proxy_dev); if (p_buf != NULL) { timeout.tv_sec = 0; timeout.tv_usec = 0; if (VBI_RAW_SERVICES(p_proxy_dev->all_services) == FALSE) { res = vbi_capture_read_sliced(p_proxy_dev->p_capture, p_buf->lines, &p_buf->line_count, &p_buf->timestamp, &timeout); } else { res = vbi_capture_read(p_proxy_dev->p_capture, p_buf->p_raw_data, p_buf->lines, &p_buf->line_count, &p_buf->timestamp, &timeout); } if (res > 0) { assert(p_buf->line_count < p_buf->max_lines); pthread_mutex_lock(&proxy.clnt_mutex); pthread_mutex_lock(&p_proxy_dev->queue_mutex); for (req = proxy.p_clnts; req != NULL; req = req->p_next) { if ( (req->dev_idx == dev_idx) && (req->state == REQ_STATE_FORWARD) && (req->all_services != 0) ) { p_buf->ref_count += 1; if (req->p_sliced == NULL) req->p_sliced = p_buf; } } pthread_mutex_unlock(&p_proxy_dev->queue_mutex); pthread_mutex_unlock(&proxy.clnt_mutex); } else if (res < 0) { /* XXX abort upon error (esp. EBUSY) */ perror("VBI read"); } pthread_mutex_lock(&p_proxy_dev->queue_mutex); if (p_buf->ref_count > 0) vbi_proxy_queue_add_tail(&p_proxy_dev->p_sliced, p_buf); else vbi_proxy_queue_add_free(p_proxy_dev, p_buf); p_proxy_dev->p_tmp_buf = NULL; pthread_mutex_unlock(&p_proxy_dev->queue_mutex); } else dprintf(DBG_MSG, "forward_data: queue overflow\n"); } /* ---------------------------------------------------------------------------- ** Process a norm change notification ** - query driver for new norm: if sucessful, this overrides information ** provided by the client (client may also provide 0) ** - trigger sending of norm change indication to all clients if scanning changes: ** -> clients must re-apply for their services; note norm changes which don't ** affect the scanning (e.g. PAL<->SECAM) are ignored */ static void vbi_proxyd_update_scanning( int dev_idx, PROXY_CLNT * req, int scanning ) { PROXY_DEV * p_proxy_dev; PROXY_CLNT * p_walk; unsigned int new_scanning; p_proxy_dev = proxy.dev + dev_idx; if (p_proxy_dev->p_capture != NULL) { /* if the info is coming from a client verify it */ if (req != NULL) { new_scanning = vbi_capture_get_scanning(p_proxy_dev->p_capture); if (new_scanning <= 0) { if ((scanning == 525) || (scanning == 625)) new_scanning = scanning; } } else new_scanning = scanning; if (new_scanning != p_proxy_dev->scanning) { dprintf(DBG_MSG, "update_scanning: changed from %d to %d\n", p_proxy_dev->scanning, new_scanning); p_proxy_dev->scanning = new_scanning; /* trigger sending of change indication to all clients except the caller */ for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) { if ( (p_walk->dev_idx == dev_idx) && ((p_walk->client_flags & VBI_PROXY_CLIENT_NO_STATUS_IND) == 0) ) { p_walk->chn_status_ind |= VBI_PROXY_CHN_NORM; } } } } } /* ---------------------------------------------------------------------------- ** Helper function: calculate timespec for 50ms timeout */ static void vbi_proxyd_calc_timeout_ms( struct timespec * p_tsp, int msecs ) { struct timeval tv; gettimeofday(&tv, NULL); tv.tv_usec += msecs * 1000L; if (tv.tv_usec > 1000 * 1000L) { tv.tv_sec += 1; tv.tv_usec -= 1000 * 1000; } p_tsp->tv_sec = tv.tv_sec; p_tsp->tv_nsec = tv.tv_usec * 1000; } /* ---------------------------------------------------------------------------- ** Clean up after thread cancellation: signal waiting master thread */ static void vbi_proxyd_acq_thread_cleanup( void * pvoid_arg ) { PROXY_DEV * p_proxy_dev; int dev_idx; dev_idx = PVOID2INT(pvoid_arg); p_proxy_dev = proxy.dev + dev_idx; dprintf(DBG_QU, "acq thread cleanup: signaling master (%d)\n", p_proxy_dev->wait_for_exit); pthread_mutex_lock(&p_proxy_dev->start_mutex); if (p_proxy_dev->wait_for_exit) { pthread_cond_signal(&p_proxy_dev->start_cond); } if (p_proxy_dev->p_tmp_buf != NULL) { vbi_proxy_queue_add_free(p_proxy_dev, p_proxy_dev->p_tmp_buf); p_proxy_dev->p_tmp_buf = NULL; } p_proxy_dev->thread_active = FALSE; pthread_mutex_unlock(&p_proxy_dev->start_mutex); } /* ---------------------------------------------------------------------------- ** Main loop for acquisition thread for devices that don't support select(2) */ static void * vbi_proxyd_acq_thread( void * pvoid_arg ) { PROXY_DEV * p_proxy_dev; int dev_idx; int ret; char byte_buf[1]; sigset_t sigmask; dev_idx = PVOID2INT(pvoid_arg); p_proxy_dev = proxy.dev + dev_idx; /* block signals which are handled by main thread */ sigemptyset(&sigmask); sigaddset(&sigmask, SIGHUP); sigaddset(&sigmask, SIGINT); sigaddset(&sigmask, SIGTERM); pthread_sigmask(SIG_BLOCK, &sigmask, NULL); pthread_cleanup_push(vbi_proxyd_acq_thread_cleanup, pvoid_arg); pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); p_proxy_dev->thread_active = TRUE; pthread_mutex_lock(&p_proxy_dev->start_mutex); pthread_cond_signal(&p_proxy_dev->start_cond); pthread_mutex_unlock(&p_proxy_dev->start_mutex); while (p_proxy_dev->wait_for_exit == FALSE) { /* read data from the VBI device and append the buffer to all client queues ** note: this function blocks in read(2) until data is available */ vbi_proxyd_forward_data(dev_idx); /* wake up the master thread to process client queues */ ret = write(p_proxy_dev->wr_fd, byte_buf, 1); if ((ret < 0) && (errno != EAGAIN)) { dprintf(DBG_MSG, "acq_thread: write error to pipe: %d\n", errno); break; } else if (ret != 1) dprintf(DBG_MSG, "acq_thread: pipe overflow\n"); } pthread_cleanup_pop(1); pthread_exit(0); return NULL; } /* ---------------------------------------------------------------------------- ** Stop acquisition thread */ static void vbi_proxyd_stop_acq_thread( PROXY_DEV * p_proxy_dev ) { struct timespec tsp; int ret; int vbi_fd; assert(p_proxy_dev->use_thread); pthread_mutex_lock(&p_proxy_dev->start_mutex); if (p_proxy_dev->thread_active) { p_proxy_dev->wait_for_exit = TRUE; pthread_cancel(p_proxy_dev->thread_id); vbi_proxyd_calc_timeout_ms(&tsp, 50); ret = pthread_cond_timedwait(&p_proxy_dev->start_cond, &p_proxy_dev->start_mutex, &tsp); if (ret != 0) { /* thread did not stop within 50ms: probably blocked in read with no incoming data */ /* dirty hack: force to wake up by closing the file handle */ vbi_fd = vbi_capture_fd(p_proxy_dev->p_capture); close(vbi_fd); dprintf(DBG_MSG, "stop_acq_thread: thread did not exit (%d): closed VBI filehandle %d\n", ret, vbi_fd); vbi_proxyd_calc_timeout_ms(&tsp, 50); ret = pthread_cond_timedwait(&p_proxy_dev->start_cond, &p_proxy_dev->start_mutex, &tsp); } if (ret == 0) { ret = pthread_join(p_proxy_dev->thread_id, NULL); if (ret == 0) dprintf(DBG_MSG, "stop_acq_thread: acq thread killed successfully\n"); else dprintf(DBG_MSG, "stop_acq_thread: pthread_join failed: %d (%s)\n", errno, strerror(errno)); } } close(p_proxy_dev->vbi_fd); close(p_proxy_dev->wr_fd); p_proxy_dev->vbi_fd = -1; p_proxy_dev->wr_fd = -1; p_proxy_dev->use_thread = FALSE; pthread_mutex_unlock(&p_proxy_dev->start_mutex); } /* ---------------------------------------------------------------------------- ** Start a thread to block in read(2) for devices that don't support select(2) */ static vbi_bool vbi_proxyd_start_acq_thread( int dev_idx ) { PROXY_DEV * p_proxy_dev; int pipe_fds[2]; vbi_bool result = FALSE; p_proxy_dev = proxy.dev + dev_idx; p_proxy_dev->use_thread = TRUE; p_proxy_dev->wait_for_exit = FALSE; p_proxy_dev->thread_active = FALSE; if (pipe(pipe_fds) == 0) { p_proxy_dev->vbi_fd = pipe_fds[0]; p_proxy_dev->wr_fd = pipe_fds[1]; fcntl(p_proxy_dev->vbi_fd, F_SETFL, O_NONBLOCK); fcntl(p_proxy_dev->wr_fd, F_SETFL, O_NONBLOCK); /* start thread */ pthread_mutex_lock(&p_proxy_dev->start_mutex); if (pthread_create(&p_proxy_dev->thread_id, NULL, vbi_proxyd_acq_thread, INT2PVOID(dev_idx)) == 0) { dprintf(DBG_MSG, "acquisiton thread started: " "id %ld, device %ld, pipe rd/wr %d/%d\n", (long)p_proxy_dev->thread_id, (long)(p_proxy_dev - proxy.dev), p_proxy_dev->vbi_fd, p_proxy_dev->wr_fd); /* wait for the slave to report the initialization result */ pthread_cond_wait(&p_proxy_dev->start_cond, &p_proxy_dev->start_mutex); pthread_mutex_unlock(&p_proxy_dev->start_mutex); result = p_proxy_dev->thread_active; } else dprintf(DBG_MSG, "start_acq_thread: pthread_create: %d (%s)\n", errno, strerror(errno)); } else dprintf(DBG_MSG, "start_acq_thread: create pipe: %d (%s)\n", errno, strerror(errno)); return result; } /* ---------------------------------------------------------------------------- ** Stop VBI acquisition (after the last client quit) */ static void vbi_proxy_stop_acquisition( PROXY_DEV * p_proxy_dev ) { if (p_proxy_dev->p_capture != NULL) { dprintf(DBG_MSG, "stop_acquisition: stopping (prev. services 0x%X)\n", p_proxy_dev->all_services); if (p_proxy_dev->use_thread) vbi_proxyd_stop_acq_thread(p_proxy_dev); vbi_capture_delete(p_proxy_dev->p_capture); p_proxy_dev->p_capture = NULL; p_proxy_dev->p_decoder = NULL; p_proxy_dev->vbi_fd = -1; vbi_proxy_queue_free_all(&p_proxy_dev->p_free); vbi_proxy_queue_free_all(&p_proxy_dev->p_sliced); } } /* ---------------------------------------------------------------------------- ** Open capture device (for the first client) ** - does not yet any services yet */ static vbi_bool vbi_proxy_start_acquisition( int dev_idx, char ** pp_errorstr ) { PROXY_DEV * p_proxy_dev; char * p_errorstr; vbi_bool result; p_proxy_dev = proxy.dev + dev_idx; result = FALSE; /* assign dummy error string if necessary */ p_errorstr = NULL; if (pp_errorstr == NULL) pp_errorstr = &p_errorstr; p_proxy_dev->vbi_api = VBI_API_V4L2; p_proxy_dev->p_capture = vbi_capture_v4l2_new(p_proxy_dev->p_dev_name, opt_buffer_count, NULL, -1, pp_errorstr, opt_debug_level); if (p_proxy_dev->p_capture == NULL) { p_proxy_dev->vbi_api = VBI_API_V4L1; p_proxy_dev->p_capture = vbi_capture_v4l_new(p_proxy_dev->p_dev_name, p_proxy_dev->scanning, NULL, -1, pp_errorstr, opt_debug_level); } if (p_proxy_dev->p_capture != NULL) { p_proxy_dev->p_decoder = vbi_capture_parameters(p_proxy_dev->p_capture); if (p_proxy_dev->p_decoder != NULL) { /* allocate buffer queue for sliced output data */ vbi_proxy_queue_allocate(dev_idx); p_proxy_dev->chn_prio = VBI_CHN_PRIO_INTERACTIVE; /* get file handle for select() to wait for VBI data */ if ((vbi_capture_get_fd_flags(p_proxy_dev->p_capture) & VBI_FD_HAS_SELECT) != 0) { p_proxy_dev->vbi_fd = vbi_capture_fd(p_proxy_dev->p_capture); result = (p_proxy_dev->vbi_fd != -1); } else result = vbi_proxyd_start_acq_thread(dev_idx); } else dprintf(DBG_MSG, "start_acquisition: capture device has no slicer!?\n"); } if (result == FALSE) { vbi_proxy_stop_acquisition(p_proxy_dev); } if ((pp_errorstr == &p_errorstr) && (p_errorstr != NULL)) free(p_errorstr); return result; } /* ---------------------------------------------------------------------------- ** Update service mask after a client was added or closed ** - TODO: update buffer_count */ static vbi_bool vbi_proxyd_update_services( int dev_idx, PROXY_CLNT * p_new_req, int new_req_strict, char ** pp_errorstr ) { PROXY_CLNT * req; PROXY_CLNT * p_walk; PROXY_DEV * p_proxy_dev; unsigned int dev_services; unsigned int tmp_services; unsigned int next_srv; int strict; int strict2; vbi_bool is_first; vbi_bool result; p_proxy_dev = proxy.dev + dev_idx; if (p_proxy_dev->p_capture == NULL) { /* cpture device not opened yet */ /* check if other clients have any services enabled */ next_srv = 0; for (req = proxy.p_clnts; req != NULL; req = req->p_next) for (strict = VBI_MIN_STRICT; strict <= VBI_MAX_STRICT; strict++) next_srv |= *VBI_GET_SERVICE_P(req, strict); if (next_srv != 0) { result = vbi_proxy_start_acquisition(dev_idx, pp_errorstr); } else { /* XXX FIXME must open device at least once to query API ** XXX must be change since device open may fail with EBUSY: better leave device open while users are connected */ if (p_proxy_dev->vbi_api == VBI_API_UNKNOWN) { vbi_proxy_start_acquisition(dev_idx, NULL); vbi_proxy_stop_acquisition(p_proxy_dev); } result = TRUE; } } else result = FALSE; if (p_proxy_dev->p_capture != NULL) { /* terminate acq thread because we're about to suspend capturing */ if (p_proxy_dev->use_thread) vbi_proxyd_stop_acq_thread(p_proxy_dev); /* XXX TODO: possible optimization: reduce number of update_service calls: ** (1) collect all services first; (2) add services at 3 strict levels; (3) update all_services for all clients */ is_first = TRUE; dev_services = 0; for (req = proxy.p_clnts; req != NULL; req = req->p_next) { if ( (req->dev_idx == dev_idx) && (req->state == REQ_STATE_FORWARD) ) { req->all_services = 0; for (strict = VBI_MIN_STRICT; strict <= VBI_MAX_STRICT; strict++) { tmp_services = *VBI_GET_SERVICE_P(req, strict); if (tmp_services != 0) { next_srv = 0; for (strict2 = strict + 1; strict2 <= VBI_MAX_STRICT; strict2++) if ((next_srv |= *VBI_GET_SERVICE_P(req, strict2)) != 0) break; /* search following clients if more services follow */ if (next_srv == 0) for (p_walk = req->p_next; p_walk != NULL; p_walk = p_walk->p_next) for (strict2 = VBI_MIN_STRICT; strict2 <= VBI_MAX_STRICT; strict2++) if ((next_srv |= *VBI_GET_SERVICE_P(p_walk, strict2)) != 0) goto next_srv_found; // break^2 next_srv_found: dprintf(DBG_MSG, "service_update: fd %d: add services=0x%X strict=%d final=%d\n", req->io.sock_fd, tmp_services, strict, (next_srv == 0)); tmp_services = vbi_capture_update_services( p_proxy_dev->p_capture, is_first, (next_srv == 0), tmp_services, strict, /* return error strings only for the new client */ (((req == p_new_req) && (strict == new_req_strict)) ? pp_errorstr : NULL) ); dev_services |= tmp_services; req->all_services |= tmp_services; is_first = FALSE; /* must not mask out client service bits unless upon a new request; afterwards ** services must be cached and re-applied, e.g. in case the norm changes back */ if (req == p_new_req) *VBI_GET_SERVICE_P(req, strict) &= tmp_services; } } } } /* check if scanning changed ** (even if all clients suspended: some might be waiting for scanning change) */ vbi_proxyd_update_scanning(dev_idx, NULL, p_proxy_dev->p_decoder->scanning); if (dev_services != 0) { p_proxy_dev->all_services = dev_services; p_proxy_dev->max_lines = p_proxy_dev->p_decoder->count[0] + p_proxy_dev->p_decoder->count[1]; /* grow/shrink buffer queue for sliced output data */ vbi_proxy_queue_allocate(dev_idx); dprintf(DBG_MSG, "service_update: new service mask 0x%X, max.lines=%d, scanning=%d\n", dev_services, p_proxy_dev->max_lines, p_proxy_dev->scanning); if ((vbi_capture_get_fd_flags(p_proxy_dev->p_capture) & VBI_FD_HAS_SELECT) != 0) { result = TRUE; } else result = vbi_proxyd_start_acq_thread(dev_idx); } else { /* no services set: not an error if clien't didn't request any */ result = is_first; } if ((dev_services == 0) || (result == FALSE)) { /* no clients remaining or acq start failed -> stop acquisition */ vbi_proxy_stop_acquisition(p_proxy_dev); } } return result; } /* ---------------------------------------------------------------------------- ** Process a client's service request ** - either during connect request or later upon service request ** - note if it's not the first request a different "strictness" may be given; ** must remember strictness for each service to be able to re-apply for the ** same services mask to the decoder later */ static vbi_bool vbi_proxyd_take_service_req( PROXY_CLNT * req, unsigned int new_services, int new_strict, char * errormsg ) { char * p_errorstr; PROXY_DEV * p_proxy_dev; int strict; vbi_bool result; p_proxy_dev = proxy.dev + req->dev_idx; p_errorstr = NULL; /* remove new services from all strict levels */ for (strict = VBI_MIN_STRICT; strict <= VBI_MAX_STRICT; strict++) *VBI_GET_SERVICE_P(req, strict) &= ~ new_services; /* add new services at the given level of strictness */ *VBI_GET_SERVICE_P(req, new_strict) |= new_services; /* merge with other client's requests and pass to the device */ result = vbi_proxyd_update_services(req->dev_idx, req, new_strict, &p_errorstr); if ( (result == FALSE) || ( ((req->all_services & new_services) == 0) && (new_services != 0) )) { if (p_errorstr != NULL) { strlcpy(errormsg, p_errorstr, VBIPROXY_ERROR_STR_MAX_LENGTH); errormsg[VBIPROXY_ERROR_STR_MAX_LENGTH - 1] = 0; } else if ( ((*VBI_GET_SERVICE_P(req, new_strict) & new_services) == 0) && (new_services != 0) ) { strlcpy(errormsg, "Sorry, proxy cannot capture any of the requested data services.", VBIPROXY_ERROR_STR_MAX_LENGTH); errormsg[VBIPROXY_ERROR_STR_MAX_LENGTH - 1] = 0; } else { strlcpy(errormsg, "Internal error in service update.", VBIPROXY_ERROR_STR_MAX_LENGTH); errormsg[VBIPROXY_ERROR_STR_MAX_LENGTH - 1] = 0; } result = FALSE; } if (p_proxy_dev->p_decoder != NULL) { /* keep a copy of the VBI line ranges: used as upper/lower boundaries in ** sliced data messages MUST NOT be changed later (at least not increased) ** even if services change, to avoid overflowing clients' buffers */ req->vbi_start[0] = p_proxy_dev->p_decoder->start[0]; req->vbi_count[0] = p_proxy_dev->p_decoder->count[0]; req->vbi_start[1] = p_proxy_dev->p_decoder->start[1]; req->vbi_count[1] = p_proxy_dev->p_decoder->count[1]; } if (p_errorstr != NULL) free(p_errorstr); return result; } /* ---------------------------------------------------------------------------- ** Search for client which owns the token */ static PROXY_CLNT * vbi_proxyd_get_token_owner( int dev_idx ) { PROXY_DEV * p_proxy_dev; PROXY_CLNT * p_walk; PROXY_CLNT * p_owner; p_proxy_dev = proxy.dev + dev_idx; p_owner = NULL; for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) { if (p_walk->dev_idx == dev_idx) { switch (p_walk->chn_state.token_state) { case REQ_TOKEN_NONE: break; case REQ_TOKEN_GRANT: case REQ_TOKEN_RETURNED: case REQ_TOKEN_RECLAIM: case REQ_TOKEN_RELEASE: case REQ_TOKEN_GRANTED: assert(p_owner == NULL); p_owner = p_walk; break; default: assert(FALSE); /* invalid state */ break; } } } return p_owner; } /* ---------------------------------------------------------------------------- ** Grant token to a given client ** - basically implements a matrix of all possible token states in current and ** future token owner: however only one may have non-"NONE" state ** - if the token is still in posession of a different client the request will ** fail, but the token is reclaimed from the other client */ static vbi_bool vbi_proxyd_token_grant( PROXY_CLNT * req ) { PROXY_CLNT * p_owner; vbi_bool token_free = TRUE; switch (req->chn_state.token_state) { case REQ_TOKEN_NONE: p_owner = vbi_proxyd_get_token_owner(req->dev_idx); if ( (p_owner == NULL) || ( (p_owner->chn_state.token_state == REQ_TOKEN_GRANT) || (p_owner->chn_state.token_state == REQ_TOKEN_RETURNED) )) { /* token is free or grant message not yet sent -> immediately grant to new client */ req->chn_state.token_state = REQ_TOKEN_GRANT; if (p_owner != NULL) p_owner->chn_state.token_state = REQ_TOKEN_NONE; } else { /* have to reclaim token from previous owner first */ if (p_owner->chn_state.token_state != REQ_TOKEN_RELEASE) p_owner->chn_state.token_state = REQ_TOKEN_RECLAIM; token_free = FALSE; } break; case REQ_TOKEN_GRANT: /* client is already about to be granted the token -> nothing to do */ break; case REQ_TOKEN_RECLAIM: /* reclaim message not yet sent -> just return to GRANTED state */ req->chn_state.token_state = REQ_TOKEN_GRANTED; break; case REQ_TOKEN_RELEASE: /* reclaim already sent -> must re-assign token */ req->chn_state.token_state = REQ_TOKEN_GRANT; break; case REQ_TOKEN_GRANTED: case REQ_TOKEN_RETURNED: /* client is still in control of the channel: nothing to do */ break; default: assert(FALSE); /* invalid state */ break; } return token_free; } /* ---------------------------------------------------------------------------- ** Adapt channel scheduler state when switching away from a channel */ static void vbi_proxyd_channel_completed( PROXY_CLNT * req, time_t whence ) { PROXY_CLNT * p_walk; assert(REQ_CONTROLS_CHN(req->chn_state.token_state)); req->chn_state.last_duration = whence - req->chn_state.last_start; req->chn_state.is_completed = TRUE; req->chn_state.cycle_count += 1; dprintf(DBG_MSG, "channel_schedule: fd %d terminated (duration %d, cycle #%d)\n", req->io.sock_fd, (int)req->chn_state.last_duration, req->chn_state.cycle_count); if (req->chn_state.cycle_count > 2) { /* cycle counter overflow: only values 1, 2 allowed (plus 0 for new requests) ** -> reduce all counters by one */ dprintf(DBG_MSG, "channel_schedule: dev #%d: leveling cycle counters\n", req->dev_idx); for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) if (p_walk->dev_idx == req->dev_idx) if (p_walk->chn_state.cycle_count > 0) p_walk->chn_state.cycle_count -= 1; } else if (req->chn_state.cycle_count == 1) { /* cycle counter hops always to maximum, i.e. from 0 to 2 so that a new request ** has immediately highest prio, but is only scheduled once before the others */ for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) if (p_walk->dev_idx == req->dev_idx) if (p_walk->chn_state.cycle_count >= 2) break; if (p_walk != NULL) req->chn_state.cycle_count = 2; } } /* ---------------------------------------------------------------------------- ** Adapt channel scheduler state when switching away from a channel */ static void vbi_proxyd_channel_stopped( PROXY_CLNT * req ) { time_t now = time(NULL); assert(REQ_CONTROLS_CHN(req->chn_state.token_state)); if ( (req->chn_state.is_completed == FALSE) && (now - req->chn_state.last_start >= req->chn_profile.min_duration) ) { vbi_proxyd_channel_completed(req, now); } req->chn_state.is_completed = FALSE; if (req->chn_state.token_state == REQ_TOKEN_GRANTED) req->chn_state.token_state = REQ_TOKEN_RECLAIM; else req->chn_state.token_state = REQ_TOKEN_NONE; } /* ---------------------------------------------------------------------------- ** Calculate next timer for scheduler ** - since there's only one alarm signal, the nearest timeout on all devices is searched */ static void vbi_proxyd_channel_timer_update( void ) { PROXY_CLNT * p_walk; PROXY_DEV * p_proxy_dev; time_t rest; time_t next_sched; time_t now; now = time(NULL); next_sched = 0; for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) { p_proxy_dev = proxy.dev + p_walk->dev_idx; if ( (p_proxy_dev->chn_prio == VBI_CHN_PRIO_BACKGROUND) && REQ_CONTROLS_CHN(p_walk->chn_state.token_state) && (p_walk->chn_state.is_completed == FALSE) ) { rest = p_walk->chn_profile.min_duration - (now - p_walk->chn_state.last_start); if ((rest > 0) && ((rest < next_sched) || (next_sched == 0))) next_sched = rest; else if (rest < 0) next_sched = 1; } /* XXX TODO: set timer to supervise TOKEN RELEASE */ } if (next_sched != 0) dprintf(DBG_MSG, "channel_timer_update: set alarm timer in %d secs\n", (int)next_sched); alarm(next_sched); proxy.chn_sched_alarm = FALSE; } /* ---------------------------------------------------------------------------- ** Determine which client's channel request is granted */ static PROXY_CLNT * vbi_proxyd_channel_schedule( int dev_idx ) { PROXY_CLNT * p_walk; PROXY_CLNT * p_sched; PROXY_CLNT * p_active; PROXY_DEV * p_proxy_dev; time_t now; p_proxy_dev = proxy.dev + dev_idx; p_sched = NULL; p_active = NULL; now = time(NULL); for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) { if ( (p_walk->dev_idx == dev_idx) && (p_walk->chn_profile.is_valid) && (p_walk->chn_prio == VBI_CHN_PRIO_BACKGROUND) ) { /* if this client's channel is currently active, check if the reservation has expired */ if (REQ_CONTROLS_CHN(p_walk->chn_state.token_state)) { if ( (now - p_walk->chn_state.last_start >= p_walk->chn_profile.min_duration) && (p_walk->chn_state.is_completed == FALSE) ) { vbi_proxyd_channel_completed(p_walk, now); } p_active = p_walk; } dprintf(DBG_MSG, "channel_schedule: fd %d: active=%d compl=%d sub-prio=0x%02X cycles#%d min-dur=%d\n", p_walk->io.sock_fd, REQ_CONTROLS_CHN(p_walk->chn_state.token_state), p_walk->chn_state.is_completed, p_walk->chn_profile.sub_prio, p_walk->chn_state.cycle_count, (int)p_walk->chn_profile.min_duration); if (p_sched != NULL) { if ( p_walk->chn_state.cycle_count + ((REQ_CONTROLS_CHN(p_walk->chn_state.token_state) && p_walk->chn_state.is_completed) ? 1 : 0) < p_sched->chn_state.cycle_count + ((REQ_CONTROLS_CHN(p_sched->chn_state.token_state) && p_sched->chn_state.is_completed) ? 1 : 0) ) { /* this one is already done (more often) */ dprintf(DBG_SCHED, "channel_schedule: fd %d wins by cycle count\n", p_walk->io.sock_fd); p_sched = p_walk; } else if (p_walk->chn_profile.sub_prio > p_sched->chn_profile.sub_prio) { /* higher priority found */ dprintf(DBG_SCHED, "channel_schedule: fd %d wins by sub-prio\n", p_walk->io.sock_fd); p_sched = p_walk; } else if (p_walk->chn_profile.sub_prio == p_sched->chn_profile.sub_prio) { /* same priority */ if ( REQ_CONTROLS_CHN(p_walk->chn_state.token_state) && !p_walk->chn_state.is_completed ) { /* this one is still active */ dprintf(DBG_SCHED, "channel_schedule: fd %d wins by being already active and non-complete\n", p_walk->io.sock_fd); p_sched = p_walk; } else if ( REQ_CONTROLS_CHN(p_sched->chn_state.token_state) && p_sched->chn_state.is_completed ) { /* prev. selected one was completed -> choose next */ dprintf(DBG_SCHED, "channel_schedule: fd %d wins because active one is completed\n", p_walk->io.sock_fd); p_sched = p_walk; } else if ( !REQ_CONTROLS_CHN(p_walk->chn_state.token_state) && !REQ_CONTROLS_CHN(p_sched->chn_state.token_state) ) { /* none active -> first come first serve */ if ( (p_walk->chn_state.last_start < p_sched->chn_state.last_start) || ( (p_walk->chn_state.last_start == p_sched->chn_state.last_start) && (p_walk->chn_profile.min_duration < p_sched->chn_profile.min_duration) )) { dprintf(DBG_SCHED, "channel_schedule: fd %d wins because longer non-active\n", p_walk->io.sock_fd); p_sched = p_walk; } } } } else p_sched = p_walk; } } if ((p_sched != p_active) && (p_active != NULL)) { vbi_proxyd_channel_stopped(p_active); } return p_sched; } /* ---------------------------------------------------------------------------- ** Update channel, after channel change request or connection release ** - if only background-prio clients are connected, the scheduler decides; ** else the channel is switched (if the client matches the daemon's max prio) */ static vbi_bool vbi_proxyd_channel_update( int dev_idx, PROXY_CLNT * req, vbi_bool forced_switch ) { PROXY_CLNT * p_walk; PROXY_CLNT * p_sched; PROXY_DEV * p_proxy_dev; VBI_CHN_PRIO max_chn_prio; vbi_bool result; p_proxy_dev = proxy.dev + dev_idx; result = FALSE; /* determine new max. channel priority */ max_chn_prio = VBI_CHN_PRIO_BACKGROUND; for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) if ((p_walk->dev_idx == dev_idx) && (p_walk->chn_prio > max_chn_prio)) max_chn_prio = p_walk->chn_prio; if (p_proxy_dev->chn_prio != max_chn_prio) { #if defined(ENABLE_V4L2) && defined(VIDIOC_S_PRIORITY) if (p_proxy_dev->vbi_api == VBI_API_V4L2) { enum v4l2_priority v4l2_prio = max_chn_prio; int fd = vbi_capture_fd(p_proxy_dev->p_capture); if (fd != -1) { if (ioctl(fd, VIDIOC_S_PRIORITY, &v4l2_prio) != 0) { dprintf(DBG_MSG, "Failed to set register v4l2 channel prio to %d: %d (%s)\n", p_proxy_dev->chn_prio, errno, strerror(errno)); } else { ioctl(fd, VIDIOC_G_PRIORITY, &v4l2_prio); dprintf(DBG_MSG, "channel_update: dev #%d: setting v4l2 channel prio to %d (was %d) (dev prio is %d)\n", dev_idx, max_chn_prio, p_proxy_dev->chn_prio, v4l2_prio); } } } #endif /* save the priority registered with the device */ p_proxy_dev->chn_prio = max_chn_prio; } /* non-bg prio OR channel has already been switched -> clear scheduler active flag */ if ( (max_chn_prio > VBI_CHN_PRIO_BACKGROUND) || forced_switch ) { for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) if ((p_walk->dev_idx == dev_idx) && REQ_CONTROLS_CHN(p_walk->chn_state.token_state)) vbi_proxyd_channel_stopped(p_walk); } if (max_chn_prio == VBI_CHN_PRIO_BACKGROUND) { /* background -> let scheduler decide */ p_sched = vbi_proxyd_channel_schedule(dev_idx); } else if ( (req != NULL) && (req->chn_prio == max_chn_prio) ) { /* non-background prio -> latest request wins */ p_sched = req; } else { /* reject switch by priority */ p_sched = NULL; } if ( (p_sched != NULL) && (max_chn_prio == VBI_CHN_PRIO_BACKGROUND) && (REQ_CONTROLS_CHN(p_sched->chn_state.token_state) == FALSE) ) { if ( vbi_proxyd_token_grant(p_sched) ) { p_sched->chn_state.is_completed = FALSE; p_sched->chn_state.last_duration = 0; p_sched->chn_state.last_start = time(NULL); /* return TRUE if requested channel control can be granted immediately */ result = (p_sched == req); } } else { /* no channel change is allowed or required */ /* flush-only flag: assume client has already done the switch -> must flush VBI buffers */ if (forced_switch) { vbi_capture_flush(p_proxy_dev->p_capture); } } if (max_chn_prio == VBI_CHN_PRIO_BACKGROUND) vbi_proxyd_channel_timer_update(); return result; } /* ---------------------------------------------------------------------------- ** Flush after channel change */ static void vbi_proxyd_channel_flush( int dev_idx, PROXY_CLNT * req ) { PROXY_CLNT * p_walk; PROXY_DEV * p_proxy_dev; req = req; p_proxy_dev = proxy.dev + dev_idx; if (p_proxy_dev->p_capture != NULL) { /* flush capture buffers */ vbi_capture_flush(p_proxy_dev->p_capture); /* flush slicer output buffer queues */ vbi_proxy_queue_release_all(dev_idx); } /* trigger sending of change indication to all clients except the caller */ for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) { if ( (p_walk->dev_idx == dev_idx) && ((p_walk->client_flags & VBI_PROXY_CLIENT_NO_STATUS_IND) == 0) ) { p_walk->chn_status_ind |= VBI_PROXY_CHN_FLUSH; } } } /* ---------------------------------------------------------------------------- ** Check channel scheduling on all devices for expired timers */ static void vbi_proxyd_channel_timer( void ) { PROXY_CLNT * p_walk; PROXY_DEV * p_proxy_dev; int dev_idx; time_t now; vbi_bool do_schedule; int user_count; now = time(NULL); for (dev_idx = 0; dev_idx < proxy.dev_count; dev_idx++) { p_proxy_dev = proxy.dev + dev_idx; do_schedule = FALSE; user_count = 0; if (p_proxy_dev->chn_prio == VBI_CHN_PRIO_BACKGROUND) { for (p_walk = proxy.p_clnts; p_walk != NULL; p_walk = p_walk->p_next) { if (p_walk->dev_idx == dev_idx) { if ( REQ_CONTROLS_CHN(p_walk->chn_state.token_state) && (p_walk->chn_state.is_completed == FALSE) && (now - p_walk->chn_state.last_start >= p_walk->chn_profile.min_duration) ) { do_schedule = TRUE; } user_count += 1; } } if (do_schedule && (user_count > 1)) { dprintf(DBG_MSG, "schedule_timer: schedule device #%d\n", dev_idx); vbi_proxyd_channel_update(dev_idx, NULL, FALSE); } } } } /* ---------------------------------------------------------------------------- ** Process client ioctl request */ static vbi_bool vbi_proxyd_take_ioctl_req( PROXY_CLNT * req, int request, void * p_arg_data, unsigned int arg_size, int * p_result, int * p_errcode ) { PROXY_DEV * p_proxy_dev; vbi_bool req_perm; int size; int vbi_fd; vbi_bool opened = FALSE; vbi_bool result = FALSE; p_proxy_dev = proxy.dev + req->dev_idx; if (p_proxy_dev->p_capture == NULL) { vbi_proxy_start_acquisition(req->dev_idx, NULL); opened = TRUE; } vbi_fd = vbi_capture_fd(p_proxy_dev->p_capture); if (vbi_fd != -1) { size = vbi_proxy_msg_check_ioctl(p_proxy_dev->vbi_api, request, p_arg_data, &req_perm); if ((size >= 0) && (size == (int) arg_size)) { /* FIXME */ if ( (req_perm == FALSE) || (req->chn_prio >= p_proxy_dev->chn_prio) || REQ_CONTROLS_CHN(req->chn_state.token_state) ) { /* TODO: possibly update norm, flush channel */ errno = 0; /* do the actual ioctl */ *p_result = ioctl(vbi_capture_fd(p_proxy_dev->p_capture), request, p_arg_data); *p_errcode = errno; result = TRUE; } else dprintf(DBG_MSG, "take_ioctl_req: no permission\n"); } else dprintf(DBG_MSG, "take_ioctl_req: invalid ioctl 0x%X or size %d\n", request, arg_size); } if (opened) { vbi_proxy_stop_acquisition(p_proxy_dev); } return result; } /* ---------------------------------------------------------------------------- ** Close the connection to the client ** - frees all allocated resources */ static void vbi_proxyd_close( PROXY_CLNT * req, vbi_bool close_all ) { close_all = close_all; if (req->state != REQ_STATE_CLOSED) { dprintf(DBG_MSG, "close: fd %d\n", req->io.sock_fd); vbi_proxy_msg_logger(LOG_INFO, req->io.sock_fd, 0, "closing connection", NULL); vbi_proxy_msg_close_io(&req->io); pthread_mutex_lock(&proxy.dev[req->dev_idx].queue_mutex); while (req->p_sliced != NULL) { vbi_proxy_queue_release_sliced(req); } pthread_mutex_unlock(&proxy.dev[req->dev_idx].queue_mutex); req->state = REQ_STATE_CLOSED; } } /* ---------------------------------------------------------------------------- ** Initialize a request structure for a new client and add it to the list */ static void vbi_proxyd_add_connection( int listen_fd, int dev_idx, vbi_bool isLocal ) { PROXY_CLNT * req; PROXY_CLNT * p_walk; int sock_fd; isLocal = isLocal; sock_fd = vbi_proxy_msg_accept_connection(listen_fd); if (sock_fd != -1) { req = calloc(sizeof(*req), 1); if (req != NULL) { dprintf(DBG_MSG, "add_connection: fd %d\n", sock_fd); req->state = REQ_STATE_WAIT_CON_REQ; req->io.lastIoTime = time(NULL); req->io.sock_fd = sock_fd; req->dev_idx = dev_idx; req->chn_prio = DEFAULT_CHN_PRIO; pthread_mutex_lock(&proxy.clnt_mutex); /* append request to the end of the chain ** note: order is significant for priority in adding services */ if (proxy.p_clnts != NULL) { p_walk = proxy.p_clnts; while (p_walk->p_next != NULL) p_walk = p_walk->p_next; p_walk->p_next = req; } else proxy.p_clnts = req; proxy.clnt_count += 1; pthread_mutex_unlock(&proxy.clnt_mutex); } else dprintf(DBG_MSG, "add_connection: fd %d: virtual memory exhausted, abort\n", sock_fd); } } /* ---------------------------------------------------------------------------- ** Initialize state for a new device */ static void vbi_proxyd_add_device( const char * p_dev_name ) { PROXY_DEV * p_proxy_dev; if (proxy.dev_count < SRV_MAX_DEVICES) { p_proxy_dev = proxy.dev + proxy.dev_count; p_proxy_dev->p_dev_name = p_dev_name; p_proxy_dev->p_sock_path = vbi_proxy_msg_get_socket_name(p_dev_name); p_proxy_dev->pipe_fd = -1; p_proxy_dev->vbi_fd = -1; p_proxy_dev->wr_fd = -1; /* initialize synchonization facilities */ pthread_cond_init(&p_proxy_dev->start_cond, NULL); pthread_mutex_init(&p_proxy_dev->start_mutex, NULL); pthread_mutex_init(&p_proxy_dev->queue_mutex, NULL); proxy.dev_count += 1; } } /* ---------------------------------------------------------------------------- ** Transmit one buffer of sliced data ** - returns FALSE upon I/O error ** - also returns a "blocked" flag which is TRUE if not all data could be written ** can be used by the caller to "stuff" the pipe, i.e. write a series of messages ** until the pipe is full ** - XXX optimization required: don't copy the block (required however if client ** doesn't want all services) */ static vbi_bool vbi_proxyd_send_sliced( PROXY_CLNT * req, vbi_bool * p_blocked ) { VBIPROXY_MSG * p_msg; uint32_t msg_size; vbi_bool result = FALSE; int max_lines; int idx; if ((req != NULL) && (p_blocked != NULL) && (req->p_sliced != NULL)) { if (VBI_RAW_SERVICES(req->all_services)) msg_size = VBIPROXY_SLICED_IND_SIZE(0, req->p_sliced->max_lines); else msg_size = VBIPROXY_SLICED_IND_SIZE(req->p_sliced->line_count, 0); msg_size += sizeof(VBIPROXY_MSG_HEADER); p_msg = malloc(msg_size); /* filter for services requested by this client */ max_lines = req->vbi_count[0] + req->vbi_count[1]; p_msg->body.sliced_ind.timestamp = req->p_sliced->timestamp; p_msg->body.sliced_ind.sliced_lines = 0; p_msg->body.sliced_ind.raw_lines = 0; /* XXX TODO allow both raw and sliced in the same message */ if (VBI_RAW_SERVICES(req->all_services) == FALSE) { for (idx = 0; (idx < req->p_sliced->line_count) && (idx < max_lines); idx++) { if ((req->p_sliced->lines[idx].id & req->all_services) != 0) { memcpy(p_msg->body.sliced_ind.u.sliced + p_msg->body.sliced_ind.sliced_lines, req->p_sliced->lines + idx, sizeof(vbi_sliced)); p_msg->body.sliced_ind.sliced_lines += 1; } } } else { if (req->p_sliced->p_raw_data != NULL) { memcpy(p_msg->body.sliced_ind.u.raw, req->p_sliced->p_raw_data, VBIPROXY_RAW_LINE_SIZE * req->p_sliced->max_lines); p_msg->body.sliced_ind.raw_lines = req->p_sliced->max_lines; } } msg_size = VBIPROXY_SLICED_IND_SIZE(p_msg->body.sliced_ind.sliced_lines, p_msg->body.sliced_ind.raw_lines); vbi_proxy_msg_write(&req->io, MSG_TYPE_SLICED_IND, msg_size, p_msg, TRUE); if (vbi_proxy_msg_handle_write(&req->io, p_blocked)) { /* if the last block could not be transmitted fully, quit the loop */ if (req->io.writeLen > 0) { dprintf(DBG_CLNT, "send_sliced: socket blocked\n"); *p_blocked = TRUE; } result = TRUE; } } else dprintf(DBG_MSG, "send_sliced: illegal NULL ptr params\n"); return result; } /* ---------------------------------------------------------------------------- ** Checks the size of a message from client to server */ static vbi_bool vbi_proxyd_check_msg( VBIPROXY_MSG * pMsg, vbi_bool * pEndianSwap ) { VBIPROXY_MSG_HEADER * pHead = &pMsg->head; VBIPROXY_MSG_BODY * pBody = &pMsg->body; unsigned int len = pMsg->head.len; vbi_bool result = FALSE; switch (pHead->type) { case MSG_TYPE_CONNECT_REQ: if ( (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->connect_req)) && (memcmp(pBody->connect_req.magics.protocol_magic, VBIPROXY_MAGIC_STR, VBIPROXY_MAGIC_LEN) == 0) ) { if (pBody->connect_req.magics.endian_magic == VBIPROXY_ENDIAN_MAGIC) { *pEndianSwap = FALSE; result = TRUE; } else if (pBody->connect_req.magics.endian_magic == VBIPROXY_ENDIAN_MISMATCH) { *pEndianSwap = TRUE; result = TRUE; } } break; case MSG_TYPE_SERVICE_REQ: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->service_req)); break; case MSG_TYPE_CHN_TOKEN_REQ: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_token_req)); break; case MSG_TYPE_CHN_NOTIFY_REQ: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_notify_req)); break; case MSG_TYPE_CHN_SUSPEND_REQ: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_notify_req)); break; case MSG_TYPE_CHN_IOCTL_REQ: result = (len == sizeof(VBIPROXY_MSG_HEADER) + VBIPROXY_CHN_IOCTL_REQ_SIZE(pBody->chn_ioctl_req.arg_size)); break; case MSG_TYPE_CHN_RECLAIM_CNF: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_reclaim_cnf)); break; case MSG_TYPE_CLOSE_REQ: result = (len == sizeof(VBIPROXY_MSG_HEADER)); break; case MSG_TYPE_DAEMON_PID_REQ: result = ( (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->daemon_pid_req)) && (memcmp(pBody->daemon_pid_req.magics.protocol_magic, VBIPROXY_MAGIC_STR, VBIPROXY_MAGIC_LEN) == 0) && (pBody->daemon_pid_req.magics.endian_magic == VBIPROXY_ENDIAN_MAGIC) ); break; case MSG_TYPE_DAEMON_PID_CNF: /* note: this is a daemon reply but accepted here since the daemon sends it to itself */ result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->daemon_pid_cnf)); break; case MSG_TYPE_CONNECT_CNF: case MSG_TYPE_CONNECT_REJ: case MSG_TYPE_SERVICE_CNF: case MSG_TYPE_SERVICE_REJ: case MSG_TYPE_SLICED_IND: case MSG_TYPE_CHN_TOKEN_CNF: case MSG_TYPE_CHN_TOKEN_IND: case MSG_TYPE_CHN_NOTIFY_CNF: case MSG_TYPE_CHN_SUSPEND_CNF: case MSG_TYPE_CHN_SUSPEND_REJ: case MSG_TYPE_CHN_IOCTL_CNF: case MSG_TYPE_CHN_IOCTL_REJ: case MSG_TYPE_CHN_RECLAIM_REQ: case MSG_TYPE_CHN_CHANGE_IND: dprintf(DBG_MSG, "check_msg: recv client msg %d (%s) at server side\n", pHead->type, vbi_proxy_msg_debug_get_type_str(pHead->type)); result = FALSE; break; default: dprintf(DBG_MSG, "check_msg: unknown msg #%d\n", pHead->type); result = FALSE; break; } if (result == FALSE) dprintf(DBG_MSG, "check_msg: illegal msg: len=%d, type=%d (%s)\n", len, pHead->type, vbi_proxy_msg_debug_get_type_str(pHead->type)); return result; } /* ---------------------------------------------------------------------------- ** Handle message from client ** - note: consistancy checks were already done by the I/O handler ** except for higher level messages (must be checked by acqctl module) ** - implemented as a matrix: "switch" over server state, and "if" cascades ** over message type ** - XXX warning: inbound messages use the same buffer as outbound! ** must have finished evaluating the message before assembling the reply */ static vbi_bool vbi_proxyd_take_message( PROXY_CLNT *req, VBIPROXY_MSG * pMsg ) { VBIPROXY_MSG_BODY * pBody = &pMsg->body; vbi_bool result = FALSE; dprintf(DBG_CLNT, "take_message: fd %d: recv msg type %d (%s)\n", req->io.sock_fd, pMsg->head.type, vbi_proxy_msg_debug_get_type_str(pMsg->head.type)); switch (pMsg->head.type) { case MSG_TYPE_CONNECT_REQ: if (req->state == REQ_STATE_WAIT_CON_REQ) { if (pBody->connect_req.magics.protocol_compat_version == VBIPROXY_COMPAT_VERSION) { dprintf(DBG_MSG, "New client: fd %d: '%s' pid=%d services=0x%X\n", req->io.sock_fd, pBody->connect_req.client_name, pBody->connect_req.pid, pBody->connect_req.services); /* if provided, update norm hint (used for first client on ancient v4l1 drivers only) */ if (pBody->connect_req.scanning != 0) proxy.dev[req->dev_idx].scanning = pBody->connect_req.scanning; /* enable forwarding of captured data (must be set before processing request!) */ req->state = REQ_STATE_FORWARD; req->buffer_count = pBody->connect_req.buffer_count; req->client_flags = pBody->connect_req.client_flags; /* XXX TODO (timeout supression) */ /* must make very sure strict is within bounds, because it's used as array index */ if (pBody->connect_req.strict < VBI_MIN_STRICT) pBody->connect_req.strict = VBI_MIN_STRICT; else if (pBody->connect_req.strict > VBI_MAX_STRICT) pBody->connect_req.strict = VBI_MAX_STRICT; if ( vbi_proxyd_take_service_req(req, pBody->connect_req.services, pBody->connect_req.strict, (char *) req->msg_buf.body.connect_rej.errorstr) ) { /* open & service initialization succeeded -> reply with confirm */ vbi_proxy_msg_fill_magics(&req->msg_buf.body.connect_cnf.magics); strlcpy((char *) req->msg_buf.body.connect_cnf.dev_vbi_name, proxy.dev[req->dev_idx].p_dev_name, VBIPROXY_DEV_NAME_MAX_LENGTH); req->msg_buf.body.connect_cnf.dev_vbi_name[VBIPROXY_DEV_NAME_MAX_LENGTH - 1] = 0; req->msg_buf.body.connect_cnf.pid = getpid(); req->msg_buf.body.connect_cnf.vbi_api_revision = proxy.dev[req->dev_idx].vbi_api; req->msg_buf.body.connect_cnf.daemon_flags = ((opt_debug_level > 0) ? VBI_PROXY_DAEMON_NO_TIMEOUTS : 0); req->msg_buf.body.connect_cnf.services = req->all_services; if (proxy.dev[req->dev_idx].p_decoder != NULL) { req->msg_buf.body.connect_cnf.dec = *proxy.dev[req->dev_idx].p_decoder; req->msg_buf.body.connect_cnf.dec.pattern = NULL; } else { /* acquisition not running: if the request is still considered sucessful ** this is only possible if no services were requested */ memset(&req->msg_buf.body.connect_cnf.dec, 0, sizeof(req->msg_buf.body.connect_cnf.dec)); req->msg_buf.body.connect_cnf.dec.start[0] = -1; req->msg_buf.body.connect_cnf.dec.start[1] = -1; } vbi_proxy_msg_write(&req->io, MSG_TYPE_CONNECT_CNF, sizeof(req->msg_buf.body.connect_cnf), &req->msg_buf, FALSE); } else { vbi_proxy_msg_fill_magics(&req->msg_buf.body.connect_rej.magics); vbi_proxy_msg_write(&req->io, MSG_TYPE_CONNECT_REJ, sizeof(req->msg_buf.body.connect_rej), &req->msg_buf, FALSE); /* drop the connection after sending the reject message */ req->state = REQ_STATE_WAIT_CLOSE; } } else { /* client uses incompatible protocol version */ vbi_proxy_msg_fill_magics(&req->msg_buf.body.connect_rej.magics); strlcpy((char *) req->msg_buf.body.connect_rej.errorstr, "Incompatible proxy protocol version", VBIPROXY_ERROR_STR_MAX_LENGTH); req->msg_buf.body.connect_rej.errorstr[VBIPROXY_ERROR_STR_MAX_LENGTH - 1] = 0; vbi_proxy_msg_write(&req->io, MSG_TYPE_CONNECT_REJ, sizeof(req->msg_buf.body.connect_rej), &req->msg_buf, FALSE); /* drop the connection */ req->state = REQ_STATE_WAIT_CLOSE; } result = TRUE; } break; case MSG_TYPE_DAEMON_PID_REQ: if (req->state == REQ_STATE_WAIT_CON_REQ) { /* this message can be sent instead of a connect request */ vbi_proxy_msg_fill_magics(&req->msg_buf.body.daemon_pid_cnf.magics); req->msg_buf.body.daemon_pid_cnf.pid = getpid(); vbi_proxy_msg_write(&req->io, MSG_TYPE_DAEMON_PID_CNF, sizeof(req->msg_buf.body.daemon_pid_cnf), &req->msg_buf, FALSE); req->state = REQ_STATE_WAIT_CLOSE; result = TRUE; } break; case MSG_TYPE_SERVICE_REQ: if (req->state == REQ_STATE_FORWARD) { if (pBody->service_req.reset) memset(req->services, 0, sizeof(req->services)); dprintf(DBG_MSG, "Update client: fd %d services: 0x%X (was %X)\n", req->io.sock_fd, pBody->service_req.services, req->all_services); /* flush all buffers in this client's queue */ pthread_mutex_lock(&proxy.dev[req->dev_idx].queue_mutex); while (req->p_sliced != NULL) { vbi_proxy_queue_release_sliced(req); } pthread_mutex_unlock(&proxy.dev[req->dev_idx].queue_mutex); if ( vbi_proxyd_take_service_req(req, pBody->service_req.services, pBody->service_req.strict, (char *) req->msg_buf.body.service_rej.errorstr) ) { if (proxy.dev[req->dev_idx].p_decoder != NULL) { req->msg_buf.body.service_cnf.dec = *proxy.dev[req->dev_idx].p_decoder; req->msg_buf.body.connect_cnf.dec.pattern = NULL; } else { /* acquisition not running: if the request is still considered sucessful ** this is only possible if no services were requested */ memset(&req->msg_buf.body.connect_cnf.dec, 0, sizeof(req->msg_buf.body.connect_cnf.dec)); req->msg_buf.body.connect_cnf.dec.start[0] = -1; req->msg_buf.body.connect_cnf.dec.start[1] = -1; } req->msg_buf.body.service_cnf.services = req->all_services; vbi_proxy_msg_write(&req->io, MSG_TYPE_SERVICE_CNF, sizeof(req->msg_buf.body.service_cnf), &req->msg_buf, FALSE); } else { vbi_proxy_msg_write(&req->io, MSG_TYPE_SERVICE_REJ, sizeof(req->msg_buf.body.service_rej), &req->msg_buf, FALSE); } result = TRUE; } break; case MSG_TYPE_CHN_TOKEN_REQ: if (req->state == REQ_STATE_FORWARD) { dprintf(DBG_MSG, "channel token request: fd %d: prio=%d sub-prio=0x%02X\n", req->io.sock_fd, pBody->chn_token_req.chn_prio, pBody->chn_token_req.chn_profile.sub_prio); /* update channel description and profile */ req->chn_prio = pBody->chn_token_req.chn_prio; req->chn_profile = pBody->chn_token_req.chn_profile; memset(&req->chn_state, 0, sizeof(req->chn_state)); /* XXX TODO: return elements: permitted, non_excl */ memset(&req->msg_buf.body.chn_token_cnf, 0, sizeof(req->msg_buf.body.chn_token_cnf)); vbi_proxyd_channel_update(req->dev_idx, req, FALSE); if (req->chn_state.token_state == REQ_TOKEN_GRANT) { req->chn_state.token_state = REQ_TOKEN_GRANTED; req->msg_buf.body.chn_token_cnf.token_ind = TRUE; } else { req->msg_buf.body.chn_token_cnf.token_ind = FALSE; } vbi_proxy_msg_write(&req->io, MSG_TYPE_CHN_TOKEN_CNF, sizeof(req->msg_buf.body.chn_token_cnf), &req->msg_buf, FALSE); result = TRUE; } break; case MSG_TYPE_CHN_NOTIFY_REQ: if (req->state == REQ_STATE_FORWARD) { vbi_bool chn_upd = FALSE; vbi_bool chn_forced = FALSE; dprintf(DBG_MSG, "channel notify: fd %d: flags=0x%X scanning=%d\n", req->io.sock_fd, pBody->chn_notify_req.notify_flags, pBody->chn_notify_req.scanning); if (pBody->chn_notify_req.notify_flags & VBI_PROXY_CHN_NORM) { /* query (verify) new scanning -> inform all clients (line count changes) */ vbi_proxyd_update_scanning(req->dev_idx, req, pBody->chn_notify_req.scanning); } if (pBody->chn_notify_req.notify_flags & VBI_PROXY_CHN_FAIL) { // XXX TODO: ignore if client hasn't got the token // else inform scheduler: } if (pBody->chn_notify_req.notify_flags & VBI_PROXY_CHN_FLUSH) { vbi_proxyd_channel_flush(req->dev_idx, req); chn_upd = TRUE; chn_forced = ! REQ_CONTROLS_CHN(req->chn_state.token_state); } if (pBody->chn_notify_req.notify_flags & VBI_PROXY_CHN_RELEASE) { if (req->chn_state.token_state != REQ_TOKEN_NONE) { req->chn_state.token_state = REQ_TOKEN_NONE; chn_upd = TRUE; } req->chn_profile.is_valid = FALSE; } else if (pBody->chn_notify_req.notify_flags & VBI_PROXY_CHN_TOKEN) { req->chn_state.token_state = REQ_TOKEN_RETURNED; chn_upd = TRUE; } if (chn_upd) vbi_proxyd_channel_update(req->dev_idx, req, chn_forced); memset(&req->msg_buf.body.chn_notify_cnf, 0, sizeof(req->msg_buf.body.chn_notify_cnf)); req->msg_buf.body.chn_notify_cnf.scanning = proxy.dev[req->dev_idx].scanning; vbi_proxy_msg_write(&req->io, MSG_TYPE_CHN_NOTIFY_CNF, sizeof(req->msg_buf.body.chn_notify_cnf), &req->msg_buf, FALSE); req->chn_status_ind = VBI_PROXY_CHN_NONE; result = TRUE; } break; case MSG_TYPE_CHN_SUSPEND_REQ: /* XXX TODO */ vbi_proxy_msg_write(&req->io, MSG_TYPE_CHN_SUSPEND_REJ, sizeof(req->msg_buf.body.chn_suspend_rej), &req->msg_buf, FALSE); result = TRUE; break; case MSG_TYPE_CHN_IOCTL_REQ: if (req->state == REQ_STATE_FORWARD) { /* XXX TODO: message may be longer than pre-allocated message buffer */ if ( vbi_proxyd_take_ioctl_req(req, req->msg_buf.body.chn_ioctl_req.request, req->msg_buf.body.chn_ioctl_req.arg_data, req->msg_buf.body.chn_ioctl_req.arg_size, &req->msg_buf.body.chn_ioctl_cnf.result, &req->msg_buf.body.chn_ioctl_cnf.errcode) ) { /* note: argsize and arg_data unchanged from req. message */ dprintf(DBG_MSG, "channel control ioctl: fd %d: request=0x%X result=%d errno=%d\n", req->io.sock_fd, req->msg_buf.body.chn_ioctl_req.request, req->msg_buf.body.chn_ioctl_cnf.result, req->msg_buf.body.chn_ioctl_cnf.errcode); vbi_proxy_msg_write(&req->io, MSG_TYPE_CHN_IOCTL_CNF, VBIPROXY_CHN_IOCTL_CNF_SIZE(req->msg_buf.body.chn_ioctl_req.arg_size), &req->msg_buf, FALSE); } else { vbi_proxy_msg_write(&req->io, MSG_TYPE_CHN_IOCTL_REJ, sizeof(req->msg_buf.body.chn_ioctl_rej), &req->msg_buf, FALSE); } result = TRUE; } break; case MSG_TYPE_CHN_RECLAIM_CNF: if (req->chn_state.token_state == REQ_TOKEN_RELEASE) { dprintf(DBG_MSG, "channel token reclain confirm: fd %d\n", req->io.sock_fd); req->chn_state.token_state = REQ_TOKEN_NONE; vbi_proxyd_channel_update(req->dev_idx, NULL, FALSE); } result = TRUE; break; case MSG_TYPE_CLOSE_REQ: /* close the connection */ vbi_proxyd_close(req, FALSE); result = TRUE; break; default: /* unknown message or client-only message */ dprintf(DBG_MSG, "take_message: protocol error: unexpected message type %d (%s)\n", pMsg->head.type, vbi_proxy_msg_debug_get_type_str(pMsg->head.type)); break; } if (result == FALSE) dprintf(DBG_MSG, "take_message: message type %d (%s, len %d) not expected in state %d\n", pMsg->head.type, vbi_proxy_msg_debug_get_type_str(pMsg->head.type), pMsg->head.len, req->state); return result; } /* ---------------------------------------------------------------------------- ** Set bits for all active sockets in fd_set for select syscall */ static int vbi_proxyd_get_fd_set( fd_set * rd, fd_set * wr ) { PROXY_CLNT * req; PROXY_DEV * p_proxy_dev; int dev_idx; int max_fd; max_fd = 0; /* add TCP/IP and UNIX-domain listening sockets */ if ((proxy.max_conn == 0) || (proxy.clnt_count < proxy.max_conn)) { if (proxy.tcp_ip_fd != -1) { FD_SET(proxy.tcp_ip_fd, rd); if (proxy.tcp_ip_fd > max_fd) max_fd = proxy.tcp_ip_fd; } } /* add listening sockets and VBI devices, if currently opened */ p_proxy_dev = proxy.dev; for (dev_idx = 0; dev_idx < proxy.dev_count; dev_idx++, p_proxy_dev++) { if (p_proxy_dev->pipe_fd != -1) { FD_SET(p_proxy_dev->pipe_fd, rd); if (p_proxy_dev->pipe_fd > max_fd) max_fd = p_proxy_dev->pipe_fd; } if (p_proxy_dev->vbi_fd != -1) { FD_SET(p_proxy_dev->vbi_fd, rd); if (p_proxy_dev->vbi_fd > max_fd) max_fd = p_proxy_dev->vbi_fd; } } /* add client connection sockets */ for (req = proxy.p_clnts; req != NULL; req = req->p_next) { /* read and write are exclusive and write takes precedence over read ** (i.e. read only if no write is pending or if a read operation has already been started) */ if ( vbi_proxy_msg_read_idle(&req->io) == FALSE ) { FD_SET(req->io.sock_fd, rd); } else if ( (vbi_proxy_msg_write_idle(&req->io) == FALSE) || (req->p_sliced != NULL) || (req->chn_status_ind != VBI_PROXY_CHN_NONE) ) { FD_SET(req->io.sock_fd, wr); } else FD_SET(req->io.sock_fd, rd); if (req->io.sock_fd > max_fd) max_fd = req->io.sock_fd; } return max_fd; } /* ---------------------------------------------------------------------------- ** Proxy daemon central connection handling */ static void vbi_proxyd_handle_client_sockets( fd_set * rd, fd_set * wr ) { PROXY_CLNT *req; PROXY_CLNT *prev, *tmp; vbi_bool io_blocked; time_t now = time(NULL); /* handle active connections */ for (req = proxy.p_clnts, prev = NULL; req != NULL; ) { io_blocked = FALSE; if ( FD_ISSET(req->io.sock_fd, rd) && vbi_proxy_msg_write_idle(&req->io) ) { /* incoming data -> start reading */ dprintf(DBG_CLNT, "handle_client_sockets: fd %d: receiving data\n", req->io.sock_fd); if (vbi_proxy_msg_handle_read(&req->io, &io_blocked, TRUE, &req->msg_buf, sizeof(req->msg_buf))) { /* check for finished read -> process request */ if ( (req->io.readOff != 0) && (req->io.readOff == req->io.readLen) ) { if (vbi_proxyd_check_msg(&req->msg_buf, &req->endianSwap)) { vbi_proxy_msg_close_read(&req->io); if (vbi_proxyd_take_message(req, &req->msg_buf) == FALSE) { /* message no accepted (e.g. wrong state) */ vbi_proxyd_close(req, FALSE); } } else { /* message has illegal size or content */ vbi_proxyd_close(req, FALSE); } } } else vbi_proxyd_close(req, FALSE); } else if ( FD_ISSET(req->io.sock_fd, wr) && !vbi_proxy_msg_write_idle(&req->io) ) { if (vbi_proxy_msg_handle_write(&req->io, &io_blocked) == FALSE) { vbi_proxyd_close(req, FALSE); } } if (req->state == REQ_STATE_WAIT_CLOSE) { /* close was pending after last write */ vbi_proxyd_close(req, FALSE); } else if (vbi_proxy_msg_is_idle(&req->io)) { /* currently no I/O in progress */ if (req->chn_state.token_state == REQ_TOKEN_RECLAIM) { dprintf(DBG_MSG, "channel token reclaim: fd %d\n", req->io.sock_fd); /* XXX TODO: supervise return of token by timer */ memset(&req->msg_buf, 0, sizeof(req->msg_buf)); vbi_proxy_msg_write(&req->io, MSG_TYPE_CHN_RECLAIM_REQ, sizeof(req->msg_buf.body.chn_reclaim_req), &req->msg_buf, FALSE); req->chn_state.token_state = REQ_TOKEN_RELEASE; } else if (req->chn_state.token_state == REQ_TOKEN_GRANT) { dprintf(DBG_MSG, "channel token grant: fd %d\n", req->io.sock_fd); memset(&req->msg_buf, 0, sizeof(req->msg_buf)); vbi_proxy_msg_write(&req->io, MSG_TYPE_CHN_TOKEN_IND, sizeof(req->msg_buf.body.chn_token_ind), &req->msg_buf, FALSE); req->chn_state.token_state = REQ_TOKEN_GRANTED; } else if (req->chn_status_ind) { /* send channel change indication */ memset(&req->msg_buf, 0, sizeof(req->msg_buf)); req->msg_buf.body.chn_change_ind.notify_flags = req->chn_status_ind; req->msg_buf.body.chn_change_ind.scanning = proxy.dev[req->dev_idx].scanning; vbi_proxy_msg_write(&req->io, MSG_TYPE_CHN_CHANGE_IND, sizeof(req->msg_buf.body.chn_change_ind), &req->msg_buf, FALSE); req->chn_status_ind = VBI_PROXY_CHN_NONE; } else { /* forward data from slicer out queue */ while ((req->p_sliced != NULL) && (io_blocked == FALSE)) { dprintf(DBG_QU, "handle_sockets: fd %d: forward sliced frame with %d lines (of max %d)\n", req->io.sock_fd, req->p_sliced->line_count, req->p_sliced->max_lines); if (vbi_proxyd_send_sliced(req, &io_blocked) ) { /* only in success case because close releases all buffers */ pthread_mutex_lock(&proxy.dev[req->dev_idx].queue_mutex); vbi_proxy_queue_release_sliced(req); pthread_mutex_unlock(&proxy.dev[req->dev_idx].queue_mutex); } else { /* I/O error */ vbi_proxyd_close(req, FALSE); io_blocked = TRUE; } } } } if (req->io.sock_fd == -1) { /* free resources (should be redundant, but does no harm) */ vbi_proxyd_close(req, FALSE); } else if ( (req->state == REQ_STATE_WAIT_CON_REQ) && ((req->client_flags & VBI_PROXY_CLIENT_NO_TIMEOUTS) == 0) && vbi_proxy_msg_check_timeout(&req->io, now) ) { dprintf(DBG_MSG, "handle_sockets: fd %d: i/o timeout in state %d (writeLen=%d, readLen=%d, readOff=%d, read msg type=%d: %s)\n", req->io.sock_fd, req->state, req->io.writeLen, req->io.readLen, req->io.readOff, req->msg_buf.head.type, vbi_proxy_msg_debug_get_type_str(req->msg_buf.head.type)); vbi_proxyd_close(req, FALSE); } else /* check for protocol or network I/O timeout */ if ( (req->state == REQ_STATE_WAIT_CON_REQ) && (now > req->io.lastIoTime + SRV_CONNECT_TIMEOUT) ) { dprintf(DBG_MSG, "handle_sockets: fd %d: protocol timeout in state %d\n", req->io.sock_fd, req->state); vbi_proxyd_close(req, FALSE); } if (req->state == REQ_STATE_CLOSED) { /* connection was closed after network error */ unsigned int clnt_services = req->all_services; int dev_idx = req->dev_idx; if (proxy.clnt_count > 0) proxy.clnt_count -= 1; dprintf(DBG_MSG, "handle_sockets: closed conn, %d remain\n", proxy.clnt_count); pthread_mutex_lock(&proxy.clnt_mutex); /* unlink from list */ tmp = req; if (prev == NULL) { proxy.p_clnts = req->p_next; req = proxy.p_clnts; } else { prev->p_next = req->p_next; req = req->p_next; } pthread_mutex_unlock(&proxy.clnt_mutex); if (clnt_services != 0) vbi_proxyd_update_services(dev_idx, NULL, 0, NULL); if (proxy.dev[dev_idx].p_capture != NULL) vbi_proxyd_channel_update(dev_idx, NULL, FALSE); free(tmp); } else { prev = req; req = req->p_next; } } } /* ---------------------------------------------------------------------------- ** Set maximum number of open client connections ** - note: does not close connections if max count is already exceeded */ static void vbi_proxyd_set_max_conn( unsigned int max_conn ) { proxy.max_conn = max_conn; } /* ---------------------------------------------------------------------------- ** Set server IP address ** - must be called before the listening sockets are created */ static void vbi_proxyd_set_address( vbi_bool do_tcp_ip, const char * pIpStr, const char * pPort ) { /* free the memory allocated for the old config strings */ if (proxy.listen_ip != NULL) { free(proxy.listen_ip); proxy.listen_ip = NULL; } if (proxy.listen_port != NULL) { free(proxy.listen_port); proxy.listen_port = NULL; } /* make a copy of the new config strings */ if (pIpStr != NULL) { proxy.listen_ip = malloc(strlen(pIpStr) + 1); strcpy(proxy.listen_ip, pIpStr); } if (pPort != NULL) { proxy.listen_port = malloc(strlen(pPort) + 1); strcpy(proxy.listen_port, pPort); } proxy.do_tcp_ip = do_tcp_ip; } /* ---------------------------------------------------------------------------- ** Emulate device permissions on the socket file */ static void vbi_proxyd_set_socket_perm( PROXY_DEV * p_proxy_dev ) { struct stat st; if (stat(p_proxy_dev->p_dev_name, &st) != -1) { if ( (chown(p_proxy_dev->p_sock_path, st.st_uid, st.st_gid) != 0) && (chown(p_proxy_dev->p_sock_path, geteuid(), st.st_gid) != 0) ) dprintf(DBG_MSG, "set_perm: failed to set socket owner %d.%d: %s\n", st.st_uid, st.st_gid, strerror(errno)); if (chmod(p_proxy_dev->p_sock_path, st.st_mode) != 0) dprintf(DBG_MSG, "set_perm: failed to set socket permission %o: %s\n", st.st_mode, strerror(errno)); } else dprintf(DBG_MSG, "set_perm: failed to stat VBI device %s\n", p_proxy_dev->p_dev_name); } /* ---------------------------------------------------------------------------- ** Stop the server, close all connections, free resources */ static void vbi_proxyd_destroy( void ) { PROXY_CLNT *req, *p_next; int dev_idx; /* close all devices */ for (dev_idx = 0; dev_idx < proxy.dev_count; dev_idx++) { vbi_proxy_stop_acquisition(proxy.dev + dev_idx); } /* shutdown all client connections & free resources */ req = proxy.p_clnts; while (req != NULL) { p_next = req->p_next; vbi_proxyd_close(req, TRUE); free(req); req = p_next; } proxy.p_clnts = NULL; proxy.clnt_count = 0; /* close listening sockets */ for (dev_idx = 0; dev_idx < proxy.dev_count; dev_idx++) { if (proxy.dev[dev_idx].pipe_fd != -1) { vbi_proxy_msg_stop_listen(FALSE, proxy.dev[dev_idx].pipe_fd, proxy.dev[dev_idx].p_sock_path); } if (proxy.dev[dev_idx].p_sock_path != NULL) free(proxy.dev[dev_idx].p_sock_path); pthread_cond_destroy(&proxy.dev[dev_idx].start_cond); pthread_mutex_destroy(&proxy.dev[dev_idx].start_mutex); pthread_mutex_destroy(&proxy.dev[dev_idx].queue_mutex); } if (proxy.tcp_ip_fd != -1) { vbi_proxy_msg_stop_listen(TRUE, proxy.tcp_ip_fd, NULL); } vbi_proxy_msg_logger(LOG_NOTICE, -1, 0, "shutting down", NULL); /* free the memory allocated for the config strings */ vbi_proxyd_set_address(FALSE, NULL, NULL); vbi_proxy_msg_set_logging(FALSE, 0, 0, NULL); } /* --------------------------------------------------------------------------- ** Signal handler to process alarm */ static void vbi_proxyd_alarm_handler( int sigval ) { sigval = sigval; proxy.chn_sched_alarm = TRUE; } /* --------------------------------------------------------------------------- ** Signal handler to catch deadly signals */ static void vbi_proxyd_signal_handler( int sigval ) { char str_buf[10]; snprintf(str_buf, sizeof (str_buf), "%d", sigval); vbi_proxy_msg_logger(LOG_NOTICE, -1, 0, "terminated by signal", str_buf, NULL); proxy.should_exit = TRUE; } /* ---------------------------------------------------------------------------- ** Initialize DB server */ static void vbi_proxyd_init( void ) { struct sigaction act; if (opt_no_detach == FALSE) { if (fork() > 0) exit(0); close(0); open("/dev/null", O_RDONLY, 0); if (opt_debug_level == 0) { close(1); open("/dev/null", O_WRONLY, 0); close(2); if (dup(1) == -1) exit(1); setsid(); } } /* ignore broken pipes (handled by select/read) */ memset(&act, 0, sizeof(act)); act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, NULL); /* handle alarm timers (for channel change scheduling) */ memset(&act, 0, sizeof(act)); act.sa_handler = vbi_proxyd_alarm_handler; sigaction(SIGALRM, &act, NULL); /* catch deadly signals for a clean shutdown (remove socket file) */ memset(&act, 0, sizeof(act)); sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask, SIGINT); sigaddset(&act.sa_mask, SIGTERM); sigaddset(&act.sa_mask, SIGHUP); act.sa_handler = vbi_proxyd_signal_handler; act.sa_flags = SA_ONESHOT; sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGHUP, &act, NULL); } /* ---------------------------------------------------------------------------- ** Set up sockets for listening to client requests */ static vbi_bool vbi_proxyd_listen( void ) { PROXY_DEV * p_proxy_dev; int dev_idx; vbi_bool result; result = TRUE; p_proxy_dev = proxy.dev; for (dev_idx = 0; (dev_idx < proxy.dev_count) && result; dev_idx++, p_proxy_dev++) { if (vbi_proxy_msg_check_connect(p_proxy_dev->p_sock_path) == FALSE) { /* create named socket in /tmp for listening to local clients */ p_proxy_dev->pipe_fd = vbi_proxy_msg_listen_socket(FALSE, NULL, p_proxy_dev->p_sock_path); if (p_proxy_dev->pipe_fd != -1) { /* copy VBI device permissions to the listening socket */ vbi_proxyd_set_socket_perm(p_proxy_dev); vbi_proxy_msg_logger(LOG_NOTICE, -1, 0, "started listening on local socket for ", p_proxy_dev->p_dev_name, NULL); } else result = FALSE; } else { vbi_proxy_msg_logger(LOG_ERR, -1, 0, "a proxy daemon is already running for ", p_proxy_dev->p_dev_name, NULL); result = FALSE; } } if (proxy.do_tcp_ip && result) { /* create TCP/IP socket */ proxy.tcp_ip_fd = vbi_proxy_msg_listen_socket(TRUE, proxy.listen_ip, proxy.listen_port); if (proxy.tcp_ip_fd != -1) { vbi_proxy_msg_logger(LOG_NOTICE, -1, 0, "started listening on TCP/IP socket", NULL); } else result = FALSE; } return result; } /* --------------------------------------------------------------------------- ** Proxy daemon main loop */ static void vbi_proxyd_main_loop( void ) { fd_set rd, wr; int max_fd; int sel_cnt; int dev_idx; while (proxy.should_exit == FALSE) { FD_ZERO(&rd); FD_ZERO(&wr); max_fd = vbi_proxyd_get_fd_set(&rd, &wr); /* wait for new clients, client messages or VBI device data (indefinitly) */ sel_cnt = select(((max_fd > 0) ? (max_fd + 1) : 0), &rd, &wr, NULL, NULL); if (sel_cnt != -1) { if (sel_cnt > 0) dprintf(DBG_CLNT, "main_loop: select: events on %d sockets\n", sel_cnt); for (dev_idx = 0; dev_idx < proxy.dev_count; dev_idx++) { /* accept new client connections on device socket */ if ((proxy.dev[dev_idx].pipe_fd != -1) && (FD_ISSET(proxy.dev[dev_idx].pipe_fd, &rd))) { vbi_proxyd_add_connection(proxy.dev[dev_idx].pipe_fd, dev_idx, TRUE); } /* check for incoming data on VBI device */ if ((proxy.dev[dev_idx].vbi_fd != -1) && (FD_ISSET(proxy.dev[dev_idx].vbi_fd, &rd))) { if (proxy.dev[dev_idx].use_thread == FALSE) { vbi_proxyd_forward_data(dev_idx); } else { /* message from acq thread slave: ** sent data is only a trigger to wake up from select() above -> discard it */ char dummy_buf[100]; int rd_count; do { rd_count = read(proxy.dev[dev_idx].vbi_fd, dummy_buf, sizeof(dummy_buf)); dprintf(DBG_QU, "main_loop: read from acq thread dev #%d pipe fd %d: %d errno=%d\n", dev_idx, proxy.dev[dev_idx].vbi_fd, sel_cnt, errno); } while (rd_count == 100); } } } /* accept new TCP/IP connections */ if ((proxy.tcp_ip_fd != -1) && (FD_ISSET(proxy.tcp_ip_fd, &rd))) { vbi_proxyd_add_connection(proxy.tcp_ip_fd, 0, FALSE); } /* send queued data or process incoming messages from clients */ vbi_proxyd_handle_client_sockets(&rd, &wr); if (proxy.chn_sched_alarm) { proxy.chn_sched_alarm = FALSE; vbi_proxyd_channel_timer(); } } else { if (errno != EINTR) { /* select syscall failed */ dprintf(DBG_MSG, "main_loop: select with max. fd %d: %s\n", max_fd, strerror(errno)); sleep(1); } } } } /* --------------------------------------------------------------------------- ** Kill-daemon only: exit upon timeout in I/O to daemon */ static void vbi_proxyd_kill_timeout( int sigval ) { sigval = sigval; /* note: cannot use printf in signal handler without risking deadlock */ exit(2); } /* --------------------------------------------------------------------------- ** Connect to running daemon, query its pid and kill it, exit. */ static void vbi_proxyd_kill_daemon( void ) { struct sigaction act; char * p_errorstr; char * p_srv_port; vbi_bool io_blocked; VBIPROXY_MSG msg_buf; VBIPROXY_MSG_STATE io; memset(&io, 0, sizeof(io)); io.sock_fd = -1; p_errorstr = NULL; p_srv_port = vbi_proxy_msg_get_socket_name(proxy.dev[0].p_dev_name); if (p_srv_port == NULL) goto failure; io.sock_fd = vbi_proxy_msg_connect_to_server(FALSE, NULL, p_srv_port, &p_errorstr); if (io.sock_fd == -1) goto failure; memset(&act, 0, sizeof(act)); act.sa_handler = vbi_proxyd_kill_timeout; sigaction(SIGALRM, &act, NULL); /* use non-blocking I/O and alarm timer for timeout handling (simpler than select) */ alarm(4); fcntl(io.sock_fd, F_SETFL, 0); /* wait for socket to reach connected state */ if (vbi_proxy_msg_finish_connect(io.sock_fd, &p_errorstr) == FALSE) goto failure; /* write service request parameters */ vbi_proxy_msg_fill_magics(&msg_buf.body.daemon_pid_req.magics); /* send the pid request message to the proxy server */ vbi_proxy_msg_write(&io, MSG_TYPE_DAEMON_PID_REQ, sizeof(msg_buf.body.daemon_pid_req), &msg_buf, FALSE); if (vbi_proxy_msg_handle_write(&io, &io_blocked) == FALSE) goto io_error; if (vbi_proxy_msg_handle_read(&io, &io_blocked, TRUE, &msg_buf, sizeof(msg_buf)) == FALSE) goto io_error; if ( (vbi_proxyd_check_msg(&msg_buf, FALSE) == FALSE) || (msg_buf.head.type != MSG_TYPE_DAEMON_PID_CNF) ) { asprintf(&p_errorstr, "%s", "Proxy protocol error"); goto failure; } if (kill(msg_buf.body.daemon_pid_cnf.pid, SIGTERM) != 0) { asprintf(&p_errorstr, "Failed to kill the daemon process (pid %d): %s", msg_buf.body.daemon_pid_cnf.pid, strerror(errno)); goto failure; } dprintf(DBG_MSG, "Killed daemon process %d.\n", msg_buf.body.daemon_pid_cnf.pid); close(io.sock_fd); exit(0); io_error: if (p_errorstr == NULL) asprintf(&p_errorstr, "Lost connection to proxy (I/O error)"); failure: /* failed to establish a connection to the server */ if (io.sock_fd != -1) close(io.sock_fd); if (p_errorstr != NULL) { fprintf(stderr, "%s\n", p_errorstr); free(p_errorstr); } exit(1); } /* --------------------------------------------------------------------------- ** Print usage and exit */ static void proxy_usage_exit( const char *argv0, const char *argvn, const char * reason ) { fprintf(stderr, "%s: %s: %s\n" "Options:\n" " -dev : VBI device path (allowed repeatedly)\n" " -buffers : number of raw capture buffers (v4l2 only)\n" " -nodetach : process remains connected to tty\n" " -kill : kill running daemon process, then exit\n" " -debug : enable debug output: 1=warnings, 2=all\n" " -syslog : enable syslog output\n" " -loglevel : log file level\n" " -logfile : log file name\n" " -maxclients : max. number of clients\n" " -help : this message\n", argv0, reason, argvn); exit(1); } /* --------------------------------------------------------------------------- ** Parse numeric value in command line options */ static vbi_bool proxy_parse_argv_numeric( char * p_number, int * p_value ) { char * p_num_end; if (*p_number != 0) { *p_value = strtol(p_number, &p_num_end, 0); return (*p_num_end == 0); } else return FALSE; } /* --------------------------------------------------------------------------- ** Parse command line options */ static void vbi_proxyd_parse_argv( int argc, char * argv[] ) { struct stat stb; int arg_val; int arg_idx = 1; while (arg_idx < argc) { if (strcasecmp(argv[arg_idx], "-dev") == 0) { if (arg_idx + 1 < argc) { if (proxy.dev_count >= SRV_MAX_DEVICES) proxy_usage_exit(argv[0], argv[arg_idx], "too many device paths"); if (stat(argv[arg_idx + 1], &stb) == -1) proxy_usage_exit(argv[0], argv[arg_idx +1], strerror(errno)); if (!S_ISCHR(stb.st_mode)) proxy_usage_exit(argv[0], argv[arg_idx +1], "not a character device"); if (access(argv[arg_idx + 1], R_OK | W_OK) == -1) proxy_usage_exit(argv[0], argv[arg_idx +1], "failed to access device"); vbi_proxyd_add_device(argv[arg_idx + 1]); arg_idx += 2; } else proxy_usage_exit(argv[0], argv[arg_idx], "missing mode keyword after"); } else if (strcasecmp(argv[arg_idx], "-buffers") == 0) { if ((arg_idx + 1 < argc) && proxy_parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_buffer_count = arg_val; if ((opt_buffer_count < 1) || (opt_buffer_count > VBI_MAX_BUFFER_COUNT)) proxy_usage_exit(argv[0], argv[arg_idx], "buffer count unsupported"); arg_idx += 2; } else proxy_usage_exit(argv[0], argv[arg_idx], "missing buffer count after"); } else if (strcasecmp(argv[arg_idx], "-debug") == 0) { if ((arg_idx + 1 < argc) && proxy_parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_debug_level = arg_val; if (opt_debug_level > 0) opt_debug_level |= DBG_MSG; arg_idx += 2; } else proxy_usage_exit(argv[0], argv[arg_idx], "missing debug level after"); } else if (strcasecmp(argv[arg_idx], "-nodetach") == 0) { opt_no_detach = TRUE; arg_idx += 1; } else if (strcasecmp(argv[arg_idx], "-kill") == 0) { opt_kill_daemon = TRUE; arg_idx += 1; } else if (strcasecmp(argv[arg_idx], "-syslog") == 0) { if ((arg_idx + 1 < argc) && proxy_parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_syslog_level = arg_val; arg_idx += 2; } else proxy_usage_exit(argv[0], argv[arg_idx], "missing log level after"); } else if (strcasecmp(argv[arg_idx], "-loglevel") == 0) { if ((arg_idx + 1 < argc) && proxy_parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_log_level = arg_val; arg_idx += 2; } else proxy_usage_exit(argv[0], argv[arg_idx], "missing log level after"); } else if (strcasecmp(argv[arg_idx], "-logfile") == 0) { if (arg_idx + 1 < argc) { p_opt_log_name = argv[arg_idx + 1]; arg_idx += 2; } else proxy_usage_exit(argv[0], argv[arg_idx], "missing mode keyword after"); } else if (strcasecmp(argv[arg_idx], "-maxclients") == 0) { if ((arg_idx + 1 < argc) && proxy_parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_max_clients = arg_val; arg_idx += 2; } else proxy_usage_exit(argv[0], argv[arg_idx], "missing log level after"); } else if (strcasecmp(argv[arg_idx], "-help") == 0) { char versbuf[50]; snprintf(versbuf, sizeof (versbuf), "(version %d.%d.%d)", VBIPROXY_VERSION>>16, (VBIPROXY_VERSION>>8)&0xff, VBIPROXY_VERSION&0xff); proxy_usage_exit(argv[0], versbuf, "the following options are available"); } else proxy_usage_exit(argv[0], argv[arg_idx], "unknown option or argument"); } /* if no device was given, use default path */ if (proxy.dev_count == 0) { /* use devfs path if subdirectory exists */ if (access(DEFAULT_VBI_DEVFS_PATH, R_OK | W_OK) == 0) vbi_proxyd_add_device(DEFAULT_VBI_DEVFS_PATH); else vbi_proxyd_add_device(DEFAULT_VBI_DEV_PATH); } } /* ---------------------------------------------------------------------------- ** Proxy daemon entry point */ int main( int argc, char ** argv ) { /* initialize state struct */ memset(&proxy, 0, sizeof(proxy)); proxy.tcp_ip_fd = -1; pthread_mutex_init(&proxy.clnt_mutex, NULL); vbi_proxyd_parse_argv(argc, argv); vbi_proxy_msg_set_debug_level( (opt_debug_level == 0) ? 0 : ((opt_debug_level & DBG_CLNT) ? 2 : 1) ); if (opt_kill_daemon) { vbi_proxyd_kill_daemon(); exit(0); } dprintf(DBG_MSG, "proxy daemon starting, rev.\n%s\n", rcsid); vbi_proxyd_init(); vbi_proxyd_set_max_conn(opt_max_clients); vbi_proxyd_set_address(FALSE, NULL, NULL); vbi_proxy_msg_set_logging(opt_debug_level > 0, opt_syslog_level, opt_log_level, p_opt_log_name); /* start listening for client connections */ if (vbi_proxyd_listen()) { vbi_proxyd_main_loop(); } vbi_proxyd_destroy(); pthread_mutex_destroy(&proxy.clnt_mutex); exit(0); return 0; } #endif /* ENABLE_PROXY */ zvbi-0.2.44/daemon/zvbi-chains.1000066400000000000000000000033251476363111200163240ustar00rootroot00000000000000.TH zvbi-chains 1 " " " " "VBI proxy wrapper" .SH NAME zvbi-chains - VBI proxy wrapper .SH SYNOPSIS .B zvbi-chains [ options ] .SH DESCRIPTION .B zvbi-chains is a small wrapper which executes the VBI application given on the command line while overloading several C library calls (such as open(2) and read(2)) so that the application can be forced to access VBI devices via the VBI proxy instead of device files directly. LD_PRELOAD is used to intercept C library calls and call functions in the libzvbi-chain shared library instead. Parameters given on the command line (e.g. device path) are passed to the library by means of environment variables. .SH OPTIONS .TP \fB-dev\fP path Path of a VBI device from which to read data. .TP \fB-debug\fP level Enables debug output: 0 = off (default); 1 = warnings, 2 = all messages. .TP \fB-help\fP Print a short description of all command line options. .SH SEE ALSO zvbid(1), v4l-conf(8) .br http://zapping.sourceforge.net/ (homepage) .SH AUTHOR Tom Zoerner (tomzo AT users.sourceforge.net) .SH COPYRIGHT Copyright (C) 2003, 2004 Tom Zoerner This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. zvbi-0.2.44/daemon/zvbid.1000066400000000000000000000044711476363111200152300ustar00rootroot00000000000000.TH zvbid 1 "(c) 2003 Tom Zoerner" " " "VBI proxy daemon" .SH NAME zvbid - VBI proxy daemon .SH SYNOPSIS .B zvbid [ options ] .SH DESCRIPTION .B zvbid is a proxy for VBI devices, i.e. it forwards one or more VBI data streams to one or more connected clients and manages channel change requests. .SH OPTIONS .TP \fB-dev\fP path Path of a device from which to read data. This argument can be given several times with different devices. .TP \fB-buffers\fP count Number of buffers to allocate for capturing VBI raw data from devices which support streaming (currently only video4linux, rev. 2) A higher number of buffers can prevent data loss in case of high latency. The downside is higher memory consumption (typically 65kB per buffer.) Default count is 8, maximum is 32. .TP \fB-nodetach\fP Daemon process remains connected to the terminal from which it was started (e.g. so that you can stop it by pressing Control-C keys). Intended for trouble shooting only. .TP \fB-kill\fP Terminates a proxy daemon running for the given device. .TP \fB-debug\fP level Enables debug output: 0= off(default); 1= general messages; In addition 2, 4, 8, ... can be added to enable debug output for various categories. .TP \fB-syslog\fP level Enables syslog output. .TP \fB-loglevel\fP level Log file level .TP \fB-logfile\fP path Path to the log file. .TP \fB-maxclients\fP count Max. number of clients which are allowed to connect simultaneously. .TP \fB-help\fP Print a short description of all command line options. .SH SEE ALSO zvbi-chains(1), v4l-conf(8) .br http://zapping.sourceforge.net/ (homepage) .SH AUTHOR Tom Zoerner (tomzo AT users.sourceforge.net) .SH COPYRIGHT Copyright (C) 2003,2004 Tom Zoerner This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. zvbi-0.2.44/daemon/zvbid.init.in000077500000000000000000000017531476363111200164430ustar00rootroot00000000000000#!/bin/sh # # $Id: zvbid.init.in,v 1.3 2004-10-04 20:48:58 mschimek Exp $ # Startup script for the zvbi daemon, RedHat style. # # This is just an example, it will not install. Packagers must # prepare an init script matching their system. # # chkconfig: - 90 10 # description: Proxy daemon to share a v4l vbi device between applications. # . /etc/init.d/functions prefix=@prefix@ exec_prefix=@exec_prefix@ [ -x @sbindir@/zvbid ] || exit 1 RETVAL=0 start(){ echo -n $"Starting vbi proxy daemon: " daemon zvbid RETVAL=$? echo touch /var/lock/subsys/zvbid return $RETVAL } stop(){ echo -n $"Stopping vbi proxy daemon: " killproc zvbid echo RETVAL=$? rm -f /var/lock/subsys/zvbid return $RETVAL } restart(){ stop start } case "$1" in start) start ;; stop) stop ;; restart) restart ;; status) status zvbid ;; condrestart) [ -e /var/lock/subsys/zvbid ] && restart ;; *) echo $"Usage: $0 {start|stop|status|restart|condrestart}" RETVAL=1 esac exit $RETVAL zvbi-0.2.44/data/000077500000000000000000000000001476363111200134705ustar00rootroot00000000000000zvbi-0.2.44/data/networks.dtd000066400000000000000000000155671476363111200160570ustar00rootroot00000000000000 (UT). --> zvbi-0.2.44/data/networks.xml000066400000000000000000003477021476363111200161030ustar00rootroot00000000000000 Albania AL 0x1900 Algeria DZ 0x1200 Andorra AD 0x1300 Austria AT 0x4300 0x1A00 ORF eins 0x4301 0x0AC1 ORF 2 0x4302 0x0AC2 ORF FS3 0x0AC3 ORF Sport+ 0x0AC4 ORF III 0x0AC7 Nick / Viva 0x0AC8 MTV Austria 0x0AC9 ATV 0x430C 0x0ACA ORF 2 Burgenland 0x0ACB ORF 2 Kärnten 0x0ACC ORF 2 Niederösterreich 0x0ACD ORF 2 Oberösterreich 0x0ACE ORF 2 Salzburg 0x0ACF ORF 2 Steiermark 0x0AD0 ORF 2 Tirol 0x0AD1 ORF 2 Vorarlberg 0x0AD2 ORF 2 Wien 0x0AD3 ATV2 0x0ADE Belarus BY 0x1700 Belgium BE 0x3200 0x1600 VT4 0x0404 0x1604 0x3604 VRT TV1 0x3201 0x1601 0x3603 CANVAS 0x3202 0x1602 0x3602 RTBF 1 0x3203 RTBF 2 0x3204 VTM 0x3205 0x1605 0x3605 Kanaal2 0x3206 0x1606 0x3606 RTBF Sat 0x3207 AB3 0x320C AB4e 0x320D Ring TV 0x320E JIM.tv 0x320F RTV-Kempen 0x3210 RTV-Mechelen 0x3211 MCM Belgium 0x3212 Vitaya 0x3213 WTV 0x3214 FocusTV 0x3215 Be 1 ana 0x3216 Be 1 num 0x3217 Be Ciné 1 0x3218 Be Sport 1 0x3219 PRIME Sport 1 0x321A PRIME Sport 2 0x321B PRIME Action 0x321C PRIME One 0x321D TV Brussel 0x321E AVSe 0x321F S televisie 0x3220 TV Limburg 0x3221 Kanaal 3 0x3222 ATV 0x3223 ROB TV 0x3224 Sporza 0x3226 VIJF tv 0x3227 Life!tv 0x3228 MTV Belgium FR 0x3229 EXQI Sport NL 0x322A EXQI Culture NL 0x322B Acht 0x322C EXQI Sport FR 0x322D EXQI Culture FR 0x322E Discovery Flanders 0x322F Télé Bruxelles 0x3230 Télésambre 0x3231 TV Com 0x3232 Canal Zoom 0x3233 Vidéoscope 0x3234 Canal C 0x3235 Télé MB 0x3236 Antenne Centre 0x3237 Télévesdre 0x3238 RTC Télé Liège 0x3239 EXQI Plus FR 0x323B EXQI Life NL 0x323C EXQI Life FR 0x323D EXQI News NL 0x323E EXQI News FR 0x323F No tele 0x3240 TV Lux 0x3241 Radio Contact Vision 0x3243 vtmKzoom 0x3243 NJAM 0x3244 Ketnet op 12 0x3245 MENT TV 0x3246 National Geographic Channel Belgium 0x3247 Libelle TV 0x3248 Lacht 0x3249 Kanaal Z - NL 0x325A CANAL Z - FR 0x325B CARTOON Network - NL 0x326A CARTOON Network - FR 0x326B LIBERTY CHANNEL - NL 0x327A LIBERTY CHANNEL - FR 0x327B TCM - NL 0x328A TCM - FR 0x328B Mozaiek/Mosaique 0x3298 Info Kanaal/Canal Info 0x3299 Be 1 + 1h 0x32A7 Be Ciné 2 0x32A8 Be Sport 2 0x32A9 Nickelodeon/MTV Belgium FR 0x32AA Nickelodeon Belgium NL 0x32AB Nickelodeon Belgium FR 0x32AC Nick Jr Belgium NL 0x32AD Nick Jr Belgium FR 0x32AE MTV Belgium NL 0x32AF anne 0x32B0 vtmKzoom+ 0x32B1 Bulgaria BG 0x2800 Croatia HR HRT1 0x0385 Nova TV 0x0386 HRT2 0x0387 HRT PLUS 0x0388 RTL Televizija 0x0400 RTL PLUS 0x0401 TV Nova 0x0402 RI-TV 0x0403 Kanal RI 0x0405 NIT 0x0406 Kapital Network 0x0407 Vox TV Zadar 0x0408 GTV Zadar 0x0409 TV Sibenik 0x040A VTV Varazdin 0x040B TV Cakovec 0x040C TV Jadran 0x040D Z1 0x040E OTV 0x040F 24sata.tv 0x0410 5 Kanal 0x0411 B1 TV (Bjelovarska TV) 0x0412 Croatian Music Channel 0x0413 Dubrovacka TV 0x0414 Infokanal Split 0x0415 Kutina TV 0x0416 Mini TV (NovaTV-djecji) 0x0417 Narodna TV 0x0418 Nezavisna televizija 0x0419 Osjecka televizija 0x041A RK TV 0x041B Saborska TV 0x041C Samoborska TV 0x041D Slavonsko-brodska Televizija 0x041E Splitska TV 0x041F Televizija Slavonije i Baranje 0x0420 Tportal.hr 0x0421 TV 4R 0x0422 TV Dalmacija 0x0423 TV Dugi Rat 0x0424 TV Plus 0x0425 TV Turopolje 0x0426 TV Velika Gorica 0x0427 Vecernji HR 0x0428 Vinkovacka televizija 0x0429 Zagrebacka Kablovska Televizija 0x042A Doma TV 0x042B RTL 2 0x042C HOO - Sport 0x042D HRT3 0x042E HRT4 0x042F Cyprus CY 0x2200 Czech Republic CZ 0x4200 0x3200 Barrandov TV 0x4200 CT 1 0x4201 0x32C1 0x3C21 CT 2 0x4202 0x32C2 0x3C22 NOVA TV 0x4203 0x32C3 0x3C23 Prima TV 0x4204 TV Praha 0x4205 TV HK 0x4206 TV Pardubice 0x4207 TV Brno 0x4208 Prima COOL 0x4209 CT24 0x420A CT4 0x420B CT5 0x420C Ocko TV 0x4210 CT1 Brno 0x4211 0x32D1 0x3B01 CT2 Brno 0x4212 0x32D2 0x3B04 NOVA CINEMA 0x4213 0x32D3 0x3B13 GALAXIE SPORT 0x4214 0x32D4 0x3B14 Prima Love 0x4215 CT24 Brno 0x421A CT4 Brno 0x421B CT5 Brno 0x421C CT1 Ostravia 0x4221 0x32E1 0x3B02 CT2 Ostravia 0x4222 0x32E2 0x3B05 CT24 Ostravia 0x422A CT4 Ostravia 0x422B CT5 Ostravia 0x422C CT1 Regional 0x4231 0x32F1 0x3C25 CT2 Regional 0x4232 0x32F2 0x3B03 Z1 TV 0x4233 CT24 Regional 0x423A CT4 Regional 0x423B CT5 Regional 0x423C Denmark DK 0x4500 0x2900 TV 2 0x4502 0x2902 0x3902 TV 2 Zulu 0x4503 0x2904 0x3904 Discovery Denmark 0x4504 TV 2 Charlie 0x4505 0x2905 TV Danmark 0x4506 0x2906 Kanal 5 0x4507 0x2907 TV 2 Film 0x4508 0x2908 TV 2 News 0x4509 0x2909 TV 2 FRI 0x450A DR2 0x49CF 0x2903 0x3903 DR1 0x7392 0x2901 0x3901 Egypt EG 0x1F00 Finland FI 0x3500 0x2600 YLE1 0x3581 0x2601 0x3601 YLE2 0x3582 0x2602 0x3607 OWL3 0x358F 0x260F 0x3614 France FR 0x3300 0x2F00 Arte / La Cinquième 0x330A 0x2F0A 0x3F0A RFO1 0x3311 0x2F11 0x3F11 RFO2 0x3312 0x2F12 0x3F12 Aqui TV 0x3320 0x2F20 0x3F20 TLM 0x3321 0x2F21 0x3F21 TLT 0x3322 0x2F22 0x3F22 Sailing Channel 0x33B2 AB1 0x33C1 0x2FC1 0x3F41 Canal J 0x33C2 0x2FC2 0x3F42 Canal Jimmy 0x33C3 0x2FC3 0x3F43 LCI 0x33C4 0x2FC4 0x3F44 La Chaîne Météo 0x33C5 0x2FC5 0x3F45 MCM 0x33C6 0x2FC6 0x3F46 TMC Monte-Carlo 0x33C7 0x2FC7 0x3F47 Paris Première 0x33C8 0x2FC8 0x3F48 Planète 0x33C9 0x2FC9 0x3F49 Série Club 0x33CA 0x2FCA 0x3F4A Télétoon 0x33CB 0x2FCB 0x3F4B Téva 0x33CC 0x2FCC 0x3F4C Disney Channel 0x33D1 Disney Channel +1 0x33D2 Disney XD 0x33D3 Disney Junior 0x33D4 TF1 0x33F1 0x2F01 0x3F01 France 2 0x33F2 0x2F02 0x3F02 France 3 0x33F3 0x2F03 0x3F03 Canal+ 0x33F4 0x2F04 0x3F04 France 5 0x33F5 0x2F05 0x3F05 M6 0x33F6 0x2F06 0x3F06 Eurosport 0xF101 0x2FE2 0x3F62 Eurosport2 0xF102 0x2FE3 0x3F63 Eurosportnews 0xF103 0x2FE4 0x3F64 TV5 0xF500 0x2FE5 0x3F65 Euronews 0xFE01 0x2FE1 0x3F61 Germany DE 0x4900 0x1D00 ARD 0x4901 0x3D41 0x0DC1 ZDF 0x4902 0x3D42 0x0DC2 ARD/ZDF Vormittagsprogramm 0x0DC3 OK54 Bürgerrundfunk Trier 0x4904 0x0D44 ARD-TV-Sternpunkt 0x0DC4 Channel21 0x4905 0x0D79 Phoenix 0x4908 0x0DC8 Arte 0x490A 0x3D05 0x0D85 VOX 0x490C 0x0D8E FESTIVAL 0x4941 0x0D41 MUXX 0x4942 0x0D42 EXTRA 0x4943 0x0D43 BR-Alpha 0x4944 RBB-1 Regional 0x0D81 RBB Brandenburg 0x4982 0x0D82 Parlamentsfernsehen 0x0D83 1A-Fernsehen 0x0D87 VIVA 0x0D88 Comedy Central 0x0D89 SuperRTL 0x0D8A RTL Club 0x0D8B n-tv 0x0D8C DSF 0x0D8D RTL 2 0x0D8F 0x0D8F DMAX 0x0D72 MTV 0x0D73 Nick Deutschland 0x0D74 KDG Infokanal 0x0D75 DAS VIERTE 0x0D76 1-2-3.TV 0x49BD 0x0D77 Tele 5 0x49BE 0x0D78 N24 0x0D7A TV Berlin 0x0D7B ONYX-TV 0x0D7C Nickelodeon 0x0D7E Home Shopping Europe 0x49BF 0x0D7F RTL 2 Regional 0x0D90 0x0D8F Eurosport 0x0D91 Kabel 1 0x0D92 Sixx 0x0D93 PRO 7 0x0D94 PRO 7 Austria 0x0AE8 0x0D14 0x0D94 SAT 1 Brandenburg 0x0D95 0x0DB9 SAT 1 Thüringen 0x0D96 0x0DB9 SAT 1 Sachsen 0x0D97 0x0DB9 SAT 1 Mecklenburg-Vorpommern 0x0D98 0x0DB9 SAT 1 Sachsen-Anhalt 0x0D99 0x0DB9 RTL Regional 0x0D9A 0x0DAB RTL Schleswig-Holstein 0x0D9B 0x0DAB RTL Hamburg 0x0D9C 0x0DAB RTL Berlin 0x0D9D 0x0DAB RTL Niedersachsen 0x0D9E 0x0DAB RTL Bremen 0x0D9F 0x0DAB RTL Nordrhein-Westfalen 0x0DA0 0x0DAB RTL Hessen 0x0DA1 0x0DAB RTL Rheinland-Pfalz 0x0DA2 0x0DAB RTL Baden-Württemberg 0x0DA3 0x0DAB RTL Bayern 0x0DA4 0x0DAB RTL Saarland 0x0DA5 0x0DAB RTL Sachsen-Anhalt 0x0DA6 0x0DAB RTL Mecklenburg-Vorpommern 0x0DA7 0x0DAB RTL Sachsen 0x0DA8 0x0DAB RTL Thüringen 0x0DA9 0x0DAB RTL Brandenburg 0x0DAA 0x0DAB RTL 0x0DAB Premiere 0x0DAC SAT 1 Regional 0x0DAD 0x0DB9 SAT 1 Schleswig-Holstein 0x0DAE 0x0DB9 SAT 1 Hamburg 0x0DAF 0x0DB9 SAT 1 Berlin 0x0DB0 0x0DB9 SAT 1 Niedersachsen 0x0DB1 0x0DB9 SAT 1 Bremen 0x0DB2 0x0DB9 SAT 1 Nordrhein-Westfalen 0x0DB3 0x0DB9 SAT 1 Hessen 0x0DB4 0x0DB9 SAT 1 Rheinland-Pfalz 0x0DB5 0x0DB9 SAT 1 Baden-Württemberg 0x0DB6 0x0DB9 SAT 1 Bayern 0x0DB7 0x0DB9 SAT 1 Saarland 0x0DB8 0x0DB9 SAT 1 0x0DB9 NEUN LIVE 0x0DBA Deutsche Welle TV Berlin 0x0DBB Berlin Offener Kanal 0x0DBD Berlin-Mix-Channel 2 0x0DBE Berlin-Mix-Channel 1 0x0DBF ARD-TV-Sternpunkt-Fehler 0x0DC5 3sat 0x49C7 0x0DC7 Kinderkanal 0x49C9 0x0DC9 BR-1 Regional 0x0DCA BR-3 0x49CB 0x3D4B 0x0DCB BR-3 Süd 0x0DCC BR-3 Nord 0x0DCD HR-1 Regional 0x0DCE NDR-1 Dreiländerweit 0x0DD0 NDR-1 Hamburg 0x0DD1 NDR-1 Niedersachsen 0x0DD2 NDR-1 Schleswig-Holstein 0x0DD3 Nord-3 (NDR/SFB/RB) 0x0DD4 NDR-3 Dreiländerweit 0x49D4 0x0DD5 NDR-3 Hamburg 0x0DD6 NDR-3 Niedersachsen 0x0DD7 NDR-3 Schleswig-Holstein 0x0DD8 RB-1 Regional 0x0DD9 RB-3 0x49D9 0x0DDA RBB-1 Regional 0x0DDB RBB Berlin 0x49DC 0x0DDC SWR-1 Baden-Württemberg 0x0DDD SWR-1 Rheinland-Pfalz 0x0DDE SR-1 Regional 0x49DF 0x0DDF Südwest 3 (SDR/SR/SWF) 0x0DE0 SW 3 Baden-Württemberg 0x49E1 0x0DE1 SW 3 Saarland 0x0DE2 SW 3 Baden-Württemb. Süd 0x0DE3 SW 3 Rheinland-Pfalz 0x49E4 0x0DE4 WDR-1 Regional 0x0DE5 WDR 0x49E6 0x0DE6 WDR-3 Bielefeld 0x0DE7 WDR-3 Dortmund 0x0DE8 WDR-3 Düsseldorf 0x0DE9 WDR-3 Köln 0x0DEA WDR-3 Münster 0x0DEB SW 3 Regional 0x0DEC SW 3 Baden-Württemberg 0x0DED SW 3 Mannheim 0x0DEE SW 3 Regional 0x0DEF SW 3 Regional 0x0DF0 NDR-1 Mecklenburg-Vorpommern 0x0DF1 NDR-3 Mecklenburg-Vorpommern 0x0DF2 MDR-1 Sachsen 0x0DF3 MDR-3 Sachsen 0x0DF4 MDR Dresden 0x0DF5 MDR-1 Sachsen-Anhalt 0x0DF6 WDR Dortmund 0x0DF7 MDR-3 Sachsen-Anhalt 0x0DF8 MDR Magdeburg 0x0DF9 MDR-1 Thüringen 0x0DFA MDR-3 Thüringen 0x0DFB MDR Erfurt 0x0DFC MDR-1 Regional 0x0DFD MDR 0x49FE 0x0DFE HR 0x49FF 0x0DCF QVC 0x5C49 0x0D7D Gibraltar GI 0x2A00 Greece GR 0x3000 0x2100 ET-1 0x3001 0x2101 0x3101 NET 0x3002 0x2102 0x3102 ET-3 0x3003 0x2103 0x3103 Hungary HU 0x3600 0x1B00 MTV1 0x3601 MTV2 0x3602 MTV1 Budapest 0x3611 MTV1 Pécs 0x3621 tv2 0x3622 MTV1 Szeged 0x3631 Duna Televizio 0x3636 MTV1 Szombathely 0x3641 MTV1 Debrecen 0x3651 MTV1 Miskolc 0x3661 Iceland IS 0x3A00 Rikisutvarpid-Sjonvarp 0x3541 Iraq IQ 0x2B00 Ireland IE 0x4200 TV3 0x3333 RTE1 0x3531 0x4201 0x3201 Network 2 0x3532 0x4202 0x3202 Teilifis na Gaeilge 0x3533 0x4203 0x3203 Israel IL 0x1400 Italy IT 0x3900 0x1500 RAI 1 0x3901 0x15101 RAI 2 0x3902 0x15102 RAI 3 0x3903 0x15103 Rete A 0x3904 Canale Italia 0x3905 0x1505 7 Gold - Telepadova 0x3906 Teleregione 0x3907 Telegenova 0x3908 Telenova 0x3909 0x1509 Arte 0x390A Canale Dieci 0x390B TRS TV 0x3910 Sky Cinema Classic 0x3911 0x1511 Sky Calcio 1 0x3913 0x1513 Sky Calcio 2 0x3914 0x1514 Sky Calcio 3 0x3915 0x1515 Sky Calcio 4 0x3916 0x1516 Sky Calcio 5 0x3917 0x1517 Sky Calcio 6 0x3918 0x1518 Sky Calcio 7 0x3919 0x1519 TN8 Telenorba 0x391A RaiNotizie24 0x3920 RAI Med 0x3921 RAI Sport Più 0x3922 RAI Edu1 0x3923 RAI Edu2 0x3924 RAI NettunoSat1 0x3925 RAI NettunoSat2 0x3926 Camera Deputati 0x3927 Senato 0x3928 RAI 4 0x3929 RAI Gulp 0x392A Discovery Italy 0x3930 MTV VH1 0x3931 MTV Italia 0x3933 0x3B102 MTV Brand New 0x3934 MTV Hits 0x3935 MTV GOLD 0x3936 MTV PULSE 0x3937 RTV38 0x3938 GAY TV 0x3939 TP9 Telepuglia 0x393A Video Italia 0x3940 SAT 2000 0x3941 Jimmy 0x3942 0x1542 Planet 0x3943 0x1543 Cartoon Network 0x3944 0x1544 Boomerang 0x3945 0x1545 CNN International 0x3946 0x1546 Cartoon Network +1 0x3947 0x1547 Sky Sports 3 0x3948 0x1548 Sky Diretta Gol 0x3949 0x1549 TG NORBA 0x394A RAISat Cinema 0x3952 RAISat Gambero Rosso 0x3954 RAISat YoYo 0x3955 RAISat Smash 0x3956 RAISat Extra 0x3959 RAISat Premium 0x395A SCI FI CHANNEL 0x3960 0x1560 Discovery Civilisations 0x3961 Discovery Travel and Adventure 0x3962 Discovery Science 0x3963 Sky Meteo24 0x3968 0x1568 Sky Cinema 2 0x3970 Sky Cinema 3 0x3971 Sky Cinema Autore 0x3972 Sky Cinema Max 0x3973 Sky Cinema 16:9 0x3974 Sky Sports 2 0x3975 Sky TG24 0x3976 Fox 0x3977 0x1577 Foxlife 0x3978 0x1578 National Geographic Channel 0x3979 0x1579 A1 0x3980 0x1580 History Channel 0x3981 0x1581 FOX Kids 0x3985 PEOPLE TV - RETE 7 0x3986 FOX Kids +1 0x3987 LA7 0x3988 0x3B101 PrimaTV 0x3989 SportItalia 0x398A Espansione TV 0x398F STUDIO UNIVERSAL 0x3990 0x1590 Marcopolo 0x3991 Alice 0x3992 Nuvolari 0x3993 Leonardo 0x3994 SUPERPIPPA CHANNEL 0x3996 0x1596 Sky Sports 1 0x3997 Sky Cinema 1 0x3998 Tele+3 0x3999 FacileTV 0x399A 0x150A Sitcom 2 0x399B 0x150B Sitcom 3 0x399C 0x150C Sitcom 4 0x399D 0x150D Sitcom 5 0x399E 0x150E Italiani nel Mondo 0x399F 0x150F Sky Calcio 8 0x39A0 0x15A0 Sky Calcio 9 0x39A1 0x15A1 Sky Calcio 10 0x39A2 0x15A2 Sky Calcio 11 0x39A3 0x15A3 Sky Calcio 12 0x39A4 0x15A4 Sky Calcio 13 0x39A5 0x15A5 Sky Calcio 14 0x39A6 0x15A6 Telesanterno 0x39A7 0x15A7 Telecentro 0x39A8 0x15A8 Telestense 0x39A9 0x15A9 TCS - Telecostasmeralda 0x39AB Disney Channel +1 0x39B0 0x15B0 Sailing Channel 0x39B1 Disney Channel 0x39B2 0x15B2 7 Gold-Sestra Rete 0x39B3 0x15B3 Rete 8-VGA 0x39B4 0x15B4 Nuovarete 0x39B5 0x15B5 Radio Italia TV 0x39B6 0x15B6 Rete 7 0x39B7 0x15B7 E! Entertainment Television 0x39B8 0x15B8 Toon Disney 0x39B9 0x15B9 Play TV Italia 0x39BA La7 Cartapiù A 0x39C1 La7 Cartapiù B 0x39C2 La7 Cartapiù C 0x39C3 La7 Cartapiù D 0x39C4 La7 Cartapiù E 0x39C5 La7 Cartapiù X 0x39C6 Bassano TV 0x39C7 0x15C7 ESPN Classic Sport 0x39C8 0x15C8 VIDEOLINA 0x39CA Mediaset Premium 5 0x39D1 0x15D1 Mediaset Premium 1 0x39D2 0x15D2 Mediaset Premium 2 0x39D3 0x15D3 Mediaset Premium 3 0x39D4 0x15D4 Mediaset Premium 4 0x39D5 0x15D5 BOING 0x39D6 0x15D6 Playlist Italia 0x39D7 0x15D7 MATCH MUSIC 0x39D8 0x15D8 Televisiva SUPER3 0x39D9 Mediashopping 0x39DA Mediaset Premium 6 0x39DB Mediaset Premium 7 0x39DC Iris 0x39DF National Geographic +1 0x39E1 0x15E1 Histroy Channel +1 0x39E2 0x15E2 Sky TV 0x39E3 0x15E3 GXT 0x39E4 0x15E4 Playhouse Disney 0x39E5 0x15E5 Sky Canale 224 0x39E6 0x15E6 Music Box 0x39E7 Tele Liguria Sud 0x39E8 TN7 Telenorba 0x39E9 Brescia Punto TV 0x39EA QOOB 0x39EB AB Channel 0x39EE Teleradiocity 0x39F1 Teleradiocity Genova 0x39F2 Teleradiocity Lombardia 0x39F3 Telestar Piemonte 0x39F4 Telestar Liguria 0x39F5 Telestar Lombardia 0x39F6 Italia 8 Piemonte 0x39F7 Italia 8 Lombardia 0x39F8 Radio Tele Europa 0x39F9 Rete 4 0xFA04 0x15105 Canale 5 0xFA05 0x15106 Italia 1 0xFA06 0x15107 TMC 0xFA08 Jordan JO 0x2500 Libya LY 0x2D00 Liechtenstein LI 0x3900 Lebanon LB 0x4A00 Luxembourg LU 0x2700 RTL-TV1 0x3209 CLUB-RTL 0x320A PLUG TV 0x3225 RTL Télé Lëtzebuerg 0x4000 RTL TVI 20 ANS 0x4020 RTL Luxembourg 0x0791 Malta MT 0x1C00 Monaco MC 0x3B00 Netherlands NL 0x3100 0x4800 Investigation Discovery 0x3100 Nederland 1 0x3101 0x4801 0x3801 Nederland 2 0x3102 0x4802 0x3802 Nederland 3 0x3103 0x4803 0x3803 RTL 4 0x3104 0x4804 0x3804 RTL 5 0x3105 0x4805 0x3805 Yorin 0x3106 0x4806 0x3806 RTV Noord 0x3110 Omrop Fryslan 0x3111 RTV Drenthe 0x3112 RTV Oost 0x3113 Omroep Gelderland 0x3114 RTV Noord-Holland 0x3116 RTV Utrecht 0x3117 RTV West 0x3118 RTV Rijnmond 0x3119 Omroep Brabant 0x311A L1 RTV Limburg 0x311B Omroep Zeeland 0x311C Omroep Flevoland 0x311D The BOX 0x3120 0x4820 0x3820 Discovery Netherlands 0x3121 Nickelodeon 0x3122 Animal Planet Benelux 0x3123 TIEN 0x3124 NET5 0x3125 SBS6 0x3126 V8 0x3128 TMF Netherlands 0x3130 TMF Belgian Flanders 0x3131 MTV NL 0x3132 Nickelodeon 0x3133 The Box 0x3134 TMF Pure 0x3135 TMF Party 0x3136 RNN7 0x3137 TMF NL 0x3138 Nick Toons 0x3139 Nick Jr. 0x313A Nick Hits 0x313B MTV Brand New 0x313C Disney Channel 0x313D Disney XD 0x313E Playhouse Disney 0x313F RTL 7 0x3147 0x4847 0x3847 RTL 8 0x3148 0x4848 0x3848 HET GESPREK 0x3150 InfoThuis TV 0x3151 Graafschap TV (Oost-Gelderland) 0x3152 Gelre TV (Veluwe) 0x3153 Gelre TV (Groot-Arnhem) 0x3154 Gelre TV (Groot-Nijmegen) 0x3155 Gelre TV (Betuwe) 0x3156 Brabant 10 (West Brabant) 0x3157 Brabant 10 (Midden Brabant) 0x3158 Brabant 10 (Noord Oost Brabant) 0x3159 Brabant 10 (Zuid Oost Brabant) 0x315A Regio22 0x315B Maximaal TV 0x315C GPTV 0x315D 1TV (Groningen) 0x315E 1TV (Drenthe) 0x315F Cultura 24 0x3160 101 TV 0x3161 Best 24 0x3162 Holland Doc 24 0x3163 Geschiedenis 24 0x3164 Consumenten 24 0x3165 Humor 24 0x3166 Sterren 24 0x3167 Spirit 24 0x3168 Familie 24 0x3169 Journaal 24 0x316A Politiek 24 0x316B RTV Rotterdam 0x3171 Brug TV 0x3172 TV Limburg 0x3173 Kindernet / Comedy Central 0x3174 Comedy Central Extra 0x3175 Norway NO 0x4700 0x3F00 NRK1 0x4701 TV 2 0x4702 NRK2 0x4703 TV Norge 0x4704 Discovery Nordic 0x4720 Poland PL 0x4800 0x3300 TVP1 0x4801 TVP2 0x4802 TV Polonia 0x4810 TVN 0x4820 TVN Siedem 0x4821 TVN24 0x4822 Discovery Poland 0x4830 Animal Planet 0x4831 TVP Warszawa 0x4880 TVP Bialystok 0x4881 TVP Bydgoszcz 0x4882 TVP Gdansk 0x4883 TVP Katowice 0x4884 TVP Krakow 0x4886 TVP Lublin 0x4887 TVP Lodz 0x4888 TVP Rzeszow 0x4890 TVP Poznan 0x4891 TVP Szczecin 0x4892 TVP Wroclaw 0x4893 Portugal PT 0x5800 RTP1 0x3510 RTP2 0x3511 RTPAF 0x3512 RTPI 0x3513 RTPAZ 0x3514 RTPM 0x3515 Romania RO 0x2E00 Russia RU 0x5700 San Marino SM 0x2300 RTV 0x3781 Slovakia SK 0x3500 STV1 0x42A1 0x35A1 0x3521 STV2 0x42A2 0x35A2 0x3522 STV3 0x42A3 0x35A3 0x3523 STV4 0x42A4 0x35A4 0x3524 STV1 B. Bystrica 0x42A5 0x35A5 0x3525 STV2 B. Bystrica 0x42A6 0x35A6 0x3526 TV JOJ 0x42B1 0x35B1 0x3511 Slovenia SI 0xAA00 SLO1 0xAAE1 SLO2 0xAAE2 KC 0xAAE3 TLM 0xAAE4 POP TV 0xAAE5 KANAL A 0xAAE6 SLO3 0xAAF1 Spain ES 0x3E00 ETB 2 0x3402 CANAL 9 0x3403 PUNT 2 0x3404 CCV 0x3405 Arte 0x340A Canal Extremadura TV 0x3415 Extremadura TV 0x3416 Telemadrid 0x3420 0x3E20 0x3E20 La Otra 0x3421 0x3E21 0x3E21 TM SAT 0x3422 0x3E22 0x3E22 La sexta 0x3423 0x3E23 0x3E23 Antena 3 0x3424 0x3E24 0x3E24 Neox 0x3425 0x3E25 0x3E25 Nova 0x3426 0x3E26 0x3E26 Cuatro 0x3427 0x3E27 0x3E27 CNN+ 0x3428 0x3E28 0x3E28 40 Latino 0x3429 0x3E29 0x3E29 24 Horas 0x342A 0x3E2A 0x3E2A Clan TVE 0x342B 0x3E2B 0x3E2B Teledeporte 0x342C 0x3E2C 0x3E2C CyL7 0x342D 8TV 0x342E EDC2 0x342F EDC3 0x3430 105tv 0x3431 CyL8 0x3432 TVE1 0x3E00 Canal+ 0xA55A ETB 1 0xBA01 TV3 0xCA03 C33 0xCA33 TVE2 0xE100 TVE Internacional Europa 0xE200 Tele5 0xE500 0x1FE5 Tele5 Estrellas 0xE501 0x1FE6 Tele5 Sport 0xE502 0x1FE7 Sweden SE 0x4E00 SVT Test Transmissions 0x4600 0x4E00 0x3E00 SVT 1 0x4601 0x4E01 0x3E01 SVT 2 0x4602 0x4E02 0x3E02 TV 4 0x4640 0x4E40 0x3E40 Syria SY 0x3600 Switzerland CH 0x4100 0x2400 Schweizer Sportfernsehen 0x049A 0x049A SF 1 0x4101 0x24C1 0x3441 0x04C1 TSR 1 0x4102 0x24C2 0x3442 0x04C2 TSI 1 0x4103 0x24C3 0x3443 0x04C3 SF 2 0x4107 0x24C7 0x3447 0x04C7 TSR 2 0x4108 0x24C8 0x3448 0x04C8 TSI 2 0x4109 0x24C9 0x3449 0x04C9 Sat Access 0x410A 0x24CA 0x344A 0x04CA U1 0x4121 0x2421 0x0495 TeleZüri 0x4122 0x2422 0x0481 TF1 Suisse 0x4123 0x049C Teleclub Abo-Fernsehen 0x0482 Zürich 1 0x0483 TeleBern 0x0484 Tele M1 0x0485 Star TV 0x0486 Pro 7 0x0487 TopTV 0x0488 Tele 24 0x0489 Kabel 1 0x048A TV3 0x048B TeleZüri 2 0x048C Swizz 0x048D Intro TV 0x048E Tele Tell 0x048F Tele Top 0x0490 TSO CH 0x0491 TVO 0x0492 Tele TI 0x0493 SHF 0x0494 MTV Swiss 0x0496 3+ 0x0497 telebasel 0x0498 NICK Swiss 0x0499 TELE-1 0x049B 4+ 0x049D SFi 0x04CC Tunisia TN 0x3700 Turkey TR 0x9000 0x4300 TRT-1 0x9001 0x4301 0x3301 TRT-2 0x9002 0x4302 0x3302 TRT-3 0x9003 0x4303 0x3303 TRT-4 0x9004 0x4304 0x3304 TRT-INT 0x9005 0x4305 0x3305 AVRASYA 0x9006 0x4306 0x3306 Show TV 0x9007 Cine 5 0x9008 Super Sport 0x9009 ATV 0x900A KANAL D 0x900B EURO D 0x900C EKO TV 0x900D BRAVO TV 0x900E GALAKSI TV 0x900F FUN TV 0x9010 TEMPO TV 0x9011 TGRT 0x9014 Show Euro 0x9017 STAR TV 0x9020 STARMAX 0x9021 KANAL 6 0x9022 STAR 4 0x9023 STAR 5 0x9024 STAR 6 0x9025 STAR 7 0x9026 STAR 8 0x9027 United Kingdom GB 0x4400 0x2C00 CNN International 0x01F2 0x5BF1 0x3B71 MERIDIAN 0x10E4 0x2C34 0x3C34 CHANNEL 5 (2) 0x1609 0x2C09 0x3C09 WESTCOUNTRY TV 0x25D0 0x2C30 0x3C30 CHANNEL 5 (3) 0x28EB 0x2C2B 0x3C2B CENTRAL TV 0x2F27 0x2C37 0x3C37 National Geographic Channel 0x320B SSVC 0x37E5 0x2C25 0x3C25 UK GOLD 0x4401 0x5BFA 0x3B7A UK LIVING 0x4402 0x2C01 0x3C01 WIRE TV 0x4403 0x2C3C 0x3C3C CHILDREN'S CHANNEL 0x4404 0x5BF0 0x3B70 BRAVO 0x4405 0x5BEF 0x3B6F LEARNING CHANNEL 0x4406 0x5BF7 0x3B77 DISCOVERY 0x4407 0x5BF2 0x3B72 FAMILY CHANNEL 0x4408 0x5BF3 0x3B73 Live TV 0x4409 0x5BF8 0x3B78 Discovery Home & Leisure 0x4420 Animal Planet 0x4421 BBC2 0x4440 0x2C40 0x3C40 BBC1 NI 0x4441 0x2C41 0x3C41 BBC2 Wales 0x4442 0x2C42 0x3C42 BBC2 Scotland 0x4444 0x2C44 0x3C44 BBC World 0x4457 0x2C57 0x3C57 BBC Prime 0x4468 0x2C68 0x3C68 BBC News 24 0x4469 0x2C69 0x3C69 BBC1 Scotland 0x447B 0x2C7B 0x3C7B BBC1 Wales 0x447D 0x2C7D 0x3C7D BBC2 NI 0x447E 0x2C7E 0x3C7E BBC1 0x447F 0x2C7F 0x3C7F TNT / Cartoon Network 0x44C1 DISNEY CHANNEL UK 0x44D1 0x5BCC 0x3B4C MTV 0x4D54 0x2C14 0x3C14 VH-1 0x4D58 0x2C20 0x3C20 VH-1 0x4D59 0x2C21 0x3C21 GRANADA PLUS 0x4D5A 0x5BF4 0x3B74 GRANADA Timeshare 0x4D5B 0x5BF5 0x3B75 HTV 0x5AAF 0x2C3F 0x3C3F QVC UK 0x5C44 CARLTON TV 0x82DD 0x2C1D 0x3C1D CARLTON SELECT 0x82E1 0x2C05 0x3C05 ULSTER TV 0x833B 0x2C3D 0x3C3D LWT 0x884B 0x2C0B 0x3C0B NBC Europe 0x8E71 0x2C31 0x3C31 0x0E86 CNBC Europe 0x8E72 0x2C35 0x3C35 CHANNEL 5 (1) 0x9602 0x2C02 0x3C02 Nickelodeon UK 0xA460 Paramount Comedy Channel UK 0xA465 TYNE TEES TV 0xA82C 0x2C2C 0x3C2C GRANADA TV 0xADD8 0x2C18 0x3C18 GMTV 0xADDC 0x5BD2 0x3B52 S4C 0xB4C7 0x2C07 0x3C07 BORDER TV 0xB7F7 0x2C27 0x3C27 CHANNEL 5 (4) 0xC47B 0x2C3B 0x3C3B FilmFour 0xC4F4 0x42F4 0x3274 ITV NETWORK 0xC8DE 0x2C1E 0x3C1E GRAMPIAN TV 0xF33A 0x2C3A 0x3C3A SCOTTISH TV 0xF9D2 0x2C12 0x3C12 YORKSHIRE TV 0xFA2C 0x2C2D 0x3C2D ANGLIA TV 0xFB9C 0x2C1C 0x3C1C CHANNEL 4 0xFCD1 0x2C11 0x3C11 CHANNEL TV 0xFCE4 0x2C24 0x3C24 RACING CHANNEL 0xFCF3 0x2C13 0x3C13 HISTORY CHANNEL 0xFCF4 0x5BF6 0x3B76 SCI FI CHANNEL 0xFCF5 0x2C15 0x3C15 SKY TRAVEL 0xFCF6 0x5BF9 0x3B79 SKY SOAPS 0xFCF7 0x2C17 0x3C17 SKY SPORTS 2 0xFCF8 0x2C08 0x3C08 SKY GOLD 0xFCF9 0x2C19 0x3C19 SKY SPORTS 0xFCFA 0x2C1A 0x3C1A MOVIE CHANNEL 0xFCFB 0x2C1B 0x3C1B SKY MOVIES PLUS 0xFCFC 0x2C0C 0x3C0C SKY NEWS 0xFCFD 0x2C0D 0x3C0D SKY ONE 0xFCFE 0x2C0E 0x3C0E SKY TWO 0xFCFF 0x2C0F 0x3C0F Ukraine UA 0x7700 0x4700 Studio 1+1 0x7700 0x07C0 M1 0x7705 0x07C5 ICTV 0x7707 Novy Kanal 0x7708 0x07C8 Vatican City VA 0x3400 Unknown AA Zee TV 0x04F9 VGB V 0xFC02 0x1234 zvbi-0.2.44/doc/000077500000000000000000000000001476363111200133245ustar00rootroot00000000000000zvbi-0.2.44/doc/Doxyfile.in000066400000000000000000003710601476363111200154460ustar00rootroot00000000000000# Doxyfile 1.9.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). # # Note: # # Use doxygen to compare the used configuration file with the template # configuration file: # doxygen -x [configFile] # Use doxygen to compare the used configuration file with the template # configuration file without replacing the environment variables or CMake type # replacement variables: # doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the configuration # file that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "ZVBI Library" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = @VERSION@ # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = # If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 # sub-directories (in 2 levels) under the output directory of each output format # and will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to # control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO # Controls the number of sub-directories that will be created when # CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every # level increment doubles the number of directories, resulting in 4096 # directories at level 8 which is the default and also the maximum value. The # sub-directories are organized in 2 levels, the first level always has a fixed # number of 16 directories. # Minimum value: 0, maximum value: 8, default value: 8. # This tag requires that the tag CREATE_SUBDIRS is set to YES. CREATE_SUBDIRS_LEVEL = 8 # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, # Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English # (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, # Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with # English messages), Korean, Korean-en (Korean with English messages), Latvian, # Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, # Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, # Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be # interpreted by doxygen. # The default value is: NO. JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # By default Python docstrings are displayed as preformatted text and doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the # doxygen's special commands can be used and the contents of the docstring # documentation blocks is shown as doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". Note that you cannot put \n's in the value part of an alias # to insert newlines (in the resulting output). You can put ^^ in the value part # of an alias to insert a newline as if a physical newline was in the original # file. When you need a literal { or } or , in the value part of an alias you # have to escape them by means of a backslash (\), this can lead to conflicts # with the commands \{ and \} for these it is advised to use the version @{ and # @} or use a double escape (\\{ and \\}) ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice # sources only. Doxygen will then generate output that is more tailored for that # language. For instance, namespaces will be presented as modules, types will be # separated into more groups, etc. # The default value is: NO. OPTIMIZE_OUTPUT_SLICE = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, # VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 5 # The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to # generate identifiers for the Markdown headings. Note: Every identifier is # unique. # Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a # sequence number starting at 0 and GITHUB use the lower case version of title # with any whitespace replaced by '-' and punctuation characters removed. # The default value is: DOXYGEN. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. MARKDOWN_ID_STYLE = DOXYGEN # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, # which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 # If the TIMESTAMP tag is set different from NO then each generated page will # contain the date or date and time when the page was generated. Setting this to # NO can help when comparing the output of multiple runs. # Possible values are: YES, NO, DATETIME and DATE. # The default value is: NO. TIMESTAMP = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. # The default value is: NO. EXTRACT_PRIV_VIRTUAL = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If this flag is set to YES, the name of an unnamed parameter in a declaration # will be determined by the corresponding definition. By default unnamed # parameters remain unnamed in the output. # The default value is: YES. RESOLVE_UNNAMED_PARAMS = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # will also hide undocumented C++ concepts if enabled. This option has no effect # if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # With the correct setting of option CASE_SENSE_NAMES doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that # are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # Possible values are: SYSTEM, NO and YES. # The default value is: SYSTEM. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_HEADERFILE tag is set to YES then the documentation for a class # will show which file needs to be included to use the class. # The default value is: YES. SHOW_HEADERFILE = YES # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = NO # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = NO # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. See also section "Changing the # layout of pages" for information. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as documenting some parameters in # a documented function twice, or documenting parameters that don't exist or # using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete # function parameter documentation. If set to NO, doxygen will accept that some # parameters have no documentation without warning. # The default value is: YES. WARN_IF_INCOMPLETE_DOC = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong parameter # documentation, but not about the absence of documentation. If EXTRACT_ALL is # set to YES then this flag will automatically be disabled. See also # WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO # If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about # undocumented enumeration values. If set to NO, doxygen will accept # undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: NO. WARN_IF_UNDOC_ENUM_VAL = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. # If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves # like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not # write the warning messages in between other messages but write them at the end # of a run, in case a WARN_LOGFILE is defined the warning messages will be # besides being in the defined file also be shown at the end of a run, unless # the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case # the behavior will remain as with the setting FAIL_ON_WARNINGS. # Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = # In the $text part of the WARN_FORMAT command it is possible that a reference # to a more specific place is given. To make it easier to jump to this place # (outside of doxygen) the user can define a custom "cut" / "paste" string. # Example: # WARN_LINE_FORMAT = "'vi $file +$line'" # See also: WARN_FORMAT # The default value is: at line $line of file $file. WARN_LINE_FORMAT = "at line $line of file $file" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). In case the file specified cannot be opened for writing the # warning and error messages are written to standard error. When as file - is # specified the warning and error messages are written to standard output # (stdout). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = @top_srcdir@/src # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. # See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # This tag can be used to specify the character encoding of the source files # that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify # character encoding on a per file pattern basis. Doxygen will compare the file # name with each pattern and apply the encoding instead of the default # INPUT_ENCODING) if there is a match. The character encodings are a list of the # form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding # "INPUT_ENCODING" for further information on supported encodings. INPUT_FILE_ENCODING = # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, # *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, # *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, # *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be # provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ bcd.h \ cache.h \ cc.h \ conv.h \ decoder.h \ dvb_demux.h \ dvb_mux.h \ idl_demux.h \ event.h \ exp-gfx.h \ exp-txt.h \ export.h \ format.h \ hamm.h \ io.h \ io-sim.h \ lang.h \ macros.h \ packet-830.h \ pdc.h \ pfc_demux.h \ proxy-msg.h \ proxy-client.h \ search.h \ sliced.h \ tables.h \ trigger.h \ ure.h \ vbi.h \ vps.h \ vt.h \ wss.h \ xds_demux.h # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = @top_srcdir@/examples \ @top_srcdir@/src # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = @top_srcdir@/doc # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that doxygen will use the data processed and written to standard output # for further processing, therefore nothing else, like debug statements or used # commands (so in case of a Windows batch file always use @echo OFF), should be # written to standard output. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The # setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can # be processed before the automatic comment starts. # Minimum value: 7, maximum value: 10000, default value: 72. FORTRAN_COMMENT_AFTER = 72 #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = NO # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the # clang parser (see: # http://clang.llvm.org/) for more accurate parsing at the cost of reduced # performance. This can be particularly helpful with template rich C++ code for # which doxygen's built-in parser lacks the necessary type information. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS # tag is set to YES then doxygen will add the directory of each input to the # include path. # The default value is: YES. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_ADD_INC_PATHS = YES # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = # If clang assisted parsing is enabled you can provide the clang parser with the # path to the directory containing a file called compile_commands.json. This # file is the compilation database (see: # http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the # options used when the source files were built. This is equivalent to # specifying the -p option to a clang tool, such as clang-check. These options # will then be passed to the parser. Any options specified with CLANG_OPTIONS # will be added as well. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse_libclang=ON option for CMake. CLANG_DATABASE_PATH = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = NO # The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) # that should be ignored while generating the index headers. The IGNORE_PREFIX # tag works for classes, function and member names. The entity will be placed in # the alphabetical list under the first letter of the entity name that remains # after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = vbi_ #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). # Note: Since the styling of scrollbars can currently not be overruled in # Webkit/Chromium, the styling will be left out of the default doxygen.css if # one or more extra stylesheets have been specified. So if scrollbar # customization is desired it has to be added explicitly. For an example see the # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE tag can be used to specify if the generated HTML output # should be rendered with a dark or light theme. # Possible values are: LIGHT always generate light mode output, DARK always # generate dark mode output, AUTO_LIGHT automatically set the mode according to # the user preference, use light mode if no preference is set (the default), # AUTO_DARK automatically set the mode according to the user preference, use # dark mode if no preference is set and TOGGLE allow to user to switch between # light and dark mode via a button. # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE = AUTO_LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML # page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be # dynamically folded and expanded in the generated HTML source code. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_CODE_FOLDING = YES # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To # create a documentation set, doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag determines the URL of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDURL = # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # on Windows. In the beginning of 2021 Microsoft took the original page, with # a.o. the download links, offline the HTML help workshop was already many years # in maintenance mode). You can download the HTML help workshop from the web # archives at Installation executable (see: # http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo # ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # The SITEMAP_URL tag is used to specify the full URL of the place where the # generated documentation will be placed on the server by the user during the # deployment of the documentation. The generated sitemap is called sitemap.xml # and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL # is specified no sitemap is generated. For information about the sitemap # protocol see https://www.sitemaps.org # This tag requires that the tag GENERATE_HTML is set to YES. SITEMAP_URL = # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path # including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine tune the look of the index (see "Fine-tuning the output"). As an # example, the default style sheet generated by doxygen has an example that # shows how to put an image at the root of the tree instead of the PROJECT_NAME. # Since the tree basically has the same information as the tab index, you could # consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the # FULL_SIDEBAR option determines if the side bar is limited to only the treeview # area (value NO) or if it should extend to the full height of the window (value # YES). Setting this to YES gives a layout similar to # https://docs.readthedocs.io with more room for contents, but less room for the # project logo, title, and description. If either GENERATE_TREEVIEW or # DISABLE_INDEX is set to NO, this option has no effect. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. FULL_SIDEBAR = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 1 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email # addresses. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. OBFUSCATE_EMAILS = YES # If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. # Possible values are: png (the default) and svg (looks nicer but requires the # pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # With MATHJAX_VERSION it is possible to specify the MathJax version to be used. # Note that the different versions of MathJax have different requirements with # regards to the different settings, so it is possible that also other MathJax # settings have to be changed when switching between the different MathJax # versions. # Possible values are: MathJax_2 and MathJax_3. # The default value is: MathJax_2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_VERSION = MathJax_2 # When MathJax is enabled you can set the default output format to be used for # the MathJax output. For more details about the output format see MathJax # version 2 (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 # (see: # http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best # compatibility. This is the name for Mathjax version 2, for MathJax version 3 # this will be translated into chtml), NativeMML (i.e. MathML. Only supported # for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This # is the name for Mathjax version 3, for MathJax version 2 this will be # translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. The default value is: # - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 # - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # for MathJax version 2 (see # https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): # MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /

");

	html->underline  = FALSE;
	html->bold	 = FALSE;
	html->italic     = FALSE;
	html->flash      = FALSE;
	html->span	 = FALSE;
	html->link	 = FALSE;

	/* XXX this can get extremely large and ugly, should be improved. */
	for (acp = pg.text, i = 0; i < pg.rows; acp += pg.columns, i++) {
		for (j = 0; j < pg.columns; j++) {
			if ((html->color
			     && ((acp[j].unicode != 0x0020
				  && acp[j].foreground != html->foreground)
				 || acp[j].background != html->background))
			    || html->link != acp[j].link
			    || html->flash != acp[j].flash) {
				style *s;
				int ord;

				if (html->italic)
					puts (html_italic[0]);
				if (html->bold)
					puts (html_bold[0]);
				if (html->underline)
					puts (html_underline[0]);
				if (html->span)
					puts ("");
				if (html->link && !acp[j].link) {
					puts ("");
					html->link = FALSE;
				}

				html->underline  = FALSE;
				html->bold	 = FALSE;
				html->italic     = FALSE;

				if (acp[j].link && !html->link) {
					vbi_link link;

					vbi_resolve_link(pgp, j, i, &link);

					switch (link.type) {
					case VBI_LINK_HTTP:
					case VBI_LINK_FTP:
					case VBI_LINK_EMAIL:
						printf ("", link.url);
						html->link = TRUE;

					default:
						break;
					}
				}

				if (html->color) {
					for (s = html->styles, ord = 0; s; s = s->next)
						if (s->ref_count > 1) {
							if ((acp[j].unicode == 0x0020
							     || s->foreground == acp[j].foreground)
							    && s->background == acp[j].background
							    && s->flash == acp[j].flash)
								break;
							ord++;
						}

					if (s != &html->def) {
						if (s && !html->headerless) {
							html->foreground = s->foreground;
							html->background = s->background;
							html->flash = s->flash;
							printf ("", ord);
						} else {
							html->foreground = acp[j].foreground;
							html->background = acp[j].background;
							if (s) {
								/* XXX acp[j].flash? */
								html->flash = s->flash;
							} else {
								html->flash = FALSE;
							}
							puts("foreground]);
							puts(";background-color:");
							hash_color(html, pg.color_map[html->background]);
							if (html->flash)
								puts("; text-decoration: blink");
							puts("\">");
						}
						
						html->span = TRUE;
					} else {
						html->foreground = s->foreground;
						html->background = s->background;
						html->flash = s->flash;
						html->span = FALSE;
					}
				}
			}

			if (acp[j].underline != html->underline) {
				html->underline = acp[j].underline;
				puts(html_underline[html->underline]);
			}

			if (acp[j].bold != html->bold) {
				html->bold = acp[j].bold;
				puts(html_bold[html->bold]);
			}

			if (acp[j].italic != html->italic) {
				html->italic = acp[j].italic;
				puts(html_italic[html->italic]);
			}

			if (vbi_is_print(acp[j].unicode)) {
				char in[2], out[1], *ip = in, *op = out;
				size_t li = sizeof(in), lo = sizeof(out), r;

				in[0 + endian] = acp[j].unicode;
				in[1 - endian] = acp[j].unicode >> 8;

				r = iconv (html->cd,
					   (void *) &ip, &li,
					   (void *) &op, &lo);
				if ((size_t) -1 == r
				    || (out[0] == 0x40
					&& acp[j].unicode != 0x0040)) {
					printf("&#%u;", acp[j].unicode);
				} else {
					escaped_putc(html, out[0]);
				}
			} else if (vbi_is_gfx(acp[j].unicode)) {
				putc(html->gfx_chr);
			} else {
				putc(0x20);
			}
		}

		putc('\n');
	}

	if (html->italic)
		puts(html_italic[0]);
	if (html->bold)
		puts(html_bold[0]);
	if (html->underline)
		puts(html_underline[0]);
	if (html->span)
		puts("");
	if (html->link)
		puts("");

	puts("
"); free_styles (html); if (!html->headerless) puts(LF "" LF ""); putc('\n'); iconv_close(html->cd); html->cd = (iconv_t) -1; if (html->export.write_error) goto failed; return TRUE; failed: free_styles (html); if ((iconv_t) -1 != html->cd) { iconv_close (html->cd); html->cd = (iconv_t) -1; } return FALSE; } static vbi_export_info info_html = { .keyword = "html", .label = N_("HTML"), .tooltip = N_("Export this page as HTML page"), .mime_type = "text/html", .extension = "html,htm", }; vbi_export_class vbi_export_class_html = { ._public = &info_html, ._new = html_new, ._delete = html_delete, .option_enum = option_enum, .option_get = option_get, .option_set = option_set, .export = export }; VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_html) /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/exp-templ.c000066400000000000000000000155001476363111200154260ustar00rootroot00000000000000/* * Template for export modules */ /* $Id: exp-templ.c,v 1.10 2007-11-27 18:26:32 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include "export.h" typedef struct tmpl_instance { /* Common to all export modules */ vbi_export export; /* Our private stuff */ /* Options */ int flip; int day; int prime; double quality; char * comment; int weekday; int counter; } tmpl_instance; /* Safer than tmpl_instance *tmpl = (tmpl_instance *)(vbi_export *) e */ #define TMPL(e) PARENT(e, tmpl_instance, export); static vbi_export * tmpl_new(void) { tmpl_instance *tmpl; if (!(tmpl = calloc(1, sizeof(*tmpl)))) return NULL; /* * The caller will initialize tmpl->export.class for us * and reset all options to their defaults, we only * have to initialize our private stuff. */ tmpl->counter = 0; return &tmpl->export; } static void tmpl_delete(vbi_export *e) { tmpl_instance *tmpl = TMPL(e); /* Uninitialize our private stuff and options */ if (tmpl->comment) free(tmpl->comment); free(tmpl); } /* convenience */ #define elements(array) (sizeof(array) / sizeof(array[0])) /* N_(), _() are NLS functions, see info gettext. */ static const char * string_menu_items[] = { N_("Sunday"), N_("Monday"), N_("Tuesday"), N_("Wednesday"), N_("Thursday"), N_("Friday"), N_("Saturday") }; static int int_menu_items[] = { 1, 3, 5, 7, 11, 13, 17, 19 }; static vbi_option_info tmpl_options[] = { VBI_OPTION_BOOL_INITIALIZER /* * Option keywords must be unique within their module * and shall contain only "AZaz09_" (be filesystem safe that is). * Note "network", "creator" and "reveal" are reserved generic * options, filtered by the export api functions. */ ("flip", N_("Boolean option"), FALSE, N_("This is a boolean option")), VBI_OPTION_INT_RANGE_INITIALIZER ("day", N_("Select a month day"), /* default, min, max, step, has no tooltip */ 13, 1, 31, 1, NULL), VBI_OPTION_INT_MENU_INITIALIZER ("prime", N_("Select a prime"), 0, int_menu_items, elements(int_menu_items), N_("Default is the first, '1'")), VBI_OPTION_REAL_RANGE_INITIALIZER ("quality", N_("Compression quality"), 100, 1, 100, 0.01, NULL), /* VBI_OPTION_REAL_MENU_INITIALIZER like int */ VBI_OPTION_STRING_INITIALIZER ("comment", N_("Add a comment"), "default comment", N_("Another tooltip")), VBI_OPTION_MENU_INITIALIZER ("weekday", N_("Select a weekday"), 2, string_menu_items, 7, N_("Default is Tuesday")) }; /* * Enumerate our options (optional if we have no options). * Instead of using a table one could also dynamically create * vbi_option_info's in tmpl_instance. */ static vbi_option_info * option_enum(vbi_export *e, int index) { e = e; /* Enumeration 0 ... n */ if (index < 0 || index >= (int) elements(tmpl_options)) return NULL; return tmpl_options + index; } #define KEYWORD(str) (strcmp(keyword, str) == 0) /* * Get an option (optional if we have no options). */ static vbi_bool option_get(vbi_export *e, const char *keyword, vbi_option_value *value) { tmpl_instance *tmpl = TMPL(e); if (KEYWORD("flip")) { value->num = tmpl->flip; } else if (KEYWORD("day")) { value->num = tmpl->day; } else if (KEYWORD("prime")) { value->num = tmpl->prime; } else if (KEYWORD("quality")) { value->dbl = tmpl->quality; } else if (KEYWORD("comment")) { if (!(value->str = vbi_export_strdup(e, NULL, tmpl->comment ? tmpl->comment : ""))) return FALSE; } else if (KEYWORD("weekday")) { value->num = tmpl->weekday; } else { vbi_export_unknown_option(e, keyword); return FALSE; } return TRUE; /* success */ } /* * Set an option (optional if we have no options). */ static vbi_bool option_set(vbi_export *e, const char *keyword, va_list args) { tmpl_instance *tmpl = TMPL(e); if (KEYWORD("flip")) { tmpl->flip = !!va_arg(args, int); } else if (KEYWORD("day")) { int day = va_arg(args, int); /* or clamp */ if (day < 1 || day > 31) { vbi_export_invalid_option(e, keyword, day); return FALSE; } tmpl->day = day; } else if (KEYWORD("prime")) { unsigned int i; unsigned int d, dmin = UINT_MAX; int value = va_arg(args, int); /* or return an error */ for (i = 0; i < elements(int_menu_items); i++) if ((d = abs(int_menu_items[i] - value)) < dmin) { tmpl->prime = int_menu_items[i]; dmin = d; } } else if (KEYWORD("quality")) { double quality = va_arg(args, double); /* or return an error */ if (quality < 1) quality = 1; else if (quality > 100) quality = 100; tmpl->quality = quality; } else if (KEYWORD("comment")) { char *comment = va_arg(args, char *); /* Note the option remains unchanged in case of error */ if (!vbi_export_strdup(e, &tmpl->comment, comment)) return FALSE; } else if (KEYWORD("weekday")) { int day = va_arg(args, int); /* or return an error */ tmpl->weekday = day % 7; } else { vbi_export_unknown_option(e, keyword); return FALSE; } return TRUE; /* success */ } /* * The output function, mandatory. */ static vbi_bool export(vbi_export *e, vbi_page *pg) { tmpl_instance *tmpl = TMPL(e); /* Write pg to target, that's all. */ vbi_export_printf (e, "Page %x.%x\n", pg->pgno, pg->subno); tmpl->counter++; /* just for fun */ /* * Should any of the module functions return unsuccessful * they better post a description of the problem. * Parameters like printf, no linefeeds '\n' please. */ /* vbi_export_error_printf(_("Writing failed: %s"), strerror(errno)); */ return FALSE; /* no success (since we didn't write anything) */ } /* * Let's describe us. * You can leave away assignments unless mandatory. */ static vbi_export_info info_tmpl = { /* The mandatory keyword must be unique and shall contain only "AZaz09_" */ .keyword = "templ", /* When omitted this module can still be used by libzvbi clients but won't be listed in a UI. */ .label = N_("Template"), .tooltip = N_("This is just an export template"), .mime_type = "misc/example", .extension = "tmpl", }; vbi_export_class vbi_export_class_tmpl = { ._public = &info_tmpl, /* Functions to allocate and free a tmpl_class vbi_export instance. When you omit these, libzvbi will allocate a bare struct vbi_export */ ._new = tmpl_new, ._delete = tmpl_delete, /* Functions to enumerate, read and write options */ .option_enum = option_enum, .option_get = option_get, .option_set = option_set, /* Function to export a page, mandatory */ .export = export }; /* * This is a constructor calling vbi_register_export_module(). * (Commented out since we don't want to register the example module.) */ #if 0 VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_tmpl) #endif /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/exp-txt.c000066400000000000000000000370471476363111200151360ustar00rootroot00000000000000/* * libzvbi - Text export functions * * Copyright (C) 2001, 2002 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: exp-txt.c,v 1.24 2013-07-02 02:32:06 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "misc.h" #include "lang.h" #include "export.h" #include "exp-txt.h" typedef struct text_instance { vbi_export export; /* Options */ int format; char * charset; unsigned color : 1; int term; int gfx_chr; int def_fg; int def_bg; iconv_t cd; char buf[32]; } text_instance; static vbi_export * text_new(void) { text_instance *text; if (!(text = calloc(1, sizeof(*text)))) return NULL; return &text->export; } static void text_delete(vbi_export *e) { text_instance *text = PARENT(e, text_instance, export); if (text->charset) free(text->charset); free(text); } #define elements(array) (sizeof(array) / sizeof(array[0])) static const char * formats[] = { N_("ASCII"), N_("ISO-8859-1 (Latin-1 Western languages)"), N_("ISO-8859-2 (Latin-2 Central and Eastern Europe languages)"), N_("ISO-8859-4 (Latin-3 Baltic languages)"), N_("ISO-8859-5 (Cyrillic)"), N_("ISO-8859-7 (Greek)"), N_("ISO-8859-8 (Hebrew)"), N_("ISO-8859-9 (Turkish)"), N_("KOI8-R (Russian and Bulgarian)"), N_("KOI8-U (Ukranian)"), N_("ISO-10646/UTF-8 (Unicode)"), }; static const char * iconv_formats[] = { "ASCII", "ISO-8859-1", "ISO-8859-2", "ISO-8859-4", "ISO-8859-5", "ISO-8859-7", "ISO-8859-8", "ISO-8859-9", "KOI8-R", "KOI8-U", "UTF-8" }; static const char * color_names[] _vbi_unused = { N_("Black"), N_("Red"), N_("Green"), N_("Yellow"), N_("Blue"), N_("Magenta"), N_("Cyan"), N_("White"), N_("Any") }; static const char * terminal[] = { /* TRANSLATORS: Terminal control codes. */ N_("None"), N_("ANSI X3.64 / VT 100"), N_("VT 200") }; static vbi_option_info text_options[] = { VBI_OPTION_MENU_INITIALIZER /* TRANSLATORS: Text export format (ASCII, Unicode, ...) menu */ ("format", N_("Format"), 0, formats, elements(formats), NULL), /* one for users, another for programs */ VBI_OPTION_STRING_INITIALIZER ("charset", NULL, "", NULL), VBI_OPTION_STRING_INITIALIZER ("gfx_chr", N_("Graphics char"), "#", N_("Replacement for block graphic characters: " "a single character or decimal (32) or hex (0x20) code")), VBI_OPTION_MENU_INITIALIZER ("control", N_("Control codes"), 0, terminal, elements(terminal), NULL), #if 0 /* obsolete (I think) */ VBI_OPTION_MENU_INITIALIZER ("fg", N_("Foreground"), 8 /* any */, color_names, elements(color_names), N_("Assumed terminal foreground color")), VBI_OPTION_MENU_INITIALIZER ("bg", N_("Background"), 8 /* any */, color_names, elements(color_names), N_("Assumed terminal background color")) #endif }; static vbi_option_info * option_enum(vbi_export *e, int index) { e = e; if (index < 0 || index >= (int) elements(text_options)) return NULL; return text_options + index; } #define KEYWORD(str) (strcmp(keyword, str) == 0) static vbi_bool option_get(vbi_export *e, const char *keyword, vbi_option_value *value) { text_instance *text = PARENT(e, text_instance, export); if (KEYWORD("format")) { value->num = text->format; } else if (KEYWORD("charset")) { if (!(value->str = vbi_export_strdup(e, NULL, text->charset))) return FALSE; } else if (KEYWORD("gfx_chr")) { if (!(value->str = vbi_export_strdup(e, NULL, "x"))) return FALSE; value->str[0] = text->gfx_chr; } else if (KEYWORD("control")) { value->num = text->term; } else if (KEYWORD("fg")) { value->num = text->def_fg; } else if (KEYWORD("bg")) { value->num = text->def_bg; } else { vbi_export_unknown_option(e, keyword); return FALSE; } return TRUE; /* success */ } static vbi_bool option_set(vbi_export *e, const char *keyword, va_list args) { text_instance *text = PARENT(e, text_instance, export); if (KEYWORD("format")) { unsigned int format = va_arg(args, unsigned int); if (format >= elements(formats)) { vbi_export_invalid_option(e, keyword, format); return FALSE; } text->format = format; } else if (KEYWORD("charset")) { char *string = va_arg(args, char *); if (!string) { vbi_export_invalid_option(e, keyword, string); return FALSE; } else if (!vbi_export_strdup(e, &text->charset, string)) return FALSE; } else if (KEYWORD("gfx_chr")) { char *s, *string = va_arg(args, char *); int value; if (!string || !string[0]) { vbi_export_invalid_option(e, keyword, string); return FALSE; } if (strlen(string) == 1) { value = string[0]; } else { value = strtol(string, &s, 0); if (s == string) value = string[0]; } text->gfx_chr = (value < 0x20 || value > 0xE000) ? 0x20 : value; } else if (KEYWORD("control")) { int term = va_arg(args, int); if (term < 0 || term > 2) { vbi_export_invalid_option(e, keyword, term); return FALSE; } text->term = term; } else if (KEYWORD("fg")) { int col = va_arg(args, int); if (col < 0 || col > 8) { vbi_export_invalid_option(e, keyword, col); return FALSE; } text->def_fg = col; } else if (KEYWORD("bg")) { int col = va_arg(args, int); if (col < 0 || col > 8) { vbi_export_invalid_option(e, keyword, col); return FALSE; } text->def_bg = col; } else { vbi_export_unknown_option(e, keyword); return FALSE; } return TRUE; } static inline char * _stpcpy(char *dst, const char *src) { while ((*dst = *src++)) dst++; return dst; } static int match_color8(vbi_rgba color) { int i, d, imin = 0, dmin = INT_MAX; for (i = 0; i < 8; i++) { d = ABS((int)( (i & 1) * 0xFF - VBI_R(color))); d += ABS((int)(((i >> 1) & 1) * 0xFF - VBI_G(color))); d += ABS((int)( (i >> 2) * 0xFF - VBI_B(color))); if (d < dmin) { dmin = d; imin = i; } } return imin; } static vbi_bool print_unicode(iconv_t cd, int endian, int unicode, char **p, int n) { char in[2], *ip, *op; size_t li, lo, r; in[0 + endian] = unicode; in[1 - endian] = unicode >> 8; ip = in; op = *p; li = sizeof(in); lo = n; r = iconv(cd, &ip, &li, &op, &lo); if ((size_t) -1 == r || (**p == 0x40 && unicode != 0x0040)) { in[0 + endian] = 0x20; in[1 - endian] = 0; ip = in; op = *p; li = sizeof(in); lo = n; r = iconv(cd, &ip, &li, &op, &lo); if ((size_t) -1 == r || (r == 1 && **p == 0x40)) goto error; } *p = op; return TRUE; error: return FALSE; } /** * @param pg Source page. * @param buf Memory location to hold the output. * @param size Size of the buffer in bytes. The function * fails when the data exceeds the buffer capacity. * @param format Character set name for iconv() conversion, * for example "ISO-8859-1". * @param table Scan page in table mode, printing all characters * within the source rectangle including runs of spaces at * the start and end of rows. When @c FALSE, scan all characters * from @a column, @a row to @a column + @a width - 1, * @a row + @a height - 1 and all intermediate rows to their * full pg->columns width. In this mode runs of spaces at * the start and end of rows are collapsed into single spaces, * blank lines are suppressed. * @param rtl Currently ignored. * @param column First source column, 0 ... pg->columns - 1. * @param row First source row, 0 ... pg->rows - 1. * @param width Number of columns to print, 1 ... pg->columns. * @param height Number of rows to print, 1 ... pg->rows. * * Print a subsection of a Teletext or Closed Caption vbi_page, * rows separated by linefeeds "\n", in the desired format. * All character attributes and colors will be lost. Graphics * characters, DRCS and all characters not representable in the * target format will be replaced by spaces. * * @return * Number of bytes written into @a buf, a value of zero when * some error occurred. In this case @a buf may contain incomplete * data. Note this function does not append a terminating null * character. */ int vbi_print_page_region(vbi_page *pg, char *buf, int size, const char *format, vbi_bool table, vbi_bool rtl, int column, int row, int width, int height) { int endian = vbi_ucs2be(); int column0, column1, row0, row1; int x, y, spaces, doubleh, doubleh0; iconv_t cd; char *p; rtl = rtl; if (0) fprintf (stderr, "vbi_print_page_region '%s' " "table=%d col=%d row=%d width=%d height=%d\n", format, table, column, row, width, height); column0 = column; row0 = row; column1 = column + width - 1; row1 = row + height - 1; if (!pg || !buf || size < 0 || !format || column0 < 0 || column1 >= pg->columns || row0 < 0 || row1 >= pg->rows || endian < 0) return 0; if ((cd = iconv_open(format, "UCS-2")) == (iconv_t) -1) return 0; p = buf; doubleh = 0; for (y = row0; y <= row1; y++) { int x0, x1, xl; x0 = (table || y == row0) ? column0 : 0; x1 = (table || y == row1) ? column1 : (pg->columns - 1); xl = (table || y != row0 || (y + 1) != row1) ? -1 : column1; doubleh0 = doubleh; spaces = 0; doubleh = 0; for (x = x0; x <= x1; x++) { vbi_char ac = pg->text[y * pg->columns + x]; if (table) { if (ac.size > VBI_DOUBLE_SIZE) ac.unicode = 0x0020; } else { switch (ac.size) { case VBI_NORMAL_SIZE: case VBI_DOUBLE_WIDTH: break; case VBI_DOUBLE_HEIGHT: case VBI_DOUBLE_SIZE: doubleh++; break; case VBI_OVER_TOP: case VBI_OVER_BOTTOM: continue; case VBI_DOUBLE_HEIGHT2: case VBI_DOUBLE_SIZE2: if (y > row0) ac.unicode = 0x0020; break; } /* * Special case two lines row0 ... row1, and all chars * in row0, column0 ... column1 are double height: Skip * row1, don't wrap around. */ if (x == xl && doubleh >= (x - x0)) { x1 = xl; y = row1; } if (ac.unicode == 0x20 || !vbi_is_print(ac.unicode)) { spaces++; continue; } else { if (spaces < (x - x0) || y == row0) { for (; spaces > 0; spaces--) if (!print_unicode(cd, endian, 0x0020, &p, buf + size - p)) goto failure; } else /* discard leading spaces */ spaces = 0; } } if (!print_unicode(cd, endian, ac.unicode, &p, buf + size - p)) goto failure; } /* if !table discard trailing spaces and blank lines */ if (y < row1) { int left = buf + size - p; if (left < 1) goto failure; if (table) { *p++ = '\n'; /* XXX convert this (eg utf16) */ } else if (spaces >= (x1 - x0)) { ; /* suppress blank line */ } else { /* exactly one space between adjacent rows */ if (!print_unicode(cd, endian, 0x0020, &p, left)) goto failure; } } else { if (doubleh0 > 0) { ; /* prentend this is a blank double height lower row */ } else { for (; spaces > 0; spaces--) if (!print_unicode(cd, endian, 0x0020, &p, buf + size - p)) goto failure; } } } iconv_close(cd); return p - buf; failure: iconv_close(cd); return 0; } static int print_char(text_instance *text, int endian, vbi_page *pg, vbi_char old, vbi_char this) { char *p; vbi_char chg, off; p = text->buf; if (text->term > 0) { union { vbi_char c; uint64_t i; } u_old, u_tmp, u_this; assert(sizeof(vbi_char) == 8); u_old.c = old; u_this.c = this; u_tmp.i = u_old.i ^ u_this.i; chg = u_tmp.c; u_tmp.i = u_tmp.i &~u_this.i; off = u_tmp.c; /* http://www.cs.ruu.nl/wais/html/na-dir/emulators-faq/part3.html */ if (chg.size) switch (this.size) { case VBI_NORMAL_SIZE: p = _stpcpy(p, "\e#5"); break; case VBI_DOUBLE_WIDTH: p = _stpcpy(p, "\e#6"); break; case VBI_DOUBLE_HEIGHT: case VBI_DOUBLE_HEIGHT2: break; /* ignore */ case VBI_DOUBLE_SIZE: p = _stpcpy(p, "\e#3"); break; case VBI_DOUBLE_SIZE2: p = _stpcpy(p, "\e#4"); break; case VBI_OVER_TOP: case VBI_OVER_BOTTOM: return -1; /* don't print */ } p = _stpcpy(p, "\e["); if (text->term == 1) { if (off.underline || off.bold || off.flash) { *p++ = ';'; /* \e[0; reset */ chg.underline = this.underline; chg.bold = this.bold; chg.flash = this.flash; chg.foreground = ~0; chg.background = ~0; } } if (chg.underline) { if (!this.underline) *p++ = '2'; /* off */ p = _stpcpy(p, "4;"); /* underline */ } if (chg.bold) { if (!this.bold) *p++ = '2'; /* off */ p = _stpcpy(p, "1;"); /* bold */ } /* italic ignored */ if (chg.flash) { if (!this.flash) *p++ = '2'; /* off */ p = _stpcpy(p, "5;"); /* flash */ } /* FIXME what is the real buffer size? */ if (chg.foreground) p += snprintf(p, 4, "3%c;", '0' + match_color8(pg->color_map[this.foreground])); if (chg.background) p += snprintf(p, 4, "4%c;", '0' + match_color8(pg->color_map[this.background])); if (p[-1] == '[') p -= 2; /* no change */ else p[-1] = 'm'; /* replace last semicolon */ } if (!vbi_is_print(this.unicode)) { if (vbi_is_gfx(this.unicode)) this.unicode = text->gfx_chr; else this.unicode = 0x0020; } if (!print_unicode(text->cd, endian, this.unicode, &p, text->buf + sizeof(text->buf) - p)) { vbi_export_write_error(&text->export); return 0; } return p - text->buf; } static vbi_bool export (vbi_export * e, vbi_page * pg) { int endian = vbi_ucs2be(); text_instance *text = PARENT(e, text_instance, export); vbi_page page; vbi_char *cp, old; int column, row, n; const char *charset; if (text->charset && text->charset[0]) charset = text->charset; else charset = iconv_formats[text->format]; text->cd = iconv_open (charset, "UCS-2"); if ((iconv_t) -1 == text->cd || endian < 0) { vbi_export_error_printf(&text->export, _("Character conversion Unicode " "(UCS-2) to %s not supported."), charset); if ((iconv_t) -1 != text->cd) iconv_close (text->cd); return FALSE; } page = *pg; /* optimize */ memset(&old, ~0, sizeof(old)); for (cp = page.text, row = 0;;) { for (column = 0; column < pg->columns; column++) { n = print_char(text, endian, &page, old, *cp); if (n < 0) { ; /* skipped */ } else if (n == 0) { iconv_close(text->cd); return FALSE; } else if (n == 1) { vbi_export_putc (e, text->buf[0]); } else { vbi_export_write (e, text->buf, n); } old = *cp++; } row++; if (row >= pg->rows) { if (text->term > 0) vbi_export_printf (e, "\e[m\n"); /* reset */ else vbi_export_putc (e, '\n'); break; } else { vbi_export_putc (e, '\n'); } } iconv_close(text->cd); return !e->write_error; } static vbi_export_info info_text = { .keyword = "text", .label = N_("Text"), .tooltip = N_("Export this page as text file"), .mime_type = "text/plain", .extension = "txt", }; vbi_export_class vbi_export_class_text = { ._public = &info_text, ._new = text_new, ._delete = text_delete, .option_enum = option_enum, .option_get = option_get, .option_set = option_set, .export = export }; /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/exp-txt.h000066400000000000000000000050011476363111200151240ustar00rootroot00000000000000/* * libzvbi - Text export functions * * Copyright (C) 2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: exp-txt.h,v 1.9 2008-02-19 00:35:15 mschimek Exp $ */ #ifndef EXP_TXT_H #define EXP_TXT_H #include "format.h" /* Public */ /** * @addtogroup Render * @{ */ extern int vbi_print_page_region(vbi_page *pg, char *buf, int size, const char *format, vbi_bool table, vbi_bool ltr, int column, int row, int width, int height); /** * @param pg Source page. * @param buf Memory location to hold the output. * @param size Size of the buffer in bytes. The function fails * when the data exceeds the buffer capacity. * @param format Character set name for iconv() conversion, * for example "ISO-8859-1". * @param table When @c FALSE, runs of spaces at the start and * end of rows will be collapsed into single spaces, blank * lines are suppressed. * @param ltr Currently ignored, please set to @c TRUE. * * Print a Teletext or Closed Caption vbi_page, rows separated * by linefeeds "\n", in the desired format. All character attributes * and colors will be lost. Graphics characters, DRCS and all * characters not representable in the target format will be replaced * by spaces. * * @return * Number of bytes written into @a buf, a value of zero when * some error occurred. In this case @a buf may contain incomplete * data. Note this function does not append a terminating null * character. **/ _vbi_inline int vbi_print_page(vbi_page *pg, char *buf, int size, const char *format, vbi_bool table, vbi_bool ltr) { return vbi_print_page_region(pg, buf, size, format, table, ltr, 0, 0, pg->columns, pg->rows); } /** @} */ /* Private */ #endif /* EXP_TXT_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/exp-vtx.c000066400000000000000000000102651476363111200151310ustar00rootroot00000000000000/* * libzvbi - VTX export function * * Copyright (C) 2001, 2007 Michael H. Schimek * * Based on code from AleVT 1.5.1 * Copyright (C) 1998, 1999 Edgar Toernig * Copyright (C) 1999 Paul Ortyl * * Based on code from VideoteXt 0.6 * Copyright (C) 1995, 1996, 1997 Martin Buck * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* $Id: exp-vtx.c,v 1.12 2008-02-19 00:35:15 mschimek Exp $ */ /* Disabled for now because this code is licensed under GPLv2+ and cannot be linked with the rest of libzvbi which is licensed under LGPLv2+ since version 0.2.28. */ #if 0 /* * VTX is the file format used by VideoteXt. It stores Teletext pages in * raw level 1.0 format. Level 1.5 additional characters (e.g. accents), the * FLOF and TOP navigation bars and the level 2.5 chrome will be lost. * (People interested in an XML based successor to VTX drop us a mail.) * * Since restoring the raw page from a fmt_page is complicated we violate * encapsulation by fetching a raw copy from the cache. :-( */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include "vbi.h" /* cache, vt.h */ #include "hamm.h" /* bit_reverse */ #include "export.h" struct header { char signature[5]; unsigned char pagenum_l; unsigned char pagenum_h; unsigned char hour; unsigned char minute; unsigned char charset; unsigned char wst_flags; unsigned char vtx_flags; }; /* * VTX - VideoteXt File (VTXV4) */ static vbi_bool export(vbi_export *e, vbi_page *pg) { vt_page page, *vtp; struct header *h; size_t needed; if (pg->pgno < 0x100 || pg->pgno > 0x8FF) { /* TRANSLATORS: Not Closed Caption pages. */ vbi_export_error_printf (e, _("Can only export Teletext pages.")); return FALSE; } /**/ if (!pg->vbi || !(vtp = vbi_cache_get(pg->vbi, pg->pgno, pg->subno, -1))) { vbi_export_error_printf(e, _("Page is not cached, sorry.")); return FALSE; } memcpy(&page, vtp, vtp_size(vtp)); /**/ if (page.function != PAGE_FUNCTION_UNKNOWN && page.function != PAGE_FUNCTION_LOP) { vbi_export_error_printf (e, _("Cannot export this page, not displayable.")); return FALSE; } needed = sizeof (*h); if (VBI_EXPORT_TARGET_ALLOC == e->target) needed += 40 * 24; if (!_vbi_export_grow_buffer_space (e, needed)) return FALSE; h = (struct header *)(e->buffer.data + e->buffer.offset); memcpy (h->signature, "VTXV4", 5); h->pagenum_l = page.pgno & 0xFF; h->pagenum_h = (page.pgno >> 8) & 15; h->hour = 0; h->minute = 0; h->charset = page.national & 7; h->wst_flags = page.flags & C4_ERASE_PAGE; h->wst_flags |= vbi_rev8 (page.flags >> 12); h->vtx_flags = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3); /* notfound, pblf (?), hamming error, virtual, seven bits */ e->buffer.offset += sizeof (*h); return vbi_export_write (e, page.data.lop.raw, 40 * 24); } static vbi_export_info info_vtx = { .keyword = "vtx", .label = "VTX", /* proper name */ .tooltip = N_("Export this page as VTX file, the format " "used by VideoteXt and vbidecode"), /* From VideoteXt examples/mime.types */ .mime_type = "application/videotext", .extension = "vtx", }; vbi_export_class vbi_export_class_vtx = { ._public = &info_vtx, /* no private data, no options */ .export = export }; VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_vtx) #endif /* 0 */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/export.c000066400000000000000000001377561476363111200150560ustar00rootroot00000000000000/* * libzvbi - Export modules * * Copyright (C) 2001, 2002, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: export.c,v 1.30 2013-08-28 14:45:33 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif #include "export.h" #include "conv.h" #include "vbi.h" /* asprintf */ extern const char _zvbi_intl_domainname[]; /** * @addtogroup Export Exporting formatted Teletext and Closed Caption pages * @ingroup HiDec * * Once libzvbi received, decoded and formatted a Teletext or Closed * Caption page you will want to render it on screen, print it as * text or store it in various formats. * * Fortunately you don't have to do it all by yourself. libzvbi provides * export modules converting a vbi_page into the desired format or * rendering directly into memory. * * A minimalistic export example: * * @code * static void * export_my_page (vbi_page *pg) * { * vbi_export *ex; * char *errstr; * * if (!(ex = vbi_export_new ("html", &errstr))) { * fprintf (stderr, "Cannot export as HTML: %s\n", errstr); * free (errstr); * return; * } * * if (!vbi_export_file (ex, "my_page.html", pg)) * puts (vbi_export_errstr (ex)); * * vbi_export_delete (ex); * } * @endcode */ /** * @addtogroup Exmod Internal export module interface * @ingroup Export * * This is the private interface between the public libzvbi export * functions and export modules. libzvbi client applications * don't use this. * * Export modules include @c "export.h" to get these * definitions. See example module exp-templ.c. */ /** * @addtogroup Render Teletext and Closed Caption page render functions * @ingroup Export * * These are functions to render Teletext and Closed Caption pages * directly into memory, essentially a more direct interface to the * functions of some important export modules. */ static vbi_bool initialized; static vbi_export_class *vbi_export_modules; /** * @param new_module Static pointer to initialized vbi_export_class structure. * * Registers a new export module. */ void vbi_register_export_module(vbi_export_class *new_module) { vbi_export_class **xcp; if (0) fprintf(stderr, "libzvbi:vbi_register_export_module(\"%s\")\n", new_module->_public->keyword); for (xcp = &vbi_export_modules; *xcp; xcp = &(*xcp)->next) if (strcmp(new_module->_public->keyword, (*xcp)->_public->keyword) < 0) break; new_module->next = *xcp; *xcp = new_module; } extern vbi_export_class vbi_export_class_ppm; extern vbi_export_class vbi_export_class_xpm; extern vbi_export_class vbi_export_class_png; extern vbi_export_class vbi_export_class_html; extern vbi_export_class vbi_export_class_tmpl; extern vbi_export_class vbi_export_class_text; /* Temporarily disabled, see exp-vtx.c. extern vbi_export_class vbi_export_class_vtx; */ /* AUTOREG not reliable, sigh. */ static void initialize(void) { static vbi_export_class *modules[] = { &vbi_export_class_ppm, &vbi_export_class_xpm, #ifdef HAVE_LIBPNG &vbi_export_class_png, #endif &vbi_export_class_html, &vbi_export_class_text, /* Temporarily disabled, see exp-vtx.c. &vbi_export_class_vtx, */ NULL, &vbi_export_class_tmpl, }; vbi_export_class **xcp; pthread_once (&vbi_init_once, vbi_init); if (!vbi_export_modules) for (xcp = modules; *xcp; xcp++) vbi_register_export_module(*xcp); initialized = TRUE; } /** * Helper function for export modules, since iconv seems * undecided what it really wants (not every iconv supports * UCS-2LE/BE). * * @return * 1 if iconv "UCS-2" is BE on this machine, 0 if LE, * -1 if unknown. */ /* XXX provide a vbi_iconv wrapper. */ int vbi_ucs2be(void) { iconv_t cd; char c = 'b', *cp = &c; char uc[2] = { 'a', 'a' }, *up = uc; size_t in = sizeof(c), out = sizeof(uc); int endianess = -1; if ((cd = iconv_open("UCS-2", /* from */ "ISO-8859-1")) != (iconv_t) -1) { iconv(cd, (void *) &cp, &in, (void *) &up, &out); if (uc[0] == 0 && uc[1] == 'b') endianess = 1; else if (uc[1] == 0 && uc[0] == 'b') endianess = 0; iconv_close(cd); } return endianess; } /* * This is old stuff, we'll see if it's still needed. */ #if 0 static char * hexnum(char *buf, unsigned int num) { char *p = buf + 5; num &= 0xffff; *--p = 0; do { *--p = "0123456789abcdef"[num % 16]; num /= 16; } while (num); return p; } static char * adjust(char *p, char *str, char fill, int width, int deq) { int c, l = width - strlen(str); while (l-- > 0) *p++ = fill; while ((c = *str++)) { if (deq && strchr(" /?*", c)) c = '_'; *p++ = c; } return p; } char * vbi_export_mkname(vbi_export *e, char *fmt, int pgno, int subno, char *usr) { char bbuf[1024]; char *s = bbuf; while ((*s = *fmt++)) if (*s++ == '%') { char buf[32], buf2[32]; int width = 0; s--; while (*fmt >= '0' && *fmt <= '9') width = width*10 + *fmt++ - '0'; switch (*fmt++) { case '%': s = adjust(s, "%", '%', width, 0); break; case 'e': // extension s = adjust(s, e->mod->extension, '.', width, 1); break; case 'n': // network label s = adjust(s, e->network.label, ' ', width, 1); break; case 'p': // pageno[.subno] if (subno) s = adjust(s,strcat(strcat(hexnum(buf, pgno), "-"), hexnum(buf2, subno)), ' ', width, 0); else s = adjust(s, hexnum(buf, pgno), ' ', width, 0); break; case 'S': // subno s = adjust(s, hexnum(buf, subno), '0', width, 0); break; case 'P': // pgno s = adjust(s, hexnum(buf, pgno), '0', width, 0); break; case 's': // user strin s = adjust(s, usr, ' ', width, 0); break; //TODO: add date, ... } } s = strdup(bbuf); if (! s) vbi_export_error_printf(e, "out of memory"); return s; } #endif /* OLD STUFF */ static vbi_option_info generic_options[] = { VBI_OPTION_STRING_INITIALIZER ("creator", NULL, PACKAGE " " VERSION, NULL), VBI_OPTION_STRING_INITIALIZER ("network", NULL, "", NULL), VBI_OPTION_BOOL_INITIALIZER ("reveal", NULL, FALSE, NULL) }; #define GENERIC (sizeof(generic_options) / sizeof(generic_options[0])) static void reset_error(vbi_export *e) { if (e->errstr) { free(e->errstr); e->errstr = NULL; } } /** * @param index Index into the export module list, 0 ... n. * * Enumerates all available export modules. You should start at index 0, * incrementing. * * Some modules may depend on machine features or the presence of certain * libraries, thus the list can vary from session to session. * * @return * Static pointer to a vbi_export_info structure (no need to be freed), * @c NULL if the index is out of bounds. */ vbi_export_info * vbi_export_info_enum(int index) { vbi_export_class *xc; if (!initialized) initialize(); for (xc = vbi_export_modules; xc && index > 0; xc = xc->next, index--); return xc ? xc->_public : NULL; } /** * @param keyword Export module identifier as in vbi_export_info and * vbi_export_new(). * * Similar to vbi_export_info_enum(), but this function attempts to find an * export module by keyword. * * @return * Static pointer to a vbi_export_info structure, @c NULL if the named export * module has not been found. */ vbi_export_info * vbi_export_info_keyword(const char *keyword) { vbi_export_class *xc; int keylen; if (!keyword) return NULL; if (!initialized) initialize(); for (keylen = 0; keyword[keylen]; keylen++) if (keyword[keylen] == ';' || keyword[keylen] == ',') break; for (xc = vbi_export_modules; xc; xc = xc->next) if (strncmp(keyword, xc->_public->keyword, keylen) == 0) return xc->_public; return NULL; } /** * @param export Pointer to a vbi_export object previously allocated with * vbi_export_new(). * * Returns the export module info for the given @a export object. * * @return * A static vbi_export_info pointer or @c NULL if @a export * is @c NULL. */ vbi_export_info * vbi_export_info_export(vbi_export *export) { if (!export) return NULL; return export->_class->_public; } static void reset_options(vbi_export *e) { vbi_option_info *oi; int i; for (i = 0; (oi = vbi_export_option_info_enum(e, i)); i++) switch (oi->type) { case VBI_OPTION_BOOL: case VBI_OPTION_INT: if (oi->menu.num) vbi_export_option_set(e, oi->keyword, oi->menu.num[oi->def.num]); else vbi_export_option_set(e, oi->keyword, oi->def.num); break; case VBI_OPTION_REAL: if (oi->menu.dbl) vbi_export_option_set(e, oi->keyword, oi->menu.dbl[oi->def.num]); else vbi_export_option_set(e, oi->keyword, oi->def.dbl); break; case VBI_OPTION_STRING: if (oi->menu.str) vbi_export_option_set(e, oi->keyword, oi->menu.str[oi->def.num]); else vbi_export_option_set(e, oi->keyword, oi->def.str); break; case VBI_OPTION_MENU: vbi_export_option_set(e, oi->keyword, oi->def.num); break; default: fprintf(stderr, "%s: unknown export option type %d\n", __FUNCTION__, oi->type); exit(EXIT_FAILURE); } } static vbi_bool option_string(vbi_export *e, const char *s2) { vbi_option_info *oi; char *s, *s1, *keyword, *string, quote; vbi_bool r = TRUE; if (!(s = s1 = vbi_export_strdup(e, NULL, s2))) return FALSE; do { while (isspace(*s)) s++; if (*s == ',' || *s == ';') { s++; continue; } if (!*s) { free(s1); return TRUE; } keyword = s; while (isalnum(*s) || *s == '_') s++; if (!*s) goto invalid; *s++ = 0; while (isspace(*s) || *s == '=') s++; if (!*s) { invalid: vbi_export_error_printf(e, _("Invalid option string \"%s\"."), s2); break; } if (!(oi = vbi_export_option_info_keyword(e, keyword))) break; switch (oi->type) { case VBI_OPTION_BOOL: case VBI_OPTION_INT: case VBI_OPTION_MENU: r = vbi_export_option_set(e, keyword, (int) strtol(s, &s, 0)); break; case VBI_OPTION_REAL: r = vbi_export_option_set(e, keyword, (double) strtod(s, &s)); break; case VBI_OPTION_STRING: quote = 0; if (*s == '\'' || *s == '\"') quote = *s++; string = s; while (*s && *s != quote && (quote || (*s != ',' && *s != ';'))) s++; if (*s) *s++ = 0; r = vbi_export_option_set(e, keyword, string); break; default: fprintf(stderr, "%s: unknown export option type %d\n", __FUNCTION__, oi->type); exit(EXIT_FAILURE); } } while (r); free(s1); return FALSE; } /** * @param keyword Export module identifier as in vbi_export_info. * @param errstr If not @c NULL this function stores a pointer to an error * description here. You must free() this string when no longer needed. * * Creates a new export module instance to export a vbi_page in * the respective module format. As a special service you can * initialize options by appending to the \@param keyword like this: * * @code * vbi_export_new ("keyword; quality=75.5, comment=\"example text\""); * @endcode * * @return * Pointer to a newly allocated vbi_export object which must be * freed by calling vbi_export_delete(). @c NULL is returned and * the @a errstr may be set (else @a NULL) if some problem occurred. */ vbi_export * vbi_export_new(const char *keyword, char **errstr) { char key[256]; vbi_export_class *xc; vbi_export *e; unsigned int keylen; if (!initialized) initialize(); if (!keyword) keyword = ""; for (keylen = 0; keyword[keylen] && keylen < (sizeof(key) - 1) && keyword[keylen] != ';' && keyword[keylen] != ','; keylen++) key[keylen] = keyword[keylen]; key[keylen] = 0; for (xc = vbi_export_modules; xc; xc = xc->next) if (strcmp(key, xc->_public->keyword) == 0) break; if (!xc) { asprintf(errstr, _("Unknown export module '%s'."), key); return NULL; } if (!xc->_new) e = calloc(1, sizeof(*e)); else e = xc->_new(); if (!e) { asprintf(errstr, _("Cannot initialize export module '%s', " "probably lack of memory."), xc->_public->label ? xc->_public->label : keyword); return NULL; } memset (&e->_handle, -1, sizeof (e->_handle)); e->_class = xc; e->errstr = NULL; e->name = NULL; reset_options(e); if (keyword[keylen] && !option_string(e, keyword + keylen + 1)) { if (errstr) *errstr = strdup(vbi_export_errstr(e)); vbi_export_delete(e); return NULL; } if (errstr) errstr = NULL; return e; } /** * @param export Pointer to a vbi_export object previously allocated with * vbi_export_new(). Can be @c NULL. * * This function frees all resources associated with the vbi_export * object. */ void vbi_export_delete(vbi_export *export) { vbi_export_class *xc; if (!export) return; if (export->errstr) free(export->errstr); if (export->network) free(export->network); if (export->creator) free(export->creator); xc = export->_class; if (xc->_new && xc->_delete) xc->_delete(export); else free(export); } /** * @param export Pointer to a initialized vbi_export object. * @param index Index in the option table 0 ... n. * * Enumerates the options available for the given export module. You * should start at index 0, incrementing. * * @return Static pointer to a vbi_option_info structure, * @c NULL if @a index is out of bounds. */ /* XXX unsigned index */ vbi_option_info * vbi_export_option_info_enum(vbi_export *export, int index) { vbi_export_class *xc; if (!export) return NULL; reset_error(export); if (index < (int) GENERIC) return generic_options + index; xc = export->_class; if (xc->option_enum) return xc->option_enum(export, index - GENERIC); else return NULL; } /** * @param export Pointer to a initialized vbi_export object. * @param keyword Keyword of the option as in vbi_option_info. * * Similar to vbi_export_option_info_enum(), but tries to find the * option info based on the given keyword. * * @return Static pointer to a vbi_option_info structure, * @c NULL if the keyword wasn't found. */ vbi_option_info * vbi_export_option_info_keyword(vbi_export *export, const char *keyword) { vbi_export_class *xc; vbi_option_info *oi; unsigned int i; if (!export || !keyword) return NULL; reset_error(export); for (i = 0; i < GENERIC; i++) if (strcmp(keyword, generic_options[i].keyword) == 0) return generic_options + i; xc = export->_class; if (!xc->option_enum) return NULL; for (i = 0; (oi = xc->option_enum(export, i)); i++) if (strcmp(keyword, oi->keyword) == 0) return oi; vbi_export_unknown_option(export, keyword); return NULL; } /** * @param export Pointer to a initialized vbi_export object. * @param keyword Keyword identifying the option, as in vbi_option_info. * @param value A place to store the current option value. * * This function queries the current value of the named option. * When the option is of type VBI_OPTION_STRING @a value.str must be * freed with free() when you don't need it any longer. When the * option is of type VBI_OPTION_MENU then @a value.num contains the * selected entry. * * @return @c TRUE on success, otherwise @a value unchanged. */ vbi_bool vbi_export_option_get(vbi_export *export, const char *keyword, vbi_option_value *value) { vbi_export_class *xc; vbi_bool r = TRUE; if (!export || !keyword || !value) return FALSE; reset_error(export); if (strcmp(keyword, "reveal") == 0) { value->num = export->reveal; } else if (strcmp(keyword, "network") == 0) { if (!(value->str = vbi_export_strdup(export, NULL, export->network ? : ""))) return FALSE; } else if (strcmp(keyword, "creator") == 0) { if (!(value->str = vbi_export_strdup(export, NULL, export->creator))) return FALSE; } else { xc = export->_class; if (xc->option_get) r = xc->option_get(export, keyword, value); else { vbi_export_unknown_option(export, keyword); r = FALSE; } } return r; } /** * @param export Pointer to a initialized vbi_export object. * @param keyword Keyword identifying the option, as in vbi_option_info. * @param ... New value to set. * * Sets the value of the named option. Make sure the value is casted * to the correct type (int, double, char *). * * Typical usage of vbi_export_option_set(): * @code * vbi_export_option_set (export, "quality", 75.5); * @endcode * * Mind that options of type @c VBI_OPTION_MENU must be set by menu * entry number (int), all other options by value. If necessary it will * be replaced by the closest value possible. Use function * vbi_export_option_menu_set() to set options with menu * by menu entry. * * @return * @c TRUE on success, otherwise the option is not changed. */ vbi_bool vbi_export_option_set(vbi_export *export, const char *keyword, ...) { vbi_export_class *xc; vbi_bool r = TRUE; va_list args; if (!export || !keyword) return FALSE; reset_error(export); va_start(args, keyword); if (strcmp(keyword, "reveal") == 0) { export->reveal = !!va_arg(args, int); } else if (strcmp(keyword, "network") == 0) { char *network = va_arg(args, char *); if (!network || !network[0]) { if (export->network) { free(export->network); export->network = NULL; } } else if (!vbi_export_strdup(export, &export->network, network)) { return FALSE; } } else if (strcmp(keyword, "creator") == 0) { char *creator = va_arg(args, char *); if (!vbi_export_strdup(export, &export->creator, creator)) return FALSE; } else { xc = export->_class; if (xc->option_set) r = xc->option_set(export, keyword, args); else r = FALSE; } va_end(args); return r; } /** * @param export Pointer to a initialized vbi_export object. * @param keyword Keyword identifying the option, as in vbi_option_info. * @param entry A place to store the current menu entry. * * Similar to vbi_export_option_get() this function queries the current * value of the named option, but returns this value as number of the * corresponding menu entry. Naturally this must be an option with * menu. * * @return * @c TRUE on success, otherwise @a value remained unchanged. */ vbi_bool vbi_export_option_menu_get(vbi_export *export, const char *keyword, int *entry) { vbi_option_info *oi; vbi_option_value val; vbi_bool r; int i; if (!export || !keyword || !entry) return FALSE; reset_error(export); if (!(oi = vbi_export_option_info_keyword(export, keyword))) return FALSE; if (!vbi_export_option_get(export, keyword, &val)) return FALSE; r = FALSE; for (i = oi->min.num; i <= oi->max.num; i++) { switch (oi->type) { case VBI_OPTION_BOOL: case VBI_OPTION_INT: if (!oi->menu.num) return FALSE; r = (oi->menu.num[i] == val.num); break; case VBI_OPTION_REAL: if (!oi->menu.dbl) return FALSE; /* XXX unsafe */ r = (oi->menu.dbl[i] == val.dbl); break; case VBI_OPTION_MENU: r = (i == val.num); break; default: fprintf(stderr, "%s: unknown export option type %d\n", __FUNCTION__, oi->type); exit(EXIT_FAILURE); } if (r) { *entry = i; break; } } return r; } /** * @param export Pointer to a initialized vbi_export object. * @param keyword Keyword identifying the option, as in vbi_option_info. * @param entry Menu entry to be selected. * * Similar to vbi_export_option_set() this function sets the value of * the named option, however it does so by number of the corresponding * menu entry. Naturally this must be an option with menu. * * @return * @c TRUE on success, otherwise the option is not changed. */ vbi_bool vbi_export_option_menu_set(vbi_export *export, const char *keyword, int entry) { vbi_option_info *oi; if (!export || !keyword) return FALSE; reset_error(export); if (!(oi = vbi_export_option_info_keyword(export, keyword))) return FALSE; if (entry < oi->min.num || entry > oi->max.num) return FALSE; switch (oi->type) { case VBI_OPTION_BOOL: case VBI_OPTION_INT: if (!oi->menu.num) return FALSE; return vbi_export_option_set(export, keyword, oi->menu.num[entry]); case VBI_OPTION_REAL: if (!oi->menu.dbl) return FALSE; return vbi_export_option_set(export, keyword, oi->menu.dbl[entry]); case VBI_OPTION_MENU: return vbi_export_option_set(export, keyword, entry); default: fprintf(stderr, "%s: unknown export option type %d\n", __FUNCTION__, oi->type); exit(EXIT_FAILURE); } } /* Output functions. */ /** * @internal * @param e Initialized vbi_export object. * @param min_space Space required in the output buffer in bytes. * * This function increases the capacity of the output buffer in the * vbi_export @a e structure to @a e->buffer.offset + min_space or * more. In other words, it ensures at least @a min_space bytes can * be written into the buffer at @a e->buffer.offset. * * Note on success this function may change the @a e->buffer.data * pointer as well as @a e->buffer.capacity. * * @returns * @c TRUE if the buffer capacity is already sufficient or was * successfully increased. @c FALSE if @a e->write_error is @c TRUE * (a previously called export output function failed) or more memory * could not be allocated. In this case @a e->buffer remains * unmodified. */ vbi_bool _vbi_export_grow_buffer_space (vbi_export * e, size_t min_space) { const size_t element_size = sizeof (*e->buffer.data); size_t offset; size_t capacity; vbi_bool success; assert (NULL != e); assert (0 != e->target); offset = e->buffer.offset; capacity = e->buffer.capacity; assert (offset <= capacity); if (unlikely (e->write_error)) return FALSE; if (capacity >= min_space && offset <= capacity - min_space) return TRUE; if (unlikely (offset > SIZE_MAX - min_space)) goto failed; if (VBI_EXPORT_TARGET_MEM == e->target) { char *old_data; /* Not enough buffer space. Change to TARGET_ALLOC to calculate the actually needed amount. */ old_data = e->buffer.data; e->target = VBI_EXPORT_TARGET_ALLOC; e->_write = NULL; e->buffer.data = NULL; e->buffer.capacity = 0; success = _vbi_grow_vector_capacity ((void **) &e->buffer.data, &e->buffer.capacity, offset + min_space, element_size); if (unlikely (!success)) goto failed; /* Carry over the old data because the output may fit after all. */ memcpy (e->buffer.data, old_data, e->buffer.offset); return TRUE; } else { success = _vbi_grow_vector_capacity ((void **) &e->buffer.data, &e->buffer.capacity, offset + min_space, element_size); if (likely (success)) return TRUE; } failed: _vbi_export_malloc_error (e); /* We do not set e->write_error here so this function can be used to preallocate e->buffer.data prior to calling output functions like vbi_export_putc() without knowing exactly how much memory is needed. */ return FALSE; } static vbi_bool write_fp (vbi_export * e, const void * src, size_t src_size) { size_t actual; actual = fwrite (src, 1, src_size, e->_handle.fp); if (unlikely (actual != src_size)) { vbi_export_write_error (e); e->write_error = TRUE; return FALSE; } return TRUE; } static vbi_bool write_fd (vbi_export * e, const void * src, size_t src_size) { while (src_size > 0) { unsigned int retry; ssize_t actual; size_t count; count = src_size; if (unlikely (src_size > SSIZE_MAX)) count = SSIZE_MAX & -4096; for (retry = 10;; --retry) { actual = write (e->_handle.fd, src, count); if (likely (actual == (ssize_t) count)) break; if (0 != actual || 0 == retry) { vbi_export_write_error (e); e->write_error = TRUE; return FALSE; } } src = (const char *) src + actual; src_size -= actual; } return TRUE; } static vbi_bool fast_flush (vbi_export * e) { if (e->buffer.offset > 0) { vbi_bool success; success = e->_write (e, e->buffer.data, e->buffer.offset); if (unlikely (!success)) { e->write_error = TRUE; return FALSE; } e->buffer.offset = 0; } return TRUE; } /** * @param e Initialized vbi_export object. * * Writes the contents of the vbi_export output buffer into the * target buffer or file. Only export modules and their callback * functions (e.g. vbi_export_link_cb) may call this function. * * If earlier vbi_export output functions failed, this function * does nothing and returns @c FALSE immediately. * * @returns * @c FALSE on write error. The buffer remains unmodified in * this case, but incomplete data may have been written into the * target file. * * @since 0.2.26 */ vbi_bool vbi_export_flush (vbi_export * e) { assert (NULL != e); assert (0 != e->target); if (unlikely (e->write_error)) return FALSE; switch (e->target) { case VBI_EXPORT_TARGET_MEM: case VBI_EXPORT_TARGET_ALLOC: /* Nothing to do. */ break; case VBI_EXPORT_TARGET_FP: case VBI_EXPORT_TARGET_FD: case VBI_EXPORT_TARGET_FILE: return fast_flush (e); default: assert (0); } return TRUE; } /** * @param e Initialized vbi_export object. * @param c Character (one byte) to be stored in the output buffer. * * If necessary this function increases the capacity of the vbi_export * output buffer, then writes one character into it. Only export * modules and their callback functions (e.g. vbi_export_link_cb) may * call this function. * * If earlier vbi_export output functions failed, this function * does nothing and returns @c FALSE immediately. * * @returns * @c FALSE if the buffer capacity was insufficent and could not be * increased. The buffer remains unmodified in this case. * * @since 0.2.26 */ vbi_bool vbi_export_putc (vbi_export * e, int c) { size_t offset; assert (NULL != e); if (unlikely (!_vbi_export_grow_buffer_space (e, 1))) { e->write_error = TRUE; return FALSE; } offset = e->buffer.offset; e->buffer.data[offset] = c; e->buffer.offset = offset + 1; return TRUE; } static vbi_bool fast_write (vbi_export * e, const void * src, size_t src_size) { if (unlikely (!fast_flush (e))) return FALSE; if (unlikely (!e->_write (e, src, src_size))) { e->write_error = TRUE; return FALSE; } return TRUE; } /** * @param e Initialized vbi_export object. * @param src Data to be copied into the buffer. * @param src_size Number of bytes to be copied. * * If necessary this function increases the capacity of the vbi_export * output buffer, then copies the data from the @a src buffer into the * output buffer. Only export modules and their callback functions * (e.g. vbi_export_link_cb) may call this function. * * If earlier vbi_export output functions failed, this function * does nothing and returns @c FALSE immediately. If the export target * is a file, the function may call vbi_export_flush() and write * the data directly into the file. * * @returns * @c FALSE if the buffer capacity was insufficent and could not be * increased, or if a write error occurred. The buffer remains * unmodified in this case, but incomplete data may have been written * into the target file. * * @since 0.2.26 */ vbi_bool vbi_export_write (vbi_export * e, const void * src, size_t src_size) { size_t offset; assert (NULL != e); assert (NULL != src); if (unlikely (e->write_error)) return FALSE; switch (e->target) { case VBI_EXPORT_TARGET_MEM: case VBI_EXPORT_TARGET_ALLOC: /* Use buffered I/O. */ break; case VBI_EXPORT_TARGET_FP: case VBI_EXPORT_TARGET_FD: case VBI_EXPORT_TARGET_FILE: if (src_size >= 4096) return fast_write (e, src, src_size); break; default: assert (0); } if (unlikely (!_vbi_export_grow_buffer_space (e, src_size))) { e->write_error = TRUE; return FALSE; } offset = e->buffer.offset; memcpy (e->buffer.data + offset, src, src_size); e->buffer.offset = offset + src_size; return TRUE; } /** * @param e Initialized vbi_export object. * @param src NUL-terminated string to be copied into the buffer, can * be @c NULL. * * If necessary this function increases the capacity of the vbi_export * output buffer, then writes the string @a src into it. It does not write * the terminating NUL or a line feed character. Only export modules * and their callback functions (e.g. vbi_export_link_cb) may call this * function. * * If earlier vbi_export output functions failed, this function * does nothing and returns @c FALSE immediately. If the export target * is a file, the function may call vbi_export_flush() and write * the data directly into the file. * * @returns * @c FALSE if the buffer capacity was insufficent and could not be * increased, or if a write error occurred. The buffer remains * unmodified in this case, but incomplete data may have been written * into the target file. * * @since 0.2.26 */ vbi_bool vbi_export_puts (vbi_export * e, const char * src) { assert (NULL != e); if (unlikely (e->write_error)) return FALSE; if (NULL == src) return TRUE; return vbi_export_write (e, src, strlen (src)); } /** * @param e Initialized vbi_export object. * @param dst_codeset Character set name for iconv() conversion, * for example "ISO-8859-1". When @c NULL the default is UTF-8. * @param src_codeset Character set name for iconv() conversion, * for example "ISO-8859-1". When @c NULL the default is UTF-8. * @param src Source buffer, can be @c NULL. * @param src_size Number of bytes in the source string (excluding * the terminating NUL, if any). * @param repl_char UCS-2 replacement for characters which are not * representable in @a dst_codeset. When zero the function will * fail if the source buffer contains unrepresentable characters. * * If necessary this function increases the capacity of the vbi_export * output buffer, then converts the string with iconv() and writes the * result into the buffer. Only export modules and their callback * functions (e.g. vbi_export_link_cb) may call this function. * * If earlier vbi_export output functions failed, this function * does nothing and returns @c FALSE immediately. If the export target * is a file, the function may call vbi_export_flush() and write * the data directly into the file. * * @returns * @c FALSE if the conversion failed, if the buffer capacity was * insufficent and could not be increased, or if a write error occurred. * The buffer remains unmodified in this case, but incomplete data may * have been written into the target file. * * @since 0.2.26 */ vbi_bool vbi_export_puts_iconv (vbi_export * e, const char * dst_codeset, const char * src_codeset, const char * src, unsigned long src_size, int repl_char) { char *buffer; unsigned long out_size; vbi_bool success; assert (NULL != e); if (unlikely (e->write_error)) return FALSE; /* Inefficient, but shall suffice for now. */ buffer = _vbi_strndup_iconv (&out_size, dst_codeset, src_codeset, src, src_size, repl_char); if (unlikely (NULL == buffer)) { _vbi_export_malloc_error (e); e->write_error = TRUE; return FALSE; } assert (sizeof (size_t) >= sizeof (out_size)); success = vbi_export_write (e, buffer, out_size); vbi_free (buffer); return success; } /** * @param e Initialized vbi_export object. * @param dst_codeset Character set name for iconv() conversion, * for example "ISO-8859-1". When @c NULL the default is UTF-8. * @param src Source string in UCS-2 format, can be @c NULL. * @param src_length Number of characters (not bytes) in the source * string. Can be -1 if the string is NUL terminated. * @param repl_char UCS-2 replacement for characters which are not * representable in @a dst_codeset. When zero the function will * fail if the source buffer contains unrepresentable characters. * * If necessary this function increases the capacity of the vbi_export * output buffer, then converts the string with iconv() and writes the * result into the buffer. Only export modules and their callback * functions (e.g. vbi_export_link_cb) may call this function. * * If earlier vbi_export output functions failed, this function * does nothing and returns @c FALSE immediately. If the export target * is a file, the function may call vbi_export_flush() and write * the data directly into the file. * * @returns * @c FALSE if the conversion failed, if the buffer capacity was * insufficent and could not be increased, or if a write error occurred. * The buffer remains unmodified in this case, but incomplete data may * have been written into the target file. * * @since 0.2.26 */ vbi_bool vbi_export_puts_iconv_ucs2 (vbi_export * e, const char * dst_codeset, const uint16_t * src, long src_length, int repl_char) { assert (NULL != e); if (unlikely (e->write_error)) return FALSE; if (NULL == src) return TRUE; if (src_length < 0) src_length = vbi_strlen_ucs2 (src); return vbi_export_puts_iconv (e, dst_codeset, "UCS-2", (const char *) src, src_length * 2, repl_char); } /** * @param e Initialized vbi_export object. * @param templ printf-like output template. * @param ap Arguments pointer. * * If necessary this function increases the capacity of the vbi_export * output buffer, then formats a string as vprintf() does and writes * it into the buffer. Only export modules and their callback functions * (e.g. vbi_export_link_cb) may call this function. * * If earlier vbi_export output functions failed, this function * does nothing and returns @c FALSE immediately. If the export target * is a file, the function may call vbi_export_flush() and write * the data directly into the file. * * @returns * @c FALSE if the buffer capacity was insufficent and could not be * increased, or if a write error occurred. * * @since 0.2.26 */ vbi_bool vbi_export_vprintf (vbi_export * e, const char * templ, va_list ap) { size_t offset; unsigned int i; va_list ap2; assert (NULL != e); assert (NULL != templ); assert (0 != e->target); if (unlikely (e->write_error)) return FALSE; if (VBI_EXPORT_TARGET_FP == e->target) { if (unlikely (!fast_flush (e))) return FALSE; if (unlikely (vfprintf (e->_handle.fp, templ, ap) < 0)) { e->write_error = TRUE; return FALSE; } return TRUE; } va_copy (ap2, ap); offset = e->buffer.offset; for (i = 0;; ++i) { size_t avail = e->buffer.capacity - offset; int len; len = vsnprintf (e->buffer.data + offset, avail, templ, ap); if (len < 0) { /* avail is not enough. */ if (unlikely (avail >= (1 << 16))) break; /* now that's ridiculous */ /* Note 256 is the minimum free space we want but the buffer actually grows by a factor two in each iteration. */ if (!_vbi_export_grow_buffer_space (e, 256)) goto failed; } else if ((size_t) len < avail) { e->buffer.offset = offset + len; return TRUE; } else { /* Plus one because the buffer must also hold a terminating NUL, although we don't need it. */ size_t needed = (size_t) len + 1; if (unlikely (i > 0)) break; /* again? */ if (!_vbi_export_grow_buffer_space (e, needed)) goto failed; } /* vsnprintf() may advance ap. */ va_copy (ap, ap2); } _vbi_export_malloc_error (e); failed: e->write_error = TRUE; return FALSE; } /** * @param e Initialized vbi_export object. * @param templ printf-like output template. * @param ... Arguments. * * If necessary this function increases the capacity of the vbi_export * output buffer, then formats a string as printf() does and writes * it into the buffer. Only export modules and their callback functions * (e.g. vbi_export_link_cb) may call this function. * * If earlier vbi_export output functions failed, this function * does nothing and returns @c FALSE immediately. If the export target * is a file, the function may call vbi_export_flush() and write * the data directly into the file. * * @returns * @c FALSE if the buffer capacity was insufficent and could not be * increased, or if a write error occurred. * * @since 0.2.26 */ vbi_bool vbi_export_printf (vbi_export * e, const char * templ, ...) { va_list ap; vbi_bool success; va_start (ap, templ); success = vbi_export_vprintf (e, templ, ap); va_end (ap); return success; } /** * @param e Initialized vbi_export object. * @param buffer Output buffer. * @param buffer_size Size of the output buffer in bytes. * @param pg Page to be exported. * * This function writes the @a pg contents, converted to the format * selected with vbi_export_new(), into the @a buffer. * * You can call this function repeatedly, it does not change the state * of the vbi_export or vbi_page structure. * * @returns * On success the function returns the actual number of bytes stored in * the buffer. If @a buffer_size is too small it returns the required * size and the buffer contents are undefined. On other errors it * returns -1 and the buffer contents are undefined. * * @since 0.2.26 */ ssize_t vbi_export_mem (vbi_export * e, void * buffer, size_t buffer_size, const vbi_page * pg) { ssize_t actual; assert (NULL != e); reset_error (e); e->target = VBI_EXPORT_TARGET_MEM; e->_write = NULL; if (NULL == buffer) buffer_size = 0; e->buffer.data = buffer; e->buffer.offset = 0; e->buffer.capacity = buffer_size; e->write_error = FALSE; /* Const cast because page formatting may alter private fields of pg. */ if (e->_class->export (e, (vbi_page *) pg)) { if (VBI_EXPORT_TARGET_ALLOC == e->target) { /* buffer_size was not enough, return the actual size needed. */ /* Or was it? We may have started to write into @a buffer, so let's finish that in any case. */ memcpy (buffer, e->buffer.data, MIN (e->buffer.offset, buffer_size)); free (e->buffer.data); } if (unlikely (e->buffer.offset > (size_t) SSIZE_MAX)) { errno = EOVERFLOW; actual = -1; /* failed */ } else { actual = e->buffer.offset; } } else { if (VBI_EXPORT_TARGET_ALLOC == e->target) free (e->buffer.data); actual = -1; /* failed */ } CLEAR (e->buffer); e->target = 0; return actual; } /** * @param e Initialized vbi_export object. * @param buffer The address of the output buffer will be stored here. * @a buffer can be @c NULL. * @param buffer_size The amount of data stored in the output buffer, * in bytes, will be stored here. @a buffer_size can be @c NULL. * @param pg Page to be exported. * * This function writes the @a pg contents, converted to the format * selected with vbi_export_new(), into a newly allocated buffer. You * must free() this buffer when it is no longer needed. * * You can call this function repeatedly, it does not change the state * of the vbi_export or vbi_page structure. * * @returns * On success the function returns the address of the allocated buffer. * On failure it returns @c NULL, and @a buffer and @a buffer_size * remain unmodified. * * @since 0.2.26 */ void * vbi_export_alloc (vbi_export * e, void ** buffer, size_t * buffer_size, const vbi_page * pg) { void *result; assert (NULL != e); reset_error (e); e->target = VBI_EXPORT_TARGET_ALLOC; e->_write = NULL; CLEAR (e->buffer); e->write_error = FALSE; /* Const cast because page formatting may alter private fields of pg. */ if (e->_class->export (e, (vbi_page *) pg)) { void *data = e->buffer.data; size_t offset = e->buffer.offset; /* Let's not waste space. */ if (e->buffer.capacity - offset >= 256) { data = realloc (data, offset); if (NULL == data) data = e->buffer.data; } if (NULL != buffer) *buffer = data; if (NULL != buffer_size) *buffer_size = offset; result = data; } else { free (e->buffer.data); /* if any */ result = NULL; } CLEAR (e->buffer); e->target = 0; return result; } /** * @param e Initialized vbi_export object. * @param fp Buffered i/o stream to write to. * @param pg Page to be exported. * * This function writes the @a pg contents, converted to the format * selected with the vbi_export_new() function, into the stream @a fp. * The caller is responsible for opening and closing the stream. Don't * forget to check for i/o errors after closing. Note this function * may write incomplete files when an error occurs. * * You can call this function repeatedly, it does not change the state * of the vbi_export or vbi_page structure. * * @returns * @c FALSE on failure, @c TRUE on success. */ vbi_bool vbi_export_stdio (vbi_export * e, FILE * fp, vbi_page * pg) { vbi_bool success; int saved_errno; if (NULL == e || NULL == fp || NULL == pg) return FALSE; reset_error (e); e->target = VBI_EXPORT_TARGET_FP; e->_write = write_fp; e->_handle.fp = fp; clearerr (fp); CLEAR (e->buffer); e->write_error = FALSE; success = e->_class->export (e, pg); if (success) success = vbi_export_flush (e); saved_errno = errno; free (e->buffer.data); CLEAR (e->buffer); memset (&e->_handle, -1, sizeof (e->_handle)); e->_write = NULL; e->target = 0; errno = saved_errno; return success; } static int xclose (int fd) { unsigned int retry = 10; do { if (likely (0 == close (fd))) return 0; if (EINTR != errno) break; } while (--retry > 0); return -1; } static int xopen (const char * name, int flags, mode_t mode) { unsigned int retry = 10; do { int fd = open (name, flags, mode); if (likely (fd >= 0)) return fd; if (EINTR != errno) break; } while (--retry > 0); return -1; } /** * @param e Initialized vbi_export object. * @param name File to be created. * @param pg Page to be exported. * * Writes the @a pg contents, converted to the format selected with * vbi_export_new(), into a new file with the given @a name. When an * error occurs after the file was opened, the function deletes the file. * * You can call this function repeatedly, it does not change the state * of the vbi_export or vbi_page structure. * * @returns * @c FALSE on failure, @c TRUE on success. */ vbi_bool vbi_export_file (vbi_export * e, const char * name, vbi_page * pg) { vbi_bool success; int saved_errno; if (NULL == e || NULL == name || NULL == pg) return FALSE; reset_error (e); /* For error messages. */ e->name = name; e->target = VBI_EXPORT_TARGET_FILE; e->_write = write_fd; e->_handle.fd = xopen (name, O_WRONLY | O_CREAT | O_TRUNC, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); if (-1 == e->_handle.fd) { vbi_export_error_printf (e, _("Cannot create file '%s': %s."), name, strerror (errno)); return FALSE; } CLEAR (e->buffer); e->write_error = FALSE; success = e->_class->export (e, pg); if (success) success = vbi_export_flush (e); saved_errno = errno; free (e->buffer.data); CLEAR (e->buffer); if (!success) { struct stat st; /* There might be a race if we attempt to delete the file after closing it, so we mark it for deletion here or leave it alone when close() fails. Also delete only if @a name is regular file. */ if (0 == stat (name, &st) && S_ISREG (st.st_mode)) unlink (name); } if (-1 == xclose (e->_handle.fd)) { if (success) { saved_errno = errno; vbi_export_write_error (e); success = FALSE; } } memset (&e->_handle, -1, sizeof (e->_handle)); e->_write = NULL; e->target = 0; e->name = NULL; errno = saved_errno; return success; } /** * @param export Pointer to a initialized vbi_export object. * @param templ See printf(). * @param ... See printf(). * * Store an error description in the @a export object. Including the current * error description (to append or prepend) is safe. */ void vbi_export_error_printf(vbi_export *export, const char *templ, ...) { char buf[512]; va_list ap; if (!export) return; va_start(ap, templ); vsnprintf(buf, sizeof(buf) - 1, templ, ap); va_end(ap); reset_error(export); export->errstr = strdup(buf); } /** * @param export Pointer to a initialized vbi_export object. * * Similar to vbi_export_error_printf this function stores an error * description in the @a export object, after examining the errno * variable and choosing an appropriate message. Only export * modules call this function. */ void vbi_export_write_error(vbi_export *export) { char *t, buf[256]; if (!export) return; if (export->name) snprintf(t = buf, sizeof(buf), _("Error while writing file '%s'"), export->name); else t = _("Error while writing file"); if (errno) { vbi_export_error_printf(export, "%s: Error %d, %s", t, errno, strerror(errno)); } else { vbi_export_error_printf(export, "%s.", t); } } void _vbi_export_malloc_error (vbi_export * e) { if (!e) return; vbi_export_error_printf (e, _("Out of memory.")); } static char * module_name (vbi_export * export) { vbi_export_class *xc = export->_class; if (xc->_public->label) return _(xc->_public->label); else return xc->_public->keyword; } /** * @param export Pointer to a initialized vbi_export object. * @param keyword Name of the unknown option. * * Store an error description in the @a export object. */ void vbi_export_unknown_option(vbi_export *export, const char *keyword) { vbi_export_error_printf (export, _("Export module '%s' has no option '%s'."), module_name (export), keyword); } /** * @param export Pointer to a initialized vbi_export object. * @param keyword Name of the unknown option. * @param ... Invalid value, type depending on the option. * * Store an error description in the @a export object. */ void vbi_export_invalid_option(vbi_export *export, const char *keyword, ...) { char buf[256]; vbi_option_info *oi; if ((oi = vbi_export_option_info_keyword(export, keyword))) { va_list args; char *s; va_start(args, keyword); switch (oi->type) { case VBI_OPTION_BOOL: case VBI_OPTION_INT: case VBI_OPTION_MENU: snprintf(buf, sizeof(buf) - 1, "'%d'", va_arg(args, int)); break; case VBI_OPTION_REAL: snprintf(buf, sizeof(buf) - 1, "'%f'", va_arg(args, double)); break; case VBI_OPTION_STRING: s = va_arg(args, char *); if (s == NULL) strcpy(buf, "NULL"); else snprintf(buf, sizeof(buf) - 1, "'%s'", s); break; default: fprintf(stderr, "%s: unknown export option type %d\n", __PRETTY_FUNCTION__, oi->type); strcpy(buf, "?"); break; } va_end(args); } else buf[0] = 0; vbi_export_error_printf (export, _("Invalid argument %s for option %s of export module %s."), buf, keyword, module_name (export)); } /** * @param export Pointer to a initialized vbi_export object. * @param d If non-zero, store pointer to allocated string here. When *d * is non-zero, free(*d) the old string first. * @param s String to be duplicated. * * Helper function for export modules. * * Same as the libc strdup(), except for @a d argument and setting * the @a export error string on failure. * * @return * @c NULL on failure, pointer to malloc()ed string otherwise. */ char * vbi_export_strdup(vbi_export *export, char **d, const char *s) { char *new = strdup(s ? s : ""); if (!new) { vbi_export_error_printf (export, _("Out of memory in export module '%s'."), module_name (export)); errno = ENOMEM; return NULL; } if (d) { if (*d) free(*d); *d = new; } return new; } /** * @param export Pointer to a initialized vbi_export object. * * @return * After an export function failed, this function returns a pointer * to a more detailed error description. Do not free this string. It * remains valid until the next call of an export function. */ char * vbi_export_errstr(vbi_export *export) { if (!export || !export->errstr) return _("Unknown error."); return export->errstr; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/export.h000066400000000000000000000467331476363111200150550ustar00rootroot00000000000000/* * libzvbi - Export modules * * Copyright (C) 2001, 2002, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: export.h,v 1.18 2008-02-24 14:17:37 mschimek Exp $ */ #ifndef EXPORT_H #define EXPORT_H #include "bcd.h" /* vbi_bool */ #include "event.h" /* vbi_network */ #include "format.h" /* vbi_page */ /* Public */ #include /* FILE */ #include /* size_t, ssize_t */ /** * @ingroup Export * @brief Export module instance, an opaque object. * * Allocate with vbi_export_new(). */ typedef struct vbi_export vbi_export; /** * @ingroup Export * @brief Information about an export module. * * Although export modules can be accessed by a static keyword (see * vbi_export_new()) they are by definition opaque. The client * can list export modules for the user and manipulate them without knowing * about their availability or purpose. To do so, information * about the module is necessary, given in this structure. * * You can obtain this information with vbi_export_info_enum(). */ typedef struct vbi_export_info { /** * Unique (within this library) keyword to identify * this export module. Can be stored in configuration files. */ char * keyword; /** * Name of the export module to be shown to the user. * Can be @c NULL indicating the module shall not be listed. * Clients are encouraged to localize this with dgettext("zvbi", label). */ char * label; /** * A brief description (or @c NULL) for the user. * Clients are encouraged to localize this with dgettext("zvbi", tooltip). */ char * tooltip; /** * Description of the export format as MIME type, * for example "text/html". May be @c NULL. */ char * mime_type; /** * Suggested filename extension. Multiple strings are * possible, separated by comma. The first string is preferred. * Example: "html,htm". May be @c NULL. */ char * extension; } vbi_export_info; /** * @ingroup Export */ typedef enum { /** * A boolean value, either @c TRUE (1) or @c FALSE (0). * * * * * *
Type:int
Default:vbi_option_info.def.num
Bounds:vbi_option_info.min.num (0) ... max.num (1), * step.num (1)
Menu:NULL
*/ VBI_OPTION_BOOL = 1, /** * A signed integer value. * * * * * *
Type:int
Default:vbi_option_info.def.num
Bounds:vbi_option_info.min.num ... max.num, step.num
Menu:NULL
* When only a few discrete values rather than a range of values are permitted @p * vbi_option_info.menu points to a vector of integers. However you must still * set the option by value, not by menu index. If the value is invalid * vbi_export_option_set() may fail or pick the closest possible value instead. * * * * * *
Type:int
Default:vbi_option_info.menu.num[vbi_option_info.def.num]
Bounds:See vbi_option_info.menu.num[] for valid values
Menu:vbi_option_info.menu.num[min.num (0) ... max.num], * step.num (1)
*/ VBI_OPTION_INT, /** * A real value. * * * * * *
Type:double
Default:vbi_option_info.def.dbl
Bounds:vbi_option_info.min.dbl ... max.dbl, * step.dbl
Menu:NULL
* As with @c VBI_OPTION_INT @p vbi_option_info.menu may point to a set * of valid values: * * * * * *
Type:double
Default:vbi_option_info.menu.dbl[vbi_option.info.def.num]
Bounds:See vbi_option_info.menu.dbl[] for valid values
Menu:vbi_option_info.menu.dbl[min.num (0) ... max.num], * step.num (1)
*/ VBI_OPTION_REAL, /** * A null terminated string. * * * * * *
Type:char *
Default:vbi_option_info.def.str
Bounds:Not applicable
Menu:NULL
* As with @c VBI_OPTION_INT @p vbi_option_info.menu may point to a set * of valid strings. Note that vbi_export_option_set() always expects a * string for this kind of option, and it may accept strings which are * not in the menu. Contrast this with @c VBI_OPTION_MENU, where a menu * index is expected. * * * * * *
Type:char *
Default:vbi_option_info.menu.str[vbi_option_info.def.num]
Bounds:Not applicable
Menu:vbi_option_info.menu.str[min.num (0) ... max.num], * step.num (1)
*/ VBI_OPTION_STRING, /** * Choice between a number of named options. The value of this kind * of option is the menu index. The menu strings can be localized * with a dgettext("zvbi", menu.str[n]) call. For details see * gettext info file. * * * * * *
Type:int
Default:vbi_option_info.def.num
Bounds:vbi_option_info.min.num (0) ... max.num, * step.num (1)
Menu:vbi_option_info.menu.str[vbi_option_info.min.num ... max.num], * step.num (1). *
*/ VBI_OPTION_MENU } vbi_option_type; /** * @ingroup Export * @brief Result of an option query. */ typedef union { int num; double dbl; char * str; } vbi_option_value; /** * @ingroup Export * @brief Option menu types. */ typedef union { int * num; double * dbl; char ** str; } vbi_option_value_ptr; /** * @ingroup Export * @brief Information about an export option. * * Although export options can be accessed by a static keyword they are * by definition opaque: the client can present them to the user and * manipulate them without knowing about their presence or purpose. * To do so, some information about the option is necessary, * given in this structure. * * You can obtain this information with vbi_export_option_info_enum(). */ typedef struct { vbi_option_type type; /**< @see vbi_option_type */ /** * Unique (within the respective export module) keyword to identify * this option. Can be stored in configuration files. */ char * keyword; /** * Name of the option to be shown to the user. * This can be @c NULL to indicate this option shall not be listed. * Can be localized with dgettext("zvbi", label). */ char * label; vbi_option_value def; /**< @see vbi_option_type */ vbi_option_value min; /**< @see vbi_option_type */ vbi_option_value max; /**< @see vbi_option_type */ vbi_option_value step; /**< @see vbi_option_type */ vbi_option_value_ptr menu; /**< @see vbi_option_type */ /** * A brief description (or @c NULL) for the user. * Can be localized with dgettext("zvbi", tooltip). */ char * tooltip; } vbi_option_info; /** * @addtogroup Export * @{ */ extern vbi_export_info * vbi_export_info_enum(int index); extern vbi_export_info * vbi_export_info_keyword(const char *keyword); extern vbi_export_info * vbi_export_info_export(vbi_export *); extern vbi_export * vbi_export_new(const char *keyword, char **errstr); extern void vbi_export_delete(vbi_export *); extern vbi_option_info * vbi_export_option_info_enum(vbi_export *, int index); extern vbi_option_info * vbi_export_option_info_keyword(vbi_export *, const char *keyword); extern vbi_bool vbi_export_option_set(vbi_export *, const char *keyword, ...); extern vbi_bool vbi_export_option_get(vbi_export *, const char *keyword, vbi_option_value *value); extern vbi_bool vbi_export_option_menu_set(vbi_export *, const char *keyword, int entry); extern vbi_bool vbi_export_option_menu_get(vbi_export *, const char *keyword, int *entry); extern ssize_t vbi_export_mem (vbi_export * e, void * buffer, size_t buffer_size, const vbi_page * pg) _vbi_nonnull ((1)); /* sic */ extern void * vbi_export_alloc (vbi_export * e, void ** buffer, size_t * buffer_size, const vbi_page * pg) _vbi_nonnull ((1)); /* sic */ extern vbi_bool vbi_export_stdio(vbi_export *, FILE *fp, vbi_page *pg); extern vbi_bool vbi_export_file(vbi_export *, const char *name, vbi_page *pg); extern char * vbi_export_errstr(vbi_export *); /** @} */ /* Private */ #ifndef DOXYGEN_SHOULD_SKIP_THIS #include #include extern const char _zvbi_intl_domainname[]; #include "version.h" #include "intl-priv.h" #endif /* !DOXYGEN_SHOULD_SKIP_THIS */ typedef struct vbi_export_class vbi_export_class; /** The export target. */ enum _vbi_export_target { /** Exporting to a client supplied buffer in memory. */ VBI_EXPORT_TARGET_MEM = 1, /** Exporting to a newly allocated buffer. */ VBI_EXPORT_TARGET_ALLOC, /** Exporting to a client supplied file pointer. */ VBI_EXPORT_TARGET_FP, /** Exporting to a client supplied file descriptor. */ VBI_EXPORT_TARGET_FD, /** Exporting to a file. */ VBI_EXPORT_TARGET_FILE, }; typedef vbi_bool _vbi_export_write_fn (vbi_export * e, const void * s, size_t n_bytes); /** * @ingroup Exmod * * Structure representing an export module instance, part of the private * export module interface. * * Export modules can read, but do not normally write its fields, as * they are maintained by the public libzvbi export functions. */ struct vbi_export { /** * Points back to export module description. */ vbi_export_class * _class; char * errstr; /**< Frontend private. */ /** * If @c target is @c VBI_EXPORT_FILE the name of the file * we are writing to, as supplied by the client. Otherwise * @c NULL. This is intended for debugging and error messages. */ const char * name; /** * Generic option: Network name or @c NULL. */ char * network; /* network name or NULL */ /** * Generic option: Creator name [by default "libzvbi"] or @c NULL. */ char * creator; /** * Generic option: Reveal hidden characters. */ vbi_bool reveal; /** * The export target. Note _vbi_export_grow_buffer_space() may * change the target from TARGET_MEM to TARGET_ALLOC if the * buffer supplied by the application is too small. */ enum _vbi_export_target target; /** * If @a target is @c VBI_EXPORT_TARGET_FP or * @c VBI_EXPORT_TARGET_FD the file pointer or file descriptor * supplied by the client. If @c VBI_EXPORT_TARGET_FILE the * file descriptor of the file we opened. Otherwise undefined. * * Private field. Not to be accessed by export modules. */ union { FILE * fp; int fd; } _handle; /** * Function to write data into @a _handle. * * Private field. Not to be accessed by export modules. */ _vbi_export_write_fn * _write; /** * Output buffer. Export modules can write into this buffer * directly after ensuring sufficient capacity, and/or call * the vbi_export_putc() etc functions. Keep in mind these * functions may call realloc(), changing the @a data pointer, * and/or vbi_export_flush(), changing the @a offset. */ struct { /** * Pointer to the start of the buffer in memory. * @c NULL if @a capacity is zero. */ char * data; /** * The number of bytes written into the buffer * so far. Must be <= @c capacity. */ size_t offset; /** * Number of bytes we can store in the buffer, may be * zero. * * Call _vbi_export_grow_buffer_space() to increase the * capacity. Keep in mind this may change the @a data * pointer. */ size_t capacity; } buffer; /** A write error occurred (like ferror()). */ vbi_bool write_error; }; /** * @ingroup Exmod * * Structure describing an export module, part of the private * export module interface. One required for each module. * * Export modules must initialize these fields (except @a next, see * exp-tmpl.c for a detailed discussion) and call vbi_export_register_module() * to become accessible. */ struct vbi_export_class { vbi_export_class * next; vbi_export_info * _public; vbi_export * (* _new)(void); void (* _delete)(vbi_export *); vbi_option_info * (* option_enum)(vbi_export *, int index); vbi_bool (* option_set)(vbi_export *, const char *keyword, va_list); vbi_bool (* option_get)(vbi_export *, const char *keyword, vbi_option_value *value); vbi_bool (* export)(vbi_export *, vbi_page *pg); }; /** * @example src/exp-templ.c * @ingroup Exmod * * Template for internal export module. */ /* * Helper functions */ /* Output functions. */ extern vbi_bool _vbi_export_grow_buffer_space (vbi_export * e, size_t min_space) _vbi_nonnull ((1)); extern vbi_bool vbi_export_flush (vbi_export * e) _vbi_nonnull ((1)); extern vbi_bool vbi_export_putc (vbi_export * e, int c) _vbi_nonnull ((1)); extern vbi_bool vbi_export_write (vbi_export * e, const void * src, size_t src_size) _vbi_nonnull ((1, 2)); extern vbi_bool vbi_export_puts (vbi_export * e, const char * src) _vbi_nonnull ((1)); extern vbi_bool vbi_export_puts_iconv (vbi_export * e, const char * dst_codeset, const char * src_codeset, const char * src, unsigned long src_size, int repl_char) _vbi_nonnull ((1)); extern vbi_bool vbi_export_puts_iconv_ucs2 (vbi_export * e, const char * dst_codeset, const uint16_t * src, long src_length, int repl_char) _vbi_nonnull ((1)); extern vbi_bool vbi_export_vprintf (vbi_export * e, const char * templ, va_list ap) _vbi_nonnull ((1, 2)); extern vbi_bool vbi_export_printf (vbi_export * e, const char * templ, ...) _vbi_nonnull ((1, 2)) _vbi_format ((printf, 2, 3)); /** * @addtogroup Exmod * @{ */ extern void vbi_register_export_module(vbi_export_class *); extern void _vbi_export_malloc_error (vbi_export * e); extern void vbi_export_write_error(vbi_export *); extern void vbi_export_unknown_option(vbi_export *, const char *keyword); extern void vbi_export_invalid_option(vbi_export *, const char *keyword, ...); extern char * vbi_export_strdup(vbi_export *, char **d, const char *s); extern void vbi_export_error_printf(vbi_export *, const char *templ, ...); extern int vbi_ucs2be(void); /* Option info building */ #define VBI_OPTION_BOUNDS_INITIALIZER_(type_, def_, min_, max_, step_) \ { type_ = def_ }, { type_ = min_ }, { type_ = max_ }, { type_ = step_ } /** * Helper macro for export modules to build option lists. Use like this: * * @code * vbi_option_info myinfo = VBI_OPTION_BOOL_INITIALIZER * ("mute", N_("Switch sound on/off"), FALSE, N_("I am a tooltip")); * @endcode * * N_() marks the string for i18n, see info gettext for details. */ #define VBI_OPTION_BOOL_INITIALIZER(key_, label_, def_, tip_) \ { VBI_OPTION_BOOL, key_, label_, VBI_OPTION_BOUNDS_INITIALIZER_( \ .num, def_, 0, 1, 1), { .num = NULL }, tip_ } /** * Helper macro for export modules to build option lists. Use like this: * * @code * vbi_option_info myinfo = VBI_OPTION_INT_RANGE_INITIALIZER * ("sampling", N_("Sampling rate"), 44100, 8000, 48000, 100, NULL); * @endcode * * Here we have no tooltip (@c NULL). */ #define VBI_OPTION_INT_RANGE_INITIALIZER(key_, label_, def_, min_, \ max_, step_, tip_) { VBI_OPTION_INT, key_, label_, \ VBI_OPTION_BOUNDS_INITIALIZER_(.num, def_, min_, max_, step_), \ { .num = NULL }, tip_ } /** * Helper macro for export modules to build option lists. Use like this: * * @code * int mymenu[] = { 29, 30, 31 }; * * vbi_option_info myinfo = VBI_OPTION_INT_MENU_INITIALIZER * ("days", NULL, 1, mymenu, 3, NULL); * @endcode * * No label and tooltip (@c NULL), i. e. this option is not to be * listed in the user interface. Default is entry 1 ("30") of 3 entries. */ #define VBI_OPTION_INT_MENU_INITIALIZER(key_, label_, def_, \ menu_, entries_, tip_) { VBI_OPTION_INT, key_, label_, \ VBI_OPTION_BOUNDS_INITIALIZER_(.num, def_, 0, (entries_) - 1, 1), \ { .num = menu_ }, tip_ } /** * Helper macro for export modules to build option lists. Use like * VBI_OPTION_INT_RANGE_INITIALIZER(), just with doubles but ints. */ #define VBI_OPTION_REAL_RANGE_INITIALIZER(key_, label_, def_, min_, \ max_, step_, tip_) { VBI_OPTION_REAL, key_, label_, \ VBI_OPTION_BOUNDS_INITIALIZER_(.dbl, def_, min_, max_, step_), \ { .dbl = NULL }, tip_ } /** * Helper macro for export modules to build option lists. Use like * VBI_OPTION_INT_MENU_INITIALIZER(), just with an array of doubles but ints. */ #define VBI_OPTION_REAL_MENU_INITIALIZER(key_, label_, def_, \ menu_, entries_, tip_) { VBI_OPTION_REAL, key_, label_, \ VBI_OPTION_BOUNDS_INITIALIZER_(.num, def_, 0, (entries_) - 1, 1), \ { .dbl = menu_ }, tip_ } /** * Helper macro for export modules to build option lists. Use like this: * * @code * vbi_option_info myinfo = VBI_OPTION_STRING_INITIALIZER * ("comment", N_("Comment"), "bububaba", "Please enter a string"); * @endcode */ #define VBI_OPTION_STRING_INITIALIZER(key_, label_, def_, tip_) \ { VBI_OPTION_STRING, key_, label_, VBI_OPTION_BOUNDS_INITIALIZER_( \ .str, def_, NULL, NULL, NULL), { .str = NULL }, tip_ } /** * Helper macro for export modules to build option lists. Use like this: * * @code * char *mymenu[] = { "txt", "html" }; * * vbi_option_info myinfo = VBI_OPTION_STRING_MENU_INITIALIZER * ("extension", "Ext", 0, mymenu, 2, N_("Select an extension")); * @endcode * * Remember this is like VBI_OPTION_STRING_INITIALIZER() in the sense * that the vbi client can pass any string as option value, not just those * proposed in the menu. In contrast a plain menu option as with * VBI_OPTION_MENU_INITIALIZER() expects menu indices as input. */ #define VBI_OPTION_STRING_MENU_INITIALIZER(key_, label_, def_, \ menu_, entries_, tip_) { VBI_OPTION_STRING, key_, label_, \ VBI_OPTION_BOUNDS_INITIALIZER_(.str, def_, 0, (entries_) - 1, 1), \ { .str = menu_ }, tip_ } /** * Helper macro for export modules to build option lists. Use like this: * * @code * char *mymenu[] = { N_("Monday"), N_("Tuesday") }; * * vbi_option_info myinfo = VBI_OPTION_MENU_INITIALIZER * ("weekday", "Weekday", 0, mymenu, 2, N_("Select a weekday")); * @endcode */ #define VBI_OPTION_MENU_INITIALIZER(key_, label_, def_, menu_, \ entries_, tip_) { VBI_OPTION_MENU, key_, label_, \ VBI_OPTION_BOUNDS_INITIALIZER_(.num, def_, 0, (entries_) - 1, 1), \ { .str = (char **)(menu_) }, tip_ } /* See exp-templ.c for an example */ /** Doesn't work, sigh. */ #define VBI_AUTOREG_EXPORT_MODULE(name) /* #define VBI_AUTOREG_EXPORT_MODULE(name) \ static void vbi_autoreg_##name(void) _vbi_attribute ((constructor)); \ static void vbi_autoreg_##name(void) { \ vbi_register_export_module(&name); \ } */ /** @} */ #endif /* EXPORT_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/format.h000066400000000000000000000330511476363111200150110ustar00rootroot00000000000000/* * libzvbi -- Formatted Teletext and Closed Caption page * * Copyright (C) 2000, 2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: format.h,v 1.11 2008-02-19 00:35:19 mschimek Exp $ */ #ifndef FORMAT_H #define FORMAT_H #include "event.h" /* vbi_nuid */ #ifndef VBI_DECODER #define VBI_DECODER typedef struct vbi_decoder vbi_decoder; #endif /* Public */ #include /** * @addtogroup Page Formatted text page * @ingroup HiDec */ /** * @ingroup Page * @brief Index into the vbi_page->color_map. * * The enumerated color names * refer to the Teletext and Closed Caption base palette of eight * colors. Note however the color_map really has 40 entries for * Teletext Level 2.5+, 32 of which are redefinable, the remaining * eight are private colors of libzvbi e. g. for * navigational information. So these symbols may not necessarily * correspond to the respective color. */ /* Code depends on order, don't change. */ typedef enum { VBI_BLACK, VBI_RED, VBI_GREEN, VBI_YELLOW, VBI_BLUE, VBI_MAGENTA, VBI_CYAN, VBI_WHITE } vbi_color; /** * @ingroup Page * @brief Colormap entry: 0xAABBGGRR. libzvbi sets the alpha channel * always to 0xFF. */ typedef uint32_t vbi_rgba; /* Private */ #define VBI_RGBA(r, g, b) \ ((((r) & 0xFF) << 0) | (((g) & 0xFF) << 8) \ | (((b) & 0xFF) << 16) | (0xFFu << 24)) #define VBI_R(rgba) (((rgba) >> 0) & 0xFF) #define VBI_G(rgba) (((rgba) >> 8) & 0xFF) #define VBI_B(rgba) (((rgba) >> 16) & 0xFF) #define VBI_A(rgba) (((rgba) >> 24) & 0xFF) /* Public */ /** * @ingroup Page * @brief Defines the opacity of a vbi_char and vbi_page border. * * Teletext Level 2.5 defines a special transparent color which * permits unusual characters with transparent foreground, opaque * background. For simplicity this type of opacity has been omitted. Also * renderers shall rely on the opacity attribute and not attempt to * interpret the color value as transparency indicator. */ typedef enum { /** * This page is supposed to be overlayed onto * video, with video displayed in place of this character (or the page * border). In other words the character is a space (vbi_char->unicode = * U+0020) and the glyph background is transparent. If desired the * renderer may also fall back to VBI_SEMI_TRANSPARENT or VBI_OPAQUE * mode. For this case vbi_char->background names the color to use as * the semi-transparent or opaque background. * * VBI_TRANSPARENT_SPACE is the opacity of subtitle pages (both border and * characters, while the 'boxed' words are marked as VBI_SEMI_TRANSPARENT), * but can also occur on a mainly VBI_OPAQUE page to create a 'window' * effect. */ VBI_TRANSPARENT_SPACE, /** * Display video instead of the background color. * Here the character is not a space and shall be displayed * in vbi_char->foreground color. Only in the background of the character * video shall look through. Again the renderer may fall back to * VBI_SEMI_TRANSPARENT or VBI_OPAQUE. */ VBI_TRANSPARENT_FULL, /** * Alpha blend video into background color, the * character background becomes translucent. This is the opacity used * for 'boxed' text on an otherwise VBI_TRANSPARENT_SPACE page, typically * a subtitle or Teletext newsflash page. The renderer may fall back * to VBI_OPAQUE. */ VBI_SEMI_TRANSPARENT, /** * Display foreground and background color. Showing * foreground or background transparent instead is not recommended because * the editor may have swapped foreground and background color, then * replaced a glyph by its inverse image, so one cannot really know if * the character foreground or background will appear transparent. */ VBI_OPAQUE } vbi_opacity; /** * @ingroup Page * @brief Defines the size of a vbi_char in a vbi_page. * Double width or height characters expand into the next * column right and/or next row below. * * Scanning two rows left to right, you will find
*
 * VBI_NORMAL_SIZE | VBI_DOUBLE_WIDTH VBI_OVER_TOP | VBI_DOUBLE_HEIGHT  | VBI_DOUBLE_SIZE  VBI_OVER_TOP
 *       x         |         x             x       | VBI_DOUBLE_HEIGHT2 | VBI_DOUBLE_SIZE2 VBI_OVER_BOTTOM
 * 
* * A VBI_DOUBLE_HEIGHT2, VBI_DOUBLE_SIZE2, VBI_OVER_TOP, VBI_OVER_BOTTOM * vbi_char has the same character unicode and attributes as the top/left anchor. * Partial characters (like a single VBI_DOUBLE_HEIGHT2) will not appear, so * VBI_DOUBLE_HEIGHT2, VBI_DOUBLE_SIZE2, VBI_OVER_TOP, VBI_OVER_BOTTOM * can be safely ignored when scanning the page. */ /* Code depends on order, don't change. */ typedef enum { VBI_NORMAL_SIZE, VBI_DOUBLE_WIDTH, VBI_DOUBLE_HEIGHT, VBI_DOUBLE_SIZE, VBI_OVER_TOP, VBI_OVER_BOTTOM, VBI_DOUBLE_HEIGHT2, VBI_DOUBLE_SIZE2 } vbi_size; /** * @ingroup Page * @brief Attributed character. */ typedef struct vbi_char { unsigned underline : 1; /**< Display character underlined. */ unsigned bold : 1; /**< Display character bold. */ unsigned italic : 1; /**< Display character slanted right. */ /** * Display character or space (U+0020), one second cycle time. */ unsigned flash : 1; /** * Replace character by space (U+0020) if not revealed. * This is used for example to hide text on question & answer pages. */ unsigned conceal : 1; /** * No function yet, default is fixed spacing. */ unsigned proportional : 1; /** * This character is part of a hyperlink. Call vbi_resolve_link() * to get more information. */ unsigned link : 1; /** * Reserved for VPT link flag. */ unsigned reserved : 1; /** * Character size, see vbi_size. */ unsigned size : 8; /** * Character opacity, see vbi_opacity. Both @a foreground * and @a background color are valid independent of @a opacity. */ unsigned opacity : 8; /** * Character foreground color, a vbi_color index * into the vbi_page->color_map. */ unsigned foreground : 8; /** * Character background color, a vbi_color index * into the vbi_page->color_map. */ unsigned background : 8; /** * DRCS color look-up table offset, see vbi_page for details. */ unsigned drcs_clut_offs : 8; /** * Character code according to ISO 10646 UCS-2 (not UTF-16). * * All Closed Caption characters can be represented in Unicode, * but unfortunately not all Teletext characters. * * ETS 300 706 * Table 36 Latin National Subset Turkish, character * 0x23 "Turkish currency symbol" is not representable in Unicode, * thus translated to private code U+E800. I was unable to identify * all Arabic glyphs in Table 44 and 45 Arabic G0 and G2, so for now * these are mapped to private code U+E620 ... U+E67F and U+E720 ... * U+E77F respectively. Table 47 G1 Block Mosaic is not representable * in Unicode, translated to private code U+EE00 ... U+EE7F. That is, * the contiguous form has bit 5 (0x20) set, the separate form cleared. * Table 48 G3 "Smooth Mosaics and Line Drawing Set" is not * representable in Unicode, translated to private code * U+EF20 ... U+EF7F. * * Teletext Level 2.5+ DRCS are represented by private code * U+F000 ... U+F7FF. The 6 lsb select character 0x00 ... 0x3F * from a DRCS plane, the 5 msb select DRCS plane 0 ... 31, see * vbi_page for details. * * @bug * Some Teletext character sets contain complementary * Latin characters. For example the Greek capital letters Alpha * and Beta are re-used as Latin capital letter A and B, while a * separate code exists for Latin capital letter C. libzvbi will * not analyse the page contents, so Greek A and B are always * translated to Greek Alpha and Beta, C to Latin C, even if they * appear in a pure Latin character word. */ unsigned unicode : 16; } vbi_char; struct vbi_font_descr; /** * @ingroup Page * @brief Formatted Teletext or Closed Caption page. * * Clients can fetch pages * from the respective cache using vbi_fetch_vt_page() or * vbi_fetch_cc_page() for evaluation, display or output. Since * the page may reference other objects in cache which are locked * by the fetch functions, vbi_unref_page() must be called when done. * Note this structure is large, some 10 KB. */ typedef struct vbi_page { /** * Points back to the source context. */ vbi_decoder * vbi; /** * Identifies the network broadcasting this page. */ vbi_nuid nuid; /** * Page number, see vbi_pgno. */ /* FIXME this shouldn't be int */ int pgno; /** * Subpage number, see vbi_subno. */ /* FIXME this shouldn't be int */ int subno; /** * Number of character rows in the page. */ int rows; /** * Number of character columns in the page. */ int columns; /** * The page contents, these are @a rows x @a columns without * padding between the rows. See vbi_char for details. */ vbi_char text[1056]; /** * To speed up rendering these variables mark the rows * which actually changed since the page has been last fetched * from cache. @a y0 ... @a y1 are the first to last row changed, * inclusive, in range 0 ... @a rows - 1. @a roll indicates the * page has been vertically scrolled this number of rows, * negative numbers up (towards lower row numbers), positive * numbers down. For example -1 means row @a y0 + 1 ... @a y1 * moved to @a y0 ... @a y1 - 1, erasing row @a y1 to all spaces. * * Practically this is only used in Closed Caption roll-up * mode, otherwise all rows are always marked dirty. Clients * are free to ignore this information. */ struct { /* int x0, x1; */ int y0, y1; int roll; } dirty; /** * When a TV displays Teletext or Closed Caption * pages, only a section in the center of the screen is * actually covered by characters. The remaining space is * referred to here as 'border', which can have a color different * from the typical black. (In the Teletext specs this is referred * to as the screen color, hence the field name.) This is a * vbi_color index into the @a color_map. */ vbi_color screen_color; /** * The 'border' can also have a distinguished * opacity. Typically this will be VBI_OPAQUE, but pages intended * for overlay onto video (Teletext subtitles, newsflash, Caption * pages) will have a @a screen_opacity of VBI_TRANSPARENT_SPACE. * See vbi_opacity for details. */ vbi_opacity screen_opacity; /** * This is the color palette indexed by vbi_color in * vbi_char and elsewhere, colors defined as vbi_rgba. Note this * palette may not correspond to the vbi_color enumeration since * Teletext allows editors to redefine the entire palette. * Closed Caption and Teletext Level 1.0/1.5 pages use * entries 0 ... 7. Teletext Level 2.5/3.5 pages use entries * 0 ... 31. Navigation related text (TOP, FLOF) added by libzvbi * uses entries 32 ... 39 which are not subject to redefinition. */ vbi_rgba color_map[40]; /** * DRCS (dynamically redefinable characters) can have * two, four or sixteen different colors. Without further details, * the effective color of each pixel is given by * @code * vbi_page->color_map[drcs_clut[drcs pixel color + vbi_char->drcs_clut_offs]], * @endcode * whereby drcs_clut[0] shall be replaced by vbi_char->foreground, * drcs_clut[1] by vbi_char->background. (Renderers are supposed to convert the * drcs_clut into a private color map of the desired pixel format.) * * Practically vbi_char->drcs_clut_offs encodes the DRCS color depth * and selects between the vbi_char colors and one of two 4- or * 16-entry Color Look-Up Tables. Also the different resolution of DRCS and * the bitplane color coding is hidden to speed up rendering. */ uint8_t * drcs_clut; /* 64 entries */ /** * Pointer to DRCS data. Per definition the maximum number of DRCS * usable at the same time, i. e. on one page, is limited to 96. However the * number of DRCS defined in a Teletext data stream can be much larger. The * 32 pointers here correspond to the 32 DRCS character planes mentioned * in the vbi_char description. Each of them points to an array of character * definitions, a DRCS font. One character occupies 60 bytes or 12 x 10 pixels, * stored left to right and top to bottom. The color of each pixel (index * into @a drcs_clut) is coded in four bits stored in little endian order, * first pixel 0x0F, second pixel 0xF0 and so on. For example the first, * top/leftmost pixel can be found at * @code * vbi_page->drcs[(unicode >> 6) & 0x1F][(unicode & 0x3F) * 60] * @endcode * * Do not access DRCS data unless referenced by a vbi_char in @a text, a * segfault may result. Do not access DRCS data after calling * vbi_unref_page(), it may not be cached anymore. */ uint8_t * drcs[32]; struct { int pgno, subno; } nav_link[6]; char nav_index[64]; struct vbi_font_descr * font[2]; unsigned int double_height_lower; /* legacy */ vbi_opacity page_opacity[2]; vbi_opacity boxed_opacity[2]; } vbi_page; /* Private */ #endif /* FORMAT_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/hamm-tables.h000066400000000000000000000330251476363111200157140ustar00rootroot00000000000000/* Generated file, do not edit! */ /* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ const uint8_t _vbi_bit_reverse [256] = { 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff }; const uint8_t _vbi_hamm8_fwd [16] = { 0x15, 0x02, 0x49, 0x5e, 0x64, 0x73, 0x38, 0x2f, 0xd0, 0xc7, 0x8c, 0x9b, 0xa1, 0xb6, 0xfd, 0xea }; const int8_t _vbi_hamm8_inv [256] = { 0x01, 0xff, 0x01, 0x01, 0xff, 0x00, 0x01, 0xff, 0xff, 0x02, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x07, 0xff, 0x00, 0x01, 0xff, 0x00, 0x00, 0xff, 0x00, 0x06, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x03, 0xff, 0xff, 0x0c, 0x01, 0xff, 0x04, 0xff, 0xff, 0x07, 0x06, 0xff, 0xff, 0x07, 0xff, 0x07, 0x07, 0x07, 0x06, 0xff, 0xff, 0x05, 0xff, 0x00, 0x0d, 0xff, 0x06, 0x06, 0x06, 0xff, 0x06, 0xff, 0xff, 0x07, 0xff, 0x02, 0x01, 0xff, 0x04, 0xff, 0xff, 0x09, 0x02, 0x02, 0xff, 0x02, 0xff, 0x02, 0x03, 0xff, 0x08, 0xff, 0xff, 0x05, 0xff, 0x00, 0x03, 0xff, 0xff, 0x02, 0x03, 0xff, 0x03, 0xff, 0x03, 0x03, 0x04, 0xff, 0xff, 0x05, 0x04, 0x04, 0x04, 0xff, 0xff, 0x02, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x07, 0xff, 0x05, 0x05, 0x05, 0x04, 0xff, 0xff, 0x05, 0x06, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x03, 0xff, 0xff, 0x0c, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x09, 0x0a, 0xff, 0xff, 0x0b, 0x0a, 0x0a, 0x0a, 0xff, 0x08, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x0d, 0xff, 0xff, 0x0b, 0x0b, 0x0b, 0x0a, 0xff, 0xff, 0x0b, 0x0c, 0x0c, 0xff, 0x0c, 0xff, 0x0c, 0x0d, 0xff, 0xff, 0x0c, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x07, 0xff, 0x0c, 0x0d, 0xff, 0x0d, 0xff, 0x0d, 0x0d, 0x06, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x0d, 0xff, 0x08, 0xff, 0xff, 0x09, 0xff, 0x09, 0x09, 0x09, 0xff, 0x02, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x09, 0x08, 0x08, 0x08, 0xff, 0x08, 0xff, 0xff, 0x09, 0x08, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x03, 0xff, 0xff, 0x0c, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x09, 0x0f, 0xff, 0x0f, 0x0f, 0xff, 0x0e, 0x0f, 0xff, 0x08, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x0d, 0xff, 0xff, 0x0e, 0x0f, 0xff, 0x0e, 0x0e, 0xff, 0x0e }; static const uint8_t _vbi_hamm24_fwd_0 [256] = { 0x8b, 0x8c, 0x92, 0x95, 0xa1, 0xa6, 0xb8, 0xbf, 0xc0, 0xc7, 0xd9, 0xde, 0xea, 0xed, 0xf3, 0xf4, 0x0a, 0x0d, 0x13, 0x14, 0x20, 0x27, 0x39, 0x3e, 0x41, 0x46, 0x58, 0x5f, 0x6b, 0x6c, 0x72, 0x75, 0x09, 0x0e, 0x10, 0x17, 0x23, 0x24, 0x3a, 0x3d, 0x42, 0x45, 0x5b, 0x5c, 0x68, 0x6f, 0x71, 0x76, 0x88, 0x8f, 0x91, 0x96, 0xa2, 0xa5, 0xbb, 0xbc, 0xc3, 0xc4, 0xda, 0xdd, 0xe9, 0xee, 0xf0, 0xf7, 0x08, 0x0f, 0x11, 0x16, 0x22, 0x25, 0x3b, 0x3c, 0x43, 0x44, 0x5a, 0x5d, 0x69, 0x6e, 0x70, 0x77, 0x89, 0x8e, 0x90, 0x97, 0xa3, 0xa4, 0xba, 0xbd, 0xc2, 0xc5, 0xdb, 0xdc, 0xe8, 0xef, 0xf1, 0xf6, 0x8a, 0x8d, 0x93, 0x94, 0xa0, 0xa7, 0xb9, 0xbe, 0xc1, 0xc6, 0xd8, 0xdf, 0xeb, 0xec, 0xf2, 0xf5, 0x0b, 0x0c, 0x12, 0x15, 0x21, 0x26, 0x38, 0x3f, 0x40, 0x47, 0x59, 0x5e, 0x6a, 0x6d, 0x73, 0x74, 0x03, 0x04, 0x1a, 0x1d, 0x29, 0x2e, 0x30, 0x37, 0x48, 0x4f, 0x51, 0x56, 0x62, 0x65, 0x7b, 0x7c, 0x82, 0x85, 0x9b, 0x9c, 0xa8, 0xaf, 0xb1, 0xb6, 0xc9, 0xce, 0xd0, 0xd7, 0xe3, 0xe4, 0xfa, 0xfd, 0x81, 0x86, 0x98, 0x9f, 0xab, 0xac, 0xb2, 0xb5, 0xca, 0xcd, 0xd3, 0xd4, 0xe0, 0xe7, 0xf9, 0xfe, 0x00, 0x07, 0x19, 0x1e, 0x2a, 0x2d, 0x33, 0x34, 0x4b, 0x4c, 0x52, 0x55, 0x61, 0x66, 0x78, 0x7f, 0x80, 0x87, 0x99, 0x9e, 0xaa, 0xad, 0xb3, 0xb4, 0xcb, 0xcc, 0xd2, 0xd5, 0xe1, 0xe6, 0xf8, 0xff, 0x01, 0x06, 0x18, 0x1f, 0x2b, 0x2c, 0x32, 0x35, 0x4a, 0x4d, 0x53, 0x54, 0x60, 0x67, 0x79, 0x7e, 0x02, 0x05, 0x1b, 0x1c, 0x28, 0x2f, 0x31, 0x36, 0x49, 0x4e, 0x50, 0x57, 0x63, 0x64, 0x7a, 0x7d, 0x83, 0x84, 0x9a, 0x9d, 0xa9, 0xae, 0xb0, 0xb7, 0xc8, 0xcf, 0xd1, 0xd6, 0xe2, 0xe5, 0xfb, 0xfc }; static const uint8_t _vbi_hamm24_fwd_1 [256] = { 0x00, 0x89, 0x8a, 0x03, 0x8b, 0x02, 0x01, 0x88, 0x01, 0x88, 0x8b, 0x02, 0x8a, 0x03, 0x00, 0x89, 0x02, 0x8b, 0x88, 0x01, 0x89, 0x00, 0x03, 0x8a, 0x03, 0x8a, 0x89, 0x00, 0x88, 0x01, 0x02, 0x8b, 0x03, 0x8a, 0x89, 0x00, 0x88, 0x01, 0x02, 0x8b, 0x02, 0x8b, 0x88, 0x01, 0x89, 0x00, 0x03, 0x8a, 0x01, 0x88, 0x8b, 0x02, 0x8a, 0x03, 0x00, 0x89, 0x00, 0x89, 0x8a, 0x03, 0x8b, 0x02, 0x01, 0x88, 0x08, 0x81, 0x82, 0x0b, 0x83, 0x0a, 0x09, 0x80, 0x09, 0x80, 0x83, 0x0a, 0x82, 0x0b, 0x08, 0x81, 0x0a, 0x83, 0x80, 0x09, 0x81, 0x08, 0x0b, 0x82, 0x0b, 0x82, 0x81, 0x08, 0x80, 0x09, 0x0a, 0x83, 0x0b, 0x82, 0x81, 0x08, 0x80, 0x09, 0x0a, 0x83, 0x0a, 0x83, 0x80, 0x09, 0x81, 0x08, 0x0b, 0x82, 0x09, 0x80, 0x83, 0x0a, 0x82, 0x0b, 0x08, 0x81, 0x08, 0x81, 0x82, 0x0b, 0x83, 0x0a, 0x09, 0x80, 0x09, 0x80, 0x83, 0x0a, 0x82, 0x0b, 0x08, 0x81, 0x08, 0x81, 0x82, 0x0b, 0x83, 0x0a, 0x09, 0x80, 0x0b, 0x82, 0x81, 0x08, 0x80, 0x09, 0x0a, 0x83, 0x0a, 0x83, 0x80, 0x09, 0x81, 0x08, 0x0b, 0x82, 0x0a, 0x83, 0x80, 0x09, 0x81, 0x08, 0x0b, 0x82, 0x0b, 0x82, 0x81, 0x08, 0x80, 0x09, 0x0a, 0x83, 0x08, 0x81, 0x82, 0x0b, 0x83, 0x0a, 0x09, 0x80, 0x09, 0x80, 0x83, 0x0a, 0x82, 0x0b, 0x08, 0x81, 0x01, 0x88, 0x8b, 0x02, 0x8a, 0x03, 0x00, 0x89, 0x00, 0x89, 0x8a, 0x03, 0x8b, 0x02, 0x01, 0x88, 0x03, 0x8a, 0x89, 0x00, 0x88, 0x01, 0x02, 0x8b, 0x02, 0x8b, 0x88, 0x01, 0x89, 0x00, 0x03, 0x8a, 0x02, 0x8b, 0x88, 0x01, 0x89, 0x00, 0x03, 0x8a, 0x03, 0x8a, 0x89, 0x00, 0x88, 0x01, 0x02, 0x8b, 0x00, 0x89, 0x8a, 0x03, 0x8b, 0x02, 0x01, 0x88, 0x01, 0x88, 0x8b, 0x02, 0x8a, 0x03, 0x00, 0x89 }; static const uint8_t _vbi_hamm24_fwd_2 [4] = { 0x00, 0x0a, 0x0b, 0x01 }; const int8_t _vbi_hamm24_inv_par [3][256] = { { 0x00, 0x21, 0x22, 0x03, 0x23, 0x02, 0x01, 0x20, 0x24, 0x05, 0x06, 0x27, 0x07, 0x26, 0x25, 0x04, 0x25, 0x04, 0x07, 0x26, 0x06, 0x27, 0x24, 0x05, 0x01, 0x20, 0x23, 0x02, 0x22, 0x03, 0x00, 0x21, 0x26, 0x07, 0x04, 0x25, 0x05, 0x24, 0x27, 0x06, 0x02, 0x23, 0x20, 0x01, 0x21, 0x00, 0x03, 0x22, 0x03, 0x22, 0x21, 0x00, 0x20, 0x01, 0x02, 0x23, 0x27, 0x06, 0x05, 0x24, 0x04, 0x25, 0x26, 0x07, 0x27, 0x06, 0x05, 0x24, 0x04, 0x25, 0x26, 0x07, 0x03, 0x22, 0x21, 0x00, 0x20, 0x01, 0x02, 0x23, 0x02, 0x23, 0x20, 0x01, 0x21, 0x00, 0x03, 0x22, 0x26, 0x07, 0x04, 0x25, 0x05, 0x24, 0x27, 0x06, 0x01, 0x20, 0x23, 0x02, 0x22, 0x03, 0x00, 0x21, 0x25, 0x04, 0x07, 0x26, 0x06, 0x27, 0x24, 0x05, 0x24, 0x05, 0x06, 0x27, 0x07, 0x26, 0x25, 0x04, 0x00, 0x21, 0x22, 0x03, 0x23, 0x02, 0x01, 0x20, 0x28, 0x09, 0x0a, 0x2b, 0x0b, 0x2a, 0x29, 0x08, 0x0c, 0x2d, 0x2e, 0x0f, 0x2f, 0x0e, 0x0d, 0x2c, 0x0d, 0x2c, 0x2f, 0x0e, 0x2e, 0x0f, 0x0c, 0x2d, 0x29, 0x08, 0x0b, 0x2a, 0x0a, 0x2b, 0x28, 0x09, 0x0e, 0x2f, 0x2c, 0x0d, 0x2d, 0x0c, 0x0f, 0x2e, 0x2a, 0x0b, 0x08, 0x29, 0x09, 0x28, 0x2b, 0x0a, 0x2b, 0x0a, 0x09, 0x28, 0x08, 0x29, 0x2a, 0x0b, 0x0f, 0x2e, 0x2d, 0x0c, 0x2c, 0x0d, 0x0e, 0x2f, 0x0f, 0x2e, 0x2d, 0x0c, 0x2c, 0x0d, 0x0e, 0x2f, 0x2b, 0x0a, 0x09, 0x28, 0x08, 0x29, 0x2a, 0x0b, 0x2a, 0x0b, 0x08, 0x29, 0x09, 0x28, 0x2b, 0x0a, 0x0e, 0x2f, 0x2c, 0x0d, 0x2d, 0x0c, 0x0f, 0x2e, 0x29, 0x08, 0x0b, 0x2a, 0x0a, 0x2b, 0x28, 0x09, 0x0d, 0x2c, 0x2f, 0x0e, 0x2e, 0x0f, 0x0c, 0x2d, 0x0c, 0x2d, 0x2e, 0x0f, 0x2f, 0x0e, 0x0d, 0x2c, 0x28, 0x09, 0x0a, 0x2b, 0x0b, 0x2a, 0x29, 0x08 }, { 0x00, 0x29, 0x2a, 0x03, 0x2b, 0x02, 0x01, 0x28, 0x2c, 0x05, 0x06, 0x2f, 0x07, 0x2e, 0x2d, 0x04, 0x2d, 0x04, 0x07, 0x2e, 0x06, 0x2f, 0x2c, 0x05, 0x01, 0x28, 0x2b, 0x02, 0x2a, 0x03, 0x00, 0x29, 0x2e, 0x07, 0x04, 0x2d, 0x05, 0x2c, 0x2f, 0x06, 0x02, 0x2b, 0x28, 0x01, 0x29, 0x00, 0x03, 0x2a, 0x03, 0x2a, 0x29, 0x00, 0x28, 0x01, 0x02, 0x2b, 0x2f, 0x06, 0x05, 0x2c, 0x04, 0x2d, 0x2e, 0x07, 0x2f, 0x06, 0x05, 0x2c, 0x04, 0x2d, 0x2e, 0x07, 0x03, 0x2a, 0x29, 0x00, 0x28, 0x01, 0x02, 0x2b, 0x02, 0x2b, 0x28, 0x01, 0x29, 0x00, 0x03, 0x2a, 0x2e, 0x07, 0x04, 0x2d, 0x05, 0x2c, 0x2f, 0x06, 0x01, 0x28, 0x2b, 0x02, 0x2a, 0x03, 0x00, 0x29, 0x2d, 0x04, 0x07, 0x2e, 0x06, 0x2f, 0x2c, 0x05, 0x2c, 0x05, 0x06, 0x2f, 0x07, 0x2e, 0x2d, 0x04, 0x00, 0x29, 0x2a, 0x03, 0x2b, 0x02, 0x01, 0x28, 0x30, 0x19, 0x1a, 0x33, 0x1b, 0x32, 0x31, 0x18, 0x1c, 0x35, 0x36, 0x1f, 0x37, 0x1e, 0x1d, 0x34, 0x1d, 0x34, 0x37, 0x1e, 0x36, 0x1f, 0x1c, 0x35, 0x31, 0x18, 0x1b, 0x32, 0x1a, 0x33, 0x30, 0x19, 0x1e, 0x37, 0x34, 0x1d, 0x35, 0x1c, 0x1f, 0x36, 0x32, 0x1b, 0x18, 0x31, 0x19, 0x30, 0x33, 0x1a, 0x33, 0x1a, 0x19, 0x30, 0x18, 0x31, 0x32, 0x1b, 0x1f, 0x36, 0x35, 0x1c, 0x34, 0x1d, 0x1e, 0x37, 0x1f, 0x36, 0x35, 0x1c, 0x34, 0x1d, 0x1e, 0x37, 0x33, 0x1a, 0x19, 0x30, 0x18, 0x31, 0x32, 0x1b, 0x32, 0x1b, 0x18, 0x31, 0x19, 0x30, 0x33, 0x1a, 0x1e, 0x37, 0x34, 0x1d, 0x35, 0x1c, 0x1f, 0x36, 0x31, 0x18, 0x1b, 0x32, 0x1a, 0x33, 0x30, 0x19, 0x1d, 0x34, 0x37, 0x1e, 0x36, 0x1f, 0x1c, 0x35, 0x1c, 0x35, 0x36, 0x1f, 0x37, 0x1e, 0x1d, 0x34, 0x30, 0x19, 0x1a, 0x33, 0x1b, 0x32, 0x31, 0x18 }, { 0x3f, 0x0e, 0x0d, 0x3c, 0x0c, 0x3d, 0x3e, 0x0f, 0x0b, 0x3a, 0x39, 0x08, 0x38, 0x09, 0x0a, 0x3b, 0x0a, 0x3b, 0x38, 0x09, 0x39, 0x08, 0x0b, 0x3a, 0x3e, 0x0f, 0x0c, 0x3d, 0x0d, 0x3c, 0x3f, 0x0e, 0x09, 0x38, 0x3b, 0x0a, 0x3a, 0x0b, 0x08, 0x39, 0x3d, 0x0c, 0x0f, 0x3e, 0x0e, 0x3f, 0x3c, 0x0d, 0x3c, 0x0d, 0x0e, 0x3f, 0x0f, 0x3e, 0x3d, 0x0c, 0x08, 0x39, 0x3a, 0x0b, 0x3b, 0x0a, 0x09, 0x38, 0x08, 0x39, 0x3a, 0x0b, 0x3b, 0x0a, 0x09, 0x38, 0x3c, 0x0d, 0x0e, 0x3f, 0x0f, 0x3e, 0x3d, 0x0c, 0x3d, 0x0c, 0x0f, 0x3e, 0x0e, 0x3f, 0x3c, 0x0d, 0x09, 0x38, 0x3b, 0x0a, 0x3a, 0x0b, 0x08, 0x39, 0x3e, 0x0f, 0x0c, 0x3d, 0x0d, 0x3c, 0x3f, 0x0e, 0x0a, 0x3b, 0x38, 0x09, 0x39, 0x08, 0x0b, 0x3a, 0x0b, 0x3a, 0x39, 0x08, 0x38, 0x09, 0x0a, 0x3b, 0x3f, 0x0e, 0x0d, 0x3c, 0x0c, 0x3d, 0x3e, 0x0f, 0x1f, 0x2e, 0x2d, 0x1c, 0x2c, 0x1d, 0x1e, 0x2f, 0x2b, 0x1a, 0x19, 0x28, 0x18, 0x29, 0x2a, 0x1b, 0x2a, 0x1b, 0x18, 0x29, 0x19, 0x28, 0x2b, 0x1a, 0x1e, 0x2f, 0x2c, 0x1d, 0x2d, 0x1c, 0x1f, 0x2e, 0x29, 0x18, 0x1b, 0x2a, 0x1a, 0x2b, 0x28, 0x19, 0x1d, 0x2c, 0x2f, 0x1e, 0x2e, 0x1f, 0x1c, 0x2d, 0x1c, 0x2d, 0x2e, 0x1f, 0x2f, 0x1e, 0x1d, 0x2c, 0x28, 0x19, 0x1a, 0x2b, 0x1b, 0x2a, 0x29, 0x18, 0x28, 0x19, 0x1a, 0x2b, 0x1b, 0x2a, 0x29, 0x18, 0x1c, 0x2d, 0x2e, 0x1f, 0x2f, 0x1e, 0x1d, 0x2c, 0x1d, 0x2c, 0x2f, 0x1e, 0x2e, 0x1f, 0x1c, 0x2d, 0x29, 0x18, 0x1b, 0x2a, 0x1a, 0x2b, 0x28, 0x19, 0x1e, 0x2f, 0x2c, 0x1d, 0x2d, 0x1c, 0x1f, 0x2e, 0x2a, 0x1b, 0x18, 0x29, 0x19, 0x28, 0x2b, 0x1a, 0x2b, 0x1a, 0x19, 0x28, 0x18, 0x29, 0x2a, 0x1b, 0x1f, 0x2e, 0x2d, 0x1c, 0x2c, 0x1d, 0x1e, 0x2f } }; static const uint8_t _vbi_hamm24_inv_d1_d4 [64] = { 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x02, 0x03, 0x04, 0x05, 0x04, 0x05, 0x06, 0x07, 0x06, 0x07, 0x08, 0x09, 0x08, 0x09, 0x0a, 0x0b, 0x0a, 0x0b, 0x0c, 0x0d, 0x0c, 0x0d, 0x0e, 0x0f, 0x0e, 0x0f, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x02, 0x03, 0x04, 0x05, 0x04, 0x05, 0x06, 0x07, 0x06, 0x07, 0x08, 0x09, 0x08, 0x09, 0x0a, 0x0b, 0x0a, 0x0b, 0x0c, 0x0d, 0x0c, 0x0d, 0x0e, 0x0f, 0x0e, 0x0f }; static const int32_t _vbi_hamm24_inv_err [64] = { 0x00000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000002, 0x00000004, 0x00000008, 0x00000000, 0x00000010, 0x00000020, 0x00000040, 0x00000080, 0x00000100, 0x00000200, 0x00000400, 0x00000000, 0x00000800, 0x00001000, 0x00002000, 0x00004000, 0x00008000, 0x00010000, 0x00020000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000 }; zvbi-0.2.44/src/hamm.c000066400000000000000000000075071476363111200144450ustar00rootroot00000000000000/* * libzvbi -- Error correction functions * * Copyright (C) 2001, 2002, 2003, 2004, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: hamm.c,v 1.11 2013-07-10 11:37:08 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include /* CHAR_BIT */ #include "hamm.h" #include "hamm-tables.h" /** * @ingroup Error * * @param p Array of unsigned bytes. * @param n Size of array. * * Of each byte of the array, changes the most significant * bit to make the number of set bits odd. * * @since 0.2.12 */ void vbi_par (uint8_t * p, unsigned int n) { while (n-- > 0) { uint8_t c = *p; /* if 0 == (inv_par[] & 32) change msb of *p. */ *p++ = c ^ (128 & ~(_vbi_hamm24_inv_par[0][c] << 2)); } } /** * @ingroup Error * @param p Array of unsigned bytes. * @param n Size of array. * * Tests the parity and clears the most significant bit of * each byte of the array. * * @return * A negative value if any byte of the array had even * parity (sum of bits modulo 2 is 0). * * @since 0.2.12 */ int vbi_unpar (uint8_t * p, unsigned int n) { int r = 0; while (n-- > 0) { uint8_t c = *p; /* if 0 == (inv_par[] & 32) set msb of r. */ r |= ~ _vbi_hamm24_inv_par[0][c] << (sizeof (int) * CHAR_BIT - 1 - 5); *p++ = c & 127; } return r; } /** * @ingroup Error * @param p A Hamming 24/18 protected 24 bit word will be stored here, * last significant byte first, lsb first transmitted. * @param c Integer between 0 ... 1 << 18 - 1. * * Encodes an 18 bit word with Hamming 24/18 protection * as specified in ETS 300 706, Section 8.3. * * @since 0.2.27 */ void vbi_ham24p (uint8_t * p, unsigned int c) { unsigned int D5_D11; unsigned int D12_D18; unsigned int P5, P6; unsigned int Byte_0; Byte_0 = (_vbi_hamm24_fwd_0 [(c >> 0) & 0xFF] ^ _vbi_hamm24_fwd_1 [(c >> 8) & 0xFF] ^ _vbi_hamm24_fwd_2 [(c >> 16) & 0x03]); p[0] = Byte_0; D5_D11 = (c >> 4) & 0x7F; D12_D18 = (c >> 11) & 0x7F; P5 = 0x80 & ~(_vbi_hamm24_inv_par[0][D12_D18] << 2); p[1] = D5_D11 | P5; P6 = 0x80 & ((_vbi_hamm24_inv_par[0][Byte_0] ^ _vbi_hamm24_inv_par[0][D5_D11]) << 2); p[2] = D12_D18 | P6; } /** * @ingroup Error * @param p Pointer to a Hamming 24/18 protected 24 bit word, * last significant byte first, lsb first transmitted. * * Decodes a Hamming 24/18 protected byte triplet * as specified in ETS 300 706, Section 8.3. * * @return * Triplet data bits D18 [msb] ... D1 [lsb] or a negative value * if the triplet contained uncorrectable errors. * * @since 0.2.12 */ int vbi_unham24p (const uint8_t * p) { unsigned int D1_D4; unsigned int D5_D11; unsigned int D12_D18; unsigned int ABCDEF; int32_t d; D1_D4 = _vbi_hamm24_inv_d1_d4[p[0] >> 2]; D5_D11 = p[1] & 0x7F; D12_D18 = p[2] & 0x7F; d = D1_D4 | (D5_D11 << 4) | (D12_D18 << 11); ABCDEF = (_vbi_hamm24_inv_par[0][p[0]] ^ _vbi_hamm24_inv_par[1][p[1]] ^ _vbi_hamm24_inv_par[2][p[2]]); /* Correct single bit error, set MSB on double bit error. */ return d ^ (int) _vbi_hamm24_inv_err[ABCDEF]; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/hamm.h000066400000000000000000000116241476363111200144450ustar00rootroot00000000000000/* * libzvbi -- Error correction functions * * Copyright (C) 2001, 2002, 2003, 2004, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: hamm.h,v 1.16 2013-07-10 11:37:13 mschimek Exp $ */ #ifndef __ZVBI_HAMM_H__ #define __ZVBI_HAMM_H__ #include /* uintN_t */ #include "macros.h" VBI_BEGIN_DECLS /* Public */ extern const uint8_t _vbi_bit_reverse [256]; extern const uint8_t _vbi_hamm8_fwd [16]; extern const int8_t _vbi_hamm8_inv [256]; extern const int8_t _vbi_hamm24_inv_par [3][256]; /** * @addtogroup Error Error correction functions * @ingroup Raw * @brief Helper functions to decode sliced VBI data. * @{ */ /** * @param c Unsigned byte. * * Reverses the bits of the argument. * * @returns * Data bits 0 [msb] ... 7 [lsb]. * * @since 0.2.12 */ _vbi_inline unsigned int vbi_rev8 (unsigned int c) { return _vbi_bit_reverse[(uint8_t) c]; } /** * @param c Unsigned 16 bit word. * * Reverses (or "reflects") the bits of the argument. * * @returns * Data bits 0 [msb] ... 15 [lsb]. * * @since 0.2.12 */ _vbi_inline unsigned int vbi_rev16 (unsigned int c) { return _vbi_bit_reverse[(uint8_t) c] * 256 + _vbi_bit_reverse[(uint8_t)(c >> 8)]; } /** * @param p Pointer to a 16 bit word, last significant * byte first. * * Reverses (or "reflects") the bits of the argument. * * @returns * Data bits 0 [msb] ... 15 [lsb]. * * @since 0.2.12 */ _vbi_inline unsigned int vbi_rev16p (const uint8_t * p) { return _vbi_bit_reverse[p[0]] * 256 + _vbi_bit_reverse[p[1]]; } /** * @param c Unsigned byte. * * @returns * Changes the most significant bit of the byte * to make the number of set bits odd. * * @since 0.2.12 */ _vbi_inline unsigned int vbi_par8 (unsigned int c) { c &= 255; /* if 0 == (inv_par[] & 32) change bit 7 of c. */ c ^= 128 & ~(_vbi_hamm24_inv_par[0][c] << 2); return c; } /** * @param c Unsigned byte. * * @returns * If the byte has odd parity (sum of bits modulo 2 is 1) the * byte AND 127, otherwise a negative value. * * @since 0.2.12 */ _vbi_inline int vbi_unpar8 (unsigned int c) { /* Disabled until someone finds a reliable way to test for cmov support at compile time. */ #if 0 int r = c & 127; /* This saves cache flushes and an explicit branch. */ __asm__ (" testb %1,%1\n" " cmovp %2,%0\n" : "+&a" (r) : "c" (c), "rm" (-1)); return r; #endif if (_vbi_hamm24_inv_par[0][(uint8_t) c] & 32) { return c & 127; } else { /* The idea is to OR results together to find a parity error in a sequence, rather than a test and branch on each byte. */ return -1; } } extern void vbi_par (uint8_t * p, unsigned int n); extern int vbi_unpar (uint8_t * p, unsigned int n); /** * @param c Integer between 0 ... 15. * * Encodes a nibble with Hamming 8/4 protection * as specified in EN 300 706, Section 8.2. * * @returns * Hamming encoded unsigned byte, lsb first transmitted. * * @since 0.2.12 */ _vbi_inline unsigned int vbi_ham8 (unsigned int c) { return _vbi_hamm8_fwd[c & 15]; } /** * @param c Hamming 8/4 protected byte, lsb first transmitted. * * Decodes a Hamming 8/4 protected byte * as specified in EN 300 706, Section 8.2. * * @returns * Data bits (D4 [msb] ... D1 [lsb]) or a negative * value if the byte contained uncorrectable errors. * * @since 0.2.12 */ _vbi_inline int vbi_unham8 (unsigned int c) { return _vbi_hamm8_inv[(uint8_t) c]; } /** * @param p Pointer to a Hamming 8/4 protected 16 bit word, * last significant byte first, lsb first transmitted. * * Decodes a Hamming 8/4 protected byte pair * as specified in EN 300 706, Section 8.2. * * @returns * Data bits D4 [msb] ... D1 of first byte and D4 ... D1 [lsb] * of second byte, or a negative value if any of the bytes * contained uncorrectable errors. * * @since 0.2.12 */ _vbi_inline int vbi_unham16p (const uint8_t * p) { return ((int) _vbi_hamm8_inv[p[0]]) | (((int) _vbi_hamm8_inv[p[1]]) << 4); } extern void vbi_ham24p (uint8_t * p, unsigned int c); extern int vbi_unham24p (const uint8_t * p) _vbi_pure; /** @} */ /* Private */ VBI_END_DECLS #endif /* __ZVBI_HAMM_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/hammgen.c000066400000000000000000000244661476363111200151420ustar00rootroot00000000000000/* * libzvbi -- Error correction tables generator * * Copyright (C) 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: hammgen.c,v 1.2 2008-07-26 06:22:00 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include static uint8_t _vbi_bit_reverse [256]; static uint8_t _vbi_hamm8_fwd [16]; static int8_t _vbi_hamm8_inv [256]; static uint8_t _vbi_hamm24_fwd_0 [256]; static uint8_t _vbi_hamm24_fwd_1 [256]; static uint8_t _vbi_hamm24_fwd_2 [4]; /* Should be uint8_t, is int8_t for compatibility with earlier versions of libzvbi. */ static int8_t _vbi_hamm24_inv_par [3][256]; static uint8_t _vbi_hamm24_inv_d1_d4 [64]; static int32_t _vbi_hamm24_inv_err [64]; static void generate_hamm24_inv_tables (void) { unsigned int i; /* EN 300 706 section 8.3, Hamming 24/18 inverse */ /* D1_D4 = _vbi_hamm24_inv_d1_d4[Byte_0 >> 2]; D5_D11 = Byte_1 & 0x7F; D12_D18 = Byte_2 & 0x7F; d = D1_D4 | (D5_D11 << 4) | (D12_D18 << 11); ABCDEF = ( _vbi_hamm24_inv_par[0][Byte_0] ^ _vbi_hamm24_inv_par[1][Byte_1] ^ _vbi_hamm24_inv_par[2][Byte_2]); // Correct single bit error, set bit 31 on double bit error. d ^= _vbi_hamm24_inv_err[ABCDEF]; This algorithm is based on an idea by R. Gancarz in AleVT 1.5.1. */ for (i = 0; i < 256; ++i) { unsigned int D1, D2, D3, D4, D5, D6, D7, D8; unsigned int D9, D10, D11, D12, D13, D14, D15, D16; unsigned int D17, D18; unsigned int P1, P2, P3, P4, P5, P6; unsigned int A, B, C, D, E, F; unsigned int j; P1 = (i >> 0) & 1; P2 = (i >> 1) & 1; D1 = (i >> 2) & 1; P3 = (i >> 3) & 1; D2 = (i >> 4) & 1; D3 = (i >> 5) & 1; D4 = (i >> 6) & 1; P4 = (i >> 7) & 1; D5 = (i >> 0) & 1; D6 = (i >> 1) & 1; D7 = (i >> 2) & 1; D8 = (i >> 3) & 1; D9 = (i >> 4) & 1; D10 = (i >> 5) & 1; D11 = (i >> 6) & 1; P5 = (i >> 7) & 1; D12 = (i >> 0) & 1; D13 = (i >> 1) & 1; D14 = (i >> 2) & 1; D15 = (i >> 3) & 1; D16 = (i >> 4) & 1; D17 = (i >> 5) & 1; D18 = (i >> 6) & 1; P6 = (i >> 7) & 1; _vbi_hamm24_inv_d1_d4 [i >> 2] = (+ (D1 << 0) + (D2 << 1) + (D3 << 2) + (D4 << 3)); A = P1 ^ D1 ^ D2 ^ D4; B = P2 ^ D1 ^ D3 ^ D4; C = P3 ^ D2 ^ D3 ^ D4; D = P4; E = 0; F = P1 ^ P2 ^ D1 ^ P3 ^ D2 ^ D3 ^ D4 ^ P4; _vbi_hamm24_inv_par [0][i] = (+ (A << 0) + (B << 1) + (C << 2) + (D << 3) + (E << 4) + (F << 5)); A = D5 ^ D7 ^ D9 ^ D11; B = D6 ^ D7 ^ D10 ^ D11; C = D8 ^ D9 ^ D10 ^ D11; D = D5 ^ D6 ^ D7 ^ D8 ^ D9 ^ D10 ^ D11; E = P5; F = D5 ^ D6 ^ D7 ^ D8 ^ D9 ^ D10 ^ D11 ^ P5; _vbi_hamm24_inv_par [1][i] = (+ (A << 0) + (B << 1) + (C << 2) + (D << 3) + (E << 4) + (F << 5)); A = D12 ^ D14 ^ D16 ^ D18; B = D13 ^ D14 ^ D17 ^ D18; C = D15 ^ D16 ^ D17 ^ D18; D = 0; E = D12 ^ D13 ^ D14 ^ D15 ^ D16 ^ D17 ^ D18; F = D12 ^ D13 ^ D14 ^ D15 ^ D16 ^ D17 ^ D18 ^ P6; _vbi_hamm24_inv_par [2][i] = (+ (A << 0) + (B << 1) + (C << 2) + (D << 3) + (E << 4) + (F << 5)); /* For compatibility with earlier versions of libzvbi. */ _vbi_hamm24_inv_par [2][i] ^= 0x3F; } for (i = 0; i < 64; ++i) { unsigned int ii; /* Undo the ^ 0x3F in _vbi_hamm24_inv_par[2][]. */ ii = i ^ 0x3F; if (0 == ii) { /* No errors. */ _vbi_hamm24_inv_err[ii] = 0; } else if (0 == (ii & 0x1F) && 0x20 == (ii & 0x20)) { /* Ignore error in P6. */ _vbi_hamm24_inv_err[ii] = 0; } else if (0x20 == (i & 0x20)) { /* Double bit error. */ _vbi_hamm24_inv_err[ii] = 1 << 31; } else { unsigned int Byte_0_3 = 1 << ((ii & 0x1F) - 1); /* Single bit error. */ if (Byte_0_3 >= (1 << 23)) { /* Invalid. (Error in P6 or outside the 24 bit word.) */ _vbi_hamm24_inv_err[ii] = 1 << 31; continue; } _vbi_hamm24_inv_err[ii] = (+ ((Byte_0_3 & 0x000004) >> (3 - 1)) + ((Byte_0_3 & 0x000070) >> (5 - 2)) + ((Byte_0_3 & 0x007F00) >> (9 - 5)) + ((Byte_0_3 & 0x7F0000) >> (17 - 12))); } } } static void generate_hamm24_fwd_tables (void) { unsigned int i; /* EN 300 706 section 8.3, Hamming 24/18 forward */ /* Byte_0 = ( _vbi_hamm24_fwd_0 [(d >> 0) & 0xFF] ^ _vbi_hamm24_fwd_1 [(d >> 8) & 0xFF] ^ _vbi_hamm24_fwd_2 [d >> 16]; D5_D11 = (d >> 4) & 0x7F; D12_D18 = (d >> 11) & 0x7F; P5 = 1 ^ parity (D12_D18); Byte_1 = D5_D11 | (P5 << 7); P6 = 1 ^ parity (Byte_0) ^ parity (Byte_1) ^ parity (D12_D18); P6 = 1 ^ parity (Byte_0) ^ parity (D5_D11) ^ P5 ^ parity (D12_D18); P6 = parity (Byte_0) ^ parity (D5_D11); Byte_2 = D12_D18 | (P6 << 7); */ for (i = 0; i < 256; ++i) { unsigned int D1, D2, D3, D4, D5, D6, D7, D8; unsigned int P1, P2, P3, P4; D1 = (i >> 0) & 1; D2 = (i >> 1) & 1; D3 = (i >> 2) & 1; D4 = (i >> 3) & 1; D5 = (i >> 4) & 1; D6 = (i >> 5) & 1; D7 = (i >> 6) & 1; D8 = (i >> 7) & 1; P1 = 1 ^ D1 ^ D2 ^ D4 ^ D5 ^ D7; P2 = 1 ^ D1 ^ D3 ^ D4 ^ D6 ^ D7; P3 = 1 ^ D2 ^ D3 ^ D4 ^ D8; P4 = 1 ^ D5 ^ D6 ^ D7 ^ D8; _vbi_hamm24_fwd_0[i] = (+ (P1 << 0) + (P2 << 1) + (D1 << 2) + (P3 << 3) + (D2 << 4) + (D3 << 5) + (D4 << 6) + (P4 << 7)); } for (i = 0; i < 256; ++i) { unsigned int D9, D10, D11, D12, D13, D14, D15, D16; unsigned int P1, P2, P3, P4; D9 = (i >> 0) & 1; D10 = (i >> 1) & 1; D11 = (i >> 2) & 1; D12 = (i >> 3) & 1; D13 = (i >> 4) & 1; D14 = (i >> 5) & 1; D15 = (i >> 6) & 1; D16 = (i >> 7) & 1; P1 = D9 ^ D11 ^ D12 ^ D14 ^ D16; P2 = D10 ^ D11 ^ D13 ^ D14; P3 = D9 ^ D10 ^ D11 ^ D15 ^ D16; P4 = D9 ^ D10 ^ D11; _vbi_hamm24_fwd_1[i] = (+ (P1 << 0) + (P2 << 1) + (P3 << 3) + (P4 << 7)); } for (i = 0; i < 4; ++i) { unsigned int D17, D18; unsigned int P1, P2, P3, P4; D17 = (i >> 0) & 1; D18 = (i >> 1) & 1; P1 = D18; P2 = D17 ^ D18; P3 = D17 ^ D18; P4 = 0; _vbi_hamm24_fwd_2[i] = (+ (P1 << 0) + (P2 << 1) + (P3 << 3) + (P4 << 7)); } } static void generate_hamm8_tables (void) { unsigned int i; /* EN 300 706 section 8.2, Hamming 8/4 */ /* Uncorrectable double bit errors. */ memset (_vbi_hamm8_inv, -1, sizeof (_vbi_hamm8_inv)); for (i = 0; i < 16; ++i) { unsigned int D1, D2, D3, D4; unsigned int P1, P2, P3, P4; unsigned int j; unsigned int c; D1 = (i >> 0) & 1; D2 = (i >> 1) & 1; D3 = (i >> 2) & 1; D4 = (i >> 3) & 1; P1 = 1 ^ D1 ^ D3 ^ D4; P2 = 1 ^ D1 ^ D2 ^ D4; P3 = 1 ^ D1 ^ D2 ^ D3; P4 = 1 ^ P1 ^ D1 ^ P2 ^ D2 ^ P3 ^ D3 ^ D4; c = (+ (P1 << 0) + (D1 << 1) + (P2 << 2) + (D2 << 3) + (P3 << 4) + (D3 << 5) + (P4 << 6) + (D4 << 7)); _vbi_hamm8_fwd[i] = c; _vbi_hamm8_inv[c] = i; for (j = 0; j < 8; ++j) { /* Single bit errors. */ _vbi_hamm8_inv[c ^ (1 << j)] = i; } } } static void generate_tables (void) { unsigned int i; for (i = 0; i < 256; ++i) { _vbi_bit_reverse[i] = (+ ((i & 0x80) >> 7) + ((i & 0x40) >> 5) + ((i & 0x20) >> 3) + ((i & 0x10) >> 1) + ((i & 0x08) << 1) + ((i & 0x04) << 3) + ((i & 0x02) << 5) + ((i & 0x01) << 7)); } generate_hamm8_tables (); generate_hamm24_fwd_tables (); generate_hamm24_inv_tables (); } static void print_tables (void) { unsigned int i; printf ("\ /* Generated file, do not edit! */\n\n\ /* This library is free software; you can redistribute it and/or\n\ modify it under the terms of the GNU Library General Public\n\ License as published by the Free Software Foundation; either\n\ version 2 of the License, or (at your option) any later version.\n\ \n\ This library is distributed in the hope that it will be useful,\n\ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n\ Library General Public License for more details.\n\ \n\ You should have received a copy of the GNU Library General Public\n\ License along with this library; if not, write to the\n\ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n\ Boston, MA 02110-1301 USA. */\n\n"); #define PRINT(type, array) \ do { \ const unsigned int n_elements = sizeof (array); \ \ printf ("%s\n%s [%u] = {", \ #type, #array, n_elements); \ for (i = 0; i < n_elements; ++i) { \ printf ("%s0x%02x%s", \ 0 == (i % 8) ? "\n\t" : "", \ array[i] & 0xFF, \ i < (n_elements - 1) ? ", " : ""); \ } \ printf ("\n};\n\n"); \ } while (0) PRINT (const uint8_t, _vbi_bit_reverse); PRINT (const uint8_t, _vbi_hamm8_fwd); PRINT (const int8_t, _vbi_hamm8_inv); PRINT (static const uint8_t, _vbi_hamm24_fwd_0); PRINT (static const uint8_t, _vbi_hamm24_fwd_1); PRINT (static const uint8_t, _vbi_hamm24_fwd_2); printf ("const int8_t\n_vbi_hamm24_inv_par [3][256] " "= {\n\t{"); for (i = 0; i < 3; ++i) { unsigned int j; for (j = 0; j < 256; ++j) { printf ("%s0x%02x%s", 0 == (j % 8) ? "\n\t\t" : "", _vbi_hamm24_inv_par[i][j], j < 255 ? ", " : ""); } printf ("\n\t}%s", i < 2 ? ", {" : "\n};\n\n"); } PRINT (static const uint8_t, _vbi_hamm24_inv_d1_d4); printf ("static const int32_t\n_vbi_hamm24_inv_err [64] = {"); for (i = 0; i < 64; ++i) { printf ("%s0x%08x%s", 0 == (i % 4) ? "\n\t" : "", _vbi_hamm24_inv_err[i], i < 63 ? ", " : ""); } printf ("\n};\n"); } int main (void) { generate_tables (); print_tables (); exit (EXIT_SUCCESS); return 0; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/idl_demux.c000066400000000000000000000233001476363111200154620ustar00rootroot00000000000000/* * libzvbi -- Teletext Independent Data Line packet demultiplexer * * Copyright (C) 2005 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: idl_demux.c,v 1.11 2013-07-10 11:37:23 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "misc.h" #include "hamm.h" /* vbi_unham8() */ #include "idl_demux.h" static void init_crc16_table (uint16_t table[256], unsigned int poly) { unsigned int i; for (i = 0; i < 256; ++i) { unsigned int crc; unsigned int val; unsigned int j; crc = 0; val = i; for (j = 0; j < 8; ++j) { crc = (crc >> 1) ^ (poly & ((1 & ~(val ^ crc)) - 1)); val >>= 1; } table[i] = crc; } } /* EN 300 708 section 6.5 IDL Format A */ #define FT_HAVE_RI (1 << 1) #define FT_HAVE_CI (1 << 2) #define FT_HAVE_DL (1 << 3) #define RI_PACKET_REPEATS (1 << 7) /* 6.5.7.1 Dummy bytes */ #define SKIP_DUMMY_BYTES 1 static uint16_t idl_a_crc_table [256]; static vbi_bool idl_a_demux_feed (vbi_idl_demux * dx, const uint8_t buffer[42], int ft) { uint8_t buf[40]; uint8_t histbyte; uint8_t dupecount; int ial; /* interpretation and address length */ unsigned int spa_length; int spa; /* service packet address */ unsigned int ri; /* repeat indicator */ unsigned int ci; /* continuity indicator */ unsigned int dl; /* data length */ unsigned int crc; unsigned int flags; unsigned int i; unsigned int j; ial = vbi_unham8 (buffer[3]); if (ial < 0) { return FALSE; } spa_length = (unsigned int) ial & 7; if (7 == spa_length) /* reserved */ return TRUE; spa = 0; for (i = 0; i < spa_length; ++i) spa |= vbi_unham8 (buffer[4 + i]) << (4 * i); if (spa < 0) { return FALSE; } if (spa != dx->address) return TRUE; ri = 0; if (ft & FT_HAVE_RI) { ri = buffer[4 + i++]; } crc = 0; for (j = 4 + i; j < 42; ++j) { crc = (crc >> 8) ^ idl_a_crc_table[(crc & 0xFF) ^ buffer[j]]; } if (ft & FT_HAVE_CI) { ci = buffer[4 + i++]; } else { ci = crc & 0xFF; crc ^= ci | (ci << 8); } if (0 != crc) { if (0 == (ri & RI_PACKET_REPEATS)) { /* Packet is corrupt and won't repeat. */ dx->ci = -1; dx->ri = -1; dx->flags |= VBI_IDL_DATA_LOST; return FALSE; } else { /* Try again. */ dx->ri = ri + 1; return FALSE; } } if (dx->ri >= 0) { if (0 != ((ri ^ dx->ri) & 0xF)) { /* Repeat packet(s) lost. */ dx->ci = -1; dx->ri = -1; dx->flags |= VBI_IDL_DATA_LOST; if (0 != (ri & 0xF)) { /* Discard repeat packet. */ return TRUE; } } } else if (0 != (ri & 0xF)) { /* Discard repeat packet. */ return TRUE; } if (dx->ci >= 0) { if (0 != ((ci ^ dx->ci) & 0xFF)) { /* Packet(s) lost. */ dx->flags |= VBI_IDL_DATA_LOST; } } histbyte = ci; dupecount = 0; dx->ci = ci + 1; if (ft & FT_HAVE_DL) { dl = buffer[4 + i++] & 0x3F; dl = MIN (dl, 36 - i); } else { dl = 36 - i; } j = 0; while (dl-- > 0) { unsigned int t; t = buffer[4 + i++]; if (SKIP_DUMMY_BYTES) { if ((t == 0 || t == 0xff) && t == histbyte) dupecount++; else { histbyte = t; if (dupecount == 7) { /* 6.5.7.1 Skip dummy byte after 8 consecutive bytes of 0x00 or 0xFF. */ dupecount = 0; continue; } dupecount = 0; } } buf[j++] = t; } flags = dx->flags | (ial & VBI_IDL_DEPENDENT); dx->flags &= ~VBI_IDL_DATA_LOST; return dx->callback (dx, buf, j, dx->flags, dx->user_data); } /* EN 300 708 section 6.8 IDL Format B */ static vbi_bool idl_b_demux_feed (vbi_idl_demux * dx, const uint8_t buffer[42], int ft) { /* TODO */ dx = dx; buffer = buffer; ft = ft; return FALSE; } /* EN 300 708 section 6.6 IDL Datavideo format */ static vbi_bool datavideo_demux_feed (vbi_idl_demux * dx, const uint8_t buffer[42]) { /* TODO */ dx = dx; buffer = buffer; return FALSE; } /* EN 300 708 section 6.7 IDL Low bit rate audio */ static vbi_bool audetel_demux_feed (vbi_idl_demux * dx, const uint8_t buffer[42]) { /* TODO */ dx = dx; buffer = buffer; return FALSE; } static vbi_bool lbra_demux_feed (vbi_idl_demux * dx, const uint8_t buffer[42]) { /* TODO */ dx = dx; buffer = buffer; return FALSE; } /** * @param dx IDL demultiplexer allocated with vbi_idl_a_demux_new(). * * Resets the IDL demux context, useful for example after a channel * change. * * @since 0.2.14 */ void vbi_idl_demux_reset (vbi_idl_demux * dx) { assert (NULL != dx); dx->ci = -1; dx->ri = -1; } /** * @param dx IDL demultiplexer allocated with vbi_idl_a_demux_new(). * @param buffer Teletext packet (last 42 bytes, i. e. without clock * run-in and framing code), as in struct vbi_sliced. * * This function takes a stream of Teletext packets, filters out packets * of the desired data channel and address and calls the output * function given to vbi_idl_a_demux_new() when new user data is available. * * @returns * FALSE if the packet contained uncorrectable errors. * * @since 0.2.14 */ vbi_bool vbi_idl_demux_feed (vbi_idl_demux * dx, const uint8_t buffer[42]) { int channel; int designation; int ft; /* format type */ assert (NULL != dx); assert (NULL != buffer); channel = vbi_unham8 (buffer[0]); designation = vbi_unham8 (buffer[1]); if ((channel | designation) < 0) return FALSE; if (15 != designation /* packet 30 or 31 */ || channel != dx->channel) return TRUE; switch (dx->format) { case _VBI_IDL_FORMAT_A: if ((ft = vbi_unham8 (buffer[2])) < 0) return FALSE; if (0 == (ft & 1)) return idl_a_demux_feed (dx, buffer, ft); else return TRUE; case _VBI_IDL_FORMAT_B: if ((ft = vbi_unham8 (buffer[2])) < 0) return FALSE; if (1 == (ft & 3)) return idl_b_demux_feed (dx, buffer, ft); else return TRUE; case _VBI_IDL_FORMAT_DATAVIDEO: return datavideo_demux_feed (dx, buffer); case _VBI_IDL_FORMAT_AUDETEL: /* 6.7.2 */ return audetel_demux_feed (dx, buffer); case _VBI_IDL_FORMAT_LBRA: /* 6.7.3 */ return lbra_demux_feed (dx, buffer); default: assert (0); } } /** * @param dx IDL demultiplexer allocated with vbi_idl_a_demux_new(). * @param sliced Sliced VBI data. * @param n_lines Number of lines in the @a sliced array. * * This function works like vbi_idl_demux_feed() but operates * on sliced VBI data and filters out @c VBI_SLICED_TELETEXT_B_625. * * @returns * FALSE if any Teletext lines contained uncorrectable errors. * * @since 0.2.26 */ vbi_bool vbi_idl_demux_feed_frame (vbi_idl_demux * dx, const vbi_sliced * sliced, unsigned int n_lines) { const vbi_sliced *end; assert (NULL != dx); assert (NULL != sliced); for (end = sliced + n_lines; sliced < end; ++sliced) { if (sliced->id & VBI_SLICED_TELETEXT_B_625) { if (!vbi_idl_demux_feed (dx, sliced->data)) return FALSE; } } return TRUE; } /** @internal */ void _vbi_idl_demux_destroy (vbi_idl_demux * dx) { assert (NULL != dx); CLEAR (*dx); } /** @internal */ vbi_bool _vbi_idl_demux_init (vbi_idl_demux * dx, _vbi_idl_format format, unsigned int channel, unsigned int address, vbi_idl_demux_cb * callback, void * user_data) { assert (NULL != dx); assert (NULL != callback); if (channel >= (1 << 4)) return FALSE; switch (format) { case _VBI_IDL_FORMAT_A: if (address >= (1 << 24)) return FALSE; if (0 == idl_a_crc_table[1]) { /* x16 + x9 + x7 + x4 + 1 */ init_crc16_table (idl_a_crc_table, 0x8940); } break; case _VBI_IDL_FORMAT_DATAVIDEO: case _VBI_IDL_FORMAT_B: case _VBI_IDL_FORMAT_AUDETEL: case _VBI_IDL_FORMAT_LBRA: /* TODO */ break; default: assert (0); } dx->format = format; dx->channel = channel; dx->address = address; vbi_idl_demux_reset (dx); dx->callback = callback; dx->user_data = user_data; return TRUE; } /** * @param dx IDL demultiplexer allocated with * vbi_idl_a_demux_new(), can be @c NULL. * * Frees all resources associated with @a dx. * * @since 0.2.14 */ void vbi_idl_demux_delete (vbi_idl_demux * dx) { if (NULL == dx) return; _vbi_idl_demux_destroy (dx); vbi_free (dx); } /** * @param channel Filter out packets of this channel. * @param address Filter out packets with this service data address. * @param callback Function to be called by vbi_idl_demux_feed() when * new data is available. * @param user_data User pointer passed through to @a callback function. * * Allocates a new Independent Data Line format A (EN 300 708 section 6.5) * demultiplexer. * * @returns * Pointer to newly allocated IDL demultiplexer which must be * freed with vbi_idl_demux_delete() when done. @c NULL on failure * (out of memory). * * @since 0.2.14 */ vbi_idl_demux * vbi_idl_a_demux_new (unsigned int channel, unsigned int address, vbi_idl_demux_cb * callback, void * user_data) { vbi_idl_demux *dx; assert (NULL != callback); if (!(dx = vbi_malloc (sizeof (*dx)))) { return NULL; } if (!_vbi_idl_demux_init (dx, _VBI_IDL_FORMAT_A, channel, address, callback, user_data)) { vbi_free (dx); dx = NULL; } return dx; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/idl_demux.h000066400000000000000000000111241476363111200154700ustar00rootroot00000000000000/* * libzvbi -- Teletext Independent Data Line packet demultiplexer * * Copyright (C) 2005 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: idl_demux.h,v 1.10 2008-02-24 14:17:32 mschimek Exp $ */ #ifndef __ZVBI_IDL_DEMUX_H__ #define __ZVBI_IDL_DEMUX_H__ #include /* FILE */ #include /* uint8_t */ #include "macros.h" #include "sliced.h" VBI_BEGIN_DECLS /* Public */ /** * @addtogroup IDLDemux Teletext IDL packet demultiplexer * @ingroup LowDec * @brief Functions to decode data transmissions in Teletext * Independent Data Line packets (EN 300 708 section 6). * @{ */ /** * @brief IDL demultiplexer context. * * The contents of this structure are private. * * Call vbi_idl_demux_new() to allocate an IDL * demultiplexer context. */ typedef struct _vbi_idl_demux vbi_idl_demux; /** @} */ /** * @addtogroup IDLDemux Teletext IDL packet demultiplexer * @name vbi_idl_demux_cb flags * @{ */ /** * Data was lost (not received or uncorrectable) between the current and * previous vbi_idl_demux_feed() call. */ #define VBI_IDL_DATA_LOST (1 << 0) /** * IDL Format A flag (EN 300 708 section 6.5.2): The data may require * the use of data in other channels or with other service packet * addresses as defined by the application. */ #define VBI_IDL_DEPENDENT (1 << 3) /** @} */ /** * @addtogroup IDLDemux Teletext IDL packet demultiplexer * @{ */ /** * @param dx IDL demultiplexer returned by * vbi_idl_a_demux_new() and given to vbi_idl_demux_feed(). * @param buffer Pointer to received user data. * @param n_bytes Number of bytes in the buffer. Can be @c 0 if * the decoded packet did not contain user data. * @param flags @c VBI_IDL_DATA_LOST, @c VBI_IDL_DEPENDENT. * @param user_data User pointer passed to vbi_idl_demux_new(). * * The vbi_idl_demux_feed() function calls a function of this type * after successfully decoding an IDL packet. * * @returns * FALSE to abort vbi_idl_demux_feed() and return FALSE. */ typedef vbi_bool vbi_idl_demux_cb (vbi_idl_demux * dx, const uint8_t * buffer, unsigned int n_bytes, unsigned int flags, void * user_data); extern void vbi_idl_demux_reset (vbi_idl_demux * dx) _vbi_nonnull ((1)); extern vbi_bool vbi_idl_demux_feed (vbi_idl_demux * dx, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_idl_demux_feed_frame (vbi_idl_demux * dx, const vbi_sliced * sliced, unsigned int n_lines) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern void vbi_idl_demux_delete (vbi_idl_demux * dx); extern vbi_idl_demux * vbi_idl_a_demux_new (unsigned int channel, unsigned int address, vbi_idl_demux_cb * callback, void * user_data) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_alloc _vbi_nonnull ((3)) #endif ; /** @} */ /* Private */ /** @internal */ #define _VBI_IDL_FORMAT_A (1 << 0) #define _VBI_IDL_FORMAT_B (1 << 1) #define _VBI_IDL_FORMAT_DATAVIDEO (1 << 2) #define _VBI_IDL_FORMAT_AUDETEL (1 << 3) #define _VBI_IDL_FORMAT_LBRA (1 << 4) /** @internal */ typedef unsigned int _vbi_idl_format; /** * @internal */ struct _vbi_idl_demux { _vbi_idl_format format; /** Filter out packets of this channel, with this address. */ int channel; int address; /** Expected next continuity indicator. */ int ci; /** Expected next repeat indicator. */ int ri; unsigned int flags; vbi_idl_demux_cb * callback; void * user_data; }; extern void _vbi_idl_demux_destroy (vbi_idl_demux * dx) _vbi_nonnull ((1)); extern vbi_bool _vbi_idl_demux_init (vbi_idl_demux * dx, _vbi_idl_format format, unsigned int channel, unsigned int address, vbi_idl_demux_cb * callback, void * user_data) _vbi_nonnull ((1, 5)); VBI_END_DECLS #endif /* __ZVBI_IDL_DEMUX_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/inout.c000066400000000000000000000550101476363111200146510ustar00rootroot00000000000000/* * libzvbi -- VBI device interfaces * * Copyright (C) 2002, 2004 Michael H. Schimek * Copyright (C) 2003, 2004 Tom Zoerner * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: inout.c,v 1.19 2008-02-19 00:35:20 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include /* open() */ #include /* close(), mmap(), munmap(), gettimeofday() */ #ifdef HAVE_SYS_IOCTL_H #include /* ioctl() */ #endif #ifdef HAVE_SYS_MMAN_H #include /* mmap(), munmap() */ #endif #include /* struct timeval */ #include #include #ifdef HAVE_WINSOCK2_H #include #endif #include "misc.h" #include "inout.h" /* Preliminary hack for tests. */ vbi_bool vbi_capture_force_read_mode = FALSE; /** * @addtogroup Device VBI capture device interface * @ingroup Raw * @brief Platform independent interface to VBI capture device drivers */ /** * @param capture Initialized vbi_capture context. * @param data Store the raw vbi data here. Use vbi_capture_parameters() to * determine the buffer size. * @param timestamp On success the capture instant in seconds and fractions * since 1970-01-01 00:00 of the video frame will be stored here. * @param timeout Wait timeout, will be read only. * * Read a raw vbi frame from the capture device. * * @return * -1 on error, examine @c errno for details. The function also fails if * vbi data is not available in raw format. 0 on timeout, 1 on success. */ int vbi_capture_read_raw(vbi_capture *capture, void *data, double *timestamp, struct timeval *timeout) { vbi_capture_buffer buffer, *bp = &buffer; int r; assert (capture != NULL); assert (timestamp != NULL); assert (timeout != NULL); buffer.data = data; if ((r = capture->read(capture, &bp, NULL, timeout)) > 0) *timestamp = buffer.timestamp; return r; } /** * @param capture Initialized vbi capture context. * @param data Stores the sliced vbi data here. Use vbi_capture_parameters() to * determine the buffer size. * @param lines Stores number of vbi lines decoded and stored in @a data, * which can be zero, here. * @param timestamp On success the capture instant in seconds and fractions * since 1970-01-01 00:00 will be stored here. * @param timeout Wait timeout, will be read only. * * Read a sliced vbi frame, that is an array of vbi_sliced structures, * from the capture device. * * Note: it's generally more efficient to use vbi_capture_pull_sliced() * instead, as that one may avoid having to copy sliced data into the * given buffer (e.g. for the VBI proxy) * * @return * -1 on error, examine @c errno for details. 0 on timeout, 1 on success. */ int vbi_capture_read_sliced(vbi_capture *capture, vbi_sliced *data, int *lines, double *timestamp, struct timeval *timeout) { vbi_capture_buffer buffer, *bp = &buffer; int r; assert (capture != NULL); assert (lines != NULL); assert (timestamp != NULL); assert (timeout != NULL); buffer.data = data; if ((r = capture->read(capture, NULL, &bp, timeout)) > 0) { *lines = ((unsigned int) buffer.size) / sizeof(vbi_sliced); *timestamp = buffer.timestamp; } return r; } /** * @param capture Initialized vbi capture context. * @param raw_data Stores the raw vbi data here. Use vbi_capture_parameters() * to determine the buffer size. * @param sliced_data Stores the sliced vbi data here. Use * vbi_capture_parameters() to determine the buffer size. * @param lines Stores number of vbi lines decoded and stored in @a data, * which can be zero, here. * @param timestamp On success the capture instant in seconds and fractions * since 1970-01-01 00:00 will be stored here. * @param timeout Wait timeout, will be read only. * * Read a raw vbi frame from the capture device, decode to sliced data * and also read the sliced vbi frame, that is an array of vbi_sliced * structures, from the capture device. * * Note: depending on the driver, captured raw data may have to be copied * from the capture buffer into the given buffer (e.g. for v4l2 streams which * use memory mapped buffers.) It's generally more efficient to use one of * the vbi_capture_pull() interfaces, especially if you don't require access * to raw data at all. * * @return * -1 on error, examine @c errno for details. The function also fails if * vbi data is not available in raw format. 0 on timeout, 1 on success. */ int vbi_capture_read(vbi_capture *capture, void *raw_data, vbi_sliced *sliced_data, int *lines, double *timestamp, struct timeval *timeout) { vbi_capture_buffer rbuffer, *rbp = &rbuffer; vbi_capture_buffer sbuffer, *sbp = &sbuffer; int r; assert (capture != NULL); assert (lines != NULL); assert (timestamp != NULL); assert (timeout != NULL); rbuffer.data = raw_data; sbuffer.data = sliced_data; if ((r = capture->read(capture, &rbp, &sbp, timeout)) > 0) { *lines = ((unsigned int) sbuffer.size) / sizeof(vbi_sliced); *timestamp = sbuffer.timestamp; } return r; } /** * @param capture Initialized vbi capture context. * @param buffer Store pointer to a vbi_capture_buffer here. * @param timeout Wait timeout, will be read only. * * Read a raw vbi frame from the capture device, returning a * pointer to the image in @a buffer->data, which has @a buffer->size. * The data remains valid until the next * vbi_capture_pull_raw() call and must be read only. * * @return * -1 on error, examine @c errno for details. The function also fails * if vbi data is not available in raw format. 0 on timeout, 1 on success. */ int vbi_capture_pull_raw(vbi_capture *capture, vbi_capture_buffer **buffer, struct timeval *timeout) { assert (capture != NULL); assert (buffer != NULL); assert (timeout != NULL); *buffer = NULL; return capture->read(capture, buffer, NULL, timeout); } /** * @param capture Initialized vbi capture context. * @param buffer Store pointer to a vbi_capture_buffer here. * @param timeout Wait timeout, will be read only. * * Read a sliced vbi frame, that is an array of vbi_sliced, * from the capture device, returning a pointer to the array as * @a buffer->data. @a buffer->size is the size of the array, that is * the number of lines decoded, which can be zero, times the size * of structure vbi_sliced. The data remains valid until the * next vbi_capture_pull_sliced() call and must be read only. * * @return * -1 on error, examine @c errno for details. 0 on timeout, 1 on success. */ int vbi_capture_pull_sliced(vbi_capture *capture, vbi_capture_buffer **buffer, struct timeval *timeout) { assert (capture != NULL); assert (buffer != NULL); assert (timeout != NULL); *buffer = NULL; return capture->read(capture, NULL, buffer, timeout); } /** * @param capture Initialized vbi capture context. * @param raw_buffer Store pointer to a vbi_capture_buffer here. * @param sliced_buffer Store pointer to a vbi_capture_buffer here. * @param timeout Wait timeout, will be read only. * * Read a raw vbi frame from the capture device and decode to sliced * data. Both raw and sliced data is returned, a pointer to the raw image * as raw_buffer->data and a pointer to an array of vbi_sliced as * sliced_buffer->data. Note sliced_buffer->size is the size of the array * in bytes. That is the number of lines decoded, which can be zero, * times the size of the vbi_sliced structure. * * The raw and sliced data remains valid * until the next vbi_capture_pull() call and must be read only. * * @return * -1 on error, examine @c errno for details. The function also fails * if vbi data is not available in raw format. 0 on timeout, 1 on success. */ int vbi_capture_pull(vbi_capture *capture, vbi_capture_buffer **raw_buffer, vbi_capture_buffer **sliced_buffer, struct timeval *timeout) { assert (capture != NULL); assert (timeout != NULL); if (raw_buffer) *raw_buffer = NULL; if (sliced_buffer) *sliced_buffer = NULL; return capture->read(capture, raw_buffer, sliced_buffer, timeout); } /** * @param capture Initialized vbi capture context. * * Describe the captured data. Raw vbi frames consist of * vbi_raw_decoder.count[0] + vbi_raw_decoder.count[1] lines in * vbi_raw_decoder.sampling_format, each vbi_raw_decoder.bytes_per_line. * Sliced vbi arrays consist of zero to * vbi_raw_decoder.count[0] + vbi_raw_decoder.count[1] vbi_sliced * structures. * * @return * Pointer to a vbi_raw_decoder structure, read only. **/ vbi_raw_decoder * vbi_capture_parameters(vbi_capture *capture) { assert (capture != NULL); return capture->parameters(capture); } /** * @param capture Initialized vbi capture context. * @param reset @c TRUE to clear all previous services before adding * new ones (by invoking vbi_raw_decoder_reset() at the appropriate * time.) * @param commit @c TRUE to apply all previously added services to * the device; when doing subsequent calls of this function, * commit should be set @c TRUE for the last call. Reading data * cannot continue before changes were commited (because capturing * has to be suspended to allow resizing the VBI image.) Note this * flag is ignored when using the VBI proxy. * @param services This must point to a set of @ref VBI_SLICED_ * symbols describing the * data services to be decoded. On return the services actually * decodable will be stored here. See vbi_raw_decoder_add() * for details. If you want to capture raw data only, set to * @c VBI_SLICED_VBI_525, @c VBI_SLICED_VBI_625 or both. * @param strict Will be passed to vbi_raw_decoder_add(). * @param errorstr If not @c NULL this function stores a pointer to an error * description here. You must free() this string when no longer needed. * * Add and/or remove one or more services to an already initialized capture * context. Can be used to dynamically change the set of active services. * Internally the function will restart parameter negotiation with the * VBI device driver and then call vbi_raw_decoder_add_services(). * You may call vbi_raw_decoder_reset() before using this function * to rebuild your service mask from scratch. Note that the number of * VBI lines may change with this call (even if a negative result is * returned) so you have to check the size of your buffers. * * @return * Bitmask of supported services among those requested (not including * previously added services), 0 upon errors. */ unsigned int vbi_capture_update_services(vbi_capture *capture, vbi_bool reset, vbi_bool commit, unsigned int services, int strict, char ** errorstr) { assert (capture != NULL); return capture->update_services(capture, reset, commit, services, strict, errorstr); } /** * @param capture Initialized vbi capture context, can be @c NULL. * * @return * The file descriptor used to read from the device. If not * applicable (e.g. when using the proxy) or the @a capture context is * invalid -1 will be returned. */ int vbi_capture_fd(vbi_capture *capture) { if (capture && (capture->get_fd != NULL)) return capture->get_fd(capture); else return -1; } /** * @internal */ void vbi_capture_set_log_fp (vbi_capture * capture, FILE * fp) { assert (NULL != capture); capture->sys_log_fp = fp; } /** * @param capture Initialized vbi capture context. * * @brief Queries the capture device for the current norm * * This function is intended to allow the application to check for * asynchronous norm changes, i.e. by a different application using * the same device. * * @return * Value 625 for PAL/SECAM norms, 525 for NTSC; * 0 if unknown, -1 on error. */ int vbi_capture_get_scanning(vbi_capture *capture) { if (capture && (capture->get_scanning != NULL)) return capture->get_scanning(capture); else return -1; } /** * @param capture Initialized vbi capture context. * * After a channel change this function should be used to discard all * VBI data in intermediate buffers which may still originate from the * previous channel. */ void vbi_capture_flush(vbi_capture *capture) { assert (capture != NULL); if (capture->flush != NULL) { capture->flush(capture); } } /** * @param capture Initialized vbi capture context. * @param p_dev_video Path to a video device (e.g. /dev/video) which * refers to the same hardware as the VBI device which is used for * capturing. Note: only useful for old video4linux drivers which * don't support norm queries through VBI devices. * * @brief Set path to video device for TV norm queries * * @return * Returns @c TRUE if the configuration option and parameters are * supported; else @c FALSE. */ vbi_bool vbi_capture_set_video_path(vbi_capture *capture, const char * p_dev_video) { assert (capture != NULL); if (capture->set_video_path != NULL) return capture->set_video_path(capture, p_dev_video); else return FALSE; } /** * @param capture Initialized vbi capture context. * * @brief Query properties of the capture device file handle */ VBI_CAPTURE_FD_FLAGS vbi_capture_get_fd_flags(vbi_capture *capture) { assert (capture != NULL); if (capture->get_fd_flags != NULL) return capture->get_fd_flags(capture); else return 0; } /** * @param capture Initialized vbi capture context, can be @c NULL. * * Free all resources associated with the @a capture context. */ void vbi_capture_delete(vbi_capture *capture) { if (capture) capture->_delete(capture); } static __inline__ void timeval_subtract (struct timeval * delta, const struct timeval * tv1, const struct timeval * tv2) { if (tv1->tv_usec < tv2->tv_usec) { delta->tv_sec = tv1->tv_sec - tv2->tv_sec - 1; delta->tv_usec = 1000000 + tv1->tv_usec - tv2->tv_usec; } else { delta->tv_sec = tv1->tv_sec - tv2->tv_sec; delta->tv_usec = tv1->tv_usec - tv2->tv_usec; } } /** * @internal * * @param timeout Timeout value given to select, will be reduced by the * difference since start time. * @param tv_start Actual time before select() was called * * @brief Substract time spent waiting in select from a given * max. timeout struct * * This functions is intended for functions which call select() repeatedly * with a given overall timeout. After each select() call the time already * spent in waiting has to be substracted from the timeout. (Note that we don't * use the Linux select(2) feature to return the time not slept in the timeout * struct, because that's not portable.) * * @return * No direct return; modifies timeout value in the struct pointed to by the * second pointer argument as described above. */ void vbi_capture_io_update_timeout (struct timeval * timeout, const struct timeval * tv_start) { struct timeval delta; struct timeval tv_stop; int errno_saved; errno_saved = errno; gettimeofday(&tv_stop, NULL); errno = errno_saved; /* first calculate difference between start and current time */ timeval_subtract (&delta, &tv_stop, tv_start); if ((delta.tv_sec | delta.tv_usec) < 0) { delta.tv_sec = 0; delta.tv_usec = 0; } else { /* substract delta from the given max. timeout */ timeval_subtract (timeout, timeout, &delta); /* check if timeout was underrun -> set rest timeout to zero */ if ((timeout->tv_sec | timeout->tv_usec) < 0) { timeout->tv_sec = 0; timeout->tv_usec = 0; } } } /** * @internal * * @param fd file handle * @param timeout maximum time to wait; when the function returns the * value is reduced by the time spent waiting. * * @brief Waits in select() for the given file handle to become readable. * * If the syscall is interrupted by an interrupt, the select() call * is repeated with a timeout reduced by the time already spent * waiting. */ int vbi_capture_io_select (int fd, struct timeval * timeout) { struct timeval tv_start; struct timeval tv; fd_set fds; int ret; while (1) { FD_ZERO(&fds); FD_SET(fd, &fds); tv = *timeout; /* Linux kernel overwrites this */ gettimeofday(&tv_start, NULL); ret = select(fd + 1, &fds, NULL, NULL, &tv); vbi_capture_io_update_timeout (timeout, &tv_start); if ((ret < 0) && (errno == EINTR)) continue; return ret; } } /* Helper functions to log the communication between the library and drivers. FIXME remove fp arg, call user log function instead (0.3). */ #define MODE_GUESS 0 #define MODE_ENUM 1 #define MODE_SET_FLAGS 2 #define MODE_ALL_FLAGS 3 /** * @internal * @param mode * - GUESS if value is enumeration or flags (used by structpr.pl) * - ENUM interpret value as an enumerated item * - SET_FLAGS interpret value as a set of flags, print set ones * - ALL_FLAGS interpret value as a set of flags, print all * @param value * @param ... vector of symbol (const char *) and value * (unsigned long) pairs. Last parameter must be NULL. */ void fprint_symbolic (FILE * fp, int mode, unsigned long value, ...) { unsigned int i, j = 0; unsigned long v; const char *s; va_list ap; if (mode == 0) { unsigned int n[2] = { 0, 0 }; va_start (ap, value); while ((s = va_arg (ap, const char *))) { v = va_arg (ap, unsigned long); n[0 == (v & (v - 1))]++; /* single bit? */ } mode = MODE_ENUM + (n[1] > n[0]); va_end (ap); } va_start (ap, value); for (i = 0; (s = va_arg (ap, const char *)); ++i) { v = va_arg (ap, unsigned long); if (v == value || MODE_ALL_FLAGS == mode || (MODE_SET_FLAGS == mode && 0 != (v & value))) { if (j++ > 0) fputc ('|', fp); if (MODE_ALL_FLAGS == mode && 0 == (v & value)) fputc ('!', fp); fputs (s, fp); value &= ~v; } } if (0 == value && 0 == j) fputc ('0', fp); else if (value) fprintf (fp, "%s0x%lx", j ? "|" : "", value); va_end (ap); } /** * @internal * Used by function printing ioctl arguments generated by structpr.pl. */ void fprint_unknown_ioctl (FILE * fp, unsigned int cmd, void * arg) { fprintf (fp, "", cmd, IOCTL_READ (cmd) ? 'R' : '-', IOCTL_WRITE (cmd) ? 'W' : '-', arg, IOCTL_ARG_SIZE (cmd)); } /** * @internal * Drop-in for open(). Logs the request on fp if not NULL. */ int device_open (FILE * fp, const char * pathname, int flags, mode_t mode) { int fd; fd = open (pathname, flags, mode); if (fp) { int saved_errno; saved_errno = errno; fprintf (fp, "%d = open (\"%s\", ", fd, pathname); fprint_symbolic (fp, MODE_SET_FLAGS, flags, "RDONLY", O_RDONLY, "WRONLY", O_WRONLY, "RDWR", O_RDWR, "CREAT", O_CREAT, "EXCL", O_EXCL, "TRUNC", O_TRUNC, "APPEND", O_APPEND, #ifdef O_NONBLOCK "NONBLOCK", O_NONBLOCK, #endif 0); fprintf (fp, ", 0%o)", mode); if (-1 == fd) { fprintf (fp, ", errno=%d, %s\n", saved_errno, strerror (saved_errno)); } else { fputc ('\n', fp); } errno = saved_errno; } return fd; } /** * @internal * Drop-in for close(). Logs the request on fp if not NULL. */ int device_close (FILE * fp, int fd) { int err; err = close (fd); if (fp) { int saved_errno; saved_errno = errno; if (-1 == err) { fprintf (fp, "%d = close (%d), errno=%d, %s\n", err, fd, saved_errno, strerror (saved_errno)); } else { fprintf (fp, "%d = close (%d)\n", err, fd); } errno = saved_errno; } return err; } #ifdef ENABLE_V4L /** * @internal * Drop-in for ioctl(). Logs the request on fp if not NULL, repeats * the ioctl if interrupted (EINTR). You must supply a function * printing the arguments, structpr.pl generates one for you * from a header file. */ int device_ioctl (FILE * fp, ioctl_log_fn * log_fn, int fd, unsigned int cmd, void * arg) { int buf[256]; int err; if (fp && IOCTL_WRITE (cmd)) { assert (sizeof (buf) >= IOCTL_ARG_SIZE (cmd)); memcpy (buf, arg, IOCTL_ARG_SIZE (cmd)); } do err = ioctl (fd, cmd, arg); while (-1 == err && EINTR == errno); if (fp && log_fn) { int saved_errno; saved_errno = errno; fprintf (fp, "%d = ", err); log_fn (fp, cmd, 0, NULL); fputc ('(', fp); if (IOCTL_WRITE (cmd)) log_fn (fp, cmd, IOCTL_READ (cmd) ? 3 : 2, &buf); if (-1 == err) { fprintf (fp, "), errno = %d, %s\n", saved_errno, strerror (saved_errno)); } else { if (IOCTL_READ (cmd)) { fputs (") -> (", fp); log_fn (fp, cmd, IOCTL_WRITE (cmd) ? 3 : 1, arg); } fputs (")\n", fp); } errno = saved_errno; } return err; } #endif /* ENABLE_V4L */ #ifdef ENABLE_V4L2 /** * @internal * Drop-in for mmap(). Logs the request on fp if not NULL. */ void * device_mmap (FILE * fp, void * start, size_t length, int prot, int flags, int fd, off_t offset) { void *r; r = mmap (start, length, prot, flags, fd, offset); if (fp) { int saved_errno; saved_errno = errno; fprintf (fp, "%p = mmap (start=%p length=%d prot=", r, start, (int) length); fprint_symbolic (fp, 2, (unsigned long) prot, "EXEC", PROT_EXEC, "READ", PROT_READ, "WRITE", PROT_WRITE, "NONE", PROT_NONE, 0); fputs (" flags=", fp); fprint_symbolic (fp, 2, (unsigned long) flags, "FIXED", MAP_FIXED, "SHARED", MAP_SHARED, "PRIVATE", MAP_PRIVATE, 0); fprintf (fp, " fd=%d offset=%d)", fd, (int) offset); if (MAP_FAILED == r) fprintf (fp, ", errno=%d, %s\n", saved_errno, strerror (saved_errno)); else fputc ('\n', fp); errno = saved_errno; } return r; } /** * @internal * Drop-in for munmap(). Logs the request on fp if not NULL. */ int device_munmap (FILE * fp, void * start, size_t length) { int r; r = munmap (start, length); if (fp) { int saved_errno; saved_errno = errno; if (-1 == r) fprintf (fp, "%d = munmap (start=%p length=%d), " "errno=%d, %s\n", r, start, (int) length, saved_errno, strerror (saved_errno)); else fprintf (fp, "%d = munmap (start=%p length=%d)\n", r, start, (int) length); errno = saved_errno; } return r; } #endif /* ENABLE_V4L2 */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/inout.h000066400000000000000000000215321476363111200146600ustar00rootroot00000000000000/* * libzvbi -- VBI device interfaces * * Copyright (C) 2002 Michael H. Schimek * Copyright (C) 2003, 2004 Tom Zoerner * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: inout.h,v 1.25 2008-02-24 14:16:28 mschimek Exp $ */ #ifndef INOUT_H #define INOUT_H #include "decoder.h" #include "bit_slicer.h" /* Public */ #include /* struct timeval */ /** * @ingroup Device */ typedef struct vbi_capture_buffer { void * data; int size; double timestamp; } vbi_capture_buffer; /** * @ingroup Device * @brief Opaque device interface handle. **/ typedef struct vbi_capture vbi_capture; /** * @ingroup Device * @brief Properties of capture file handle */ typedef enum { /** * Is set when select(2) can be used to wait for * new data on the capture device file handle. */ VBI_FD_HAS_SELECT = 1<<0, /** * Is set when the capture device supports * "user-space DMA". In this case it's more efficient * to use one of the "pull" functions to read raw data * because otherwise the data has to be copied once * more into the passed buffer. */ VBI_FD_HAS_MMAP = 1<<1, /** * Is not set when the capture device file handle is * not the actual device. In this case it can only be * used for select(2) and not for ioctl(2) */ VBI_FD_IS_DEVICE = 1<<2 } VBI_CAPTURE_FD_FLAGS; /** * @addtogroup Device * @{ */ extern vbi_capture * vbi_capture_v4l2_new(const char *dev_name, int buffers, unsigned int *services, int strict, char **errorstr, vbi_bool trace); extern vbi_capture * vbi_capture_v4l2k_new(const char * dev_name, int fd, int buffers, unsigned int * services, int strict, char ** errorstr, vbi_bool trace); extern vbi_capture * vbi_capture_v4l_new(const char *dev_name, int scanning, unsigned int *services, int strict, char **errorstr, vbi_bool trace); extern vbi_capture * vbi_capture_v4l_sidecar_new(const char *dev_name, int given_fd, unsigned int *services, int strict, char **errorstr, vbi_bool trace); extern vbi_capture * vbi_capture_bktr_new (const char * dev_name, int scanning, unsigned int * services, int strict, char ** errstr, vbi_bool trace); extern int vbi_capture_dvb_filter(vbi_capture *cap, int pid); /* This function is deprecated. Use vbi_capture_dvb_new2() instead. See io-dvb.c or the Doxygen documentation for details. */ extern vbi_capture * vbi_capture_dvb_new (char * dev, int scanning, unsigned int * services, int strict, char ** errstr, vbi_bool trace) _vbi_deprecated; extern int64_t vbi_capture_dvb_last_pts (const vbi_capture * cap); extern vbi_capture * vbi_capture_dvb_new2 (const char * device_name, unsigned int pid, char ** errstr, vbi_bool trace); struct vbi_proxy_client; extern vbi_capture * vbi_capture_proxy_new( struct vbi_proxy_client * vpc, int buffers, int scanning, unsigned int *p_services, int strict, char **pp_errorstr ); extern int vbi_capture_read_raw(vbi_capture *capture, void *data, double *timestamp, struct timeval *timeout); extern int vbi_capture_read_sliced(vbi_capture *capture, vbi_sliced *data, int *lines, double *timestamp, struct timeval *timeout); extern int vbi_capture_read(vbi_capture *capture, void *raw_data, vbi_sliced *sliced_data, int *lines, double *timestamp, struct timeval *timeout); extern int vbi_capture_pull_raw(vbi_capture *capture, vbi_capture_buffer **buffer, struct timeval *timeout); extern int vbi_capture_pull_sliced(vbi_capture *capture, vbi_capture_buffer **buffer, struct timeval *timeout); extern int vbi_capture_pull(vbi_capture *capture, vbi_capture_buffer **raw_buffer, vbi_capture_buffer **sliced_buffer, struct timeval *timeout); extern vbi_raw_decoder *vbi_capture_parameters(vbi_capture *capture); extern int vbi_capture_fd(vbi_capture *capture); extern unsigned int vbi_capture_update_services(vbi_capture *capture, vbi_bool reset, vbi_bool commit, unsigned int services, int strict, char ** errorstr); extern int vbi_capture_get_scanning(vbi_capture *capture); extern void vbi_capture_flush(vbi_capture *capture); extern void vbi_capture_delete(vbi_capture *capture); extern vbi_bool vbi_capture_set_video_path(vbi_capture *capture, const char * p_dev_video); extern VBI_CAPTURE_FD_FLAGS vbi_capture_get_fd_flags(vbi_capture *capture); /** @} */ /* Private */ #ifndef DOXYGEN_SHOULD_SKIP_THIS #include #include #include #include /* open() */ extern const char _zvbi_intl_domainname[]; #include "version.h" #include "intl-priv.h" #if defined (_IOC_SIZE) /* Linux */ #define IOCTL_ARG_SIZE(cmd) _IOC_SIZE (cmd) #define IOCTL_READ(cmd) (_IOC_DIR (cmd) & _IOC_READ) #define IOCTL_WRITE(cmd) (_IOC_DIR (cmd) & _IOC_WRITE) #define IOCTL_READ_WRITE(cmd) (_IOC_DIR (cmd) == (_IOC_READ | _IOC_WRITE)) #define IOCTL_NUMBER(cmd) _IOC_NR (cmd) #elif defined (IOCPARM_LEN) /* FreeBSD */ #define IOCTL_ARG_SIZE(cmd) IOCPARM_LEN (cmd) #define IOCTL_READ(cmd) ((cmd) & IOC_OUT) #define IOCTL_WRITE(cmd) ((cmd) & IOC_IN) #define IOCTL_READ_WRITE(cmd) (((cmd) & IOC_DIRMASK) == (IOC_IN | IOC_OUT)) #define IOCTL_NUMBER(cmd) ((cmd) & 0xFF) #else /* Don't worry, only used for debugging */ #define IOCTL_ARG_SIZE(cmd) 0 #define IOCTL_READ(cmd) 0 #define IOCTL_WRITE(cmd) 0 #define IOCTL_READ_WRITE(cmd) 0 #define IOCTL_NUMBER(cmd) 0 #endif typedef void ioctl_log_fn (FILE * fp, unsigned int cmd, int rw, void * arg); extern void fprint_symbolic (FILE * fp, int mode, unsigned long value, ...); extern void fprint_unknown_ioctl (FILE * fp, unsigned int cmd, void * arg); extern int device_open (FILE * fp, const char * pathname, int flags, mode_t mode); extern int device_close (FILE * fp, int fd); #ifdef ENABLE_V4L extern int device_ioctl (FILE * fp, ioctl_log_fn * fn, int fd, unsigned int cmd, void * arg); #endif /* ENABLE_V4L */ #ifdef ENABLE_V4L2 extern void * device_mmap (FILE * fp, void * start, size_t length, int prot, int flags, int fd, off_t offset); extern int device_munmap (FILE * fp, void * start, size_t length); #endif /* ENABLE_V4L2 */ extern void vbi_capture_set_log_fp (vbi_capture * capture, FILE * fp); /* Preliminary hack for tests. */ extern vbi_bool vbi_capture_force_read_mode; #endif /* !DOXYGEN_SHOULD_SKIP_THIS */ /** * @ingroup Devmod */ struct vbi_capture { int (* read)(vbi_capture *, vbi_capture_buffer **, vbi_capture_buffer **, const struct timeval *); vbi_bool (* sampling_point) (vbi_capture *, vbi3_bit_slicer_point *, unsigned int row, unsigned int nth_bit); vbi_bool (* debug) (vbi_capture *, vbi_bool enable); vbi_raw_decoder * (* parameters)(vbi_capture *); unsigned int (* update_services)(vbi_capture *, vbi_bool, vbi_bool, unsigned int, int, char **); int (* get_scanning)(vbi_capture *); void (* flush)(vbi_capture *); int (* get_fd)(vbi_capture *); VBI_CAPTURE_FD_FLAGS (* get_fd_flags)(vbi_capture *); vbi_bool (* set_video_path)(vbi_capture *, const char *); void (* _delete)(vbi_capture *); /* Log all system calls if non-NULL. */ FILE * sys_log_fp; }; extern void vbi_capture_io_update_timeout (struct timeval * timeout, const struct timeval * tv_start); extern int vbi_capture_io_select (int fd, struct timeval * timeout); #endif /* INOUT_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/intl-priv.h000066400000000000000000000044461476363111200154530ustar00rootroot00000000000000/* * libzvbi -- Localization (gettext) helpers * * Copyright (C) 2004 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: intl-priv.h,v 1.6 2008-02-19 00:35:20 mschimek Exp $ */ #ifndef INTL_PRIV_H #define INTL_PRIV_H #if 3 == VBI_VERSION_MINOR # include "intl.h" #else extern const char _zvbi_intl_domainname[]; # define vbi_intl_domainname _zvbi_intl_domainname #endif #ifdef ENABLE_NLS # include # include # define _(String) dgettext (vbi_intl_domainname, String) # ifdef gettext_noop # define N_(String) gettext_noop (String) # else # define N_(String) (String) # endif #else /* Stubs that do something close enough. */ # define gettext(Msgid) ((const char *) (Msgid)) # define dgettext(Domainname, Msgid) ((const char *) (Msgid)) # define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) # define ngettext(Msgid1, Msgid2, N) \ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) # define dngettext(Domainname, Msgid1, Msgid2, N) \ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) # define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) # define textdomain(Domainname) ((const char *) (Domainname)) # define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) # define bind_textdomain_codeset(Domainname, Codeset) \ ((const char *) (Codeset)) # define _(String) (String) # define N_(String) (String) #endif #endif /* INTL_PRIV_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/io-bktr.c000066400000000000000000000213661476363111200150710ustar00rootroot00000000000000/* * libzvbi -- FreeBSD/OpenBSD bktr driver interface * * Copyright (C) 2002 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ static const char rcsid [] = "$Id: io-bktr.c,v 1.17 2008-02-19 00:35:20 mschimek Exp $"; #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "vbi.h" #include "inout.h" #define printv(format, args...) \ do { \ if (trace) { \ fprintf(stderr, format ,##args); \ fflush(stderr); \ } \ } while (0) #ifdef ENABLE_BKTR #include #include #include #include #include #include #include #include /* timeval */ #include /* fd_set */ #include typedef struct vbi_capture_bktr { vbi_capture capture; int fd; vbi_bool select; vbi_raw_decoder dec; double time_per_frame; vbi_capture_buffer *raw_buffer; int num_raw_buffers; vbi_capture_buffer sliced_buffer; } vbi_capture_bktr; static int bktr_read (vbi_capture * vc, vbi_capture_buffer ** raw, vbi_capture_buffer ** sliced, const struct timeval * timeout) { vbi_capture_bktr *v = PARENT(vc, vbi_capture_bktr, capture); vbi_capture_buffer *my_raw = v->raw_buffer; struct timeval tv; int r; while (v->select) { fd_set fds; FD_ZERO(&fds); FD_SET(v->fd, &fds); tv = *timeout; /* kernel overwrites this? */ r = select(v->fd + 1, &fds, NULL, NULL, &tv); if (r < 0 && errno == EINTR) continue; if (r <= 0) return r; /* timeout or error */ break; } if (!raw) raw = &my_raw; if (!*raw) *raw = v->raw_buffer; else (*raw)->size = v->raw_buffer[0].size; for (;;) { /* from zapping/libvbi/v4lx.c */ pthread_testcancel(); r = read(v->fd, (*raw)->data, (*raw)->size); if (r == -1 && errno == EINTR) continue; if (r == (*raw)->size) break; else return -1; } gettimeofday(&tv, NULL); (*raw)->timestamp = tv.tv_sec + tv.tv_usec * (1 / 1e6); if (sliced) { int lines; if (*sliced) { lines = vbi_raw_decode(&v->dec, (*raw)->data, (vbi_sliced *)(*sliced)->data); } else { *sliced = &v->sliced_buffer; lines = vbi_raw_decode(&v->dec, (*raw)->data, (vbi_sliced *)(v->sliced_buffer.data)); } (*sliced)->size = lines * sizeof(vbi_sliced); (*sliced)->timestamp = (*raw)->timestamp; } return 1; } static vbi_raw_decoder * bktr_parameters(vbi_capture *vc) { vbi_capture_bktr *v = PARENT(vc, vbi_capture_bktr, capture); return &v->dec; } static void bktr_delete(vbi_capture *vc) { vbi_capture_bktr *v = PARENT(vc, vbi_capture_bktr, capture); if (v->sliced_buffer.data) free(v->sliced_buffer.data); for (; v->num_raw_buffers > 0; v->num_raw_buffers--) free(v->raw_buffer[v->num_raw_buffers - 1].data); vbi_raw_decoder_destroy (&v->dec); if (-1 != v->fd) device_close (v->capture.sys_log_fp, v->fd); free(v); } static int bktr_fd(vbi_capture *vc) { vbi_capture_bktr *v = PARENT(vc, vbi_capture_bktr, capture); return v->fd; } vbi_capture * vbi_capture_bktr_new (const char * dev_name, int scanning, unsigned int * services, int strict, char ** errstr, vbi_bool trace) { char *error = NULL; char *driver_name = _("BKTR driver"); vbi_capture_bktr *v; pthread_once (&vbi_init_once, vbi_init); assert(services && *services != 0); if (!errstr) errstr = &error; *errstr = NULL; printv ("Try to open bktr vbi device, " "libzvbi interface rev.\n %s\n", rcsid); if (!(v = (vbi_capture_bktr *) calloc(1, sizeof(*v)))) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } vbi_raw_decoder_init (&v->dec); v->capture.parameters = bktr_parameters; v->capture._delete = bktr_delete; v->capture.get_fd = bktr_fd; v->fd = device_open (v->capture.sys_log_fp, dev_name, O_RDONLY, 0); if (-1 == v->fd) { asprintf(errstr, _("Cannot open '%s': %s."), dev_name, strerror(errno)); goto io_error; } printv("Opened %s\n", dev_name); /* * XXX * Can we somehow verify this really is /dev/vbiN (bktr) and not * /dev/hcfr (halt and catch fire on read) ? */ v->dec.bytes_per_line = 2048; v->dec.interlaced = FALSE; v->dec.synchronous = TRUE; v->dec.count[0] = 16; v->dec.count[1] = 16; switch (scanning) { default: /* fall through */ case 625: /* Not confirmed */ v->dec.scanning = 625; v->dec.sampling_rate = 35468950; v->dec.offset = (int)(10.2e-6 * 35468950); v->dec.start[0] = 22 + 1 - v->dec.count[0]; v->dec.start[1] = 335 + 1 - v->dec.count[1]; break; case 525: /* Not confirmed */ v->dec.scanning = 525; v->dec.sampling_rate = 28636363; v->dec.offset = (int)(9.2e-6 * 28636363); v->dec.start[0] = 10; v->dec.start[1] = 273; break; } v->time_per_frame = (v->dec.scanning == 625) ? 1.0 / 25 : 1001.0 / 30000; v->select = FALSE; /* XXX ? */ printv("Guessed videostandard %d\n", v->dec.scanning); v->dec.sampling_format = VBI_PIXFMT_YUV420; if (*services & ~(VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625)) { *services = vbi_raw_decoder_add_services (&v->dec, *services, strict); if (*services == 0) { asprintf(errstr, _("Sorry, %s (%s) cannot " "capture any of " "the requested data services."), dev_name, driver_name); goto failure; } v->sliced_buffer.data = malloc((v->dec.count[0] + v->dec.count[1]) * sizeof(vbi_sliced)); if (!v->sliced_buffer.data) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } } printv("Will decode services 0x%08x\n", *services); /* Read mode */ if (!v->select) printv("Warning: no read select, reading will block\n"); v->capture.read = bktr_read; v->raw_buffer = calloc(1, sizeof(v->raw_buffer[0])); if (!v->raw_buffer) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } v->raw_buffer[0].size = (v->dec.count[0] + v->dec.count[1]) * v->dec.bytes_per_line; v->raw_buffer[0].data = malloc(v->raw_buffer[0].size); if (!v->raw_buffer[0].data) { asprintf(errstr, _("Not enough memory to allocate " "vbi capture buffer (%d KB)."), (v->raw_buffer[0].size + 1023) >> 10); goto failure; } v->num_raw_buffers = 1; printv("Capture buffer allocated\n"); printv("Successful opened %s (%s)\n", dev_name, driver_name); if (errstr == &error) { free (error); error = NULL; } return &v->capture; failure: io_error: if (v) bktr_delete(&v->capture); if (errstr == &error) { free (error); error = NULL; } return NULL; } #else /** * @param dev_name Name of the device to open. * @param scanning The current video standard. Value is 625 * (PAL/SECAM family) or 525 (NTSC family). * @param services This must point to a set of @ref VBI_SLICED_ * symbols describing the * data services to be decoded. On return the services actually * decodable will be stored here. See vbi_raw_decoder_add() * for details. If you want to capture raw data only, set to * @c VBI_SLICED_VBI_525, @c VBI_SLICED_VBI_625 or both. * @param strict Will be passed to vbi_raw_decoder_add(). * @param errstr If not @c NULL this function stores a pointer to an error * description here. You must free() this string when no longer needed. * @param trace If @c TRUE print progress messages on stderr. * * @bug You must enable continuous video capturing to read VBI data from * the bktr driver, using an RGB video format, and the VBI device must be * opened before video capturing starts (METEORCAPTUR). * * @return * Initialized vbi_capture context, @c NULL on failure. */ vbi_capture * vbi_capture_bktr_new (const char * dev_name, int scanning, unsigned int * services, int strict, char ** errstr, vbi_bool trace) { dev_name = dev_name; scanning = scanning; services = services; strict = strict; trace = trace; pthread_once (&vbi_init_once, vbi_init); printv ("Libzvbi bktr interface rev.\n %s\n", rcsid); if (errstr) asprintf (errstr, _("BKTR driver interface not compiled.")); return NULL; } #endif /* !ENABLE_BKTR */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/io-dvb.c000066400000000000000000000373131476363111200147010ustar00rootroot00000000000000/* * libzvbi -- Linux DVB driver interface * * (c) 2004, 2005, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef ENABLE_DVB #include /* read() */ #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #include #include #ifndef HAVE_S64_U64 /* Linux 2.6.x asm/types.h defines __s64 and __u64 only if __GNUC__ is defined. */ typedef int64_t __s64; typedef uint64_t __u64; #endif #include "dvb/dmx.h" #include "inout.h" #include "vbi.h" /* vbi_init_once */ #include "dvb_demux.h" typedef struct { vbi_capture capture; int fd; /* FIXME This may be too small? */ uint8_t pes_buffer[1024 * 8]; const uint8_t * bp; unsigned int b_left; vbi_dvb_demux * demux; vbi_capture_buffer sliced_buffer; vbi_sliced sliced_data[256]; double sample_time; int64_t last_pts; vbi_bool do_trace; vbi_bool bug_compatible; } vbi_capture_dvb; #define printv(format, args...) \ do { \ if (dvb->do_trace) { \ fprintf (stderr, "libzvbi: " format ,##args); \ fflush (stderr); \ } \ } while (0) static __inline__ void timeval_subtract (struct timeval * delta, const struct timeval * tv1, const struct timeval * tv2) { if (tv1->tv_usec < tv2->tv_usec) { delta->tv_sec = tv1->tv_sec - tv2->tv_sec - 1; delta->tv_usec = 1000000 + tv1->tv_usec - tv2->tv_usec; } else { delta->tv_sec = tv1->tv_sec - tv2->tv_sec; delta->tv_usec = tv1->tv_usec - tv2->tv_usec; } } static void timeout_subtract_elapsed (struct timeval * result, const struct timeval * timeout, const struct timeval * now, const struct timeval * start) { struct timeval elapsed; timeval_subtract (&elapsed, now, start); if ((elapsed.tv_sec | elapsed.tv_usec) > 0) { timeval_subtract (result, timeout, &elapsed); if ((result->tv_sec | result->tv_usec) < 0) { result->tv_sec = 0; result->tv_usec = 0; } } else { *result = *timeout; } } static ssize_t select_read (vbi_capture_dvb * dvb, struct timeval * now, const struct timeval * start, const struct timeval * timeout) { struct timeval tv; ssize_t actual; select_again: /* tv = timeout - (now - start). */ timeout_subtract_elapsed (&tv, timeout, now, start); /* Shortcut: don't wait if timeout is zero or elapsed. */ if ((tv.tv_sec | tv.tv_usec) > 0) { fd_set set; int r; select_again_with_timeout: FD_ZERO (&set); FD_SET (dvb->fd, &set); /* Note Linux select() may change tv. */ r = select (dvb->fd + 1, /* readable */ &set, /* writeable */ NULL, /* in exception */ NULL, &tv); switch (r) { case -1: /* error */ switch (errno) { case EINTR: gettimeofday (now, /* timezone */ NULL); goto select_again; default: return -1; /* error */ } case 0: /* timeout */ if (dvb->bug_compatible) return -1; /* error */ return 0; /* timeout */ default: break; } } read_again: /* Non-blocking. */ actual = read (dvb->fd, dvb->pes_buffer, sizeof (dvb->pes_buffer)); switch (actual) { case -1: /* error */ switch (errno) { case EAGAIN: if (dvb->bug_compatible) return -1; /* error */ if ((timeout->tv_sec | timeout->tv_usec) <= 0) return 0; /* timeout */ gettimeofday (now, /* timezone */ NULL); timeout_subtract_elapsed (&tv, timeout, now, start); if ((tv.tv_sec | tv.tv_usec) <= 0) return 0; /* timeout */ goto select_again_with_timeout; case EINTR: goto read_again; default: return -1; /* error */ } case 0: /* EOF? */ printv ("End of file\n"); errno = 0; return -1; /* error */ default: break; } return actual; } static int dvb_read (vbi_capture * cap, vbi_capture_buffer ** raw, vbi_capture_buffer ** sliced, const struct timeval * timeout) { vbi_capture_dvb *dvb = PARENT (cap, vbi_capture_dvb, capture); vbi_capture_buffer *sb; struct timeval start; struct timeval now; unsigned int n_lines; int64_t pts; if (!sliced || !(sb = *sliced)) { sb = &dvb->sliced_buffer; sb->data = dvb->sliced_data; } start.tv_sec = 0; start.tv_usec = 0; /* When timeout is zero elapsed time doesn't matter. */ if ((timeout->tv_sec | timeout->tv_usec) > 0) gettimeofday (&start, /* timezone */ NULL); now = start; for (;;) { if (0 == dvb->b_left) { ssize_t actual; actual = select_read (dvb, &now, &start, timeout); if (actual <= 0) return actual; gettimeofday (&now, /* timezone */ NULL); /* XXX inaccurate. Should be the time when we received the first byte of the first packet containing data of the returned frame. Or so. */ dvb->sample_time = now.tv_sec + now.tv_usec * (1 / 1e6); dvb->bp = dvb->pes_buffer; dvb->b_left = actual; } /* Demultiplexer coroutine. Returns when one frame is complete or the buffer is empty, advancing bp and b_left. Don't change sb->data in flight. */ /* XXX max sliced lines needs an API change. Currently this value is determined by vbi_raw_decoder line count below, 256 / 2 because fields don't really apply here and in practice even 32 should be enough. */ n_lines = vbi_dvb_demux_cor (dvb->demux, sb->data, /* max sliced lines */ 128, &pts, &dvb->bp, &dvb->b_left); if (n_lines > 0) break; if (dvb->bug_compatible) { /* Only one read(), timeout ignored. */ return 0; /* timeout */ } else { /* Read until EAGAIN, another error or the timeout expires, in this order. */ } } if (sliced) { sb->size = n_lines * sizeof (vbi_sliced); sb->timestamp = dvb->sample_time; /* XXX PTS needs an API change. sb->sample_time = dvb->sample_time; sb->stream_time = pts; (first sliced line) */ dvb->last_pts = pts; *sliced = sb; } if (raw && *raw) { /* Not implemented yet. */ sb = *raw; sb->size = 0; } return 1; /* success */ } static vbi_raw_decoder * dvb_parameters (vbi_capture * cap) { /* This is kinda silly but we keep it for compatibility with earlier versions of the library. */ static vbi_raw_decoder raw = { .count = { 128, 128 }, /* see dvb_read() */ }; cap = cap; /* unused, no warning please */ return &raw; } static unsigned int dvb_update_services (vbi_capture * cap, vbi_bool reset, vbi_bool commit, unsigned int services, int strict, char ** errstr) { cap = cap; /* unused, no warning please */ reset = reset; commit = commit; services = services; strict = strict; errstr = errstr; return (VBI_SLICED_TELETEXT_B | VBI_SLICED_VPS | VBI_SLICED_CAPTION_625 | VBI_SLICED_WSS_625); } static void dvb_flush (vbi_capture * cap) { vbi_capture_dvb *dvb = PARENT (cap, vbi_capture_dvb, capture); vbi_dvb_demux_reset (dvb->demux); dvb->bp = dvb->pes_buffer; dvb->b_left = 0; } static VBI_CAPTURE_FD_FLAGS dvb_get_fd_flags (vbi_capture * cap) { cap = cap; /* unused, no warning please */ return (VBI_FD_HAS_SELECT | VBI_FD_IS_DEVICE); } static int dvb_get_fd (vbi_capture * cap) { vbi_capture_dvb *dvb = PARENT (cap, vbi_capture_dvb, capture); return dvb->fd; } int64_t vbi_capture_dvb_last_pts (const vbi_capture * cap) { const vbi_capture_dvb *dvb = CONST_PARENT (cap, vbi_capture_dvb, capture); return dvb->last_pts; } int vbi_capture_dvb_filter (vbi_capture * cap, int pid) { vbi_capture_dvb *dvb = PARENT (cap, vbi_capture_dvb, capture); struct dmx_pes_filter_params filter; CLEAR (filter); filter.pid = pid; filter.input = DMX_IN_FRONTEND; filter.output = DMX_OUT_TAP; filter.pes_type = DMX_PES_OTHER; filter.flags = DMX_IMMEDIATE_START; if (-1 == ioctl (dvb->fd, DMX_SET_PES_FILTER, &filter)) return -1; printv ("Capturing PES packets with PID %d\n", pid); return 0; } static void dvb_delete (vbi_capture * cap) { vbi_capture_dvb *dvb; if (NULL == cap) return; dvb = PARENT (cap, vbi_capture_dvb, capture); if (-1 != dvb->fd) { /* Error ignored. */ device_close (dvb->capture.sys_log_fp, dvb->fd); } vbi_dvb_demux_delete (dvb->demux); /* Make unusable. */ CLEAR (*dvb); free (dvb); } static vbi_bool open_device (vbi_capture_dvb * dvb, const char * device_name, char ** errstr) { int saved_errno; struct stat st; if (-1 == stat (device_name, &st)) goto io_error; if (!S_ISCHR (st.st_mode)) { asprintf (errstr, _("%s is not a device."), device_name); saved_errno = 0; goto failed; } /* XXX Can we check if this is really a DVB demux device? */ dvb->fd = device_open (dvb->capture.sys_log_fp, device_name, O_RDONLY | O_NONBLOCK, /* mode */ 0); if (-1 == dvb->fd) goto io_error; return TRUE; io_error: saved_errno = errno; asprintf (errstr, _("Cannot open '%s': %s."), device_name, strerror (saved_errno)); /* fall through */ failed: dvb->fd = -1; errno = saved_errno; return FALSE; } vbi_capture * vbi_capture_dvb_new2 (const char * device_name, unsigned int pid, char ** errstr, vbi_bool trace) { char *error = NULL; int saved_errno; vbi_capture_dvb *dvb; pthread_once (&vbi_init_once, vbi_init); if (NULL == errstr) errstr = &error; *errstr = NULL; dvb = vbi_malloc (sizeof (*dvb)); if (NULL == dvb) goto no_memory; CLEAR (*dvb); dvb->capture.read = dvb_read; dvb->capture.sampling_point = NULL; dvb->capture.debug = NULL; dvb->capture.parameters = dvb_parameters; dvb->capture.update_services = dvb_update_services; dvb->capture.get_scanning = NULL; dvb->capture.flush = dvb_flush; dvb->capture.get_fd = dvb_get_fd; dvb->capture.get_fd_flags = dvb_get_fd_flags; dvb->capture.set_video_path = NULL; dvb->capture._delete = dvb_delete; dvb->fd = -1; dvb->do_trace = trace; dvb->demux = vbi_dvb_pes_demux_new (/* callback */ NULL, /* user_data */ NULL); if (NULL == dvb->demux) goto no_memory; if (!open_device (dvb, device_name, errstr)) { saved_errno = errno; goto failed; } printv ("Opened device %s\n", device_name); if (0 != pid) { if (-1 == vbi_capture_dvb_filter (&dvb->capture, pid)) { saved_errno = errno; asprintf (errstr, _("DMX_SET_PES_FILTER failed: %s."), strerror (errno)); goto failed; } } dvb_flush (&dvb->capture); if (errstr == &error) { free (error); error = NULL; } return &dvb->capture; no_memory: asprintf (errstr, _("Virtual memory exhausted.")); saved_errno = ENOMEM; /* fall through */ failed: if (NULL != dvb) { dvb_delete (&dvb->capture); dvb = NULL; } if (errstr == &error) { free (error); error = NULL; } errno = saved_errno; return NULL; } vbi_capture * vbi_capture_dvb_new (char * dev, int scanning, unsigned int * services, int strict, char ** errstr, vbi_bool trace) { char *error = NULL; vbi_capture *cap; vbi_capture_dvb *dvb; scanning = scanning; /* unused, no warning please */ services = services; strict = strict; if (NULL == errstr) errstr = &error; *errstr = NULL; cap = vbi_capture_dvb_new2 (dev, /* pid */ 0, errstr, trace); if (NULL == cap) goto failed; dvb = PARENT (cap, vbi_capture_dvb, capture); dvb->bug_compatible = TRUE; failed: if (errstr == &error) { free (error); error = NULL; } return cap; } #else /* !ENABLE_DVB */ #include "inout.h" #include "vbi.h" /** * @param cap Initialized DVB vbi_capture context. * * Returns the presentation time stamp associated with the data * last read from the context. The PTS refers to the first sliced * VBI line, not the last packet containing data of that frame. * * Note timestamps returned by vbi_capture read functions contain * the system time (gettimeofday()) when the packet containing the * first sliced line was captured, not the PTS. * * @returns * Presentation time stamp (33 bits). * * @bug * The read functions should return the PTS along with * the capture timestamp. * * @since 0.2.13 */ int64_t vbi_capture_dvb_last_pts (const vbi_capture * cap) { cap = cap; /* unused, no warning please */ return 0; } /** * @param cap Initialized DVB vbi_capture context. * @param pid Filter out a stream with this PID. * * Programs the DVB device transport stream demultiplexer on the * DVB capture device to filter out packets with this PID. * * @returns * -1 on failure, 0 on success. */ int vbi_capture_dvb_filter (vbi_capture * cap, int pid) { cap = cap; /* unused, no warning please */ pid = pid; return -1; } /** * @param device_name Name of the DVB device to open. * @param pid Filter out a stream with this PID. You can pass 0 here * and set or change the PID later with vbi_capture_dvb_filter(). * @param errstr If not @c NULL the function stores a pointer to an error * description here. You must free() this string when no longer needed. * @param trace If @c TRUE print progress and warning messages on stderr. * * Initializes a vbi_capture context reading from a Linux DVB device. * * @returns * Initialized vbi_capture context, @c NULL on failure. * * @since 0.2.13 */ vbi_capture * vbi_capture_dvb_new2 (const char * device_name, unsigned int pid, char ** errstr, vbi_bool trace) { device_name = device_name; /* unused, no warning please */ pid = pid; trace = trace; if (NULL != errstr) asprintf (errstr, _("DVB interface not compiled.")); return NULL; } /** * @param dev Name of the DVB device to open. * @param scanning Ignored. * @param services Ignored. * @param strict Ignored. * @param errstr If not @c NULL the function stores a pointer to an error * description here. You must free() this string when no longer needed. * @param trace If @c TRUE print progress and warning messages on stderr. * * @deprecated * This function is deprecated. Use vbi_capture_dvb_new2() instead. * * Initializes a vbi_capture context reading from a Linux DVB device. * You must call vbi_capture_dvb_filter() before you can read. * * @returns * Initialized vbi_capture context, @c NULL on failure. * * @bug * This function ignores the scanning, services and strict parameter. * The read method of this DVB capture context returns -1 on timeout * instead of 0. It returns 0 when a single read() does not complete * a frame, regardless if the timeout expired. (Decoding resumes with * the next call.) Older versions pass select or read EINTR errors * back to the caller. They may return partial frames when VBI data * of one frame is distributed across multiple PES packets. They will * not return VPS, CC, or WSS data and can malfunction or segfault * given unusual PES streams. On error and select timeout older versions * invariably print a warning on stderr. */ vbi_capture * vbi_capture_dvb_new (char * dev, int scanning, unsigned int * services, int strict, char ** errstr, vbi_bool trace) { dev = dev; /* unused, no warning please */ scanning = scanning; services = services; strict = strict; trace = trace; if (NULL != errstr) asprintf (errstr, _("DVB interface not compiled.")); return NULL; } #endif /* !ENABLE_DVB */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/io-sim.c000066400000000000000000001770461476363111200147260ustar00rootroot00000000000000/* * libzvbi -- VBI device simulation * * Copyright (C) 2004, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: io-sim.c,v 1.18 2009-12-14 23:43:40 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include /* sin(), log() */ #include #include /* isspace() */ #include /* INT_MAX */ #include "misc.h" #include "sliced.h" #include "version.h" #include "sampling_par.h" #include "raw_decoder.h" #include "hamm.h" #if 2 == VBI_VERSION_MINOR # define sp_sample_format sampling_format # define SAMPLES_PER_LINE(sp) \ ((sp)->bytes_per_line / VBI_PIXFMT_BPP ((sp)->sampling_format)) # define SYSTEM_525(sp) \ (525 == (sp)->scanning) #else # include "vps.h" # include "wss.h" # define sp_sample_format sample_format # define SAMPLES_PER_LINE(sp) ((sp)->samples_per_line) # define SYSTEM_525(sp) \ (0 != (VBI_VIDEOSTD_SET_525_60 & (sp)->videostd_set)) #endif #include "io-sim.h" /** * @addtogroup Rawenc Raw VBI encoder * @ingroup Raw * @brief Converting sliced VBI data to raw VBI images. * * These are functions converting sliced VBI data to raw VBI images as * transmitted in the vertical blanking interval of analog video standards. * They are mainly intended for tests of the libzvbi bit slicer and * raw VBI decoder. */ #if 2 == VBI_VERSION_MINOR # define VBI_PIXFMT_RGB24_LE VBI_PIXFMT_RGB24 # define VBI_PIXFMT_BGR24_LE VBI_PIXFMT_BGR24 # define VBI_PIXFMT_RGBA24_LE VBI_PIXFMT_RGBA32_LE # define VBI_PIXFMT_BGRA24_LE VBI_PIXFMT_BGRA32_LE # define VBI_PIXFMT_RGBA24_BE VBI_PIXFMT_RGBA32_BE # define VBI_PIXFMT_BGRA24_BE VBI_PIXFMT_BGRA32_BE # define vbi_pixfmt_bytes_per_pixel VBI_PIXFMT_BPP #endif #undef warning #define warning(function, templ, args...) \ do { \ if (_vbi_global_log.mask & VBI_LOG_WARNING) \ _vbi_log_printf (_vbi_global_log.fn, \ _vbi_global_log.user_data, \ VBI_LOG_WARNING, __FILE__, function, \ templ , ##args); \ } while (0) #define PI 3.1415926535897932384626433832795029 #define PULSE(zero_level) \ do { \ if (0 == seq) { \ raw[i] = SATURATE (zero_level, 0, 255); \ } else if (3 == seq) { \ raw[i] = SATURATE (zero_level + (int) signal_amp, \ 0, 255); \ } else if ((seq ^ bit) & 1) { /* down */ \ double r = sin (q * tr - (PI / 2.0)); \ r = r * r * signal_amp; \ raw[i] = SATURATE (zero_level + (int) r, 0, 255); \ } else { /* up */ \ double r = sin (q * tr); \ r = r * r * signal_amp; \ raw[i] = SATURATE (zero_level + (int) r, 0, 255); \ } \ } while (0) #define PULSE_SEQ(zero_level) \ do { \ double tr; \ unsigned int bit; \ unsigned int byte; \ unsigned int seq; \ \ tr = t - t1; \ bit = tr * bit_rate; \ byte = bit >> 3; \ bit &= 7; \ seq = (buf[byte] >> 7) + buf[byte + 1] * 2; \ seq = (seq >> bit) & 3; \ PULSE (zero_level); \ } while (0) #ifndef HAVE_SINCOS /* This is a GNU extension. */ _vbi_inline void sincos (double x, double * sinx, double * cosx) { *sinx = sin (x); *cosx = cos (x); } #endif /* This is a GNU extension. */ #ifndef HAVE_LOG2 # define log2(x) (log (x) / M_LN2) #endif static void signal_teletext (uint8_t * raw, const vbi_sampling_par *sp, int black_level, double signal_amp, double bit_rate, unsigned int frc, unsigned int payload, const vbi_sliced * sliced) { double bit_period = 1.0 / bit_rate; /* Teletext System B: Sixth CRI pulse at 12 us (+.5 b/c we start with a 0 bit). */ double t1 = 12e-6 - 13 * bit_period; double t2 = t1 + (payload * 8 + 24 + 1) * bit_period; double q = (PI / 2.0) * bit_rate; double sample_period = 1.0 / sp->sampling_rate; unsigned int samples_per_line; uint8_t buf[64]; unsigned int i; double t; buf[0] = 0x00; buf[1] = 0x55; /* clock run-in */ buf[2] = 0x55; buf[3] = frc; memcpy (buf + 4, sliced->data, payload); buf[payload + 4] = 0x00; t = sp->offset / (double) sp->sampling_rate; samples_per_line = SAMPLES_PER_LINE (sp); for (i = 0; i < samples_per_line; ++i) { if (t >= t1 && t < t2) PULSE_SEQ (black_level); t += sample_period; } } static void signal_vps (uint8_t * raw, const vbi_sampling_par *sp, int black_level, int white_level, const vbi_sliced * sliced) { static const uint8_t biphase [] = { 0xAA, 0x6A, 0x9A, 0x5A, 0xA6, 0x66, 0x96, 0x56, 0xA9, 0x69, 0x99, 0x59, 0xA5, 0x65, 0x95, 0x55 }; double bit_rate = 15625 * 160 * 2; double t1 = 12.5e-6 - .5 / bit_rate; double t4 = t1 + ((4 + 13 * 2) * 8) / bit_rate; double q = (PI / 2.0) * bit_rate; double sample_period = 1.0 / sp->sampling_rate; unsigned int samples_per_line; double signal_amp = (0.5 / 0.7) * (white_level - black_level); uint8_t buf[32]; unsigned int i; double t; CLEAR (buf); buf[1] = 0x55; /* 0101 0101 */ buf[2] = 0x55; /* 0101 0101 */ buf[3] = 0x51; /* 0101 0001 */ buf[4] = 0x99; /* 1001 1001 */ for (i = 0; i < 13; ++i) { unsigned int b = sliced->data[i]; buf[5 + i * 2] = biphase[b >> 4]; buf[6 + i * 2] = biphase[b & 15]; } buf[6 + 12 * 2] &= 0x7F; t = sp->offset / (double) sp->sampling_rate; samples_per_line = SAMPLES_PER_LINE (sp); for (i = 0; i < samples_per_line; ++i) { if (t >= t1 && t < t4) PULSE_SEQ (black_level); t += sample_period; } } static void wss_biphase (uint8_t buf[32], const vbi_sliced * sliced) { unsigned int bit; unsigned int data; unsigned int i; /* 29 bit run-in and 24 bit start code, lsb first. */ buf[0] = 0x00; buf[1] = 0x1F; /* 0001 1111 */ buf[2] = 0xC7; /* 1100 0111 */ buf[3] = 0x71; /* 0111 0001 */ buf[4] = 0x1C; /* 000 | 1 1100 */ buf[5] = 0x8F; /* 1000 1111 */ buf[6] = 0x07; /* 0000 0111 */ buf[7] = 0x1F; /* 1 1111 */ bit = 8 + 29 + 24; data = sliced->data[0] + sliced->data[1] * 256; for (i = 0; i < 14; ++i) { static const unsigned int biphase [] = { 0x38, 0x07 }; unsigned int byte; unsigned int shift; unsigned int seq; byte = bit >> 3; shift = bit & 7; bit += 6; seq = biphase[data & 1] << shift; data >>= 1; assert (byte < 31); buf[byte] |= seq; buf[byte + 1] = seq >> 8; } } static void signal_wss_625 (uint8_t * raw, const vbi_sampling_par *sp, int black_level, int white_level, const vbi_sliced * sliced) { double bit_rate = 15625 * 320; double t1 = 11.0e-6 - .5 / bit_rate; double t4 = t1 + (29 + 24 + 14 * 6 + 1) / bit_rate; double q = (PI / 2.0) * bit_rate; double sample_period = 1.0 / sp->sampling_rate; double signal_amp = (0.5 / 0.7) * (white_level - black_level); unsigned int samples_per_line; uint8_t buf[32]; unsigned int i; double t; CLEAR (buf); wss_biphase (buf, sliced); t = sp->offset / (double) sp->sampling_rate; samples_per_line = SAMPLES_PER_LINE (sp); for (i = 0; i < samples_per_line; ++i) { if (t >= t1 && t < t4) PULSE_SEQ (black_level); t += sample_period; } } static void signal_closed_caption (uint8_t * raw, const vbi_sampling_par *sp, int blank_level, int white_level, unsigned int flags, double bit_rate, const vbi_sliced * sliced) { double D = 1.0 / bit_rate; double t0 = 10.5e-6; /* CRI start half amplitude (EIA 608-B) */ double t1 = t0 - .25 * D; /* CRI start, blanking level */ double t2 = t1 + 7 * D; /* CRI 7 cycles */ /* First start bit, left edge half amplitude, minus rise time. */ double t3 = t0 + 6.5 * D - 120e-9; double q1 = PI * bit_rate * 2; /* Max. rise/fall time 240 ns (EIA 608-B). */ double q2 = PI / 120e-9; double signal_mean; double signal_high; double sample_period = 1.0 / sp->sampling_rate; unsigned int samples_per_line; double t; unsigned int data; unsigned int i; /* Twice 7 data + odd parity, start bit 0 -> 1 */ data = (sliced->data[1] << 12) + (sliced->data[0] << 4) + 8; t = sp->offset / (double) sp->sampling_rate; samples_per_line = SAMPLES_PER_LINE (sp); if (flags & _VBI_RAW_SHIFT_CC_CRI) { /* Wrong signal shape found by Rich Kadel, zapping-misc@lists.sourceforge.net 2006-07-16. */ t0 += D / 2; t1 += D / 2; t2 += D / 2; } if (flags & _VBI_RAW_LOW_AMP_CC) { /* Low amplitude signal found by Rich Kadel, zapping-misc@lists.sourceforge.net 2007-08-15. */ white_level = white_level * 6 / 10; } signal_mean = (white_level - blank_level) * .25; /* 25 IRE */ signal_high = blank_level + (white_level - blank_level) * .5; for (i = 0; i < samples_per_line; ++i) { if (t >= t1 && t < t2) { raw[i] = SATURATE (blank_level + (1.0 - cos (q1 * (t - t1))) * signal_mean, 0, 255); } else { unsigned int bit; unsigned int seq; double d; d = t - t3; bit = d * bit_rate; seq = (data >> bit) & 3; d -= bit * D; if ((1 == seq || 2 == seq) && fabs (d) < .120e-6) { int level; if (1 == seq) level = blank_level + (1.0 + cos (q2 * d)) * signal_mean; else level = blank_level + (1.0 - cos (q2 * d)) * signal_mean; raw[i] = SATURATE (level, 0, 255); } else if (data & (2 << bit)) { raw[i] = SATURATE (signal_high, 0, 255); } else { raw[i] = SATURATE (blank_level, 0, 255); } } t += sample_period; } } static void clear_image (uint8_t * p, unsigned int value, unsigned int width, unsigned int height, unsigned int bytes_per_line) { if (width == bytes_per_line) { memset (p, value, height * bytes_per_line); } else { while (height-- > 0) { memset (p, value, width); p += bytes_per_line; } } } /** * @param raw Noise will be added to this raw VBI image. * @param sp Describes the raw VBI data in the buffer. @a sp->sampling_format * must be @c VBI_PIXFMT_Y8 (@c VBI_PIXFMT_YUV420 in libzvbi 0.2.x). * Note for compatibility in libzvbi 0.2.x vbi_sampling_par is a * synonym of vbi_raw_decoder, but the (private) decoder fields in * this structure are ignored. * @param min_freq Minimum frequency of the noise in Hz. * @param max_freq Maximum frequency of the noise in Hz. @a min_freq and * @a max_freq define the cut off frequency at the half power points * (gain -3 dB). * @param amplitude Maximum amplitude of the noise, should lie in range * 0 to 256. * @param seed Seed for the pseudo random number generator built into * this function. Given the same @a seed value the function will add * the same noise, which can be useful for tests. * * This function adds white noise to a raw VBI image. * * To produce realistic noise @a min_freq = 0, @a max_freq = 5e6 and * @a amplitude = 20 to 50 seems appropriate. * * @returns * FALSE if the @a sp sampling parameters are invalid. * * @since 0.2.26 */ vbi_bool vbi_raw_add_noise (uint8_t * raw, const vbi_sampling_par *sp, unsigned int min_freq, unsigned int max_freq, unsigned int amplitude, unsigned int seed) { double f0, w0, sn, cs, bw, alpha, a0; float a1, a2, b0, b1, z0, z1, z2; unsigned int n_lines; unsigned long samples_per_line; unsigned long padding; uint32_t seed32; assert (NULL != raw); assert (NULL != sp); if (unlikely (!_vbi_sampling_par_valid_log (sp, /* log */ NULL))) return FALSE; switch (sp->sp_sample_format) { #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_YUV444: case VBI_PIXFMT_YVU444: case VBI_PIXFMT_YUV422: case VBI_PIXFMT_YVU422: case VBI_PIXFMT_YUV411: case VBI_PIXFMT_YVU411: case VBI_PIXFMT_YVU420: case VBI_PIXFMT_YUV410: case VBI_PIXFMT_YVU410: case VBI_PIXFMT_Y8: #endif case VBI_PIXFMT_YUV420: break; default: return FALSE; } if (unlikely (sp->sampling_rate <= 0)) return FALSE; /* Biquad bandpass filter. http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */ f0 = ((double) min_freq + max_freq) * 0.5; if (f0 <= 0.0) return TRUE; w0 = 2 * M_PI * f0 / sp->sampling_rate; sincos (w0, &sn, &cs); bw = fabs (log2 (MAX (min_freq, max_freq) / f0)); alpha = sn * sinh (log (2) / 2 * bw * w0 / sn); a0 = 1 + alpha; a1 = 2 * cs / a0; a2 = (alpha - 1) / a0; b0 = sn / (2*a0); b1 = 0; if (amplitude > 256) amplitude = 256; n_lines = sp->count[0] + sp->count[1]; #if 2 == VBI_VERSION_MINOR if (unlikely (0 == amplitude || 0 == n_lines || 0 == sp->bytes_per_line)) return TRUE; samples_per_line = sp->bytes_per_line; padding = 0; #else if (unlikely (0 == amplitude || 0 == n_lines || 0 == sp->samples_per_line)) return TRUE; samples_per_line = sp->samples_per_line; padding = sp->bytes_per_line - samples_per_line; #endif seed32 = seed; z1 = 0; z2 = 0; do { uint8_t *raw_end = raw + samples_per_line; do { int noise; /* We use our own simple PRNG to produce predictable results for tests. */ seed32 = seed32 * 1103515245u + 12345; noise = ((seed32 / 65536) % (amplitude * 2 + 1)) - amplitude; z0 = noise + a1 * z1 + a2 * z2; noise = (int)(b0 * (z0 - z2) + b1 * z1); z2 = z1; z1 = z0; *raw++ = SATURATE (*raw + noise, 0, 255); } while (raw < raw_end); raw += padding; } while (--n_lines > 0); return TRUE; } static vbi_bool signal_u8 (uint8_t * raw, const vbi_sampling_par *sp, int blank_level, int black_level, int white_level, unsigned int flags, const vbi_sliced * sliced, unsigned int n_sliced_lines, const char * caller) { unsigned int n_scan_lines; unsigned int samples_per_line; n_scan_lines = sp->count[0] + sp->count[1]; samples_per_line = SAMPLES_PER_LINE (sp); clear_image (raw, SATURATE (blank_level, 0, 255), samples_per_line, n_scan_lines, sp->bytes_per_line); for (; n_sliced_lines-- > 0; ++sliced) { unsigned int row; uint8_t *raw1; if (0 == sliced->line) { goto bounds; } else if (0 != sp->start[1] && sliced->line >= (unsigned int) sp->start[1]) { row = sliced->line - sp->start[1]; if (row >= (unsigned int) sp->count[1]) goto bounds; if (sp->interlaced) { row = row * 2 + !(flags & _VBI_RAW_SWAP_FIELDS); } else if (0 == (flags & _VBI_RAW_SWAP_FIELDS)) { row += sp->count[0]; } } else if (0 != sp->start[0] && sliced->line >= (unsigned int) sp->start[0]) { row = sliced->line - sp->start[0]; if (row >= (unsigned int) sp->count[0]) goto bounds; if (sp->interlaced) { row *= 2 + !!(flags & _VBI_RAW_SWAP_FIELDS); } else if (flags & _VBI_RAW_SWAP_FIELDS) { row += sp->count[0]; } } else { bounds: warning (caller, "Sliced line %u out of bounds.", sliced->line); return FALSE; } raw1 = raw + row * sp->bytes_per_line; switch (sliced->id) { case VBI_SLICED_TELETEXT_A: /* ok? */ signal_teletext (raw1, sp, black_level, /* amplitude */ .7 * (white_level - black_level), /* bit_rate */ 25 * 625 * 397, /* FRC */ 0xE7, /* payload */ 37, sliced); break; case VBI_SLICED_TELETEXT_B_L10_625: case VBI_SLICED_TELETEXT_B_L25_625: case VBI_SLICED_TELETEXT_B: signal_teletext (raw1, sp, black_level, .66 * (white_level - black_level), 25 * 625 * 444, 0x27, 42, sliced); break; case VBI_SLICED_TELETEXT_C_625: signal_teletext (raw1, sp, black_level, .7 * (white_level - black_level), 25 * 625 * 367, 0xE7, 33, sliced); break; case VBI_SLICED_TELETEXT_D_625: signal_teletext (raw1, sp, black_level, .7 * (white_level - black_level), 5642787, 0xA7, 34, sliced); break; case VBI_SLICED_CAPTION_625_F1: case VBI_SLICED_CAPTION_625_F2: case VBI_SLICED_CAPTION_625: signal_closed_caption (raw1, sp, blank_level, white_level, flags, 25 * 625 * 32, sliced); break; case VBI_SLICED_VPS: case VBI_SLICED_VPS_F2: signal_vps (raw1, sp, black_level, white_level, sliced); break; case VBI_SLICED_WSS_625: signal_wss_625 (raw1, sp, black_level, white_level, sliced); break; case VBI_SLICED_TELETEXT_B_525: signal_teletext (raw1, sp, black_level, /* amplitude */ .7 * (white_level - black_level), /* bit_rate */ 5727272, /* FRC */ 0x27, /* payload */ 34, sliced); break; case VBI_SLICED_TELETEXT_C_525: signal_teletext (raw1, sp, black_level, .7 * (white_level - black_level), 5727272, 0xE7, 33, sliced); break; case VBI_SLICED_TELETEXT_D_525: signal_teletext (raw1, sp, black_level, .7 * (white_level - black_level), 5727272, 0xA7, 34, sliced); break; case VBI_SLICED_CAPTION_525_F1: case VBI_SLICED_CAPTION_525_F2: case VBI_SLICED_CAPTION_525: signal_closed_caption (raw1, sp, blank_level, white_level, flags, 30000 * 525 * 32 / 1001, sliced); break; default: warning (caller, "Service 0x%08x (%s) not supported.", sliced->id, vbi_sliced_name (sliced->id)); return FALSE; } } return TRUE; } vbi_bool _vbi_raw_vbi_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int white_level, unsigned int flags, const vbi_sliced * sliced, unsigned int n_sliced_lines) { unsigned int n_scan_lines; unsigned int black_level; if (unlikely (!_vbi_sampling_par_valid_log (sp, NULL))) return FALSE; n_scan_lines = sp->count[0] + sp->count[1]; if (unlikely (n_scan_lines * sp->bytes_per_line > raw_size)) { warning (__FUNCTION__, "(%u + %u lines) * %lu bytes_per_line " "> %lu raw_size.", sp->count[0], sp->count[1], (unsigned long) sp->bytes_per_line, raw_size); return FALSE; } if (unlikely (0 != white_level && blank_level > white_level)) { warning (__FUNCTION__, "Invalid blanking %d or peak white level %d.", blank_level, white_level); } if (SYSTEM_525 (sp)) { /* Observed value. */ const unsigned int peak = 200; /* 255 */ if (0 == white_level) { blank_level = (int)(40.0 * peak / 140); black_level = (int)(47.5 * peak / 140); white_level = peak; } else { black_level = (int)(blank_level + 7.5 * (white_level - blank_level)); } } else { const unsigned int peak = 200; /* 255 */ if (0 == white_level) { blank_level = (int)(43.0 * peak / 140); white_level = peak; } black_level = blank_level; } return signal_u8 (raw, sp, blank_level, black_level, white_level, flags, sliced, n_sliced_lines, __FUNCTION__); } #define RGBA_TO_RGB16(value) \ (+(((value) & 0xF8) >> (3 - 0)) \ +(((value) & 0xFC00) >> (10 - 5)) \ +(((value) & 0xF80000) >> (19 - 11))) #define RGBA_TO_RGBA15(value) \ (+(((value) & 0xF8) >> (3 - 0)) \ +(((value) & 0xF800) >> (11 - 5)) \ +(((value) & 0xF80000) >> (19 - 10)) \ +(((value) & 0x80000000) >> (31 - 15))) #define RGBA_TO_ARGB15(value) \ (+(((value) & 0xF8) >> (3 - 1)) \ +(((value) & 0xF800) >> (11 - 6)) \ +(((value) & 0xF80000) >> (19 - 11)) \ +(((value) & 0x80000000) >> (31 - 0))) #define RGBA_TO_RGBA12(value) \ (+(((value) & 0xF0) >> (4 - 0)) \ +(((value) & 0xF000) >> (12 - 4)) \ +(((value) & 0xF00000) >> (20 - 8)) \ +(((value) & 0xF0000000) >> (28 - 12))) #define RGBA_TO_ARGB12(value) \ (+(((value) & 0xF0) << -(4 - 12)) \ +(((value) & 0xF000) >> (12 - 8)) \ +(((value) & 0xF00000) >> (20 - 4)) \ +(((value) & 0xF0000000) >> (28 - 0))) #define RGBA_TO_RGB8(value) \ (+(((value) & 0xE0) >> (5 - 0)) \ +(((value) & 0xE000) >> (13 - 3)) \ +(((value) & 0xC00000) >> (22 - 6))) #define RGBA_TO_BGR8(value) \ (+(((value) & 0xE0) >> (5 - 5)) \ +(((value) & 0xE000) >> (13 - 2)) \ +(((value) & 0xC00000) >> (22 - 0))) #define RGBA_TO_RGBA7(value) \ (+(((value) & 0xC0) >> (6 - 0)) \ +(((value) & 0xE000) >> (13 - 2)) \ +(((value) & 0xC00000) >> (22 - 5)) \ +(((value) & 0x80000000) >> (31 - 7))) #define RGBA_TO_ARGB7(value) \ (+(((value) & 0xC0) >> (6 - 6)) \ +(((value) & 0xE000) >> (13 - 3)) \ +(((value) & 0xC00000) >> (22 - 1)) \ +(((value) & 0x80000000) >> (31 - 0))) #define MST1(d, val, mask) (d) = ((d) & ~(mask)) | ((val) & (mask)) #define MST2(d, val, mask) (d) = ((d) & (mask)) | (val) #define SCAN_LINE_TO_N(conv, n) \ do { \ for (i = 0; i < samples_per_line; ++i) { \ uint8_t *dd = d + i * (n); \ unsigned int value = s[i] * 0x01010101; \ unsigned int mask = ~pixel_mask; \ \ value = conv (value) & pixel_mask; \ MST2 (dd[0], value >> 0, mask >> 0); \ if (n >= 2) \ MST2 (dd[1], value >> 8, mask >> 8); \ if (n >= 3) \ MST2 (dd[2], value >> 16, mask >> 16); \ if (n >= 4) \ MST2 (dd[3], value >> 24, mask >> 24); \ } \ } while (0) #define SCAN_LINE_TO_RGB2(conv, endian) \ do { \ for (i = 0; i < samples_per_line; ++i) { \ uint8_t *dd = d + i * 2; \ unsigned int value = s[i] * 0x01010101; \ unsigned int mask; \ \ value = conv (value) & pixel_mask; \ mask = ~pixel_mask; \ MST2 (dd[0 + endian], value >> 0, mask >> 0); \ MST2 (dd[1 - endian], value >> 8, mask >> 8); \ } \ } while (0) vbi_bool _vbi_raw_video_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int black_level, int white_level, unsigned int pixel_mask, unsigned int flags, const vbi_sliced * sliced, unsigned int n_sliced_lines) { unsigned int n_scan_lines; unsigned int samples_per_line; vbi_sampling_par sp8; unsigned int size; uint8_t *buf; uint8_t *s; uint8_t *d; if (unlikely (!_vbi_sampling_par_valid_log (sp, NULL))) return FALSE; n_scan_lines = sp->count[0] + sp->count[1]; if (unlikely (n_scan_lines * sp->bytes_per_line > raw_size)) { warning (__FUNCTION__, "%u + %u lines * %lu bytes_per_line > %lu raw_size.", sp->count[0], sp->count[1], (unsigned long) sp->bytes_per_line, raw_size); return FALSE; } if (unlikely (0 != white_level && (blank_level > black_level || black_level > white_level))) { warning (__FUNCTION__, "Invalid blanking %d, black %d or peak " "white level %d.", blank_level, black_level, white_level); } switch (sp->sp_sample_format) { #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_YVUA24_LE: /* 0xAAUUVVYY */ case VBI_PIXFMT_YVU24_LE: /* 0x00UUVVYY */ #endif case VBI_PIXFMT_YVYU: case VBI_PIXFMT_VYUY: /* 0xAAUUVVYY */ pixel_mask = (+ ((pixel_mask & 0xFF00) << 8) + ((pixel_mask & 0xFF0000) >> 8) + ((pixel_mask & 0xFF0000FF))); break; #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_YUVA24_BE: /* 0xYYUUVVAA */ #endif case VBI_PIXFMT_RGBA24_BE: /* 0xRRGGBBAA */ pixel_mask = SWAB32 (pixel_mask); break; #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_YVUA24_BE: /* 0xYYVVUUAA */ pixel_mask = (+ ((pixel_mask & 0xFF) << 24) + ((pixel_mask & 0xFFFF00)) + ((pixel_mask & 0xFF000000) >> 24)); break; case VBI_PIXFMT_YUV24_BE: /* 0xAAYYUUVV */ case VBI_PIXFMT_ARGB24_BE: /* 0xAARRGGBB */ case VBI_PIXFMT_BGRA12_LE: case VBI_PIXFMT_BGRA12_BE: case VBI_PIXFMT_ABGR12_LE: case VBI_PIXFMT_ABGR12_BE: case VBI_PIXFMT_BGRA7: case VBI_PIXFMT_ABGR7: #endif case VBI_PIXFMT_BGR24_LE: /* 0x00RRGGBB */ case VBI_PIXFMT_BGRA15_LE: case VBI_PIXFMT_BGRA15_BE: case VBI_PIXFMT_ABGR15_LE: case VBI_PIXFMT_ABGR15_BE: pixel_mask = (+ ((pixel_mask & 0xFF) << 16) + ((pixel_mask & 0xFF0000) >> 16) + ((pixel_mask & 0xFF00FF00))); break; #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_YVU24_BE: /* 0x00YYVVUU */ pixel_mask = (+ ((pixel_mask & 0xFF) << 16) + ((pixel_mask & 0xFFFF00) >> 8)); break; #endif case VBI_PIXFMT_BGRA24_BE: /* 0xBBGGRRAA */ pixel_mask = (+ ((pixel_mask & 0xFFFFFF) << 8) + ((pixel_mask & 0xFF000000) >> 24)); break; default: break; } switch (sp->sp_sample_format) { case VBI_PIXFMT_RGB16_LE: case VBI_PIXFMT_RGB16_BE: case VBI_PIXFMT_BGR16_LE: case VBI_PIXFMT_BGR16_BE: pixel_mask = RGBA_TO_RGB16 (pixel_mask); break; case VBI_PIXFMT_RGBA15_LE: case VBI_PIXFMT_RGBA15_BE: case VBI_PIXFMT_BGRA15_LE: case VBI_PIXFMT_BGRA15_BE: pixel_mask = RGBA_TO_RGBA15 (pixel_mask); break; case VBI_PIXFMT_ARGB15_LE: case VBI_PIXFMT_ARGB15_BE: case VBI_PIXFMT_ABGR15_LE: case VBI_PIXFMT_ABGR15_BE: pixel_mask = RGBA_TO_ARGB15 (pixel_mask); break; #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_RGBA12_LE: case VBI_PIXFMT_RGBA12_BE: case VBI_PIXFMT_BGRA12_LE: case VBI_PIXFMT_BGRA12_BE: pixel_mask = RGBA_TO_RGBA12 (pixel_mask); break; case VBI_PIXFMT_ARGB12_LE: case VBI_PIXFMT_ARGB12_BE: case VBI_PIXFMT_ABGR12_LE: case VBI_PIXFMT_ABGR12_BE: pixel_mask = RGBA_TO_ARGB12 (pixel_mask); break; case VBI_PIXFMT_RGB8: pixel_mask = RGBA_TO_RGB8 (pixel_mask); break; case VBI_PIXFMT_BGR8: pixel_mask = RGBA_TO_BGR8 (pixel_mask); break; case VBI_PIXFMT_RGBA7: case VBI_PIXFMT_BGRA7: pixel_mask = RGBA_TO_RGBA7 (pixel_mask); break; case VBI_PIXFMT_ARGB7: case VBI_PIXFMT_ABGR7: pixel_mask = RGBA_TO_ARGB7 (pixel_mask); break; #endif default: break; } if (0 == pixel_mask) { /* Done! :-) */ return TRUE; } /* ITU-R BT.601 sampling assumed. */ if (SYSTEM_525 (sp)) { if (0 == white_level) { /* Cutting off the bottom of the signal confuses the vbi_bit_slicer (can't adjust the threshold fast enough), probably other decoders as well. */ blank_level = 5; /* 16 - 40 * 220 / 100; */ black_level = 16; white_level = 16 + 219; } } else { if (0 == white_level) { /* Observed values: 30-30-280 (WSS PAL) -? */ blank_level = 5; /* 16 - 43 * 220 / 100; */ black_level = 16; white_level = 16 + 219; } } sp8 = *sp; samples_per_line = SAMPLES_PER_LINE (sp); #if 3 == VBI_VERSION_MINOR sp8.sample_format = VBI_PIXFMT_Y8; #else sp8.sampling_format = VBI_PIXFMT_YUV420; #endif sp8.bytes_per_line = samples_per_line * 1 /* bpp */; size = n_scan_lines * samples_per_line; buf = vbi_malloc (size); if (NULL == buf) { error (NULL, "Out of memory."); errno = ENOMEM; return FALSE; } if (!signal_u8 (buf, &sp8, blank_level, black_level, white_level, flags, sliced, n_sliced_lines, __FUNCTION__)) { vbi_free (buf); return FALSE; } s = buf; d = raw; while (n_scan_lines-- > 0) { unsigned int i; switch (sp->sp_sample_format) { #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_NONE: case VBI_PIXFMT_RESERVED0: case VBI_PIXFMT_RESERVED1: case VBI_PIXFMT_RESERVED2: case VBI_PIXFMT_RESERVED3: break; case VBI_PIXFMT_YUV444: case VBI_PIXFMT_YVU444: case VBI_PIXFMT_YUV422: case VBI_PIXFMT_YVU422: case VBI_PIXFMT_YUV411: case VBI_PIXFMT_YVU411: case VBI_PIXFMT_YVU420: case VBI_PIXFMT_YUV410: case VBI_PIXFMT_YVU410: case VBI_PIXFMT_Y8: #endif #if 2 == VBI_VERSION_MINOR case VBI_PIXFMT_PAL8: #endif case VBI_PIXFMT_YUV420: for (i = 0; i < samples_per_line; ++i) MST1 (d[i], s[i], pixel_mask); break; #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_YUVA24_LE: case VBI_PIXFMT_YVUA24_LE: case VBI_PIXFMT_YUVA24_BE: case VBI_PIXFMT_YVUA24_BE: #endif case VBI_PIXFMT_RGBA24_LE: case VBI_PIXFMT_RGBA24_BE: case VBI_PIXFMT_BGRA24_LE: case VBI_PIXFMT_BGRA24_BE: SCAN_LINE_TO_N (+, 4); break; #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_YUV24_LE: case VBI_PIXFMT_YUV24_BE: case VBI_PIXFMT_YVU24_LE: case VBI_PIXFMT_YVU24_BE: #endif case VBI_PIXFMT_RGB24_LE: case VBI_PIXFMT_BGR24_LE: SCAN_LINE_TO_N (+, 3); break; case VBI_PIXFMT_YUYV: case VBI_PIXFMT_YVYU: for (i = 0; i < samples_per_line; i += 2) { uint8_t *dd = d + i * 2; unsigned int uv = (s[i] + s[i + 1] + 1) >> 1; MST1 (dd[0], s[i], pixel_mask); MST1 (dd[1], uv, pixel_mask >> 8); MST1 (dd[2], s[i + 1], pixel_mask); MST1 (dd[3], uv, pixel_mask >> 16); } break; case VBI_PIXFMT_UYVY: case VBI_PIXFMT_VYUY: for (i = 0; i < samples_per_line; i += 2) { uint8_t *dd = d + i * 2; unsigned int uv = (s[i] + s[i + 1] + 1) >> 1; MST1 (dd[0], uv, pixel_mask >> 8); MST1 (dd[1], s[i], pixel_mask); MST1 (dd[2], uv, pixel_mask >> 16); MST1 (dd[3], s[i + 1], pixel_mask); } break; case VBI_PIXFMT_RGB16_LE: case VBI_PIXFMT_BGR16_LE: SCAN_LINE_TO_RGB2 (RGBA_TO_RGB16, 0); break; case VBI_PIXFMT_RGB16_BE: case VBI_PIXFMT_BGR16_BE: SCAN_LINE_TO_RGB2 (RGBA_TO_RGB16, 1); break; case VBI_PIXFMT_RGBA15_LE: case VBI_PIXFMT_BGRA15_LE: SCAN_LINE_TO_RGB2 (RGBA_TO_RGBA15, 0); break; case VBI_PIXFMT_RGBA15_BE: case VBI_PIXFMT_BGRA15_BE: SCAN_LINE_TO_RGB2 (RGBA_TO_RGBA15, 1); break; case VBI_PIXFMT_ARGB15_LE: case VBI_PIXFMT_ABGR15_LE: SCAN_LINE_TO_RGB2 (RGBA_TO_ARGB15, 0); break; case VBI_PIXFMT_ARGB15_BE: case VBI_PIXFMT_ABGR15_BE: SCAN_LINE_TO_RGB2 (RGBA_TO_ARGB15, 1); break; #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_RGBA12_LE: case VBI_PIXFMT_BGRA12_LE: SCAN_LINE_TO_RGB2 (RGBA_TO_RGBA12, 0); break; case VBI_PIXFMT_RGBA12_BE: case VBI_PIXFMT_BGRA12_BE: SCAN_LINE_TO_RGB2 (RGBA_TO_RGBA12, 1); break; case VBI_PIXFMT_ARGB12_LE: case VBI_PIXFMT_ABGR12_LE: SCAN_LINE_TO_RGB2 (RGBA_TO_ARGB12, 0); break; case VBI_PIXFMT_ARGB12_BE: case VBI_PIXFMT_ABGR12_BE: SCAN_LINE_TO_RGB2 (RGBA_TO_ARGB12, 1); break; case VBI_PIXFMT_RGB8: SCAN_LINE_TO_N (RGBA_TO_RGB8, 1); break; case VBI_PIXFMT_BGR8: SCAN_LINE_TO_N (RGBA_TO_BGR8, 1); break; case VBI_PIXFMT_RGBA7: case VBI_PIXFMT_BGRA7: SCAN_LINE_TO_N (RGBA_TO_RGBA7, 1); break; case VBI_PIXFMT_ARGB7: case VBI_PIXFMT_ABGR7: SCAN_LINE_TO_N (RGBA_TO_ARGB7, 1); break; #endif /* 3 == VBI_VERSION_MINOR */ } s += sp8.bytes_per_line; d += sp->bytes_per_line; } vbi_free (buf); return TRUE; } /** * @example examples/rawout.c * Raw VBI output example. */ /** * @param raw A raw VBI image will be stored here. * @param raw_size Size of the @a raw buffer in bytes. The buffer * must be large enough for @a sp->count[0] + count[1] lines * of @a sp->bytes_per_line each, with @a sp->samples_per_line * (in libzvbi 0.2.x @a sp->bytes_per_line) bytes actually written. * @param sp Describes the raw VBI data to generate. @a sp->sampling_format * must be @c VBI_PIXFMT_Y8 (@c VBI_PIXFMT_YUV420 with libzvbi 0.2.x). * @a sp->synchronous is ignored. Note for compatibility in libzvbi * 0.2.x vbi_sampling_par is a synonym of vbi_raw_decoder, but the * (private) decoder fields in this structure are ignored. * @param blank_level The level of the horizontal blanking in the raw * VBI image. Must be <= @a white_level. * @param white_level The peak white level in the raw VBI image. Set to * zero to get the default blanking and white level. * @param swap_fields If @c TRUE the second field will be stored first * in the @c raw buffer. Note you can also get an interlaced image * by setting @a sp->interlaced to @c TRUE. @a sp->synchronous is * ignored. * @param sliced Pointer to an array of vbi_sliced containing the * VBI data to be encoded. * @param n_sliced_lines Number of elements in the @a sliced array. * * This function basically reverses the operation of the vbi_raw_decoder, * taking sliced VBI data and generating a raw VBI image similar to those * you would get from raw VBI sampling hardware. The following data services * are currently supported: All Teletext services, VPS, WSS 625, Closed * Caption 525 and 625. * * The function encodes sliced data as is, e.g. without adding or * checking parity bits, without checking if the line number is correct * for the respective data service, or if the signal will fit completely * in the given space (@a sp->offset and @a sp->samples_per_line at * @a sp->sampling_rate). * * Apart of the payload the generated video signal is invariable and * attempts to be faithful to related standards. You can only change the * characteristics of the assumed capture device. Sync pulses and color * bursts and not generated if the sampling parameters extend to this area. * * @note * This function is mainly intended for testing purposes. It is optimized * for accuracy, not for speed. * * @returns * @c FALSE if the @a raw_size is too small, if the @a sp sampling * parameters are invalid, if the signal levels are invalid, * if the @a sliced array contains unsupported services or line numbers * outside the @a sp sampling parameters. * * @since 0.2.22 */ vbi_bool vbi_raw_vbi_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int white_level, vbi_bool swap_fields, const vbi_sliced * sliced, unsigned int n_sliced_lines) { return _vbi_raw_vbi_image (raw, raw_size, sp, blank_level, white_level, swap_fields ? _VBI_RAW_SWAP_FIELDS : 0, sliced, n_sliced_lines); } /** * @param raw A raw VBI image will be stored here. * @param raw_size Size of the @a raw buffer in bytes. The buffer * must be large enough for @a sp->count[0] + count[1] lines * of @a sp->bytes_per_line each, with @a sp->samples_per_line * times bytes per pixel (in libzvbi 0.2.x @a sp->bytes_per_line) * actually written. * @param sp Describes the raw VBI data to generate. Note for * compatibility in libzvbi 0.2.x vbi_sampling_par is a synonym of * vbi_raw_decoder, but the (private) decoder fields in this * structure are ignored. * @param blank_level The level of the horizontal blanking in the raw * VBI image. Must be <= @a black_level. * @param black_level The black level in the raw VBI image. Must be * <= @a white_level. * @param white_level The peak white level in the raw VBI image. Set to * zero to get the default blanking, black and white level. * @param pixel_mask This mask selects which color or alpha channel * shall contain VBI data. Depending on @a sp->sampling_format it is * interpreted as 0xAABBGGRR or 0xAAVVUUYY. A value of 0x000000FF * for example writes data in "red bits", not changing other * bits in the @a raw buffer. When the @a sp->sampling_format is a * planar YUV the function writes the Y plane only. * @param swap_fields If @c TRUE the second field will be stored first * in the @c raw buffer. Note you can also get an interlaced image * by setting @a sp->interlaced to @c TRUE. @a sp->synchronous is * ignored. * @param sliced Pointer to an array of vbi_sliced containing the * VBI data to be encoded. * @param n_sliced_lines Number of elements in the @a sliced array. * * Generates a raw VBI image similar to those you get from video * capture hardware. Otherwise identical to vbi_raw_vbi_image(). * * @returns * @c FALSE if the @a raw_size is too small, if the @a sp sampling * parameters are invalid, if the signal levels are invalid, * if the @a sliced array contains unsupported services or line numbers * outside the @a sp sampling parameters. * * @since 0.2.22 */ vbi_bool vbi_raw_video_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int black_level, int white_level, unsigned int pixel_mask, vbi_bool swap_fields, const vbi_sliced * sliced, unsigned int n_sliced_lines) { return _vbi_raw_video_image (raw, raw_size, sp, blank_level, black_level, white_level, pixel_mask, swap_fields ? _VBI_RAW_SWAP_FIELDS : 0, sliced, n_sliced_lines); } /* Capture interface */ #if 3 == VBI_VERSION_MINOR # include "io-priv.h" #else # include "inout.h" #endif #include "hamm.h" #define MAGIC 0xd804289c struct buffer { char * data; unsigned int size; unsigned int capacity; }; typedef struct { vbi_capture cap; unsigned int magic; vbi_sampling_par sp; vbi3_raw_decoder * rd; vbi_bool decode_raw; vbi_capture_buffer raw_buffer; size_t raw_f1_size; size_t raw_f2_size; uint8_t * desync_buffer[2]; unsigned int desync_i; double capture_time; int64_t stream_time; vbi_capture_buffer sliced_buffer; vbi_sliced sliced[50]; unsigned int teletext_page; unsigned int teletext_row; struct buffer caption_buffers[2]; unsigned int caption_i; uint8_t vps_buffer[13]; uint8_t wss_buffer[2]; unsigned int noise_min_freq; unsigned int noise_max_freq; unsigned int noise_amplitude; unsigned int noise_seed; unsigned int flags; } vbi_capture_sim; unsigned int _vbi_capture_sim_get_flags (vbi_capture * cap) { vbi_capture_sim *sim; assert (NULL != cap); sim = PARENT (cap, vbi_capture_sim, cap); assert (MAGIC == sim->magic); return sim->flags; } void _vbi_capture_sim_set_flags (vbi_capture * cap, unsigned int flags) { vbi_capture_sim *sim; assert (NULL != cap); sim = PARENT (cap, vbi_capture_sim, cap); assert (MAGIC == sim->magic); sim->flags = flags; } /** * @param cap Initialized vbi_capture context opened with * vbi_capture_sim_new(). * @param min_freq Minimum frequency of the noise in Hz. * @param max_freq Maximum frequency of the noise in Hz. @a min_freq and * @a max_freq define the cut off frequency at the half power points * (gain -3 dB). * @param amplitude Maximum amplitude of the noise, should lie in range * 0 to 256. * * This function shapes the white noise to be added to simulated raw VBI * data. By default no noise is added. To disable the noise set * @a amplitude to zero. * * To produce realistic noise @a min_freq = 0, @a max_freq = 5e6 and * @a amplitude = 20 to 50 seems appropriate. * * @since 0.2.26 */ void vbi_capture_sim_add_noise (vbi_capture * cap, unsigned int min_freq, unsigned int max_freq, unsigned int amplitude) { vbi_capture_sim *sim; assert (NULL != cap); sim = PARENT (cap, vbi_capture_sim, cap); assert (MAGIC == sim->magic); if (0 == max_freq) amplitude = 0; sim->noise_min_freq = min_freq; sim->noise_max_freq = max_freq; sim->noise_amplitude = amplitude; sim->noise_seed = 123456789; } static vbi_bool extend_buffer (struct buffer * b, unsigned int new_capacity) { char *new_data; new_data = vbi_realloc (b->data, new_capacity); if (NULL == new_data) return FALSE; b->data = new_data; b->capacity = new_capacity; return TRUE; } static const char caption_default_test_stream [] = "" "LIBZVBI CAPTION SIMULATION CC1." "" "LIBZVBI CAPTION SIMULATION CC2." "" "LIBZVBI CAPTION SIMULATION CC3." "" "LIBZVBI CAPTION SIMULATION CC4." ; /* TODO: regression test for repeated control code bug: */ static vbi_bool get_attr (unsigned int * value, const char * s, const char * name, unsigned int default_value, unsigned int minimum, unsigned int maximum) { unsigned long u; unsigned int len; vbi_bool present; present = FALSE; u = default_value; len = strlen (name); for (; 0 != *s && '>' != *s; ++s) { int delta; if (!isalpha (*s)) continue; delta = strncmp (s, name, len); if (0 == delta) { s += len; } else { while (isalnum (*s)) ++s; } while (isspace (*s++)) ; if ('=' != s[-1] || '"' != *s) break; if (0 == delta) { present = TRUE; u = strtoul (s + 1, NULL, 0); break; } do ++s; while (0 != *s && '"' != *s); } *value = SATURATE (u, (unsigned long) minimum, (unsigned long) maximum); return present; } static vbi_bool caption_append_zeroes (vbi_capture_sim * sim, vbi_pgno channel, unsigned int n_bytes) { struct buffer *b; b = &sim->caption_buffers[((channel - 1) >> 1) & 1]; if (b->size + n_bytes > b->capacity) { unsigned int new_capacity; new_capacity = b->capacity + ((n_bytes + 255) & ~255); if (!extend_buffer (b, new_capacity)) return FALSE; } memset (b->data + b->size, 0x80, n_bytes); b->size += n_bytes; return TRUE; } static unsigned int caption_append_command (vbi_capture_sim * sim, unsigned int * inout_ch, const char * s) { static const _vbi_key_value_pair elements [] = { { "aof", 0x1422 }, { "aon", 0x1423 }, { "bao", 0x102E }, { "bas", 0x102F }, { "bbo", 0x1024 }, { "bbs", 0x1025 }, { "bco", 0x1026 }, { "bcs", 0x1027 }, { "bgo", 0x1022 }, { "bgs", 0x1023 }, { "bmo", 0x102C }, { "bms", 0x102D }, { "bro", 0x1028 }, { "brs", 0x1029 }, { "bs", 0x1421 }, { "bt", 0x172D }, { "bwo", 0x1020 }, { "bws", 0x1021 }, { "byo", 0x102A }, { "bys", 0x102B }, { "cmd", 0x0001 }, { "cr", 0x142D }, { "der", 0x1424 }, { "edm", 0x142C }, { "enm", 0x142E }, { "eoc", 0x142F }, { "ext2", 0x1200 }, { "ext3", 0x1300 }, { "fa", 0x172E }, { "fau", 0x172F }, { "fon", 0x1428 }, { "mr", 0x1120 }, { "pac", 0x1040 }, { "pause", 0x0002 }, { "rcl", 0x1420 }, { "rdc", 0x1429 }, { "rtd", 0x142B }, { "ru2", 0x1425 }, { "ru3", 0x1426 }, { "ru4", 0x1427 }, { "spec", 0x1130 }, { "sync", 0x0003 }, { "to1", 0x1721 }, { "to2", 0x1722 }, { "to3", 0x1723 }, { "tr", 0x142A }, }; static const int row_code [16] = { 0x1140, 0x1160, 0x1240, 0x1260, 0x1540, 0x1560, 0x1640, 0x1660, 0x1740, 0x1760, 0x1040, 0x1340, 0x1360, 0x1440, 0x1460, -1 }; struct buffer *b; int value; unsigned int cmd; unsigned int n_frames; unsigned int u_value; int n_padding_bytes; unsigned int i; vbi_bool parity; if (!_vbi_keyword_lookup (&value, &s, elements, N_ELEMENTS (elements))) return TRUE; get_attr (inout_ch, s, "ch", *inout_ch, 1, 4); cmd = value | (((*inout_ch - 1) & 1) << 11); parity = TRUE; switch (value) { case 1: /* cmd */ get_attr (&cmd, s, "code", 0, 0, 0xFFFF); parity = FALSE; break; case 2: /* pause */ get_attr (&n_frames, s, "frames", 60, 1, INT_MAX); if (n_frames > 120 * 60 * 30) return TRUE; return caption_append_zeroes (sim, *inout_ch, n_frames * 2); case 3: /* sync */ n_padding_bytes = sim->caption_buffers[0].size - sim->caption_buffers[1].size; if (0 == n_padding_bytes) { return TRUE; } else if (n_padding_bytes < 0) { return caption_append_zeroes (sim, 0, n_padding_bytes); } else { return caption_append_zeroes (sim, 2, n_padding_bytes); } case 0x1040: /* preamble address code */ if (get_attr (&u_value, s, "column", 1, 1, 32)) { cmd |= 0x0010 | (((u_value - 1) / 4) << 1); } else { get_attr (&u_value, s, "color", 0, 0, 7); cmd |= u_value << 1; } get_attr (&u_value, s, "row", 15, 1, 15); cmd |= row_code[u_value - 1]; get_attr (&u_value, s, "u", 0, 0, 1); cmd |= u_value; break; case 0x1120: /* midrow code */ get_attr (&u_value, s, "color", 0, 0, 7); cmd |= u_value << 1; get_attr (&u_value, s, "u", 0, 0, 1); cmd |= u_value; break; case 0x1130: /* special character */ get_attr (&u_value, s, "code", 0, 0, 15); cmd |= u_value; break; case 0x1200: /* extended character set */ case 0x1300: get_attr (&u_value, s, "code", 32, 32, 63); cmd |= u_value; break; case 0x1420: /* rcl */ case 0x1421: /* bs */ case 0x1422: /* aof */ case 0x1423: /* aon */ case 0x1424: /* der */ case 0x1425: /* ru3 */ case 0x1426: /* ru4 */ case 0x1427: /* ru5 */ case 0x1428: /* fon */ case 0x1429: /* rdc */ case 0x142A: /* tr */ case 0x142B: /* rtd */ case 0x142C: /* edm */ case 0x142D: /* cr */ case 0x142E: /* enm */ case 0x142F: /* eoc */ /* Field bit (EIA 608-B Sec. 8.4, 8.5). */ cmd |= ((*inout_ch - 1) & 2) << 7; default: break; } b = &sim->caption_buffers[((*inout_ch - 1) >> 1) & 1]; i = b->size; if (i + 3 > b->capacity) { if (!extend_buffer (b, b->capacity + 256)) return FALSE; } if (i & 1) b->data[i++] = 0x80; if (likely (parity)) { b->data[i] = vbi_par8 (cmd >> 8); b->data[i + 1] = vbi_par8 (cmd); } else { /* To test error checks. */ b->data[i] = cmd >> 8; b->data[i + 1] = cmd; } b->size = i + 2; return TRUE; } vbi_bool vbi_capture_sim_load_caption (vbi_capture * cap, const char * stream, vbi_bool append) { vbi_capture_sim *sim; struct buffer *b; unsigned int ch; const char *s; assert (NULL != cap); sim = PARENT (cap, vbi_capture_sim, cap); assert (MAGIC == sim->magic); if (!append) { vbi_free (sim->caption_buffers[0].data); vbi_free (sim->caption_buffers[1].data); CLEAR (sim->caption_buffers); sim->caption_i = 0; } if (NULL == stream) return TRUE; ch = 1; /* CC1, T1 */ b = &sim->caption_buffers[0]; for (s = stream;;) { int c = *s++; if (0 == c) { break; } else if (c < 0x20) { continue; } else if ('&' == c) { if ('#' == *s) { char *end; c = strtoul (s + 1, &end, 10); s = end; if (';' == *s) ++s; } else if (0 == strncmp (s, "amp;", 4)) { s += 4; } else if (0 == strncmp (s, "lt;", 3)) { s += 3; c = '<'; } else if (0 == strncmp (s, "gt;", 3)) { s += 3; c = '>'; } else if (0 == strncmp (s, "ts;", 3)) { /* Transparent space. */ if (!caption_append_command (sim, &ch, "")) return FALSE; continue; } } else if ('<' == c) { int delimiter; if (!caption_append_command (sim, &ch, s)) return FALSE; b = &sim->caption_buffers[((ch - 1) >> 1) & 1]; /* Skip until '>', except between quotes. */ delimiter = '>'; for (; 0 != *s && delimiter != *s; ++s) { if ('"' == *s) delimiter ^= '>'; } if (0 != *s) ++s; /* skip delimiter */ continue; } if (b->size >= b->capacity) { unsigned int check_buffer_size = (b->capacity + 256); if (b->capacity > check_buffer_size) return FALSE; if (!extend_buffer (b, check_buffer_size)) return FALSE; } b->data[b->size++] = vbi_par8 (c); } return TRUE; } static void gen_caption (vbi_capture_sim * sim, vbi_sliced ** inout_sliced, vbi_service_set service_set, unsigned int line) { vbi_sliced *s; struct buffer *b; unsigned int i; b = &sim->caption_buffers[(line > 200)]; i = sim->caption_i; if (i + 1 < b->size) { s = *inout_sliced; *inout_sliced = s + 1; s->id = service_set; s->line = line; s->data[0] = b->data[i]; s->data[1] = b->data[i + 1]; } } static void gen_teletext_b_row (vbi_capture_sim * sim, uint8_t return_buf[45]) { static uint8_t s1[2][10] = { { 0x02, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15 }, { 0x02, 0x15, 0x02, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15 } }; static uint8_t s2[32] = "100\2LIBZVBI\7 00:00:00"; static uint8_t s3[40] = " LIBZVBI TELETEXT SIMULATION "; static uint8_t s4[40] = " Page 100 "; static uint8_t s5[10][42] = { { 0x02, 0x2f, 0x97, 0x20, 0x37, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0xb5, 0x20 }, { 0xc7, 0x2f, 0x97, 0x0d, 0xb5, 0x04, 0x20, 0x9d, 0x83, 0x8c, 0x08, 0x2a, 0x2a, 0x2a, 0x89, 0x20, 0x20, 0x0d, 0x54, 0x45, 0xd3, 0x54, 0x20, 0xd0, 0xc1, 0xc7, 0x45, 0x8c, 0x20, 0x20, 0x08, 0x2a, 0x2a, 0x2a, 0x89, 0x0d, 0x20, 0x20, 0x1c, 0x97, 0xb5, 0x20 }, { 0x02, 0xd0, 0x97, 0x20, 0xb5, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xea, 0x20 }, { 0xc7, 0xd0, 0x97, 0x20, 0xb5, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xb5, 0x20 }, { 0x02, 0xc7, 0x97, 0x20, 0xb5, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x15, 0x1a, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x97, 0x19, 0xb5, 0x20 }, { 0xc7, 0xc7, 0x97, 0x20, 0xb5, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xb5, 0x20 }, { 0x02, 0x8c, 0x97, 0x9e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x13, 0x7f, 0x7f, 0x7f, 0x7f, 0x16, 0x7f, 0x7f, 0x7f, 0x7f, 0x92, 0x7f, 0x92, 0x7f, 0x7f, 0x15, 0x7f, 0x7f, 0x15, 0x7f, 0x91, 0x91, 0x7f, 0x7f, 0x91, 0x94, 0x7f, 0x94, 0x7f, 0x94, 0x97, 0xb5, 0x20 }, { 0xc7, 0x8c, 0x97, 0x9e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x13, 0x7f, 0x7f, 0x7f, 0x7f, 0x16, 0x7f, 0x7f, 0x7f, 0x7f, 0x92, 0x7f, 0x7f, 0x7f, 0x7f, 0x15, 0x7f, 0x7f, 0x7f, 0x7f, 0x91, 0x7f, 0x7f, 0x7f, 0x7f, 0x94, 0x7f, 0x7f, 0x7f, 0x7f, 0x97, 0xb5, 0x20 }, { 0x02, 0x9b, 0x97, 0x9e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x13, 0x7f, 0x7f, 0x7f, 0x7f, 0x16, 0x7f, 0x7f, 0x7f, 0x7f, 0x92, 0x7f, 0x7f, 0x7f, 0x7f, 0x15, 0x7f, 0x7f, 0x7f, 0x7f, 0x91, 0x7f, 0x7f, 0x7f, 0x7f, 0x94, 0x7f, 0x7f, 0x7f, 0x7f, 0x97, 0xb5, 0x20 }, { 0xc7, 0x9b, 0x97, 0x20, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0xa1, 0x20 } }; unsigned int i; return_buf[0] = 0x55; return_buf[1] = 0x55; return_buf[2] = 0x27; if (sim->teletext_row >= 13) sim->teletext_row = 0; switch (sim->teletext_row) { case 0: memcpy (return_buf + 3, s1[sim->teletext_page], 10); sim->teletext_page ^= 1; for (i = 0; i < 32; ++i) return_buf[13 + i] = vbi_par8 (s2[i]); break; case 1: return_buf[3] = 0x02; return_buf[4] = 0x02; for (i = 0; i < 40; ++i) return_buf[5 + i] = vbi_par8 (s3[i]); break; case 2: return_buf[3] = 0x02; return_buf[4] = 0x49; for (i = 0; i < 40; ++i) return_buf[5 + i] = vbi_par8 (s4[i]); break; default: memcpy (return_buf + 3, s5[sim->teletext_row - 3], 42); break; } ++sim->teletext_row; } static void gen_teletext_b (vbi_capture_sim * sim, vbi_sliced ** inout_sliced, vbi_sliced * sliced_end, unsigned int line) { uint8_t buf[45]; vbi_sliced *s; s = *inout_sliced; if (s >= sliced_end) return; s->id = VBI_SLICED_TELETEXT_B; s->line = line; gen_teletext_b_row (sim, buf); memcpy (&s->data, buf + 3, 42); *inout_sliced = s + 1; } static unsigned int gen_sliced_525 (vbi_capture_sim * sim) { vbi_sliced *s; unsigned int i; s = sim->sliced; assert (N_ELEMENTS (sim->sliced) >= 4); if (0) { for (i = 0; i < N_ELEMENTS (s->data); ++i) s->data[i] = rand (); s[1] = s[0]; s[2] = s[0]; s[0].id = VBI_SLICED_TELETEXT_B_525; s[0].line = 10; s[1].id = VBI_SLICED_TELETEXT_C_525; s[1].line = 11; s[2].id = VBI_SLICED_TELETEXT_D_525; s[2].line = 12; s += 3; } if (sim->caption_buffers[0].size > 0) gen_caption (sim, &s, VBI_SLICED_CAPTION_525, 21); if (sim->caption_buffers[1].size > 0) gen_caption (sim, &s, VBI_SLICED_CAPTION_525, 284); sim->caption_i += 2; if (sim->caption_i >= sim->caption_buffers[0].size && sim->caption_i >= sim->caption_buffers[1].size) sim->caption_i = 0; return s - sim->sliced; } #if 3 == VBI_VERSION_MINOR vbi_bool vbi_capture_sim_load_vps (vbi_capture * cap, const vbi_program_id *pid) { vbi_capture_sim *sim; vbi_program_id pid2; assert (NULL != cap); sim = PARENT (cap, vbi_capture_sim, cap); assert (MAGIC == sim->magic); if (NULL == pid) { CLEAR (pid2); pid2.cni_type = VBI_CNI_TYPE_VPS; pid2.channel = VBI_PID_CHANNEL_VPS; pid2.pil = VBI_PIL_TIMER_CONTROL; pid = &pid2; } return vbi_encode_vps_pdc (sim->vps_buffer, pid); } vbi_bool vbi_capture_sim_load_wss_625 (vbi_capture * cap, const vbi_aspect_ratio *ar) { vbi_capture_sim *sim; vbi_aspect_ratio ar2; assert (NULL != cap); sim = PARENT (cap, vbi_capture_sim, cap); assert (MAGIC == sim->magic); if (NULL == ar) { CLEAR (ar2); ar = &ar2; } return vbi_encode_wss_625 (sim->wss_buffer, ar); } #endif /* 3 == VBI_VERSION_MINOR */ static unsigned int gen_sliced_625 (vbi_capture_sim * sim) { vbi_sliced *s; vbi_sliced *end; unsigned int i; s = sim->sliced; end = &sim->sliced[N_ELEMENTS (sim->sliced)]; assert (N_ELEMENTS (sim->sliced) >= 5); if (0) { for (i = 0; i < N_ELEMENTS (s->data); ++i) s->data[i] = rand (); s[1] = s[0]; s[2] = s[0]; s[0].id = VBI_SLICED_TELETEXT_A; s[0].line = 6; s[1].id = VBI_SLICED_TELETEXT_C_625; s[1].line = 7; s[2].id = VBI_SLICED_TELETEXT_D_625; s[2].line = 8; s += 3; } gen_teletext_b (sim, &s, end - 3, 9); gen_teletext_b (sim, &s, end - 3, 10); gen_teletext_b (sim, &s, end - 3, 11); gen_teletext_b (sim, &s, end - 3, 12); gen_teletext_b (sim, &s, end - 3, 13); gen_teletext_b (sim, &s, end - 3, 14); gen_teletext_b (sim, &s, end - 3, 15); s->id = VBI_SLICED_VPS; s->line = 16; assert (sizeof (s->data) >= sizeof (sim->vps_buffer)); memcpy (s->data, sim->vps_buffer, sizeof (sim->vps_buffer)); ++s; gen_teletext_b (sim, &s, end - 2, 19); gen_teletext_b (sim, &s, end - 2, 20); gen_teletext_b (sim, &s, end - 2, 21); if (sim->caption_buffers[0].size > 0) gen_caption (sim, &s, VBI_SLICED_CAPTION_625, 22); sim->caption_i += 2; if (sim->caption_i >= sim->caption_buffers[0].size) sim->caption_i = 0; s->id = VBI_SLICED_WSS_625; s->line = 23; assert (sizeof (s->data) >= sizeof (sim->wss_buffer)); memcpy (s->data, sim->wss_buffer, sizeof (sim->wss_buffer)); ++s; gen_teletext_b (sim, &s, end, 320); gen_teletext_b (sim, &s, end, 321); gen_teletext_b (sim, &s, end, 322); gen_teletext_b (sim, &s, end, 323); gen_teletext_b (sim, &s, end, 324); gen_teletext_b (sim, &s, end, 325); gen_teletext_b (sim, &s, end, 326); gen_teletext_b (sim, &s, end, 327); gen_teletext_b (sim, &s, end, 328); gen_teletext_b (sim, &s, end, 332); gen_teletext_b (sim, &s, end, 333); gen_teletext_b (sim, &s, end, 334); gen_teletext_b (sim, &s, end, 335); return s - sim->sliced; } /** * @param cap Initialized vbi_capture context opened with * vbi_capture_sim_new(). * @param enable @c TRUE to enable decoding of the simulated raw * VBI data. * * By default this module generates sliced VBI data and converts it * to raw VBI data, returning both through the read functions. With * this function you can enable decoding of the raw VBI data back * to sliced VBI data, which is mainly interesting to test the * libzvbi bit slicer and raw VBI decoder. * * @since 0.2.22 */ void vbi_capture_sim_decode_raw (vbi_capture * cap, vbi_bool enable) { vbi_capture_sim *sim; assert (NULL != cap); sim = PARENT (cap, vbi_capture_sim, cap); assert (MAGIC == sim->magic); sim->decode_raw = !!enable; } static void copy_field (uint8_t * dst, const uint8_t * src, unsigned int height, unsigned long bytes_per_line) { while (height-- > 0) { memcpy (dst, src, bytes_per_line); dst += bytes_per_line; src += bytes_per_line * 2; } } static void delay_raw_data (vbi_capture_sim * sim, uint8_t * raw_data) { unsigned int i; /* Delay the raw VBI data by one field. */ i = sim->desync_i; if (sim->sp.interlaced) { assert (sim->sp.count[0] == sim->sp.count[1]); copy_field (sim->desync_buffer[i ^ 1], raw_data + sim->sp.bytes_per_line, sim->sp.count[0], sim->sp.bytes_per_line); copy_field (raw_data + sim->sp.bytes_per_line, raw_data, sim->sp.count[0], sim->sp.bytes_per_line); copy_field (raw_data, sim->desync_buffer[i], sim->sp.count[0], sim->sp.bytes_per_line); } else { memcpy (sim->desync_buffer[i ^ 1], raw_data + sim->raw_f1_size, sim->raw_f2_size); memmove (raw_data + sim->raw_f2_size, raw_data, sim->raw_f1_size); memcpy (raw_data, sim->desync_buffer[i], sim->raw_f2_size); } sim->desync_i = i ^ 1; } static vbi_bool sim_read (vbi_capture * cap, vbi_capture_buffer ** raw, vbi_capture_buffer ** sliced, const struct timeval * timeout) { vbi_capture_sim *sim = PARENT (cap, vbi_capture_sim, cap); unsigned int n_lines; timeout = timeout; n_lines = 0; if (NULL != raw || NULL != sliced) { if (SYSTEM_525 (&sim->sp)) { n_lines = gen_sliced_525 (sim); } else { n_lines = gen_sliced_625 (sim); } } if (NULL != raw) { uint8_t *raw_data; vbi_bool success; if (NULL == *raw) { /* Return our buffer. */ *raw = &sim->raw_buffer; raw_data = sim->raw_buffer.data; } else { /* XXX check max size here, after the API required clients to pass one. */ raw_data = (*raw)->data; (*raw)->size = sim->raw_buffer.size; } (*raw)->timestamp = sim->capture_time; memset (raw_data, 0x80, sim->raw_buffer.size); success = _vbi_raw_vbi_image (raw_data, sim->raw_buffer.size, &sim->sp, /* blank_level: default */ 0, /* white_level: default */ 0, sim->flags, sim->sliced, n_lines); assert (success); if (sim->noise_amplitude > 0) { success = vbi_raw_add_noise (raw_data, &sim->sp, sim->noise_min_freq, sim->noise_max_freq, sim->noise_amplitude, sim->noise_seed); assert (success); sim->noise_seed = sim->noise_seed * 1103515245 + 56789; } if (!sim->sp.synchronous) delay_raw_data (sim, raw_data); if (sim->decode_raw) { /* Decode the simulated raw VBI data to test our encoder & decoder. */ memset (sim->sliced, 0xAA, sizeof (sim->sliced)); n_lines = vbi3_raw_decoder_decode (sim->rd, sim->sliced, sizeof (sim->sliced), raw_data); } } if (NULL != sliced) { if (NULL == *sliced) { /* Return our buffer. */ *sliced = &sim->sliced_buffer; } else { /* XXX check max size here, after the API required clients to pass one. */ memcpy ((*sliced)->data, sim->sliced, n_lines * sizeof (sim->sliced[0])); } (*sliced)->size = n_lines * sizeof (sim->sliced[0]); (*sliced)->timestamp = sim->capture_time; } if (SYSTEM_525 (&sim->sp)) { sim->capture_time += 1001 / 30000.0; } else { sim->capture_time += 1 / 25.0; } return TRUE; } static vbi_bool sim_sampling_point (vbi_capture * cap, vbi3_bit_slicer_point *point, unsigned int row, unsigned int nth_bit) { vbi_capture_sim *sim = PARENT (cap, vbi_capture_sim, cap); if (!sim->decode_raw) return FALSE; return vbi3_raw_decoder_sampling_point (sim->rd, point, row, nth_bit); } static vbi_bool sim_debug (vbi_capture * cap, vbi_bool enable) { vbi_capture_sim *sim = PARENT (cap, vbi_capture_sim, cap); return vbi3_raw_decoder_debug (sim->rd, enable); } /* For compatibility in libzvbi 0.2 struct vbi_sampling_par == vbi_raw_decoder. In 0.3 we'll drop the decoding related fields. */ #if 3 == VBI_VERSION_MINOR static const vbi_sampling_par * #else static vbi_raw_decoder * #endif sim_parameters (vbi_capture * cap) { vbi_capture_sim *sim = PARENT (cap, vbi_capture_sim, cap); return &sim->sp; } static int sim_get_fd (vbi_capture * cap) { cap = cap; /* unused */ return -1; /* not available */ } static void sim_delete (vbi_capture * cap) { vbi_capture_sim *sim = PARENT (cap, vbi_capture_sim, cap); vbi_capture_sim_load_caption (cap, /* test_stream */ NULL, /* append */ FALSE); vbi3_raw_decoder_delete (sim->rd); vbi_free (sim->desync_buffer[1]); vbi_free (sim->desync_buffer[0]); vbi_free (sim->raw_buffer.data); CLEAR (*sim); vbi_free (sim); } /** * @param scanning Whether to simulate a device receiving PAL/SECAM * (value 625) or NTSC (525) video. * @param services This parameter must point to a set of @ref VBI_SLICED_ * symbols describing the data services to be simulated. On return the * services actually simulated will be stored here. Currently Teletext * System B, VPS, PAL WSS and PAL/NTSC Closed Caption are supported. * @param interlaced If @c TRUE the simulated raw VBI images will be * interlaced like video images. Otherwise they will contain fields in * sequential order, the first field at the top. Usually real devices * provide sequential images. * @param synchronous If @c FALSE raw VBI images will be delayed by * one field (putting a bottom field first in raw VBI images), simulating * defective hardware. The @a interlaced and @a synchronous parameters * correspond to fields in struct vbi_raw_decoder. * * This function opens a simulated VBI device providing raw and sliced VBI * data. It can be used to test applications in absence of a real device. * * The VBI data is valid but limited. Just one Teletext page and one line * of roll-up caption. The WSS and VPS data is set to defaults, the VPS * contains no CNI. * * @note * The simulation does not run in real time. * Reading from the simulated device will return data immediately. * * @returns * Initialized vbi_capture context, @c NULL on failure (out of memory). * * @since 0.2.22 */ vbi_capture * vbi_capture_sim_new (int scanning, unsigned int * services, vbi_bool interlaced, vbi_bool synchronous) { vbi_capture_sim *sim; vbi_videostd_set videostd_set; vbi_bool success; sim = calloc (1, sizeof (*sim)); if (NULL == sim) { errno = ENOMEM; return NULL; } sim->magic = MAGIC; sim->cap.read = sim_read; sim->cap.parameters = sim_parameters; sim->cap.debug = sim_debug; sim->cap.sampling_point = sim_sampling_point; sim->cap.get_fd = sim_get_fd; sim->cap._delete = sim_delete; sim->capture_time = 0.0; videostd_set = _vbi_videostd_set_from_scanning (scanning); assert (VBI_VIDEOSTD_SET_EMPTY != videostd_set); /* Sampling parameters. */ *services = vbi_sampling_par_from_services (&sim->sp, /* return max_rate */ NULL, videostd_set, *services); if (0 == *services) { goto failure; } sim->sp.interlaced = interlaced; sim->sp.synchronous = synchronous; /* Raw VBI buffer. */ sim->raw_f1_size = sim->sp.bytes_per_line * sim->sp.count[0]; sim->raw_f2_size = sim->sp.bytes_per_line * sim->sp.count[1]; sim->raw_buffer.size = sim->raw_f1_size + sim->raw_f2_size; sim->raw_buffer.data = vbi_malloc (sim->raw_buffer.size); if (NULL == sim->raw_buffer.data) { goto failure; } if (!synchronous) { size_t size; size = sim->sp.bytes_per_line * sim->sp.count[1]; sim->desync_buffer[0] = calloc (1, size); sim->desync_buffer[1] = calloc (1, size); if (NULL == sim->desync_buffer[0] || NULL == sim->desync_buffer[1]) { goto failure; } } /* Sliced VBI buffer. */ sim->sliced_buffer.data = sim->sliced; sim->sliced_buffer.size = sizeof (sim->sliced); /* Raw VBI decoder. */ sim->rd = vbi3_raw_decoder_new (&sim->sp); if (NULL == sim->rd) { goto failure; } vbi3_raw_decoder_add_services (sim->rd, *services, 0); /* Signal simulation. */ #if 3 == VBI_VERSION_MINOR success = vbi_capture_sim_load_vps (&sim->cap, NULL); assert (success); success = vbi_capture_sim_load_wss_625 (&sim->cap, NULL); assert (success); #else { const char vps[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xfc, 0x00, 0x00 }; const char wss[] = { 0x08, 0x06 }; memcpy (sim->vps_buffer, vps, sizeof (vps)); memcpy (sim->wss_buffer, wss, sizeof (wss)); } #endif success = vbi_capture_sim_load_caption (&sim->cap, caption_default_test_stream, /* append */ FALSE); if (!success) { goto failure; } return &sim->cap; failure: sim_delete (&sim->cap); return NULL; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/io-sim.h000066400000000000000000000074451476363111200147260ustar00rootroot00000000000000/* * libzvbi -- VBI device simulation * * Copyright (C) 2004, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: io-sim.h,v 1.11 2013-08-28 14:45:23 mschimek Exp $ */ #ifndef __ZVBI_IO_SIM_H__ #define __ZVBI_IO_SIM_H__ #include "macros.h" #include "version.h" #include "sampling_par.h" #include "inout.h" #if 3 == VBI_VERSION_MINOR # include "aspect_ratio.h" # include "pdc.h" #endif VBI_BEGIN_DECLS /* Public */ /** * @addtogroup Rawenc * @{ */ extern vbi_bool vbi_raw_video_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int black_level, int white_level, unsigned int pixel_mask, vbi_bool swap_fields, const vbi_sliced * sliced, unsigned int n_sliced_lines); extern vbi_bool vbi_raw_add_noise (uint8_t * raw, const vbi_sampling_par *sp, unsigned int min_freq, unsigned int max_freq, unsigned int amplitude, unsigned int seed); extern vbi_bool vbi_raw_vbi_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int white_level, vbi_bool swap_fields, const vbi_sliced * sliced, unsigned int n_sliced_lines); /** @} */ /** * @addtogroup Device * @{ */ extern void vbi_capture_sim_add_noise (vbi_capture * cap, unsigned int min_freq, unsigned int max_freq, unsigned int amplitude); extern vbi_bool vbi_capture_sim_load_caption (vbi_capture * cap, const char * stream, vbi_bool append); #if 3 == VBI_VERSION_MINOR extern vbi_bool vbi_capture_sim_load_vps (vbi_capture * cap, const vbi_program_id *pid); extern vbi_bool vbi_capture_sim_load_wss_625 (vbi_capture * cap, const vbi_aspect_ratio *ar); #endif extern void vbi_capture_sim_decode_raw (vbi_capture * cap, vbi_bool enable); extern vbi_capture * vbi_capture_sim_new (int scanning, unsigned int * services, vbi_bool interlaced, vbi_bool synchronous); /** @} */ /* Private */ extern unsigned int _vbi_capture_sim_get_flags (vbi_capture * cap); extern void _vbi_capture_sim_set_flags (vbi_capture * cap, unsigned int flags); #define _VBI_RAW_SWAP_FIELDS (1 << 0) #define _VBI_RAW_SHIFT_CC_CRI (1 << 1) #define _VBI_RAW_LOW_AMP_CC (1 << 2) /* NB. Currently this flag has no effect in _vbi_raw_*_image(). Call vbi_raw_add_noise() instead. */ #define _VBI_RAW_NOISE_2 (1 << 17) extern vbi_bool _vbi_raw_video_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int black_level, int white_level, unsigned int pixel_mask, unsigned int flags, const vbi_sliced * sliced, unsigned int n_sliced_lines); extern vbi_bool _vbi_raw_vbi_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int white_level, unsigned int flags, const vbi_sliced * sliced, unsigned int n_sliced_lines); VBI_END_DECLS #endif /* __ZVBI_IO_SIM_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/io-v4l.c000066400000000000000000001077351476363111200146410ustar00rootroot00000000000000/* * libzvbi -- Video For Linux driver interface * * Copyright (C) 1999-2004 Michael H. Schimek * Copyright (C) 2003, 2004 Tom Zoerner * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ static const char rcsid [] = "$Id: io-v4l.c,v 1.39 2013-07-02 04:04:04 mschimek Exp $"; #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "misc.h" #include "vbi.h" #include "inout.h" #ifdef ENABLE_V4L #include #include #include #include #include #include /* read(), dup2(), getuid() */ #include #include /* timeval */ #include /* fd_set, uid_t */ #include /* S_ISCHR */ #ifdef HAVE_SYS_IOCTL_H #include /* for (_)videodev.h */ #endif #include #include "videodev.h" #include "_videodev.h" /* This macro checks at compile time if the arg type is correct, device_ioctl() repeats the ioctl if interrupted (EINTR) and logs the args and result if sys_log_fp is non-zero. */ #define xioctl(v, cmd, arg) \ (IOCTL_ARG_TYPE_CHECK_ ## cmd (arg), \ device_ioctl (v->capture.sys_log_fp, fprint_ioctl_arg, v->fd, \ cmd, (void *)(arg))) #define xioctl_fd(v, fd, cmd, arg) \ (IOCTL_ARG_TYPE_CHECK_ ## cmd (arg), \ device_ioctl (v->capture.sys_log_fp, fprint_ioctl_arg, fd, \ cmd, (void *)(arg))) /* Custom ioctl of the bttv driver. */ #define BTTV_VBISIZE _IOR('v' , BASE_VIDIOCPRIVATE+8, int) static __inline__ void IOCTL_ARG_TYPE_CHECK_BTTV_VBISIZE (const int *arg _vbi_unused) {} #undef REQUIRE_SELECT #undef REQUIRE_SVBIFMT /* else accept current parameters */ #undef REQUIRE_VIDEOSTD /* if clueless, assume PAL/SECAM */ #define FLUSH_FRAME_COUNT 2 #define printv(format, args...) \ do { \ if (v->do_trace) { \ fprintf(stderr, "libzvbi: " format ,##args); \ fflush(stderr); \ } \ } while (0) typedef struct vbi_capture_v4l { vbi_capture capture; int fd; vbi_bool has_select; vbi_bool read_active; vbi_bool do_trace; signed char has_s_fmt; struct video_capability vcap; char * p_dev_name; char * p_video_name; int fd_video; vbi_raw_decoder dec; unsigned int services; /* all services, including raw */ double time_per_frame; vbi_capture_buffer *raw_buffer; int num_raw_buffers; vbi_capture_buffer sliced_buffer; int flush_frame_count; } vbi_capture_v4l; static void v4l_read_stop(vbi_capture_v4l *v) { for (; v->num_raw_buffers > 0; v->num_raw_buffers--) { free(v->raw_buffer[v->num_raw_buffers - 1].data); v->raw_buffer[v->num_raw_buffers - 1].data = NULL; } free(v->raw_buffer); v->raw_buffer = NULL; } static int v4l_suspend(vbi_capture_v4l *v) { int fd; v4l_read_stop(v); if (v->read_active) { printv("Suspending read: re-open device...\n"); /* hack: cannot suspend read to allow SVBIFMT, need to close device */ fd = device_open (v->capture.sys_log_fp, v->p_dev_name, O_RDWR, 0); if (-1 == fd) { printv ("v4l2-suspend: failed to re-open " "VBI device: %d: %s\n", errno, strerror(errno)); return -1; } /* use dup2() to keep the same fd, which may be used by our client */ device_close (v->capture.sys_log_fp, v->fd); dup2 (fd, v->fd); device_close (v->capture.sys_log_fp, fd); v->read_active = FALSE; } return 0; } static int v4l_read_alloc(vbi_capture_v4l *v, char ** errstr) { assert(v->raw_buffer == NULL); v->raw_buffer = calloc(1, sizeof(v->raw_buffer[0])); if (v->raw_buffer == NULL) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } v->raw_buffer[0].size = (v->dec.count[0] + v->dec.count[1]) * v->dec.bytes_per_line; v->raw_buffer[0].data = malloc(v->raw_buffer[0].size); if (v->raw_buffer[0].data == NULL) { asprintf(errstr, _("Not enough memory to allocate " "vbi capture buffer (%d KB)."), (v->raw_buffer[0].size + 1023) >> 10); goto failure; } v->num_raw_buffers = 1; printv("Capture buffer allocated: %d bytes\n", v->raw_buffer[0].size); return 0; failure: v4l_read_stop(v); return -1; } static int v4l_read_frame(vbi_capture_v4l *v, vbi_capture_buffer *raw, struct timeval *timeout) { struct timeval tv; int r; if (v->has_select) { tv = *timeout; r = vbi_capture_io_select(v->fd, &tv); if (r <= 0) return r; } v->read_active = TRUE; for (;;) { pthread_testcancel(); r = read(v->fd, raw->data, raw->size); if (r == -1 && (errno == EINTR || errno == ETIME)) continue; if (r == -1) return -1; if (r != raw->size) { errno = EIO; return -1; } else break; } return 1; } static int v4l_read(vbi_capture *vc, vbi_capture_buffer **raw, vbi_capture_buffer **sliced, const struct timeval *timeout_orig) { vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture); vbi_capture_buffer *my_raw = v->raw_buffer; struct timeval tv; int r; if (my_raw == NULL) { printv("read buffer not allocated (must add services first)\n"); errno = EINVAL; return -1; } if (raw == NULL) raw = &my_raw; if (*raw == NULL) *raw = v->raw_buffer; else (*raw)->size = v->raw_buffer[0].size; tv = *timeout_orig; while (1) { r = v4l_read_frame(v, *raw, &tv); if (r <= 0) return r; if (v->flush_frame_count > 0) { v->flush_frame_count -= 1; printv("Skipping frame (%d remaining)\n", v->flush_frame_count); } else break; } gettimeofday(&tv, NULL); (*raw)->timestamp = tv.tv_sec + tv.tv_usec * (1 / 1e6); if (sliced) { int lines; if (*sliced) { lines = vbi_raw_decode(&v->dec, (*raw)->data, (vbi_sliced *)(*sliced)->data); } else { *sliced = &v->sliced_buffer; lines = vbi_raw_decode(&v->dec, (*raw)->data, (vbi_sliced *)(v->sliced_buffer.data)); } (*sliced)->size = lines * sizeof(vbi_sliced); (*sliced)->timestamp = (*raw)->timestamp; } return 1; } static void v4l_flush(vbi_capture *vc) { vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture); struct timeval tv; int fd_flags = 0; int r; if ( (v->raw_buffer == NULL) || (v->read_active == FALSE) ) return; v->flush_frame_count = FLUSH_FRAME_COUNT; if (v->has_select) { memset(&tv, 0, sizeof(tv)); r = vbi_capture_io_select(v->fd, &tv); if (r <= 0) return; } if (v->has_select == FALSE) { fd_flags = fcntl(v->fd, F_GETFL, NULL); if (fd_flags == -1) return; /* no select supported by driver -> make read non-blocking */ if ((fd_flags & O_NONBLOCK) == 0) { fcntl(v->fd, F_SETFL, fd_flags | O_NONBLOCK); } } r = read(v->fd, v->raw_buffer->data, v->raw_buffer->size); if ((v->has_select == FALSE) && ((fd_flags & O_NONBLOCK) == 0)) { fcntl(v->fd, F_SETFL, fd_flags); } } /* Molto rumore per nulla. */ #include #include #include static void perm_check(vbi_capture_v4l *v, const char *name) { struct stat st; int old_errno = errno; uid_t uid = geteuid(); gid_t gid = getegid(); if (stat(name, &st) == -1) { printv("stat %s failed: %d, %s\n", name, errno, strerror(errno)); errno = old_errno; return; } printv("%s permissions: user=%d.%d mode=0%o, I am %d.%d\n", name, st.st_uid, st.st_gid, st.st_mode, uid, gid); errno = old_errno; } static vbi_bool reverse_lookup(vbi_capture_v4l *v, int fd, struct stat *vbi_stat) { struct video_capability vcap; struct video_unit vunit; CLEAR (vcap); if (-1 == xioctl_fd (v, fd, VIDIOCGCAP, &vcap)) { printv ("Driver doesn't support VIDIOCGCAP, " "probably not V4L API\n"); return FALSE; } if (!(vcap.type & VID_TYPE_CAPTURE)) { printv("Driver is no video capture device\n"); return FALSE; } CLEAR (vunit); if (-1 == xioctl_fd (v, fd, VIDIOCGUNIT, &vunit)) { printv ("Driver doesn't support VIDIOCGUNIT\n"); return FALSE; } if (vunit.vbi != (int) minor(vbi_stat->st_rdev)) { printv("Driver reports vbi minor %d, need %d\n", vunit.vbi, minor(vbi_stat->st_rdev)); return FALSE; } printv("Matched\n"); return TRUE; } static void set_scanning_from_mode(vbi_capture_v4l *v, int mode, int * strict) { switch (mode) { case VIDEO_MODE_NTSC: printv("Videostandard is NTSC\n"); v->dec.scanning = 525; break; case VIDEO_MODE_PAL: case VIDEO_MODE_SECAM: printv("Videostandard is PAL/SECAM\n"); v->dec.scanning = 625; break; default: /* * One last chance, we'll try to guess * the scanning if GVBIFMT is available. */ printv("Videostandard unknown (%d)\n", mode); v->dec.scanning = 0; *strict = TRUE; break; } } static vbi_bool get_videostd(vbi_capture_v4l *v, int fd, int *mode) { struct video_tuner vtuner; struct video_channel vchan; CLEAR (vtuner); CLEAR (vchan); if (0 == xioctl_fd (v, fd, VIDIOCGTUNER, &vtuner)) { printv ("Driver supports VIDIOCGTUNER: " "mode %d (0=PAL, 1=NTSC, 2=SECAM)\n", vtuner.mode); *mode = vtuner.mode; return TRUE; } else if (0 == xioctl_fd (v, fd, VIDIOCGCHAN, &vchan)) { printv ("Driver supports VIDIOCGCHAN: norm %d\n", vchan.norm); *mode = vchan.norm; return TRUE; } else printv("Driver doesn't support VIDIOCGTUNER or VIDIOCGCHAN\n"); return FALSE; } static int probe_video_device(vbi_capture_v4l *v, const char *name, struct stat *vbi_stat ) { struct stat vid_stat; int video_fd; if (stat(name, &vid_stat) == -1) { printv("stat failed: %d, %s\n", errno, strerror(errno)); return -1; } if (!S_ISCHR(vid_stat.st_mode)) { printv("%s is no character special file\n", name); return -1; } if (major(vid_stat.st_rdev) != major(vbi_stat->st_rdev)) { printv("Mismatch of major device number: " "%s: %d, %d; vbi: %d, %d\n", name, major(vid_stat.st_rdev), minor(vid_stat.st_rdev), major(vbi_stat->st_rdev), minor(vbi_stat->st_rdev)); return -1; } /* when radio device is opened a running video capture is destroyed (v4l2) @TZO@ */ if (minor(vid_stat.st_rdev) >= 64) { printv("Not a v4l video minor device number (i.e. >= 64): " "%s: %d, %d\n", name, major(vid_stat.st_rdev), minor(vid_stat.st_rdev)); return -1; } video_fd = device_open (v->capture.sys_log_fp, name, O_RDWR, 0); if (-1 == video_fd) { printv ("Cannot open %s: %d, %s\n", name, errno, strerror(errno)); perm_check(v, name); return -1; } if (!reverse_lookup(v, video_fd, vbi_stat)) { device_close (v->capture.sys_log_fp, video_fd); return -1; } return video_fd; } static vbi_bool xopendir (const char * name, DIR ** dir, struct dirent ** dirent) { int saved_errno; long int size; int fd; *dir = opendir (name); if (NULL == *dir) return FALSE; fd = dirfd (*dir); if (-1 == fd) goto failure; size = fpathconf (fd, _PC_NAME_MAX); if (size <= 0) goto failure; size = MAX (size, (long int) sizeof ((*dirent)->d_name)); size += sizeof (**dirent) - sizeof ((*dirent)->d_name) + 1; *dirent = calloc (1, size); if (NULL == *dirent) goto failure; return TRUE; failure: saved_errno = errno; closedir (*dir); *dir = NULL; errno = saved_errno; return FALSE; } static int open_video_dev(vbi_capture_v4l *v, struct stat *p_vbi_stat, vbi_bool do_dev_scan) { static const char * const video_devices[] = { "/dev/video", "/dev/video0", "/dev/video1", "/dev/video2", "/dev/video3", "/dev/v4l/video", "/dev/v4l/video0", "/dev/v4l/video1", "/dev/v4l/video2", "/dev/v4l/video3", }; struct dirent *dirent; DIR *dir; int video_fd; unsigned int i; video_fd = -1; for (i = 0; i < sizeof(video_devices) / sizeof(video_devices[0]); i++) { printv("Try %s: ", video_devices[i]); video_fd = probe_video_device(v, video_devices[i], p_vbi_stat); if (video_fd != -1) { v->p_video_name = strdup(video_devices[i]); goto done; } } if (do_dev_scan) { /* @TOMZO@ note: this is insane - dev directory has typically ~4000 nodes */ printv("Traversing /dev\n"); if (!xopendir ("/dev", &dir, &dirent)) { printv ("Cannot open /dev: %d, %s\n", errno, strerror (errno)); perm_check (v, "/dev"); goto done; } while (dirent == readdir (dir)) { char name[sizeof(dirent->d_name)+sizeof("/dev/")-1]; snprintf (name, sizeof(name), "/dev/%s", dirent->d_name); printv("Try %s: ", name); video_fd = probe_video_device(v, name, p_vbi_stat); if (video_fd != -1) { v->p_video_name = strdup(name); free (dirent); closedir (dir); goto done; } } printv("Traversing finished\n"); free (dirent); closedir (dir); } errno = ENOENT; done: return video_fd; } static vbi_bool guess_bttv_v4l(vbi_capture_v4l *v, int *strict, int given_fd, int scanning) { struct stat vbi_stat; int video_fd; int mode = -1; if (scanning) { v->dec.scanning = scanning; return TRUE; } printv("Attempt to guess the videostandard\n"); if (get_videostd(v, v->fd, &mode)) goto finish; /* * Bttv v4l has no VIDIOCGUNIT pointing back to * the associated video device, now it's getting * dirty. We'll walk /dev, first level of, and * assume v4l major is still 81. Not tested with devfs. */ printv("Attempt to find a reverse VIDIOCGUNIT\n"); if (fstat(v->fd, &vbi_stat) == -1) { printv("fstat failed: %d, %s\n", errno, strerror(errno)); goto finish; } if (!S_ISCHR(vbi_stat.st_mode)) { printv("VBI device is no character special file, reject\n"); return FALSE; } if (major(vbi_stat.st_rdev) != 81) { printv("VBI device CSF has major number %d, expect 81\n" "Warning: will assume this is still a v4l device\n", major(vbi_stat.st_rdev)); goto finish; } printv("VBI device type verified\n"); if (given_fd > -1) { printv("Try suggested corresponding video fd\n"); if (reverse_lookup(v, given_fd, &vbi_stat)) { if (get_videostd(v, given_fd, &mode)) v->fd_video = given_fd; goto finish; } } /* find video device path and open the device */ video_fd = open_video_dev(v, &vbi_stat, TRUE); if (video_fd != -1) { if (get_videostd(v, video_fd, &mode)) { device_close (v->capture.sys_log_fp, video_fd); return FALSE; } device_close (v->capture.sys_log_fp, video_fd); } finish: set_scanning_from_mode(v, mode, strict); return TRUE; } static vbi_bool v4l_update_scanning(vbi_capture_v4l *v, int * p_strict) { int video_fd; int mode = -1; vbi_bool result = FALSE; if ( get_videostd(v, v->fd, &mode) ) { result = TRUE; } else if (v->p_video_name != NULL) { video_fd = device_open (v->capture.sys_log_fp, v->p_video_name, O_RDWR, 0); if (-1 != video_fd) { if (get_videostd(v, video_fd, &mode)) { result = TRUE; } device_close (v->capture.sys_log_fp, video_fd); } else { printv("Failed to open video device '%d': %s", errno, strerror(errno)); } } else if (v->fd_video != -1) { if (get_videostd(v, v->fd_video, &mode)) { result = TRUE; } } if (result) set_scanning_from_mode(v, mode, p_strict); return result; } static int v4l_get_scanning(vbi_capture *vc) { vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture); int strict; int old_scanning = v->dec.scanning; int new_scanning = -1; if ( v4l_update_scanning(v, &strict) ) { new_scanning = v->dec.scanning; } v->dec.scanning = old_scanning; printv("Guessed video standard %d\n", new_scanning); return new_scanning; } static vbi_bool set_parameters(vbi_capture_v4l *v, struct vbi_format *p_vfmt, int *p_max_rate, unsigned int *services, int strict, char **errstr) { struct vbi_format vfmt_temp; vbi_raw_decoder dec_temp; unsigned int sup_services; /* check if the driver supports CSVBIFMT: try with unchanged parameters */ if (v->has_s_fmt == -1) { vfmt_temp = *p_vfmt; v->has_s_fmt = (0 == xioctl (v, VIDIOCSVBIFMT, &vfmt_temp) || errno == EBUSY); printv ("Driver does%s support VIDIOCSVBIFMT\n", v->has_s_fmt ? "" : " not"); } if (v->has_s_fmt == 0) return TRUE; /* Speculative, vbi_format is not documented */ printv("Attempt to set vbi capture parameters\n"); memset(&dec_temp, 0, sizeof(dec_temp)); sup_services = vbi_raw_decoder_parameters(&dec_temp, *services | v->services, dec_temp.scanning, p_max_rate); if ((sup_services & *services) == 0) { asprintf(errstr, _("Sorry, %s (%s) cannot capture any of the " "requested data services."), v->p_dev_name, v->vcap.name); return FALSE; } *services &= sup_services; vfmt_temp = *p_vfmt; memset(p_vfmt, 0, sizeof(*p_vfmt)); p_vfmt->sample_format = VIDEO_PALETTE_RAW; p_vfmt->sampling_rate = dec_temp.sampling_rate; p_vfmt->samples_per_line = dec_temp.bytes_per_line; p_vfmt->start[0] = dec_temp.start[0]; p_vfmt->count[0] = dec_temp.count[1]; p_vfmt->start[1] = dec_temp.start[0]; p_vfmt->count[1] = dec_temp.count[1]; /* Single field allowed? */ if (!p_vfmt->count[0]) { p_vfmt->start[0] = (dec_temp.scanning == 625) ? 6 : 10; p_vfmt->count[0] = 1; } else if (!p_vfmt->count[1]) { p_vfmt->start[1] = (dec_temp.scanning == 625) ? 318 : 272; p_vfmt->count[1] = 1; } if (0 == xioctl (v, VIDIOCSVBIFMT, p_vfmt)) return TRUE; p_vfmt->sampling_rate = vfmt_temp.sampling_rate; p_vfmt->samples_per_line = vfmt_temp.samples_per_line; if (0 == xioctl (v, VIDIOCSVBIFMT, p_vfmt)) return TRUE; /* XXX correct count */ p_vfmt->start[0] = vfmt_temp.start[0]; p_vfmt->start[1] = vfmt_temp.start[1]; if (0 == xioctl (v, VIDIOCSVBIFMT, p_vfmt)) return TRUE; switch (errno) { case EBUSY: #ifndef REQUIRE_SVBIFMT printv("VIDIOCSVBIFMT returned EBUSY, " "will try the current parameters\n"); *p_vfmt = vfmt_temp; return TRUE; #endif asprintf(errstr, _("Cannot initialize %s (%s), " "the device is already in use."), v->p_dev_name, v->vcap.name); break; case EINVAL: if (strict < 2) { printv("VIDIOCSVBIFMT returned EINVAL, " "will try the current parameters\n"); *p_vfmt = vfmt_temp; return TRUE; } break; default: asprintf(errstr, _("Could not set the vbi " "capture parameters for %s (%s): %s."), v->p_dev_name, v->vcap.name, strerror(errno)); /* guess = _("Maybe a bug in the driver or libzvbi."); */ break; } return FALSE; } static vbi_raw_decoder * v4l_parameters(vbi_capture *vc) { vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture); return &v->dec; } static void v4l_delete(vbi_capture *vc) { vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture); v4l_read_stop(v); vbi_raw_decoder_destroy(&v->dec); if (v->sliced_buffer.data) free(v->sliced_buffer.data); if (v->p_dev_name != NULL) free(v->p_dev_name); if (v->p_video_name != NULL) free(v->p_video_name); if (-1 != v->fd) device_close (v->capture.sys_log_fp, v->fd); free(v); } static VBI_CAPTURE_FD_FLAGS v4l_get_fd_flags(vbi_capture *vc) { vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture); VBI_CAPTURE_FD_FLAGS result; result = VBI_FD_IS_DEVICE; if (v->has_select) result |= VBI_FD_HAS_SELECT; return result; } static vbi_bool v4l_set_video_path(vbi_capture *vc, const char * p_dev_video) { vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture); if (v->p_video_name != NULL) free(v->p_video_name); v->p_video_name = strdup(p_dev_video); return TRUE; } static int v4l_get_fd(vbi_capture *vc) { vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture); return v->fd; } static void print_vfmt(const char *s, struct vbi_format *vfmt) { fprintf(stderr, "%sformat %08x, %d Hz, %d bpl, " "F1 %d+%d, F2 %d+%d, flags %08x\n", s, vfmt->sample_format, vfmt->sampling_rate, vfmt->samples_per_line, vfmt->start[0], vfmt->count[0], vfmt->start[1], vfmt->count[1], vfmt->flags); } static unsigned int v4l_update_services(vbi_capture *vc, vbi_bool reset, vbi_bool commit, unsigned int services, int strict, char ** errstr) { vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture); struct vbi_format vfmt; int max_rate; max_rate = 0; /* suspend capturing, or driver will return EBUSY */ v4l_suspend(v); if (reset) { v4l_update_scanning(v, &strict); vbi_raw_decoder_reset(&v->dec); v->services = 0; } CLEAR (vfmt); if (0 == xioctl (v, VIDIOCGVBIFMT, &vfmt)) { if (vfmt.start[1] > 0 && vfmt.count[1]) { if (vfmt.start[1] >= 286) v->dec.scanning = 625; else v->dec.scanning = 525; } printv("Driver supports VIDIOCGVBIFMT, " "guessed videostandard %d\n", v->dec.scanning); if (v->do_trace) print_vfmt("VBI capture parameters supported: ", &vfmt); if (strict >= 0 && v->dec.scanning) if (!set_parameters(v, &vfmt, &max_rate, &services, strict, errstr)) goto failure; if (v->do_trace) print_vfmt("VBI capture parameters granted: ", &vfmt); printv("Accept current vbi parameters\n"); if (vfmt.sample_format != VIDEO_PALETTE_RAW) { asprintf(errstr, _("%s (%s) offers unknown vbi " "sampling format #%d. " "This may be a driver bug " "or libzvbi is too old."), v->p_dev_name, v->vcap.name, vfmt.sample_format); goto io_error; } /* grow pattern array if necessary ** note: must do this even if service add fails later, to stay in sync with driver */ vbi_raw_decoder_resize(&v->dec, vfmt.start, vfmt.count); v->dec.sampling_rate = vfmt.sampling_rate; v->dec.bytes_per_line = vfmt.samples_per_line; if (v->dec.scanning == 625) /* v->dec.offset = (int)(10.2e-6 * vfmt.sampling_rate); */ v->dec.offset = (int)(6.8e-6 * vfmt.sampling_rate); else if (v->dec.scanning == 525) v->dec.offset = (int)(9.2e-6 * vfmt.sampling_rate); else /* we don't know */ v->dec.offset = (int)(9.7e-6 * vfmt.sampling_rate); v->dec.start[0] = vfmt.start[0]; v->dec.count[0] = vfmt.count[0]; v->dec.start[1] = vfmt.start[1]; v->dec.count[1] = vfmt.count[1]; v->dec.interlaced = !!(vfmt.flags & VBI_INTERLACED); v->dec.synchronous = !(vfmt.flags & VBI_UNSYNC); v->time_per_frame = (v->dec.scanning == 625) ? 1.0 / 25 : 1001.0 / 30000; /* Unknown. */ v->has_select = FALSE; } else { int size; /* * If a more reliable method exists to identify the bttv * driver I'll be glad to hear about it. Lesson: Don't * call a v4l private IOCTL without knowing who's * listening. All we know at this point: It's a csf, and * it may be a v4l device. * garetxe: This isn't reliable, bttv doesn't return * anything useful in vcap.name. */ printv("Driver doesn't support VIDIOCGVBIFMT (errno %d), " "will assume bttv interface\n", errno); /* bttv 0.7.x has no select. 0.8+ supports VIDIOCGVBIFMT. */ v->has_select = FALSE; if (0 && !strstr(v->vcap.name, "bttv") && !strstr(v->vcap.name, "BTTV")) { asprintf(errstr, _("Cannot capture with %s (%s), " "has no standard vbi interface."), v->p_dev_name, v->vcap.name); goto io_error; } v->dec.bytes_per_line = 2048; v->dec.interlaced = FALSE; v->dec.synchronous = TRUE; printv("Attempt to determine vbi frame size\n"); size = xioctl (v, BTTV_VBISIZE, 0); if (-1 == size) { printv ("Driver does not support BTTV_VBISIZE, " "assume old BTTV driver\n"); v->dec.count[0] = 16; v->dec.count[1] = 16; } else if (size % 2048) { asprintf (errstr, _("Cannot identify %s (%s), reported " "vbi frame size suggests this is " "not a bttv driver."), v->p_dev_name, v->vcap.name); goto io_error; } else { printv ("Driver supports BTTV_VBISIZE: %d bytes, " "assume top field dominance and 2048 bpl\n", size); size /= 2048; v->dec.count[0] = size >> 1; v->dec.count[1] = size - v->dec.count[0]; } switch (v->dec.scanning) { default: #ifdef REQUIRE_VIDEOSTD asprintf(errstr, _("Cannot set or determine current " "videostandard of %s (%s)."), v->p_dev_name, v->vcap.name); goto io_error; #endif printv("Warning: Videostandard not confirmed, " "will assume PAL/SECAM\n"); v->dec.scanning = 625; /* fall through */ case 625: /* Not confirmed */ v->dec.sampling_rate = 35468950; v->dec.offset = (int)(9.2e-6 * 35468950); v->dec.start[0] = 22 + 1 - v->dec.count[0]; v->dec.start[1] = 335 + 1 - v->dec.count[1]; break; case 525: /* Confirmed for bttv 0.7.52 */ v->dec.sampling_rate = 28636363; v->dec.offset = (int)(9.2e-6 * 28636363); v->dec.start[0] = 10; v->dec.start[1] = 273; break; } v->time_per_frame = (v->dec.scanning == 625) ? 1.0 / 25 : 1001.0 / 30000; } v->dec.sampling_format = VBI_PIXFMT_YUV420; if (services & ~(VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625)) { /* Nyquist */ if (v->dec.sampling_rate < max_rate * 3 / 2) { asprintf(errstr, _("Cannot capture the requested " "data services with " "%s (%s), the sampling frequency " "%.2f MHz is too low."), v->p_dev_name, v->vcap.name, v->dec.sampling_rate / 1e6); services = 0; goto failure; } printv("Nyquist check passed\n"); printv("Request decoding of services 0x%08x, strict level %d\n", services, strict); /* those services which are already set must be checked for strictness */ if ( (strict > 0) && ((services & v->dec.services) != 0) ) { unsigned int tmp_services; tmp_services = vbi_raw_decoder_check_services(&v->dec, services & v->dec.services, strict); /* mask out unsupported services */ services &= tmp_services | ~(services & v->dec.services); } if ( (services & ~v->dec.services) != 0 ) services &= vbi_raw_decoder_add_services(&v->dec, services & ~ v->dec.services, strict); if (services == 0) { asprintf(errstr, _("Sorry, %s (%s) cannot " "capture any of " "the requested data services."), v->p_dev_name, v->vcap.name); goto failure; } if (v->sliced_buffer.data != NULL) free(v->sliced_buffer.data); v->sliced_buffer.data = malloc((v->dec.count[0] + v->dec.count[1]) * sizeof(vbi_sliced)); if (!v->sliced_buffer.data) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } } failure: v->services |= services; printv("Will capture services 0x%08x, added 0x%0x commit:%d\n", v->services, services, commit); if (commit && (v->services != 0)) v4l_read_alloc(v, errstr); return services; io_error: return 0; } static vbi_capture * v4l_new(const char *dev_name, int given_fd, int scanning, unsigned int *services, int strict, char **errstr, vbi_bool trace) { char *error = NULL; vbi_capture_v4l *v; pthread_once (&vbi_init_once, vbi_init); if (!errstr) errstr = &error; *errstr = NULL; if (scanning != 525 && scanning != 625) scanning = 0; if (!(v = (vbi_capture_v4l *) calloc(1, sizeof(*v)))) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } vbi_raw_decoder_init (&v->dec); v->do_trace = trace; printv ("Try to open v4l vbi device, " "libzvbi interface rev.\n %s\n", rcsid); v->capture.parameters = v4l_parameters; v->capture._delete = v4l_delete; v->capture.get_fd = v4l_get_fd; v->capture.get_fd_flags = v4l_get_fd_flags; v->capture.read = v4l_read; v->capture.update_services = v4l_update_services; v->capture.get_scanning = v4l_get_scanning; v->capture.flush = v4l_flush; v->capture.set_video_path = v4l_set_video_path; if (0) v->capture.sys_log_fp = stderr; v->p_dev_name = strdup(dev_name); if (v->p_dev_name == NULL) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } v->fd = device_open (v->capture.sys_log_fp, v->p_dev_name, O_RDONLY, 0); if (-1 == v->fd) { asprintf (errstr, _("Cannot open '%s': %d, %s."), v->p_dev_name, errno, strerror(errno)); perm_check(v, v->p_dev_name); goto io_error; } printv("Opened %s\n", v->p_dev_name); /* used to store given_fd if necessary */ v->fd_video = -1; if (-1 == xioctl (v, VIDIOCGCAP, &v->vcap)) { /* * Older bttv drivers don't support any * v4l ioctls, let's see if we can guess the beast. */ printv("Driver doesn't support VIDIOCGCAP\n"); strlcpy(v->vcap.name, _("driver unknown"), sizeof(v->vcap.name) - 1); v->vcap.name[sizeof(v->vcap.name) - 1] = 0; if (!guess_bttv_v4l(v, &strict, given_fd, scanning)) goto failure; } else { if (v->vcap.name[0] != 0) { printv("Driver name '%s'\n", v->vcap.name); } else { strlcpy(v->vcap.name, _("driver unknown"), sizeof(v->vcap.name) - 1); v->vcap.name[sizeof(v->vcap.name) - 1] = 0; } if (!(v->vcap.type & VID_TYPE_TELETEXT)) { asprintf(errstr, _("%s (%s) is not a raw vbi device."), v->p_dev_name, v->vcap.name); goto failure; } guess_bttv_v4l(v, &strict, given_fd, scanning); } printv("%s (%s) is a v4l vbi device\n", v->p_dev_name, v->vcap.name); v->has_select = FALSE; /* FIXME if possible */ v->has_s_fmt = -1; v->read_active = FALSE; printv("Hinted video standard %d, guessed %d\n", scanning, v->dec.scanning); #ifdef REQUIRE_SELECT if (!v->select) { asprintf(errstr, _("%s (%s) does not support " "the select() function."), v->p_dev_name, v->vcap.name); goto failure; } #endif v->services = 0; if (services != NULL) { assert(*services != 0); v->services = v4l_update_services(&v->capture, FALSE, TRUE, *services, strict, errstr); if (v->services == 0) { goto failure; } *services = v->services; if (!v->dec.scanning && strict >= 1) { printv("Try to guess video standard from vbi bottom field " "boundaries: start=%d, count=%d\n", v->dec.start[1], v->dec.count[1]); if (v->dec.start[1] <= 0 || !v->dec.count[1]) { /* * We may have requested single field capture * ourselves, but then we had guessed already. */ #ifdef REQUIRE_VIDEOSTD asprintf(errstr, _("Cannot set or determine current " "videostandard of %s (%s)."), v->p_dev_name, v->vcap.name); goto failure; #endif printv("Warning: Videostandard not confirmed, " "will assume PAL/SECAM\n"); v->dec.scanning = 625; v->time_per_frame = 1.0 / 25; } else if (v->dec.start[1] < 286) { v->dec.scanning = 525; v->time_per_frame = 1001.0 / 30000; } else { v->dec.scanning = 625; v->time_per_frame = 1.0 / 25; } } printv("Guessed videostandard %d\n", v->dec.scanning); } if (!v->has_select) printv("Warning: no read select, reading will block\n"); printv("Successful opened %s (%s)\n", v->p_dev_name, v->vcap.name); if (errstr == &error) { free (error); error = NULL; } return &v->capture; failure: io_error: if (v) v4l_delete(&v->capture); if (errstr == &error) { free (error); error = NULL; } return NULL; } vbi_capture * vbi_capture_v4l_sidecar_new(const char *dev_name, int video_fd, unsigned int *services, int strict, char **errstr, vbi_bool trace) { return v4l_new(dev_name, video_fd, 0, services, strict, errstr, trace); } vbi_capture * vbi_capture_v4l_new(const char *dev_name, int scanning, unsigned int *services, int strict, char **errstr, vbi_bool trace) { return v4l_new(dev_name, -1, scanning, services, strict, errstr, trace); } #else /** * @param dev_name Name of the device to open, usually one of * @c /dev/vbi or @c /dev/vbi0 and up. * @param given_fd File handle of an already open video device, * usually one of @c /dev/video or @c /dev/video0 and up. * Must be assorted with the named vbi device, i.e. refer to * the same driver instance and hardware. * @param services This must point to a set of @ref VBI_SLICED_ * symbols describing the * data services to be decoded. On return the services actually * decodable will be stored here. See vbi_raw_decoder_add() * for details. If you want to capture raw data only, set to * @c VBI_SLICED_VBI_525, @c VBI_SLICED_VBI_625 or both. * If this parameter is @c NULL, no services will be installed. * You can do so later with vbi_capture_update_services(); note the * reset parameter must be set to @c TRUE in this case. * @param strict Will be passed to vbi_raw_decoder_add(). * @param errstr If not @c NULL this function stores a pointer to an error * description here. You must free() this string when no longer needed. * @param trace If @c TRUE print progress messages on stderr. * * This functions behaves much like vbi_capture_v4l_new, with the sole * difference that it uses the given file handle to determine the current * video standard if such queries aren't supported by the VBI device. * * @return * Initialized vbi_capture context, @c NULL on failure. */ vbi_capture * vbi_capture_v4l_sidecar_new(const char *dev_name, int given_fd, unsigned int *services, int strict, char **errstr, vbi_bool trace) { pthread_once (&vbi_init_once, vbi_init); if (errstr) asprintf(errstr, _("V4L driver interface not compiled.")); return NULL; } /** * @param dev_name Name of the device to open, usually one of * @c /dev/vbi or @c /dev/vbi0 and up. * @param scanning Can be used to specify the current TV norm for * old drivers which don't support ioctls to query the current * norm. Value is 625 (PAL/SECAM family) or 525 (NTSC family). * Set to 0 if you don't know the norm. * @param services This must point to a set of @ref VBI_SLICED_ * symbols describing the * data services to be decoded. On return the services actually * decodable will be stored here. See vbi_raw_decoder_add() * for details. If you want to capture raw data only, set to * @c VBI_SLICED_VBI_525, @c VBI_SLICED_VBI_625 or both. * If this parameter is @c NULL, no services will be installed. * You can do so later with vbi_capture_update_services(); note the * reset parameter must be set to @c TRUE in this case. * @param strict Will be passed to vbi_raw_decoder_add(). * @param errstr If not @c NULL this function stores a pointer to an error * description here. You must free() this string when no longer needed. * @param trace If @c TRUE print progress messages on stderr. * * @return * Initialized vbi_capture context, @c NULL on failure. */ vbi_capture * vbi_capture_v4l_new(const char *dev_name, int scanning, unsigned int *services, int strict, char **errstr, vbi_bool trace) { dev_name = dev_name; scanning = scanning; services = services; strict = strict; pthread_once (&vbi_init_once, vbi_init); if (trace) fprintf (stderr, "Libzvbi V4L interface rev.\n %s\n", rcsid); if (errstr) asprintf (errstr, _("V4L driver interface not compiled.")); return NULL; } #endif /* !ENABLE_V4L */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/io-v4l2.c000066400000000000000000000136351476363111200147160ustar00rootroot00000000000000/* * libzvbi -- Video For Linux Two 0.20 driver interface * * Copyright (C) 1999-2004 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ static const char rcsid [] = "$Id: io-v4l2.c,v 1.37 2008-02-19 00:35:20 mschimek Exp $"; #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "vbi.h" #include "inout.h" #ifdef ENABLE_V4L2 #include #include #include #include #include #include #include /* timeval */ #include /* fd_set */ #ifdef HAVE_SYS_IOCTL_H #include /* for (_)videodev2.h */ #endif #include /* for videodev2.h */ #include #ifndef HAVE_S64_U64 # include /* Linux 2.6.x asm/types.h defines __s64 and __u64 only if __GNUC__ is defined. */ typedef int64_t __s64; typedef uint64_t __u64; #endif #include "videodev2.h" #include "_videodev2.h" /* This macro checks at compile time if the arg type is correct, device_ioctl() repeats the ioctl if interrupted (EINTR) and logs the args and result if sys_log_fp is non-zero. */ #define xioctl(v, cmd, arg) \ (IOCTL_ARG_TYPE_CHECK_ ## cmd (arg), \ device_ioctl (v->capture.sys_log_fp, fprint_ioctl_arg, v->fd, \ cmd, (void *)(arg))) #define printv(format, args...) \ do { \ if (v->do_trace) { \ fprintf (stderr, format ,##args); \ fflush (stderr); \ } \ } while (0) typedef struct { vbi_capture capture; int fd; struct v4l2_capability vcap; vbi_bool do_trace; } vbi_capture_v4l2; static void v4l2_delete (vbi_capture * vc) { vbi_capture_v4l2 *v = PARENT (vc, vbi_capture_v4l2, capture); if (-1 != v->fd) device_close (v->capture.sys_log_fp, v->fd); CLEAR (*v); free(v); } /* document below */ vbi_capture * vbi_capture_v4l2_new (const char * dev_name, int buffers, unsigned int * services, int strict, char ** errstr, vbi_bool trace) { char *error = NULL; vbi_capture_v4l2 *v; pthread_once (&vbi_init_once, vbi_init); if (!errstr) errstr = &error; *errstr = NULL; if (!(v = calloc (1, sizeof (*v)))) { asprintf (errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } v->do_trace = trace; printv ("Try to open V4L2 0.20 VBI device, " "libzvbi interface rev.\n %s\n", rcsid); v->fd = device_open (v->capture.sys_log_fp, dev_name, O_RDWR, 0); if (-1 == v->fd) { v->fd = device_open (v->capture.sys_log_fp, dev_name, O_RDONLY, 0); if (-1 == v->fd) { asprintf (errstr, _("Cannot open '%s': %d, %s."), dev_name, errno, strerror (errno)); goto io_error; } } printv ("Opened %s\n", dev_name); if (-1 == xioctl (v, VIDIOC_QUERYCAP, &v->vcap)) { /* TRANSLATORS: Cannot identify '/dev/some'. */ /* asprintf (errstr, _("Cannot identify '%s': %s."), dev_name, strerror (errno)); */ v4l2_delete (&v->capture); if (errstr == &error) { errstr = NULL; free (error); error = NULL; } /* Try V4L2 2.6. */ return vbi_capture_v4l2k_new (dev_name, -1, buffers, services, strict, errstr, trace); } /* XXX localize. */ asprintf (errstr, "V4L2 0.20 API not supported."); io_error: failure: if (v) v4l2_delete (&v->capture); if (errstr == &error) { free (error); error = NULL; } return NULL; } #else /** * @param dev_name Name of the device to open, usually one of * @c /dev/vbi or @c /dev/vbi0 and up. * @param buffers Number of device buffers for raw vbi data, when * the driver supports streaming. Otherwise one bounce buffer * is allocated for vbi_capture_pull(). * @param services This must point to a set of @ref VBI_SLICED_ * symbols describing the * data services to be decoded. On return the services actually * decodable will be stored here. See vbi_raw_decoder_add() * for details. If you want to capture raw data only, set to * @c VBI_SLICED_VBI_525, @c VBI_SLICED_VBI_625 or both. * If this parameter is @c NULL, no services will be installed. * You can do so later with vbi_capture_update_services(); note the * reset parameter must be set to @c TRUE in this case. * @param strict Will be passed to vbi_raw_decoder_add(). * @param errstr If not @c NULL this function stores a pointer to an error * description here. You must free() this string when no longer needed. * @param trace If @c TRUE print progress messages on stderr. * * Note: Starting with libzvbi 0.2.9 the V4L2 0.20 API is no longer * supported. The function still recognizes V4L2 0.20 drivers * for debugging purposes and supports Linux 2.6 V4L2 drivers. * * @return * Initialized vbi_capture context, @c NULL on failure. */ vbi_capture * vbi_capture_v4l2_new (const char * dev_name, int buffers, unsigned int * services, int strict, char ** errstr, vbi_bool trace) { dev_name = dev_name; buffers = buffers; services = services; strict = strict; pthread_once (&vbi_init_once, vbi_init); if (trace) fprintf (stderr, "Libzvbi V4L2 interface rev.\n %s\n", rcsid); if (errstr) asprintf (errstr, _("V4L2 driver interface not compiled.")); return NULL; } #endif /* !ENABLE_V4L2 */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/io-v4l2k.c000066400000000000000000001110561476363111200150650ustar00rootroot00000000000000/* * libzvbi -- Video For Linux Two (version 2002-10 and later) * driver interface * * Copyright (C) 2002-2005 Michael H. Schimek * Copyright (C) 2003, 2004 Tom Zoerner * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ static const char rcsid [] = "$Id: io-v4l2k.c,v 1.50 2009-12-14 23:43:20 mschimek Exp $"; /* * Around Oct-Nov 2002 the V4L2 API was revised for inclusion into * Linux 2.5/2.6. There are a few subtle differences, in order to * keep the source clean this interface has been forked off from the * old V4L2 interface. "v4l2k" is no official designation, there is * none, take it as v4l2-kernel or v4l-2000. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "vbi.h" #include "inout.h" #ifdef ENABLE_V4L2 #include #include #include #include #include #include /* read(), dup2() */ #include #include /* timeval */ #include /* fd_set */ #ifdef HAVE_SYS_IOCTL_H #include /* for (_)videodev2k.h */ #endif #ifdef HAVE_SYS_MMAN_H #include /* PROT_READ, MAP_SHARED */ #endif #include /* __u8 and friends for videodev2k.h */ #include #include "raw_decoder.h" #include "version.h" #ifndef HAVE_S64_U64 # include /* Linux 2.6.x asm/types.h defines __s64 and __u64 only if __GNUC__ is defined. */ typedef int64_t __s64; typedef uint64_t __u64; #endif #include "videodev2k.h" #include "_videodev2k.h" /* This macro checks at compile time if the arg type is correct, device_ioctl() repeats the ioctl if interrupted (EINTR) and logs the args and result if sys_log_fp is non-zero. */ #define xioctl(v, cmd, arg) \ (IOCTL_ARG_TYPE_CHECK_ ## cmd (arg), \ device_ioctl (v->capture.sys_log_fp, fprint_ioctl_arg, v->fd, \ cmd, (void *)(arg))) #undef REQUIRE_SELECT #undef REQUIRE_G_FMT /* before S_FMT */ #undef REQUIRE_S_FMT /* else accept current format */ #define ENQUEUE_SUSPENDED -3 #define ENQUEUE_STREAM_OFF -2 #define ENQUEUE_BUFS_QUEUED -1 #define ENQUEUE_IS_UNQUEUED(X) ((X) >= 0) #define FLUSH_FRAME_COUNT 2 typedef struct vbi_capture_v4l2 { vbi_capture capture; int fd; vbi_bool close_me; int btype; /* v4l2 stream type */ vbi_bool streaming; vbi_bool read_active; int has_try_fmt; int enqueue; struct v4l2_buffer vbuf; struct v4l2_capability vcap; char * p_dev_name; vbi_sampling_par sp; vbi3_raw_decoder rd; unsigned int services; /* all services, including raw */ double time_per_frame; vbi_capture_buffer *raw_buffer; unsigned int num_raw_buffers; int buf_req_count; vbi_capture_buffer sliced_buffer; int flush_frame_count; vbi_bool pal_start1_fix; vbi_bool saa7134_ntsc_fix; vbi_bool bttv_offset_fix; vbi_bool cx88_ntsc_fix; vbi_bool bttv_min_start_fix; vbi_bool bttv_ntsc_rate_fix; _vbi_log_hook log; } vbi_capture_v4l2; static void vbi_sliced_data_from_raw (vbi_capture_v4l2 * v, vbi_capture_buffer ** sliced, const vbi_capture_buffer *raw) { vbi_capture_buffer *b; unsigned int max_lines; unsigned int n_lines; assert (NULL != sliced); b = *sliced; if (NULL == b) { /* Store sliced data in our buffer and return a buffer pointer. */ b = &v->sliced_buffer; *sliced = b; } else { /* Store sliced data in client buffer. */ } max_lines = v->sp.count[0] + v->sp.count[1]; n_lines = vbi3_raw_decoder_decode (&v->rd, (vbi_sliced *) b->data, max_lines, (uint8_t *) raw->data); b->size = n_lines * sizeof (vbi_sliced); b->timestamp = raw->timestamp; } static void v4l2_stream_stop(vbi_capture_v4l2 *v) { if (v->enqueue >= ENQUEUE_BUFS_QUEUED) { info (&v->log, "Suspending stream."); if (-1 == xioctl (v, VIDIOC_STREAMOFF, &v->btype)) { /* Error ignored. */ } } for (; v->num_raw_buffers > 0; v->num_raw_buffers--) { device_munmap (v->capture.sys_log_fp, v->raw_buffer[v->num_raw_buffers - 1].data, v->raw_buffer[v->num_raw_buffers - 1].size); } if (v->raw_buffer != NULL) { free(v->raw_buffer); v->raw_buffer = NULL; } v->enqueue = ENQUEUE_SUSPENDED; } static int v4l2_stream_alloc(vbi_capture_v4l2 *v, char ** errstr) { struct v4l2_requestbuffers vrbuf; struct v4l2_buffer vbuf; char * guess = NULL; int errno_copy; assert(v->enqueue == ENQUEUE_SUSPENDED); assert(v->raw_buffer == NULL); info (&v->log, "Requesting %d streaming i/o buffers.", v->buf_req_count); memset(&vrbuf, 0, sizeof(vrbuf)); vrbuf.type = v->btype; vrbuf.count = v->buf_req_count; vrbuf.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl (v, VIDIOC_REQBUFS, &vrbuf)) { asprintf (errstr, _("Cannot request streaming i/o buffers " "from %s (%s): %s."), v->p_dev_name, v->vcap.card, strerror(errno)); guess = _("Possibly a driver bug."); goto failure; } if (vrbuf.count == 0) { asprintf(errstr, _("%s (%s) granted no streaming i/o buffers, " "perhaps the physical memory is exhausted."), v->p_dev_name, v->vcap.card); goto failure; } info (&v->log, "Mapping %d streaming i/o buffers.", vrbuf.count); v->raw_buffer = calloc(vrbuf.count, sizeof(v->raw_buffer[0])); if (v->raw_buffer == NULL) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } v->num_raw_buffers = 0; while (v->num_raw_buffers < vrbuf.count) { uint8_t *p; vbuf.type = v->btype; vbuf.index = v->num_raw_buffers; vbuf.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl (v, VIDIOC_QUERYBUF, &vbuf)) { asprintf (errstr, _("Querying streaming i/o buffer #%d " "from %s (%s) failed: %s."), v->num_raw_buffers, v->p_dev_name, v->vcap.card, strerror(errno)); goto mmap_failure; } p = device_mmap (v->capture.sys_log_fp, /* start any */ NULL, vbuf.length, PROT_READ | PROT_WRITE, MAP_SHARED, /* MAP_PRIVATE ? */ v->fd, vbuf.m.offset); /* V4L2 spec requires PROT_WRITE regardless if we write buffers, but broken drivers might reject it. */ if (MAP_FAILED == p) p = device_mmap (v->capture.sys_log_fp, /* start any */ NULL, vbuf.length, PROT_READ, MAP_SHARED, v->fd, vbuf.m.offset); if (MAP_FAILED == p) { if (errno == ENOMEM && v->num_raw_buffers >= 2) { info (&v->log, "Memory mapping buffer #%d " "failed with errno %d (ignored).", v->num_raw_buffers, errno); break; } asprintf(errstr, _("Memory mapping streaming i/o buffer #%d " "from %s (%s) failed: %s."), v->num_raw_buffers, v->p_dev_name, v->vcap.card, strerror(errno)); goto mmap_failure; } else { unsigned int i, s; v->raw_buffer[v->num_raw_buffers].data = p; v->raw_buffer[v->num_raw_buffers].size = vbuf.length; for (i = s = 0; i < vbuf.length; i++) s += p[i]; if (s % vbuf.length) { fprintf(stderr, "Security warning: driver %s (%s) seems to mmap " "physical memory uncleared. Please contact the " "driver author.\n", v->p_dev_name, v->vcap.card); exit(EXIT_FAILURE); } } if (-1 == xioctl (v, VIDIOC_QBUF, &vbuf)) { asprintf (errstr, _("Cannot enqueue streaming i/o buffer " "#%d to %s (%s): %s."), v->num_raw_buffers, v->p_dev_name, v->vcap.card, strerror(errno)); guess = _("Probably a driver bug."); goto mmap_failure; } v->num_raw_buffers++; } v->enqueue = ENQUEUE_STREAM_OFF; return 0; mmap_failure: errno_copy = errno; v4l2_stream_stop(v); errno = errno_copy; failure: info (&v->log, "Failed with errno %d, errmsg '%s'.", errno, *errstr); return -1; } static vbi_bool restart_stream (vbi_capture_v4l2 * v) { unsigned int i; if (-1 == xioctl (v, VIDIOC_STREAMOFF, &v->btype)) return FALSE; for (i = 0; i < v->num_raw_buffers; ++i) { struct v4l2_buffer vbuf; CLEAR (vbuf); vbuf.index = i; vbuf.type = v->btype; vbuf.memory = V4L2_MEMORY_MMAP; /* Error ignored. */ xioctl (v, VIDIOC_QBUF, &vbuf); } if (-1 == xioctl (v, VIDIOC_STREAMON, &v->btype)) return FALSE; return TRUE; } static int v4l2_stream(vbi_capture *vc, vbi_capture_buffer **raw, vbi_capture_buffer **sliced, const struct timeval *timeout_orig) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); struct timeval timeout = *timeout_orig; vbi_capture_buffer *b; int r; if ((v->enqueue == ENQUEUE_SUSPENDED) || (v->services == 0)) { /* stream was suspended (add_services not committed) */ error (&v->log, "No services set or not committed."); errno = ESRCH; return -1; } if (v->enqueue == ENQUEUE_STREAM_OFF) { if (-1 == xioctl (v, VIDIOC_STREAMON, &v->btype)) { error (&v->log, "Failed to enable streaming, errno %d.", errno); return -1; } } else if (ENQUEUE_IS_UNQUEUED(v->enqueue)) { v->vbuf.type = v->btype; v->vbuf.index = v->enqueue; v->vbuf.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf)) { error (&v->log, "Failed to enqueue previous " "buffer, errno %d.", errno); return -1; } } v->enqueue = ENQUEUE_BUFS_QUEUED; while (1) { /* wait for the next frame */ r = vbi_capture_io_select(v->fd, &timeout); if (r <= 0) { if (r < 0) { error (&v->log, "select() failed with errno %d.", errno); } return r; } v->vbuf.type = v->btype; v->vbuf.memory = V4L2_MEMORY_MMAP; /* retrieve the captured frame from the queue */ r = xioctl (v, VIDIOC_DQBUF, &v->vbuf); if (-1 == r) { int saved_errno; saved_errno = errno; error (&v->log, "Failed to dequeue buffer, errno %d.", errno); /* On EIO bttv dequeues the buffer, other drivers may not. Actually the caller should restart on error. */ /* Errors ignored. */ restart_stream (v); errno = saved_errno; return -1; } if (v->flush_frame_count > 0) { v->flush_frame_count -= 1; info (&v->log, "Skipping frame (%d remaining).", v->flush_frame_count); if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf)) { error (&v->log, "Failed to enqueue buffer, errno %d.", errno); return -1; } } else { break; } } assert (v->vbuf.index < v->num_raw_buffers); b = &v->raw_buffer[v->vbuf.index]; b->timestamp = v->vbuf.timestamp.tv_sec + v->vbuf.timestamp.tv_usec * (1 / 1e6); if (NULL != raw) { vbi_capture_buffer *r; r = *raw; if (NULL == r) { /* Store raw data in our buffer and return a buffer pointer. */ *raw = b; /* Keep this buffer out of the queue. */ v->enqueue = v->vbuf.index; } else { /* Store raw data in client buffer. */ memcpy (r->data, b->data, b->size); /* FIXME client should pass max buffer size. */ r->size = b->size; r->timestamp = b->timestamp; } } if (NULL != sliced) { vbi_sliced_data_from_raw (v, sliced, b); } /* if no raw pointer returned to the caller, re-queue buffer immediately ** else the buffer is re-queued upon the next call to read() */ if (v->enqueue == ENQUEUE_BUFS_QUEUED) { if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf)) { error (&v->log, "Failed to queue buffer, errno %d.", errno); return -1; } } return 1; } static void v4l2_stream_flush(vbi_capture *vc) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); struct timeval tv; unsigned int max_loop; /* stream not enabled yet -> nothing to flush */ if ( (v->enqueue == ENQUEUE_SUSPENDED) || (v->enqueue == ENQUEUE_STREAM_OFF) ) return; if (ENQUEUE_IS_UNQUEUED(v->enqueue)) { v->vbuf.type = v->btype; v->vbuf.index = v->enqueue; v->vbuf.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf)) { error (&v->log, "Failed to enqueue buffer, errno %d.", errno); return; } } v->enqueue = ENQUEUE_BUFS_QUEUED; for (max_loop = 0; max_loop < v->num_raw_buffers; max_loop++) { /* check if there are any buffers pending for de-queueing */ /* use zero timeout to prevent select() from blocking */ memset(&tv, 0, sizeof(tv)); if (vbi_capture_io_select(v->fd, &tv) <= 0) return; if ((-1 == xioctl (v, VIDIOC_DQBUF, &v->vbuf)) && (errno != EIO)) return; /* immediately queue the buffer again, thereby discarding it's content */ if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf)) return; } } static void v4l2_read_stop(vbi_capture_v4l2 *v) { for (; v->num_raw_buffers > 0; v->num_raw_buffers--) { free(v->raw_buffer[v->num_raw_buffers - 1].data); v->raw_buffer[v->num_raw_buffers - 1].data = NULL; } free(v->raw_buffer); v->raw_buffer = NULL; } static int v4l2_suspend(vbi_capture_v4l2 *v) { int fd; if (v->streaming) { v4l2_stream_stop(v); } else { v4l2_read_stop(v); if (v->read_active) { info (&v->log, "Reopen device."); /* hack: cannot suspend read to allow S_FMT, need to close device */ fd = device_open (v->capture.sys_log_fp, v->p_dev_name, O_RDWR, 0); if (fd == -1) { error (&v->log, "Failed to reopen device, " "errno %d.", errno); return -1; } /* use dup2() to keep the same fd, which may be used by our client */ device_close (v->capture.sys_log_fp, v->fd); dup2(fd, v->fd); device_close (v->capture.sys_log_fp, fd); v->read_active = FALSE; } } return 0; } static int v4l2_read_alloc(vbi_capture_v4l2 *v, char ** errstr) { assert(v->raw_buffer == NULL); v->raw_buffer = calloc(1, sizeof(v->raw_buffer[0])); if (!v->raw_buffer) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } v->raw_buffer[0].size = (v->sp.count[0] + v->sp.count[1]) * v->sp.bytes_per_line; v->raw_buffer[0].data = malloc(v->raw_buffer[0].size); if (!v->raw_buffer[0].data) { asprintf(errstr, _("Not enough memory to allocate " "vbi capture buffer (%d KB)."), (v->raw_buffer[0].size + 1023) >> 10); goto failure; } v->num_raw_buffers = 1; info (&v->log, "Capture buffer allocated."); return 0; failure: info (&v->log, "Failed with errno %d, errmsg '%s'.", errno, *errstr); return -1; } static int v4l2_read_frame(vbi_capture_v4l2 *v, vbi_capture_buffer *raw, struct timeval *timeout) { int r; /* wait until data is available or timeout expires */ r = vbi_capture_io_select(v->fd, timeout); if (r <= 0) { if (r < 0) { error (&v->log, "select() failed with errno %d.", errno); } return r; } v->read_active = TRUE; for (;;) { /* from zapping/libvbi/v4lx.c */ pthread_testcancel(); r = read(v->fd, raw->data, raw->size); if (r == -1 && (errno == EINTR || errno == ETIME)) continue; if (r == -1) return -1; if (r != raw->size) { errno = EIO; return -1; } else break; } return 1; } static int v4l2_read(vbi_capture *vc, vbi_capture_buffer **raw, vbi_capture_buffer **sliced, const struct timeval *timeout) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); vbi_capture_buffer *my_raw = v->raw_buffer; struct timeval tv; int r; if ((my_raw == NULL) || (v->services == 0)) { info (&v->log, "No services set or not committed."); errno = EINVAL; return -1; } if (raw == NULL) raw = &my_raw; if (*raw == NULL) *raw = v->raw_buffer; else (*raw)->size = v->raw_buffer[0].size; tv = *timeout; while (1) { r = v4l2_read_frame(v, *raw, &tv); if (r <= 0) return r; if (v->flush_frame_count > 0) { v->flush_frame_count -= 1; info (&v->log, "Skipping frame (%d remaining).", v->flush_frame_count); } else break; } gettimeofday(&tv, NULL); (*raw)->timestamp = tv.tv_sec + tv.tv_usec * (1 / 1e6); if (sliced) { vbi_sliced_data_from_raw (v, sliced, *raw); } return 1; } static void v4l2_read_flush(vbi_capture *vc) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); struct timeval tv; int r; if ( (v->raw_buffer == NULL) || (v->read_active == FALSE) ) return; memset(&tv, 0, sizeof(tv)); r = vbi_capture_io_select(v->fd, &tv); if (r <= 0) return; do { r = read(v->fd, v->raw_buffer->data, v->raw_buffer->size); } while ((r < 0) && (errno == EINTR)); } static vbi_bool v4l2_get_videostd(vbi_capture_v4l2 *v, char ** errstr) { struct v4l2_standard vstd; v4l2_std_id stdid; unsigned int i; int r; char * guess = NULL; if (-1 == xioctl (v, VIDIOC_G_STD, &stdid)) { asprintf (errstr, _("Cannot query current videostandard " "of %s (%s): %s."), v->p_dev_name, v->vcap.card, strerror(errno)); guess = _("Probably a driver bug."); goto failure; } for (i = 0; i < 100; ++i) { CLEAR (vstd); vstd.index = i; r = xioctl (v, VIDIOC_ENUMSTD, &vstd); if (-1 == r) break; if (vstd.id & stdid) break; } if (i >= 100) { r = -1; errno = 0; } if (-1 == r) { asprintf(errstr, _("Cannot query current " "videostandard of %s (%s): %s."), v->p_dev_name, v->vcap.card, strerror(errno)); guess = _("Probably a driver bug."); goto failure; } info (&v->log, "Current scanning system is %d.", vstd.framelines); /* add_vbi_services() eliminates non 525/625 */ v->sp.scanning = vstd.framelines; return TRUE; failure: info (&v->log, "Failed with errno %d, errmsg '%s'.", errno, *errstr); return FALSE; } static int v4l2_get_scanning(vbi_capture *vc) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); int old_scanning = v->sp.scanning; int new_scanning = -1; if ( v4l2_get_videostd(v, NULL) ) { new_scanning = v->sp.scanning; } v->sp.scanning = old_scanning; return new_scanning; } static void print_vfmt (vbi_capture_v4l2 * v, const char * s, struct v4l2_format * vfmt) { if (0 == (v->log.mask & VBI_LOG_INFO)) return; _vbi_log_printf (v->log.fn, v->log.user_data, VBI_LOG_INFO, __FILE__, __FUNCTION__, "%sformat %08x [%c%c%c%c], %d Hz, %d bpl, offs %d, " "F1 %d...%d, F2 %d...%d, flags %08x.", s, vfmt->fmt.vbi.sample_format, (char)((vfmt->fmt.vbi.sample_format ) & 0xff), (char)((vfmt->fmt.vbi.sample_format >> 8) & 0xff), (char)((vfmt->fmt.vbi.sample_format >> 16) & 0xff), (char)((vfmt->fmt.vbi.sample_format >> 24) & 0xff), vfmt->fmt.vbi.sampling_rate, vfmt->fmt.vbi.samples_per_line, vfmt->fmt.vbi.offset, vfmt->fmt.vbi.start[0], vfmt->fmt.vbi.start[0] + vfmt->fmt.vbi.count[0] - 1, vfmt->fmt.vbi.start[1], vfmt->fmt.vbi.start[1] + vfmt->fmt.vbi.count[1] - 1, vfmt->fmt.vbi.flags); } static unsigned int v4l2_update_services(vbi_capture *vc, vbi_bool reset, vbi_bool commit, unsigned int services, int strict, char ** errstr) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); struct v4l2_format vfmt; unsigned int max_rate; int g_fmt; int s_fmt; char * guess = NULL; /* suspend capturing, or driver will return EBUSY */ v4l2_suspend(v); if (reset) { /* query current norm */ if (v4l2_get_videostd(v, errstr) == FALSE) goto io_error; vbi3_raw_decoder_reset (&v->rd); v->services = 0; } memset(&vfmt, 0, sizeof(vfmt)); vfmt.type = v->btype = V4L2_BUF_TYPE_VBI_CAPTURE; max_rate = 0; info (&v->log, "Querying current vbi parameters..."); g_fmt = xioctl (v, VIDIOC_G_FMT, &vfmt); if (-1 == g_fmt) { info (&v->log, "...failed with errno %d.", errno); #ifdef REQUIRE_G_FMT asprintf(errstr, _("Cannot query current " "vbi parameters of %s (%s): %s."), v->p_dev_name, v->vcap.card, strerror(errno)); goto io_error; #else strict = MAX(0, strict); #endif } else { info (&v->log, "...success."); print_vfmt (v, "VBI capture parameters supported: ", &vfmt); if (v->has_try_fmt == -1) { struct v4l2_format vfmt_temp = vfmt; /* test if TRY_FMT is available by feeding it the current ** parameters, which should always succeed */ v->has_try_fmt = (0 == xioctl (v, VIDIOC_TRY_FMT, &vfmt_temp)); } } if (strict >= 0) { struct v4l2_format vfmt_temp = vfmt; vbi_sampling_par dec_temp; unsigned int f2_offset; unsigned int sup_services; int r; info (&v->log, "Attempt to set vbi capture parameters."); memset(&dec_temp, 0, sizeof(dec_temp)); sup_services = _vbi_sampling_par_from_services_log (&dec_temp, &max_rate, _vbi_videostd_set_from_scanning (v->sp.scanning), services | v->services, &v->log); services &= sup_services; if (0 == services) { asprintf(errstr, _("Sorry, %s (%s) cannot capture any of the " "requested data services with scanning %d."), v->p_dev_name, v->vcap.card, v->sp.scanning); goto failure; } vfmt.fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; vfmt.fmt.vbi.sampling_rate = dec_temp.sampling_rate; vfmt.fmt.vbi.samples_per_line = dec_temp.bytes_per_line; vfmt.fmt.vbi.offset = dec_temp.offset; vfmt.fmt.vbi.start[0] = dec_temp.start[0]; vfmt.fmt.vbi.count[0] = dec_temp.count[0]; vfmt.fmt.vbi.start[1] = dec_temp.start[1]; vfmt.fmt.vbi.count[1] = dec_temp.count[1]; f2_offset = (625 == v->sp.scanning) ? 312 : 263; /* Some broken drivers may take start (= 0) into account despite count being zero. */ if (0 == vfmt.fmt.vbi.count[1]) { vfmt.fmt.vbi.start[1] = vfmt.fmt.vbi.start[0] + f2_offset; } else if (0 == vfmt.fmt.vbi.count[0]) { vfmt.fmt.vbi.start[0] = vfmt.fmt.vbi.start[1] - f2_offset; } if (v->bttv_min_start_fix) { int min_start[2]; unsigned int i; /* Captures MAX (count[0], count[1]) lines starting at min_start, ignoring the requested start: proxy-test vps. */ if (625 == v->sp.scanning) { min_start[0] = 7; min_start[1] = 320; } else { min_start[0] = 10; min_start[1] = 273; } for (i = 0; i < 2; ++i) { if (vfmt.fmt.vbi.count[i] > 0) { vfmt.fmt.vbi.count[i] += (int) vfmt.fmt.vbi.start[i] - min_start[i]; vfmt.fmt.vbi.start[i] = min_start[i]; } } } /* 0 == count check omitted because above we made sure start is valid and drivers should ignore it in this case anyway. */ if (v->pal_start1_fix && 625 == v->sp.scanning) { vfmt.fmt.vbi.start[1] -= 1; } if (v->saa7134_ntsc_fix && 525 == v->sp.scanning) { vfmt.fmt.vbi.start[0] += 6; vfmt.fmt.vbi.start[1] += 6; } print_vfmt (v, "VBI capture parameters requested: ", &vfmt); if ((v->has_try_fmt != 1) || commit) { s_fmt = VIDIOC_S_FMT; /* Arg type check requires constant cmd number. */ r = xioctl (v, VIDIOC_S_FMT, &vfmt); } else { s_fmt = VIDIOC_TRY_FMT; r = xioctl (v, VIDIOC_TRY_FMT, &vfmt); } if (-1 == r) { switch (errno) { case EBUSY: #ifndef REQUIRE_S_FMT if (g_fmt != -1) { info (&v->log, "VIDIOC_S_FMT returned EBUSY, " "will try the current " "parameters."); vfmt = vfmt_temp; break; } #endif asprintf(errstr, _("Cannot initialize %s (%s), " "the device is already in use."), v->p_dev_name, v->vcap.card); goto io_error; default: asprintf(errstr, _("Could not set the vbi capture parameters " "for %s (%s): %d, %s."), v->p_dev_name, v->vcap.card, errno, strerror(errno)); guess = _("Possibly a driver bug."); goto io_error; } if (commit && (v->has_try_fmt == 1) && 0 != vbi3_raw_decoder_services (&v->rd)) { /* FIXME strictness of services is not considered */ unsigned int old_services; unsigned int tmp_services; old_services = vbi3_raw_decoder_services (&v->rd); tmp_services = _vbi_sampling_par_check_services_log (&v->sp, old_services, /* strict */ 0, &v->log); if (old_services != tmp_services) vbi3_raw_decoder_remove_services (&v->rd, old_services & ~ tmp_services); } } else { info (&v->log, "Successfully %s vbi capture " "parameters.", ((s_fmt == (int)VIDIOC_S_FMT) ? "set" : "tried")); } } print_vfmt (v, "VBI capture parameters granted: ", &vfmt); { vbi_bool fixed = FALSE; if (v->cx88_ntsc_fix && 9 == vfmt.fmt.vbi.start[0] && 272 == vfmt.fmt.vbi.start[1]) { /* Captures only 288 * 4 samples/line, no work-around possible. */ /* XXX when was this fixed? */ asprintf (errstr, _("A known bug in driver %s %u.%u.%u " "impedes VBI capturing in NTSC mode. " "Please upgrade the driver."), v->vcap.driver, (v->vcap.version >> 16) & 0xFF, (v->vcap.version >> 8) & 0xFF, (v->vcap.version >> 0) & 0xFF); errno = 0; goto io_error; } if (v->pal_start1_fix && 625 == v->sp.scanning && 319 == vfmt.fmt.vbi.start[1]) { vfmt.fmt.vbi.start[1] += 1; fixed = TRUE; } if (v->bttv_offset_fix && 128 == vfmt.fmt.vbi.offset) { vfmt.fmt.vbi.offset = 244; fixed = TRUE; } if (v->bttv_ntsc_rate_fix && 525 == v->sp.scanning && 35468950 == vfmt.fmt.vbi.sampling_rate) { vfmt.fmt.vbi.sampling_rate = 28636363; fixed = TRUE; } if (fixed) print_vfmt (v, "Fixes applied: ", &vfmt); } v->sp.sampling_rate = vfmt.fmt.vbi.sampling_rate; v->sp.bytes_per_line = vfmt.fmt.vbi.samples_per_line; v->sp.offset = vfmt.fmt.vbi.offset; v->sp.start[0] = vfmt.fmt.vbi.start[0]; v->sp.start[1] = vfmt.fmt.vbi.start[1]; v->sp.count[0] = vfmt.fmt.vbi.count[0]; v->sp.count[1] = vfmt.fmt.vbi.count[1]; v->sp.interlaced = !!(vfmt.fmt.vbi.flags & V4L2_VBI_INTERLACED); v->sp.synchronous = !(vfmt.fmt.vbi.flags & V4L2_VBI_UNSYNC); v->time_per_frame = ((v->sp.scanning == 625) ? 1.0 / 25 : 1001.0 / 30000); v->sp.sampling_format = VBI_PIXFMT_YUV420; if (vfmt.fmt.vbi.sample_format != V4L2_PIX_FMT_GREY) { asprintf(errstr, _("%s (%s) offers unknown vbi sampling format #%d. " "This may be a driver bug or libzvbi is too old."), v->p_dev_name, v->vcap.card, vfmt.fmt.vbi.sample_format); goto io_error; } /* grow pattern array if necessary ** note: must do this even if service ** add fails later, to stay in sync with driver */ vbi3_raw_decoder_set_sampling_par (&v->rd, &v->sp, /* strict */ 0); if (services & ~(VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625)) { /* Nyquist (we're generous at 1.5) */ if (v->sp.sampling_rate < (int) max_rate * 3 / 2) { asprintf(errstr, _("Cannot capture the requested " "data services with " "%s (%s), the sampling frequency " "%.2f MHz is too low."), v->p_dev_name, v->vcap.card, v->sp.sampling_rate / 1e6); services = 0; goto failure; } info (&v->log, "Nyquist check passed."); info (&v->log, "Request decoding of services 0x%08x, " "strict level %d.", services, strict); /* those services which are already set must be checked for strictness */ if (strict > 0 && 0 != (services & vbi3_raw_decoder_services (&v->rd))) { unsigned int old_services; unsigned int tmp_services; old_services = vbi3_raw_decoder_services (&v->rd); tmp_services = _vbi_sampling_par_check_services_log (&v->sp, services & old_services, strict, &v->log); /* mask out unsupported services */ services &= tmp_services | ~(services & old_services); } if ( (services & ~vbi3_raw_decoder_services (&v->rd)) != 0 ) services &= vbi3_raw_decoder_add_services (&v->rd, services & ~vbi3_raw_decoder_services (&v->rd), strict); if (services == 0) { asprintf(errstr, _("Sorry, %s (%s) cannot capture any of " "the requested data services."), v->p_dev_name, v->vcap.card); goto failure; } if (v->sliced_buffer.data != NULL) free(v->sliced_buffer.data); v->sliced_buffer.data = malloc((v->sp.count[0] + v->sp.count[1]) * sizeof(vbi_sliced)); if (!v->sliced_buffer.data) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto io_error; } } failure: v->services |= services; info (&v->log, "Will capture services 0x%08x, " "added 0x%0x commit=%d.", v->services, services, commit); if (commit && (v->services != 0)) { if (v->streaming) { if (v4l2_stream_alloc(v, errstr) != 0) goto io_error; } else { if (v4l2_read_alloc(v, errstr) != 0) goto io_error; } } return services; io_error: info (&v->log, "Failed with errno %d, errmsg '%s'.", errno, *errstr); return 0; } #if 3 == VBI_VERSION_MINOR static vbi_sampling_par * v4l2_parameters(vbi_capture *vc) #else static vbi_raw_decoder * v4l2_parameters(vbi_capture *vc) #endif { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); /* For compatibility in libzvbi 0.2 struct vbi_sampling_par == vbi_raw_decoder. In 0.3 we'll drop the decoding related fields. */ return &v->sp; } static void v4l2_delete(vbi_capture *vc) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); if (v->streaming) v4l2_stream_stop(v); else v4l2_read_stop(v); _vbi3_raw_decoder_destroy (&v->rd); if (v->sliced_buffer.data) free(v->sliced_buffer.data); if (v->p_dev_name != NULL) free(v->p_dev_name); if (v->close_me && v->fd != -1) device_close (v->capture.sys_log_fp, v->fd); free(v); } static VBI_CAPTURE_FD_FLAGS v4l2_get_fd_flags(vbi_capture *vc) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); VBI_CAPTURE_FD_FLAGS result; result = VBI_FD_IS_DEVICE | VBI_FD_HAS_SELECT; if (v->streaming) result |= VBI_FD_HAS_MMAP; return result; } static int v4l2_get_fd(vbi_capture *vc) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); return v->fd; } static void v4l2_flush(vbi_capture *vc) { vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture); v->flush_frame_count = FLUSH_FRAME_COUNT; if (v->streaming) v4l2_stream_flush(vc); else v4l2_read_flush(vc); } /* document below */ vbi_capture * vbi_capture_v4l2k_new (const char * dev_name, int fd, int buffers, unsigned int * services, int strict, char ** errstr, vbi_bool trace) { char *guess = NULL; char *error = NULL; vbi_capture_v4l2 *v; _vbi_log_hook log; pthread_once (&vbi_init_once, vbi_init); /* Needed to reopen device. */ assert(dev_name != NULL); assert(buffers > 0); if (!errstr) errstr = &error; *errstr = NULL; CLEAR (log); if (trace) { log.fn = vbi_log_on_stderr; log.mask = VBI_LOG_INFO * 2 - 1; } if (!(v = calloc(1, sizeof(*v)))) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } v->log = log; _vbi3_raw_decoder_init (&v->rd, /* sampling_par */ NULL); if (trace) { vbi3_raw_decoder_set_log_fn (&v->rd, vbi_log_on_stderr, /* user_data */ NULL, /* mask */ VBI_LOG_INFO * 2 - 1); } if (0) v->capture.sys_log_fp = stderr; info (&v->log, "Try to open V4L2 2.6 VBI device, " "libzvbi interface rev.\n %s.", rcsid); v->p_dev_name = strdup(dev_name); if (v->p_dev_name == NULL) { asprintf(errstr, _("Virtual memory exhausted.")); errno = ENOMEM; goto failure; } v->capture.parameters = v4l2_parameters; v->capture._delete = v4l2_delete; v->capture.get_fd = v4l2_get_fd; v->capture.get_fd_flags = v4l2_get_fd_flags; v->capture.update_services = v4l2_update_services; v->capture.get_scanning = v4l2_get_scanning; v->capture.flush = v4l2_flush; if (-1 == fd) { v->fd = device_open (v->capture.sys_log_fp, v->p_dev_name, O_RDWR, 0); if (-1 == v->fd) { asprintf(errstr, _("Cannot open '%s': %d, %s."), v->p_dev_name, errno, strerror(errno)); goto io_error; } v->close_me = TRUE; info (&v->log, "Opened %s.", v->p_dev_name); } else { v->fd = fd; v->close_me = FALSE; info (&v->log, "Using v4l2k device fd %d.", fd); } if (-1 == xioctl (v, VIDIOC_QUERYCAP, &v->vcap)) { asprintf(errstr, _("Cannot identify '%s': %d, %s."), v->p_dev_name, errno, strerror(errno)); guess = _("Probably not a v4l2 device."); goto io_error; } if (!(v->vcap.capabilities & V4L2_CAP_VBI_CAPTURE)) { asprintf(errstr, _("%s (%s) is not a raw vbi device."), v->p_dev_name, v->vcap.card); goto failure; } info (&v->log, "%s (%s) is a v4l2 vbi device,\n" "driver %s, version 0x%08x.", v->p_dev_name, v->vcap.card, v->vcap.driver, v->vcap.version); if (0 == strcmp ((char *) v->vcap.driver, "bttv")) { if (v->vcap.version <= 0x00090F) { v->pal_start1_fix = TRUE; v->bttv_min_start_fix = TRUE; } v->bttv_offset_fix = TRUE; v->bttv_ntsc_rate_fix = TRUE; } else if (0 == strcmp ((char *) v->vcap.driver, "saa7134")) { if (v->vcap.version <= 0x00020C) v->saa7134_ntsc_fix = TRUE; v->pal_start1_fix = TRUE; } else if (0 == strcmp ((char *) v->vcap.driver, "cx8800")) { v->cx88_ntsc_fix = TRUE; } v->has_try_fmt = -1; v->buf_req_count = buffers; if (v->vcap.capabilities & V4L2_CAP_STREAMING && !vbi_capture_force_read_mode) { info (&v->log, "Using streaming interface."); fcntl(v->fd, F_SETFL, O_NONBLOCK); v->streaming = TRUE; v->enqueue = ENQUEUE_SUSPENDED; v->capture.read = v4l2_stream; } else if (v->vcap.capabilities & V4L2_CAP_READWRITE) { info (&v->log, "Using read interface."); v->capture.read = v4l2_read; v->read_active = FALSE; } else { asprintf(errstr, _("%s (%s) lacks a vbi read interface, " "possibly an output only device " "or a driver bug."), v->p_dev_name, v->vcap.card); goto failure; } v->services = 0; if (services != NULL) { assert(*services != 0); v->services = v4l2_update_services(&v->capture, TRUE, TRUE, *services, strict, errstr); if (v->services == 0) goto failure; *services = v->services; } info (&v->log, "Successfully opened %s (%s).", v->p_dev_name, v->vcap.card); if (errstr == &error) { free (error); error = NULL; } return &v->capture; io_error: failure: info ((NULL != v) ? &v->log : &log, "Failed with errno %d, errmsg '%s'.", errno, *errstr); if (NULL != v) v4l2_delete (&v->capture); if (errstr == &error) { free (error); error = NULL; } return NULL; } #else /** * @param dev_name Name of the device to open, usually one of * @c /dev/vbi or @c /dev/vbi0 and up. * @param fd File handle of VBI device if already opened by caller, * else value -1. * @param buffers Number of device buffers for raw vbi data, when * the driver supports streaming. Otherwise one bounce buffer * is allocated for vbi_capture_pull(). * @param services This must point to a set of @ref VBI_SLICED_ * symbols describing the * data services to be decoded. On return the services actually * decodable will be stored here. See vbi_raw_decoder_add() * for details. If you want to capture raw data only, set to * @c VBI_SLICED_VBI_525, @c VBI_SLICED_VBI_625 or both. * If this parameter is @c NULL, no services will be installed. * You can do so later with vbi_capture_update_services(); note the * reset parameter must be set to @c TRUE in this case. * @param strict Will be passed to vbi_raw_decoder_add(). * @param errstr If not @c NULL this function stores a pointer to an error * description here. You must free() this string when no longer needed. * @param trace If @c TRUE print progress messages on stderr. * * @return * Initialized vbi_capture context, @c NULL on failure. */ vbi_capture * vbi_capture_v4l2k_new(const char *dev_name, int fd, int buffers, unsigned int *services, int strict, char **errstr, vbi_bool trace) { dev_name = dev_name; fd = fd; buffers = buffers; services = services; strict = strict; pthread_once (&vbi_init_once, vbi_init); if (trace) fprintf (stderr, "Libzvbi V4L2 2.6 interface rev.\n %s\n", rcsid); if (errstr) asprintf (errstr, _("V4L2 driver interface not compiled.")); return NULL; } #endif /* !ENABLE_V4L2 */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/lang.c000066400000000000000000001056751476363111200144510ustar00rootroot00000000000000/* * libzvbi - Teletext and Closed Caption character set * * Copyright (C) 2000, 2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: lang.c,v 1.18 2016-02-08 07:30:48 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "lang.h" /* * Teletext font descriptors * * ETS 300 706 Table 32, 33, 34 */ struct vbi_font_descr vbi_font_descriptors[88] = { /* 0 - Western and Central Europe */ { LATIN_G0, LATIN_G2, ENGLISH, "English" }, { LATIN_G0, LATIN_G2, GERMAN, "Deutsch" }, { LATIN_G0, LATIN_G2, SWE_FIN_HUN, "Svenska / Suomi / Magyar" }, { LATIN_G0, LATIN_G2, ITALIAN, "Italiano" }, { LATIN_G0, LATIN_G2, FRENCH, "Franais" }, { LATIN_G0, LATIN_G2, PORTUG_SPANISH, "Portugus / Espaol" }, { LATIN_G0, LATIN_G2, CZECH_SLOVAK, "Cesky / Slovencina" }, { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, /* 8 - Eastern Europe */ { LATIN_G0, LATIN_G2, POLISH, "Polski" }, { LATIN_G0, LATIN_G2, GERMAN, "Deutsch" }, { LATIN_G0, LATIN_G2, SWE_FIN_HUN, "Svenska / Suomi / Magyar" }, { LATIN_G0, LATIN_G2, ITALIAN, "Italiano" }, { LATIN_G0, LATIN_G2, FRENCH, "Franais" }, { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, { LATIN_G0, LATIN_G2, CZECH_SLOVAK, "Cesky / Slovencina" }, { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, /* 16 - Western Europe and Turkey */ { LATIN_G0, LATIN_G2, ENGLISH, "English" }, { LATIN_G0, LATIN_G2, GERMAN, "Deutsch" }, { LATIN_G0, LATIN_G2, SWE_FIN_HUN, "Svenska / Suomi / Magyar" }, { LATIN_G0, LATIN_G2, ITALIAN, "Italiano" }, { LATIN_G0, LATIN_G2, FRENCH, "Franais" }, { LATIN_G0, LATIN_G2, PORTUG_SPANISH, "Portugus / Espaol" }, { LATIN_G0, LATIN_G2, TURKISH, "Trke" }, { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, /* 24 - Central and Southeast Europe */ { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, { LATIN_G0, LATIN_G2, SERB_CRO_SLO, "Srbski / Hrvatski / Slovenscina" }, { LATIN_G0, LATIN_G2, NO_SUBSET, 0 }, { LATIN_G0, LATIN_G2, RUMANIAN, "Romna" }, /* 32 - Cyrillic */ { CYRILLIC_1_G0, CYRILLIC_G2, NO_SUBSET, "Srpski / Hrvatski" }, { LATIN_G0, LATIN_G2, GERMAN, "Deutsch" }, { LATIN_G0, LATIN_G2, ESTONIAN, "Eesti" }, { LATIN_G0, LATIN_G2, LETT_LITH, "Lettish / Lietuviskai" }, { CYRILLIC_2_G0, CYRILLIC_G2, NO_SUBSET, "Russky / Balgarski " }, { CYRILLIC_3_G0, CYRILLIC_G2, NO_SUBSET, "Ukrayins'ka" }, { LATIN_G0, LATIN_G2, CZECH_SLOVAK, "Cesky / Slovencina" }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, /* 48 */ { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { LATIN_G0, LATIN_G2, TURKISH, "Trke" }, { GREEK_G0, GREEK_G2, NO_SUBSET, "Ellinika'" }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, /* 64 - Arabic */ { LATIN_G0, ARABIC_G2, ENGLISH, "Alarabia / English" }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { LATIN_G0, ARABIC_G2, FRENCH, "Alarabia / Franais" }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { ARABIC_G0, ARABIC_G2, NO_SUBSET, "Alarabia" }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, /* 80 */ { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { 0, 0, NO_SUBSET, 0 }, { HEBREW_G0, ARABIC_G2, NO_SUBSET, "Ivrit" }, { 0, 0, NO_SUBSET, 0 }, { ARABIC_G0, ARABIC_G2, NO_SUBSET, "Alarabia" }, }; #if 0 /* * These characters mimic a few common block mosaic, smooth mosaic * or line drawing character patterns, eg. horizontal bars * * Not currently used (anymore), maybe later. */ static const unsigned char gfx_transcript[] = { /* 2 */ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, /* 3 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, /* 2 */ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, /* 3 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, /* 6 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, /* 7 */ 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, /* 6 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, /* 7 */ 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, /* 2 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 3 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 4 */ 0x2B, 0x2B, 0x2B, 0x2B, 0x3C, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x2A, 0x2A, 0x6F, /* 5 */ 0x7C, 0x2D, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x3E, 0x3C, 0x00, 0x00, 0x00, /* 6 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 7 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #endif /* * Teletext character set * * (Similar to ISO 6937 - * ftp://dkuug.dk/i18n/charmaps/ISO_6937) */ /* * ETS 300 706 Table 36: Latin National Option Sub-sets * * Latin G0 character code to Unicode mapping per national subset, * unmodified codes (NO_SUBSET) in row zero. * * [13][0] Turkish currency symbol not in Unicode, use private code U+E800 */ static const unsigned short national_subset[14][13] = { { 0x0023u, 0x0024u, 0x0040u, 0x005Bu, 0x005Cu, 0x005Du, 0x005Eu, 0x005Fu, 0x0060u, 0x007Bu, 0x007Cu, 0x007Du, 0x007Eu }, { 0x0023u, 0x016Fu, 0x010Du, 0x0165u, 0x017Eu, 0x00FDu, 0x00EDu, 0x0159u, 0x00E9u, 0x00E1u, 0x011Bu, 0x00FAu, 0x0161u }, { 0x00A3u, 0x0024u, 0x0040u, 0x2190u, 0x00BDu, 0x2192u, 0x2191u, 0x0023u, 0x2014u, 0x00BCu, 0x2016u, 0x00BEu, 0x00F7u }, { 0x0023u, 0x00F5u, 0x0160u, 0x00C4u, 0x00D6u, 0x017Du, 0x00DCu, 0x00D5u, 0x0161u, 0x00E4u, 0x00F6u, 0x017Eu, 0x00FCu }, { 0x00E9u, 0x00EFu, 0x00E0u, 0x00EBu, 0x00EAu, 0x00F9u, 0x00EEu, 0x0023u, 0x00E8u, 0x00E2u, 0x00F4u, 0x00FBu, 0x00E7u }, { 0x0023u, 0x0024u, 0x00A7u, 0x00C4u, 0x00D6u, 0x00DCu, 0x005Eu, 0x005Fu, 0x00B0u, 0x00E4u, 0x00F6u, 0x00FCu, 0x00DFu }, { 0x00A3u, 0x0024u, 0x00E9u, 0x00B0u, 0x00E7u, 0x2192u, 0x2191u, 0x0023u, 0x00F9u, 0x00E0u, 0x00F2u, 0x00E8u, 0x00ECu }, { 0x0023u, 0x0024u, 0x0160u, 0x0117u, 0x0229u, 0x017Du, 0x010Du, 0x016Bu, 0x0161u, 0x0105u, 0x0173u, 0x017Eu, 0x012Fu }, { 0x0023u, 0x0144u, 0x0105u, 0x01B5u, 0x015Au, 0x0141u, 0x0107u, 0x00F3u, 0x0119u, 0x017Cu, 0x015Bu, 0x0142u, 0x017Au }, { 0x00E7u, 0x0024u, 0x00A1u, 0x00E1u, 0x00E9u, 0x00EDu, 0x00F3u, 0x00FAu, 0x00BFu, 0x00FCu, 0x00F1u, 0x00E8u, 0x00E0u }, { 0x0023u, 0x00A4u, 0x0162u, 0x00C2u, 0x015Eu, 0x01CDu, 0x00CDu, 0x0131u, 0x0163u, 0x00E2u, 0x015Fu, 0X01CEu, 0x00EEu }, { 0x0023u, 0x00CBu, 0x010Cu, 0x0106u, 0x017Du, 0x00D0u, 0x0160u, 0x00EBu, 0x010Du, 0x0107u, 0x017Eu, 0x00F0u, 0x0161u }, { 0x0023u, 0x00A4u, 0x00C9u, 0x00C4u, 0x00D6u, 0x00C5u, 0x00DCu, 0x005Fu, 0x00E9u, 0x00E4u, 0x00F6u, 0x00E5u, 0x00FCu }, { 0xE800u, 0x011Fu, 0x0130u, 0x015Eu, 0x00D6u, 0x00C7u, 0x00DCu, 0x011Eu, 0x0131u, 0x015Fu, 0x00F6u, 0x00E7u, 0x00FCu } }; /* * ETS 300 706 Table 37: Latin G2 Supplementary Set * * 0x49 seems to be dot below; not in Unicode (except combining), use U+002E. */ static const unsigned short latin_g2[96] = { 0x00A0u, 0x00A1u, 0x00A2u, 0x00A3u, 0x0024u, 0x00A5u, 0x0023u, 0x00A7u, 0x00A4u, 0x2018u, 0x201Cu, 0x00ABu, 0x2190u, 0x2191u, 0x2192u, 0x2193u, 0x00B0u, 0x00B1u, 0x00B2u, 0x00B3u, 0x00D7u, 0x00B5u, 0x00B6u, 0x00B7u, 0x00F7u, 0x2019u, 0x201Du, 0x00BBu, 0x00BCu, 0x00BDu, 0x00BEu, 0x00BFu, 0x0020u, 0x02CBu, 0x02CAu, 0x02C6u, 0x02DCu, 0x02C9u, 0x02D8u, 0x02D9u, 0x00A8u, 0x002Eu, 0x02DAu, 0x02CFu, 0x02CDu, 0x02DDu, 0x02DBu, 0x02C7u, 0x2014u, 0x00B9u, 0x00AEu, 0x00A9u, 0x2122u, 0x266Au, 0x20A0u, 0x2030u, 0x0251u, 0x0020u, 0x0020u, 0x0020u, 0x215Bu, 0x215Cu, 0x215Du, 0x215Eu, 0x2126u, 0x00C6u, 0x00D0u, 0x00AAu, 0x0126u, 0x0020u, 0x0132u, 0x013Fu, 0x0141u, 0x00D8u, 0x0152u, 0x00BAu, 0x00DEu, 0x0166u, 0x014Au, 0x0149u, 0x0138u, 0x00E6u, 0x0111u, 0x00F0u, 0x0127u, 0x0131u, 0x0133u, 0x0140u, 0x0142u, 0x00F8u, 0x0153u, 0x00DFu, 0x00FEu, 0x0167u, 0x014Bu, 0x25A0u }; /* * ETS 300 706 Table 38: Cyrillic G0 Primary Set - Option 1 - Serbian/Croatian */ static const unsigned short cyrillic_1_g0[64] = { 0x0427u, 0x0410u, 0x0411u, 0x0426u, 0x0414u, 0x0415u, 0x0424u, 0x0413u, 0x0425u, 0x0418u, 0x0408u, 0x041Au, 0x041Bu, 0x041Cu, 0x041Du, 0x041Eu, 0x041Fu, 0x040Cu, 0x0420u, 0x0421u, 0x0422u, 0x0423u, 0x0412u, 0x0403u, 0x0409u, 0x040Au, 0x0417u, 0x040Bu, 0x0416u, 0x0402u, 0x0428u, 0x040Fu, 0x0447u, 0x0430u, 0x0431u, 0x0446u, 0x0434u, 0x0435u, 0x0444u, 0x0433u, 0x0445u, 0x0438u, 0x0458u, 0x043Au, 0x043Bu, 0x043Cu, 0x043Du, 0x043Eu, 0x043Fu, 0x045Cu, 0x0440u, 0x0441u, 0x0442u, 0x0443u, 0x0432u, 0x0453u, 0x0459u, 0x045Au, 0x0437u, 0x045Bu, 0x0436u, 0x0452u, 0x0448u, 0x25A0u }; /* * ETS 300 706 Table 39: Cyrillic G0 Primary Set - Option 2 - Russian/Bulgarian */ static const unsigned short cyrillic_2_g0[64] = { 0x042Eu, 0x0410u, 0x0411u, 0x0426u, 0x0414u, 0x0415u, 0x0424u, 0x0413u, 0x0425u, 0x0418u, 0x040Du, 0x041Au, 0x041Bu, 0x041Cu, 0x041Du, 0x041Eu, 0x041Fu, 0x042Fu, 0x0420u, 0x0421u, 0x0422u, 0x0423u, 0x0416u, 0x0412u, 0x042Cu, 0x042Au, 0x0417u, 0x0428u, 0x042Du, 0x0429u, 0x0427u, 0x042Bu, 0x044Eu, 0x0430u, 0x0431u, 0x0446u, 0x0434u, 0x0435u, 0x0444u, 0x0433u, 0x0445u, 0x0438u, 0x045Du, 0x043Au, 0x043Bu, 0x043Cu, 0x043Du, 0x043Eu, 0x043Fu, 0x044Fu, 0x0440u, 0x0441u, 0x0442u, 0x0443u, 0x0436u, 0x0432u, 0x044Cu, 0x044Au, 0x0437u, 0x0448u, 0x044Du, 0x0449u, 0x0447u, 0x25A0u }; /* * ETS 300 706 Table 40: Cyrillic G0 Primary Set - Option 3 - Ukrainian */ static const unsigned short cyrillic_3_g0[64] = { 0x042Eu, 0x0410u, 0x0411u, 0x0426u, 0x0414u, 0x0415u, 0x0424u, 0x0413u, 0x0425u, 0x0418u, 0x040Du, 0x041Au, 0x041Bu, 0x041Cu, 0x041Du, 0x041Eu, 0x041Fu, 0x042Fu, 0x0420u, 0x0421u, 0x0422u, 0x0423u, 0x0416u, 0x0412u, 0x042Cu, 0x0406u, 0x0417u, 0x0428u, 0x0404u, 0x0429u, 0x0427u, 0x0407u, 0x044Eu, 0x0430u, 0x0431u, 0x0446u, 0x0434u, 0x0435u, 0x0444u, 0x0433u, 0x0445u, 0x0438u, 0x045Du, 0x043Au, 0x043Bu, 0x043Cu, 0x043Du, 0x043Eu, 0x043Fu, 0x044Fu, 0x0440u, 0x0441u, 0x0442u, 0x0443u, 0x0436u, 0x0432u, 0x044Cu, 0x0456u, 0x0437u, 0x0448u, 0x0454u, 0x0449u, 0x0447u, 0x25A0u }; /* * ETS 300 706 Table 41: Cyrillic G2 Supplementary Set */ static const unsigned short cyrillic_g2[96] = { 0x00A0u, 0x00A1u, 0x00A2u, 0x00A3u, 0x0020u, 0x00A5u, 0x0023u, 0x00A7u, 0x0020u, 0x2018u, 0x201Cu, 0x00ABu, 0x2190u, 0x2191u, 0x2192u, 0x2193u, 0x00B0u, 0x00B1u, 0x00B2u, 0x00B3u, 0x00D7u, 0x00B5u, 0x00B6u, 0x00B7u, 0x00F7u, 0x2019u, 0x201Du, 0x00BBu, 0x00BCu, 0x00BDu, 0x00BEu, 0x00BFu, 0x0020u, 0x02CBu, 0x02CAu, 0x02C6u, 0x02DCu, 0x02C9u, 0x02D8u, 0x02D9u, 0x00A8u, 0x002Eu, 0x02DAu, 0x02CFu, 0x02CDu, 0x02DDu, 0x02DBu, 0x02C7u, 0x2014u, 0x00B9u, 0x00AEu, 0x00A9u, 0x2122u, 0x266Au, 0x20A0u, 0x2030u, 0x0251u, 0x0141u, 0x0142u, 0x00DFu, 0x215Bu, 0x215Cu, 0x215Du, 0x215Eu, 0x0044u, 0x0045u, 0x0046u, 0x0047u, 0x0049u, 0x004Au, 0x004Bu, 0x004Cu, 0x004Eu, 0x0051u, 0x0052u, 0x0053u, 0x0055u, 0x0056u, 0x0057u, 0x005Au, 0x0064u, 0x0065u, 0x0066u, 0x0067u, 0x0069u, 0x006Au, 0x006Bu, 0x006Cu, 0x006Eu, 0x0071u, 0x0072u, 0x0073u, 0x0075u, 0x0076u, 0x0077u, 0x007Au }; /* * ETS 300 706 Table 42: Greek G0 Primary Set */ static const unsigned short greek_g0[64] = { 0x0390u, 0x0391u, 0x0392u, 0x0393u, 0x0394u, 0x0395u, 0x0396u, 0x0397u, 0x0398u, 0x0399u, 0x039Au, 0x039Bu, 0x039Cu, 0x039Du, 0x039Eu, 0x039Fu, 0x03A0u, 0x03A1u, 0x0374u, 0x03A3u, 0x03A4u, 0x03A5u, 0x03A6u, 0x03A7u, 0x03A8u, 0x03A9u, 0x03AAu, 0x03ABu, 0x03ACu, 0x03ADu, 0x03AEu, 0x03AFu, 0x03B0u, 0x03B1u, 0x03B2u, 0x03B3u, 0x03B4u, 0x03B5u, 0x03B6u, 0x03B7u, 0x03B8u, 0x03B9u, 0x03BAu, 0x03BBu, 0x03BCu, 0x03BDu, 0x03BEu, 0x03BFu, 0x03C0u, 0x03C1u, 0x03C2u, 0x03C3u, 0x03C4u, 0x03C5u, 0x03C6u, 0x03C7u, 0x03C8u, 0x03C9u, 0x03CAu, 0x03CBu, 0x03CCu, 0x03CDu, 0x03CEu, 0x25A0u }; /* * ETS 300 706 Table 43: Greek G2 Supplementary Set */ static const unsigned short greek_g2[96] = { 0x00A0u, 0x0061u, 0x0062u, 0x00A3u, 0x0065u, 0x0068u, 0x0069u, 0x00A7u, 0x003Au, 0x2018u, 0x201Cu, 0x006Bu, 0x2190u, 0x2191u, 0x2192u, 0x2193u, 0x00B0u, 0x00B1u, 0x00B2u, 0x00B3u, 0x0078u, 0x006Du, 0x006Eu, 0x0070u, 0x00F7u, 0x2019u, 0x201Du, 0x0074u, 0x00BCu, 0x00BDu, 0x00BEu, 0x0078u, 0x0020u, 0x02CBu, 0x02CAu, 0x02C6u, 0x02DCu, 0x02C9u, 0x02D8u, 0x02D9u, 0x00A8u, 0x002Eu, 0x02DAu, 0x02CFu, 0x02CDu, 0x02DDu, 0x02DBu, 0x02C7u, 0x003Fu, 0x00B9u, 0x00AEu, 0x00A9u, 0x2122u, 0x266Au, 0x20A0u, 0x2030u, 0x0251u, 0x038Au, 0x038Eu, 0x038Fu, 0x215Bu, 0x215Cu, 0x215Du, 0x215Eu, 0x0043u, 0x0044u, 0x0046u, 0x0047u, 0x004Au, 0x004Cu, 0x0051u, 0x0052u, 0x0053u, 0x0055u, 0x0056u, 0x0057u, 0x0059u, 0x005Au, 0x0386u, 0x0389u, 0x0063u, 0x0064u, 0x0066u, 0x0067u, 0x006Au, 0x006Cu, 0x0071u, 0x0072u, 0x0073u, 0x0075u, 0x0076u, 0x0077u, 0x0079u, 0x007Au, 0x0388u, 0x25A0u }; /* * ETS 300 706 Table 44: Arabic G0 Primary Set * * XXX 0X0000 is what? * Until these tables are finished use private codes U+E6xx. */ static const unsigned short arabic_g0[96] = { /* 0x0020, 0x0021, 0x0022, 0x00A3, 0x0024, 0x0025, 0x0000, 0x0000, 0x0029, 0x0028, 0x002A, 0x002B, 0x060C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x061B, 0x003E, 0x003D, 0x003C, 0x061F, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0023, 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, 0x062B, 0x062D, 0x062C, 0x062E, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x25A0 */ 0x0020u, 0x0021u, 0x0022u, 0x00A3u, 0x0024u, 0x0025u, 0xE606u, 0xE607u, 0x0029u, 0x0028u, 0x002Au, 0x002Bu, 0x060Cu, 0x002Du, 0x002Eu, 0x002Fu, 0x0030u, 0x0031u, 0x0032u, 0x0033u, 0x0034u, 0x0035u, 0x0036u, 0x0037u, 0x0038u, 0x0039u, 0x003Au, 0x061Bu, 0x003Eu, 0x003Du, 0x003Cu, 0x061Fu, 0xE620u, 0xE621u, 0xE622u, 0xE623u, 0xE624u, 0xE625u, 0xE626u, 0xE627u, 0xE628u, 0xE629u, 0xE62Au, 0xE62Bu, 0xE62Cu, 0xE62Du, 0xE62Eu, 0xE62Fu, 0xE630u, 0xE631u, 0xE632u, 0xE633u, 0xE634u, 0xE635u, 0xE636u, 0xE637u, 0xE638u, 0xE639u, 0xE63Au, 0xE63Bu, 0xE63Cu, 0xE63Du, 0xE63Eu, 0x0023u, 0xE640u, 0xE641u, 0xE642u, 0xE643u, 0xE644u, 0xE645u, 0xE646u, 0xE647u, 0xE648u, 0xE649u, 0xE64Au, 0xE64Bu, 0xE64Cu, 0xE64Du, 0xE64Eu, 0xE64Fu, 0xE650u, 0xE651u, 0xE652u, 0xE653u, 0xE654u, 0xE655u, 0xE656u, 0xE657u, 0xE658u, 0xE659u, 0xE65Au, 0xE65Bu, 0xE65Cu, 0xE65Du, 0xE65Eu, 0x25A0u }; /* * ETS 300 706 Table 45: Arabic G2 Supplementary Set * * XXX 0X0000 is what? * Until these tables are finished use private codes U+E7xx. */ static const unsigned short arabic_g2[96] = { /* 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, 0x0668, 0x0669, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 */ 0xE660u, 0xE661u, 0xE662u, 0xE663u, 0xE664u, 0xE665u, 0xE666u, 0xE667u, 0xE668u, 0xE669u, 0xE66Au, 0xE66Bu, 0xE66Cu, 0xE66Du, 0xE66Eu, 0xE66Fu, 0xE670u, 0xE671u, 0xE672u, 0xE673u, 0xE674u, 0xE675u, 0xE676u, 0xE677u, 0xE678u, 0xE679u, 0xE67Au, 0xE67Bu, 0xE67Cu, 0xE67Du, 0xE67Eu, 0xE67Fu, 0x00E0u, 0x0041u, 0x0042u, 0x0043u, 0x0044u, 0x0045u, 0x0046u, 0x0047u, 0x0048u, 0x0049u, 0x004Au, 0x004Bu, 0x004Cu, 0x004Du, 0x004Eu, 0x004Fu, 0x0050u, 0x0051u, 0x0052u, 0x0053u, 0x0054u, 0x0055u, 0x0056u, 0x0057u, 0x0058u, 0x0059u, 0x005Au, 0x00EBu, 0x00EAu, 0x00F9u, 0x00EEu, 0xE75Fu, 0x00E9u, 0x0061u, 0x0062u, 0x0063u, 0x0064u, 0x0065u, 0x0066u, 0x0067u, 0x0068u, 0x0069u, 0x006Au, 0x006Bu, 0x006Cu, 0x006Du, 0x006Eu, 0x006Fu, 0x0070u, 0x0071u, 0x0072u, 0x0073u, 0x0074u, 0x0075u, 0x0076u, 0x0077u, 0x0078u, 0x0079u, 0x007Au, 0x00E2u, 0x00F4u, 0x00FBu, 0x00E7u, 0x0020u }; /* * ETS 300 706 Table 46: Hebrew G0 Primary Set */ static const unsigned short hebrew_g0[37] = { 0x2190u, 0x00BDu, 0x2192u, 0x2191u, 0x0023u, 0x05D0u, 0x05D1u, 0x05D2u, 0x05D3u, 0x05D4u, 0x05D5u, 0x05D6u, 0x05D7u, 0x05D8u, 0x05D9u, 0x05DAu, 0x05DBu, 0x05DCu, 0x05DDu, 0x05DEu, 0x05DFu, 0x05E0u, 0x05E1u, 0x05E2u, 0x05E3u, 0x05E4u, 0x05E5u, 0x05E6u, 0x05E7u, 0x05E8u, 0x05E9u, 0x05EAu, 0x20AAu, 0x2016u, 0x00BEu, 0x00F7u, 0x25A0u }; /** * @internal * @param s Teletext character set as listed in ETS 300 706 section 15. * @param n National character subset as listed in section 15, only * applicable to character set LATIN_G0, ignored otherwise. * @param c Character code in range 0x20 ... 0x7F. * * Translate Teletext character code to Unicode. * * Exceptions: * ETS 300 706 Table 36 Latin National Subset Turkish character * 0x23 Turkish currency symbol is not representable in Unicode, * translated to private code U+E800. Was unable to identify all * Arabic glyphs in Table 44 and 45 Arabic G0 and G2, these are * mapped to private code U+E620 ... U+E67F and U+E720 ... U+E77F * respectively. Table 47 G1 Block Mosaic is not representable * in Unicode, translated to private code U+EE00 ... U+EE7F. * (contiguous form has bit 5 set, separate form cleared). * Table 48 G3 Smooth Mosaics and Line Drawing Set is not * representable in Unicode, translated to private code U+EF20 * ... U+EF7F. * * Note that some Teletext character sets contain complementary * Latin characters. For example the Greek capital letters Alpha * and Beta are reused as Latin capital letter A and B, while a * separate code exists for Latin capital letter C. This function * is unable to distinguish between uses, so it will always translate * Greek A and B to Alpha and Beta, C to Latin C. * * Private codes U+F000 ... U+F7FF are reserved for DRCS. * * @return * Unicode value. */ unsigned int vbi_teletext_unicode(vbi_character_set s, vbi_national_subset n, unsigned int c) { int i; assert(c >= 0x20 && c <= 0x7F); switch (s) { case LATIN_G0: /* shortcut */ if (0xF8000019u & (1u << (c & 31))) { if (n > 0) { assert(n < 14); for (i = 0; i < 13; i++) if (c == national_subset[0][i]) return national_subset[n][i]; } if (c == 0x24) return 0x00A4u; else if (c == 0x7C) return 0x00A6u; else if (c == 0x7F) return 0x25A0u; } return c; case LATIN_G2: return latin_g2[c - 0x20]; case CYRILLIC_1_G0: if (c < 0x40) return c; else return cyrillic_1_g0[c - 0x40]; case CYRILLIC_2_G0: if (c == 0x26) return 0x044Bu; else if (c < 0x40) return c; else return cyrillic_2_g0[c - 0x40]; case CYRILLIC_3_G0: if (c == 0x26) return 0x00EFu; else if (c < 0x40) return c; else return cyrillic_3_g0[c - 0x40]; case CYRILLIC_G2: return cyrillic_g2[c - 0x20]; case GREEK_G0: if (c == 0x3C) return 0x00ABu; else if (c == 0x3E) return 0x00BBu; else if (c < 0x40) return c; else return greek_g0[c - 0x40]; case GREEK_G2: return greek_g2[c - 0x20]; case ARABIC_G0: return arabic_g0[c - 0x20]; case ARABIC_G2: return arabic_g2[c - 0x20]; case HEBREW_G0: if (c < 0x5B) return c; else return hebrew_g0[c - 0x5B]; case BLOCK_MOSAIC_G1: /* 0x20 ... 0x3F -> 0xEE00 ... 0xEE1F separated */ /* 0xEE20 ... 0xEE3F contiguous */ /* 0x60 ... 0x7F -> 0xEE40 ... 0xEE5F separated */ /* 0xEE60 ... 0xEE7F contiguous */ assert(c < 0x40 || c >= 0x60); return 0xEE00u + c; case SMOOTH_MOSAIC_G3: return 0xEF00u + c; default: fprintf(stderr, "%s: unknown char set %d\n", __FUNCTION__, s); exit(EXIT_FAILURE); } } /* * Unicode U+00C0 ... U+017F to * Teletext accent ((Latin G2 0x40 ... 0x4F) - 0x40) << 12 + Latin G0 * * 40 SPACE 48 COMBINING DIAERESIS (U+0308) * 41 COMBINING GRAVE ACCENT (U+0300) 49 COMBINING DOT BELOW (U+0323) ? * 42 COMBINING ACUTE ACCENT (U+0301) 4A COMBINING RING ABOVE (U+030A) * 43 COMBINING CIRCUMFLEX ACCENT (U+0302) 4B COMBINING CEDILLA (U+0327) * 44 COMBINING TILDE (U+0303) 4C COMBINING MACRON BELOW (U+0331) ? * 45 COMBINING MACRON (U+0304) 4D COMBINING DOUBLE ACUTE ACCENT (U+030B) * 46 COMBINING BREVE (U+0306) 4E COMBINING OGONEK (U+0328) * 47 COMBINING DOT ABOVE (U+0307) 4F COMBINING CARON (U+030C) */ static const unsigned short composed[12 * 16] = { /* U+00C0 */ 0x1041, 0x2041, 0x3041, 0x4041, 0x8041, 0xA041, 0x0000, 0xB043, 0x1045, 0x2045, 0x3045, 0x8045, 0x1049, 0x2049, 0x3049, 0x8049, 0x0000, 0x404E, 0x104F, 0x204F, 0x304F, 0x404F, 0x804F, 0x0000, 0x0000, 0x1055, 0x2055, 0x3055, 0x8055, 0x2059, 0x0000, 0x0000, 0x1061, 0x2061, 0x3061, 0x4061, 0x8061, 0xA061, 0x0000, 0xB063, 0x1065, 0x2065, 0x3065, 0x8065, 0x1069, 0x2069, 0x3069, 0x8069, 0x0000, 0x406E, 0x106F, 0x206F, 0x306F, 0x406F, 0x806F, 0x0000, 0x0000, 0x1075, 0x2075, 0x3075, 0x8075, 0x2079, 0x0000, 0x8079, /* U+0100 */ 0x5041, 0x5061, 0x6041, 0x6061, 0xE041, 0xE061, 0x2043, 0x2063, 0x3043, 0x3063, 0x7043, 0x7063, 0xF043, 0xF063, 0xF044, 0xF064, 0x0000, 0x0000, 0x5045, 0x5065, 0x6045, 0x6065, 0x7045, 0x7065, 0xE045, 0xE065, 0xF045, 0xF065, 0x3047, 0x3067, 0x6047, 0x6067, 0x7047, 0x7067, 0xB047, 0xB067, 0x3048, 0x3068, 0x0000, 0x0000, 0x4049, 0x4069, 0x5049, 0x5069, 0x6049, 0x6069, 0xE049, 0xE069, 0x7049, 0x0000, 0x0000, 0x0000, 0x304A, 0x306A, 0xB04B, 0xB06B, 0x0000, 0x204C, 0x206C, 0xB04C, 0xB06C, 0xF04C, 0xF06C, 0x0000, /* U+0140 */ 0x0000, 0x0000, 0x0000, 0x204E, 0x206E, 0xB04E, 0xB06E, 0xF04E, 0xF06E, 0x0000, 0x0000, 0x0000, 0x504F, 0x506F, 0x604F, 0x606F, 0xD04F, 0xD06F, 0x0000, 0x0000, 0x2052, 0x2072, 0xB052, 0xB072, 0xF052, 0xF072, 0x2053, 0x2073, 0x3053, 0x3073, 0xB053, 0xB073, 0xF053, 0xF073, 0xB054, 0xB074, 0xF054, 0xF074, 0x0000, 0x0000, 0x4055, 0x4075, 0x5055, 0x5075, 0x6055, 0x6075, 0xA055, 0xA075, 0xD055, 0xD075, 0xE055, 0xE075, 0x3057, 0x3077, 0x3059, 0x3079, 0x8059, 0x205A, 0x207A, 0x705A, 0x707A, 0xF05A, 0xF07A, 0x0000 }; /** * @internal * @param a Accent 0 ... 15. * @param c Character code in range 0x20 ... 0x7F. * * Translate Teletext Latin G0 character 0x20 ... 0x7F combined * with accent code from Latin G2 0x40 ... 0x4F to Unicode. Not all * combinations are representable in Unicode. * * @bug * Since version 0.2.36 if @a a equals zero (no diacritical mark) this * function replaces character U+002A in the Latin G0 set by U+0040 * in accordance with EN 300 706 V1.2.1 Table 35 Note 3. * * @return * Unicode value or 0. */ unsigned int vbi_teletext_composed_unicode(unsigned int a, unsigned int c) { unsigned int i; assert(a <= 15); assert(c >= 0x20 && c <= 0x7F); if (a == 0) { if (c == 0x2A) return 0x0040u; return vbi_teletext_unicode(LATIN_G0, NO_SUBSET, c); } c += a << 12; for (i = 0; i < sizeof(composed) / sizeof(composed[0]); i++) if (composed[i] == c) return 0x00C0u + i; return 0; } static inline int is_blank(vbi_char c) { /* flash/conceal: undecided; underline: nope. */ if (c.flash || c.conceal || c.underline) return 0; else return c.unicode <= 0x0020 || c.unicode == 0x00A0 || c.unicode == 0xEE00 /* blank, separated */ || c.unicode == 0xEE20; /* blank, contiguous */ } static inline int is_full(vbi_char c) { /* flash/conceal: undecided. */ if (c.flash || c.conceal) return 0; else return c.unicode == 0xEE7F /* G1 block, contiguous form */ || c.unicode == 0xFF3F; /* G3 block */ } /** * @internal * @param pg Formatted vbi_page to be optimized. * @param column First column, 0 ... pg->columns - 1. * @param row First row, 0 ... pg->rows - 1. * @param width Number of columns to optimize, 1 ... pg->columns. * @param height Number of rows to optimize, 1 ... pg->rows. * * Experimental. */ void vbi_optimize_page(vbi_page *pg, int column, int row, int width, int height) { vbi_char c, l, *cp; int column0, row0; int column1, row1; column0 = column; row0 = row; column1 = column + width; row1 = row + height; l = pg->text[pg->columns * row + column]; for (row = row0; row < row1; row++) for (column = column0; column < column1; column++) { cp = pg->text + pg->columns * row + column; c = *cp; if (is_blank(c)) { c.bold = l.bold; c.italic = l.italic; c.foreground = l.foreground; } else if (is_full(c)) { c.bold = l.bold; c.italic = l.italic; c.background = l.background; } *cp = l = c; } for (row = row1 - 1; row >= row0; row--) for (column = column1 - 1; column >= column0; column--) { cp = pg->text + pg->columns * row + column; c = *cp; if (is_blank(c)) { c.bold = l.bold; c.italic = l.italic; c.foreground = l.foreground; } else if (is_full(c)) { c.bold = l.bold; c.italic = l.italic; c.background = l.background; } *cp = l = c; } } /* Closed Caption character set */ /* Closed Caption Basic Character Set. 47 CFR Section 15.119 (g) */ static const uint16_t caption [96][2] = { { 0x0020, 0x0020 }, { 0x0021, 0x0021 }, { 0x0022, 0x0022 }, { 0x0023, 0x0023 }, { 0x0024, 0x0024 }, { 0x0025, 0x0025 }, { 0x0026, 0x0026 }, { 0x0027, 0x0027 }, { 0x0028, 0x0028 }, { 0x0029, 0x0029 }, { 0x00E1, 0x00C1 }, /* 0x2A a with acute accent */ { 0x002B, 0x002B }, { 0x002C, 0x002C }, { 0x002D, 0x002D }, { 0x002E, 0x002E }, { 0x002F, 0x002F }, { 0x0030, 0x0030 }, { 0x0031, 0x0031 }, { 0x0032, 0x0032 }, { 0x0033, 0x0033 }, { 0x0034, 0x0034 }, { 0x0035, 0x0035 }, { 0x0036, 0x0036 }, { 0x0037, 0x0037 }, { 0x0038, 0x0038 }, { 0x0039, 0x0039 }, { 0x003A, 0x003A }, { 0x003B, 0x003B }, { 0x003C, 0x003C }, { 0x003D, 0x003D }, { 0x003E, 0x003E }, { 0x003F, 0x003F }, { 0x0040, 0x0040 }, { 0x0041, 0x0041 }, { 0x0042, 0x0042 }, { 0x0043, 0x0043 }, { 0x0044, 0x0044 }, { 0x0045, 0x0045 }, { 0x0046, 0x0046 }, { 0x0047, 0x0047 }, { 0x0048, 0x0048 }, { 0x0049, 0x0049 }, { 0x004A, 0x004A }, { 0x004B, 0x004B }, { 0x004C, 0x004C }, { 0x004D, 0x004D }, { 0x004E, 0x004E }, { 0x004F, 0x004F }, { 0x0050, 0x0050 }, { 0x0051, 0x0051 }, { 0x0052, 0x0052 }, { 0x0053, 0x0053 }, { 0x0054, 0x0054 }, { 0x0055, 0x0055 }, { 0x0056, 0x0056 }, { 0x0057, 0x0057 }, { 0x0058, 0x0058 }, { 0x0059, 0x0059 }, { 0x005A, 0x005A }, { 0x005B, 0x005B }, { 0x00E9, 0x00C9 }, /* 0x5C e with acute accent */ { 0x005D, 0x005D }, { 0x00ED, 0x00CD }, /* 0x5E i with acute accent */ { 0x00F3, 0x00D3 }, /* 0x5F o with acute accent */ { 0x00FA, 0x00DA }, /* 0x60 u with acute accent */ { 0x0061, 0x0041 }, { 0x0062, 0x0042 }, { 0x0063, 0x0043 }, { 0x0064, 0x0044 }, { 0x0065, 0x0045 }, { 0x0066, 0x0046 }, { 0x0067, 0x0047 }, { 0x0068, 0x0048 }, { 0x0069, 0x0049 }, { 0x006A, 0x004A }, { 0x006B, 0x004B }, { 0x006C, 0x004C }, { 0x006D, 0x004D }, { 0x006E, 0x004E }, { 0x006F, 0x004F }, { 0x0070, 0x0050 }, { 0x0071, 0x0051 }, { 0x0072, 0x0052 }, { 0x0073, 0x0053 }, { 0x0074, 0x0054 }, { 0x0075, 0x0055 }, { 0x0076, 0x0056 }, { 0x0077, 0x0057 }, { 0x0078, 0x0058 }, { 0x0079, 0x0059 }, { 0x007A, 0x005A }, { 0x00E7, 0x00C7 }, /* 0x7B c with cedilla */ { 0x00F7, 0x00F7 }, /* 0x7C Division sign */ { 0x00D1, 0x00D1 }, /* 0x7D N with tilde */ { 0x00F1, 0x00D1 }, /* 0x7E n with tilde */ { 0x25A0, 0x25A0 } /* 0x7F Black square */ }; /* Closed Caption Special Characters. 47 CFR Section 15.119 (g) */ static const uint16_t caption_special [16][2] = { { 0x00AE, 0x00AE }, /* 0x1130 Registered symbol */ { 0x00B0, 0x00B0 }, /* 0x1131 Degree sign */ { 0x00BD, 0x00BD }, /* 0x1132 1/2 */ { 0x00BF, 0x00BF }, /* 0x1133 Inverse question mark */ { 0x2122, 0x2122 }, /* 0x1134 Trademark symbol */ { 0x00A2, 0x00A2 }, /* 0x1135 Cents sign */ { 0x00A3, 0x00A3 }, /* 0x1136 Pounds sign */ { 0x266A, 0x266A }, /* 0x1137 Music note */ { 0x00E0, 0x00C0 }, /* 0x1138 a with grave accent */ { 0x0020, 0x0020 }, /* 0x1139 Transparent space */ { 0x00E8, 0x00C8 }, /* 0x113A e with grave accent */ { 0x00E2, 0x00C2 }, /* 0x113B a with circumflex */ { 0x00EA, 0x00CA }, /* 0x113C e with circumflex */ { 0x00EE, 0x00CE }, /* 0x113D i with circumflex */ { 0x00F4, 0x00D4 }, /* 0x113E o with circumflex */ { 0x00FB, 0x00DB } /* 0x113F u with circumflex */ }; /* Closed Caption Extended Character Set. EIA 608-B Section 6.4.2 */ static const uint16_t caption_extended2 [32][2] = { { 0x00C1, 0x00C1 }, /* 0x1220 A with acute accent */ { 0x00C9, 0x00C9 }, /* 0x1221 E with acute accent */ { 0x00D3, 0x00D3 }, /* 0x1222 O with acute accent */ { 0x00DA, 0x00DA }, /* 0x1223 U with acute accent */ { 0x00DC, 0x00DC }, /* 0x1224 U with diaeresis */ { 0x00FC, 0x00DC }, /* 0x1225 u with diaeresis */ { 0x2018, 0x2018 }, /* 0x1226 Opening single quote */ { 0x00A1, 0x00A1 }, /* 0x1227 Inverted exclamation mark */ { 0x002A, 0x002A }, /* 0x1228 Asterisk */ { 0x0027, 0x0027 }, /* 0x1229 Plain single quote */ { 0x2500, 0x2500 }, /* 0x122A Em dash (for box drawing) */ { 0x00A9, 0x00A9 }, /* 0x122B Copyright */ { 0x2120, 0x2120 }, /* 0x122C Service mark */ { 0x2022, 0x2022 }, /* 0x122D Round bullet */ { 0x201C, 0x201C }, /* 0x122E Opening double quotes */ { 0x201D, 0x201D }, /* 0x122F Closing double quotes */ { 0x00C0, 0x00C0 }, /* 0x1230 A with grave accent */ { 0x00C2, 0x00C2 }, /* 0x1231 A with circumflex accent */ { 0x00C7, 0x00C7 }, /* 0x1232 C with cedilla */ { 0x00C8, 0x00C8 }, /* 0x1233 E with grave accent */ { 0x00CA, 0x00CA }, /* 0x1234 E with circumflex accent */ { 0x00CB, 0x00CB }, /* 0x1235 E with diaeresis */ { 0x00EB, 0x00CB }, /* 0x1236 e with diaeresis */ { 0x00CE, 0x00CE }, /* 0x1237 I with circumflex */ { 0x00CF, 0x00CF }, /* 0x1238 I with diaeresis */ { 0x00EF, 0x00CF }, /* 0x1239 i with diaeresis */ { 0x00D4, 0x00D4 }, /* 0x123A O with circumflex */ { 0x00D9, 0x00D9 }, /* 0x123B U with grave accent */ { 0x00F9, 0x00D9 }, /* 0x123C u with grave accent */ { 0x00DB, 0x00DB }, /* 0x123D U with circumflex accent */ { 0x00AB, 0x00AB }, /* 0x123E Opening guillemets */ { 0x00BB, 0x00BB } /* 0x123F Closing guillemets */ }; /* Closed Caption Extended Character Set. EIA 608-B Section 6.4.2 */ static const uint16_t caption_extended3 [32][2] = { { 0x00C3, 0x00C3 }, /* 0x1320 A with tilde */ { 0x00E3, 0x00C3 }, /* 0x1321 a with tilde */ { 0x00CD, 0x00CD }, /* 0x1322 I with acute accent */ { 0x00CC, 0x00CC }, /* 0x1323 I with grave accent */ { 0x00EC, 0x00CC }, /* 0x1324 i with grave accent */ { 0x00D2, 0x00D2 }, /* 0x1325 O with grave accent */ { 0x00F2, 0x00D2 }, /* 0x1326 o with grave accent */ { 0x00D5, 0x00D5 }, /* 0x1327 O with tilde */ { 0x00F5, 0x00D5 }, /* 0x1328 o with tilde */ { 0x007B, 0x007B }, /* 0x1329 Opening curly brace */ { 0x007D, 0x007D }, /* 0x132A Closing curly brace */ { 0x005C, 0x005C }, /* 0x132B Backslash */ { 0x005E, 0x005E }, /* 0x132C Caret */ { 0x005F, 0x005F }, /* 0x132D Underscore */ { 0x007C, 0x007C }, /* 0x132E Vertical bar */ { 0x007E, 0x007E }, /* 0x132F Tilde */ { 0x00C4, 0x00C4 }, /* 0x1330 A with diaeresis */ { 0x00E4, 0x00C4 }, /* 0x1331 a with diaeresis */ { 0x00D6, 0x00D6 }, /* 0x1332 O with diaeresis */ { 0x00F6, 0x00D6 }, /* 0x1333 o with diaeresis */ { 0x00DF, 0x00DF }, /* 0x1334 Sharp s */ { 0x00A5, 0x00A5 }, /* 0x1335 Yen sign */ { 0x00A4, 0x00A4 }, /* 0x1336 Currency sign */ { 0x2502, 0x2502 }, /* 0x1337 Vertical bar (for box drawing) */ { 0x00C5, 0x00C5 }, /* 0x1338 A with ring above */ { 0x00E5, 0x00C5 }, /* 0x1339 a with ring above */ { 0x00D8, 0x00D8 }, /* 0x133A O with slash */ { 0x00F8, 0x00D8 }, /* 0x133B o with slash */ { 0x250C, 0x250C }, /* 0x133C Upper left corner */ { 0x2510, 0x2510 }, /* 0x133D Upper right corner */ { 0x2514, 0x2514 }, /* 0x133E Lower left corner */ { 0x2518, 0x2518 } /* 0x133F Lower right corner */ }; /** * @ingroup Conv * @param c Character code in range 0x20 ... 0x7F, * 0x1130 ... 0x113F, 0x1930 ... 0x193F, 0x1220 ... 0x123F, * 0x1A20 ... 0x1A3F, 0x1320 ... 0x133F, 0x1B20 ... 0x1B3F. * @param to_upper Convert the character to upper case. (In general * real time caption is capitalized, but for a few accented characters * older versions of the standard defined only lower case character * codes. This option is available to conveniently capitalize all * characters received.) * * Converts a Closed Caption character code to Unicode. Codes * in range 0x1130 to 0x1B3F are "special characters" and "extended * characters" (e.g. caption command 0x11 0x37). * * @see vbi_strndup_iconv_caption() * * @return * Unicode value or 0 if @a c is outside the valid ranges. * * @since 0.2.23 */ unsigned int vbi_caption_unicode (unsigned int c, vbi_bool to_upper) { to_upper = !!to_upper; /* Note the comparisons are sorted for shortest path. */ if (likely (c < 0x80)) { if (likely (c >= 0x20)) { return caption[c - 0x20][to_upper]; } } else { c &= ~0x0800; /* ignore channel bit */ if (c < 0x1240) { if (c < 0x1140 && c >= 0x1130) { /* 001 c001 011 xxxx */ return caption_special[c - 0x1130][to_upper]; } else if (c >= 0x1220) { /* 001 c010 01x xxxx */ return caption_extended2[c - 0x1220][to_upper]; } } else if (c < 0x1340 && c >= 0x1320) { /* 001 c011 01x xxxx */ return caption_extended3[c - 0x1320][to_upper]; } } return 0; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/lang.h000066400000000000000000000077021476363111200144460ustar00rootroot00000000000000/* * libzvbi -- Teletext and Closed Caption character set * * Copyright (C) 2000, 2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: lang.h,v 1.9 2008-02-19 00:35:20 mschimek Exp $ */ #ifndef LANG_H #define LANG_H #include "bcd.h" /* vbi_bool */ #include "format.h" /* vbi_page */ /** * @internal * * Teletext character set according to ETS 300 706, Section 15. */ typedef enum { LATIN_G0 = 1, LATIN_G2, CYRILLIC_1_G0, CYRILLIC_2_G0, CYRILLIC_3_G0, CYRILLIC_G2, GREEK_G0, GREEK_G2, ARABIC_G0, ARABIC_G2, HEBREW_G0, BLOCK_MOSAIC_G1, SMOOTH_MOSAIC_G3 } vbi_character_set; /** * @internal * * Teletext Latin G0 national option subsets according to * ETS 300 706, Section 15.2; Section 15.6.2 Table 36. */ typedef enum { NO_SUBSET, CZECH_SLOVAK, ENGLISH, ESTONIAN, FRENCH, GERMAN, ITALIAN, LETT_LITH, POLISH, PORTUG_SPANISH, RUMANIAN, SERB_CRO_SLO, SWE_FIN_HUN, TURKISH } vbi_national_subset; /** * @internal * * vbi_font_descriptors[], array of vbi_font_descr implements * the Teletext character set designation tables in ETS 300 706, * Section 15: Table 32, 33 and 34. */ struct vbi_font_descr { vbi_character_set G0; vbi_character_set G2; vbi_national_subset subset; /* applies only to LATIN_G0 */ char * label; /* Latin-1 */ }; extern struct vbi_font_descr vbi_font_descriptors[88]; #define VALID_CHARACTER_SET(n) ((n) < 88 && vbi_font_descriptors[n].G0) /* Public */ /** * @ingroup Page * @brief Opaque font descriptor. */ typedef struct vbi_font_descr vbi_font_descr; /** * @ingroup Page * @param unicode Unicode as in vbi_char. * * @return * @c TRUE if @a unicode represents a Teletext or Closed Caption * printable character. This excludes Teletext Arabic characters (which * are represented by private codes U+E600 ... U+E7FF until the conversion * table is ready), the Teletext Turkish currency sign U+E800 which is not * representable in Unicode, the Teletext G1 Block Mosaic and G3 Smooth * Mosaics and Line Drawing Set, with codes U+EE00 ... U+EFFF, and * Teletext DRCS coded U+F000 ... U+F7FF. */ _vbi_inline vbi_bool vbi_is_print(unsigned int unicode) { return unicode < 0xE600; } /** * @ingroup Page * @param unicode Unicode as in vbi_char. * * @return * @c TRUE if @a unicode represents a Teletext G1 Block Mosaic or G3 Smooth * Mosaics and Line Drawing Set, that is a code in range U+EE00 ... U+EFFF. */ _vbi_inline vbi_bool vbi_is_gfx(unsigned int unicode) { return unicode >= 0xEE00 && unicode <= 0xEFFF; } /** * @ingroup Page * @param unicode Unicode as in vbi_char. * * @return * @c TRUE if @a unicode represents a Teletext DRCS (Dynamically * Redefinable Character), that is a code in range U+F000 ... U+F7FF. **/ _vbi_inline vbi_bool vbi_is_drcs(unsigned int unicode) { return unicode >= 0xF000; } extern unsigned int vbi_caption_unicode (unsigned int c, vbi_bool to_upper); /* Private */ extern unsigned int vbi_teletext_unicode(vbi_character_set s, vbi_national_subset n, unsigned int c); extern unsigned int vbi_teletext_composed_unicode(unsigned int a, unsigned int c); extern void vbi_optimize_page(vbi_page *pg, int column, int row, int width, int height); #endif /* LANG_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/libzvbi.h000066400000000000000000001403761476363111200151730ustar00rootroot00000000000000/* * libzvbi -- VBI decoding library * * Copyright (C) 2000, 2001, 2002 Michael H. Schimek * Copyright (C) 2000, 2001 Iñaki García Etxebarria * * Originally based on AleVT 1.5.1 by Edgar Toernig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ #ifndef __LIBZVBI_H__ #define __LIBZVBI_H__ #define VBI_VERSION_MAJOR 0 #define VBI_VERSION_MINOR 2 #define VBI_VERSION_MICRO 44 #ifdef __cplusplus extern "C" { #endif typedef struct vbi_decoder vbi_decoder; /* macros.h */ #if __GNUC__ >= 4 # define _vbi_sentinel __attribute__ ((__sentinel__(0))) # define _vbi_deprecated __attribute__ ((__deprecated__)) #else # define _vbi_sentinel # define _vbi_deprecated # define __restrict__ #endif #if (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) || __GNUC__ >= 4 # define _vbi_nonnull(params) __attribute__ ((__nonnull__ params)) # define _vbi_format(params) __attribute__ ((__format__ params)) #else # define _vbi_nonnull(params) # define _vbi_format(params) #endif #if __GNUC__ >= 3 # define _vbi_pure __attribute__ ((__pure__)) # define _vbi_alloc __attribute__ ((__malloc__)) #else # define _vbi_pure # define _vbi_alloc #endif #if __GNUC__ >= 2 # define _vbi_unused __attribute__ ((__unused__)) # define _vbi_const __attribute__ ((__const__)) # define _vbi_inline static __inline__ #else # define _vbi_unused # define _vbi_const # define _vbi_inline static #endif #ifndef TRUE # define TRUE 1 #endif #ifndef FALSE # define FALSE 0 #endif typedef int vbi_bool; #ifndef NULL # ifdef __cplusplus # define NULL (0L) # else # define NULL ((void *) 0) # endif #endif /* XXX Document me - for variadic funcs. */ #define VBI_END ((void *) 0) #if 0 typedef void vbi_lock_fn (void * user_data); typedef void vbi_unlock_fn (void * user_data); #endif typedef enum { VBI_LOG_ERROR = 1 << 3, VBI_LOG_WARNING = 1 << 4, VBI_LOG_NOTICE = 1 << 5, VBI_LOG_INFO = 1 << 6, VBI_LOG_DEBUG = 1 << 7, VBI_LOG_DRIVER = 1 << 8, VBI_LOG_DEBUG2 = 1 << 9, VBI_LOG_DEBUG3 = 1 << 10 } vbi_log_mask; typedef void vbi_log_fn (vbi_log_mask level, const char * context, const char * message, void * user_data); extern vbi_log_fn vbi_log_on_stderr; /* bcd.h */ /* XXX unsigned? */ typedef int vbi_pgno; typedef int vbi_subno; #define VBI_ANY_SUBNO 0x3F7F #define VBI_NO_SUBNO 0x3F7F _vbi_inline unsigned int vbi_dec2bcd(unsigned int dec) { return (dec % 10) + ((dec / 10) % 10) * 16 + ((dec / 100) % 10) * 256; } #define vbi_bin2bcd(n) vbi_dec2bcd(n) _vbi_inline unsigned int vbi_bcd2dec(unsigned int bcd) { return (bcd & 15) + ((bcd >> 4) & 15) * 10 + ((bcd >> 8) & 15) * 100; } #define vbi_bcd2bin(n) vbi_bcd2dec(n) _vbi_inline unsigned int vbi_add_bcd(unsigned int a, unsigned int b) { unsigned int t; a += 0x06666666; t = a + b; b ^= a ^ t; b = (~b & 0x11111110) >> 3; b |= b * 2; return t - b; } _vbi_inline vbi_bool vbi_is_bcd(unsigned int bcd) { static const unsigned int x = 0x06666666; return (((bcd + x) ^ (bcd ^ x)) & 0x11111110) == 0; } _vbi_inline vbi_bool vbi_bcd_digits_greater (unsigned int bcd, unsigned int maximum) { maximum ^= ~0; return 0 != (((bcd + maximum) ^ bcd ^ maximum) & 0x11111110); } /* conv.h */ #include #include /* uint16_t */ #define VBI_NUL_TERMINATED -1 extern unsigned long vbi_strlen_ucs2 (const uint16_t * src); extern char * vbi_strndup_iconv (const char * dst_codeset, const char * src_codeset, const char * src, unsigned long src_size, int repl_char) _vbi_alloc; extern char * vbi_strndup_iconv_ucs2 (const char * dst_codeset, const uint16_t * src, long src_length, int repl_char) _vbi_alloc; extern char * vbi_strndup_iconv_caption (const char * dst_codeset, const char * src, long src_length, int repl_char) _vbi_alloc; #if 3 == VBI_VERSION_MINOR extern char * vbi_strndup_iconv_teletext (const char * dst_codeset, const vbi_ttx_charset *cs, const uint8_t * src, long src_length, int repl_char) _vbi_alloc _vbi_nonnull ((2)); #endif extern vbi_bool vbi_fputs_iconv (FILE * fp, const char * dst_codeset, const char * src_codeset, const char * src, unsigned long src_size, int repl_char) _vbi_nonnull ((1)); extern vbi_bool vbi_fputs_iconv_ucs2 (FILE * fp, const char * dst_codeset, const uint16_t * src, long src_length, int repl_char) _vbi_nonnull ((1)); extern const char * vbi_locale_codeset (void); /* network.h */ typedef enum { VBI_CNI_TYPE_NONE, VBI_CNI_TYPE_UNKNOWN = VBI_CNI_TYPE_NONE, VBI_CNI_TYPE_VPS, VBI_CNI_TYPE_8301, VBI_CNI_TYPE_8302, VBI_CNI_TYPE_PDC_A, VBI_CNI_TYPE_PDC_B } vbi_cni_type; /* pdc.h */ #include /* time_t */ typedef unsigned int vbi_pil; #define VBI_PIL(month, day, hour, minute) \ (((day) << 15) | ((month) << 11) | ((hour) << 6) | (minute)) #define VBI_PIL_MONTH(pil) (((pil) >> 11) & 15) #define VBI_PIL_DAY(pil) (((pil) >> 15) & 31) #define VBI_PIL_HOUR(pil) (((pil) >> 6) & 31) #define VBI_PIL_MINUTE(pil) ((pil) & 63) enum { VBI_PIL_TIMER_CONTROL = VBI_PIL (15, 0, 31, 63), VBI_PIL_INHIBIT_TERMINATE = VBI_PIL (15, 0, 30, 63), VBI_PIL_INTERRUPTION = VBI_PIL (15, 0, 29, 63), VBI_PIL_CONTINUE = VBI_PIL (15, 0, 28, 63), VBI_PIL_NSPV = VBI_PIL (15, 15, 31, 63), VBI_PIL_END = VBI_PIL (15, 15, 31, 63) }; extern vbi_bool vbi_pil_is_valid_date (vbi_pil pil); extern time_t vbi_pil_to_time (vbi_pil pil, time_t start, const char * tz); extern time_t vbi_pil_lto_to_time (vbi_pil pil, time_t start, int seconds_east); extern vbi_bool vbi_pty_validity_window (time_t * begin, time_t * end, time_t time, const char * tz) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_pil_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, const char * tz) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_pil_lto_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, int seconds_east) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; typedef enum { VBI_PID_CHANNEL_LCI_0 = 0, VBI_PID_CHANNEL_LCI_1, VBI_PID_CHANNEL_LCI_2, VBI_PID_CHANNEL_LCI_3, VBI_PID_CHANNEL_VPS, VBI_PID_CHANNEL_PDC_DESCRIPTOR, VBI_PID_CHANNEL_XDS_CURRENT, VBI_PID_CHANNEL_XDS_FUTURE, VBI_MAX_PID_CHANNELS } vbi_pid_channel; typedef enum { VBI_PCS_AUDIO_UNKNOWN = 0, VBI_PCS_AUDIO_MONO, VBI_PCS_AUDIO_STEREO, VBI_PCS_AUDIO_BILINGUAL } vbi_pcs_audio; typedef struct { vbi_pid_channel channel; vbi_cni_type cni_type; unsigned int cni; vbi_pil pil; vbi_bool luf; vbi_bool mi; vbi_bool prf; vbi_pcs_audio pcs_audio; unsigned int pty; vbi_bool tape_delayed; void * _reserved2[2]; int _reserved3[4]; } vbi_program_id; /* event.h */ #include typedef unsigned int vbi_nuid; typedef struct { vbi_nuid nuid; signed char name[64]; signed char call[40]; int tape_delay; int cni_vps; int cni_8301; int cni_8302; int reserved; int cycle; } vbi_network; /* * Link */ typedef enum { VBI_LINK_NONE = 0, VBI_LINK_MESSAGE, VBI_LINK_PAGE, VBI_LINK_SUBPAGE, VBI_LINK_HTTP, VBI_LINK_FTP, VBI_LINK_EMAIL, VBI_LINK_LID, VBI_LINK_TELEWEB } vbi_link_type; typedef enum { VBI_WEBLINK_UNKNOWN = 0, VBI_WEBLINK_PROGRAM_RELATED, VBI_WEBLINK_NETWORK_RELATED, VBI_WEBLINK_STATION_RELATED, VBI_WEBLINK_SPONSOR_MESSAGE, VBI_WEBLINK_OPERATOR } vbi_itv_type; typedef struct vbi_link { vbi_link_type type; vbi_bool eacem; signed char name[80]; signed char url[256]; signed char script[256]; vbi_nuid nuid; vbi_pgno pgno; vbi_subno subno; double expires; vbi_itv_type itv_type; int priority; vbi_bool autoload; } vbi_link; /* * Aspect ratio information. */ typedef enum { VBI_SUBT_NONE, VBI_SUBT_ACTIVE, VBI_SUBT_MATTE, VBI_SUBT_UNKNOWN } vbi_subt; typedef struct { int first_line; int last_line; double ratio; vbi_bool film_mode; vbi_subt open_subtitles; } vbi_aspect_ratio; /* * Program Info * * ATTN this is new stuff and subject to change */ typedef enum { VBI_RATING_AUTH_NONE = 0, VBI_RATING_AUTH_MPAA, VBI_RATING_AUTH_TV_US, VBI_RATING_AUTH_TV_CA_EN, VBI_RATING_AUTH_TV_CA_FR } vbi_rating_auth; #define VBI_RATING_D 0x08 #define VBI_RATING_L 0x04 #define VBI_RATING_S 0x02 #define VBI_RATING_V 0x01 extern const char * vbi_rating_string(vbi_rating_auth auth, int id); typedef enum { VBI_PROG_CLASSF_NONE = 0, VBI_PROG_CLASSF_EIA_608, VBI_PROG_CLASSF_ETS_300231 } vbi_prog_classf; extern const char * vbi_prog_type_string(vbi_prog_classf classf, int id); /* code depends on order, don't change */ typedef enum { VBI_AUDIO_MODE_NONE = 0, VBI_AUDIO_MODE_MONO, VBI_AUDIO_MODE_STEREO, VBI_AUDIO_MODE_STEREO_SURROUND, VBI_AUDIO_MODE_SIMULATED_STEREO, VBI_AUDIO_MODE_VIDEO_DESCRIPTIONS, VBI_AUDIO_MODE_NON_PROGRAM_AUDIO, VBI_AUDIO_MODE_SPECIAL_EFFECTS, VBI_AUDIO_MODE_DATA_SERVICE, VBI_AUDIO_MODE_UNKNOWN } vbi_audio_mode; typedef struct vbi_program_info { /* * Refers to the current or next program. * (No [2] to allow clients filtering current data more easily.) */ unsigned int future : 1; /* 01 Program Identification Number */ /* If unknown all these fields are -1 */ signed char month; /* 0 ... 11 */ signed char day; /* 0 ... 30 */ signed char hour; /* 0 ... 23 */ signed char min; /* 0 ... 59 */ /* * VD: "T indicates if a program is routinely tape delayed for the * Mountain and Pacific time zones." */ signed char tape_delayed; /* 02 Program Length */ /* If unknown all these fields are -1 */ signed char length_hour; /* 0 ... 63 */ signed char length_min; /* 0 ... 59 */ signed char elapsed_hour; /* 0 ... 63 */ signed char elapsed_min; /* 0 ... 59 */ signed char elapsed_sec; /* 0 ... 59 */ /* 03 Program name */ /* If unknown title[0] == 0 */ signed char title[64]; /* ASCII + '\0' */ /* 04 Program type */ /* * If unknown type_classf == VBI_PROG_CLASSF_NONE. * VBI_PROG_CLASSF_EIA_608 can have up to 32 tags * identifying 96 keywords. Their numerical value * is given here instead of composing a string for * easier filtering. Use vbi_prog_type_str_by_id to * get the keywords. A zero marks the end. */ vbi_prog_classf type_classf; int type_id[33]; /* 05 Program rating */ /* * For details STFW for "v-chip" * If unknown rating_auth == VBI_RATING_NONE */ vbi_rating_auth rating_auth; int rating_id; /* Only valid when auth == VBI_RATING_TV_US */ int rating_dlsv; /* 06 Program Audio Services */ /* * BTSC audio (two independent tracks) is flagged according to XDS, * Zweiton/NICAM/EIA-J audio is flagged mono/none, stereo/none or * mono/mono for bilingual transmissions. */ struct { /* If unknown mode == VBI_AUDIO_MODE_UNKNOWN */ vbi_audio_mode mode; /* If unknown language == NULL */ unsigned char * language; /* Latin-1 */ } audio[2]; /* primary and secondary */ /* 07 Program Caption Services */ /* * Bits 0...7 corresponding to Caption page 1...8. * Note for the current program this information is also * available via vbi_classify_page(). * * If unknown caption_services == -1, _language[] = NULL */ int caption_services; unsigned char * caption_language[8]; /* Latin-1 */ /* 08 Copy Generation Management System */ /* If unknown cgms_a == -1 */ int cgms_a; /* XXX */ /* 09 Aspect Ratio */ /* * Note for the current program this information is also * available via VBI_EVENT_ASPECT. * * If unknown first_line == last_line == -1, ratio == 0.0 */ vbi_aspect_ratio aspect; /* 10 - 17 Program Description */ /* * 8 rows of 0...32 ASCII chars + '\0', * if unknown description[0...7][0] == 0 */ signed char description[8][33]; } vbi_program_info; extern void vbi_reset_prog_info(vbi_program_info *pi); #define VBI_EVENT_NONE 0x0000 #define VBI_EVENT_CLOSE 0x0001 #define VBI_EVENT_TTX_PAGE 0x0002 #define VBI_EVENT_CAPTION 0x0004 #define VBI_EVENT_NETWORK 0x0008 #define VBI_EVENT_TRIGGER 0x0010 #define VBI_EVENT_ASPECT 0x0040 #define VBI_EVENT_PROG_INFO 0x0080 #define VBI_EVENT_NETWORK_ID 0x0100 #define VBI_EVENT_LOCAL_TIME 0x0400 #define VBI_EVENT_PROG_ID 0x0800 typedef enum { VBI_DST_UNKNOWN = 0, VBI_DST_INCLUDED, VBI_DST_INACTIVE, VBI_DST_ACTIVE } vbi_dst_state; typedef struct { time_t time; int seconds_east; vbi_bool seconds_east_valid; vbi_dst_state dst_state; } vbi_local_time; /* Experimental CC608 decoder. */ #define _VBI_EVENT_CC608 0x1000 #define _VBI_EVENT_CC608_STREAM 0x2000 struct _vbi_event_cc608_page; struct _vbi_event_cc608_stream; #include /* XXX network, aspect, prog_info: should only notify about * changes and provide functions to query current value. */ typedef struct vbi_event { int type; union { struct { int pgno; int subno; uint8_t * raw_header; int pn_offset; unsigned int roll_header : 1; unsigned int header_update : 1; unsigned int clock_update : 1; } ttx_page; struct { int pgno; } caption; vbi_network network; vbi_link * trigger; vbi_aspect_ratio aspect; vbi_program_info * prog_info; vbi_local_time * local_time; vbi_program_id * prog_id; /* Experimental. */ struct _vbi_event_cc608_page * _cc608; struct _vbi_event_cc608_stream * _cc608_stream; } ev; } vbi_event; typedef void (* vbi_event_handler)(vbi_event *event, void *user_data); extern vbi_bool vbi_event_handler_add(vbi_decoder *vbi, int event_mask, vbi_event_handler handler, void *user_data); extern void vbi_event_handler_remove(vbi_decoder *vbi, vbi_event_handler handler); extern vbi_bool vbi_event_handler_register(vbi_decoder *vbi, int event_mask, vbi_event_handler handler, void *user_data); extern void vbi_event_handler_unregister(vbi_decoder *vbi, vbi_event_handler handler, void *user_data); /* format.h */ #include /* Code depends on order, don't change. */ typedef enum { VBI_BLACK, VBI_RED, VBI_GREEN, VBI_YELLOW, VBI_BLUE, VBI_MAGENTA, VBI_CYAN, VBI_WHITE } vbi_color; typedef uint32_t vbi_rgba; typedef enum { VBI_TRANSPARENT_SPACE, VBI_TRANSPARENT_FULL, VBI_SEMI_TRANSPARENT, VBI_OPAQUE } vbi_opacity; /* Code depends on order, don't change. */ typedef enum { VBI_NORMAL_SIZE, VBI_DOUBLE_WIDTH, VBI_DOUBLE_HEIGHT, VBI_DOUBLE_SIZE, VBI_OVER_TOP, VBI_OVER_BOTTOM, VBI_DOUBLE_HEIGHT2, VBI_DOUBLE_SIZE2 } vbi_size; typedef struct vbi_char { unsigned underline : 1; unsigned bold : 1; unsigned italic : 1; unsigned flash : 1; unsigned conceal : 1; unsigned proportional : 1; unsigned link : 1; unsigned reserved : 1; unsigned size : 8; unsigned opacity : 8; unsigned foreground : 8; unsigned background : 8; unsigned drcs_clut_offs : 8; unsigned unicode : 16; } vbi_char; struct vbi_font_descr; typedef struct vbi_page { vbi_decoder * vbi; vbi_nuid nuid; /* FIXME this shouldn't be int */ int pgno; /* FIXME this shouldn't be int */ int subno; int rows; int columns; vbi_char text[1056]; struct { /* int x0, x1; */ int y0, y1; int roll; } dirty; vbi_color screen_color; vbi_opacity screen_opacity; vbi_rgba color_map[40]; uint8_t * drcs_clut; /* 64 entries */ uint8_t * drcs[32]; struct { int pgno, subno; } nav_link[6]; char nav_index[64]; struct vbi_font_descr * font[2]; unsigned int double_height_lower; /* legacy */ vbi_opacity page_opacity[2]; vbi_opacity boxed_opacity[2]; } vbi_page; /* lang.h */ typedef struct vbi_font_descr vbi_font_descr; _vbi_inline vbi_bool vbi_is_print(unsigned int unicode) { return unicode < 0xE600; } _vbi_inline vbi_bool vbi_is_gfx(unsigned int unicode) { return unicode >= 0xEE00 && unicode <= 0xEFFF; } _vbi_inline vbi_bool vbi_is_drcs(unsigned int unicode) { return unicode >= 0xF000; } extern unsigned int vbi_caption_unicode (unsigned int c, vbi_bool to_upper); /* export.h */ #include /* FILE */ #include /* size_t, ssize_t */ typedef struct vbi_export vbi_export; typedef struct vbi_export_info { char * keyword; char * label; char * tooltip; char * mime_type; char * extension; } vbi_export_info; typedef enum { VBI_OPTION_BOOL = 1, VBI_OPTION_INT, VBI_OPTION_REAL, VBI_OPTION_STRING, VBI_OPTION_MENU } vbi_option_type; typedef union { int num; double dbl; char * str; } vbi_option_value; typedef union { int * num; double * dbl; char ** str; } vbi_option_value_ptr; typedef struct { vbi_option_type type; char * keyword; char * label; vbi_option_value def; vbi_option_value min; vbi_option_value max; vbi_option_value step; vbi_option_value_ptr menu; char * tooltip; } vbi_option_info; extern vbi_export_info * vbi_export_info_enum(int index); extern vbi_export_info * vbi_export_info_keyword(const char *keyword); extern vbi_export_info * vbi_export_info_export(vbi_export *); extern vbi_export * vbi_export_new(const char *keyword, char **errstr); extern void vbi_export_delete(vbi_export *); extern vbi_option_info * vbi_export_option_info_enum(vbi_export *, int index); extern vbi_option_info * vbi_export_option_info_keyword(vbi_export *, const char *keyword); extern vbi_bool vbi_export_option_set(vbi_export *, const char *keyword, ...); extern vbi_bool vbi_export_option_get(vbi_export *, const char *keyword, vbi_option_value *value); extern vbi_bool vbi_export_option_menu_set(vbi_export *, const char *keyword, int entry); extern vbi_bool vbi_export_option_menu_get(vbi_export *, const char *keyword, int *entry); extern ssize_t vbi_export_mem (vbi_export * e, void * buffer, size_t buffer_size, const vbi_page * pg) _vbi_nonnull ((1)); /* sic */ extern void * vbi_export_alloc (vbi_export * e, void ** buffer, size_t * buffer_size, const vbi_page * pg) _vbi_nonnull ((1)); /* sic */ extern vbi_bool vbi_export_stdio(vbi_export *, FILE *fp, vbi_page *pg); extern vbi_bool vbi_export_file(vbi_export *, const char *name, vbi_page *pg); extern char * vbi_export_errstr(vbi_export *); /* cache.h */ extern void vbi_unref_page(vbi_page *pg); extern int vbi_is_cached(vbi_decoder *, int pgno, int subno); extern int vbi_cache_hi_subno(vbi_decoder *vbi, int pgno); /* search.h */ typedef enum { VBI_SEARCH_ERROR = -3, VBI_SEARCH_CACHE_EMPTY, VBI_SEARCH_CANCELED, VBI_SEARCH_NOT_FOUND = 0, VBI_SEARCH_SUCCESS } vbi_search_status; typedef struct vbi_search vbi_search; extern vbi_search * vbi_search_new(vbi_decoder *vbi, vbi_pgno pgno, vbi_subno subno, uint16_t *pattern, vbi_bool casefold, vbi_bool regexp, int (* progress)(vbi_page *pg)); extern void vbi_search_delete(vbi_search *search); extern vbi_search_status vbi_search_next(vbi_search *search, vbi_page **pg, int dir); /* sliced.h */ #include #define VBI_SLICED_NONE 0 #define VBI_SLICED_UNKNOWN 0 #define VBI_SLICED_ANTIOPE 0x00002000 #define VBI_SLICED_TELETEXT_A 0x00002000 #define VBI_SLICED_TELETEXT_B_L10_625 0x00000001 #define VBI_SLICED_TELETEXT_B_L25_625 0x00000002 #define VBI_SLICED_TELETEXT_B (VBI_SLICED_TELETEXT_B_L10_625 | \ VBI_SLICED_TELETEXT_B_L25_625) #define VBI_SLICED_TELETEXT_B_625 VBI_SLICED_TELETEXT_B #define VBI_SLICED_TELETEXT_C_625 0x00004000 #define VBI_SLICED_TELETEXT_D_625 0x00008000 #define VBI_SLICED_VPS 0x00000004 #define VBI_SLICED_VPS_F2 0x00001000 #define VBI_SLICED_CAPTION_625_F1 0x00000008 #define VBI_SLICED_CAPTION_625_F2 0x00000010 #define VBI_SLICED_CAPTION_625 (VBI_SLICED_CAPTION_625_F1 | \ VBI_SLICED_CAPTION_625_F2) #define VBI_SLICED_WSS_625 0x00000400 #define VBI_SLICED_CAPTION_525_F1 0x00000020 #define VBI_SLICED_CAPTION_525_F2 0x00000040 #define VBI_SLICED_CAPTION_525 (VBI_SLICED_CAPTION_525_F1 | \ VBI_SLICED_CAPTION_525_F2) #define VBI_SLICED_2xCAPTION_525 0x00000080 #define VBI_SLICED_TELETEXT_B_525 0x00010000 #define VBI_SLICED_NABTS 0x00000100 #define VBI_SLICED_TELETEXT_C_525 0x00000100 #define VBI_SLICED_TELETEXT_BD_525 0x00000200 #define VBI_SLICED_TELETEXT_D_525 0x00020000 #define VBI_SLICED_WSS_CPR1204 0x00000800 #define VBI_SLICED_VBI_625 0x20000000 #define VBI_SLICED_VBI_525 0x40000000 typedef unsigned int vbi_service_set; typedef struct { uint32_t id; uint32_t line; uint8_t data[56]; } vbi_sliced; extern const char * vbi_sliced_name (vbi_service_set service) _vbi_const; extern unsigned int vbi_sliced_payload_bits (vbi_service_set service) _vbi_const; /* decoder.h */ #include /* Bit slicer */ /* Attn: keep this in sync with rte, don't change order */ typedef enum { VBI_PIXFMT_YUV420 = 1, VBI_PIXFMT_YUYV, VBI_PIXFMT_YVYU, VBI_PIXFMT_UYVY, VBI_PIXFMT_VYUY, VBI_PIXFMT_PAL8, VBI_PIXFMT_RGBA32_LE = 32, VBI_PIXFMT_RGBA32_BE, VBI_PIXFMT_BGRA32_LE, VBI_PIXFMT_BGRA32_BE, VBI_PIXFMT_ABGR32_BE = 32, /* synonyms */ VBI_PIXFMT_ABGR32_LE, VBI_PIXFMT_ARGB32_BE, VBI_PIXFMT_ARGB32_LE, VBI_PIXFMT_RGB24, VBI_PIXFMT_BGR24, VBI_PIXFMT_RGB16_LE, VBI_PIXFMT_RGB16_BE, VBI_PIXFMT_BGR16_LE, VBI_PIXFMT_BGR16_BE, VBI_PIXFMT_RGBA15_LE, VBI_PIXFMT_RGBA15_BE, VBI_PIXFMT_BGRA15_LE, VBI_PIXFMT_BGRA15_BE, VBI_PIXFMT_ARGB15_LE, VBI_PIXFMT_ARGB15_BE, VBI_PIXFMT_ABGR15_LE, VBI_PIXFMT_ABGR15_BE } vbi_pixfmt; typedef enum { VBI_MODULATION_NRZ_LSB, VBI_MODULATION_NRZ_MSB, VBI_MODULATION_BIPHASE_LSB, VBI_MODULATION_BIPHASE_MSB } vbi_modulation; typedef struct vbi_bit_slicer { vbi_bool (* func)(struct vbi_bit_slicer *slicer, uint8_t *raw, uint8_t *buf); unsigned int cri; unsigned int cri_mask; int thresh; int cri_bytes; int cri_rate; int oversampling_rate; int phase_shift; int step; unsigned int frc; int frc_bits; int payload; int endian; int skip; } vbi_bit_slicer; extern void vbi_bit_slicer_init(vbi_bit_slicer *slicer, int raw_samples, int sampling_rate, int cri_rate, int bit_rate, unsigned int cri_frc, unsigned int cri_mask, int cri_bits, int frc_bits, int payload, vbi_modulation modulation, vbi_pixfmt fmt); _vbi_inline vbi_bool vbi_bit_slice(vbi_bit_slicer *slicer, uint8_t *raw, uint8_t *buf) { return slicer->func(slicer, raw, buf); } typedef struct vbi_raw_decoder { /* Sampling parameters */ int scanning; vbi_pixfmt sampling_format; int sampling_rate; /* Hz */ int bytes_per_line; int offset; /* 0H, samples */ int start[2]; /* ITU-R numbering */ int count[2]; /* field lines */ vbi_bool interlaced; vbi_bool synchronous; /*< private >*/ pthread_mutex_t mutex; unsigned int services; int num_jobs; int8_t * pattern; struct _vbi_raw_decoder_job { unsigned int id; int offset; vbi_bit_slicer slicer; } jobs[8]; } vbi_raw_decoder; extern void vbi_raw_decoder_init(vbi_raw_decoder *rd); extern void vbi_raw_decoder_reset(vbi_raw_decoder *rd); extern void vbi_raw_decoder_destroy(vbi_raw_decoder *rd); extern unsigned int vbi_raw_decoder_add_services(vbi_raw_decoder *rd, unsigned int services, int strict); extern unsigned int vbi_raw_decoder_check_services(vbi_raw_decoder *rd, unsigned int services, int strict); extern unsigned int vbi_raw_decoder_remove_services(vbi_raw_decoder *rd, unsigned int services); extern void vbi_raw_decoder_resize( vbi_raw_decoder *rd, int * start, unsigned int * count ); extern unsigned int vbi_raw_decoder_parameters(vbi_raw_decoder *rd, unsigned int services, int scanning, int *max_rate); extern int vbi_raw_decode(vbi_raw_decoder *rd, uint8_t *raw, vbi_sliced *out); /* sampling_par.h */ typedef vbi_raw_decoder vbi_sampling_par; #define VBI_VIDEOSTD_SET_EMPTY 0 #define VBI_VIDEOSTD_SET_PAL_BG 1 #define VBI_VIDEOSTD_SET_625_50 1 #define VBI_VIDEOSTD_SET_525_60 2 #define VBI_VIDEOSTD_SET_ALL 3 typedef uint64_t vbi_videostd_set; /* dvb_demux.h */ typedef struct _vbi_dvb_demux vbi_dvb_demux; typedef vbi_bool vbi_dvb_demux_cb (vbi_dvb_demux * dx, void * user_data, const vbi_sliced * sliced, unsigned int sliced_lines, int64_t pts); extern void vbi_dvb_demux_reset (vbi_dvb_demux * dx); extern unsigned int vbi_dvb_demux_cor (vbi_dvb_demux * dx, vbi_sliced * sliced, unsigned int sliced_lines, int64_t * pts, const uint8_t ** buffer, unsigned int * buffer_left); extern vbi_bool vbi_dvb_demux_feed (vbi_dvb_demux * dx, const uint8_t * buffer, unsigned int buffer_size); extern void vbi_dvb_demux_set_log_fn (vbi_dvb_demux * dx, vbi_log_mask mask, vbi_log_fn * log_fn, void * user_data); extern void vbi_dvb_demux_delete (vbi_dvb_demux * dx); extern vbi_dvb_demux * vbi_dvb_pes_demux_new (vbi_dvb_demux_cb * callback, void * user_data); /* dvb_mux.h */ extern vbi_bool vbi_dvb_multiplex_sliced (uint8_t ** packet, unsigned int * packet_left, const vbi_sliced ** sliced, unsigned int * sliced_left, vbi_service_set service_mask, unsigned int data_identifier, vbi_bool stuffing) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2, 3, 4)) #endif ; extern vbi_bool vbi_dvb_multiplex_raw (uint8_t ** packet, unsigned int * packet_left, const uint8_t ** raw, unsigned int * raw_left, unsigned int data_identifier, vbi_videostd_set videostd_set, unsigned int line, unsigned int first_pixel_position, unsigned int n_pixels_total, vbi_bool stuffing) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2, 3, 4)) #endif ; typedef struct _vbi_dvb_mux vbi_dvb_mux; typedef vbi_bool vbi_dvb_mux_cb (vbi_dvb_mux * mx, void * user_data, const uint8_t * packet, unsigned int packet_size); extern void vbi_dvb_mux_reset (vbi_dvb_mux * mx) _vbi_nonnull ((1)); extern vbi_bool vbi_dvb_mux_cor (vbi_dvb_mux * mx, uint8_t ** buffer, unsigned int * buffer_left, const vbi_sliced ** sliced, unsigned int * sliced_lines, vbi_service_set service_mask, const uint8_t * raw, const vbi_sampling_par *sampling_par, int64_t pts) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2, 3, 4, 5)) #endif ; extern vbi_bool vbi_dvb_mux_feed (vbi_dvb_mux * mx, const vbi_sliced * sliced, unsigned int sliced_lines, vbi_service_set service_mask, const uint8_t * raw, const vbi_sampling_par *sampling_par, int64_t pts) _vbi_nonnull ((1)); extern unsigned int vbi_dvb_mux_get_data_identifier (const vbi_dvb_mux * mx) _vbi_nonnull ((1)); extern vbi_bool vbi_dvb_mux_set_data_identifier (vbi_dvb_mux * mx, unsigned int data_identifier) _vbi_nonnull ((1)); extern unsigned int vbi_dvb_mux_get_min_pes_packet_size (vbi_dvb_mux * mx) _vbi_nonnull ((1)); extern unsigned int vbi_dvb_mux_get_max_pes_packet_size (vbi_dvb_mux * mx) _vbi_nonnull ((1)); extern vbi_bool vbi_dvb_mux_set_pes_packet_size (vbi_dvb_mux * mx, unsigned int min_size, unsigned int max_size) _vbi_nonnull ((1)); extern void vbi_dvb_mux_delete (vbi_dvb_mux * mx); extern vbi_dvb_mux * vbi_dvb_pes_mux_new (vbi_dvb_mux_cb * callback, void * user_data) _vbi_alloc; extern vbi_dvb_mux * vbi_dvb_ts_mux_new (unsigned int pid, vbi_dvb_mux_cb * callback, void * user_data) _vbi_alloc; /* idl_demux.h */ typedef struct _vbi_idl_demux vbi_idl_demux; #define VBI_IDL_DATA_LOST (1 << 0) #define VBI_IDL_DEPENDENT (1 << 3) typedef vbi_bool vbi_idl_demux_cb (vbi_idl_demux * dx, const uint8_t * buffer, unsigned int n_bytes, unsigned int flags, void * user_data); extern void vbi_idl_demux_reset (vbi_idl_demux * dx) _vbi_nonnull ((1)); extern vbi_bool vbi_idl_demux_feed (vbi_idl_demux * dx, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_idl_demux_feed_frame (vbi_idl_demux * dx, const vbi_sliced * sliced, unsigned int n_lines) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern void vbi_idl_demux_delete (vbi_idl_demux * dx); extern vbi_idl_demux * vbi_idl_a_demux_new (unsigned int channel, unsigned int address, vbi_idl_demux_cb * callback, void * user_data) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_alloc _vbi_nonnull ((3)) #endif ; /* pfc_demux.h */ typedef struct { vbi_pgno pgno; unsigned int stream; unsigned int application_id; unsigned int block_size; uint8_t block[2048]; } vbi_pfc_block; typedef struct _vbi_pfc_demux vbi_pfc_demux; typedef vbi_bool vbi_pfc_demux_cb (vbi_pfc_demux * dx, void * user_data, const vbi_pfc_block * block); extern void vbi_pfc_demux_reset (vbi_pfc_demux * dx) _vbi_nonnull ((1)); extern vbi_bool vbi_pfc_demux_feed (vbi_pfc_demux * dx, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_pfc_demux_feed_frame (vbi_pfc_demux * dx, const vbi_sliced * sliced, unsigned int n_lines) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern void vbi_pfc_demux_delete (vbi_pfc_demux * dx); extern vbi_pfc_demux * vbi_pfc_demux_new (vbi_pgno pgno, unsigned int stream, vbi_pfc_demux_cb * callback, void * user_data) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_alloc _vbi_nonnull ((3)) #endif ; /* xds_demux.h */ typedef enum { VBI_XDS_CLASS_CURRENT = 0x00, VBI_XDS_CLASS_FUTURE, VBI_XDS_CLASS_CHANNEL, VBI_XDS_CLASS_MISC, VBI_XDS_CLASS_PUBLIC_SERVICE, VBI_XDS_CLASS_RESERVED, VBI_XDS_CLASS_UNDEFINED } vbi_xds_class; #define VBI_XDS_MAX_CLASSES (VBI_XDS_CLASS_UNDEFINED + 1) typedef enum { VBI_XDS_PROGRAM_ID = 0x01, VBI_XDS_PROGRAM_LENGTH, VBI_XDS_PROGRAM_NAME, VBI_XDS_PROGRAM_TYPE, VBI_XDS_PROGRAM_RATING, VBI_XDS_PROGRAM_AUDIO_SERVICES, VBI_XDS_PROGRAM_CAPTION_SERVICES, VBI_XDS_PROGRAM_CGMS, VBI_XDS_PROGRAM_ASPECT_RATIO, VBI_XDS_PROGRAM_DATA = 0x0C, VBI_XDS_PROGRAM_MISC_DATA, VBI_XDS_PROGRAM_DESCRIPTION_BEGIN = 0x10, VBI_XDS_PROGRAM_DESCRIPTION_END = 0x18 } vbi_xds_subclass_program; typedef enum { VBI_XDS_CHANNEL_NAME = 0x01, VBI_XDS_CHANNEL_CALL_LETTERS, VBI_XDS_CHANNEL_TAPE_DELAY, VBI_XDS_CHANNEL_TSID } vbi_xds_subclass_channel; typedef enum { VBI_XDS_TIME_OF_DAY = 0x01, VBI_XDS_IMPULSE_CAPTURE_ID, VBI_XDS_SUPPLEMENTAL_DATA_LOCATION, VBI_XDS_LOCAL_TIME_ZONE, VBI_XDS_OUT_OF_BAND_CHANNEL = 0x40, VBI_XDS_CHANNEL_MAP_POINTER, VBI_XDS_CHANNEL_MAP_HEADER, VBI_XDS_CHANNEL_MAP } vbi_xds_subclass_misc; #ifndef DOXYGEN_SHOULD_SKIP_THIS /* Compatibility. */ #define VBI_XDS_MISC_TIME_OF_DAY VBI_XDS_TIME_OF_DAY #define VBI_XDS_MISC_IMPULSE_CAPTURE_ID VBI_XDS_IMPULSE_CAPTURE_ID #define VBI_XDS_MISC_SUPPLEMENTAL_DATA_LOCATION \ VBI_XDS_SUPPLEMENTAL_DATA_LOCATION #define VBI_XDS_MISC_LOCAL_TIME_ZONE VBI_XDS_LOCAL_TIME_ZONE #endif /* DOXYGEN_SHOULD_SKIP_THIS */ typedef enum { VBI_XDS_WEATHER_BULLETIN = 0x01, VBI_XDS_WEATHER_MESSAGE } vbi_xds_subclass_public_service; #define VBI_XDS_MAX_SUBCLASSES (0x18) typedef unsigned int vbi_xds_subclass; typedef struct { vbi_xds_class xds_class; vbi_xds_subclass xds_subclass; unsigned int buffer_size; uint8_t buffer[36]; } vbi_xds_packet; extern void _vbi_xds_packet_dump (const vbi_xds_packet * xp, FILE * fp); typedef struct _vbi_xds_demux vbi_xds_demux; typedef vbi_bool vbi_xds_demux_cb (vbi_xds_demux * xd, const vbi_xds_packet * xp, void * user_data); extern void vbi_xds_demux_reset (vbi_xds_demux * xd); extern vbi_bool vbi_xds_demux_feed (vbi_xds_demux * xd, const uint8_t buffer[2]); extern vbi_bool vbi_xds_demux_feed_frame (vbi_xds_demux * xd, const vbi_sliced * sliced, unsigned int n_lines); extern void vbi_xds_demux_delete (vbi_xds_demux * xd); extern vbi_xds_demux * vbi_xds_demux_new (vbi_xds_demux_cb * callback, void * user_data) _vbi_alloc; /* io.h */ #include /* struct timeval */ typedef struct vbi_capture_buffer { void * data; int size; double timestamp; } vbi_capture_buffer; typedef struct vbi_capture vbi_capture; typedef enum { VBI_FD_HAS_SELECT = 1<<0, VBI_FD_HAS_MMAP = 1<<1, VBI_FD_IS_DEVICE = 1<<2 } VBI_CAPTURE_FD_FLAGS; extern vbi_capture * vbi_capture_v4l2_new(const char *dev_name, int buffers, unsigned int *services, int strict, char **errorstr, vbi_bool trace); extern vbi_capture * vbi_capture_v4l2k_new(const char * dev_name, int fd, int buffers, unsigned int * services, int strict, char ** errorstr, vbi_bool trace); extern vbi_capture * vbi_capture_v4l_new(const char *dev_name, int scanning, unsigned int *services, int strict, char **errorstr, vbi_bool trace); extern vbi_capture * vbi_capture_v4l_sidecar_new(const char *dev_name, int given_fd, unsigned int *services, int strict, char **errorstr, vbi_bool trace); extern vbi_capture * vbi_capture_bktr_new (const char * dev_name, int scanning, unsigned int * services, int strict, char ** errstr, vbi_bool trace); extern int vbi_capture_dvb_filter(vbi_capture *cap, int pid); /* This function is deprecated. Use vbi_capture_dvb_new2() instead. See io-dvb.c or the Doxygen documentation for details. */ extern vbi_capture * vbi_capture_dvb_new (char * dev, int scanning, unsigned int * services, int strict, char ** errstr, vbi_bool trace) _vbi_deprecated; extern int64_t vbi_capture_dvb_last_pts (const vbi_capture * cap); extern vbi_capture * vbi_capture_dvb_new2 (const char * device_name, unsigned int pid, char ** errstr, vbi_bool trace); struct vbi_proxy_client; extern vbi_capture * vbi_capture_proxy_new( struct vbi_proxy_client * vpc, int buffers, int scanning, unsigned int *p_services, int strict, char **pp_errorstr ); extern int vbi_capture_read_raw(vbi_capture *capture, void *data, double *timestamp, struct timeval *timeout); extern int vbi_capture_read_sliced(vbi_capture *capture, vbi_sliced *data, int *lines, double *timestamp, struct timeval *timeout); extern int vbi_capture_read(vbi_capture *capture, void *raw_data, vbi_sliced *sliced_data, int *lines, double *timestamp, struct timeval *timeout); extern int vbi_capture_pull_raw(vbi_capture *capture, vbi_capture_buffer **buffer, struct timeval *timeout); extern int vbi_capture_pull_sliced(vbi_capture *capture, vbi_capture_buffer **buffer, struct timeval *timeout); extern int vbi_capture_pull(vbi_capture *capture, vbi_capture_buffer **raw_buffer, vbi_capture_buffer **sliced_buffer, struct timeval *timeout); extern vbi_raw_decoder *vbi_capture_parameters(vbi_capture *capture); extern int vbi_capture_fd(vbi_capture *capture); extern unsigned int vbi_capture_update_services(vbi_capture *capture, vbi_bool reset, vbi_bool commit, unsigned int services, int strict, char ** errorstr); extern int vbi_capture_get_scanning(vbi_capture *capture); extern void vbi_capture_flush(vbi_capture *capture); extern void vbi_capture_delete(vbi_capture *capture); extern vbi_bool vbi_capture_set_video_path(vbi_capture *capture, const char * p_dev_video); extern VBI_CAPTURE_FD_FLAGS vbi_capture_get_fd_flags(vbi_capture *capture); /* io-sim.h */ extern vbi_bool vbi_raw_video_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int black_level, int white_level, unsigned int pixel_mask, vbi_bool swap_fields, const vbi_sliced * sliced, unsigned int n_sliced_lines); extern vbi_bool vbi_raw_add_noise (uint8_t * raw, const vbi_sampling_par *sp, unsigned int min_freq, unsigned int max_freq, unsigned int amplitude, unsigned int seed); extern vbi_bool vbi_raw_vbi_image (uint8_t * raw, unsigned long raw_size, const vbi_sampling_par *sp, int blank_level, int white_level, vbi_bool swap_fields, const vbi_sliced * sliced, unsigned int n_sliced_lines); extern void vbi_capture_sim_add_noise (vbi_capture * cap, unsigned int min_freq, unsigned int max_freq, unsigned int amplitude); extern vbi_bool vbi_capture_sim_load_caption (vbi_capture * cap, const char * stream, vbi_bool append); #if 3 == VBI_VERSION_MINOR extern vbi_bool vbi_capture_sim_load_vps (vbi_capture * cap, const vbi_program_id *pid); extern vbi_bool vbi_capture_sim_load_wss_625 (vbi_capture * cap, const vbi_aspect_ratio *ar); #endif extern void vbi_capture_sim_decode_raw (vbi_capture * cap, vbi_bool enable); extern vbi_capture * vbi_capture_sim_new (int scanning, unsigned int * services, vbi_bool interlaced, vbi_bool synchronous); /* proxy-msg.h */ typedef enum { VBI_CHN_PRIO_BACKGROUND = 1, VBI_CHN_PRIO_INTERACTIVE = 2, VBI_CHN_PRIO_DEFAULT = VBI_CHN_PRIO_INTERACTIVE, VBI_CHN_PRIO_RECORD = 3 } VBI_CHN_PRIO; typedef enum { VBI_CHN_SUBPRIO_MINIMAL = 0x00, VBI_CHN_SUBPRIO_CHECK = 0x10, VBI_CHN_SUBPRIO_UPDATE = 0x20, VBI_CHN_SUBPRIO_INITIAL = 0x30, VBI_CHN_SUBPRIO_VPS_PDC = 0x40 } VBI_CHN_SUBPRIO; typedef struct { uint8_t is_valid; uint8_t sub_prio; uint8_t allow_suspend; uint8_t reserved0; time_t min_duration; time_t exp_duration; uint8_t reserved1[16]; } vbi_channel_profile; typedef enum { VBI_PROXY_DAEMON_NO_TIMEOUTS = 1<<0 } VBI_PROXY_DAEMON_FLAGS; typedef enum { VBI_PROXY_CLIENT_NO_TIMEOUTS = 1<<0, VBI_PROXY_CLIENT_NO_STATUS_IND = 1<<1 } VBI_PROXY_CLIENT_FLAGS; typedef enum { VBI_PROXY_CHN_RELEASE = 1<<0, VBI_PROXY_CHN_TOKEN = 1<<1, VBI_PROXY_CHN_FLUSH = 1<<2, VBI_PROXY_CHN_NORM = 1<<3, VBI_PROXY_CHN_FAIL = 1<<4, VBI_PROXY_CHN_NONE = 0 } VBI_PROXY_CHN_FLAGS; typedef enum { VBI_API_UNKNOWN, VBI_API_V4L1, VBI_API_V4L2, VBI_API_BKTR } VBI_DRIVER_API_REV; #define VBIPROXY_VERSION 0x00000100 #define VBIPROXY_COMPAT_VERSION 0x00000100 /* proxy-client.h */ #include /* struct timeval */ typedef struct vbi_proxy_client vbi_proxy_client; typedef enum { VBI_PROXY_EV_CHN_GRANTED = 1<<0, VBI_PROXY_EV_CHN_CHANGED = 1<<1, VBI_PROXY_EV_NORM_CHANGED = 1<<2, VBI_PROXY_EV_CHN_RECLAIMED = 1<<3, VBI_PROXY_EV_NONE = 0 } VBI_PROXY_EV_TYPE; typedef void VBI_PROXY_CLIENT_CALLBACK ( void * p_client_data, VBI_PROXY_EV_TYPE ev_mask ); /* forward declaration from io.h */ struct vbi_capture_buffer; extern vbi_proxy_client * vbi_proxy_client_create( const char *dev_name, const char *p_client_name, VBI_PROXY_CLIENT_FLAGS client_flags, char **pp_errorstr, int trace_level ); extern void vbi_proxy_client_destroy( vbi_proxy_client * vpc ); extern vbi_capture * vbi_proxy_client_get_capture_if( vbi_proxy_client * vpc ); extern VBI_PROXY_CLIENT_CALLBACK * vbi_proxy_client_set_callback( vbi_proxy_client * vpc, VBI_PROXY_CLIENT_CALLBACK * p_callback, void * p_data ); extern VBI_DRIVER_API_REV vbi_proxy_client_get_driver_api( vbi_proxy_client * vpc ); extern int vbi_proxy_client_channel_request( vbi_proxy_client * vpc, VBI_CHN_PRIO chn_prio, vbi_channel_profile * chn_profile ); extern int vbi_proxy_client_channel_notify( vbi_proxy_client * vpc, VBI_PROXY_CHN_FLAGS notify_flags, unsigned int scanning ); typedef enum { VBI_PROXY_SUSPEND_START, VBI_PROXY_SUSPEND_STOP } VBI_PROXY_SUSPEND; extern int vbi_proxy_client_channel_suspend( vbi_proxy_client * vpc, VBI_PROXY_SUSPEND cmd ); int vbi_proxy_client_device_ioctl( vbi_proxy_client * vpc, int request, void * p_arg ); extern int vbi_proxy_client_get_channel_desc( vbi_proxy_client * vpc, unsigned int * p_scanning, vbi_bool * p_granted ); extern vbi_bool vbi_proxy_client_has_channel_control( vbi_proxy_client * vpc ); /* exp-gfx.h */ extern void vbi_draw_vt_page_region(vbi_page *pg, vbi_pixfmt fmt, void *canvas, int rowstride, int column, int row, int width, int height, int reveal, int flash_on); _vbi_inline void vbi_draw_vt_page(vbi_page *pg, vbi_pixfmt fmt, void *canvas, int reveal, int flash_on) { vbi_draw_vt_page_region(pg, fmt, canvas, -1, 0, 0, pg->columns, pg->rows, reveal, flash_on); } extern void vbi_draw_cc_page_region(vbi_page *pg, vbi_pixfmt fmt, void *canvas, int rowstride, int column, int row, int width, int height); _vbi_inline void vbi_draw_cc_page(vbi_page *pg, vbi_pixfmt fmt, void *canvas) { vbi_draw_cc_page_region(pg, fmt, canvas, -1, 0, 0, pg->columns, pg->rows); } extern void vbi_get_max_rendered_size(int *w, int *h); extern void vbi_get_vt_cell_size(int *w, int *h); /* exp-txt.h */ extern int vbi_print_page_region(vbi_page *pg, char *buf, int size, const char *format, vbi_bool table, vbi_bool ltr, int column, int row, int width, int height); _vbi_inline int vbi_print_page(vbi_page *pg, char *buf, int size, const char *format, vbi_bool table, vbi_bool ltr) { return vbi_print_page_region(pg, buf, size, format, table, ltr, 0, 0, pg->columns, pg->rows); } /* hamm.h */ extern const uint8_t _vbi_bit_reverse [256]; extern const uint8_t _vbi_hamm8_fwd [16]; extern const int8_t _vbi_hamm8_inv [256]; extern const int8_t _vbi_hamm24_inv_par [3][256]; _vbi_inline unsigned int vbi_rev8 (unsigned int c) { return _vbi_bit_reverse[(uint8_t) c]; } _vbi_inline unsigned int vbi_rev16 (unsigned int c) { return _vbi_bit_reverse[(uint8_t) c] * 256 + _vbi_bit_reverse[(uint8_t)(c >> 8)]; } _vbi_inline unsigned int vbi_rev16p (const uint8_t * p) { return _vbi_bit_reverse[p[0]] * 256 + _vbi_bit_reverse[p[1]]; } _vbi_inline unsigned int vbi_par8 (unsigned int c) { c &= 255; /* if 0 == (inv_par[] & 32) change bit 7 of c. */ c ^= 128 & ~(_vbi_hamm24_inv_par[0][c] << 2); return c; } _vbi_inline int vbi_unpar8 (unsigned int c) { /* Disabled until someone finds a reliable way to test for cmov support at compile time. */ #if 0 int r = c & 127; /* This saves cache flushes and an explicit branch. */ __asm__ (" testb %1,%1\n" " cmovp %2,%0\n" : "+&a" (r) : "c" (c), "rm" (-1)); return r; #endif if (_vbi_hamm24_inv_par[0][(uint8_t) c] & 32) { return c & 127; } else { /* The idea is to OR results together to find a parity error in a sequence, rather than a test and branch on each byte. */ return -1; } } extern void vbi_par (uint8_t * p, unsigned int n); extern int vbi_unpar (uint8_t * p, unsigned int n); _vbi_inline unsigned int vbi_ham8 (unsigned int c) { return _vbi_hamm8_fwd[c & 15]; } _vbi_inline int vbi_unham8 (unsigned int c) { return _vbi_hamm8_inv[(uint8_t) c]; } _vbi_inline int vbi_unham16p (const uint8_t * p) { return ((int) _vbi_hamm8_inv[p[0]]) | (((int) _vbi_hamm8_inv[p[1]]) << 4); } extern void vbi_ham24p (uint8_t * p, unsigned int c); extern int vbi_unham24p (const uint8_t * p) _vbi_pure; /* cc.h */ extern vbi_bool vbi_fetch_cc_page(vbi_decoder *vbi, vbi_page *pg, vbi_pgno pgno, vbi_bool reset); /* teletext_decoder.h */ typedef enum { VBI_WST_LEVEL_1, VBI_WST_LEVEL_1p5, VBI_WST_LEVEL_2p5, VBI_WST_LEVEL_3p5 } vbi_wst_level; extern void vbi_teletext_set_default_region(vbi_decoder *vbi, int default_region); extern void vbi_teletext_set_level(vbi_decoder *vbi, int level); extern vbi_bool vbi_fetch_vt_page(vbi_decoder *vbi, vbi_page *pg, vbi_pgno pgno, vbi_subno subno, vbi_wst_level max_level, int display_rows, vbi_bool navigation); extern int vbi_page_title(vbi_decoder *vbi, int pgno, int subno, char *buf); extern void vbi_resolve_link(vbi_page *pg, int column, int row, vbi_link *ld); extern void vbi_resolve_home(vbi_page *pg, vbi_link *ld); /* tables.h */ extern const char * vbi_rating_string(vbi_rating_auth auth, int id); extern const char * vbi_prog_type_string(vbi_prog_classf classf, int id); /* packet-830.h */ extern vbi_bool vbi_decode_teletext_8301_cni (unsigned int * cni, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_decode_teletext_8301_local_time (time_t * utc_time, int * seconds_east, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2, 3)) #endif ; extern vbi_bool vbi_decode_teletext_8302_cni (unsigned int * cni, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_decode_teletext_8302_pdc (vbi_program_id * pid, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; /* vps.h */ extern vbi_bool vbi_decode_vps_cni (unsigned int * cni, const uint8_t buffer[13]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_encode_vps_cni (uint8_t buffer[13], unsigned int cni) _vbi_nonnull ((1)); extern vbi_bool vbi_decode_vps_pdc (vbi_program_id * pid, const uint8_t buffer[13]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_encode_vps_pdc (uint8_t buffer[13], const vbi_program_id * pid) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; vbi_bool vbi_decode_dvb_pdc_descriptor (vbi_program_id * pid, const uint8_t buffer[5]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; vbi_bool vbi_encode_dvb_pdc_descriptor (uint8_t buffer[5], const vbi_program_id * pid) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; /* vbi.h */ typedef enum { VBI_NO_PAGE = 0x00, VBI_NORMAL_PAGE = 0x01, VBI_SUBTITLE_PAGE = 0x70, VBI_SUBTITLE_INDEX = 0x78, VBI_NONSTD_SUBPAGES = 0x79, VBI_PROGR_WARNING = 0x7A, VBI_CURRENT_PROGR = 0x7C, VBI_NOW_AND_NEXT = 0x7D, VBI_PROGR_INDEX = 0x7F, VBI_PROGR_SCHEDULE = 0x81, VBI_UNKNOWN_PAGE = 0xFF } vbi_page_type; extern void vbi_set_brightness(vbi_decoder *vbi, int brightness); extern void vbi_set_contrast(vbi_decoder *vbi, int contrast); extern vbi_decoder * vbi_decoder_new(void); extern void vbi_decoder_delete(vbi_decoder *vbi); extern void vbi_decode(vbi_decoder *vbi, vbi_sliced *sliced, int lines, double timestamp); extern void vbi_channel_switched(vbi_decoder *vbi, vbi_nuid nuid); extern vbi_page_type vbi_classify_page(vbi_decoder *vbi, vbi_pgno pgno, vbi_subno *subno, char **language); extern void vbi_version(unsigned int *major, unsigned int *minor, unsigned int *micro); extern void vbi_set_log_fn (vbi_log_mask mask, vbi_log_fn * log_fn, void * user_data); #ifdef __cplusplus } #endif #endif /* __LIBZVBI_H__ */ zvbi-0.2.44/src/macros.h000066400000000000000000000072011476363111200150030ustar00rootroot00000000000000/* * libzvbi -- Useful macros * * Copyright (C) 2002, 2003, 2004, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: macros.h,v 1.12 2013-07-10 23:11:18 mschimek Exp $ */ #ifndef __ZVBI_MACROS_H__ #define __ZVBI_MACROS_H__ #ifdef __cplusplus # define VBI_BEGIN_DECLS extern "C" { # define VBI_END_DECLS } #else # define VBI_BEGIN_DECLS # define VBI_END_DECLS #endif VBI_BEGIN_DECLS /* Public */ #if __GNUC__ >= 4 # define _vbi_sentinel __attribute__ ((__sentinel__(0))) # define _vbi_deprecated __attribute__ ((__deprecated__)) #else # define _vbi_sentinel # define _vbi_deprecated # define __restrict__ #endif #if (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) || __GNUC__ >= 4 # define _vbi_nonnull(params) __attribute__ ((__nonnull__ params)) # define _vbi_format(params) __attribute__ ((__format__ params)) #else # define _vbi_nonnull(params) # define _vbi_format(params) #endif #if __GNUC__ >= 3 # define _vbi_pure __attribute__ ((__pure__)) # define _vbi_alloc __attribute__ ((__malloc__)) #else # define _vbi_pure # define _vbi_alloc #endif #if __GNUC__ >= 2 # define _vbi_unused __attribute__ ((__unused__)) # define _vbi_const __attribute__ ((__const__)) # define _vbi_inline static __inline__ #else # define _vbi_unused # define _vbi_const # define _vbi_inline static #endif /** * @ingroup Basic * @name Boolean type * @{ */ #ifndef TRUE # define TRUE 1 #endif #ifndef FALSE # define FALSE 0 #endif typedef int vbi_bool; /** @} */ #ifndef NULL # ifdef __cplusplus # define NULL (0L) # else # define NULL ((void *) 0) # endif #endif /* XXX Document me - for variadic funcs. */ #define VBI_END ((void *) 0) #if 0 typedef void vbi_lock_fn (void * user_data); typedef void vbi_unlock_fn (void * user_data); #endif /** * @ingroup Basic * @{ */ typedef enum { /** External error causes, for example lack of memory. */ VBI_LOG_ERROR = 1 << 3, /** * Invalid parameters and similar problems which suggest * a bug in the application using the library. */ VBI_LOG_WARNING = 1 << 4, /** * Causes of possibly undesired results, for example when a * data service cannot be decoded with the current video * standard setting. */ VBI_LOG_NOTICE = 1 << 5, /** Progress messages. */ VBI_LOG_INFO = 1 << 6, /** Information useful to debug the library. */ VBI_LOG_DEBUG = 1 << 7, /** Driver responses (strace). Not implemented yet. */ VBI_LOG_DRIVER = 1 << 8, /** More detailed debugging information. */ VBI_LOG_DEBUG2 = 1 << 9, VBI_LOG_DEBUG3 = 1 << 10 } vbi_log_mask; typedef void vbi_log_fn (vbi_log_mask level, const char * context, const char * message, void * user_data); extern vbi_log_fn vbi_log_on_stderr; /** @} */ /* Private */ typedef struct { vbi_log_fn * fn; void * user_data; vbi_log_mask mask; } _vbi_log_hook; VBI_END_DECLS #endif /* __ZVBI_MACROS_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/misc.c000066400000000000000000000210031476363111200144410ustar00rootroot00000000000000/* * libzvbi -- Miscellaneous cows and chickens * * Copyright (C) 2000-2003 Iaki Garca Etxebarria * Copyright (C) 2001-2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: misc.c,v 1.13 2008-02-19 00:35:20 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "misc.h" #ifdef ZAPPING8 const char vbi_intl_domainname[] = PACKAGE; #else # include "version.h" # if 2 == VBI_VERSION_MINOR const char _zvbi_intl_domainname[] = PACKAGE; # else const char vbi_intl_domainname[] = PACKAGE; # endif #endif _vbi_log_hook _vbi_global_log; /** * @internal * Number of set bits. */ unsigned int _vbi_popcnt (uint32_t x) { x -= ((x >> 1) & 0x55555555); x = (x & 0x33333333) + ((x >> 2) & 0x33333333); x = (x + (x >> 4)) & 0x0F0F0F0F; return ((uint32_t)(x * 0x01010101)) >> 24; } /** * @internal * @param dst The string will be stored in this buffer. * @param src NUL-terminated string to be copied. * @param size Maximum number of bytes to be copied, including the * terminating NUL (i.e. this is the size of the @a dst buffer). * * Copies @a src to @a dst, but no more than @a size - 1 characters. * Always NUL-terminates @a dst, unless @a size is zero. * * strlcpy() is a BSD extension. Don't call this function * directly, we #define strlcpy if necessary. * * @returns * strlen (src). */ size_t _vbi_strlcpy (char * dst, const char * src, size_t size) { const char *src1; assert (NULL != dst); assert (NULL != src); src1 = src; if (likely (size > 1)) { char *end = dst + size - 1; do { if (unlikely (0 == (*dst++ = *src++))) goto finish; } while (dst < end); *dst = 0; } else if (size > 0) { *dst = 0; } while (*src++) ; finish: return src - src1 - 1; } /** * @internal * strndup() is a BSD/GNU extension. Don't call this function * directly, we #define strndup if necessary. */ char * _vbi_strndup (const char * s, size_t len) { size_t n; char *r; if (NULL == s) return NULL; n = strlen (s); len = MIN (len, n); r = vbi_malloc (len + 1); if (r) { memcpy (r, s, len); r[len] = 0; } return r; } /** * @internal * vasprintf() is a BSD/GNU extension. Don't call this function * directly, we #define vasprintf if necessary. */ int _vbi_vasprintf (char ** dstp, const char * templ, va_list ap) { char *buf; unsigned long size; va_list ap2; int temp; assert (NULL != dstp); assert (NULL != templ); temp = errno; buf = NULL; size = 64; va_copy (ap2, ap); for (;;) { char *buf2; long len; if (!(buf2 = vbi_realloc (buf, size))) break; buf = buf2; len = vsnprintf (buf, size, templ, ap); if (len < 0) { /* Not enough. */ size *= 2; } else if ((unsigned long) len < size) { *dstp = buf; errno = temp; return len; } else { /* Size needed. */ size = len + 1; } /* vsnprintf() may advance ap. */ va_copy (ap, ap2); } vbi_free (buf); buf = NULL; /* According to "man 3 asprintf" GNU's version leaves *dstp undefined on error, so don't count on it. FreeBSD's asprintf NULLs *dstp, which is safer. */ *dstp = NULL; errno = temp; return -1; } /** * @internal * asprintf() is a GNU extension. Don't call this function * directly, we #define asprintf if necessary. */ int _vbi_asprintf (char ** dstp, const char * templ, ...) { va_list ap; int len; va_start (ap, templ); /* May fail, returning -1. */ len = vasprintf (dstp, templ, ap); va_end (ap); return len; } /** @internal */ vbi_bool _vbi_keyword_lookup (int * value, const char ** inout_s, const _vbi_key_value_pair *table, unsigned int n_pairs) { const char *s; unsigned int i; assert (NULL != value); assert (NULL != inout_s); assert (NULL != *inout_s); assert (NULL != table); s = *inout_s; while (isspace (*s)) ++s; if (isdigit (*s)) { long val; char *end; val = strtol (s, &end, 10); for (i = 0; NULL != table[i].key; ++i) { if (val == table[i].value) { *value = val; *inout_s = end; return TRUE; } } } else { for (i = 0; i < n_pairs; ++i) { size_t len = strlen (table[i].key); if (0 == strncasecmp (s, table[i].key, len) && !isalnum (s[len])) { *value = table[i].value; *inout_s = s + len; return TRUE; } } } return FALSE; } void _vbi_shrink_vector_capacity (void ** vector, size_t * capacity, size_t min_capacity, size_t element_size) { void *new_vec; size_t new_capacity; if (min_capacity >= *capacity) return; new_capacity = min_capacity; new_vec = vbi_realloc (*vector, new_capacity * element_size); if (unlikely (NULL == new_vec)) return; *vector = new_vec; *capacity = new_capacity; } vbi_bool _vbi_grow_vector_capacity (void ** vector, size_t * capacity, size_t min_capacity, size_t element_size) { void *new_vec; size_t old_capacity; size_t new_capacity; size_t max_capacity; assert (min_capacity > 0); assert (element_size > 0); max_capacity = SIZE_MAX / element_size; if (unlikely (min_capacity > max_capacity)) { goto failed; } old_capacity = *capacity; if (unlikely (old_capacity > max_capacity - (1 << 16))) { new_capacity = max_capacity; } else if (old_capacity >= (1 << 16)) { new_capacity = MAX (min_capacity, old_capacity + (1 << 16)); } else { new_capacity = MAX (min_capacity, old_capacity * 2); } new_vec = vbi_realloc (*vector, new_capacity * element_size); if (unlikely (NULL == new_vec)) { if (new_capacity <= min_capacity) goto failed; new_capacity = min_capacity; new_vec = vbi_realloc (*vector, new_capacity * element_size); if (unlikely (NULL == new_vec)) goto failed; } *vector = new_vec; *capacity = new_capacity; return TRUE; failed: errno = ENOMEM; return FALSE; } /** * @ingroup Basic * * Log function printing messages on standard output. * * @since 0.2.22 */ void vbi_log_on_stderr (vbi_log_mask level, const char * context, const char * message, void * user_data) { vbi_log_mask max_level; /* This function exists in libzvbi 0.2 with vbi_ prefix and in libzvbi 0.3 and Zapping with vbi_ prefix (so I can use both versions in Zapping until 0.3 is finished). */ if (0 == strncmp (context, "vbi_", 4)) { context += 4; /* Not "vbi_" to prevent an accidental s/vbi_/vbi_. */ } else if (0 == strncmp (context, "vbi" "3_", 5)) { context += 5; } if (NULL != user_data) { max_level = * (vbi_log_mask *) user_data; if (level > max_level) return; } fprintf (stderr, "libzvbi:%s: %s\n", context, message); } /** @internal */ void _vbi_log_vprintf (vbi_log_fn log_fn, void * user_data, vbi_log_mask mask, const char * source_file, const char * context, const char * templ, va_list ap) { char ctx_buffer[160]; char *msg_buffer; int saved_errno; unsigned int i; int r; assert (NULL != source_file); assert (NULL != context); assert (NULL != templ); if (NULL == log_fn) return; saved_errno = errno; for (i = 0; i < N_ELEMENTS (ctx_buffer) - 2; ++i) { int c = source_file[i]; if ('.' == c) break; ctx_buffer[i] = c; } ctx_buffer[i++] = ':'; strlcpy (ctx_buffer + i, context, N_ELEMENTS (ctx_buffer) - i); r = vasprintf (&msg_buffer, templ, ap); if (r > 1 && NULL != msg_buffer) { log_fn (mask, ctx_buffer, msg_buffer, user_data); vbi_free (msg_buffer); msg_buffer = NULL; } errno = saved_errno; } /** @internal */ void _vbi_log_printf (vbi_log_fn log_fn, void * user_data, vbi_log_mask mask, const char * source_file, const char * context, const char * templ, ...) { va_list ap; va_start (ap, templ); _vbi_log_vprintf (log_fn, user_data, mask, source_file, context, templ, ap); va_end (ap); } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/misc.h000066400000000000000000000277171476363111200144700ustar00rootroot00000000000000/* * libzvbi -- Miscellaneous cows and chickens * * Copyright (C) 2000-2003 Iñaki García Etxebarria * Copyright (C) 2002-2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: misc.h,v 1.24 2013-07-02 02:32:31 mschimek Exp $ */ #ifndef MISC_H #define MISC_H #include #include #include #include #include #include /* (u)intXX_t */ #include /* (s)size_t */ #include /* DBL_MAX */ #include /* (S)SIZE_MAX */ #include #include "macros.h" #include "version.h" #define N_ELEMENTS(array) (sizeof (array) / sizeof (*(array))) #ifdef __GNUC__ #if __GNUC__ < 3 /* Expect expression usually true/false, schedule accordingly. */ # define likely(expr) (expr) # define unlikely(expr) (expr) #else # define likely(expr) __builtin_expect(expr, 1) # define unlikely(expr) __builtin_expect(expr, 0) #endif #undef __i386__ #undef __i686__ /* FIXME #cpu is deprecated #if #cpu (i386) # define __i386__ 1 #endif #if #cpu (i686) # define __i686__ 1 #endif */ /* &x == PARENT (&x.tm_min, struct tm, tm_min), safer than &x == (struct tm *) &x.tm_min. A NULL _ptr is safe and will return NULL, not -offsetof(_member). */ #undef PARENT #define PARENT(_ptr, _type, _member) ({ \ __typeof__ (&((_type *) 0)->_member) _p = (_ptr); \ (_p != 0) ? (_type *)(((char *) _p) - offsetof (_type, \ _member)) : (_type *) 0; \ }) /* Like PARENT(), to be used with const _ptr. */ #define CONST_PARENT(_ptr, _type, _member) ({ \ __typeof__ (&((const _type *) 0)->_member) _p = (_ptr); \ (_p != 0) ? (const _type *)(((const char *) _p) - offsetof \ (const _type, _member)) : (const _type *) 0; \ }) /* Note the following macros have no side effects only when you compile with GCC, so don't expect this. */ /* Absolute value of int, long or long long without a branch. Note ABS (INT_MIN) -> INT_MAX + 1. */ #undef ABS #define ABS(n) ({ \ register __typeof__ (n) _n = (n), _t = _n; \ if (-1 == (-1 >> 1)) { /* do we have signed shifts? */ \ _t >>= sizeof (_t) * 8 - 1; \ _n ^= _t; \ _n -= _t; \ } else if (_n < 0) { /* also warns if n is unsigned type */ \ _n = -_n; \ } \ /* return */ _n; \ }) #undef MIN #define MIN(x, y) ({ \ __typeof__ (x) _x = (x); \ __typeof__ (y) _y = (y); \ (void)(&_x == &_y); /* warn if types do not match */ \ /* return */ (_x < _y) ? _x : _y; \ }) #undef MAX #define MAX(x, y) ({ \ __typeof__ (x) _x = (x); \ __typeof__ (y) _y = (y); \ (void)(&_x == &_y); /* warn if types do not match */ \ /* return */ (_x > _y) ? _x : _y; \ }) /* Note other compilers may swap only int, long or pointer. */ #undef SWAP #define SWAP(x, y) \ do { \ __typeof__ (x) _x = x; \ x = y; \ y = _x; \ } while (0) #undef SATURATE #ifdef __i686__ /* has conditional move */ #define SATURATE(n, min, max) ({ \ __typeof__ (n) _n = (n); \ __typeof__ (n) _min = (min); \ __typeof__ (n) _max = (max); \ (void)(&_n == &_min); /* warn if types do not match */ \ (void)(&_n == &_max); \ if (_n < _min) \ _n = _min; \ if (_n > _max) \ _n = _max; \ /* return */ _n; \ }) #else #define SATURATE(n, min, max) ({ \ __typeof__ (n) _n = (n); \ __typeof__ (n) _min = (min); \ __typeof__ (n) _max = (max); \ (void)(&_n == &_min); /* warn if types do not match */ \ (void)(&_n == &_max); \ if (_n < _min) \ _n = _min; \ else if (_n > _max) \ _n = _max; \ /* return */ _n; \ }) #endif #else /* !__GNUC__ */ #define likely(expr) (expr) #define unlikely(expr) (expr) #undef __i386__ #undef __i686__ static char * PARENT_HELPER (char *p, unsigned int offset) { return (0 == p) ? ((char *) 0) : p - offset; } static const char * CONST_PARENT_HELPER (const char *p, unsigned int offset) { return (0 == p) ? ((char *) 0) : p - offset; } #define PARENT(_ptr, _type, _member) \ ((0 == offsetof (_type, _member)) ? (_type *)(_ptr) \ : (_type *) PARENT_HELPER ((char *)(_ptr), offsetof (_type, _member))) #define CONST_PARENT(_ptr, _type, _member) \ ((0 == offsetof (const _type, _member)) ? (const _type *)(_ptr) \ : (const _type *) CONST_PARENT_HELPER ((const char *)(_ptr), \ offsetof (const _type, _member))) #undef ABS #define ABS(n) (((n) < 0) ? -(n) : (n)) #undef MIN #define MIN(x, y) (((x) < (y)) ? (x) : (y)) #undef MAX #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #undef SWAP #define SWAP(x, y) \ do { \ long _x = x; \ x = y; \ y = _x; \ } while (0) #undef SATURATE #define SATURATE(n, min, max) MIN (MAX (min, n), max) #endif /* !__GNUC__ */ /* 32 bit constant byte reverse, e.g. 0xAABBCCDD -> 0xDDCCBBAA. */ #define SWAB32(m) \ (+ (((m) & 0xFF000000) >> 24) \ + (((m) & 0xFF0000) >> 8) \ + (((m) & 0xFF00) << 8) \ + (((m) & 0xFF) << 24)) #ifdef HAVE_BUILTIN_POPCOUNT # define popcnt(x) __builtin_popcount ((uint32_t)(x)) #else # define popcnt(x) _vbi_popcnt (x) #endif extern unsigned int _vbi_popcnt (uint32_t x); /* NB GCC inlines and optimizes these functions when size is const. */ #define SET(var) memset (&(var), ~0, sizeof (var)) #define CLEAR(var) memset (&(var), 0, sizeof (var)) /* Useful to copy arrays, otherwise use assignment. */ #define COPY(d, s) \ (assert (sizeof (d) == sizeof (s)), memcpy (d, s, sizeof (d))) /* Copy string const into char array. */ #define STRACPY(array, s) \ do { \ /* Complain if s is no string const or won't fit. */ \ const char t_[sizeof (array) - 1] _vbi_unused = s; \ \ memcpy (array, s, sizeof (s)); \ } while (0) /* Copy bits through mask. */ #define COPY_SET_MASK(dest, from, mask) \ (dest ^= (from) ^ (dest & (mask))) /* Set bits if cond is TRUE, clear if FALSE. */ #define COPY_SET_COND(dest, bits, cond) \ ((cond) ? (dest |= (bits)) : (dest &= ~(bits))) /* Set and clear bits. */ #define COPY_SET_CLEAR(dest, set, clear) \ (dest = (dest & ~(clear)) | (set)) /* For applications, debugging and fault injection during unit tests. */ #if 2 == VBI_VERSION_MINOR # define vbi_malloc malloc # define vbi_realloc realloc # define vbi_strdup strdup # define vbi_free free #else extern void * (* vbi_malloc) (size_t); extern void * (* vbi_realloc) (void *, size_t) _vbi_nonnull ((1)); extern char * (* vbi_strdup) (const char *) _vbi_nonnull ((1)); extern void (* vbi_free) (void *); #endif #define vbi_cache_malloc vbi_malloc #define vbi_cache_free vbi_free /* Helper functions. */ _vbi_inline int _vbi_to_ascii (int c) { if (c < 0) return '?'; c &= 0x7F; if (c < 0x20 || c >= 0x7F) return '.'; return c; } typedef struct { const char * key; int value; } _vbi_key_value_pair; extern vbi_bool _vbi_keyword_lookup (int * value, const char ** inout_s, const _vbi_key_value_pair * table, unsigned int n_pairs) _vbi_nonnull ((1, 2, 3)); extern void _vbi_shrink_vector_capacity (void ** vector, size_t * capacity, size_t min_capacity, size_t element_size) _vbi_nonnull ((1, 2)); extern vbi_bool _vbi_grow_vector_capacity (void ** vector, size_t * capacity, size_t min_capacity, size_t element_size) _vbi_nonnull ((1, 2)); /* Logging stuff. */ extern _vbi_log_hook _vbi_global_log; extern void _vbi_log_vprintf (vbi_log_fn * log_fn, void * user_data, vbi_log_mask level, const char * source_file, const char * context, const char * templ, va_list ap) _vbi_nonnull ((1, 4, 5, 6)); extern void _vbi_log_printf (vbi_log_fn * log_fn, void * user_data, vbi_log_mask level, const char * source_file, const char * context, const char * templ, ...) _vbi_nonnull ((1, 4, 5, 6)) _vbi_format ((printf, 6, 7)); #define _vbi_log(hook, level, templ, args...) \ do { \ _vbi_log_hook *_h = hook; \ \ if ((NULL != _h && 0 != (_h->mask & level)) \ || (_h = &_vbi_global_log, 0 != (_h->mask & level))) \ _vbi_log_printf (_h->fn, _h->user_data, \ level, __FILE__, __FUNCTION__, \ templ , ##args); \ } while (0) #define _vbi_vlog(hook, level, templ, ap) \ do { \ _vbi_log_hook *_h = hook; \ \ if ((NULL != _h && 0 != (_h->mask & level)) \ || (_h = &_vbi_global_log, 0 != (_h->mask & level))) \ _vbi_log_vprintf (_h->fn, _h->user_data, \ level, __FILE__, __FUNCTION__, \ templ, ap); \ } while (0) #define error(hook, templ, args...) \ _vbi_log (hook, VBI_LOG_ERROR, templ , ##args) #define warning(hook, templ, args...) \ _vbi_log (hook, VBI_LOG_ERROR, templ , ##args) #define notice(hook, templ, args...) \ _vbi_log (hook, VBI_LOG_NOTICE, templ , ##args) #define info(hook, templ, args...) \ _vbi_log (hook, VBI_LOG_INFO, templ , ##args) #define debug1(hook, templ, args...) \ _vbi_log (hook, VBI_LOG_DEBUG, templ , ##args) #define debug2(hook, templ, args...) \ _vbi_log (hook, VBI_LOG_DEBUG2, templ , ##args) #define debug3(hook, templ, args...) \ _vbi_log (hook, VBI_LOG_DEBUG3, templ , ##args) /* Portability stuff. */ /* These should be defined in inttypes.h. */ #ifndef PRId64 # define PRId64 "lld" #endif #ifndef PRIu64 # define PRIu64 "llu" #endif #ifndef PRIx64 # define PRIx64 "llx" #endif /* Should be defined in C99 limits.h? */ #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) #endif #ifndef TIME_MIN # define TIME_MIN (_vbi_time_min ()) _vbi_inline time_t _vbi_time_min (void) { const time_t t = (time_t) -1.25; if (t < -1) { return (time_t)((sizeof (time_t) > 4) ? DBL_MIN : FLT_MIN); } else if (t < 0) { return ((uint64_t) 1) << (sizeof (time_t) * 8 - 1); } else { return 0; } } #endif #ifndef TIME_MAX # define TIME_MAX (_vbi_time_max ()) _vbi_inline time_t _vbi_time_max (void) { const time_t t = (time_t) -1.25; if (t < -1) { return (time_t)((sizeof (time_t) > 4) ? DBL_MAX : FLT_MAX); } else if (t < 0) { /* Most likely signed 32 or 64 bit. */ return (((uint64_t) 1) << (sizeof (time_t) * 8 - 1)) - 1; } else { return -1; } } #endif /* va_copy is C99. */ #ifndef va_copy # define va_copy(ap1, ap2) do { ap1 = ap2; } while (0) #endif /* Use this instead of strncpy(). strlcpy() is a BSD extension. */ #ifndef HAVE_STRLCPY # define strlcpy _vbi_strlcpy #endif #ifndef _WIN32 #undef strncpy #define strncpy use_strlcpy_instead #endif extern size_t _vbi_strlcpy (char * dst, const char * src, size_t size) _vbi_nonnull ((1, 2)); /* strndup() is a BSD/GNU extension. */ #ifndef HAVE_STRNDUP # define strndup _vbi_strndup #endif extern char * _vbi_strndup (const char * s, size_t len) _vbi_nonnull ((1)); /* vasprintf() is a GNU extension. */ #ifndef HAVE_VASPRINTF # define vasprintf _vbi_vasprintf #endif extern int _vbi_vasprintf (char ** dstp, const char * templ, va_list ap) _vbi_nonnull ((1, 2)); /* asprintf() is a GNU extension. */ #ifndef HAVE_ASPRINTF # define asprintf _vbi_asprintf #endif extern int _vbi_asprintf (char ** dstp, const char * templ, ...) _vbi_nonnull ((1, 2)) _vbi_format ((printf, 2, 3)); #undef sprintf #define sprintf use_snprintf_or_asprintf_instead #endif /* MISC_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/network-table.h000066400000000000000000001541461476363111200163100ustar00rootroot00000000000000/* Generated from http://zapping.sf.net/zvbi-0.3/networks.xml */ const struct vbi_cni_entry vbi_cni_table[] = { { 193, "AT", "ORF eins", 0x4301, 0x0000, 0x0000, 0x0AC1 }, { 194, "AT", "ORF 2", 0x4302, 0x0000, 0x0000, 0x0AC2 }, { 195, "AT", "ORF FS3", 0x0000, 0x0000, 0x0000, 0x0AC3 }, { 680, "AT", "ORF Sport+", 0x0000, 0x0000, 0x0000, 0x0AC4 }, { 524, "AT", "ORF III", 0x0000, 0x0000, 0x0000, 0x0AC7 }, { 681, "AT", "Nick / Viva", 0x0000, 0x0000, 0x0000, 0x0AC8 }, { 682, "AT", "MTV Austria", 0x0000, 0x0000, 0x0000, 0x0AC9 }, { 205, "AT", "ATV", 0x430C, 0x0000, 0x0000, 0x0ACA }, { 196, "AT", "ORF 2 Burgenland", 0x0000, 0x0000, 0x0000, 0x0ACB }, { 197, "AT", "ORF 2 Kärnten", 0x0000, 0x0000, 0x0000, 0x0ACC }, { 198, "AT", "ORF 2 Niederösterreich", 0x0000, 0x0000, 0x0000, 0x0ACD }, { 199, "AT", "ORF 2 Oberösterreich", 0x0000, 0x0000, 0x0000, 0x0ACE }, { 200, "AT", "ORF 2 Salzburg", 0x0000, 0x0000, 0x0000, 0x0ACF }, { 201, "AT", "ORF 2 Steiermark", 0x0000, 0x0000, 0x0000, 0x0AD0 }, { 202, "AT", "ORF 2 Tirol", 0x0000, 0x0000, 0x0000, 0x0AD1 }, { 203, "AT", "ORF 2 Vorarlberg", 0x0000, 0x0000, 0x0000, 0x0AD2 }, { 204, "AT", "ORF 2 Wien", 0x0000, 0x0000, 0x0000, 0x0AD3 }, { 916, "AT", "ATV2", 0x0000, 0x0000, 0x0000, 0x0ADE }, { 6, "BE", "VT4", 0x0404, 0x1604, 0x3604, 0x0000 }, { 1, "BE", "VRT TV1", 0x3201, 0x1601, 0x3603, 0x0000 }, { 5, "BE", "CANVAS", 0x3202, 0x1602, 0x3602, 0x0000 }, { 3, "BE", "RTBF 1", 0x3203, 0x0000, 0x0000, 0x0000 }, { 4, "BE", "RTBF 2", 0x3204, 0x0000, 0x0000, 0x0000 }, { 7, "BE", "VTM", 0x3205, 0x1605, 0x3605, 0x0000 }, { 2, "BE", "Kanaal2", 0x3206, 0x1606, 0x3606, 0x0000 }, { 381, "BE", "RTBF Sat", 0x3207, 0x0000, 0x0000, 0x0000 }, { 379, "BE", "AB3", 0x320C, 0x0000, 0x0000, 0x0000 }, { 380, "BE", "AB4e", 0x320D, 0x0000, 0x0000, 0x0000 }, { 402, "BE", "Ring TV", 0x320E, 0x0000, 0x0000, 0x0000 }, { 384, "BE", "JIM.tv", 0x320F, 0x0000, 0x0000, 0x0000 }, { 386, "BE", "RTV-Kempen", 0x3210, 0x0000, 0x0000, 0x0000 }, { 387, "BE", "RTV-Mechelen", 0x3211, 0x0000, 0x0000, 0x0000 }, { 388, "BE", "MCM Belgium", 0x3212, 0x0000, 0x0000, 0x0000 }, { 389, "BE", "Vitaya", 0x3213, 0x0000, 0x0000, 0x0000 }, { 390, "BE", "WTV", 0x3214, 0x0000, 0x0000, 0x0000 }, { 391, "BE", "FocusTV", 0x3215, 0x0000, 0x0000, 0x0000 }, { 392, "BE", "Be 1 ana", 0x3216, 0x0000, 0x0000, 0x0000 }, { 393, "BE", "Be 1 num", 0x3217, 0x0000, 0x0000, 0x0000 }, { 394, "BE", "Be Ciné 1", 0x3218, 0x0000, 0x0000, 0x0000 }, { 395, "BE", "Be Sport 1", 0x3219, 0x0000, 0x0000, 0x0000 }, { 396, "BE", "PRIME Sport 1", 0x321A, 0x0000, 0x0000, 0x0000 }, { 397, "BE", "PRIME Sport 2", 0x321B, 0x0000, 0x0000, 0x0000 }, { 398, "BE", "PRIME Action", 0x321C, 0x0000, 0x0000, 0x0000 }, { 399, "BE", "PRIME One", 0x321D, 0x0000, 0x0000, 0x0000 }, { 403, "BE", "TV Brussel", 0x321E, 0x0000, 0x0000, 0x0000 }, { 404, "BE", "AVSe", 0x321F, 0x0000, 0x0000, 0x0000 }, { 689, "BE", "S televisie", 0x3220, 0x0000, 0x0000, 0x0000 }, { 400, "BE", "TV Limburg", 0x3221, 0x0000, 0x0000, 0x0000 }, { 401, "BE", "Kanaal 3", 0x3222, 0x0000, 0x0000, 0x0000 }, { 405, "BE", "ATV", 0x3223, 0x0000, 0x0000, 0x0000 }, { 406, "BE", "ROB TV", 0x3224, 0x0000, 0x0000, 0x0000 }, { 429, "BE", "Sporza", 0x3226, 0x0000, 0x0000, 0x0000 }, { 625, "BE", "VIJF tv", 0x3227, 0x0000, 0x0000, 0x0000 }, { 687, "BE", "Life!tv", 0x3228, 0x0000, 0x0000, 0x0000 }, { 688, "BE", "MTV Belgium FR", 0x3229, 0x0000, 0x0000, 0x0000 }, { 751, "BE", "EXQI Sport NL", 0x322A, 0x0000, 0x0000, 0x0000 }, { 752, "BE", "EXQI Culture NL", 0x322B, 0x0000, 0x0000, 0x0000 }, { 753, "BE", "Acht", 0x322C, 0x0000, 0x0000, 0x0000 }, { 754, "BE", "EXQI Sport FR", 0x322D, 0x0000, 0x0000, 0x0000 }, { 755, "BE", "EXQI Culture FR", 0x322E, 0x0000, 0x0000, 0x0000 }, { 756, "BE", "Discovery Flanders", 0x322F, 0x0000, 0x0000, 0x0000 }, { 407, "BE", "Télé Bruxelles", 0x3230, 0x0000, 0x0000, 0x0000 }, { 408, "BE", "Télésambre", 0x3231, 0x0000, 0x0000, 0x0000 }, { 409, "BE", "TV Com", 0x3232, 0x0000, 0x0000, 0x0000 }, { 410, "BE", "Canal Zoom", 0x3233, 0x0000, 0x0000, 0x0000 }, { 411, "BE", "Vidéoscope", 0x3234, 0x0000, 0x0000, 0x0000 }, { 412, "BE", "Canal C", 0x3235, 0x0000, 0x0000, 0x0000 }, { 413, "BE", "Télé MB", 0x3236, 0x0000, 0x0000, 0x0000 }, { 414, "BE", "Antenne Centre", 0x3237, 0x0000, 0x0000, 0x0000 }, { 415, "BE", "Télévesdre", 0x3238, 0x0000, 0x0000, 0x0000 }, { 416, "BE", "RTC Télé Liège", 0x3239, 0x0000, 0x0000, 0x0000 }, { 757, "BE", "EXQI Plus FR", 0x323B, 0x0000, 0x0000, 0x0000 }, { 758, "BE", "EXQI Life NL", 0x323C, 0x0000, 0x0000, 0x0000 }, { 759, "BE", "EXQI Life FR", 0x323D, 0x0000, 0x0000, 0x0000 }, { 760, "BE", "EXQI News NL", 0x323E, 0x0000, 0x0000, 0x0000 }, { 761, "BE", "EXQI News FR", 0x323F, 0x0000, 0x0000, 0x0000 }, { 417, "BE", "No tele", 0x3240, 0x0000, 0x0000, 0x0000 }, { 418, "BE", "TV Lux", 0x3241, 0x0000, 0x0000, 0x0000 }, { 762, "BE", "Radio Contact Vision", 0x3243, 0x0000, 0x0000, 0x0000 }, { 763, "BE", "vtmKzoom", 0x3243, 0x0000, 0x0000, 0x0000 }, { 764, "BE", "NJAM", 0x3244, 0x0000, 0x0000, 0x0000 }, { 765, "BE", "Ketnet op 12", 0x3245, 0x0000, 0x0000, 0x0000 }, { 766, "BE", "MENT TV", 0x3246, 0x0000, 0x0000, 0x0000 }, { 767, "BE", "National Geographic Channel Belgium", 0x3247, 0x0000, 0x0000, 0x0000 }, { 768, "BE", "Libelle TV", 0x3248, 0x0000, 0x0000, 0x0000 }, { 769, "BE", "Lacht", 0x3249, 0x0000, 0x0000, 0x0000 }, { 419, "BE", "Kanaal Z - NL", 0x325A, 0x0000, 0x0000, 0x0000 }, { 420, "BE", "CANAL Z - FR", 0x325B, 0x0000, 0x0000, 0x0000 }, { 421, "BE", "CARTOON Network - NL", 0x326A, 0x0000, 0x0000, 0x0000 }, { 422, "BE", "CARTOON Network - FR", 0x326B, 0x0000, 0x0000, 0x0000 }, { 423, "BE", "LIBERTY CHANNEL - NL", 0x327A, 0x0000, 0x0000, 0x0000 }, { 424, "BE", "LIBERTY CHANNEL - FR", 0x327B, 0x0000, 0x0000, 0x0000 }, { 425, "BE", "TCM - NL", 0x328A, 0x0000, 0x0000, 0x0000 }, { 426, "BE", "TCM - FR", 0x328B, 0x0000, 0x0000, 0x0000 }, { 427, "BE", "Mozaiek/Mosaique", 0x3298, 0x0000, 0x0000, 0x0000 }, { 428, "BE", "Info Kanaal/Canal Info", 0x3299, 0x0000, 0x0000, 0x0000 }, { 530, "BE", "Be 1 + 1h", 0x32A7, 0x0000, 0x0000, 0x0000 }, { 531, "BE", "Be Ciné 2", 0x32A8, 0x0000, 0x0000, 0x0000 }, { 532, "BE", "Be Sport 2", 0x32A9, 0x0000, 0x0000, 0x0000 }, { 770, "BE", "Nickelodeon/MTV Belgium FR", 0x32AA, 0x0000, 0x0000, 0x0000 }, { 771, "BE", "Nickelodeon Belgium NL", 0x32AB, 0x0000, 0x0000, 0x0000 }, { 772, "BE", "Nickelodeon Belgium FR", 0x32AC, 0x0000, 0x0000, 0x0000 }, { 773, "BE", "Nick Jr Belgium NL", 0x32AD, 0x0000, 0x0000, 0x0000 }, { 774, "BE", "Nick Jr Belgium FR", 0x32AE, 0x0000, 0x0000, 0x0000 }, { 775, "BE", "MTV Belgium NL", 0x32AF, 0x0000, 0x0000, 0x0000 }, { 776, "BE", "anne", 0x32B0, 0x0000, 0x0000, 0x0000 }, { 777, "BE", "vtmKzoom+", 0x32B1, 0x0000, 0x0000, 0x0000 }, { 8, "HR", "HRT1", 0x0385, 0x0000, 0x0000, 0x0000 }, { 636, "HR", "Nova TV", 0x0386, 0x0000, 0x0000, 0x0000 }, { 690, "HR", "HRT2", 0x0387, 0x0000, 0x0000, 0x0000 }, { 691, "HR", "HRT PLUS", 0x0388, 0x0000, 0x0000, 0x0000 }, { 692, "HR", "RTL Televizija", 0x0400, 0x0000, 0x0000, 0x0000 }, { 778, "HR", "RTL PLUS", 0x0401, 0x0000, 0x0000, 0x0000 }, { 779, "HR", "TV Nova", 0x0402, 0x0000, 0x0000, 0x0000 }, { 780, "HR", "RI-TV", 0x0403, 0x0000, 0x0000, 0x0000 }, { 781, "HR", "Kanal RI", 0x0405, 0x0000, 0x0000, 0x0000 }, { 782, "HR", "NIT", 0x0406, 0x0000, 0x0000, 0x0000 }, { 783, "HR", "Kapital Network", 0x0407, 0x0000, 0x0000, 0x0000 }, { 784, "HR", "Vox TV Zadar", 0x0408, 0x0000, 0x0000, 0x0000 }, { 785, "HR", "GTV Zadar", 0x0409, 0x0000, 0x0000, 0x0000 }, { 786, "HR", "TV Sibenik", 0x040A, 0x0000, 0x0000, 0x0000 }, { 787, "HR", "VTV Varazdin", 0x040B, 0x0000, 0x0000, 0x0000 }, { 788, "HR", "TV Cakovec", 0x040C, 0x0000, 0x0000, 0x0000 }, { 789, "HR", "TV Jadran", 0x040D, 0x0000, 0x0000, 0x0000 }, { 790, "HR", "Z1", 0x040E, 0x0000, 0x0000, 0x0000 }, { 791, "HR", "OTV", 0x040F, 0x0000, 0x0000, 0x0000 }, { 792, "HR", "24sata.tv", 0x0410, 0x0000, 0x0000, 0x0000 }, { 793, "HR", "5 Kanal", 0x0411, 0x0000, 0x0000, 0x0000 }, { 794, "HR", "B1 TV (Bjelovarska TV)", 0x0412, 0x0000, 0x0000, 0x0000 }, { 795, "HR", "Croatian Music Channel", 0x0413, 0x0000, 0x0000, 0x0000 }, { 796, "HR", "Dubrovacka TV", 0x0414, 0x0000, 0x0000, 0x0000 }, { 797, "HR", "Infokanal Split", 0x0415, 0x0000, 0x0000, 0x0000 }, { 798, "HR", "Kutina TV", 0x0416, 0x0000, 0x0000, 0x0000 }, { 799, "HR", "Mini TV (NovaTV-djecji)", 0x0417, 0x0000, 0x0000, 0x0000 }, { 800, "HR", "Narodna TV", 0x0418, 0x0000, 0x0000, 0x0000 }, { 801, "HR", "Nezavisna televizija", 0x0419, 0x0000, 0x0000, 0x0000 }, { 802, "HR", "Osjecka televizija", 0x041A, 0x0000, 0x0000, 0x0000 }, { 803, "HR", "RK TV", 0x041B, 0x0000, 0x0000, 0x0000 }, { 804, "HR", "Saborska TV", 0x041C, 0x0000, 0x0000, 0x0000 }, { 805, "HR", "Samoborska TV", 0x041D, 0x0000, 0x0000, 0x0000 }, { 806, "HR", "Slavonsko-brodska Televizija", 0x041E, 0x0000, 0x0000, 0x0000 }, { 807, "HR", "Splitska TV", 0x041F, 0x0000, 0x0000, 0x0000 }, { 808, "HR", "Televizija Slavonije i Baranje", 0x0420, 0x0000, 0x0000, 0x0000 }, { 809, "HR", "Tportal.hr", 0x0421, 0x0000, 0x0000, 0x0000 }, { 810, "HR", "TV 4R", 0x0422, 0x0000, 0x0000, 0x0000 }, { 811, "HR", "TV Dalmacija", 0x0423, 0x0000, 0x0000, 0x0000 }, { 812, "HR", "TV Dugi Rat", 0x0424, 0x0000, 0x0000, 0x0000 }, { 813, "HR", "TV Plus", 0x0425, 0x0000, 0x0000, 0x0000 }, { 814, "HR", "TV Turopolje", 0x0426, 0x0000, 0x0000, 0x0000 }, { 815, "HR", "TV Velika Gorica", 0x0427, 0x0000, 0x0000, 0x0000 }, { 816, "HR", "Vecernji HR", 0x0428, 0x0000, 0x0000, 0x0000 }, { 817, "HR", "Vinkovacka televizija", 0x0429, 0x0000, 0x0000, 0x0000 }, { 818, "HR", "Zagrebacka Kablovska Televizija", 0x042A, 0x0000, 0x0000, 0x0000 }, { 819, "HR", "Doma TV", 0x042B, 0x0000, 0x0000, 0x0000 }, { 820, "HR", "RTL 2", 0x042C, 0x0000, 0x0000, 0x0000 }, { 821, "HR", "HOO - Sport", 0x042D, 0x0000, 0x0000, 0x0000 }, { 822, "HR", "HRT3", 0x042E, 0x0000, 0x0000, 0x0000 }, { 823, "HR", "HRT4", 0x042F, 0x0000, 0x0000, 0x0000 }, { 824, "CZ", "Barrandov TV", 0x4200, 0x0000, 0x0000, 0x0000 }, { 9, "CZ", "CT 1", 0x4201, 0x32C1, 0x3C21, 0x0000 }, { 10, "CZ", "CT 2", 0x4202, 0x32C2, 0x3C22, 0x0000 }, { 17, "CZ", "NOVA TV", 0x4203, 0x32C3, 0x3C23, 0x0000 }, { 430, "CZ", "Prima TV", 0x4204, 0x0000, 0x0000, 0x0000 }, { 431, "CZ", "TV Praha", 0x4205, 0x0000, 0x0000, 0x0000 }, { 432, "CZ", "TV HK", 0x4206, 0x0000, 0x0000, 0x0000 }, { 433, "CZ", "TV Pardubice", 0x4207, 0x0000, 0x0000, 0x0000 }, { 434, "CZ", "TV Brno", 0x4208, 0x0000, 0x0000, 0x0000 }, { 825, "CZ", "Prima COOL", 0x4209, 0x0000, 0x0000, 0x0000 }, { 637, "CZ", "CT24", 0x420A, 0x0000, 0x0000, 0x0000 }, { 826, "CZ", "CT4", 0x420B, 0x0000, 0x0000, 0x0000 }, { 827, "CZ", "CT5", 0x420C, 0x0000, 0x0000, 0x0000 }, { 828, "CZ", "Ocko TV", 0x4210, 0x0000, 0x0000, 0x0000 }, { 12, "CZ", "CT1 Brno", 0x4211, 0x32D1, 0x3B01, 0x0000 }, { 15, "CZ", "CT2 Brno", 0x4212, 0x32D2, 0x3B04, 0x0000 }, { 693, "CZ", "NOVA CINEMA", 0x4213, 0x32D3, 0x3B13, 0x0000 }, { 694, "CZ", "GALAXIE SPORT", 0x4214, 0x32D4, 0x3B14, 0x0000 }, { 829, "CZ", "Prima Love", 0x4215, 0x0000, 0x0000, 0x0000 }, { 830, "CZ", "CT24 Brno", 0x421A, 0x0000, 0x0000, 0x0000 }, { 831, "CZ", "CT4 Brno", 0x421B, 0x0000, 0x0000, 0x0000 }, { 832, "CZ", "CT5 Brno", 0x421C, 0x0000, 0x0000, 0x0000 }, { 13, "CZ", "CT1 Ostravia", 0x4221, 0x32E1, 0x3B02, 0x0000 }, { 16, "CZ", "CT2 Ostravia", 0x4222, 0x32E2, 0x3B05, 0x0000 }, { 833, "CZ", "CT24 Ostravia", 0x422A, 0x0000, 0x0000, 0x0000 }, { 834, "CZ", "CT4 Ostravia", 0x422B, 0x0000, 0x0000, 0x0000 }, { 835, "CZ", "CT5 Ostravia", 0x422C, 0x0000, 0x0000, 0x0000 }, { 11, "CZ", "CT1 Regional", 0x4231, 0x32F1, 0x3C25, 0x0000 }, { 14, "CZ", "CT2 Regional", 0x4232, 0x32F2, 0x3B03, 0x0000 }, { 836, "CZ", "Z1 TV", 0x4233, 0x0000, 0x0000, 0x0000 }, { 837, "CZ", "CT24 Regional", 0x423A, 0x0000, 0x0000, 0x0000 }, { 838, "CZ", "CT4 Regional", 0x423B, 0x0000, 0x0000, 0x0000 }, { 839, "CZ", "CT5 Regional", 0x423C, 0x0000, 0x0000, 0x0000 }, { 20, "DK", "TV 2", 0x4502, 0x2902, 0x3902, 0x0000 }, { 21, "DK", "TV 2 Zulu", 0x4503, 0x2904, 0x3904, 0x0000 }, { 435, "DK", "Discovery Denmark", 0x4504, 0x0000, 0x0000, 0x0000 }, { 436, "DK", "TV 2 Charlie", 0x4505, 0x2905, 0x0000, 0x0000 }, { 696, "DK", "TV Danmark", 0x4506, 0x2906, 0x0000, 0x0000 }, { 697, "DK", "Kanal 5", 0x4507, 0x2907, 0x0000, 0x0000 }, { 533, "DK", "TV 2 Film", 0x4508, 0x2908, 0x0000, 0x0000 }, { 535, "DK", "TV 2 News", 0x4509, 0x2909, 0x0000, 0x0000 }, { 695, "DK", "TV 2 FRI", 0x450A, 0x0000, 0x0000, 0x0000 }, { 19, "DK", "DR2", 0x49CF, 0x2903, 0x3903, 0x0000 }, { 18, "DK", "DR1", 0x7392, 0x2901, 0x3901, 0x0000 }, { 23, "FI", "YLE1", 0x3581, 0x2601, 0x3601, 0x0000 }, { 24, "FI", "YLE2", 0x3582, 0x2602, 0x3607, 0x0000 }, { 22, "FI", "OWL3", 0x358F, 0x260F, 0x3614, 0x0000 }, { 27, "FR", "Arte / La Cinquième", 0x330A, 0x2F0A, 0x3F0A, 0x0000 }, { 41, "FR", "RFO1", 0x3311, 0x2F11, 0x3F11, 0x0000 }, { 42, "FR", "RFO2", 0x3312, 0x2F12, 0x3F12, 0x0000 }, { 26, "FR", "Aqui TV", 0x3320, 0x2F20, 0x3F20, 0x0000 }, { 47, "FR", "TLM", 0x3321, 0x2F21, 0x3F21, 0x0000 }, { 48, "FR", "TLT", 0x3322, 0x2F22, 0x3F22, 0x0000 }, { 437, "FR", "Sailing Channel", 0x33B2, 0x0000, 0x0000, 0x0000 }, { 25, "FR", "AB1", 0x33C1, 0x2FC1, 0x3F41, 0x0000 }, { 28, "FR", "Canal J", 0x33C2, 0x2FC2, 0x3F42, 0x0000 }, { 29, "FR", "Canal Jimmy", 0x33C3, 0x2FC3, 0x3F43, 0x0000 }, { 36, "FR", "LCI", 0x33C4, 0x2FC4, 0x3F44, 0x0000 }, { 35, "FR", "La Chaîne Météo", 0x33C5, 0x2FC5, 0x3F45, 0x0000 }, { 38, "FR", "MCM", 0x33C6, 0x2FC6, 0x3F46, 0x0000 }, { 49, "FR", "TMC Monte-Carlo", 0x33C7, 0x2FC7, 0x3F47, 0x0000 }, { 39, "FR", "Paris Première", 0x33C8, 0x2FC8, 0x3F48, 0x0000 }, { 40, "FR", "Planète", 0x33C9, 0x2FC9, 0x3F49, 0x0000 }, { 43, "FR", "Série Club", 0x33CA, 0x2FCA, 0x3F4A, 0x0000 }, { 44, "FR", "Télétoon", 0x33CB, 0x2FCB, 0x3F4B, 0x0000 }, { 45, "FR", "Téva", 0x33CC, 0x2FCC, 0x3F4C, 0x0000 }, { 840, "FR", "Disney Channel", 0x33D1, 0x0000, 0x0000, 0x0000 }, { 841, "FR", "Disney Channel +1", 0x33D2, 0x0000, 0x0000, 0x0000 }, { 842, "FR", "Disney XD", 0x33D3, 0x0000, 0x0000, 0x0000 }, { 843, "FR", "Disney Junior", 0x33D4, 0x0000, 0x0000, 0x0000 }, { 46, "FR", "TF1", 0x33F1, 0x2F01, 0x3F01, 0x0000 }, { 33, "FR", "France 2", 0x33F2, 0x2F02, 0x3F02, 0x0000 }, { 34, "FR", "France 3", 0x33F3, 0x2F03, 0x3F03, 0x0000 }, { 30, "FR", "Canal+", 0x33F4, 0x2F04, 0x3F04, 0x0000 }, { 378, "FR", "France 5", 0x33F5, 0x2F05, 0x3F05, 0x0000 }, { 37, "FR", "M6", 0x33F6, 0x2F06, 0x3F06, 0x0000 }, { 32, "FR", "Eurosport", 0xF101, 0x2FE2, 0x3F62, 0x0000 }, { 536, "FR", "Eurosport2", 0xF102, 0x2FE3, 0x3F63, 0x0000 }, { 537, "FR", "Eurosportnews", 0xF103, 0x2FE4, 0x3F64, 0x0000 }, { 50, "FR", "TV5", 0xF500, 0x2FE5, 0x3F65, 0x0000 }, { 31, "FR", "Euronews", 0xFE01, 0x2FE1, 0x3F61, 0x0000 }, { 117, "DE", "ARD", 0x4901, 0x0000, 0x3D41, 0x0DC1 }, { 118, "DE", "ZDF", 0x4902, 0x0000, 0x3D42, 0x0DC2 }, { 119, "DE", "ARD/ZDF Vormittagsprogramm", 0x0000, 0x0000, 0x0000, 0x0DC3 }, { 844, "DE", "OK54 Bürgerrundfunk Trier", 0x4904, 0x0000, 0x0000, 0x0D44 }, { 120, "DE", "ARD-TV-Sternpunkt", 0x0000, 0x0000, 0x0000, 0x0DC4 }, { 632, "DE", "Channel21", 0x4905, 0x0000, 0x0000, 0x0D79 }, { 123, "DE", "Phoenix", 0x4908, 0x0000, 0x0000, 0x0DC8 }, { 60, "DE", "Arte", 0x490A, 0x0000, 0x3D05, 0x0D85 }, { 68, "DE", "VOX", 0x490C, 0x0000, 0x0000, 0x0D8E }, { 51, "DE", "FESTIVAL", 0x4941, 0x0000, 0x0000, 0x0D41 }, { 52, "DE", "MUXX", 0x4942, 0x0000, 0x0000, 0x0D42 }, { 53, "DE", "EXTRA", 0x4943, 0x0000, 0x0000, 0x0D43 }, { 377, "DE", "BR-Alpha", 0x4944, 0x0000, 0x0000, 0x0000 }, { 58, "DE", "RBB-1 Regional", 0x0000, 0x0000, 0x0000, 0x0D81 }, { 59, "DE", "RBB Brandenburg", 0x4982, 0x0000, 0x0000, 0x0D82 }, { 748, "DE", "Parlamentsfernsehen", 0x0000, 0x0000, 0x0000, 0x0D83 }, { 61, "DE", "1A-Fernsehen", 0x0000, 0x0000, 0x0000, 0x0D87 }, { 62, "DE", "VIVA", 0x0000, 0x0000, 0x0000, 0x0D88 }, { 63, "DE", "Comedy Central", 0x0000, 0x0000, 0x0000, 0x0D89 }, { 64, "DE", "SuperRTL", 0x0000, 0x0000, 0x0000, 0x0D8A }, { 65, "DE", "RTL Club", 0x0000, 0x0000, 0x0000, 0x0D8B }, { 66, "DE", "n-tv", 0x0000, 0x0000, 0x0000, 0x0D8C }, { 67, "DE", "DSF", 0x0000, 0x0000, 0x0000, 0x0D8D }, { 69, "DE", "RTL 2", 0x0D8F, 0x0000, 0x0000, 0x0D8F }, { 749, "DE", "DMAX", 0x0000, 0x0000, 0x0000, 0x0D72 }, { 664, "DE", "MTV", 0x0000, 0x0000, 0x0000, 0x0D73 }, { 665, "DE", "Nick Deutschland", 0x0000, 0x0000, 0x0000, 0x0D74 }, { 634, "DE", "KDG Infokanal", 0x0000, 0x0000, 0x0000, 0x0D75 }, { 633, "DE", "DAS VIERTE", 0x0000, 0x0000, 0x0000, 0x0D76 }, { 538, "DE", "1-2-3.TV", 0x49BD, 0x0000, 0x0000, 0x0D77 }, { 522, "DE", "Tele 5", 0x49BE, 0x0000, 0x0000, 0x0D78 }, { 523, "DE", "N24", 0x0000, 0x0000, 0x0000, 0x0D7A }, { 631, "DE", "TV Berlin", 0x0000, 0x0000, 0x0000, 0x0D7B }, { 54, "DE", "ONYX-TV", 0x0000, 0x0000, 0x0000, 0x0D7C }, { 56, "DE", "Nickelodeon", 0x0000, 0x0000, 0x0000, 0x0D7E }, { 57, "DE", "Home Shopping Europe", 0x49BF, 0x0000, 0x0000, 0x0D7F }, { 70, "DE", "RTL 2 Regional", 0x0000, 0x0000, 0x0000, 0x0D90 }, { 71, "DE", "Eurosport", 0x0000, 0x0000, 0x0000, 0x0D91 }, { 72, "DE", "Kabel 1", 0x0000, 0x0000, 0x0000, 0x0D92 }, { 910, "DE", "Sixx", 0x0000, 0x0000, 0x0000, 0x0D93 }, { 73, "DE", "PRO 7", 0x0000, 0x0000, 0x0000, 0x0D94 }, { 74, "DE", "PRO 7 Austria", 0x0AE8, 0x0000, 0x0000, 0x0D14 }, { 75, "DE", "SAT 1 Brandenburg", 0x0000, 0x0000, 0x0000, 0x0D95 }, { 76, "DE", "SAT 1 Thüringen", 0x0000, 0x0000, 0x0000, 0x0D96 }, { 77, "DE", "SAT 1 Sachsen", 0x0000, 0x0000, 0x0000, 0x0D97 }, { 78, "DE", "SAT 1 Mecklenburg-Vorpommern", 0x0000, 0x0000, 0x0000, 0x0D98 }, { 79, "DE", "SAT 1 Sachsen-Anhalt", 0x0000, 0x0000, 0x0000, 0x0D99 }, { 80, "DE", "RTL Regional", 0x0000, 0x0000, 0x0000, 0x0D9A }, { 81, "DE", "RTL Schleswig-Holstein", 0x0000, 0x0000, 0x0000, 0x0D9B }, { 82, "DE", "RTL Hamburg", 0x0000, 0x0000, 0x0000, 0x0D9C }, { 83, "DE", "RTL Berlin", 0x0000, 0x0000, 0x0000, 0x0D9D }, { 84, "DE", "RTL Niedersachsen", 0x0000, 0x0000, 0x0000, 0x0D9E }, { 85, "DE", "RTL Bremen", 0x0000, 0x0000, 0x0000, 0x0D9F }, { 86, "DE", "RTL Nordrhein-Westfalen", 0x0000, 0x0000, 0x0000, 0x0DA0 }, { 87, "DE", "RTL Hessen", 0x0000, 0x0000, 0x0000, 0x0DA1 }, { 88, "DE", "RTL Rheinland-Pfalz", 0x0000, 0x0000, 0x0000, 0x0DA2 }, { 89, "DE", "RTL Baden-Württemberg", 0x0000, 0x0000, 0x0000, 0x0DA3 }, { 90, "DE", "RTL Bayern", 0x0000, 0x0000, 0x0000, 0x0DA4 }, { 91, "DE", "RTL Saarland", 0x0000, 0x0000, 0x0000, 0x0DA5 }, { 92, "DE", "RTL Sachsen-Anhalt", 0x0000, 0x0000, 0x0000, 0x0DA6 }, { 93, "DE", "RTL Mecklenburg-Vorpommern", 0x0000, 0x0000, 0x0000, 0x0DA7 }, { 94, "DE", "RTL Sachsen", 0x0000, 0x0000, 0x0000, 0x0DA8 }, { 95, "DE", "RTL Thüringen", 0x0000, 0x0000, 0x0000, 0x0DA9 }, { 96, "DE", "RTL Brandenburg", 0x0000, 0x0000, 0x0000, 0x0DAA }, { 97, "DE", "RTL", 0x0000, 0x0000, 0x0000, 0x0DAB }, { 98, "DE", "Premiere", 0x0000, 0x0000, 0x0000, 0x0DAC }, { 99, "DE", "SAT 1 Regional", 0x0000, 0x0000, 0x0000, 0x0DAD }, { 100, "DE", "SAT 1 Schleswig-Holstein", 0x0000, 0x0000, 0x0000, 0x0DAE }, { 101, "DE", "SAT 1 Hamburg", 0x0000, 0x0000, 0x0000, 0x0DAF }, { 102, "DE", "SAT 1 Berlin", 0x0000, 0x0000, 0x0000, 0x0DB0 }, { 103, "DE", "SAT 1 Niedersachsen", 0x0000, 0x0000, 0x0000, 0x0DB1 }, { 104, "DE", "SAT 1 Bremen", 0x0000, 0x0000, 0x0000, 0x0DB2 }, { 105, "DE", "SAT 1 Nordrhein-Westfalen", 0x0000, 0x0000, 0x0000, 0x0DB3 }, { 106, "DE", "SAT 1 Hessen", 0x0000, 0x0000, 0x0000, 0x0DB4 }, { 107, "DE", "SAT 1 Rheinland-Pfalz", 0x0000, 0x0000, 0x0000, 0x0DB5 }, { 108, "DE", "SAT 1 Baden-Württemberg", 0x0000, 0x0000, 0x0000, 0x0DB6 }, { 109, "DE", "SAT 1 Bayern", 0x0000, 0x0000, 0x0000, 0x0DB7 }, { 110, "DE", "SAT 1 Saarland", 0x0000, 0x0000, 0x0000, 0x0DB8 }, { 111, "DE", "SAT 1", 0x0000, 0x0000, 0x0000, 0x0DB9 }, { 112, "DE", "NEUN LIVE", 0x0000, 0x0000, 0x0000, 0x0DBA }, { 113, "DE", "Deutsche Welle TV Berlin", 0x0000, 0x0000, 0x0000, 0x0DBB }, { 114, "DE", "Berlin Offener Kanal", 0x0000, 0x0000, 0x0000, 0x0DBD }, { 115, "DE", "Berlin-Mix-Channel 2", 0x0000, 0x0000, 0x0000, 0x0DBE }, { 116, "DE", "Berlin-Mix-Channel 1", 0x0000, 0x0000, 0x0000, 0x0DBF }, { 121, "DE", "ARD-TV-Sternpunkt-Fehler", 0x0000, 0x0000, 0x0000, 0x0DC5 }, { 122, "DE", "3sat", 0x49C7, 0x0000, 0x0000, 0x0DC7 }, { 124, "DE", "Kinderkanal", 0x49C9, 0x0000, 0x0000, 0x0DC9 }, { 125, "DE", "BR-1 Regional", 0x0000, 0x0000, 0x0000, 0x0DCA }, { 126, "DE", "BR-3", 0x49CB, 0x0000, 0x3D4B, 0x0DCB }, { 127, "DE", "BR-3 Süd", 0x0000, 0x0000, 0x0000, 0x0DCC }, { 128, "DE", "BR-3 Nord", 0x0000, 0x0000, 0x0000, 0x0DCD }, { 129, "DE", "HR-1 Regional", 0x0000, 0x0000, 0x0000, 0x0DCE }, { 131, "DE", "NDR-1 Dreiländerweit", 0x0000, 0x0000, 0x0000, 0x0DD0 }, { 132, "DE", "NDR-1 Hamburg", 0x0000, 0x0000, 0x0000, 0x0DD1 }, { 133, "DE", "NDR-1 Niedersachsen", 0x0000, 0x0000, 0x0000, 0x0DD2 }, { 134, "DE", "NDR-1 Schleswig-Holstein", 0x0000, 0x0000, 0x0000, 0x0DD3 }, { 135, "DE", "Nord-3 (NDR/SFB/RB)", 0x0000, 0x0000, 0x0000, 0x0DD4 }, { 136, "DE", "NDR-3 Dreiländerweit", 0x49D4, 0x0000, 0x0000, 0x0DD5 }, { 137, "DE", "NDR-3 Hamburg", 0x0000, 0x0000, 0x0000, 0x0DD6 }, { 138, "DE", "NDR-3 Niedersachsen", 0x0000, 0x0000, 0x0000, 0x0DD7 }, { 139, "DE", "NDR-3 Schleswig-Holstein", 0x0000, 0x0000, 0x0000, 0x0DD8 }, { 140, "DE", "RB-1 Regional", 0x0000, 0x0000, 0x0000, 0x0DD9 }, { 141, "DE", "RB-3", 0x49D9, 0x0000, 0x0000, 0x0DDA }, { 142, "DE", "RBB-1 Regional", 0x0000, 0x0000, 0x0000, 0x0DDB }, { 143, "DE", "RBB Berlin", 0x49DC, 0x0000, 0x0000, 0x0DDC }, { 144, "DE", "SWR-1 Baden-Württemberg", 0x0000, 0x0000, 0x0000, 0x0DDD }, { 145, "DE", "SWR-1 Rheinland-Pfalz", 0x0000, 0x0000, 0x0000, 0x0DDE }, { 146, "DE", "SR-1 Regional", 0x49DF, 0x0000, 0x0000, 0x0DDF }, { 147, "DE", "Südwest 3 (SDR/SR/SWF)", 0x0000, 0x0000, 0x0000, 0x0DE0 }, { 148, "DE", "SW 3 Baden-Württemberg", 0x49E1, 0x0000, 0x0000, 0x0DE1 }, { 149, "DE", "SW 3 Saarland", 0x0000, 0x0000, 0x0000, 0x0DE2 }, { 150, "DE", "SW 3 Baden-Württemb. Süd", 0x0000, 0x0000, 0x0000, 0x0DE3 }, { 151, "DE", "SW 3 Rheinland-Pfalz", 0x49E4, 0x0000, 0x0000, 0x0DE4 }, { 152, "DE", "WDR-1 Regional", 0x0000, 0x0000, 0x0000, 0x0DE5 }, { 153, "DE", "WDR", 0x49E6, 0x0000, 0x0000, 0x0DE6 }, { 154, "DE", "WDR-3 Bielefeld", 0x0000, 0x0000, 0x0000, 0x0DE7 }, { 155, "DE", "WDR-3 Dortmund", 0x0000, 0x0000, 0x0000, 0x0DE8 }, { 156, "DE", "WDR-3 Düsseldorf", 0x0000, 0x0000, 0x0000, 0x0DE9 }, { 157, "DE", "WDR-3 Köln", 0x0000, 0x0000, 0x0000, 0x0DEA }, { 158, "DE", "WDR-3 Münster", 0x0000, 0x0000, 0x0000, 0x0DEB }, { 159, "DE", "SW 3 Regional", 0x0000, 0x0000, 0x0000, 0x0DEC }, { 160, "DE", "SW 3 Baden-Württemberg", 0x0000, 0x0000, 0x0000, 0x0DED }, { 161, "DE", "SW 3 Mannheim", 0x0000, 0x0000, 0x0000, 0x0DEE }, { 162, "DE", "SW 3 Regional", 0x0000, 0x0000, 0x0000, 0x0DEF }, { 163, "DE", "SW 3 Regional", 0x0000, 0x0000, 0x0000, 0x0DF0 }, { 164, "DE", "NDR-1 Mecklenburg-Vorpommern", 0x0000, 0x0000, 0x0000, 0x0DF1 }, { 165, "DE", "NDR-3 Mecklenburg-Vorpommern", 0x0000, 0x0000, 0x0000, 0x0DF2 }, { 166, "DE", "MDR-1 Sachsen", 0x0000, 0x0000, 0x0000, 0x0DF3 }, { 167, "DE", "MDR-3 Sachsen", 0x0000, 0x0000, 0x0000, 0x0DF4 }, { 168, "DE", "MDR Dresden", 0x0000, 0x0000, 0x0000, 0x0DF5 }, { 169, "DE", "MDR-1 Sachsen-Anhalt", 0x0000, 0x0000, 0x0000, 0x0DF6 }, { 170, "DE", "WDR Dortmund", 0x0000, 0x0000, 0x0000, 0x0DF7 }, { 171, "DE", "MDR-3 Sachsen-Anhalt", 0x0000, 0x0000, 0x0000, 0x0DF8 }, { 172, "DE", "MDR Magdeburg", 0x0000, 0x0000, 0x0000, 0x0DF9 }, { 173, "DE", "MDR-1 Thüringen", 0x0000, 0x0000, 0x0000, 0x0DFA }, { 174, "DE", "MDR-3 Thüringen", 0x0000, 0x0000, 0x0000, 0x0DFB }, { 175, "DE", "MDR Erfurt", 0x0000, 0x0000, 0x0000, 0x0DFC }, { 176, "DE", "MDR-1 Regional", 0x0000, 0x0000, 0x0000, 0x0DFD }, { 177, "DE", "MDR", 0x49FE, 0x0000, 0x0000, 0x0DFE }, { 130, "DE", "HR", 0x49FF, 0x0000, 0x0000, 0x0DCF }, { 55, "DE", "QVC", 0x5C49, 0x0000, 0x0000, 0x0D7D }, { 206, "GR", "ET-1", 0x3001, 0x2101, 0x3101, 0x0000 }, { 207, "GR", "NET", 0x3002, 0x2102, 0x3102, 0x0000 }, { 208, "GR", "ET-3", 0x3003, 0x2103, 0x3103, 0x0000 }, { 210, "HU", "MTV1", 0x3601, 0x0000, 0x0000, 0x0000 }, { 217, "HU", "MTV2", 0x3602, 0x0000, 0x0000, 0x0000 }, { 211, "HU", "MTV1 Budapest", 0x3611, 0x0000, 0x0000, 0x0000 }, { 214, "HU", "MTV1 Pécs", 0x3621, 0x0000, 0x0000, 0x0000 }, { 218, "HU", "tv2", 0x3622, 0x0000, 0x0000, 0x0000 }, { 215, "HU", "MTV1 Szeged", 0x3631, 0x0000, 0x0000, 0x0000 }, { 209, "HU", "Duna Televizio", 0x3636, 0x0000, 0x0000, 0x0000 }, { 216, "HU", "MTV1 Szombathely", 0x3641, 0x0000, 0x0000, 0x0000 }, { 212, "HU", "MTV1 Debrecen", 0x3651, 0x0000, 0x0000, 0x0000 }, { 213, "HU", "MTV1 Miskolc", 0x3661, 0x0000, 0x0000, 0x0000 }, { 219, "IS", "Rikisutvarpid-Sjonvarp", 0x3541, 0x0000, 0x0000, 0x0000 }, { 223, "IE", "TV3", 0x3333, 0x0000, 0x0000, 0x0000 }, { 221, "IE", "RTE1", 0x3531, 0x4201, 0x3201, 0x0000 }, { 220, "IE", "Network 2", 0x3532, 0x4202, 0x3202, 0x0000 }, { 222, "IE", "Teilifis na Gaeilge", 0x3533, 0x4203, 0x3203, 0x0000 }, { 227, "IT", "RAI 1", 0x3901, 0x0000, 0x0000, 0x0000 }, { 228, "IT", "RAI 2", 0x3902, 0x0000, 0x0000, 0x0000 }, { 229, "IT", "RAI 3", 0x3903, 0x0000, 0x0000, 0x0000 }, { 231, "IT", "Rete A", 0x3904, 0x0000, 0x0000, 0x0000 }, { 539, "IT", "Canale Italia", 0x3905, 0x1505, 0x0000, 0x0000 }, { 698, "IT", "7 Gold - Telepadova", 0x3906, 0x0000, 0x0000, 0x0000 }, { 699, "IT", "Teleregione", 0x3907, 0x0000, 0x0000, 0x0000 }, { 846, "IT", "Telegenova", 0x3908, 0x0000, 0x0000, 0x0000 }, { 540, "IT", "Telenova", 0x3909, 0x1509, 0x0000, 0x0000 }, { 224, "IT", "Arte", 0x390A, 0x0000, 0x0000, 0x0000 }, { 847, "IT", "Canale Dieci", 0x390B, 0x0000, 0x0000, 0x0000 }, { 237, "IT", "TRS TV", 0x3910, 0x0000, 0x0000, 0x0000 }, { 541, "IT", "Sky Cinema Classic", 0x3911, 0x1511, 0x0000, 0x0000 }, { 543, "IT", "Sky Calcio 1", 0x3913, 0x1513, 0x0000, 0x0000 }, { 544, "IT", "Sky Calcio 2", 0x3914, 0x1514, 0x0000, 0x0000 }, { 545, "IT", "Sky Calcio 3", 0x3915, 0x1515, 0x0000, 0x0000 }, { 546, "IT", "Sky Calcio 4", 0x3916, 0x1516, 0x0000, 0x0000 }, { 547, "IT", "Sky Calcio 5", 0x3917, 0x1517, 0x0000, 0x0000 }, { 548, "IT", "Sky Calcio 6", 0x3918, 0x1518, 0x0000, 0x0000 }, { 549, "IT", "Sky Calcio 7", 0x3919, 0x1519, 0x0000, 0x0000 }, { 656, "IT", "TN8 Telenorba", 0x391A, 0x0000, 0x0000, 0x0000 }, { 449, "IT", "RaiNotizie24", 0x3920, 0x0000, 0x0000, 0x0000 }, { 450, "IT", "RAI Med", 0x3921, 0x0000, 0x0000, 0x0000 }, { 451, "IT", "RAI Sport Più", 0x3922, 0x0000, 0x0000, 0x0000 }, { 452, "IT", "RAI Edu1", 0x3923, 0x0000, 0x0000, 0x0000 }, { 453, "IT", "RAI Edu2", 0x3924, 0x0000, 0x0000, 0x0000 }, { 454, "IT", "RAI NettunoSat1", 0x3925, 0x0000, 0x0000, 0x0000 }, { 455, "IT", "RAI NettunoSat2", 0x3926, 0x0000, 0x0000, 0x0000 }, { 456, "IT", "Camera Deputati", 0x3927, 0x0000, 0x0000, 0x0000 }, { 457, "IT", "Senato", 0x3928, 0x0000, 0x0000, 0x0000 }, { 639, "IT", "RAI 4", 0x3929, 0x0000, 0x0000, 0x0000 }, { 640, "IT", "RAI Gulp", 0x392A, 0x0000, 0x0000, 0x0000 }, { 438, "IT", "Discovery Italy", 0x3930, 0x0000, 0x0000, 0x0000 }, { 700, "IT", "MTV VH1", 0x3931, 0x0000, 0x0000, 0x0000 }, { 446, "IT", "MTV Italia", 0x3933, 0x0000, 0x0000, 0x0000 }, { 447, "IT", "MTV Brand New", 0x3934, 0x0000, 0x0000, 0x0000 }, { 448, "IT", "MTV Hits", 0x3935, 0x0000, 0x0000, 0x0000 }, { 701, "IT", "MTV GOLD", 0x3936, 0x0000, 0x0000, 0x0000 }, { 702, "IT", "MTV PULSE", 0x3937, 0x0000, 0x0000, 0x0000 }, { 232, "IT", "RTV38", 0x3938, 0x0000, 0x0000, 0x0000 }, { 444, "IT", "GAY TV", 0x3939, 0x0000, 0x0000, 0x0000 }, { 657, "IT", "TP9 Telepuglia", 0x393A, 0x0000, 0x0000, 0x0000 }, { 527, "IT", "Video Italia", 0x3940, 0x0000, 0x0000, 0x0000 }, { 467, "IT", "SAT 2000", 0x3941, 0x0000, 0x0000, 0x0000 }, { 550, "IT", "Jimmy", 0x3942, 0x1542, 0x0000, 0x0000 }, { 551, "IT", "Planet", 0x3943, 0x1543, 0x0000, 0x0000 }, { 552, "IT", "Cartoon Network", 0x3944, 0x1544, 0x0000, 0x0000 }, { 553, "IT", "Boomerang", 0x3945, 0x1545, 0x0000, 0x0000 }, { 554, "IT", "CNN International", 0x3946, 0x1546, 0x0000, 0x0000 }, { 555, "IT", "Cartoon Network +1", 0x3947, 0x1547, 0x0000, 0x0000 }, { 556, "IT", "Sky Sports 3", 0x3948, 0x1548, 0x0000, 0x0000 }, { 557, "IT", "Sky Diretta Gol", 0x3949, 0x1549, 0x0000, 0x0000 }, { 658, "IT", "TG NORBA", 0x394A, 0x0000, 0x0000, 0x0000 }, { 460, "IT", "RAISat Cinema", 0x3952, 0x0000, 0x0000, 0x0000 }, { 462, "IT", "RAISat Gambero Rosso", 0x3954, 0x0000, 0x0000, 0x0000 }, { 463, "IT", "RAISat YoYo", 0x3955, 0x0000, 0x0000, 0x0000 }, { 464, "IT", "RAISat Smash", 0x3956, 0x0000, 0x0000, 0x0000 }, { 643, "IT", "RAISat Extra", 0x3959, 0x0000, 0x0000, 0x0000 }, { 644, "IT", "RAISat Premium", 0x395A, 0x0000, 0x0000, 0x0000 }, { 558, "IT", "SCI FI CHANNEL", 0x3960, 0x1560, 0x0000, 0x0000 }, { 439, "IT", "Discovery Civilisations", 0x3961, 0x0000, 0x0000, 0x0000 }, { 440, "IT", "Discovery Travel and Adventure", 0x3962, 0x0000, 0x0000, 0x0000 }, { 441, "IT", "Discovery Science", 0x3963, 0x0000, 0x0000, 0x0000 }, { 562, "IT", "Sky Meteo24", 0x3968, 0x1568, 0x0000, 0x0000 }, { 563, "IT", "Sky Cinema 2", 0x3970, 0x0000, 0x0000, 0x0000 }, { 564, "IT", "Sky Cinema 3", 0x3971, 0x0000, 0x0000, 0x0000 }, { 565, "IT", "Sky Cinema Autore", 0x3972, 0x0000, 0x0000, 0x0000 }, { 566, "IT", "Sky Cinema Max", 0x3973, 0x0000, 0x0000, 0x0000 }, { 567, "IT", "Sky Cinema 16:9", 0x3974, 0x0000, 0x0000, 0x0000 }, { 568, "IT", "Sky Sports 2", 0x3975, 0x0000, 0x0000, 0x0000 }, { 569, "IT", "Sky TG24", 0x3976, 0x0000, 0x0000, 0x0000 }, { 570, "IT", "Fox", 0x3977, 0x1577, 0x0000, 0x0000 }, { 571, "IT", "Foxlife", 0x3978, 0x1578, 0x0000, 0x0000 }, { 572, "IT", "National Geographic Channel", 0x3979, 0x1579, 0x0000, 0x0000 }, { 573, "IT", "A1", 0x3980, 0x1580, 0x0000, 0x0000 }, { 574, "IT", "History Channel", 0x3981, 0x1581, 0x0000, 0x0000 }, { 442, "IT", "FOX Kids", 0x3985, 0x0000, 0x0000, 0x0000 }, { 469, "IT", "PEOPLE TV - RETE 7", 0x3986, 0x0000, 0x0000, 0x0000 }, { 443, "IT", "FOX Kids +1", 0x3987, 0x0000, 0x0000, 0x0000 }, { 445, "IT", "LA7", 0x3988, 0x0000, 0x0000, 0x0000 }, { 470, "IT", "PrimaTV", 0x3989, 0x0000, 0x0000, 0x0000 }, { 471, "IT", "SportItalia", 0x398A, 0x0000, 0x0000, 0x0000 }, { 645, "IT", "Espansione TV", 0x398F, 0x0000, 0x0000, 0x0000 }, { 576, "IT", "STUDIO UNIVERSAL", 0x3990, 0x1590, 0x0000, 0x0000 }, { 472, "IT", "Marcopolo", 0x3991, 0x0000, 0x0000, 0x0000 }, { 473, "IT", "Alice", 0x3992, 0x0000, 0x0000, 0x0000 }, { 474, "IT", "Nuvolari", 0x3993, 0x0000, 0x0000, 0x0000 }, { 475, "IT", "Leonardo", 0x3994, 0x0000, 0x0000, 0x0000 }, { 577, "IT", "SUPERPIPPA CHANNEL", 0x3996, 0x1596, 0x0000, 0x0000 }, { 233, "IT", "Sky Sports 1", 0x3997, 0x0000, 0x0000, 0x0000 }, { 234, "IT", "Sky Cinema 1", 0x3998, 0x0000, 0x0000, 0x0000 }, { 235, "IT", "Tele+3", 0x3999, 0x0000, 0x0000, 0x0000 }, { 646, "IT", "FacileTV", 0x399A, 0x150A, 0x0000, 0x0000 }, { 647, "IT", "Sitcom 2", 0x399B, 0x150B, 0x0000, 0x0000 }, { 648, "IT", "Sitcom 3", 0x399C, 0x150C, 0x0000, 0x0000 }, { 649, "IT", "Sitcom 4", 0x399D, 0x150D, 0x0000, 0x0000 }, { 650, "IT", "Sitcom 5", 0x399E, 0x150E, 0x0000, 0x0000 }, { 703, "IT", "Italiani nel Mondo", 0x399F, 0x150F, 0x0000, 0x0000 }, { 580, "IT", "Sky Calcio 8", 0x39A0, 0x15A0, 0x0000, 0x0000 }, { 581, "IT", "Sky Calcio 9", 0x39A1, 0x15A1, 0x0000, 0x0000 }, { 582, "IT", "Sky Calcio 10", 0x39A2, 0x15A2, 0x0000, 0x0000 }, { 583, "IT", "Sky Calcio 11", 0x39A3, 0x15A3, 0x0000, 0x0000 }, { 584, "IT", "Sky Calcio 12", 0x39A4, 0x15A4, 0x0000, 0x0000 }, { 585, "IT", "Sky Calcio 13", 0x39A5, 0x15A5, 0x0000, 0x0000 }, { 586, "IT", "Sky Calcio 14", 0x39A6, 0x15A6, 0x0000, 0x0000 }, { 587, "IT", "Telesanterno", 0x39A7, 0x15A7, 0x0000, 0x0000 }, { 588, "IT", "Telecentro", 0x39A8, 0x15A8, 0x0000, 0x0000 }, { 589, "IT", "Telestense", 0x39A9, 0x15A9, 0x0000, 0x0000 }, { 704, "IT", "TCS - Telecostasmeralda", 0x39AB, 0x0000, 0x0000, 0x0000 }, { 590, "IT", "Disney Channel +1", 0x39B0, 0x15B0, 0x0000, 0x0000 }, { 466, "IT", "Sailing Channel", 0x39B1, 0x0000, 0x0000, 0x0000 }, { 592, "IT", "Disney Channel", 0x39B2, 0x15B2, 0x0000, 0x0000 }, { 593, "IT", "7 Gold-Sestra Rete", 0x39B3, 0x15B3, 0x0000, 0x0000 }, { 594, "IT", "Rete 8-VGA", 0x39B4, 0x15B4, 0x0000, 0x0000 }, { 595, "IT", "Nuovarete", 0x39B5, 0x15B5, 0x0000, 0x0000 }, { 596, "IT", "Radio Italia TV", 0x39B6, 0x15B6, 0x0000, 0x0000 }, { 597, "IT", "Rete 7", 0x39B7, 0x15B7, 0x0000, 0x0000 }, { 598, "IT", "E! Entertainment Television", 0x39B8, 0x15B8, 0x0000, 0x0000 }, { 599, "IT", "Toon Disney", 0x39B9, 0x15B9, 0x0000, 0x0000 }, { 705, "IT", "Play TV Italia", 0x39BA, 0x0000, 0x0000, 0x0000 }, { 706, "IT", "La7 Cartapiù A", 0x39C1, 0x0000, 0x0000, 0x0000 }, { 707, "IT", "La7 Cartapiù B", 0x39C2, 0x0000, 0x0000, 0x0000 }, { 708, "IT", "La7 Cartapiù C", 0x39C3, 0x0000, 0x0000, 0x0000 }, { 709, "IT", "La7 Cartapiù D", 0x39C4, 0x0000, 0x0000, 0x0000 }, { 710, "IT", "La7 Cartapiù E", 0x39C5, 0x0000, 0x0000, 0x0000 }, { 711, "IT", "La7 Cartapiù X", 0x39C6, 0x0000, 0x0000, 0x0000 }, { 600, "IT", "Bassano TV", 0x39C7, 0x15C7, 0x0000, 0x0000 }, { 601, "IT", "ESPN Classic Sport", 0x39C8, 0x15C8, 0x0000, 0x0000 }, { 468, "IT", "VIDEOLINA", 0x39CA, 0x0000, 0x0000, 0x0000 }, { 607, "IT", "Mediaset Premium 5", 0x39D1, 0x15D1, 0x0000, 0x0000 }, { 603, "IT", "Mediaset Premium 1", 0x39D2, 0x15D2, 0x0000, 0x0000 }, { 604, "IT", "Mediaset Premium 2", 0x39D3, 0x15D3, 0x0000, 0x0000 }, { 605, "IT", "Mediaset Premium 3", 0x39D4, 0x15D4, 0x0000, 0x0000 }, { 606, "IT", "Mediaset Premium 4", 0x39D5, 0x15D5, 0x0000, 0x0000 }, { 608, "IT", "BOING", 0x39D6, 0x15D6, 0x0000, 0x0000 }, { 609, "IT", "Playlist Italia", 0x39D7, 0x15D7, 0x0000, 0x0000 }, { 610, "IT", "MATCH MUSIC", 0x39D8, 0x15D8, 0x0000, 0x0000 }, { 654, "IT", "Televisiva SUPER3", 0x39D9, 0x0000, 0x0000, 0x0000 }, { 651, "IT", "Mediashopping", 0x39DA, 0x0000, 0x0000, 0x0000 }, { 712, "IT", "Mediaset Premium 6", 0x39DB, 0x0000, 0x0000, 0x0000 }, { 713, "IT", "Mediaset Premium 7", 0x39DC, 0x0000, 0x0000, 0x0000 }, { 714, "IT", "Iris", 0x39DF, 0x0000, 0x0000, 0x0000 }, { 611, "IT", "National Geographic +1", 0x39E1, 0x15E1, 0x0000, 0x0000 }, { 612, "IT", "Histroy Channel +1", 0x39E2, 0x15E2, 0x0000, 0x0000 }, { 613, "IT", "Sky TV", 0x39E3, 0x15E3, 0x0000, 0x0000 }, { 614, "IT", "GXT", 0x39E4, 0x15E4, 0x0000, 0x0000 }, { 615, "IT", "Playhouse Disney", 0x39E5, 0x15E5, 0x0000, 0x0000 }, { 616, "IT", "Sky Canale 224", 0x39E6, 0x15E6, 0x0000, 0x0000 }, { 653, "IT", "Music Box", 0x39E7, 0x0000, 0x0000, 0x0000 }, { 715, "IT", "Tele Liguria Sud", 0x39E8, 0x0000, 0x0000, 0x0000 }, { 655, "IT", "TN7 Telenorba", 0x39E9, 0x0000, 0x0000, 0x0000 }, { 716, "IT", "Brescia Punto TV", 0x39EA, 0x0000, 0x0000, 0x0000 }, { 717, "IT", "QOOB", 0x39EB, 0x0000, 0x0000, 0x0000 }, { 851, "IT", "AB Channel", 0x39EE, 0x0000, 0x0000, 0x0000 }, { 617, "IT", "Teleradiocity", 0x39F1, 0x0000, 0x0000, 0x0000 }, { 618, "IT", "Teleradiocity Genova", 0x39F2, 0x0000, 0x0000, 0x0000 }, { 619, "IT", "Teleradiocity Lombardia", 0x39F3, 0x0000, 0x0000, 0x0000 }, { 620, "IT", "Telestar Piemonte", 0x39F4, 0x0000, 0x0000, 0x0000 }, { 621, "IT", "Telestar Liguria", 0x39F5, 0x0000, 0x0000, 0x0000 }, { 622, "IT", "Telestar Lombardia", 0x39F6, 0x0000, 0x0000, 0x0000 }, { 623, "IT", "Italia 8 Piemonte", 0x39F7, 0x0000, 0x0000, 0x0000 }, { 624, "IT", "Italia 8 Lombardia", 0x39F8, 0x0000, 0x0000, 0x0000 }, { 652, "IT", "Radio Tele Europa", 0x39F9, 0x0000, 0x0000, 0x0000 }, { 230, "IT", "Rete 4", 0xFA04, 0x0000, 0x0000, 0x0000 }, { 225, "IT", "Canale 5", 0xFA05, 0x0000, 0x0000, 0x0000 }, { 226, "IT", "Italia 1", 0xFA06, 0x0000, 0x0000, 0x0000 }, { 236, "IT", "TMC", 0xFA08, 0x0000, 0x0000, 0x0000 }, { 382, "LU", "RTL-TV1", 0x3209, 0x0000, 0x0000, 0x0000 }, { 383, "LU", "CLUB-RTL", 0x320A, 0x0000, 0x0000, 0x0000 }, { 385, "LU", "PLUG TV", 0x3225, 0x0000, 0x0000, 0x0000 }, { 238, "LU", "RTL Télé Lëtzebuerg", 0x4000, 0x0000, 0x0000, 0x0000 }, { 718, "LU", "RTL TVI 20 ANS", 0x4020, 0x0000, 0x0000, 0x0000 }, { 750, "LU", "RTL Luxembourg", 0x0000, 0x0000, 0x0000, 0x0791 }, { 852, "NL", "Investigation Discovery", 0x3100, 0x0000, 0x0000, 0x0000 }, { 239, "NL", "Nederland 1", 0x3101, 0x4801, 0x3801, 0x0000 }, { 240, "NL", "Nederland 2", 0x3102, 0x4802, 0x3802, 0x0000 }, { 241, "NL", "Nederland 3", 0x3103, 0x4803, 0x3803, 0x0000 }, { 242, "NL", "RTL 4", 0x3104, 0x4804, 0x3804, 0x0000 }, { 243, "NL", "RTL 5", 0x3105, 0x4805, 0x3805, 0x0000 }, { 244, "NL", "Yorin", 0x3106, 0x4806, 0x3806, 0x0000 }, { 853, "NL", "RTV Noord", 0x3110, 0x0000, 0x0000, 0x0000 }, { 854, "NL", "Omrop Fryslan", 0x3111, 0x0000, 0x0000, 0x0000 }, { 855, "NL", "RTV Drenthe", 0x3112, 0x0000, 0x0000, 0x0000 }, { 856, "NL", "RTV Oost", 0x3113, 0x0000, 0x0000, 0x0000 }, { 857, "NL", "Omroep Gelderland", 0x3114, 0x0000, 0x0000, 0x0000 }, { 858, "NL", "RTV Noord-Holland", 0x3116, 0x0000, 0x0000, 0x0000 }, { 859, "NL", "RTV Utrecht", 0x3117, 0x0000, 0x0000, 0x0000 }, { 860, "NL", "RTV West", 0x3118, 0x0000, 0x0000, 0x0000 }, { 861, "NL", "RTV Rijnmond", 0x3119, 0x0000, 0x0000, 0x0000 }, { 862, "NL", "Omroep Brabant", 0x311A, 0x0000, 0x0000, 0x0000 }, { 863, "NL", "L1 RTV Limburg", 0x311B, 0x0000, 0x0000, 0x0000 }, { 864, "NL", "Omroep Zeeland", 0x311C, 0x0000, 0x0000, 0x0000 }, { 865, "NL", "Omroep Flevoland", 0x311D, 0x0000, 0x0000, 0x0000 }, { 528, "NL", "The BOX", 0x3120, 0x4820, 0x3820, 0x0000 }, { 477, "NL", "Discovery Netherlands", 0x3121, 0x0000, 0x0000, 0x0000 }, { 478, "NL", "Nickelodeon", 0x3122, 0x0000, 0x0000, 0x0000 }, { 476, "NL", "Animal Planet Benelux", 0x3123, 0x0000, 0x0000, 0x0000 }, { 626, "NL", "TIEN", 0x3124, 0x0000, 0x0000, 0x0000 }, { 479, "NL", "NET5", 0x3125, 0x0000, 0x0000, 0x0000 }, { 480, "NL", "SBS6", 0x3126, 0x0000, 0x0000, 0x0000 }, { 481, "NL", "V8", 0x3128, 0x0000, 0x0000, 0x0000 }, { 482, "NL", "TMF Netherlands", 0x3130, 0x0000, 0x0000, 0x0000 }, { 483, "NL", "TMF Belgian Flanders", 0x3131, 0x0000, 0x0000, 0x0000 }, { 484, "NL", "MTV NL", 0x3132, 0x0000, 0x0000, 0x0000 }, { 724, "NL", "Nickelodeon", 0x3133, 0x0000, 0x0000, 0x0000 }, { 725, "NL", "The Box", 0x3134, 0x0000, 0x0000, 0x0000 }, { 726, "NL", "TMF Pure", 0x3135, 0x0000, 0x0000, 0x0000 }, { 727, "NL", "TMF Party", 0x3136, 0x0000, 0x0000, 0x0000 }, { 627, "NL", "RNN7", 0x3137, 0x0000, 0x0000, 0x0000 }, { 728, "NL", "TMF NL", 0x3138, 0x0000, 0x0000, 0x0000 }, { 729, "NL", "Nick Toons", 0x3139, 0x0000, 0x0000, 0x0000 }, { 730, "NL", "Nick Jr.", 0x313A, 0x0000, 0x0000, 0x0000 }, { 731, "NL", "Nick Hits", 0x313B, 0x0000, 0x0000, 0x0000 }, { 732, "NL", "MTV Brand New", 0x313C, 0x0000, 0x0000, 0x0000 }, { 866, "NL", "Disney Channel", 0x313D, 0x0000, 0x0000, 0x0000 }, { 867, "NL", "Disney XD", 0x313E, 0x0000, 0x0000, 0x0000 }, { 868, "NL", "Playhouse Disney", 0x313F, 0x0000, 0x0000, 0x0000 }, { 683, "NL", "RTL 7", 0x3147, 0x4847, 0x3847, 0x0000 }, { 722, "NL", "RTL 8", 0x3148, 0x4848, 0x3848, 0x0000 }, { 723, "NL", "HET GESPREK", 0x3150, 0x0000, 0x0000, 0x0000 }, { 869, "NL", "InfoThuis TV", 0x3151, 0x0000, 0x0000, 0x0000 }, { 870, "NL", "Graafschap TV (Oost-Gelderland)", 0x3152, 0x0000, 0x0000, 0x0000 }, { 871, "NL", "Gelre TV (Veluwe)", 0x3153, 0x0000, 0x0000, 0x0000 }, { 872, "NL", "Gelre TV (Groot-Arnhem)", 0x3154, 0x0000, 0x0000, 0x0000 }, { 873, "NL", "Gelre TV (Groot-Nijmegen)", 0x3155, 0x0000, 0x0000, 0x0000 }, { 874, "NL", "Gelre TV (Betuwe)", 0x3156, 0x0000, 0x0000, 0x0000 }, { 875, "NL", "Brabant 10 (West Brabant)", 0x3157, 0x0000, 0x0000, 0x0000 }, { 876, "NL", "Brabant 10 (Midden Brabant)", 0x3158, 0x0000, 0x0000, 0x0000 }, { 877, "NL", "Brabant 10 (Noord Oost Brabant)", 0x3159, 0x0000, 0x0000, 0x0000 }, { 878, "NL", "Brabant 10 (Zuid Oost Brabant)", 0x315A, 0x0000, 0x0000, 0x0000 }, { 879, "NL", "Regio22", 0x315B, 0x0000, 0x0000, 0x0000 }, { 880, "NL", "Maximaal TV", 0x315C, 0x0000, 0x0000, 0x0000 }, { 881, "NL", "GPTV", 0x315D, 0x0000, 0x0000, 0x0000 }, { 882, "NL", "1TV (Groningen)", 0x315E, 0x0000, 0x0000, 0x0000 }, { 883, "NL", "1TV (Drenthe)", 0x315F, 0x0000, 0x0000, 0x0000 }, { 884, "NL", "Cultura 24", 0x3160, 0x0000, 0x0000, 0x0000 }, { 885, "NL", "101 TV", 0x3161, 0x0000, 0x0000, 0x0000 }, { 886, "NL", "Best 24", 0x3162, 0x0000, 0x0000, 0x0000 }, { 887, "NL", "Holland Doc 24", 0x3163, 0x0000, 0x0000, 0x0000 }, { 888, "NL", "Geschiedenis 24", 0x3164, 0x0000, 0x0000, 0x0000 }, { 889, "NL", "Consumenten 24", 0x3165, 0x0000, 0x0000, 0x0000 }, { 890, "NL", "Humor 24", 0x3166, 0x0000, 0x0000, 0x0000 }, { 891, "NL", "Sterren 24", 0x3167, 0x0000, 0x0000, 0x0000 }, { 892, "NL", "Spirit 24", 0x3168, 0x0000, 0x0000, 0x0000 }, { 893, "NL", "Familie 24", 0x3169, 0x0000, 0x0000, 0x0000 }, { 894, "NL", "Journaal 24", 0x316A, 0x0000, 0x0000, 0x0000 }, { 895, "NL", "Politiek 24", 0x316B, 0x0000, 0x0000, 0x0000 }, { 896, "NL", "RTV Rotterdam", 0x3171, 0x0000, 0x0000, 0x0000 }, { 897, "NL", "Brug TV", 0x3172, 0x0000, 0x0000, 0x0000 }, { 898, "NL", "TV Limburg", 0x3173, 0x0000, 0x0000, 0x0000 }, { 899, "NL", "Kindernet / Comedy Central", 0x3174, 0x0000, 0x0000, 0x0000 }, { 900, "NL", "Comedy Central Extra", 0x3175, 0x0000, 0x0000, 0x0000 }, { 245, "NO", "NRK1", 0x4701, 0x0000, 0x0000, 0x0000 }, { 247, "NO", "TV 2", 0x4702, 0x0000, 0x0000, 0x0000 }, { 246, "NO", "NRK2", 0x4703, 0x0000, 0x0000, 0x0000 }, { 248, "NO", "TV Norge", 0x4704, 0x0000, 0x0000, 0x0000 }, { 485, "NO", "Discovery Nordic", 0x4720, 0x0000, 0x0000, 0x0000 }, { 250, "PL", "TVP1", 0x4801, 0x0000, 0x0000, 0x0000 }, { 251, "PL", "TVP2", 0x4802, 0x0000, 0x0000, 0x0000 }, { 249, "PL", "TV Polonia", 0x4810, 0x0000, 0x0000, 0x0000 }, { 499, "PL", "TVN", 0x4820, 0x0000, 0x0000, 0x0000 }, { 500, "PL", "TVN Siedem", 0x4821, 0x0000, 0x0000, 0x0000 }, { 501, "PL", "TVN24", 0x4822, 0x0000, 0x0000, 0x0000 }, { 486, "PL", "Discovery Poland", 0x4830, 0x0000, 0x0000, 0x0000 }, { 628, "PL", "Animal Planet", 0x4831, 0x0000, 0x0000, 0x0000 }, { 487, "PL", "TVP Warszawa", 0x4880, 0x0000, 0x0000, 0x0000 }, { 488, "PL", "TVP Bialystok", 0x4881, 0x0000, 0x0000, 0x0000 }, { 489, "PL", "TVP Bydgoszcz", 0x4882, 0x0000, 0x0000, 0x0000 }, { 490, "PL", "TVP Gdansk", 0x4883, 0x0000, 0x0000, 0x0000 }, { 491, "PL", "TVP Katowice", 0x4884, 0x0000, 0x0000, 0x0000 }, { 492, "PL", "TVP Krakow", 0x4886, 0x0000, 0x0000, 0x0000 }, { 493, "PL", "TVP Lublin", 0x4887, 0x0000, 0x0000, 0x0000 }, { 494, "PL", "TVP Lodz", 0x4888, 0x0000, 0x0000, 0x0000 }, { 495, "PL", "TVP Rzeszow", 0x4890, 0x0000, 0x0000, 0x0000 }, { 496, "PL", "TVP Poznan", 0x4891, 0x0000, 0x0000, 0x0000 }, { 497, "PL", "TVP Szczecin", 0x4892, 0x0000, 0x0000, 0x0000 }, { 498, "PL", "TVP Wroclaw", 0x4893, 0x0000, 0x0000, 0x0000 }, { 252, "PT", "RTP1", 0x3510, 0x0000, 0x0000, 0x0000 }, { 253, "PT", "RTP2", 0x3511, 0x0000, 0x0000, 0x0000 }, { 254, "PT", "RTPAF", 0x3512, 0x0000, 0x0000, 0x0000 }, { 256, "PT", "RTPI", 0x3513, 0x0000, 0x0000, 0x0000 }, { 255, "PT", "RTPAZ", 0x3514, 0x0000, 0x0000, 0x0000 }, { 257, "PT", "RTPM", 0x3515, 0x0000, 0x0000, 0x0000 }, { 258, "SM", "RTV", 0x3781, 0x0000, 0x0000, 0x0000 }, { 259, "SK", "STV1", 0x42A1, 0x35A1, 0x3521, 0x0000 }, { 260, "SK", "STV2", 0x42A2, 0x35A2, 0x3522, 0x0000 }, { 261, "SK", "STV3", 0x42A3, 0x35A3, 0x3523, 0x0000 }, { 262, "SK", "STV4", 0x42A4, 0x35A4, 0x3524, 0x0000 }, { 263, "SK", "STV1 B. Bystrica", 0x42A5, 0x35A5, 0x3525, 0x0000 }, { 264, "SK", "STV2 B. Bystrica", 0x42A6, 0x35A6, 0x3526, 0x0000 }, { 659, "SK", "TV JOJ", 0x42B1, 0x35B1, 0x3511, 0x0000 }, { 265, "SI", "SLO1", 0xAAE1, 0x0000, 0x0000, 0x0000 }, { 266, "SI", "SLO2", 0xAAE2, 0x0000, 0x0000, 0x0000 }, { 267, "SI", "KC", 0xAAE3, 0x0000, 0x0000, 0x0000 }, { 268, "SI", "TLM", 0xAAE4, 0x0000, 0x0000, 0x0000 }, { 733, "SI", "POP TV", 0xAAE5, 0x0000, 0x0000, 0x0000 }, { 734, "SI", "KANAL A", 0xAAE6, 0x0000, 0x0000, 0x0000 }, { 269, "SI", "SLO3", 0xAAF1, 0x0000, 0x0000, 0x0000 }, { 273, "ES", "ETB 2", 0x3402, 0x0000, 0x0000, 0x0000 }, { 503, "ES", "CANAL 9", 0x3403, 0x0000, 0x0000, 0x0000 }, { 504, "ES", "PUNT 2", 0x3404, 0x0000, 0x0000, 0x0000 }, { 505, "ES", "CCV", 0x3405, 0x0000, 0x0000, 0x0000 }, { 270, "ES", "Arte", 0x340A, 0x0000, 0x0000, 0x0000 }, { 901, "ES", "Canal Extremadura TV", 0x3415, 0x0000, 0x0000, 0x0000 }, { 902, "ES", "Extremadura TV", 0x3416, 0x0000, 0x0000, 0x0000 }, { 660, "ES", "Telemadrid", 0x3420, 0x3E20, 0x3E20, 0x0000 }, { 661, "ES", "La Otra", 0x3421, 0x3E21, 0x3E21, 0x0000 }, { 662, "ES", "TM SAT", 0x3422, 0x3E22, 0x3E22, 0x0000 }, { 735, "ES", "La sexta", 0x3423, 0x3E23, 0x3E23, 0x0000 }, { 736, "ES", "Antena 3", 0x3424, 0x3E24, 0x3E24, 0x0000 }, { 737, "ES", "Neox", 0x3425, 0x3E25, 0x3E25, 0x0000 }, { 738, "ES", "Nova", 0x3426, 0x3E26, 0x3E26, 0x0000 }, { 739, "ES", "Cuatro", 0x3427, 0x3E27, 0x3E27, 0x0000 }, { 740, "ES", "CNN+", 0x3428, 0x3E28, 0x3E28, 0x0000 }, { 741, "ES", "40 Latino", 0x3429, 0x3E29, 0x3E29, 0x0000 }, { 742, "ES", "24 Horas", 0x342A, 0x3E2A, 0x3E2A, 0x0000 }, { 743, "ES", "Clan TVE", 0x342B, 0x3E2B, 0x3E2B, 0x0000 }, { 744, "ES", "Teledeporte", 0x342C, 0x3E2C, 0x3E2C, 0x0000 }, { 747, "ES", "CyL7", 0x342D, 0x0000, 0x0000, 0x0000 }, { 903, "ES", "8TV", 0x342E, 0x0000, 0x0000, 0x0000 }, { 904, "ES", "EDC2", 0x342F, 0x0000, 0x0000, 0x0000 }, { 905, "ES", "EDC3", 0x3430, 0x0000, 0x0000, 0x0000 }, { 906, "ES", "105tv", 0x3431, 0x0000, 0x0000, 0x0000 }, { 907, "ES", "CyL8", 0x3432, 0x0000, 0x0000, 0x0000 }, { 275, "ES", "TVE1", 0x3E00, 0x0000, 0x0000, 0x0000 }, { 277, "ES", "Canal+", 0xA55A, 0x0000, 0x0000, 0x0000 }, { 272, "ES", "ETB 1", 0xBA01, 0x0000, 0x0000, 0x0000 }, { 274, "ES", "TV3", 0xCA03, 0x0000, 0x0000, 0x0000 }, { 271, "ES", "C33", 0xCA33, 0x0000, 0x0000, 0x0000 }, { 276, "ES", "TVE2", 0xE100, 0x0000, 0x0000, 0x0000 }, { 502, "ES", "TVE Internacional Europa", 0xE200, 0x0000, 0x0000, 0x0000 }, { 630, "ES", "Tele5", 0xE500, 0x1FE5, 0x0000, 0x0000 }, { 745, "ES", "Tele5 Estrellas", 0xE501, 0x1FE6, 0x0000, 0x0000 }, { 746, "ES", "Tele5 Sport", 0xE502, 0x1FE7, 0x0000, 0x0000 }, { 280, "SE", "SVT Test Transmissions", 0x4600, 0x4E00, 0x3E00, 0x0000 }, { 278, "SE", "SVT 1", 0x4601, 0x4E01, 0x3E01, 0x0000 }, { 279, "SE", "SVT 2", 0x4602, 0x4E02, 0x3E02, 0x0000 }, { 281, "SE", "TV 4", 0x4640, 0x4E40, 0x3E40, 0x0000 }, { 907, "CH", "Schweizer Sportfernsehen", 0x049A, 0x0000, 0x0000, 0x049A }, { 186, "CH", "SF 1", 0x4101, 0x24C1, 0x3441, 0x04C1 }, { 187, "CH", "TSR 1", 0x4102, 0x24C2, 0x3442, 0x04C2 }, { 188, "CH", "TSI 1", 0x4103, 0x24C3, 0x3443, 0x04C3 }, { 189, "CH", "SF 2", 0x4107, 0x24C7, 0x3447, 0x04C7 }, { 190, "CH", "TSR 2", 0x4108, 0x24C8, 0x3448, 0x04C8 }, { 191, "CH", "TSI 2", 0x4109, 0x24C9, 0x3449, 0x04C9 }, { 192, "CH", "Sat Access", 0x410A, 0x24CA, 0x344A, 0x04CA }, { 506, "CH", "U1", 0x4121, 0x2421, 0x0000, 0x0495 }, { 178, "CH", "TeleZüri", 0x4122, 0x2422, 0x0000, 0x0481 }, { 908, "CH", "TF1 Suisse", 0x4123, 0x0000, 0x0000, 0x049C }, { 179, "CH", "Teleclub Abo-Fernsehen", 0x0000, 0x0000, 0x0000, 0x0482 }, { 180, "CH", "Zürich 1", 0x0000, 0x0000, 0x0000, 0x0483 }, { 181, "CH", "TeleBern", 0x0000, 0x0000, 0x0000, 0x0484 }, { 182, "CH", "Tele M1", 0x0000, 0x0000, 0x0000, 0x0485 }, { 183, "CH", "Star TV", 0x0000, 0x0000, 0x0000, 0x0486 }, { 184, "CH", "Pro 7", 0x0000, 0x0000, 0x0000, 0x0487 }, { 185, "CH", "TopTV", 0x0000, 0x0000, 0x0000, 0x0488 }, { 667, "CH", "Tele 24", 0x0000, 0x0000, 0x0000, 0x0489 }, { 668, "CH", "Kabel 1", 0x0000, 0x0000, 0x0000, 0x048A }, { 669, "CH", "TV3", 0x0000, 0x0000, 0x0000, 0x048B }, { 670, "CH", "TeleZüri 2", 0x0000, 0x0000, 0x0000, 0x048C }, { 671, "CH", "Swizz", 0x0000, 0x0000, 0x0000, 0x048D }, { 672, "CH", "Intro TV", 0x0000, 0x0000, 0x0000, 0x048E }, { 673, "CH", "Tele Tell", 0x0000, 0x0000, 0x0000, 0x048F }, { 674, "CH", "Tele Top", 0x0000, 0x0000, 0x0000, 0x0490 }, { 675, "CH", "TSO CH", 0x0000, 0x0000, 0x0000, 0x0491 }, { 676, "CH", "TVO", 0x0000, 0x0000, 0x0000, 0x0492 }, { 677, "CH", "Tele TI", 0x0000, 0x0000, 0x0000, 0x0493 }, { 678, "CH", "SHF", 0x0000, 0x0000, 0x0000, 0x0494 }, { 679, "CH", "MTV Swiss", 0x0000, 0x0000, 0x0000, 0x0496 }, { 911, "CH", "3+", 0x0000, 0x0000, 0x0000, 0x0497 }, { 912, "CH", "telebasel", 0x0000, 0x0000, 0x0000, 0x0498 }, { 913, "CH", "NICK Swiss", 0x0000, 0x0000, 0x0000, 0x0499 }, { 914, "CH", "TELE-1", 0x0000, 0x0000, 0x0000, 0x049B }, { 915, "CH", "4+", 0x0000, 0x0000, 0x0000, 0x049D }, { 666, "CH", "SFi", 0x0000, 0x0000, 0x0000, 0x04CC }, { 302, "TR", "TRT-1", 0x9001, 0x4301, 0x3301, 0x0000 }, { 303, "TR", "TRT-2", 0x9002, 0x4302, 0x3302, 0x0000 }, { 304, "TR", "TRT-3", 0x9003, 0x4303, 0x3303, 0x0000 }, { 305, "TR", "TRT-4", 0x9004, 0x4304, 0x3304, 0x0000 }, { 306, "TR", "TRT-INT", 0x9005, 0x4305, 0x3305, 0x0000 }, { 290, "TR", "AVRASYA", 0x9006, 0x4306, 0x3306, 0x0000 }, { 298, "TR", "Show TV", 0x9007, 0x0000, 0x0000, 0x0000 }, { 292, "TR", "Cine 5", 0x9008, 0x0000, 0x0000, 0x0000 }, { 299, "TR", "Super Sport", 0x9009, 0x0000, 0x0000, 0x0000 }, { 289, "TR", "ATV", 0x900A, 0x0000, 0x0000, 0x0000 }, { 297, "TR", "KANAL D", 0x900B, 0x0000, 0x0000, 0x0000 }, { 294, "TR", "EURO D", 0x900C, 0x0000, 0x0000, 0x0000 }, { 293, "TR", "EKO TV", 0x900D, 0x0000, 0x0000, 0x0000 }, { 291, "TR", "BRAVO TV", 0x900E, 0x0000, 0x0000, 0x0000 }, { 296, "TR", "GALAKSI TV", 0x900F, 0x0000, 0x0000, 0x0000 }, { 295, "TR", "FUN TV", 0x9010, 0x0000, 0x0000, 0x0000 }, { 300, "TR", "TEMPO TV", 0x9011, 0x0000, 0x0000, 0x0000 }, { 301, "TR", "TGRT", 0x9014, 0x0000, 0x0000, 0x0000 }, { 663, "TR", "Show Euro", 0x9017, 0x0000, 0x0000, 0x0000 }, { 507, "TR", "STAR TV", 0x9020, 0x0000, 0x0000, 0x0000 }, { 508, "TR", "STARMAX", 0x9021, 0x0000, 0x0000, 0x0000 }, { 509, "TR", "KANAL 6", 0x9022, 0x0000, 0x0000, 0x0000 }, { 510, "TR", "STAR 4", 0x9023, 0x0000, 0x0000, 0x0000 }, { 511, "TR", "STAR 5", 0x9024, 0x0000, 0x0000, 0x0000 }, { 512, "TR", "STAR 6", 0x9025, 0x0000, 0x0000, 0x0000 }, { 513, "TR", "STAR 7", 0x9026, 0x0000, 0x0000, 0x0000 }, { 514, "TR", "STAR 8", 0x9027, 0x0000, 0x0000, 0x0000 }, { 331, "GB", "CNN International", 0x01F2, 0x5BF1, 0x3B71, 0x0000 }, { 346, "GB", "MERIDIAN", 0x10E4, 0x2C34, 0x3C34, 0x0000 }, { 326, "GB", "CHANNEL 5 (2)", 0x1609, 0x2C09, 0x3C09, 0x0000 }, { 374, "GB", "WESTCOUNTRY TV", 0x25D0, 0x2C30, 0x3C30, 0x0000 }, { 327, "GB", "CHANNEL 5 (3)", 0x28EB, 0x2C2B, 0x3C2B, 0x0000 }, { 323, "GB", "CENTRAL TV", 0x2F27, 0x2C37, 0x3C37, 0x0000 }, { 529, "GB", "National Geographic Channel", 0x320B, 0x0000, 0x0000, 0x0000 }, { 366, "GB", "SSVC", 0x37E5, 0x2C25, 0x3C25, 0x0000 }, { 369, "GB", "UK GOLD", 0x4401, 0x5BFA, 0x3B7A, 0x0000 }, { 370, "GB", "UK LIVING", 0x4402, 0x2C01, 0x3C01, 0x0000 }, { 375, "GB", "WIRE TV", 0x4403, 0x2C3C, 0x3C3C, 0x0000 }, { 330, "GB", "CHILDREN'S CHANNEL", 0x4404, 0x5BF0, 0x3B70, 0x0000 }, { 320, "GB", "BRAVO", 0x4405, 0x5BEF, 0x3B6F, 0x0000 }, { 343, "GB", "LEARNING CHANNEL", 0x4406, 0x5BF7, 0x3B77, 0x0000 }, { 332, "GB", "DISCOVERY", 0x4407, 0x5BF2, 0x3B72, 0x0000 }, { 334, "GB", "FAMILY CHANNEL", 0x4408, 0x5BF3, 0x3B73, 0x0000 }, { 344, "GB", "Live TV", 0x4409, 0x5BF8, 0x3B78, 0x0000 }, { 515, "GB", "Discovery Home & Leisure", 0x4420, 0x0000, 0x0000, 0x0000 }, { 909, "GB", "Animal Planet", 0x4421, 0x0000, 0x0000, 0x0000 }, { 315, "GB", "BBC2", 0x4440, 0x2C40, 0x3C40, 0x0000 }, { 312, "GB", "BBC1 NI", 0x4441, 0x2C41, 0x3C41, 0x0000 }, { 318, "GB", "BBC2 Wales", 0x4442, 0x2C42, 0x3C42, 0x0000 }, { 317, "GB", "BBC2 Scotland", 0x4444, 0x2C44, 0x3C44, 0x0000 }, { 310, "GB", "BBC World", 0x4457, 0x2C57, 0x3C57, 0x0000 }, { 309, "GB", "BBC Prime", 0x4468, 0x2C68, 0x3C68, 0x0000 }, { 308, "GB", "BBC News 24", 0x4469, 0x2C69, 0x3C69, 0x0000 }, { 313, "GB", "BBC1 Scotland", 0x447B, 0x2C7B, 0x3C7B, 0x0000 }, { 314, "GB", "BBC1 Wales", 0x447D, 0x2C7D, 0x3C7D, 0x0000 }, { 316, "GB", "BBC2 NI", 0x447E, 0x2C7E, 0x3C7E, 0x0000 }, { 311, "GB", "BBC1", 0x447F, 0x2C7F, 0x3C7F, 0x0000 }, { 367, "GB", "TNT / Cartoon Network", 0x44C1, 0x0000, 0x0000, 0x0000 }, { 333, "GB", "DISNEY CHANNEL UK", 0x44D1, 0x5BCC, 0x3B4C, 0x0000 }, { 348, "GB", "MTV", 0x4D54, 0x2C14, 0x3C14, 0x0000 }, { 372, "GB", "VH-1", 0x4D58, 0x2C20, 0x3C20, 0x0000 }, { 373, "GB", "VH-1", 0x4D59, 0x2C21, 0x3C21, 0x0000 }, { 337, "GB", "GRANADA PLUS", 0x4D5A, 0x5BF4, 0x3B74, 0x0000 }, { 338, "GB", "GRANADA Timeshare", 0x4D5B, 0x5BF5, 0x3B75, 0x0000 }, { 341, "GB", "HTV", 0x5AAF, 0x2C3F, 0x3C3F, 0x0000 }, { 352, "GB", "QVC UK", 0x5C44, 0x0000, 0x0000, 0x0000 }, { 322, "GB", "CARLTON TV", 0x82DD, 0x2C1D, 0x3C1D, 0x0000 }, { 321, "GB", "CARLTON SELECT", 0x82E1, 0x2C05, 0x3C05, 0x0000 }, { 371, "GB", "ULSTER TV", 0x833B, 0x2C3D, 0x3C3D, 0x0000 }, { 345, "GB", "LWT", 0x884B, 0x2C0B, 0x3C0B, 0x0000 }, { 349, "GB", "NBC Europe", 0x8E71, 0x2C31, 0x3C31, 0x0E86 }, { 517, "GB", "CNBC Europe", 0x8E72, 0x2C35, 0x3C35, 0x0000 }, { 325, "GB", "CHANNEL 5 (1)", 0x9602, 0x2C02, 0x3C02, 0x0000 }, { 350, "GB", "Nickelodeon UK", 0xA460, 0x0000, 0x0000, 0x0000 }, { 351, "GB", "Paramount Comedy Channel UK", 0xA465, 0x0000, 0x0000, 0x0000 }, { 368, "GB", "TYNE TEES TV", 0xA82C, 0x2C2C, 0x3C2C, 0x0000 }, { 339, "GB", "GRANADA TV", 0xADD8, 0x2C18, 0x3C18, 0x0000 }, { 335, "GB", "GMTV", 0xADDC, 0x5BD2, 0x3B52, 0x0000 }, { 354, "GB", "S4C", 0xB4C7, 0x2C07, 0x3C07, 0x0000 }, { 319, "GB", "BORDER TV", 0xB7F7, 0x2C27, 0x3C27, 0x0000 }, { 328, "GB", "CHANNEL 5 (4)", 0xC47B, 0x2C3B, 0x3C3B, 0x0000 }, { 516, "GB", "FilmFour", 0xC4F4, 0x42F4, 0x3274, 0x0000 }, { 342, "GB", "ITV NETWORK", 0xC8DE, 0x2C1E, 0x3C1E, 0x0000 }, { 336, "GB", "GRAMPIAN TV", 0xF33A, 0x2C3A, 0x3C3A, 0x0000 }, { 356, "GB", "SCOTTISH TV", 0xF9D2, 0x2C12, 0x3C12, 0x0000 }, { 376, "GB", "YORKSHIRE TV", 0xFA2C, 0x2C2D, 0x3C2D, 0x0000 }, { 307, "GB", "ANGLIA TV", 0xFB9C, 0x2C1C, 0x3C1C, 0x0000 }, { 324, "GB", "CHANNEL 4", 0xFCD1, 0x2C11, 0x3C11, 0x0000 }, { 329, "GB", "CHANNEL TV", 0xFCE4, 0x2C24, 0x3C24, 0x0000 }, { 353, "GB", "RACING CHANNEL", 0xFCF3, 0x2C13, 0x3C13, 0x0000 }, { 340, "GB", "HISTORY CHANNEL", 0xFCF4, 0x5BF6, 0x3B76, 0x0000 }, { 355, "GB", "SCI FI CHANNEL", 0xFCF5, 0x2C15, 0x3C15, 0x0000 }, { 364, "GB", "SKY TRAVEL", 0xFCF6, 0x5BF9, 0x3B79, 0x0000 }, { 361, "GB", "SKY SOAPS", 0xFCF7, 0x2C17, 0x3C17, 0x0000 }, { 363, "GB", "SKY SPORTS 2", 0xFCF8, 0x2C08, 0x3C08, 0x0000 }, { 357, "GB", "SKY GOLD", 0xFCF9, 0x2C19, 0x3C19, 0x0000 }, { 362, "GB", "SKY SPORTS", 0xFCFA, 0x2C1A, 0x3C1A, 0x0000 }, { 347, "GB", "MOVIE CHANNEL", 0xFCFB, 0x2C1B, 0x3C1B, 0x0000 }, { 358, "GB", "SKY MOVIES PLUS", 0xFCFC, 0x2C0C, 0x3C0C, 0x0000 }, { 359, "GB", "SKY NEWS", 0xFCFD, 0x2C0D, 0x3C0D, 0x0000 }, { 360, "GB", "SKY ONE", 0xFCFE, 0x2C0E, 0x3C0E, 0x0000 }, { 365, "GB", "SKY TWO", 0xFCFF, 0x2C0F, 0x3C0F, 0x0000 }, { 518, "UA", "Studio 1+1", 0x7700, 0x0000, 0x0000, 0x07C0 }, { 519, "UA", "M1", 0x7705, 0x0000, 0x0000, 0x07C5 }, { 520, "UA", "ICTV", 0x7707, 0x0000, 0x0000, 0x0000 }, { 521, "UA", "Novy Kanal", 0x7708, 0x0000, 0x0000, 0x07C8 }, { 684, "AA", "Zee TV", 0x04F9, 0x0000, 0x0000, 0x0000 }, { 685, "AA", "VGB V", 0xFC02, 0x1234, 0x0000, 0x0000 }, { 0, "", 0, 0, 0, 0, 0 } }; zvbi-0.2.44/src/network-table.pl000077500000000000000000000022571476363111200164720ustar00rootroot00000000000000#! /usr/bin/perl -w # $Id: network-table.pl,v 1.2 2005-10-07 14:53:20 mschimek Exp $ use strict; use XML::Simple; # http://search.cpan.org/search?query=XML::Simple use Data::Dumper; my $xml = XMLin ("-", ForceContent => 1, ForceArray => ["network"]); # print Dumper ($xml); print "/* Generated from http://zapping.sf.net/zvbi-0.3/networks.xml */ const struct vbi_cni_entry vbi_cni_table[] = { "; for (@{$xml->{country}}) { my $crecord = $_; my $cc = $_->{"country-code"}->{content}; for (@{$_->{"network"}}) { my $nrecord = $_; if (!(defined ($nrecord->{"cni-8301"}->{content}) || defined ($nrecord->{"cni-8302"}->{content}) || defined ($nrecord->{"cni-pdc-b"}->{content}) || defined ($nrecord->{"cni-vps"}->{content}))) { next; } print "\t{ ", substr ($nrecord->{"id"}, 1), ", \"", $cc, "\", \"", $nrecord->{"name"}->{content}, "\""; for (qw/cni-8301 cni-8302 cni-pdc-b cni-vps/) { if (defined ($nrecord->{$_}->{content})) { my $value = hex ($nrecord->{$_}->{content}); printf ", 0x%04X", $value; } else { print ", 0x0000"; } } print " },\n"; } } print "\t{ 0, \"\", 0, 0, 0, 0, 0 }\n};\n\n"; zvbi-0.2.44/src/network.h000066400000000000000000000053221476363111200152120ustar00rootroot00000000000000/* * libzvbi - Network identification * * Copyright (C) 2004-2006 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: network.h,v 1.2 2009-03-04 21:42:09 mschimek Exp $ */ #ifndef __ZVBI_NETWORK_H__ #define __ZVBI_NETWORK_H__ VBI_BEGIN_DECLS /* Public */ /** */ typedef enum { VBI_CNI_TYPE_NONE, VBI_CNI_TYPE_UNKNOWN = VBI_CNI_TYPE_NONE, /** * Video Programming System (VPS) format, a PDC CNI, for * example from vbi_decode_vps_cni(). Note VPS transmits only * the 4 lsb of the country code (0xcnn). * * Example ZDF: 0xDC2. */ VBI_CNI_TYPE_VPS, /** * Teletext packet 8/30 format 1, for example from * vbi_decode_teletext_8301_cni(). The country code is stored * in the MSB, the network code in the LSB (0xccnn). Note * these CNIs may use different country and network codes than * the PDC CNIs. * * Example BBC 1: 0x447F, ZDF: 0x4902. */ VBI_CNI_TYPE_8301, /** * Teletext packet 8/30 format 2 (PDC), for example from * vbi_decode_teletext_8302_cni(). The country code is stored * in the MSB, the network code in the LSB (0xccnn). * * Example BBC 1: 0x2C7F, ZDF: 0x1DC2. */ VBI_CNI_TYPE_8302, /** * PDC Preselection method "A" format encoded on Teletext * pages. This number consists of 2 hex digits for the * country code and 3 bcd digits for the network code. * * Example ZDF: 0x1D102. (German PDC-A network codes 101 ... 163 * correspond to 8/30/2 codes 0xC1 ... 0xFF. Other countries may * use different schemes.) */ VBI_CNI_TYPE_PDC_A, /** * PDC Preselection method "B" format encoded in Teletext * packet X/26 local enhancement data (0x3cnn). X/26 transmits * only the 4 lsb of the country code and the 7 lsb of * the network code. To avoid ambiguity these CNIs may not * use the same country and network codes as other PDC CNIs. * * Example BBC 1: 0x3C7F. */ VBI_CNI_TYPE_PDC_B } vbi_cni_type; /* Private */ VBI_END_DECLS #endif /* __ZVBI_NETWORK_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/packet-830.c000066400000000000000000000200571476363111200152750ustar00rootroot00000000000000/* * libzvbi - Teletext packet decoder, packet 8/30 * * Copyright (C) 2003, 2004 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* $Id: packet-830.c,v 1.4 2013-07-10 11:37:18 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "misc.h" #include "bcd.h" /* vbi_lbcd2bin() */ #include "hamm.h" /* vbi_rev16p(), vbi_iham8() */ #include "packet-830.h" /** * @addtogroup Packet830 Teletext Packet 8/30 Decoder * @ingroup LowDec * @brief Functions to decode Teletext packet 8/30 (ETS 300 706). * * Teletext pages are transmitted in packets numbered 0 to 31. Packet * 0 to 25 contain the text of the page, packet 26 to 29 additional * information like Fastext links. Packet 30 and 31 are reserved for * data transmissions unrelated to any page. Since each packet * contains a magazine number 1 to 8 (the first digit of the Teletext * page number) 16 logical channels can be distinguished. Packet 30 * with magazine number 8 carries a Country and Network Identifier, * and either a local time (format 1) or PDC label (format 2). * * These are low level functions. See test/decode.c for a usage * example. * * The @a vbi_decoder module can decode a full Teletext signal and * provide the information transmitted in packet 8/30 as @c * VBI_EVENT_NETWORK, @c VBI_EVENT_NETWORK_ID, @c VBI_EVENT_LOCAL_TIME * and @c VBI_EVENT_PROG_ID. See examples/network.c and * examples/pdc1.c. */ /* Resources: http://pdc.ro.nu/jd-code.html */ /** * @param cni CNI of type VBI_CNI_TYPE_8301 will be stored here. * @param buffer Teletext packet as defined for @c VBI_SLICED_TELETEXT_B, * i.e. 42 bytes without clock run-in and framing code. * * Decodes a Teletext packet 8/30 format 1 according to ETS 300 706 * section 9.8.1, returning the contained 16 bit Country and Network * Identifier in @a *cni. * * @returns * Always @c TRUE, no error checking possible. It may be prudent to * wait for a second transmission of the received CNI to ensure * correct reception. * * @since 0.2.34 */ vbi_bool vbi_decode_teletext_8301_cni (unsigned int * cni, const uint8_t buffer[42]) { assert (NULL != cni); assert (NULL != buffer); *cni = vbi_rev16p (buffer + 9); return TRUE; } /* Should use vbi_bcd2bin() but that function is limited to four BCD digits in libzvbi 0.2 and cannot be changed for compatibility reasons. */ static int bcd2bin (int bcd) { unsigned int f; int bin = 0; for (f = 1; f < 100000; f = f * 10) { bin += (bcd & 15) * f; bcd >>= 4; } return bin; } /** * @param time The current time in seconds since 1970-01-01 00:00 UTC * will be stored here. * @param seconds_east The offset of the local time of the intended * audience of the program in seconds east of UTC will be stored here, * including a daylight-saving time offset if daylight-saving is * currently in effect in that time zone. To get the local time of the * intended audience add @a seconds_east to @a time. * @param buffer Teletext packet as defined for @c VBI_SLICED_TELETEXT_B, * i.e. 42 bytes without clock run-in and framing code. * * Decodes a Teletext packet 8/30 format 1 according to ETS 300 706 * section 9.8.1, returning the current time in the UTC time zone and * the time zone of the intended audience of the program. * * @returns * On error the function returns @c FALSE: * - The buffer contains uncorrectable errors or * - The time is not representable as a time_t. * In these cases @a *time and @a *seconds_east remain unchanged. * * @since 0.2.34 */ vbi_bool vbi_decode_teletext_8301_local_time (time_t * time, int * seconds_east, const uint8_t buffer[42]) { int64_t mjd; int64_t utc; int64_t t; int bcd; int field; int offset; assert (NULL != time); assert (NULL != seconds_east); assert (NULL != buffer); /* Modified Julian Date. */ bcd = (+ ((buffer[12] & 15) << 16) + (buffer[13] << 8) + buffer[14] - 0x11111); if (unlikely (!vbi_is_bcd (bcd))) { errno = 0; return FALSE; } mjd = bcd2bin (bcd); /* UTC time. */ bcd = (+ (buffer[15] << 16) + (buffer[16] << 8) + buffer[17] - 0x111111); if (unlikely (!vbi_is_bcd (bcd))) { errno = 0; return FALSE; } utc = (bcd & 15) + ((bcd >> 4) & 15) * 10; if (unlikely (utc > 60)) { errno = 0; return FALSE; } field = ((bcd >> 8) & 15) + ((bcd >> 12) & 15) * 10; if (unlikely (field >= 60)) { errno = 0; return FALSE; } utc += field * 60; field = ((bcd >> 16) & 15) + (bcd >> 20) * 10; if (unlikely (field >= 24)) { errno = 0; return FALSE; } utc += field * 3600; /* Local time offset in seconds east of UTC. */ offset = (buffer[11] & 0x3E) * (15 * 60); if (buffer[11] & 0x40) offset = -offset; t = (mjd - 40587) * 86400 + utc; if (t < TIME_MIN || t > TIME_MAX) { errno = EOVERFLOW; return FALSE; } *time = t; *seconds_east = offset; return TRUE; } /** * @param cni CNI of type VBI_CNI_TYPE_8302 will be stored here. * @param buffer Teletext packet as defined for @c VBI_SLICED_TELETEXT_B, * i.e. 42 bytes without clock run-in and framing code. * * Decodes a Teletext packet 8/30 format 2 according to ETS 300 706 * section 9.8.2, returning the contained 16 bit Country and Network * Identifier in @a *cni. * * @returns * @c FALSE if the buffer contains uncorrectable errors. In this case * @a *cni remains unchanged. * * @since 0.2.34 */ vbi_bool vbi_decode_teletext_8302_cni (unsigned int * cni, const uint8_t buffer[42]) { int b[13]; assert (NULL != cni); assert (NULL != buffer); b[ 7] = vbi_unham16p (buffer + 10); b[ 8] = vbi_unham16p (buffer + 12); b[10] = vbi_unham16p (buffer + 16); b[11] = vbi_unham16p (buffer + 18); if (unlikely ((b[7] | b[8] | b[10] | b[11]) < 0)) return FALSE; b[ 7] = vbi_rev8 (b[ 7]); b[ 8] = vbi_rev8 (b[ 8]); b[10] = vbi_rev8 (b[10]); b[11] = vbi_rev8 (b[11]); *cni = (+ ((b[ 7] & 0x0F) << 12) + ((b[10] & 0x03) << 10) + ((b[11] & 0xC0) << 2) + (b[ 8] & 0xC0) + (b[11] & 0x3F)); return TRUE; } /** * @param pid PDC program ID will be stored here. * @param buffer Teletext packet as defined for @c VBI_SLICED_TELETEXT_B, * i.e. 42 bytes without clock run-in and framing code. * * Decodes a Teletext packet 8/30 format 2 according to ETS 300 231, * and stores the contained PDC recording-control data in @a *pid. * * @returns * @c FALSE if the buffer contains uncorrectable errors or invalid * data. In this case @a *pid remains unchanged. * * @since 0.2.34 */ vbi_bool vbi_decode_teletext_8302_pdc (vbi_program_id * pid, const uint8_t buffer[42]) { uint8_t b[13]; unsigned int i; int error; assert (NULL != pid); assert (NULL != buffer); error = vbi_unham8 (buffer[9]); b[ 6] = vbi_rev8 (error) >> 4; for (i = 7; i <= 12; ++i) { int t; t = vbi_unham16p (buffer + i * 2 - 4); error |= t; b[i] = vbi_rev8 (t); } if (unlikely (error < 0)) return FALSE; CLEAR (*pid); pid->channel = VBI_PID_CHANNEL_LCI_0 + ((b[6] >> 2) & 3); pid->cni_type = VBI_CNI_TYPE_8302; pid->cni = (+ ((b[ 7] & 0x0F) << 12) + ((b[10] & 0x03) << 10) + ((b[11] & 0xC0) << 2) + (b[ 8] & 0xC0) + (b[11] & 0x3F)); pid->pil = (+ ((b[ 8] & 0x3F) << 14) + (b[ 9] << 6) + (b[10] >> 2)); pid->luf = (b[6] >> 1) & 1; pid->mi = (b[7] >> 5) & 1; pid->prf = (b[6] >> 0) & 1; pid->pcs_audio = (b[7] >> 6) & 3; pid->pty = b[12]; return TRUE; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/packet-830.h000066400000000000000000000036121476363111200153000ustar00rootroot00000000000000/* * libzvbi - Teletext packet decoder, packet 8/30 * * Copyright (C) 2003, 2004 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* $Id: packet-830.h,v 1.3 2009-03-04 21:48:40 mschimek Exp $ */ #ifndef __ZVBI_PACKET_830_H__ #define __ZVBI_PACKET_830_H__ #include /* uint8_t */ #include /* time_t */ #include "macros.h" #include "pdc.h" /* vbi_program_id */ VBI_BEGIN_DECLS /* Public */ /** * @addtogroup Packet830 * @{ */ extern vbi_bool vbi_decode_teletext_8301_cni (unsigned int * cni, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_decode_teletext_8301_local_time (time_t * utc_time, int * seconds_east, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2, 3)) #endif ; extern vbi_bool vbi_decode_teletext_8302_cni (unsigned int * cni, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_decode_teletext_8302_pdc (vbi_program_id * pid, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; /** @} */ /* Private */ VBI_END_DECLS #endif /* __ZVBI_PACKET_830_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/packet.c000066400000000000000000001772521476363111200147770ustar00rootroot00000000000000/* * libzvbi -- Teletext decoder frontend * * Copyright (C) 2000, 2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: packet.c,v 1.34 2014-02-18 16:56:03 mschimek Exp $ */ #include "site_def.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #include "misc.h" #include "hamm.h" #include "lang.h" #include "export.h" #include "tables.h" #include "vps.h" #include "vbi.h" #include "cache-priv.h" #include "packet-830.h" #ifndef FPC # define FPC 0 #endif static vbi_bool convert_drcs(cache_page *vtp, uint8_t *raw); _vbi_inline void dump_page_link (struct ttx_page_link link) { printf ("T%x %3x/%04x\n", link.function, link.pgno, link.subno); } static void dump_raw(cache_page *vtp, vbi_bool unham) { int i, j; printf("Page %03x.%04x\n", vtp->pgno, vtp->subno); for (j = 0; j < 25; j++) { if (unham) for (i = 0; i < 40; i++) printf("%01x ", vbi_unham8 (vtp->data.lop.raw[j][i]) & 0xF); else for (i = 0; i < 40; i++) printf("%02x ", vtp->data.lop.raw[j][i]); for (i = 0; i < 40; i++) putchar(_vbi_to_ascii (vtp->data.lop.raw[j][i])); putchar('\n'); } } static void dump_extension(const struct ttx_extension *ext) { int i; printf("Extension:\ndesignations %08x\n", ext->designations); printf("char set primary %d secondary %d\n", ext->charset_code[0], ext->charset_code[1]); printf("default screen col %d row col %d\n", ext->def_screen_color, ext->def_row_color); printf("bbg subst %d color table remapping %d, %d\n", ext->fallback.black_bg_substitution, ext->foreground_clut, ext->background_clut); printf("panel left %d right %d\n", ext->fallback.left_panel_columns, ext->fallback.left_panel_columns); printf("color map (bgr):\n"); for (i = 0; i < 40; i++) { printf("%08x, ", ext->color_map[i]); if ((i % 8) == 7) printf("\n"); } printf("dclut4 global: "); for (i = 0; i <= 3; i++) printf("%2d ", ext->drcs_clut[i + 2]); printf("\ndclut4 normal: "); for (i = 0; i <= 3; i++) printf("%2d ", ext->drcs_clut[i + 6]); printf("\ndclut16 global: "); for (i = 0; i <= 15; i++) printf("%2d ", ext->drcs_clut[i + 10]); printf("\ndclut16 normal: "); for (i = 0; i <= 15; i++) printf("%2d ", ext->drcs_clut[i + 26]); printf("\n\n"); } static void dump_drcs(cache_page *vtp) { int i, j, k; uint8_t *p = vtp->data.drcs.chars[0]; printf("\nDRCS page %03x/%04x\n", vtp->pgno, vtp->subno); for (i = 0; i < 48; i++) { printf("DRC #%d mode %02x\n", i, vtp->data.drcs.mode[i]); for (j = 0; j < 10; p += 6, j++) { for (k = 0; k < 6; k++) printf("%x%x", p[k] & 15, p[k] >> 4); putchar('\n'); } } } #if 0 static void dump_page_info(struct teletext *vt) { int i, j; for (i = 0; i < 0x800; i += 16) { printf("%03x: ", i + 0x100); for (j = 0; j < 16; j++) printf("%02x:%02x:%04x ", vt->page_info[i + j].code & 0xFF, vt->page_info[i + j].language & 0xFF, vt->page_info[i + j].subcode & 0xFFFF); putchar('\n'); } putchar('\n'); } #endif _vbi_inline vbi_bool unham_page_link(struct ttx_page_link *p, const uint8_t *raw, int magazine) { int b1, b2, b3, err, m; err = b1 = vbi_unham16p (raw + 0); err |= b2 = vbi_unham16p (raw + 2); err |= b3 = vbi_unham16p (raw + 4); if (err < 0) return FALSE; m = ((b3 >> 5) & 6) + (b2 >> 7); p->pgno = ((magazine ^ m) ? : 8) * 256 + b1; p->subno = (b3 * 256 + b2) & 0x3f7f; return TRUE; } static inline vbi_bool parse_mot(struct ttx_magazine *mag, uint8_t *raw, int packet) { int err, i, j; switch (packet) { case 1 ... 8: { int index = (packet - 1) << 5; int n0, n1; for (i = 0; i < 20; index++, i++) { if (i == 10) index += 6; n0 = vbi_unham8 (*raw++); n1 = vbi_unham8 (*raw++); if ((n0 | n1) < 0) continue; mag->pop_lut[index] = n0 & 7; mag->drcs_lut[index] = n1 & 7; } return TRUE; } case 9 ... 14: { int index = (packet - 9) * 0x30 + 10; for (i = 0; i < 20; index++, i++) { int n0, n1; if (i == 6 || i == 12) { if (index == 0x100) break; else index += 10; } n0 = vbi_unham8 (*raw++); n1 = vbi_unham8 (*raw++); if ((n0 | n1) < 0) continue; mag->pop_lut[index] = n0 & 7; mag->drcs_lut[index] = n1 & 7; } return TRUE; } case 15 ... 18: /* not used */ return TRUE; case 22 ... 23: /* level 3.5 pops */ packet--; case 19 ... 20: /* level 2.5 pops */ { struct ttx_pop_link *pop; pop = &mag->pop_link[0][(packet - 19) * 4]; for (i = 0; i < 4; raw += 10, pop++, i++) { int n[10]; for (err = j = 0; j < 10; j++) err |= n[j] = vbi_unham8 (raw[j]); if (err < 0) /* XXX unused bytes poss. not hammed (^ N3) */ continue; pop->pgno = (((n[0] & 7) ? : 8) << 8) + (n[1] << 4) + n[2]; /* n[3] number of subpages ignored */ if (n[4] & 1) memset(&pop->fallback, 0, sizeof(pop->fallback)); else { int x = (n[4] >> 1) & 3; pop->fallback.black_bg_substitution = n[4] >> 3; /* x: 0/0, 16/0, 0/16, 8/8 */ pop->fallback.left_panel_columns = "\00\20\00\10"[x]; pop->fallback.right_panel_columns = "\00\00\20\10"[x]; } pop->default_obj[0].type = n[5] & 3; pop->default_obj[0].address = (n[7] << 4) + n[6]; pop->default_obj[1].type = n[5] >> 2; pop->default_obj[1].address = (n[9] << 4) + n[8]; } return TRUE; } case 21: /* level 2.5 drcs */ case 24: /* level 3.5 drcs */ { int index = (packet == 21) ? 0 : 8; int n[4]; for (i = 0; i < 8; raw += 4, index++, i++) { for (err = j = 0; j < 4; j++) err |= n[j] = vbi_unham8 (raw[j]); if (err < 0) continue; mag->drcs_link[0][index] = (((n[0] & 7) ? : 8) << 8) + (n[1] << 4) + n[2]; /* n[3] number of subpages ignored */ } return TRUE; } } return TRUE; } static vbi_bool parse_pop(cache_page *vtp, uint8_t *raw, int packet) { int designation, triplet[13]; struct ttx_triplet *trip; int i; if ((designation = vbi_unham8 (raw[0])) < 0) return FALSE; for (raw++, i = 0; i < 13; raw += 3, i++) triplet[i] = vbi_unham24p (raw); if (packet == 26) packet += designation; switch (packet) { case 1 ... 2: if (!(designation & 1)) return FALSE; /* fixed usage */ case 3 ... 4: if (designation & 1) { int index = (packet - 1) * 26; for (index += 2, i = 1; i < 13; index += 2, i++) if (triplet[i] >= 0) { vtp->data.pop.pointer[index + 0] = triplet[i] & 0x1FF; vtp->data.pop.pointer[index + 1] = triplet[i] >> 9; } return TRUE; } /* fall through */ case 5 ... 42: trip = vtp->data.pop.triplet + (packet - 3) * 13; for (i = 0; i < 13; trip++, i++) if (triplet[i] >= 0) { trip->address = (triplet[i] >> 0) & 0x3F; trip->mode = (triplet[i] >> 6) & 0x1F; trip->data = (triplet[i] >> 11); } return TRUE; } return FALSE; } static unsigned int expand[64]; static void init_expand(void) { int i, j, n; for (i = 0; i < 64; i++) { for (n = j = 0; j < 6; j++) if (i & (0x20 >> j)) n |= 1 << (j * 4); expand[i] = n; } } static vbi_bool convert_drcs(cache_page *vtp, uint8_t *raw) { uint8_t *p, *d; int i, j, q; p = raw; vtp->data.drcs.invalid = 0; for (i = 0; i < 24; p += 40, i++) if (vtp->lop_packets & (2 << i)) { for (j = 0; j < 20; j++) if (vbi_unpar8 (p[j]) < 0x40) { vtp->data.drcs.invalid |= 1ULL << (i * 2); break; } for (j = 20; j < 40; j++) if (vbi_unpar8 (p[j]) < 0x40) { vtp->data.drcs.invalid |= 1ULL << (i * 2 + 1); break; } } else { vtp->data.drcs.invalid |= 3ULL << (i * 2); } p = raw; d = vtp->data.drcs.chars[0]; for (i = 0; i < 48; i++) { switch (vtp->data.drcs.mode[i]) { case DRCS_MODE_12_10_1: for (j = 0; j < 20; d += 3, j++) { d[0] = q = expand[p[j] & 0x3F]; d[1] = q >> 8; d[2] = q >> 16; } p += 20; break; case DRCS_MODE_12_10_2: if (vtp->data.drcs.invalid & (3ULL << i)) { vtp->data.drcs.invalid |= (3ULL << i); d += 60; } else for (j = 0; j < 20; d += 3, j++) { q = expand[p[j + 0] & 0x3F] + expand[p[j + 20] & 0x3F] * 2; d[0] = q; d[1] = q >> 8; d[2] = q >> 16; } p += 40; d += 60; i += 1; break; case DRCS_MODE_12_10_4: if (vtp->data.drcs.invalid & (15ULL << i)) { vtp->data.drcs.invalid |= (15ULL << i); d += 60; } else for (j = 0; j < 20; d += 3, j++) { q = expand[p[j + 0] & 0x3F] + expand[p[j + 20] & 0x3F] * 2 + expand[p[j + 40] & 0x3F] * 4 + expand[p[j + 60] & 0x3F] * 8; d[0] = q; d[1] = q >> 8; d[2] = q >> 16; } p += 80; d += 180; i += 3; break; case DRCS_MODE_6_5_4: for (j = 0; j < 20; p += 4, d += 6, j++) { q = expand[p[0] & 0x3F] + expand[p[1] & 0x3F] * 2 + expand[p[2] & 0x3F] * 4 + expand[p[3] & 0x3F] * 8; d[0] = (q & 15) * 0x11; d[1] = ((q >> 4) & 15) * 0x11; d[2] = ((q >> 8) & 15) * 0x11; d[3] = ((q >> 12) & 15) * 0x11; d[4] = ((q >> 16) & 15) * 0x11; d[5] = (q >> 20) * 0x11; } break; default: vtp->data.drcs.invalid |= (1ULL << i); p += 20; d += 60; break; } } if (0) dump_drcs(vtp); return TRUE; } static int page_language(struct teletext *vt, const cache_network *cn, const cache_page *vtp, int pgno, int national) { const struct ttx_magazine *mag; const struct ttx_extension *ext; int charset_code; int lang = -1; /***/ if (vtp) { if (vtp->function != PAGE_FUNCTION_LOP) return lang; pgno = vtp->pgno; national = vtp->national; } if (vt->max_level <= VBI_WST_LEVEL_1p5) mag = &vt->default_magazine; else mag = cache_network_const_magazine (cn, pgno); ext = (NULL != vtp && 0 != vtp->x28_designations) ? &vtp->data.ext_lop.ext : &mag->extension; charset_code = ext->charset_code[0]; if (VALID_CHARACTER_SET(charset_code)) lang = charset_code; charset_code = (charset_code & ~7) + national; if (VALID_CHARACTER_SET(charset_code)) lang = charset_code; return lang; } static vbi_bool parse_mip_page(vbi_decoder *vbi, cache_page *vtp, int pgno, int code, int *subp_index) { uint8_t *raw; int subc, old_code, old_subc; struct ttx_page_stat *ps; if (code < 0) return FALSE; ps = cache_network_page_stat (vbi->cn, pgno); switch (code) { case 0x52 ... 0x6F: /* reserved */ case 0xD2 ... 0xDF: /* reserved */ case 0xFA ... 0xFC: /* reserved */ case 0xFF: /* reserved, we use it as 'unknown' flag */ return TRUE; case 0x02 ... 0x4F: case 0x82 ... 0xCF: subc = code & 0x7F; code = (code >= 0x80) ? VBI_PROGR_SCHEDULE : VBI_NORMAL_PAGE; break; case 0x70 ... 0x77: { cache_page *cp; code = VBI_SUBTITLE_PAGE; subc = 0; /* cp may be NULL. */ cp = _vbi_cache_get_page (vbi->ca, vbi->cn, pgno, /* subno */ 0, /* subno_mask */ 0); ps->charset_code = page_language (&vbi->vt, vbi->cn, cp, pgno, code & 7); cache_page_unref (cp); break; } case 0x50 ... 0x51: /* normal */ case 0xD0 ... 0xD1: /* program */ case 0xE0 ... 0xE1: /* data */ case 0x7B: /* current program */ case 0xF8: /* keyword search list */ if (*subp_index > 10 * 13) return FALSE; raw = &vtp->data.unknown.raw[*subp_index / 13 + 15] [(*subp_index % 13) * 3 + 1]; (*subp_index)++; if ((subc = vbi_unham16p (raw) | (vbi_unham8 (raw[2]) << 8)) < 0) return FALSE; if ((code & 15) == 1) subc += 1 << 12; else if (subc < 2) return FALSE; code = (code == 0xF8) ? VBI_KEYWORD_SEARCH_LIST : (code == 0x7B) ? VBI_CURRENT_PROGR : (code >= 0xE0) ? VBI_CA_DATA_BROADCAST : (code >= 0xD0) ? VBI_PROGR_SCHEDULE : VBI_NORMAL_PAGE; break; default: code = code; subc = 0; break; } old_code = ps->page_type; old_subc = ps->subcode; /* * When we got incorrect numbers and proved otherwise by * actually receiving the page... */ if (old_code == VBI_UNKNOWN_PAGE || old_code == VBI_SUBTITLE_PAGE || code != VBI_NO_PAGE || code == VBI_SUBTITLE_PAGE) ps->page_type = code; if (old_code == VBI_UNKNOWN_PAGE || subc > old_subc) ps->subcode = subc; return TRUE; } static vbi_bool parse_mip(vbi_decoder *vbi, cache_page *vtp) { int packet, pgno, i, spi = 0; if (0) dump_raw(vtp, TRUE); for (packet = 1, pgno = vtp->pgno & 0xF00; packet <= 8; packet++, pgno += 0x20) if (vtp->lop_packets & (1 << packet)) { uint8_t *raw = vtp->data.unknown.raw[packet]; for (i = 0x00; i <= 0x09; raw += 2, i++) if (!parse_mip_page(vbi, vtp, pgno + i, vbi_unham16p (raw), &spi)) return FALSE; for (i = 0x10; i <= 0x19; raw += 2, i++) if (!parse_mip_page(vbi, vtp, pgno + i, vbi_unham16p (raw), &spi)) return FALSE; } for (packet = 9, pgno = vtp->pgno & 0xF00; packet <= 14; packet++, pgno += 0x30) if (vtp->lop_packets & (1 << packet)) { uint8_t *raw = vtp->data.unknown.raw[packet]; for (i = 0x0A; i <= 0x0F; raw += 2, i++) if (!parse_mip_page(vbi, vtp, pgno + i, vbi_unham16p (raw), &spi)) return FALSE; if (packet == 14) /* 0xFA ... 0xFF */ break; for (i = 0x1A; i <= 0x1F; raw += 2, i++) if (!parse_mip_page(vbi, vtp, pgno + i, vbi_unham16p (raw), &spi)) return FALSE; for (i = 0x2A; i <= 0x2F; raw += 2, i++) if (!parse_mip_page(vbi, vtp, pgno + i, vbi_unham16p (raw), &spi)) return FALSE; } /* if (0 && packet == 1) dump_page_info(&vbi->vt); */ return TRUE; } static void eacem_trigger(vbi_decoder *vbi, cache_page *vtp) { vbi_page pg; uint8_t *s; int i, j; if (0) dump_raw(vtp, FALSE); if (!(vbi->event_mask & VBI_EVENT_TRIGGER)) return; if (!vbi_format_vt_page(vbi, &pg, vtp, VBI_WST_LEVEL_1p5, 24, 0)) return; s = (uint8_t *) pg.text; for (i = 1; i < 25; i++) for (j = 0; j < 40; j++) { int c = pg.text[i * 41 + j].unicode; *s++ = (c >= 0x20 && c <= 0xFF) ? c : 0x20; } *s = 0; vbi_eacem_trigger(vbi, (uint8_t *) pg.text); } /* 11.2 Table Of Pages navigation */ static const int dec2bcdp[20] = { 0x000, 0x040, 0x080, 0x120, 0x160, 0x200, 0x240, 0x280, 0x320, 0x360, 0x400, 0x440, 0x480, 0x520, 0x560, 0x600, 0x640, 0x680, 0x720, 0x760 }; static vbi_bool unham_top_page_link (struct ttx_page_link * pl, const uint8_t buffer[8]) { int n4[8]; int err; unsigned int i; vbi_pgno pgno; vbi_subno subno; err = 0; for (i = 0; i < 8; ++i) err |= n4[i] = vbi_unham8 (buffer[i]); pgno = n4[0] * 256 + n4[1] * 16 + n4[2]; if (err < 0 || pgno < 0x100 || pgno > 0x8FF) return FALSE; subno = (n4[3] << 12) | (n4[4] << 8) | (n4[5] << 4) | n4[6]; switch ((enum ttx_top_page_function) n4[7]) { case TOP_PAGE_FUNCTION_AIT: pl->function = PAGE_FUNCTION_AIT; break; case TOP_PAGE_FUNCTION_MPT: pl->function = PAGE_FUNCTION_MPT; break; case TOP_PAGE_FUNCTION_MPT_EX: pl->function = PAGE_FUNCTION_MPT_EX; break; default: pl->function = PAGE_FUNCTION_UNKNOWN; break; } pl->pgno = pgno; pl->subno = subno & 0x3F7F; /* flags? */ return TRUE; } static inline vbi_bool parse_btt(vbi_decoder *vbi, uint8_t *raw, int packet) { switch (packet) { case 1 ... 20: { int i, j, code, index = dec2bcdp[packet - 1]; for (i = 0; i < 4; i++) { for (j = 0; j < 10; index++, j++) { struct ttx_page_stat *ps; ps = cache_network_page_stat (vbi->cn, 0x100 + index); if ((code = vbi_unham8 (*raw++)) < 0) break; switch (code) { case BTT_SUBTITLE: { cache_page *cp; ps->page_type = VBI_SUBTITLE_PAGE; cp = _vbi_cache_get_page (vbi->ca, vbi->cn, index + 0x100, /* subno */ 0, /* subno_mask */ 0); if (NULL != cp) { ps->charset_code = page_language (&vbi->vt, vbi->cn, cp, 0, 0); cache_page_unref (cp); } break; } case BTT_PROGR_INDEX_S: case BTT_PROGR_INDEX_M: /* Usually schedule, not index (likely BTT_GROUP) */ ps->page_type = VBI_PROGR_SCHEDULE; break; case BTT_BLOCK_S: case BTT_BLOCK_M: ps->page_type = VBI_TOP_BLOCK; break; case BTT_GROUP_S: case BTT_GROUP_M: ps->page_type = VBI_TOP_GROUP; break; case 8 ... 11: ps->page_type = VBI_NORMAL_PAGE; break; default: ps->page_type = VBI_NO_PAGE; continue; } switch (code) { case BTT_PROGR_INDEX_M: case BTT_BLOCK_M: case BTT_GROUP_M: case BTT_NORMAL_M: /* -> mpt, mpt_ex */ break; default: ps->subcode = 0; break; } } index += ((index & 0xFF) == 0x9A) ? 0x66 : 0x06; } break; } case 21 ... 23: { struct ttx_page_link *pl; int i; pl = vbi->cn->btt_link + (packet - 21) * 5; vbi->cn->have_top = TRUE; for (i = 0; i < 5; raw += 8, pl++, i++) { struct ttx_page_stat *ps; if (!unham_top_page_link(pl, raw)) continue; if (0) { printf("BTT #%d: ", (packet - 21) * 5); dump_page_link(*pl); } switch (pl->function) { case PAGE_FUNCTION_MPT: case PAGE_FUNCTION_AIT: case PAGE_FUNCTION_MPT_EX: ps = cache_network_page_stat (vbi->cn, pl->pgno); ps->page_type = VBI_TOP_PAGE; ps->subcode = 0; break; default: break; } } break; } } /* if (0 && packet == 1) dump_page_info(&vbi->vt); */ return TRUE; } static vbi_bool parse_ait(cache_page *vtp, uint8_t *raw, int packet) { int i, n; struct ttx_ait_title *ait; if (packet < 1 || packet > 23) return TRUE; ait = &vtp->data.ait.title[(packet - 1) * 2]; if (unham_top_page_link(&ait[0].link, raw + 0)) { for (i = 0; i < 12; i++) if ((n = vbi_unpar8 (raw[i + 8])) >= 0) ait[0].text[i] = n; } if (unham_top_page_link(&ait[1].link, raw + 20)) { for (i = 0; i < 12; i++) if ((n = vbi_unpar8 (raw[i + 28])) >= 0) ait[1].text[i] = n; } return TRUE; } static inline vbi_bool parse_mpt(cache_network *cn, uint8_t *raw, int packet) { int i, j, index; int n; switch (packet) { case 1 ... 20: index = dec2bcdp[packet - 1]; for (i = 0; i < 4; i++) { for (j = 0; j < 10; index++, j++) if ((n = vbi_unham8 (*raw++)) >= 0) { struct ttx_page_stat *ps; int code, subc; ps = cache_network_page_stat (cn, 0x100 + index); code = ps->page_type; subc = ps->subcode; if (n > 9) n = 0xFFFEL; /* mpt_ex? not transm?? */ if (code != VBI_NO_PAGE && code != VBI_UNKNOWN_PAGE && (subc >= 0xFFFF || n > subc)) ps->subcode = n; } index += ((index & 0xFF) == 0x9A) ? 0x66 : 0x06; } } return TRUE; } static inline vbi_bool parse_mpt_ex(cache_network *cn, uint8_t *raw, int packet) { int i, code, subc; struct ttx_page_link p; switch (packet) { case 1 ... 23: for (i = 0; i < 5; raw += 8, i++) { struct ttx_page_stat *ps; if (!unham_top_page_link(&p, raw)) continue; if (0) { printf("MPT-EX #%d: ", (packet - 1) * 5); dump_page_link(p); } if (p.pgno < 0x100) break; else if (p.pgno > 0x8FF || p.subno < 1) continue; ps = cache_network_page_stat (cn, p.pgno); code = ps->page_type; subc = ps->subcode; if (code != VBI_NO_PAGE && code != VBI_UNKNOWN_PAGE && (p.subno > subc /* evidence */ /* || subc >= 0xFFFF unknown */ || subc >= 0xFFFE /* mpt > 9 */)) ps->subcode = p.subno; } break; } return TRUE; } /** * @internal * @param vbi Initialized vbi decoding context. * @param vtp Raw teletext page to be converted. * @param cached The raw page is already cached, update the cache. * @param new_function The page function to convert to. * * Since MOT, MIP and X/28 are optional, the function of a system page * may not be clear until we format a LOP and find a link of certain type, * so this function converts a page "after the fact". * * @return * Pointer to the converted page, either @a vtp or the cached copy. **/ cache_page * vbi_convert_page(vbi_decoder *vbi, cache_page *vtp, vbi_bool cached, enum ttx_page_function new_function) { cache_page page; int i; if (vtp->function != PAGE_FUNCTION_UNKNOWN) return NULL; memcpy(&page, vtp, sizeof(*vtp) - sizeof(vtp->data) + sizeof(vtp->data.unknown)); switch (new_function) { case PAGE_FUNCTION_LOP: vtp->function = new_function; return vtp; case PAGE_FUNCTION_GPOP: case PAGE_FUNCTION_POP: memset(page.data.pop.pointer, 0xFF, sizeof(page.data.pop.pointer)); memset(page.data.pop.triplet, 0xFF, sizeof(page.data.pop.triplet)); for (i = 1; i <= 25; i++) if (vtp->lop_packets & (1 << i)) if (!parse_pop(&page, vtp->data.unknown.raw[i], i)) return FALSE; if (vtp->x26_designations) { memcpy (&page.data.pop.triplet[23 * 13], vtp->data.enh_lop.enh, 16 * 13 * sizeof (struct ttx_triplet)); } break; case PAGE_FUNCTION_GDRCS: case PAGE_FUNCTION_DRCS: memmove (&page.data.drcs.lop, &vtp->data.unknown, sizeof (page.data.drcs.lop)); CLEAR (page.data.drcs.mode); page.lop_packets = vtp->lop_packets; if (!convert_drcs(&page, vtp->data.unknown.raw[1])) return FALSE; break; case PAGE_FUNCTION_AIT: CLEAR (page.data.ait); for (i = 1; i <= 23; i++) if (vtp->lop_packets & (1 << i)) if (!parse_ait(&page, vtp->data.unknown.raw[i], i)) return FALSE; break; case PAGE_FUNCTION_MPT: for (i = 1; i <= 20; i++) if (vtp->lop_packets & (1 << i)) if (!parse_mpt(vbi->cn, vtp->data.unknown.raw[i], i)) return FALSE; break; case PAGE_FUNCTION_MPT_EX: for (i = 1; i <= 20; i++) if (vtp->lop_packets & (1 << i)) if (!parse_mpt_ex(vbi->cn, vtp->data.unknown.raw[i], i)) return FALSE; break; default: return NULL; } page.function = new_function; if (cached) { cache_page *new_vtp; new_vtp = _vbi_cache_put_page (vbi->ca, vbi->cn, &page); if (NULL != new_vtp) cache_page_unref (vtp); return new_vtp; } else { memcpy (vtp, &page, cache_page_size (&page)); return vtp; } } static unsigned int station_lookup(vbi_cni_type type, int cni, const char **country, const char **name) { const struct vbi_cni_entry *p; if (!cni) return 0; switch (type) { case VBI_CNI_TYPE_8301: for (p = vbi_cni_table; p->name; p++) if (p->cni1 == cni) { *country = p->country; *name = p->name; return p->id; } break; case VBI_CNI_TYPE_8302: for (p = vbi_cni_table; p->name; p++) if (p->cni2 == cni) { *country = p->country; *name = p->name; return p->id; } cni &= 0x0FFF; /* fall through */ case VBI_CNI_TYPE_VPS: /* if (cni == 0x0DC3) in decoder cni = mark ? 0x0DC2 : 0x0DC1; */ for (p = vbi_cni_table; p->name; p++) if (p->cni4 == cni) { *country = p->country; *name = p->name; return p->id; } break; case VBI_CNI_TYPE_PDC_B: for (p = vbi_cni_table; p->name; p++) if (p->cni3 == cni) { *country = p->country; *name = p->name; return p->id; } /* try code | 0x0080 & 0x0FFF -> VPS ? */ break; default: break; } return 0; } static void unknown_cni(vbi_decoder *vbi, const char *dl, int cni) { vbi = vbi; /* if (cni == 0) */ return; fprintf(stderr, "This network broadcasts an unknown CNI of 0x%04x using a %s data line.\n" "If you see this message always when switching to this channel please\n" "report network name, country, CNI and data line at http://zapping.sf.net\n" "for inclusion in the Country and Network Identifier table. Thank you.\n", cni, dl); } /** * @internal * @param vbi Initialized vbi decoding context. * @param buf 13 bytes. * * Decode a VPS datagram (13 bytes) according to * ETS 300 231 and update decoder state. This may * send a @a VBI_EVENT_NETWORK, @a VBI_EVENT_NETWORK_ID * or @a VBI_EVENT_PROG_ID. */ void vbi_decode_vps(vbi_decoder *vbi, uint8_t *buf) { vbi_network *n = &vbi->network.ev.network; const char *country, *name; unsigned int cni; vbi_decode_vps_cni (&cni, buf); if (cni != (unsigned int) n->cni_vps) { n->cni_vps = cni; n->cycle = 1; CLEAR (vbi->vps_pid); /* May fail, leaving vbi->vps_pid unmodified. */ vbi_decode_vps_pdc (&vbi->vps_pid, buf); } else if (n->cycle == 1) { unsigned int id; id = station_lookup(VBI_CNI_TYPE_VPS, cni, &country, &name); if (0 == id) { n->name[0] = 0; unknown_cni(vbi, "VPS", cni); } else { strlcpy((char *) n->name, name, sizeof(n->name) - 1); n->name[sizeof(n->name) - 1] = 0; } if (id != n->nuid) { if (n->nuid != 0) vbi_chsw_reset(vbi, id); n->nuid = id; vbi->network.type = VBI_EVENT_NETWORK; vbi_send_event(vbi, &vbi->network); } vbi->network.type = VBI_EVENT_NETWORK_ID; vbi_send_event(vbi, &vbi->network); n->cycle = 2; if (vbi->event_mask & VBI_EVENT_PROG_ID) { vbi_program_id pid; vbi_event e; CLEAR (pid); if (!vbi_decode_vps_pdc (&pid, buf)) return; /* VPS has no error protection so we send an event only after we receive a PID twice. */ if (0 != memcmp (&pid, &vbi->vps_pid, sizeof (pid))) { vbi->vps_pid = pid; return; } /* We also send an event if the PID did not change so the app can see if the signal is still present. */ CLEAR (e); e.type = VBI_EVENT_PROG_ID; e.ev.prog_id = &pid; vbi_send_event (vbi, &e); } } } static vbi_bool parse_bsd(vbi_decoder *vbi, uint8_t *raw, int packet, int designation) { vbi_network *n = &vbi->network.ev.network; int err, i; switch (packet) { case 26: /* TODO, iff */ break; case 30: if (designation >= 4) break; if (designation <= 1) { const char *country, *name; int cni; #if 0 printf("\nPacket 8/30/%d:\n", designation); #endif cni = vbi_rev16p (raw + 7); if (cni != n->cni_8301) { n->cni_8301 = cni; n->cycle = 1; } else if (n->cycle == 1) { unsigned int id; id = station_lookup(VBI_CNI_TYPE_8301, cni, &country, &name); if (!id) { n->name[0] = 0; unknown_cni(vbi, "8/30/1", cni); } else { strlcpy((char *) n->name, name, sizeof(n->name) - 1); n->name[sizeof(n->name) - 1] = 0; } if (id != n->nuid) { if (n->nuid != 0) vbi_chsw_reset(vbi, id); n->nuid = id; vbi->network.type = VBI_EVENT_NETWORK; vbi_send_event(vbi, &vbi->network); } vbi->network.type = VBI_EVENT_NETWORK_ID; vbi_send_event(vbi, &vbi->network); n->cycle = 2; } #if 0 if (1) { /* country and network identifier */ if (station_lookup(VBI_CNI_TYPE_8301, cni, &country, &name)) printf("... country: %s\n... station: %s\n", country, name); else printf("... unknown CNI %04x\n", cni); } if (1) { /* local time */ int lto, mjd, utc_h, utc_m, utc_s; struct tm tm; time_t ti; lto = (raw[9] & 0x7F) >> 1; mjd = + ((raw[10] & 15) - 1) * 10000 + ((raw[11] >> 4) - 1) * 1000 + ((raw[11] & 15) - 1) * 100 + ((raw[12] >> 4) - 1) * 10 + ((raw[12] & 15) - 1); utc_h = ((raw[13] >> 4) - 1) * 10 + ((raw[13] & 15) - 1); utc_m = ((raw[14] >> 4) - 1) * 10 + ((raw[14] & 15) - 1); utc_s = ((raw[15] >> 4) - 1) * 10 + ((raw[15] & 15) - 1); ti = (mjd - 40587) * 86400 + 43200; localtime_r(&ti, &tm); printf("... local time: MJD %d %02d %s %04d, UTC %02d:%02d:%02d %c%02d%02d\n", mjd, tm.tm_mday, month_names[tm.tm_mon + 1], tm.tm_year + 1900, utc_h, utc_m, utc_s, (raw[9] & 0x80) ? '-' : '+', lto >> 1, (lto & 1) * 30); } #endif /* BSDATA_TEST */ } else /* if (designation <= 3) */ { int t, b[7]; const char *country, *name; int cni; #if 0 printf("\nPacket 8/30/%d:\n", designation); #endif for (err = i = 0; i < 7; i++) { err |= t = vbi_unham16p (raw + i * 2 + 6); b[i] = vbi_rev8 (t); } if (err < 0) return FALSE; cni = + ((b[4] & 0x03) << 10) + ((b[5] & 0xC0) << 2) + (b[2] & 0xC0) + (b[5] & 0x3F) + ((b[1] & 0x0F) << 12); if (cni == 0x0DC3) cni = (b[2] & 0x10) ? 0x0DC2 : 0x0DC1; if (cni != n->cni_8302) { n->cni_8302 = cni; n->cycle = 1; } else if (n->cycle == 1) { unsigned int id; id = station_lookup(VBI_CNI_TYPE_8302, cni, &country, &name); if (!id) { n->name[0] = 0; unknown_cni(vbi, "8/30/2", cni); } else { strlcpy((char *) n->name, name, sizeof(n->name) - 1); n->name[sizeof(n->name) - 1] = 0; } if (id != n->nuid) { if (n->nuid != 0) vbi_chsw_reset(vbi, id); n->nuid = id; vbi->network.type = VBI_EVENT_NETWORK; vbi_send_event(vbi, &vbi->network); } vbi->network.type = VBI_EVENT_NETWORK_ID; vbi_send_event(vbi, &vbi->network); n->cycle = 2; } #if 0 if (1) { /* country and network identifier */ const char *country, *name; if (station_lookup(VBI_CNI_TYPE_8302, cni, &country, &name)) printf("... country: %s\n... station: %s\n", country, name); else printf("... unknown CNI %04x\n", cni); } if (1) { /* PDC data */ int lci, luf, prf, mi, pil; lci = (b[0] >> 2) & 3; luf = !!(b[0] & 2); prf = b[0] & 1; mi = !!(b[1] & 0x20); pil = ((b[2] & 0x3F) << 14) + (b[3] << 6) + (b[4] >> 2); printf("... label channel %d: update %d," " prepare to record %d, mode %d\n", lci, luf, prf, mi); dump_pil(pil); } if (1) { int pty, pcs; pcs = b[1] >> 6; pty = b[6]; printf("... analog audio: %s\n", pcs_names[pcs]); dump_pty(pty); } #endif /* BSDATA_TEST */ } #if 0 /* * "transmission status message, e.g. the programme title", * "default G0 set". XXX add to program_info event. */ if (1) { printf("... status: \""); for (i = 20; i < 40; i++) { int c = vbi_parity(raw[i]); c = (c < 0) ? '?' : _vbi_to_ascii (c); putchar(c); } printf("\"\n"); } #endif return TRUE; } return TRUE; } static int same_header(int cur_pgno, const uint8_t *cur, int ref_pgno, const uint8_t *ref, int *page_num_offsetp) { uint8_t buf[3]; int i, j = 32 - 3, err = 0, neq = 0; ref_pgno = ref_pgno; /* Assumes vbi_is_bcd(cur_pgno) */ buf[2] = (cur_pgno & 15) + '0'; buf[1] = ((cur_pgno >> 4) & 15) + '0'; buf[0] = (cur_pgno >> 8) + '0'; vbi_par (buf, 3); for (i = 8; i < 32; cur++, ref++, i++) { /* Skip page number */ if (i < j && cur[0] == buf[0] && cur[1] == buf[1] && cur[2] == buf[2]) { j = i; /* here, once */ i += 3; cur += 3; ref += 3; continue; } err |= vbi_unpar8 (*cur); err |= vbi_unpar8 (*ref); neq |= *cur - *ref; } if (err < 0 || j >= 32 - 3) /* parity error, rare */ return -2; /* inconclusive, useless */ *page_num_offsetp = j; if (!neq) return TRUE; /* Test false negative due to date transition */ if (((ref[32] * 256 + ref[33]) & 0x7F7F) == 0x3233 && ((cur[32] * 256 + cur[33]) & 0x7F7F) == 0x3030) { return -1; /* inconclusive */ } /* * The problem here is that individual pages or * magazines from the same network can still differ. */ return FALSE; } static inline vbi_bool same_clock(const uint8_t *cur, const uint8_t *ref) { int i; for (i = 32; i < 40; cur++, ref++, i++) if (*cur != *ref && (vbi_unpar8 (*cur) | vbi_unpar8 (*ref)) >= 0) return FALSE; return TRUE; } static inline vbi_bool store_lop(vbi_decoder *vbi, const cache_page *vtp) { struct ttx_page_stat *ps; cache_page *new_cp; vbi_event event; event.type = VBI_EVENT_TTX_PAGE; event.ev.ttx_page.pgno = vtp->pgno; event.ev.ttx_page.subno = vtp->subno; event.ev.ttx_page.roll_header = (((vtp->flags & ( C5_NEWSFLASH | C6_SUBTITLE | C7_SUPPRESS_HEADER | C9_INTERRUPTED | C10_INHIBIT_DISPLAY)) == 0) && (vtp->pgno <= 0x199 || (vtp->flags & C11_MAGAZINE_SERIAL)) && vbi_is_bcd(vtp->pgno) /* no hex numbers */); event.ev.ttx_page.header_update = FALSE; event.ev.ttx_page.raw_header = NULL; event.ev.ttx_page.pn_offset = -1; /* * We're not always notified about a channel switch, * this code prevents a terrible mess in the cache. * * The roll_header thing shall reduce false negatives, * slows down detection of some stations, but does help. * A little. Maybe this should be optional. */ if (event.ev.ttx_page.roll_header) { int r; if (vbi->vt.header_page.pgno == 0) { /* First page after channel switch */ r = same_header(vtp->pgno, vtp->data.lop.raw[0] + 8, vtp->pgno, vtp->data.lop.raw[0] + 8, &event.ev.ttx_page.pn_offset); event.ev.ttx_page.header_update = TRUE; event.ev.ttx_page.clock_update = TRUE; } else { r = same_header(vtp->pgno, vtp->data.lop.raw[0] + 8, vbi->vt.header_page.pgno, vbi->vt.header + 8, &event.ev.ttx_page.pn_offset); event.ev.ttx_page.clock_update = !same_clock(vtp->data.lop.raw[0], vbi->vt.header); } switch (r) { case TRUE: // fprintf(stderr, "+"); pthread_mutex_lock(&vbi->chswcd_mutex); vbi->chswcd = 0; pthread_mutex_unlock(&vbi->chswcd_mutex); vbi->vt.header_page.pgno = vtp->pgno; memcpy(vbi->vt.header + 8, vtp->data.lop.raw[0] + 8, 32); event.ev.ttx_page.raw_header = vbi->vt.header; break; case FALSE: /* * What can I do when every magazin has its own * header? Ouch. Let's hope p100 repeats frequently. */ if (((vtp->pgno ^ vbi->vt.header_page.pgno) & 0xF00) == 0) { /* pthread_mutex_lock(&vbi->chswcd_mutex); if (vbi->chswcd == 0) vbi->chswcd = 40; pthread_mutex_unlock(&vbi->chswcd_mutex); */ vbi_chsw_reset(vbi, 0); return TRUE; } /* fall through */ default: /* inconclusive */ pthread_mutex_lock(&vbi->chswcd_mutex); if (vbi->chswcd > 0) { pthread_mutex_unlock(&vbi->chswcd_mutex); return TRUE; } pthread_mutex_unlock(&vbi->chswcd_mutex); if (r == -1) { vbi->vt.header_page.pgno = vtp->pgno; memcpy(vbi->vt.header + 8, vtp->data.lop.raw[0] + 8, 32); event.ev.ttx_page.raw_header = vbi->vt.header; // fprintf(stderr, "/"); } else /* broken header */ { event.ev.ttx_page.roll_header = FALSE; event.ev.ttx_page.clock_update = FALSE; // fprintf(stderr, "X"); } break; } if (0) { int i; for (i = 0; i < 40; i++) putchar(_vbi_to_ascii (vtp->data.unknown.raw[0][i])); putchar('\r'); fflush(stdout); } } else { // fprintf(stderr, "-"); } /* * Collect information about those pages * not listed in MIP etc. */ ps = cache_network_page_stat (vbi->cn, vtp->pgno); if (ps->page_type == VBI_SUBTITLE_PAGE) { if (ps->charset_code == 0xFF) ps->charset_code = page_language (&vbi->vt, vbi->cn, vtp, 0, 0); } else if (ps->page_type == VBI_NO_PAGE || ps->page_type == VBI_UNKNOWN_PAGE) { ps->page_type = VBI_NORMAL_PAGE; } if (ps->subcode >= 0xFFFE || vtp->subno > ps->subcode) ps->subcode = vtp->subno; /* * Store the page and send event. */ new_cp = _vbi_cache_put_page (vbi->ca, vbi->cn, vtp); if (NULL != new_cp) { vbi_send_event(vbi, &event); cache_page_unref (new_cp); } return TRUE; } static void lop_parity_check (cache_page * cvtp, struct raw_page * rvtp) { if (0 != cvtp->x26_designations) { struct ttx_triplet *trip = cvtp->data.enh_lop.enh; struct ttx_triplet *trip_end = trip + N_ELEMENTS (cvtp->data.enh_lop.enh); unsigned int row = 0; /* This is a little work-around for Teletext encoders which transmit X/26 fallback characters with even parity as noted in EN 300 706 Table 25. The page formatting code can detect parity errors and pick a replacement character, however the parity check below attempts to correct errors when a page is retransmitted, and requires odd parity on all characters. */ for (; trip < trip_end; ++trip) { if (trip->address < 40) { switch (trip->mode) { case 0x01: /* G1 block mosaic character */ case 0x02: /* G3 smooth mosaic or line drawing character */ case 0x0B: /* G3 smooth mosaic or line drawing character */ case 0x08: /* modified G0 and G2 character set designation */ case 0x09: /* G0 character */ case 0x0D: /* drcs character invocation */ case 0x0F: /* G2 character */ case 0x10 ... 0x1F: /* characters including diacritical marks */ { unsigned int column = trip->address; unsigned int c = rvtp->lop_raw[row][column]; rvtp->lop_raw[row][column] = vbi_par8 (c); break; } default: break; } } else if (trip->address > 63) { /* Missed triplet or uncorrectable transmission error. */ break; } else { switch (trip->mode) { case 0x01: /* full row colour */ case 0x04: /* set active position */ row = trip->address - 40; if (0 == row) row = 24; break; case 0x07: /* address display row 0 */ row = 0; break; default: break; } } } } /* Level 1 parity check. */ { unsigned int packet; for (packet = 1; packet <= 25; ++packet) { unsigned int i; int n; if (0 == (rvtp->lop_packets & (1 << packet))) continue; n = 0; for (i = 0; i < 40; ++i) n |= vbi_unpar8 (rvtp->lop_raw[packet][i]); if (n >= 0) { /* Parity is good, replace cached row. We could replace individual characters, but a single parity bit isn't very reliable. */ memcpy (cvtp->data.lop.raw[packet], rvtp->lop_raw[packet], 40); cvtp->lop_packets |= 1 << packet; } } } } #define TTX_EVENTS (VBI_EVENT_TTX_PAGE) #define BSDATA_EVENTS (VBI_EVENT_NETWORK | VBI_EVENT_NETWORK_ID) /* * Teletext packet 27, page linking */ static inline vbi_bool parse_27(vbi_decoder *vbi, uint8_t *p, cache_page *cvtp, int mag0) { int designation, control; int i; vbi = vbi; if (cvtp->function == PAGE_FUNCTION_DISCARD) return TRUE; if ((designation = vbi_unham8 (*p)) < 0) return FALSE; // printf("Packet X/27/%d page %x\n", designation, cvtp->pgno); switch (designation) { case 0: if ((control = vbi_unham8 (p[37])) < 0) return FALSE; /* printf("%x.%x X/27/%d %02x\n", cvtp->pgno, cvtp->subno, designation, control); */ #if 0 /* * CRC cannot be trusted, some stations transmit rubbish. * Link Control Byte bits 1 ... 3 cannot be trusted, ETS 300 706 is * inconclusive and not all stations follow the suggestions in ETR 287. */ crc = p[38] + p[39] * 256; /* printf("CRC: %04x\n", crc); */ if ((control & 7) == 0) return FALSE; #endif cvtp->data.unknown.have_flof = control >> 3; /* display row 24 */ /* fall through */ case 1: case 2: case 3: for (p++, i = 0; i <= 5; p += 6, i++) { if (!unham_page_link(cvtp->data.unknown.link + designation * 6 + i, p, mag0)) { /* return TRUE; */ } // printf("X/27/%d link[%d] page %03x/%03x\n", designation, i, // cvtp->data.unknown.link[designation * 6 + i].pgno, cvtp->data.unknown.link[designation * 6 + i].subno); } break; case 4: case 5: for (p++, i = 0; i <= 5; p += 6, i++) { int t1, t2; t1 = vbi_unham24p (p + 0); t2 = vbi_unham24p (p + 3); if ((t1 | t2) < 0) return FALSE; cvtp->data.unknown.link[designation * 6 + i].function = t1 & 3; cvtp->data.unknown.link[designation * 6 + i].pgno = ((((t1 >> 12) & 0x7) ^ mag0) ? : 8) * 256 + ((t1 >> 11) & 0x0F0) + ((t1 >> 7) & 0x00F); cvtp->data.unknown.link[designation * 6 + i].subno = (t2 >> 3) & 0xFFFF; if(0) printf("X/27/%d link[%d] type %d page %03x subno %04x\n", designation, i, cvtp->data.unknown.link[designation * 6 + i].function, cvtp->data.unknown.link[designation * 6 + i].pgno, cvtp->data.unknown.link[designation * 6 + i].subno); } break; } return TRUE; } struct bit_stream { int * triplet; unsigned int buffer; unsigned int left; }; static unsigned int get_bits (struct bit_stream * bs, unsigned int count) { unsigned int r; int n; r = bs->buffer; n = count - bs->left; if (n > 0) { bs->buffer = *(bs->triplet)++; r |= bs->buffer << bs->left; bs->left = 18 - n; } else { n = count; bs->left -= count; } bs->buffer >>= n; return r & ((1UL << count) - 1); } /* * Teletext packets 28 and 29, Level 2.5/3.5 enhancement */ static vbi_bool parse_28_29(vbi_decoder *vbi, uint8_t *p, cache_page *cvtp, int mag8, int packet) { int designation, function, coding; int triplets[13]; struct bit_stream bs; struct ttx_extension *ext; int i, j, err = 0; if ((designation = vbi_unham8 (*p)) < 0) return FALSE; if (0) fprintf(stderr, "Packet %d/%d/%d page %x\n", mag8, packet, designation, cvtp->pgno); for (p++, i = 0; i < 13; p += 3, i++) err |= triplets[i] = vbi_unham24p (p); bs.triplet = triplets; bs.buffer = 0; bs.left = 0; switch (designation) { case 0: /* X/28/0, M/29/0 Level 2.5 */ case 4: /* X/28/4, M/29/4 Level 3.5 */ if (err < 0) return FALSE; function = get_bits (&bs, 4); coding = get_bits (&bs, 3); /* page coding ignored */ // printf("... function %d\n", function); /* * ZDF and BR3 transmit GPOP 1EE/.. with 1/28/0 function * 0 = PAGE_FUNCTION_LOP, should be PAGE_FUNCTION_GPOP. * Makes no sense to me. Update: also encountered pages * mFE and mFF with function = 0. Strange. */ if (function != PAGE_FUNCTION_LOP && packet == 28) { if (cvtp->function != PAGE_FUNCTION_UNKNOWN && cvtp->function != function) return FALSE; /* XXX discard rpage? */ // XXX rethink cvtp->function = function; } if (function != PAGE_FUNCTION_LOP) return FALSE; /* XXX X/28/0 Format 2, distinguish how? */ ext = &cache_network_magazine (vbi->cn, mag8 * 0x100)->extension; if (packet == 28) { if (!cvtp->data.ext_lop.ext.designations) { cvtp->data.ext_lop.ext = *ext; } cvtp->x28_designations |= 1 << designation; ext = &cvtp->data.ext_lop.ext; } if (designation == 4 && (ext->designations & (1 << 0))) get_bits (&bs, 14 + 2 + 1 + 4); else { vbi_bool left_panel; vbi_bool right_panel; unsigned int left_columns; ext->charset_code[0] = get_bits (&bs, 7); ext->charset_code[1] = get_bits (&bs, 7); left_panel = get_bits (&bs, 1); right_panel = get_bits (&bs, 1); /* 0 - panels required at Level 3.5 only, 1 - at 2.5 and 3.5 ignored. */ get_bits (&bs, 1); left_columns = get_bits (&bs, 4); if (left_panel && 0 == left_columns) left_columns = 16; ext->fallback.left_panel_columns = left_columns & -left_panel; ext->fallback.right_panel_columns = (16 - left_columns) & -right_panel; } j = (designation == 4) ? 16 : 32; for (i = j - 16; i < j; i++) { vbi_rgba col = get_bits (&bs, 12); if (i == 8) /* transparent */ continue; col = VBI_RGBA((col >> 0) & 15, (col >> 4) & 15, (col >> 8) & 15); ext->color_map[i] = col | (col << 4); } if (designation == 4 && (ext->designations & (1 << 0))) get_bits (&bs, 10 + 1 + 3); else { ext->def_screen_color = get_bits (&bs, 5); ext->def_row_color = get_bits (&bs, 5); ext->fallback.black_bg_substitution = get_bits (&bs, 1); i = get_bits (&bs, 3); /* color table remapping */ ext->foreground_clut = "\00\00\00\10\10\20\20\20"[i]; ext->background_clut = "\00\10\20\10\20\10\20\30"[i]; } ext->designations |= 1 << designation; if (packet == 29) { if (0 && designation == 4) ext->designations &= ~(1 << 0); /* XXX update inherited_mag_desig = page->extension.designations >> 16; new_mag_desig = 1 << designation; page_desig = page->extension.designations; if (((inherited_mag_desig | page_desig) & new_mag_desig) == 0) shortcut: AND of (inherited_mag_desig | page_desig) of all pages with extensions, no updates required in round 2++ other option, all M/29/x should have been received within the maximum repetition interval of 20 s. */ } return FALSE; case 1: /* X/28/1, M/29/1 Level 3.5 DRCS CLUT */ ext = &cache_network_magazine (vbi->cn, mag8 * 0x100)->extension; if (packet == 28) { if (!cvtp->data.ext_lop.ext.designations) { cvtp->data.ext_lop.ext = *ext; } cvtp->x28_designations |= 1 << designation; ext = &cvtp->data.ext_lop.ext; /* XXX TODO - lop? */ } /* 9.4.4: "Compatibility, not for Level 2.5/3.5 decoders." No more details, so we ignore this triplet. */ ++bs.triplet; for (i = 0; i < 8; i++) ext->drcs_clut[i + 2] = vbi_rev8 (get_bits (&bs, 5)) >> 3; for (i = 0; i < 32; i++) ext->drcs_clut[i + 10] = vbi_rev8 (get_bits (&bs, 5)) >> 3; ext->designations |= 1 << 1; if (0) dump_extension(ext); return FALSE; case 3: /* X/28/3 Level 2.5, 3.5 DRCS download page */ if (packet == 29) break; /* M/29/3 undefined */ if (err < 0) return FALSE; function = get_bits (&bs, 4); coding = get_bits (&bs, 3); /* page coding ignored */ if (function != PAGE_FUNCTION_GDRCS && function != PAGE_FUNCTION_DRCS) return FALSE; if (cvtp->function == PAGE_FUNCTION_UNKNOWN) { /* If to prevent warning: statement with no effect when .raw unions coincidentally align. */ if (&cvtp->data.drcs.lop != &cvtp->data.unknown) { memmove (&cvtp->data.drcs.lop, &cvtp->data.unknown, sizeof (cvtp->data.drcs.lop)); } cvtp->function = function; } else if (cvtp->function != function) { cvtp->function = PAGE_FUNCTION_DISCARD; return 0; } get_bits (&bs, 11); for (i = 0; i < 48; i++) cvtp->data.drcs.mode[i] = get_bits (&bs, 4); default: /* ? */ break; } return TRUE; } /* * Teletext packet 8/30, broadcast service data */ static inline vbi_bool parse_8_30(vbi_decoder *vbi, uint8_t *buffer, int packet) { uint8_t *p; int designation; p = buffer + 2; if ((designation = vbi_unham8 (*p)) < 0) return FALSE; // printf("Packet 8/30/%d\n", designation); if (designation > 4) return TRUE; /* ignored */ if (vbi->event_mask & TTX_EVENTS) { if (!unham_page_link(&vbi->cn->initial_page, p + 1, 0)) return FALSE; if ((vbi->cn->initial_page.pgno & 0xFF) == 0xFF) { vbi->cn->initial_page.pgno = 0x100; vbi->cn->initial_page.subno = VBI_ANY_SUBNO; } } if (vbi->event_mask & BSDATA_EVENTS) { if (!parse_bsd(vbi, p, packet, designation)) return FALSE; } if (designation < 2) { /* 8/30 format 1 */ if (vbi->event_mask & VBI_EVENT_LOCAL_TIME) { vbi_local_time lt; vbi_event e; CLEAR (e); if (!vbi_decode_teletext_8301_local_time (<.time, <.seconds_east, buffer)) return FALSE; lt.seconds_east_valid = TRUE; lt.dst_state = VBI_DST_INCLUDED; e.type = VBI_EVENT_LOCAL_TIME; e.ev.local_time = < vbi_send_event (vbi, &e); } } else { /* 8/30 format 2 */ if (vbi->event_mask & VBI_EVENT_PROG_ID) { vbi_program_id pid; vbi_event e; if (!vbi_decode_teletext_8302_pdc (&pid, buffer)) return FALSE; CLEAR (e); e.type = VBI_EVENT_PROG_ID; e.ev.prog_id = &pid; vbi_send_event (vbi, &e); } } return TRUE; } /** * @internal * @param vbi Initialized vbi decoding context. * @param p Packet data. * * Parse a teletext packet (42 bytes) and update the decoder * state accordingly. This function may send events. * * Return value: * FALSE if the packet contained uncorrectable errors. */ vbi_bool vbi_decode_teletext(vbi_decoder *vbi, uint8_t *buffer) { cache_page *cvtp; struct raw_page *rvtp; int pmag, mag0, mag8, packet; struct ttx_magazine *mag; uint8_t *p; p = buffer; if ((pmag = vbi_unham16p (p)) < 0) return FALSE; mag0 = pmag & 7; mag8 = mag0 ? : 8; packet = pmag >> 3; if (packet < 30 && !(vbi->event_mask & TTX_EVENTS)) return TRUE; mag = cache_network_magazine (vbi->cn, mag8 * 0x100); rvtp = vbi->vt.raw_page + mag0; cvtp = rvtp->page; p += 2; if (0) { unsigned int i; fprintf(stderr, "packet 0x%x %d >", mag8 * 0x100, packet); for (i = 0; i < 40; i++) fputc(_vbi_to_ascii (p[i]), stderr); fprintf(stderr, "<\n"); } switch (packet) { case 0: { int pgno, page, subpage, flags; struct raw_page *curr; cache_page *vtp; int i; if ((page = vbi_unham16p (p)) < 0) { vbi_teletext_desync(vbi); // printf("Hamming error in packet 0 page number\n"); return FALSE; } pgno = mag8 * 256 + page; /* * Store page terminated by new header. */ while ((curr = vbi->vt.current)) { vtp = curr->page; if (vtp->flags & C11_MAGAZINE_SERIAL && !(vtp->flags & C4_ERASE_PAGE)) { if (vtp->pgno == pgno) break; } else { curr = rvtp; vtp = curr->page; if ((vtp->pgno & 0xFF) == page && !(vtp->flags & C4_ERASE_PAGE)) break; } switch (vtp->function) { case PAGE_FUNCTION_DISCARD: case PAGE_FUNCTION_EPG: break; case PAGE_FUNCTION_LOP: lop_parity_check(vtp, curr); if (!store_lop(vbi, vtp)) return FALSE; break; case PAGE_FUNCTION_DRCS: case PAGE_FUNCTION_GDRCS: { if (convert_drcs(vtp, vtp->data.drcs.lop.raw[1])) _vbi_cache_put_page (vbi->ca, vbi->cn, vtp); break; } case PAGE_FUNCTION_MIP: parse_mip(vbi, vtp); break; case PAGE_FUNCTION_EACEM_TRIGGER: eacem_trigger(vbi, vtp); break; default: { cache_page *new_cp; new_cp = _vbi_cache_put_page (vbi->ca, vbi->cn, vtp); cache_page_unref (new_cp); break; } } vtp->function = PAGE_FUNCTION_DISCARD; break; } /* * Prepare for new page. */ cvtp->pgno = pgno; vbi->vt.current = rvtp; subpage = vbi_unham16p (p + 2) + vbi_unham16p (p + 4) * 256; flags = vbi_unham16p (p + 6); if (page == 0xFF || (subpage | flags) < 0) { cvtp->function = PAGE_FUNCTION_DISCARD; return FALSE; } cvtp->subno = subpage & 0x3F7F; cvtp->national = vbi_rev8 (flags) & 7; cvtp->flags = (flags << 16) + subpage; if (0 && ((page & 15) > 9 || page > 0x99)) printf("data page %03x/%04x n%d\n", cvtp->pgno, cvtp->subno, cvtp->national); if (1 && pgno != 0x1E7 && !(cvtp->flags & C4_ERASE_PAGE) && (vtp = _vbi_cache_get_page (vbi->ca, vbi->cn, cvtp->pgno, cvtp->subno, /* subno_mask */ -1))) { memset(&cvtp->data, 0, sizeof(cvtp->data)); memcpy(&cvtp->data, &vtp->data, cache_page_size(vtp) - sizeof(*vtp) + sizeof(vtp->data)); /* XXX write cache directly | erc?*/ /* XXX data page update */ cvtp->function = vtp->function; switch (cvtp->function) { case PAGE_FUNCTION_UNKNOWN: case PAGE_FUNCTION_LOP: memcpy(cvtp->data.unknown.raw[0], p, 40); default: break; } cvtp->lop_packets = vtp->lop_packets; cvtp->x26_designations = vtp->x26_designations; cvtp->x27_designations = vtp->x27_designations; cvtp->x28_designations = vtp->x28_designations; cache_page_unref (vtp); vtp = NULL; } else { struct ttx_page_stat *ps; ps = cache_network_page_stat (vbi->cn, cvtp->pgno); cvtp->flags |= C4_ERASE_PAGE; if (0) printf("rebuilding %3x/%04x from scratch\n", cvtp->pgno, cvtp->subno); if (cvtp->pgno == 0x1F0) { cvtp->function = PAGE_FUNCTION_BTT; ps->page_type = VBI_TOP_PAGE; } else if (cvtp->pgno == 0x1E7) { cvtp->function = PAGE_FUNCTION_EACEM_TRIGGER; ps->page_type = VBI_DISP_SYSTEM_PAGE; ps->subcode = 0; memset(cvtp->data.unknown.raw[0], 0x20, sizeof(cvtp->data.unknown.raw)); memset(cvtp->data.enh_lop.enh, 0xFF, sizeof(cvtp->data.enh_lop.enh)); } else if (page == 0xFD) { cvtp->function = PAGE_FUNCTION_MIP; ps->page_type = VBI_SYSTEM_PAGE; } else if (page == 0xFE) { cvtp->function = PAGE_FUNCTION_MOT; ps->page_type = VBI_SYSTEM_PAGE; } else if (FPC && ps->page_type == VBI_EPG_DATA) { cvtp->function = PAGE_FUNCTION_DISCARD; #if 0 /* TODO */ int stream = (cvtp->subno >> 8) & 15; if (stream >= 2) { cvtp->function = PAGE_FUNCTION_DISCARD; // fprintf(stderr, "Discard FPC %d\n", stream); } else { struct page_clear *pc = vbi->epg_pc + stream; int ci = cvtp->subno & 15; cvtp->function = PAGE_FUNCTION_EPG; pc->pfc.pgno = cvtp->pgno; if (((pc->ci + 1) & 15) != ci) vbi_reset_page_clear(pc); pc->ci = ci; pc->packet = 0; pc->num_packets = ((cvtp->subno >> 4) & 7) + ((cvtp->subno >> 9) & 0x18); } #endif } else { cvtp->function = PAGE_FUNCTION_UNKNOWN; memcpy(cvtp->data.unknown.raw[0] + 0, p, 40); memset(cvtp->data.unknown.raw[0] + 40, 0x20, sizeof(cvtp->data.unknown.raw) - 40); memset(cvtp->data.unknown.link, 0xFF, sizeof(cvtp->data.unknown.link)); memset(cvtp->data.enh_lop.enh, 0xFF, sizeof(cvtp->data.enh_lop.enh)); cvtp->data.unknown.have_flof = FALSE; } cvtp->lop_packets = 1; cvtp->x26_designations = 0; cvtp->x27_designations = 0; cvtp->x28_designations = 0; } if (cvtp->function == PAGE_FUNCTION_UNKNOWN) { enum ttx_page_function function; struct ttx_page_stat *ps; function = PAGE_FUNCTION_UNKNOWN; ps = cache_network_page_stat (vbi->cn, cvtp->pgno); switch (ps->page_type) { case 0x01 ... 0x51: case 0x70 ... 0x7F: case 0x81 ... 0xD1: case 0xF4 ... 0xF7: case VBI_TOP_BLOCK: case VBI_TOP_GROUP: function = PAGE_FUNCTION_LOP; break; case VBI_SYSTEM_PAGE: /* no MOT or MIP?? */ /* remains function = PAGE_FUNCTION_UNKNOWN; */ break; case VBI_TOP_PAGE: for (i = 0; i < 8; i++) if (cvtp->pgno == vbi->cn->btt_link[i].pgno) break; if (i < 8) { switch (vbi->cn->btt_link[i].function) { case PAGE_FUNCTION_AIT: case PAGE_FUNCTION_MPT: case PAGE_FUNCTION_MPT_EX: function = vbi->cn->btt_link[i].function; break; default: if (0) printf("page is TOP, link %d, unknown type %d\n", i, vbi->cn->btt_link[i].function); } } else if (0) printf("page claims to be TOP, link not found\n"); break; case 0xE5: case 0xE8 ... 0xEB: function = PAGE_FUNCTION_DRCS; break; case 0xE6: case 0xEC ... 0xEF: function = PAGE_FUNCTION_POP; break; case VBI_TRIGGER_DATA: function = PAGE_FUNCTION_EACEM_TRIGGER; break; case VBI_EPG_DATA: /* EPG/NexTView transport layer */ if (FPC) { function = PAGE_FUNCTION_EPG; break; } /* fall through */ case 0x52 ... 0x6F: /* reserved */ case VBI_ACI: /* ACI page */ case VBI_NOT_PUBLIC: case 0xD2 ... 0xDF: /* reserved */ case 0xE0 ... 0xE2: /* data broadcasting */ case 0xE4: /* data broadcasting */ case 0xF0 ... 0xF3: /* broadcaster system page */ function = PAGE_FUNCTION_DISCARD; break; default: if (page <= 0x99 && (page & 15) <= 9) function = PAGE_FUNCTION_LOP; /* else remains function = PAGE_FUNCTION_UNKNOWN; */ } if (function != PAGE_FUNCTION_UNKNOWN) { vbi_convert_page(vbi, cvtp, FALSE, function); } } //XXX? cvtp->data.ext_lop.ext.designations = 0; rvtp->lop_packets = 0; rvtp->num_triplets = 0; return TRUE; } case 1 ... 25: { int n; int i; switch (cvtp->function) { case PAGE_FUNCTION_DISCARD: return TRUE; case PAGE_FUNCTION_MOT: if (!parse_mot(cache_network_magazine (vbi->cn, mag8 * 0x100), p, packet)) return FALSE; break; case PAGE_FUNCTION_GPOP: case PAGE_FUNCTION_POP: if (!parse_pop(cvtp, p, packet)) return FALSE; break; case PAGE_FUNCTION_GDRCS: case PAGE_FUNCTION_DRCS: memcpy (cvtp->data.drcs.lop.raw[packet], p, 40); break; case PAGE_FUNCTION_BTT: if (!parse_btt(vbi, p, packet)) return FALSE; break; case PAGE_FUNCTION_AIT: if (!(parse_ait(cvtp, p, packet))) return FALSE; break; case PAGE_FUNCTION_MPT: if (!(parse_mpt(vbi->cn, p, packet))) return FALSE; break; case PAGE_FUNCTION_MPT_EX: if (!(parse_mpt_ex(vbi->cn, p, packet))) return FALSE; break; case PAGE_FUNCTION_EPG: #if 0 /* TODO */ parse_page_clear(vbi->epg_pc + ((cvtp->subno >> 8) & 1), p, packet); #endif break; case PAGE_FUNCTION_LOP: /* Parity check postponed until we received the X/26 enhancement packets pertaining to this page. See lop_parity_check(). */ memcpy(rvtp->lop_raw[packet], p, 40); rvtp->lop_packets |= 1 << packet; return TRUE; case PAGE_FUNCTION_EACEM_TRIGGER: for (n = i = 0; i < 40; i++) n |= vbi_unpar8 (p[i]); if (n < 0) return FALSE; /* fall through */ case PAGE_FUNCTION_MIP: default: memcpy(cvtp->data.unknown.raw[packet], p, 40); break; } cvtp->lop_packets |= 1 << packet; break; } case 26: { int designation; struct ttx_triplet triplet; int i; /* * Page enhancement packet */ switch (cvtp->function) { case PAGE_FUNCTION_DISCARD: return TRUE; case PAGE_FUNCTION_GPOP: case PAGE_FUNCTION_POP: return parse_pop(cvtp, p, packet); case PAGE_FUNCTION_GDRCS: case PAGE_FUNCTION_DRCS: case PAGE_FUNCTION_BTT: case PAGE_FUNCTION_AIT: case PAGE_FUNCTION_MPT: case PAGE_FUNCTION_MPT_EX: /* X/26 ? */ vbi_teletext_desync(vbi); return TRUE; case PAGE_FUNCTION_EACEM_TRIGGER: default: break; } if ((designation = vbi_unham8 (*p)) < 0) return FALSE; if (rvtp->num_triplets >= 16 * 13 || rvtp->num_triplets != designation * 13) { rvtp->num_triplets = -1; return FALSE; } for (p++, i = 0; i < 13; p += 3, i++) { int t = vbi_unham24p (p); if (t < 0) break; /* XXX */ triplet.address = t & 0x3F; triplet.mode = (t >> 6) & 0x1F; triplet.data = t >> 11; cvtp->data.enh_lop.enh[rvtp->num_triplets++] = triplet; } cvtp->x26_designations |= 1 << designation; break; } case 27: if (!parse_27(vbi, p, cvtp, mag0)) return FALSE; break; case 28: if (cvtp->function == PAGE_FUNCTION_DISCARD) break; /* fall through */ case 29: if (!parse_28_29(vbi, p, cvtp, mag8, packet)) return FALSE; break; case 30: case 31: /* * IDL packet (ETS 300 708) */ switch (/* Channel */ pmag & 15) { case 0: /* Packet 8/30 (ETS 300 706) */ if (!parse_8_30(vbi, buffer, packet)) return FALSE; break; default: break; } break; } return TRUE; } /* * ETS 300 706 Table 30: Colour Map */ static const vbi_rgba default_color_map[40] = { VBI_RGBA(0x00, 0x00, 0x00), VBI_RGBA(0xFF, 0x00, 0x00), VBI_RGBA(0x00, 0xFF, 0x00), VBI_RGBA(0xFF, 0xFF, 0x00), VBI_RGBA(0x00, 0x00, 0xFF), VBI_RGBA(0xFF, 0x00, 0xFF), VBI_RGBA(0x00, 0xFF, 0xFF), VBI_RGBA(0xFF, 0xFF, 0xFF), VBI_RGBA(0x00, 0x00, 0x00), VBI_RGBA(0x77, 0x00, 0x00), VBI_RGBA(0x00, 0x77, 0x00), VBI_RGBA(0x77, 0x77, 0x00), VBI_RGBA(0x00, 0x00, 0x77), VBI_RGBA(0x77, 0x00, 0x77), VBI_RGBA(0x00, 0x77, 0x77), VBI_RGBA(0x77, 0x77, 0x77), VBI_RGBA(0xFF, 0x00, 0x55), VBI_RGBA(0xFF, 0x77, 0x00), VBI_RGBA(0x00, 0xFF, 0x77), VBI_RGBA(0xFF, 0xFF, 0xBB), VBI_RGBA(0x00, 0xCC, 0xAA), VBI_RGBA(0x55, 0x00, 0x00), VBI_RGBA(0x66, 0x55, 0x22), VBI_RGBA(0xCC, 0x77, 0x77), VBI_RGBA(0x33, 0x33, 0x33), VBI_RGBA(0xFF, 0x77, 0x77), VBI_RGBA(0x77, 0xFF, 0x77), VBI_RGBA(0xFF, 0xFF, 0x77), VBI_RGBA(0x77, 0x77, 0xFF), VBI_RGBA(0xFF, 0x77, 0xFF), VBI_RGBA(0x77, 0xFF, 0xFF), VBI_RGBA(0xDD, 0xDD, 0xDD), /* Private colors */ VBI_RGBA(0x00, 0x00, 0x00), VBI_RGBA(0xFF, 0xAA, 0x99), VBI_RGBA(0x44, 0xEE, 0x00), VBI_RGBA(0xFF, 0xDD, 0x00), VBI_RGBA(0xFF, 0xAA, 0x99), VBI_RGBA(0xFF, 0x00, 0xFF), VBI_RGBA(0x00, 0xFF, 0xFF), VBI_RGBA(0xEE, 0xEE, 0xEE) }; /** * @param vbi Initialized vbi decoding context. * @param default_region A value between 0 ... 80, index into * the Teletext character set table according to ETS 300 706, * Section 15 (or libzvbi source file lang.c). The three last * significant bits will be replaced. * * Teletext uses a 7 bit character set. To support multiple * languages there are eight national variants which replace * the square bracket, backslash and other characters in a * fashion similar to ISO 646. These national variants are * selected by a 3 bit code in the header of each Teletext page. * * Eventually eight character sets proved to be insufficient, * so manufacturers of Teletext decoders interpreted these bits * differently in certain countries or regions. Teletext Level * 1.5 finally defined a method to transmit an 8 bit character * code which applies to a single page or provides the upper 5 * bits of the character code for all pages. * * Regrettably some networks still only transmit the lower 3 * bits. With this function you can supply an 8 bit default * character code for all pages. The built-in default is 16. */ void vbi_teletext_set_default_region(vbi_decoder *vbi, int default_region) { int i; if (default_region < 0 || default_region > 87) return; vbi->vt.region = default_region; for (i = 0x100; i <= 0x800; i += 0x100) { struct ttx_extension *ext; ext = &cache_network_magazine (vbi->cn, i)->extension; ext->charset_code[0] = default_region; ext->charset_code[1] = 0; } vbi->vt.default_magazine.extension.charset_code[0] = default_region; vbi->vt.default_magazine.extension.charset_code[1] = 0; } /** * @param vbi Initialized vbi decoding context. * @param level * * @deprecated * This became a parameter of vbi_fetch_vt_page(). */ void vbi_teletext_set_level(vbi_decoder *vbi, int level) { if (level < VBI_WST_LEVEL_1) level = VBI_WST_LEVEL_1; else if (level > VBI_WST_LEVEL_3p5) level = VBI_WST_LEVEL_3p5; vbi->vt.max_level = level; } /** * @internal * @param vbi Initialized vbi decoding context. * * This function must be called after desynchronisation * has been detected (i. e. vbi data has been lost) * to reset the Teletext decoder. */ void vbi_teletext_desync(vbi_decoder *vbi) { int i; /* Discard all in progress pages */ for (i = 0; i < 8; i++) vbi->vt.raw_page[i].page->function = PAGE_FUNCTION_DISCARD; #if 0 /* TODO */ vbi_reset_page_clear(vbi->epg_pc + 0); vbi_reset_page_clear(vbi->epg_pc + 1); vbi->epg_pc[0].pfc.stream = 1; vbi->epg_pc[1].pfc.stream = 2; #endif } static void ttx_extension_init (struct ttx_extension * ext) { unsigned int i; CLEAR (*ext); ext->def_screen_color = VBI_BLACK; /* A.5 */ ext->def_row_color = VBI_BLACK; /* A.5 */ for (i = 0; i < 8; ++i) ext->drcs_clut[2 + i] = i & 3; for (i = 0; i < 32; ++i) ext->drcs_clut[2 + 8 + i] = i & 15; memcpy (ext->color_map, default_color_map, sizeof (ext->color_map)); } static void ttx_magazine_init (struct ttx_magazine * mag) { ttx_extension_init (&mag->extension); /* Valid range 0 ... 7, -1 == broken link. */ memset (mag->pop_lut, -1, sizeof (mag->pop_lut)); memset (mag->drcs_lut, -1, sizeof (mag->pop_lut)); /* NO_PAGE (pgno): (pgno & 0xFF) == 0xFF. */ memset (mag->pop_link, -1, sizeof (mag->pop_link)); memset (mag->drcs_link, -1, sizeof (mag->drcs_link)); } static void ttx_page_stat_init (struct ttx_page_stat * ps) { CLEAR (*ps); ps->page_type = VBI_UNKNOWN_PAGE; ps->charset_code = 0xFF; ps->subcode = SUBCODE_UNKNOWN; } /** * @param vbi Initialized vbi decoding context. * * This function must be called after a channel switch, * to reset the Teletext decoder. */ void vbi_teletext_channel_switched(vbi_decoder *vbi) { unsigned int i; vbi->cn->initial_page.pgno = 0x100; vbi->cn->initial_page.subno = VBI_ANY_SUBNO; vbi->cn->have_top = FALSE; for (i = 0; i < N_ELEMENTS (vbi->cn->_pages); ++i) ttx_page_stat_init (vbi->cn->_pages + i); /* Magazine defaults */ for (i = 0; i < N_ELEMENTS (vbi->cn->_magazines); ++i) ttx_magazine_init (vbi->cn->_magazines + i); vbi_teletext_set_default_region(vbi, vbi->vt.region); vbi_teletext_desync(vbi); } /** * @internal * @param vbi VBI decoding context. * * This function is called during @a vbi destruction * to destroy the Teletext subset of @a vbi object. */ void vbi_teletext_destroy(vbi_decoder *vbi) { vbi = vbi; } /** * @internal * @param vbi VBI decoding context. * * This function is called during @a vbi initialization * to initialize the Teletext subset of @a vbi object. */ void vbi_teletext_init(vbi_decoder *vbi) { init_expand(); vbi->vt.region = 16; vbi->vt.max_level = VBI_WST_LEVEL_2p5; ttx_magazine_init (&vbi->vt.default_magazine); vbi_teletext_channel_switched(vbi); /* Reset */ } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/page_table.c000066400000000000000000000557751476363111200156200ustar00rootroot00000000000000/* * libzvbi -- Table of Teletext page numbers * * Copyright (C) 2006, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: page_table.c,v 1.5 2008-02-19 00:35:20 mschimek Exp $ */ /* Note this module is not an offical part of the library yet because it needs more testing and the interface may change. Use at your own risk. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "misc.h" #include "page_table.h" /** * addtogroup PageTable Teletext Page Number Table * ingroup LowDec * brief A set of Teletext page numbers. * * Sometimes application want to operate on multiple Teletext * pages or subpages. The vbi_page_table structure can simply and * efficiently remember the page numbers for you. It is used for * example by the vbi_sliced_filter to remember the Teletext pages * the caller wishes to keep or drop. * * The vbi_page_table is optimized for fast queries, while adding or * removing pages and especially subpages may take longer. */ /* 0 ... 0x3F7E; 0x3F7F == VBI_ANY_SUBNO. */ #define MAX_SUBNO 0x3F7E /* XXX Later. */ enum { VBI_ERR_INVALID_PGNO = 0, VBI_ERR_INVALID_SUBNO = 0, }; struct subpage_range { /* 0x100 ... 0x8FF. */ vbi_pgno pgno; /* 0x0000 ... MAX_SUBNO. */ vbi_subno first; /* 0x0000 ... MAX_SUBNO, last >= first. */ vbi_subno last; }; struct _vbi_page_table { /* One bit for each Teletext page with subpage range 0 ... MAX_SUBNO. These are not in the subpages vector. vbi_pgno 0x100 -> pages[0] & 1. */ uint32_t pages[(0x900 - 0x100) / 32]; /* Number of set bits in the pages[] array. */ unsigned int pages_popcnt; /* A vector of subpages, current size and capacity (counting struct subpage_range). */ struct subpage_range * subpages; unsigned int subpages_size; unsigned int subpages_capacity; }; static __inline__ vbi_bool valid_pgno (vbi_pgno pgno) { return ((unsigned int) pgno - 0x100 < 0x800); } static vbi_bool contains_all_subpages (const vbi_page_table *pt, vbi_pgno pgno) { uint32_t mask; unsigned int offset; mask = 1 << (pgno & 31); offset = (pgno - 0x100) >> 5; return (0 != (pt->pages[offset] & mask)); } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno The page number in question. Need not be a valid * Teletext page number. * * The function returns @c TRUE if the page @a pgno and all its * subpages have been added to the page table. */ vbi_bool vbi_page_table_contains_all_subpages (const vbi_page_table *pt, vbi_pgno pgno) { assert (NULL != pt); if (unlikely (!valid_pgno (pgno))) return FALSE; return contains_all_subpages (pt, pgno); } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno The page number in question. Need not be a valid * Teletext page number. * @param subno The subpage number in question. Need not be a valid * Teletext subpage number. Can be @c VBI_ANY_SUBNO. * * The function returns @c TRUE if subpage @a subno of page @a pgno * has been added to the page table. */ vbi_bool vbi_page_table_contains_subpage (const vbi_page_table *pt, vbi_pgno pgno, vbi_subno subno) { unsigned int i; assert (NULL != pt); if (unlikely (!valid_pgno (pgno))) return FALSE; if (contains_all_subpages (pt, pgno)) return TRUE; if (VBI_ANY_SUBNO == subno) { for (i = 0; i < pt->subpages_size; ++i) { if (pgno == pt->subpages[i].pgno) return TRUE; } } else { for (i = 0; i < pt->subpages_size; ++i) { if (pgno == pt->subpages[i].pgno && subno >= pt->subpages[i].first && subno <= pt->subpages[i].last) return TRUE; } } return FALSE; } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno Pointer to a page number. The function stores here the * next higher page number which has been added to the page table. * @param subno Pointer to a subpage number. The function stores here the * next higher subpage number which has been added to the page table. * * This function can be used to iterate over the page and subpage * numbers which have been added to the page table. * * When @a *pgno is less than 0x100 it will return the lowest * page and subpage number in the page table. * * Otherwise it will return the next subpage of this page, or if no * higher subpages of this page have been added the first subpage of * the next higher page number in the table. A @a *subno value of * @c VBI_ANY_SUBNO stands for the highest subpage number in the * table, so the function will also return the first subpage of the * next higher page. * * When all subpages of the returned @a *pgno are in the table, the * returned @a *subno will be @c VBI_ANY_SUBNO. (This is the common * case and there is no point in iterating through all the subpages.) * * When no page numbers higher than @a *pgno are in the table, or * when this is the highest page and @a *subno is VBI_ANY_SUBNO or * there are no subpage numbers higher than @a *subno, the function * returns @c FALSE. * * @code * vbi_pgno pgno = 0; * vbi_subno subno; * * // Iterate over the subpages of all pages. * while (!vbi_page_table_next_subpage (pt, &pgno, &subno) { * // Do things on page pgno, subno. * // subno is in range 0 to 0x3F7E inclusive, or VBI_ANY_SUBNO. * } * @endcode */ vbi_bool vbi_page_table_next_subpage (const vbi_page_table *pt, vbi_pgno * pgno, vbi_subno * subno) { vbi_pgno last_pgno; vbi_pgno last_subno; vbi_pgno next_pgno; vbi_pgno next_subno; vbi_pgno min_pgno; vbi_subno min_subno; uint32_t mask; unsigned int offset; unsigned int i; assert (NULL != pt); assert (NULL != pgno); assert (NULL != subno); last_pgno = *pgno; last_subno = *subno; if (last_pgno >= 0x8FF) { return FALSE; } else if (last_pgno < 0x100) { next_pgno = 0x100; } else { if (last_subno <= MAX_SUBNO /* not ANY */) { next_subno = last_subno + 1; min_subno = MAX_SUBNO + 1; for (i = 0; i < pt->subpages_size; ++i) { if (last_pgno != pt->subpages[i].pgno) continue; if (next_subno > pt->subpages[i].last) continue; if (next_subno >= pt->subpages[i].first) { *subno = next_subno; return TRUE; } if (pt->subpages[i].first < min_subno) min_subno = pt->subpages[i].first; } if (min_subno <= MAX_SUBNO) { *subno = min_subno; return TRUE; } } next_pgno = last_pgno + 1; } min_pgno = 0x900; for (i = 0; i < pt->subpages_size; ++i) { if (next_pgno <= pt->subpages[i].pgno && next_pgno < min_pgno) { min_pgno = pt->subpages[i].pgno; min_subno = pt->subpages[i].first; } } mask = -1 << (next_pgno & 31); offset = (next_pgno - 0x100) >> 5; mask &= pt->pages[offset]; next_pgno &= ~31; for (;;) { if (0 != mask) break; next_pgno += 32; if (next_pgno >= 0x900) return FALSE; mask = pt->pages[++offset]; } #ifdef HAVE_FFS next_pgno += ffs (mask) - 1; #elif defined HAVE___BUILTIN_FFS next_pgno += __builtin_ffs (mask) - 1; #else for (i = 0; i < 32; ++i) { if (0 != (mask & (1 << i))) { next_pgno += i; break; } } #endif if (min_pgno < next_pgno) { *pgno = min_pgno; *subno = min_subno; } else { *pgno = next_pgno; *subno = VBI_ANY_SUBNO; } return TRUE; } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno Pointer to a page number. The function stores here the * next higher page number which has been added to the page table. * * This function can be used to iterate over the page numbers * which have been added to the page table. If multiple subpages * of a page have been added, the function returns this page * number only once. * * When @a *pgno is less than 0x100 it returns the lowest page number * in the page table, otherwise the next higher page number which has * been added. When there are no higher page numbers it returns @c FALSE. * * @code * vbi_pgno pgno = 0; * * // Iterate over all pages. * while (!vbi_page_table_next_page (pt, &pgno) { * // Do things on page pgno. * } * @endcode */ vbi_bool vbi_page_table_next_page (const vbi_page_table *pt, vbi_pgno * pgno) { vbi_subno subno = VBI_ANY_SUBNO; return vbi_page_table_next_subpage (pt, pgno, &subno); } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * * This function returns the number of pages which have been added * to the page table. Multiple subpages of a page count as one page. * * This is a fast function. It just returns the value of a counter * maintained by the add and remove functions. */ unsigned int vbi_page_table_num_pages (const vbi_page_table *pt) { assert (NULL != pt); return pt->pages_popcnt + pt->subpages_size; } static void shrink_vector (void ** vector, unsigned int * capacity, unsigned int min_capacity, unsigned int element_size) { void *new_vec; unsigned int new_capacity; if (min_capacity >= *capacity) return; new_capacity = min_capacity; new_vec = vbi_realloc (*vector, new_capacity * element_size); if (unlikely (NULL == new_vec)) return; *vector = new_vec; *capacity = new_capacity; } static vbi_bool extend_vector (void ** vector, unsigned int * capacity, unsigned int min_capacity, unsigned int element_size) { void *new_vec; unsigned int new_capacity; unsigned int max_capacity; assert (min_capacity > 0); assert (element_size > 0); /* This looks a bit odd to prevent overflows. */ max_capacity = UINT_MAX / element_size; if (unlikely (min_capacity > max_capacity)) { errno = ENOMEM; return FALSE; } new_capacity = *capacity; if (unlikely (new_capacity > (max_capacity / 2))) { new_capacity = max_capacity; } else { new_capacity = MIN (min_capacity, new_capacity * 2); } new_vec = vbi_realloc (*vector, new_capacity * element_size); if (unlikely (NULL == new_vec)) { /* XXX we should try less new_capacity before giving up. */ errno = ENOMEM; return FALSE; } *vector = new_vec; *capacity = new_capacity; return TRUE; } static void shrink_subpages_vector (vbi_page_table * pt) { if (pt->subpages_size >= pt->subpages_capacity / 4) return; shrink_vector ((void **) &pt->subpages, &pt->subpages_capacity, pt->subpages_capacity / 2, sizeof (*pt->subpages)); } static vbi_bool extend_subpages_vector (vbi_page_table * pt, unsigned int min_capacity) { if (min_capacity <= pt->subpages_capacity) return TRUE; return extend_vector ((void **) &pt->subpages, &pt->subpages_capacity, min_capacity, sizeof (*pt->subpages)); } static vbi_bool valid_subpage_range (vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) { if (unlikely (!valid_pgno (pgno))) { errno = VBI_ERR_INVALID_PGNO; return FALSE; } if (unlikely ((unsigned int) first_subno > MAX_SUBNO || (unsigned int) last_subno > MAX_SUBNO)) { errno = VBI_ERR_INVALID_SUBNO; return FALSE; } return TRUE; } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno The page in question. Must be in range 0x100 to 0x8FF * inclusive. * @param first_subno First subpage number to remove. * @param last_subno Last subpage number to remove. Both @a first_subno * and @a last_subno must be in range 0 to 0x3F7E inclusive, or * both must be @c VBI_ANY_SUBNO. * * This function removes the Teletext subpages of page @a pgno from * @a first_subno to @a last_subno inclusive. When @a first_subno * and @a last_subno is @c VBI_ANY_SUBNO, it removes the page and * all its subpages as vbi_page_table_remove_page() does. * * @a returns * @c FALSE on failure (invalid page or subpage numbers or out of memory). */ vbi_bool vbi_page_table_remove_subpages (vbi_page_table * pt, vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) { uint32_t mask; unsigned int offset; unsigned int i; assert (NULL != pt); if (VBI_ANY_SUBNO == first_subno && VBI_ANY_SUBNO == last_subno) return vbi_page_table_remove_pages (pt, pgno, pgno); if (unlikely (!valid_subpage_range (pgno, first_subno, last_subno))) return FALSE; if (first_subno > last_subno) SWAP (first_subno, last_subno); mask = 1 << (pgno & 31); offset = (pgno - 0x100) >> 5; if (0 != (pt->pages[offset] & mask)) { i = pt->subpages_size; if (!extend_subpages_vector (pt, i + 2)) return FALSE; --pt->pages_popcnt; pt->pages[offset] &= ~mask; if (first_subno > 0) { pt->subpages[i].pgno = pgno; pt->subpages[i].first = 0; pt->subpages[i++].last = first_subno - 1; } if (last_subno < MAX_SUBNO) { pt->subpages[i].pgno = pgno; pt->subpages[i].first = last_subno + 1; pt->subpages[i++].last = MAX_SUBNO; } pt->subpages_size = i; return TRUE; } for (i = 0; i < pt->subpages_size; ++i) { if (pgno != pt->subpages[i].pgno) continue; if (first_subno > pt->subpages[i].last) continue; if (last_subno < pt->subpages[i].first) continue; if (first_subno > pt->subpages[i].first && last_subno < pt->subpages[i].last) { if (!extend_subpages_vector (pt, pt->subpages_size + 1)) return FALSE; memmove (&pt->subpages[i + 1], &pt->subpages[i], (pt->subpages_size - i) * sizeof (*pt->subpages)); pt->subpages[i].last = first_subno; pt->subpages[i + 1].first = last_subno + 1; ++pt->subpages_size; ++i; continue; } if (first_subno > pt->subpages[i].first) pt->subpages[i].first = first_subno; if (last_subno < pt->subpages[i].last) pt->subpages[i].last = last_subno; if (pt->subpages[i].first > pt->subpages[i].last) { memmove (&pt->subpages[i], &pt->subpages[i + 1], (pt->subpages_size - i) * sizeof (*pt->subpages)); --pt->subpages_size; --i; } } shrink_subpages_vector (pt); return TRUE; } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno The page in question. Must be in range 0x100 to 0x8FF * inclusive. * @param first_subno First subpage number to add. * @param last_subno Last subpage number to add. Both @a first_subno * and @a last_subno must be in range 0 to 0x3F7E inclusive, or * both must be @c VBI_ANY_SUBNO. * * This function adds the Teletext subpages of page @a pgno from * from @a first_subno to @a last_subno inclusive. When @a first_subno * and @a last_subno is @c VBI_ANY_SUBNO, it adds all subpages as * vbi_page_table_add_page() does. * * @a returns * @c FALSE on failure (invalid page or subpage numbers or out of memory). */ vbi_bool vbi_page_table_add_subpages (vbi_page_table * pt, vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) { unsigned int i; assert (NULL != pt); if (VBI_ANY_SUBNO == first_subno && VBI_ANY_SUBNO == last_subno) return vbi_page_table_add_pages (pt, pgno, pgno); if (unlikely (!valid_subpage_range (pgno, first_subno, last_subno))) return FALSE; if (vbi_page_table_contains_page (pt, pgno)) return TRUE; if (first_subno > last_subno) SWAP (first_subno, last_subno); for (i = 0; i < pt->subpages_size; ++i) { if (pgno == pt->subpages[i].pgno && last_subno >= pt->subpages[i].first && first_subno <= pt->subpages[i].last) { if (first_subno < pt->subpages[i].first) pt->subpages[i].first = first_subno; if (last_subno > pt->subpages[i].last) pt->subpages[i].last = last_subno; return TRUE; } } if (!extend_subpages_vector (pt, i + 1)) return FALSE; pt->subpages[i].pgno = pgno; pt->subpages[i].first = first_subno; pt->subpages[i].last = last_subno; pt->subpages_size = i + 1; return TRUE; } static void remove_subpages_in_page_range (vbi_page_table * pt, vbi_pgno first_pgno, vbi_pgno last_pgno) { unsigned int i; unsigned int j; for (i = 0, j = 0; i < pt->subpages_size; ++i) { if (pt->subpages[i].pgno < first_pgno || pt->subpages[i].pgno > last_pgno) { if (j < i) { memcpy (&pt->subpages[j], &pt->subpages[i], sizeof (*pt->subpages)); } ++j; } } pt->subpages_size = j; shrink_subpages_vector (pt); } static vbi_bool valid_pgno_range (vbi_pgno first_pgno, vbi_pgno last_pgno) { if (likely (valid_pgno (first_pgno) && valid_pgno (last_pgno))) return TRUE; errno = VBI_ERR_INVALID_PGNO; return FALSE; } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param first_pgno First page number to remove. * @param last_pgno Last page number to remove. Both @a first_pgno and * @a last_pgno must be in range 0x100 to 0x8FF inclusive. * * This function removes all Teletext pages from @a first_pgno to * @a last_pgno inclusive, also non-displayable system pages with * hex digits in the page number, and all their subpages from the * page table. * * @a returns * @c FALSE on failure (invalid page numbers or out of memory). */ vbi_bool vbi_page_table_remove_pages (vbi_page_table * pt, vbi_pgno first_pgno, vbi_pgno last_pgno) { uint32_t first_mask; uint32_t last_mask; uint32_t old_mask; unsigned int first_offset; unsigned int last_offset; assert (NULL != pt); if (unlikely (!valid_pgno_range (first_pgno, last_pgno))) return FALSE; if (first_pgno > last_pgno) SWAP (first_pgno, last_pgno); if (0x8FF == last_pgno && 0x100 == first_pgno) { pt->subpages_size = 0; shrink_subpages_vector (pt); memset (pt->pages, 0, sizeof (pt->pages)); pt->pages_popcnt = 0; return TRUE; } remove_subpages_in_page_range (pt, first_pgno, last_pgno); /* 0 -> 0xFFFF FFFF, 1 -> 0xFFFF FFFE, 31 -> 0x8000 0000. */ first_mask = -1 << (first_pgno & 31); first_offset = (first_pgno - 0x100) >> 5; /* 0 -> 0x01, 1 -> 0x03, 31 -> 0xFFFF FFFF. */ last_mask = ~(-2 << (last_pgno & 31)); last_offset = (last_pgno - 0x100) >> 5; if (first_offset != last_offset) { old_mask = pt->pages[first_offset]; pt->pages_popcnt -= popcnt (old_mask & first_mask); pt->pages[first_offset] = old_mask & ~first_mask; first_mask = -1; while (++first_offset < last_offset) { old_mask = pt->pages[first_offset]; pt->pages_popcnt -= popcnt (old_mask); pt->pages[first_offset] = 0; } } old_mask = pt->pages[last_offset]; last_mask &= first_mask; pt->pages_popcnt -= popcnt (old_mask & last_mask); pt->pages[last_offset] = old_mask & ~last_mask; return TRUE; } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param first_pgno First page number to add. * @param last_pgno Last page number to add. Both @a first_pgno and * @a last_pgno must be in range 0x100 to 0x8FF inclusive. * * This function adds all Teletext pages from @a first_pgno to * @a last_pgno inclusive, also non-displayable system pages with * hex digits in the page number, and all their subpages to the page * table. * * @a returns * @c FALSE on failure (invalid page numbers or out of memory). */ vbi_bool vbi_page_table_add_pages (vbi_page_table * pt, vbi_pgno first_pgno, vbi_pgno last_pgno) { uint32_t first_mask; uint32_t last_mask; uint32_t old_mask; unsigned int first_offset; unsigned int last_offset; assert (NULL != pt); if (unlikely (!valid_pgno_range (first_pgno, last_pgno))) return FALSE; if (first_pgno > last_pgno) SWAP (first_pgno, last_pgno); if (0x8FF == last_pgno && 0x100 == first_pgno) { pt->subpages_size = 0; shrink_subpages_vector (pt); memset (pt->pages, -1, sizeof (pt->pages)); pt->pages_popcnt = 0x800; return TRUE; } /* Remove duplicates of pages[] in subpages. */ remove_subpages_in_page_range (pt, first_pgno, last_pgno); /* 0 -> 0xFFFF FFFF, 1 -> 0xFFFF FFFE, 31 -> 0x8000 0000. */ first_mask = -1 << (first_pgno & 31); first_offset = (first_pgno - 0x100) >> 5; /* 0 -> 0x01, 1 -> 0x03, 31 -> 0xFFFF FFFF. */ last_mask = ~(-2 << (last_pgno & 31)); last_offset = (last_pgno - 0x100) >> 5; if (first_offset != last_offset) { old_mask = pt->pages[first_offset]; pt->pages_popcnt += popcnt (first_mask & ~old_mask); pt->pages[first_offset] = first_mask | old_mask; first_mask = -1; while (++first_offset < last_offset) { old_mask = pt->pages[first_offset]; pt->pages_popcnt += 32 - popcnt (old_mask); pt->pages[first_offset] = -1; } } old_mask = pt->pages[last_offset]; last_mask &= first_mask; pt->pages_popcnt += popcnt (last_mask & ~old_mask); pt->pages[last_offset] = last_mask | old_mask; return TRUE; } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * * This function removes all Teletext pages from 0x100 to 0x8FF * inclusive and all their subpages from the page table. */ void vbi_page_table_remove_all_pages (vbi_page_table * pt) { vbi_page_table_remove_pages (pt, 0x100, 0x8FF); } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * * This function adds all displayable Teletext pages from 0x100 to * 0x899 (i.e. all page numbers which are valid BCD numbers) and all * their subpages to the page table. */ void vbi_page_table_add_all_displayable_pages (vbi_page_table * pt) { vbi_pgno pgno; assert (NULL != pt); for (pgno = 0x100; pgno < 0x900; pgno += 0x100 - 0xA0) { vbi_pgno end_pgno = pgno + 0xA0; do { uint32_t mask; uint32_t old_mask; unsigned int offset; remove_subpages_in_page_range (pt, pgno + 0x00, pgno + 0x09); remove_subpages_in_page_range (pt, pgno + 0x10, pgno + 0x19); mask = 0x03FF03FF; offset = (pgno - 0x100) >> 5; old_mask = pt->pages[offset]; pt->pages_popcnt += popcnt (mask & ~old_mask); pt->pages[offset] = mask | old_mask; pgno += 0x20; } while (pgno < end_pgno); } } /** * @param pt Teletext page table allocated with vbi_page_table_new(). * * This function adds all Teletext pages from 0x100 to 0x8FF inclusive * and all their subpages to the page table. */ void vbi_page_table_add_all_pages (vbi_page_table * pt) { vbi_page_table_add_pages (pt, 0x100, 0x8FF); } /** * @param pt Teletext page table allocated with vbi_page_table_new(), * can be @c NULL. * * Frees all resources associated with @a pt. */ void vbi_page_table_delete (vbi_page_table * pt) { if (NULL == pt) return; vbi_free (pt->subpages); CLEAR (*pt); vbi_free (pt); } /** * Allocates a new Teletext page number table. Initially no page numbers * are in the table. * * @returns * @c NULL on failure (out of memory). */ vbi_page_table * vbi_page_table_new (void) { vbi_page_table *pt; pt = vbi_malloc (sizeof (*pt)); if (NULL == pt) { return NULL; } CLEAR (*pt); return pt; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/page_table.h000066400000000000000000000143551476363111200156120ustar00rootroot00000000000000/* * libzvbi -- Table of Teletext page numbers * * Copyright (C) 2006, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: page_table.h,v 1.5 2008-02-24 14:17:16 mschimek Exp $ */ /* Note this module is not an offical part of the library yet because it needs more testing and the interface may change. Use at your own risk. */ #ifndef __ZVBI_PAGE_TABLE_H__ #define __ZVBI_PAGE_TABLE_H__ #include "macros.h" #include "bcd.h" VBI_BEGIN_DECLS typedef struct _vbi_page_table vbi_page_table; extern vbi_bool vbi_page_table_contains_all_subpages (const vbi_page_table *pt, vbi_pgno pgno) _vbi_nonnull ((1)); extern vbi_bool vbi_page_table_contains_subpage (const vbi_page_table *pt, vbi_pgno pgno, vbi_subno subno) _vbi_nonnull ((1)); /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno The page number in question. Need not be a valid * Teletext page number. * * The function returns @c TRUE if any subpages of page @a pgno * have been added to the page table. */ static __inline__ vbi_bool vbi_page_table_contains_page (const vbi_page_table *pt, vbi_pgno pgno) { return vbi_page_table_contains_subpage (pt, pgno, VBI_ANY_SUBNO); } extern vbi_bool vbi_page_table_next_subpage (const vbi_page_table *pt, vbi_pgno * pgno, vbi_subno * subno) _vbi_nonnull ((1, 2, 3)); extern vbi_bool vbi_page_table_next_page (const vbi_page_table *pt, vbi_pgno * pgno) _vbi_nonnull ((1, 2)); extern unsigned int vbi_page_table_num_pages (const vbi_page_table *pt) _vbi_nonnull ((1)); extern vbi_bool vbi_page_table_remove_subpages (vbi_page_table * pt, vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) _vbi_nonnull ((1)); /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno The page in question. Must be in range 0x100 to 0x8FF * inclusive. * @param subno The subpage number to remove. Must be in range 0 to * 0x3F7E inclusive, or VBI_ANY_SUBNO. * * This function removes subpage @a subno of page @a pgno from * the page table. When @a subno is @c VBI_ANY_SUBNO, it removes * the page and all its subpages as vbi_page_table_remove_page() does. * * @a returns * @c FALSE on failure (invalid page or subpage number or out of memory). */ static __inline__ vbi_bool vbi_page_table_remove_subpage (vbi_page_table * pt, vbi_pgno pgno, vbi_subno subno) { return vbi_page_table_remove_subpages (pt, pgno, subno, subno); } extern vbi_bool vbi_page_table_add_subpages (vbi_page_table * pt, vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) _vbi_nonnull ((1)); /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno The page in question. Must be in range 0x100 to 0x8FF * inclusive. * @param subno The subpage number to add. Must be in range 0 to 0x3F7E * inclusive, or VBI_ANY_SUBNO. * * This function adds subpage @a subno of page @a pgno to * the page table. When @a subno is @c VBI_ANY_SUBNO, it adds * all subpages of page @a pgno as vbi_page_table_add_page() does. * * @a returns * @c FALSE on failure (invalid page or subpage number or out of memory). */ static __inline__ vbi_bool vbi_page_table_add_subpage (vbi_page_table * pt, vbi_pgno pgno, vbi_subno subno) { return vbi_page_table_add_subpages (pt, pgno, subno, subno); } extern vbi_bool vbi_page_table_remove_pages (vbi_page_table * pt, vbi_pgno first_pgno, vbi_pgno last_pgno) _vbi_nonnull ((1)); /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno The page to remove. Must be in range 0x100 to 0x8FF * inclusive. * * This function removes page @a pgno and all its subpages from the * page table. * * @a returns * @c FALSE on failure (invalid page number or out of memory). */ static __inline__ vbi_bool vbi_page_table_remove_page (vbi_page_table * pt, vbi_pgno pgno) { return vbi_page_table_remove_pages (pt, pgno, pgno); } extern vbi_bool vbi_page_table_add_pages (vbi_page_table * pt, vbi_pgno first_pgno, vbi_pgno last_pgno) _vbi_nonnull ((1)); /** * @param pt Teletext page table allocated with vbi_page_table_new(). * @param pgno The page to add. Must be in range 0x100 to 0x8FF * inclusive. * * This function adds page @a pgno and all its subpages to the * page table. * * Note if a Teletext page has subpages the transmitted subpage number * is generally in range 1 to N. For pages without subpages (just a * single page) the subpage number zero is transmitted. * * Use this function to match page @a pgno regardless if it has * subpages. Do not explicitely add subpage zero of page @a pgno * (with vbi_page_table_add_subpage()) unless you want to match * @a pgno only if it has no subpages, as subpage lookups are * considerably less efficient. * * @a returns * @c FALSE on failure (invalid page number or out of memory). */ static __inline__ vbi_bool vbi_page_table_add_page (vbi_page_table * pt, vbi_pgno pgno) { return vbi_page_table_add_pages (pt, pgno, pgno); } extern void vbi_page_table_remove_all_pages (vbi_page_table * pt) _vbi_nonnull ((1)); extern void vbi_page_table_add_all_displayable_pages (vbi_page_table * pt) _vbi_nonnull ((1)); extern void vbi_page_table_add_all_pages (vbi_page_table * pt) _vbi_nonnull ((1)); extern void vbi_page_table_delete (vbi_page_table * pt); extern vbi_page_table * vbi_page_table_new (void); VBI_END_DECLS #endif /* __ZVBI_PAGE_TABLE_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/pdc.c000066400000000000000000001130431476363111200142620ustar00rootroot00000000000000/* * libzvbi - Program Delivery Control * * Copyright (C) 2000-2004 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* $Id: pdc.c,v 1.4 2009-03-23 01:30:33 mschimek Exp $ */ #include "../site_def.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifndef _POSIX_C_SOURCE # define _POSIX_C_SOURCE 200112L // needed for localtime_r() and others on mingw-w64 #endif #include #include /* FLT_MAX, DBL_MAX */ #include #ifdef _WIN32 # include static int setenv(const char *name, const char *value, int overwrite) { if (!overwrite) { char *old_value = getenv(name); if (old_value) { return 0; } } return SetEnvironmentVariable(name, value) ? 0 : -1; } static int unsetenv(const char *name) { return SetEnvironmentVariable(name, NULL) ? 0 : -1; } #endif #include "misc.h" #include "hamm.h" /* vbi_unpar8() */ #include "bcd.h" /* vbi_is_bcd() */ #include "pdc.h" #include "conv.h" /** * @addtogroup ProgramID VPS/PDC Program ID * @ingroup LowDec * @brief Functions to decode VPS/PDC Program IDs and helper functions. * * Program IDs are transmitted by networks to remotely control video * recorders. They can be used to * - start and stop recording exactly when a program starts and ends, * even when the program is early or late or overruns, * - pause recording during a planned or unplanned interruption * (you can safely assume that no station transmits a pause code * during commercial breaks), * - keep recording a program which continues on a different channel, * - record a program after it has been postponed to a later date * and possibly a different channel, * - record all episodes of a series without prior knowledge of the * broadcast times, * - alert the user about emergency messages. * * The basic principle is to transmit a label along with the program * containing the originally announced start date and time. When * the label is no longer transmitted the program has ended. When two * programs on different channels are scheduled for recording the * recorder may have to scan the channels alternately. Better accuracy * than a few seconds within the actual start should not be expected. * * Libzvbi supports Program IDs transmitted in Teletext packet 8/30 * format 2 and in VPS packets as defined in EN 300 231 "Television * systems; Specification of the domestic video Programme Delivery * Control system (PDC)", and DVB PDC descriptors as defined in EN 300 * 468 "Specification for Service Information (SI) in DVB systems". * Support for XDS Current/Future Program ID packets as defined in EIA * 608-B "Recommended Practice for Line 21 Data Service" is planned * but not implemented yet. * * Program IDs are available through the low level functions * vbi_decode_teletext_8302_pdc(), vbi_decode_vps_pdc(), * vbi_decode_dvb_pdc_descriptor() and the @a vbi_decoder event @c * VBI_EVENT_PROG_ID. * * @example examples/pdc1.c * @example examples/pdc2.c */ /* XXX Preliminary, will not be returned to clients. */ enum { VBI_ERR_NO_TIME = 0x7081900, VBI_ERR_INVALID_PIL, }; /** * @internal * @param pil vbi_pil to print. * @param fp Destination stream. * * Prints a vbi_pil as service code or date and time string without * trailing newline. This is intended for debugging. */ void _vbi_pil_dump (vbi_pil pil, FILE * fp) { switch (pil) { case VBI_PIL_TIMER_CONTROL: fputs ("TC", fp); break; case VBI_PIL_INHIBIT_TERMINATE: fputs ("RI/T", fp); break; case VBI_PIL_INTERRUPTION: fputs ("INT", fp); break; case VBI_PIL_CONTINUE: fputs ("CONT", fp); break; case VBI_PIL_NSPV: /* VBI_PIL_NSPV (PDC) == VBI_PIL_END (XDS) */ fputs ("NSPV/END", fp); break; default: fprintf (fp, "%05x (%02u-%02u %02u:%02u)", pil, VBI_PIL_MONTH (pil), VBI_PIL_DAY (pil), VBI_PIL_HOUR (pil), VBI_PIL_MINUTE (pil)); break; } } #if 3 != VBI_VERSION_MINOR # define vbi_cni_type_name(type) "" #endif /** * @internal * @param pid vbi_program_id structure to print. * @param fp Destination stream. * * Prints the contents of a vbi_program_id structure as a string * without trailing newline. This is intended for debugging. */ void _vbi_program_id_dump (const vbi_program_id * pid, FILE * fp) { static const char *pcs_audio [] = { "UNKNOWN", "MONO", "STEREO", "BILINGUAL" }; fprintf (fp, "ch=%u cni=%04x (%s) pil=", pid->channel, pid->cni, vbi_cni_type_name (pid->cni_type)); _vbi_pil_dump (pid->pil, fp); fprintf (fp, " luf=%u mi=%u prf=%u " "pcs=%s pty=%02x tape_delayed=%u", pid->luf, pid->mi, pid->prf, pcs_audio[pid->pcs_audio], pid->pty, pid->tape_delayed); } /** * @internal * @param pil The PIL will be stored here. * @param inout_s Pointer to input buffer pointer, which will be * incremented by the number of bytes read. The buffer must contain * a NUL-terminated ASCII or UTF-8 string. * * Converts a date of the format MM-DDThh:mm to a PIL. MM must be in * range 00 ... 15, DD and hh in range 00 ... 31 and mm in range 00 * ... 63. The MM-DDT part can be omitted. In this case day and month * zero will be stored in @a pil. The separators -T: can be omitted. * Additionally the symbols "cont[inue]", "end", "inhibit", * "int[erruption]", "nspv", "rit", "terminate", "tc" and "timer" are * recognized and converted to the respective PIL service * code. Leading white space and upper/lower case is ignored. * * You can call the vbi_pil_is_valid_date() function to determine if * a valid date and time or service code has been entered. * * @returns * @c FALSE on syntax errors. In this case @a *pil and @a *inout_s * remain unmodified. */ vbi_bool _vbi_pil_from_string (vbi_pil * pil, const char ** inout_s) { const char *s; unsigned int value[4]; unsigned int i; unsigned int n_fields; unsigned int sep_mask; assert (NULL != pil); assert (NULL != inout_s); assert (NULL != *inout_s); s = *inout_s; while (isspace (*s)) ++s; if (!isdigit (*s)) { static const _vbi_key_value_pair symbols [] = { { "cont", VBI_PIL_CONTINUE }, { "continue", VBI_PIL_CONTINUE }, { "end", VBI_PIL_END }, { "inhibit", VBI_PIL_INHIBIT_TERMINATE }, { "int", VBI_PIL_INTERRUPTION }, { "interruption", VBI_PIL_INTERRUPTION }, { "nspv", VBI_PIL_NSPV }, { "rit", VBI_PIL_INHIBIT_TERMINATE }, { "terminate", VBI_PIL_INHIBIT_TERMINATE }, { "tc", VBI_PIL_TIMER_CONTROL }, { "timer", VBI_PIL_TIMER_CONTROL } }; int n; if (_vbi_keyword_lookup (&n, inout_s, symbols, N_ELEMENTS (symbols))) { *pil = n; return TRUE; } else { return FALSE; } } n_fields = 4; sep_mask = 0; for (i = 0; i < n_fields; ++i) { int c; if (!isdigit (s[0])) { if (2 == i && 0 == sep_mask) { n_fields = 2; break; } return FALSE; } else if (!isdigit (s[1])) { return FALSE; } value[i] = (s[0] - '0') * 10 + s[1] - '0'; s += 2; c = *s; if (i < n_fields - 1) { if (0 == i && ':' == c) { n_fields = 2; sep_mask |= 1 << 2; ++s; } else if ("-T:"[i] == c) { sep_mask |= 1 << i; ++s; } } } if (n_fields < 4) { value[3] = value[1]; value[2] = value[0]; value[1] = 0; value[0] = 0; } if (unlikely (value[0] > 15 || (value[1] | value[2]) > 31 || value[3] > 63)) return FALSE; *inout_s = s; *pil = VBI_PIL (value[0], value[1], value[2], value[3]); return TRUE; } static const uint8_t month_days[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; /** * @param pil Program Identification Label. * * Determines if @a pil represents a valid date and time. * * Since PILs have no year field February 29th is considered valid. * You can find out if this date is valid in a given year with the * vbi_pil_to_time() function. * * 24:00 is not valid (an unreal hour) as defined in EN 300 231 * Annex F and EIA 608-B Section 9.5.1.1. * * @returns * @c TRUE if @a pil represents a valid date and time, @c FALSE * if @a pil contains an unreal date or time (e.g. Jan 0 27:61), * a service code or unallocated code. * * @since 0.2.34 */ vbi_bool vbi_pil_is_valid_date (vbi_pil pil) { unsigned int month; unsigned int day; month = VBI_PIL_MONTH (pil); day = VBI_PIL_DAY (pil); /* Note this also checks for zero month and day. */ return (month - 1 < 12 && day - 1 < month_days[month - 1] && VBI_PIL_HOUR (pil) < 24 && VBI_PIL_MINUTE (pil) < 60); } static vbi_bool tm_mon_mday_from_pil (struct tm * tm, vbi_pil pil) { unsigned int month0; month0 = VBI_PIL_MONTH (pil) - 1; if (month0 >= (unsigned int) tm->tm_mon + 6) { /* POSIX defines tm_year as int. */ if (unlikely (tm->tm_year <= INT_MIN)) return FALSE; --tm->tm_year; } else if (month0 + 6 < (unsigned int) tm->tm_mon) { if (unlikely (tm->tm_year >= INT_MAX)) return FALSE; ++tm->tm_year; } tm->tm_mon = month0; tm->tm_mday = VBI_PIL_DAY (pil); return TRUE; } static vbi_bool is_leap_year (unsigned int year) { if (0 != year % 4) return FALSE; else if (0 == year % 400) return TRUE; else return (0 != year % 100); } static vbi_bool tm_leap_day_check (const struct tm * tm) { return (1 != tm->tm_mon || tm->tm_mday <= 28 || is_leap_year (tm->tm_year + 1900)); } static vbi_bool restore_tz (char ** old_tz, const char * tz) { int saved_errno; if (NULL == tz) return TRUE; if (NULL == *old_tz) { /* Errors ignored, should never fail. */ unsetenv ("TZ"); } else { if (unlikely (-1 == setenv ("TZ", *old_tz, /* overwrite */ TRUE))) { /* ENOMEM: Out of memory. */ saved_errno = errno; free (*old_tz); *old_tz = NULL; errno = saved_errno; return FALSE; } free (*old_tz); *old_tz = NULL; } tzset (); return TRUE; } static vbi_bool change_tz (char ** old_tz, const char * tz) { const char *s; int saved_errno; *old_tz = NULL; s = getenv ("TZ"); if (NULL != s) { *old_tz = strdup (s); if (unlikely (NULL == *old_tz)) { errno = ENOMEM; return FALSE; } } if (unlikely (-1 == setenv ("TZ", tz, /* overwrite */ TRUE))) { /* EINVAL: tz is "" or contains '='. ENOMEM: Out of memory. */ saved_errno = errno; free (*old_tz); *old_tz = NULL; errno = saved_errno; return FALSE; } tzset (); return TRUE; } static vbi_bool localtime_tz (struct tm * tm, char ** old_tz, time_t t, const char * tz) { int saved_errno; *old_tz = NULL; /* Some system calls below may not set errno on failure. */ errno = 0; if (NULL != tz) { if (unlikely (!change_tz (old_tz, tz))) return FALSE; } CLEAR (*tm); if ((time_t) -1 == t) { if (unlikely ((time_t) -1 == time (&t))) { saved_errno = errno; /* We do not ignore errors here because the information that TZ wasn't restored seems more important to me. */ if (unlikely (!restore_tz (old_tz, tz))) return FALSE; /* time() can fail but POSIX defines no error code. On Linux EFAULT is possible. */ if (0 == saved_errno) errno = VBI_ERR_NO_TIME; else errno = saved_errno; return FALSE; } } if (unlikely (NULL == localtime_r (&t, tm))) { saved_errno = errno; if (unlikely (!restore_tz (old_tz, tz))) return FALSE; errno = saved_errno; return FALSE; } return TRUE; } #undef mktime #undef timegm time_t _vbi_mktime (struct tm * tm); /** * @internal * GNU libc mktime() appears to clamp its result, but it will be used * in video recording apps where malfunction is not an option. I want * it to fail with EOVERFLOW as POSIX suggests, so the caller can * detect and address the problem. Don't call this function directly, * we #define mktime to override the libc version. */ time_t _vbi_mktime (struct tm * tm) { time_t result = mktime (tm); if (unlikely (result <= TIME_MIN || result >= TIME_MAX)) { errno = EOVERFLOW; result = (time_t) -1; } return result; } #if defined(HAVE_TIMEGM) || defined(_WIN32) #ifdef _WIN32 #define timegm _mkgmtime #endif /** * @internal * timegm() is a GNU extension. It works like mktime(), but interprets * @a tm as a time in the UTC zone instead of the time zone defined by * the TZ environment variable. GNU libc timegm() appears to clamp the * result, but we want an EOVERFLOW error instead. In fact no error * codes are defined for timegm(), so we check for 0 == errno * too. Note this function is NOT THREAD SAFE because its replacement * is not. Don't call this function directly, we #define timegm to * override the libc version. */ time_t _vbi_timegm (struct tm * tm) { time_t result; errno = 0; result = timegm (tm); if (unlikely ((time_t) -1 == result)) { if (0 == errno) errno = EOVERFLOW; } else if (unlikely (result <= TIME_MIN || result >= TIME_MAX)) { errno = EOVERFLOW; result = (time_t) -1; } return result; } #undef timegm #else time_t _vbi_timegm (struct tm * tm); /** * @internal * * Replacement for timegm() on non-GNU systems. Note this function is * NOT THREAD SAFE because the C library permits the conversion of a * broken-down time in an arbitrary time zone only by setting the TZ * environment variable. The function may also fail to restore the * value of TZ if insufficient memory is available. Don't call this * function directly, we #define timegm. */ time_t _vbi_timegm (struct tm * tm) { char *old_tz; int saved_errno; time_t result; if (unlikely (!change_tz (&old_tz, "UTC"))) return (time_t) -1; result = mktime (tm); if (unlikely (result <= TIME_MIN || result >= TIME_MAX)) { saved_errno = EOVERFLOW; result = (time_t) -1; } else { saved_errno = errno; } if (unlikely (!restore_tz (&old_tz, "UTC"))) return (time_t) -1; errno = saved_errno; return result; } #endif /* !HAVE_TIMEGM */ #define mktime _vbi_mktime #define timegm _vbi_timegm static time_t valid_pil_lto_to_time (vbi_pil pil, time_t start, int seconds_east) { struct tm tm; /* Some system calls below may not set errno on failure, but we must distinguish those errors from VBI_ERR_INVALID_PIL for valid_pil_lto_validity_window(). */ errno = 0; CLEAR (tm); if ((time_t) -1 == start) { if (unlikely ((time_t) -1 == time (&start))) { /* time() can fail but POSIX defines no error code. On Linux EFAULT is possible. */ if (0 == errno) errno = VBI_ERR_NO_TIME; return (time_t) -1; } } if (seconds_east < 0) { /* Note start can be negative. */ if (unlikely (start < -seconds_east)) { errno = EOVERFLOW; return (time_t) -1; } } else { if (unlikely (start > TIME_MAX - seconds_east)) { errno = EOVERFLOW; return (time_t) -1; } } start += seconds_east; if (unlikely (NULL == gmtime_r (&start, &tm))) return (time_t) -1; if (unlikely (!tm_mon_mday_from_pil (&tm, pil))) { errno = EOVERFLOW; return (time_t) -1; } if (unlikely (!tm_leap_day_check (&tm))) { errno = VBI_ERR_INVALID_PIL; return (time_t) -1; } tm.tm_hour = VBI_PIL_HOUR (pil); tm.tm_min = VBI_PIL_MINUTE (pil); tm.tm_sec = 0; start = timegm (&tm); if (unlikely ((time_t) -1 == start)) return (time_t) -1; if (seconds_east > 0) { /* Note start can be negative. */ if (unlikely (start < seconds_east)) { errno = EOVERFLOW; return (time_t) -1; } } else { if (unlikely (start > TIME_MAX + seconds_east)) { errno = EOVERFLOW; return (time_t) -1; } } return start - seconds_east; } /** * @param pil Program Identification Label (PIL) to convert. * @param start The most recently announced start time of the * program. If zero the current system time will be used. * @param seconds_east A time zone specified as an offset in seconds * east of UTC, for example +1 * 60 * 60 for CET. @a seconds_east * may include a daylight-saving time (DST) offset. * * This function converts a PIL to a time_t in the same manner * localtime() converts a broken-down time to time_t. * * Since PILs do not contain a year field, the year is determined from * the @a start parameter, that is the most recently announced start * time of the program or "AT-1" in EN 300 231 parlance. If @a pil * contains a month more than five months after @a start, @a pil is * assumed to refer to an earlier date than @a start. * * @a pil is assumed to be a time in the time zone specified by @a * seconds_east. @a start will be converted to a local time in the * same time zone to determine the correct year. * * Teletext packet 8/30 format 2, VPS and DVB PDC descriptors give a * PIL relative to the time zone of the intended audience of the * program. An offset from UTC including the DST offset in effect at * the specified date may be available on Teletext program * announcement pages (see struct vbi_preselection). Another offset * from UTC including the @em current DST offset is available as @c * VBI_EVENT_LOCAL_TIME, but of course that information is * insufficient to determine if DST is in effect at other dates. * * XDS Current/Future Program ID packets give a PIL relative to UTC, * so @a seconds_east should be zero. * * @returns * The PIL as a time_t, that is the number of seconds since * 1970-01-01 00:00 UTC. On error the function * returns (time_t) -1: * - @a pil does not contain a valid date or time. February 29th is * a valid date only if the estimated year is a leap year. * - @a start is zero and the current system time could not be * determined. * - The time specified by @a pil, @a start and @a seconds_east * cannot be represented as a time_t value (2038 is closer than * you think!). * * @since 0.2.34 * * @bug * This function is not thread safe. That is a limitation of the C * library which permits the conversion of a broken-down time in an * arbitrary time zone only by setting the TZ environment variable. * The function may also fail to restore the value of TZ if * insufficient memory is available. */ time_t vbi_pil_lto_to_time (vbi_pil pil, time_t start, int seconds_east) { time_t t; if (unlikely (!vbi_pil_is_valid_date (pil))) { #if 3 == VBI_VERSION_MINOR errno = VBI_ERR_INVALID_PIL; #else errno = 0; #endif return (time_t) -1; } t = valid_pil_lto_to_time (pil, start, seconds_east); #if 2 == VBI_VERSION_MINOR errno = 0; #endif return t; } /** * @param pil Program Identification Label (PIL) to convert. * @param start The most recently announced start time of the * program. If zero the current system time will be used. * @param tz A time zone name in the same format as the TZ environment * variable. If @c NULL the current value of TZ will be used. * * This function converts a PIL to a time_t in the same manner * localtime() converts a broken-down time to time_t. * * Since PILs do not contain a year field, the year is determined from * the @a start parameter, that is the most recently announced start * time of the program or "AT-1" in EN 300 231 parlance. If @a pil * contains a month more than five months after @a start, @a pil is * assumed to refer to an earlier date than @a start. * * @a pil is assumed to be a time in the time zone @a tz. @a start * will be converted to a local time in the same time zone to * determine the correct year. * * Teletext packet 8/30 format 2, VPS and DVB PDC descriptors give a * PIL relative to the time zone of the intended audience of the * program. Ideally the time zone would be specified as a geographic * area like "Europe/London", such that the function can determine * the correct offset from UTC and if daylight-saving time is in * effect at the specified date. See the documentation of the * localtime() function and the TZ environment variable for details. * * XDS Current/Future Program ID packets give a PIL relative to UTC. * Just specify time zone "UTC" in this case. * * @returns * The PIL as a time_t, that is the number of seconds since * 1970-01-01 00:00 UTC. On error the function * returns (time_t) -1: * - @a pil does not contain a valid date or time. February 29th is * a valid date only if the estimated year is a leap year. * - @a tz is empty or contains an equal sign '='. * - @a start is zero and the current system time could not be * determined. * - The time specified by @a pil, @a start and @a tz cannot be * represented as a time_t value. * - Insufficient memory was available. * * @since 0.2.34 * * @bug * This function is not thread safe unless @a tz is @c NULL. That is * a limitation of the C library which permits the conversion of a * broken-down time in an arbitrary time zone only by setting the TZ * environment variable. The function may also fail to restore the * value of TZ if insufficient memory is available. */ time_t vbi_pil_to_time (vbi_pil pil, time_t start, const char * tz) { struct tm tm; char *old_tz; time_t result; int saved_errno; if (unlikely (!vbi_pil_is_valid_date (pil))) { #if 3 == VBI_VERSION_MINOR errno = VBI_ERR_INVALID_PIL; #else errno = 0; #endif return (time_t) -1; } if (NULL != tz && 0 == strcmp (tz, "UTC")) { time_t t; t = valid_pil_lto_to_time (pil, start, /* seconds_east */ 0); #if 2 == VBI_VERSION_MINOR errno = 0; #endif return t; } if (unlikely (!localtime_tz (&tm, &old_tz, start, tz))) { #if 2 == VBI_VERSION_MINOR errno = 0; #endif return (time_t) -1; } if (unlikely (!tm_mon_mday_from_pil (&tm, pil))) { saved_errno = EOVERFLOW; goto failed; } if (unlikely (!tm_leap_day_check (&tm))) { saved_errno = VBI_ERR_INVALID_PIL; goto failed; } tm.tm_hour = VBI_PIL_HOUR (pil); tm.tm_min = VBI_PIL_MINUTE (pil); tm.tm_sec = 0; tm.tm_isdst = -1; /* unknown */ result = mktime (&tm); if (unlikely ((time_t) -1 == result)) goto failed; if (unlikely (!restore_tz (&old_tz, tz))) { #if 2 == VBI_VERSION_MINOR errno = 0; #endif return (time_t) -1; } return result; failed: if (unlikely (!restore_tz (&old_tz, tz))) { #if 2 == VBI_VERSION_MINOR errno = 0; #endif return (time_t) -1; } #if 3 == VBI_VERSION_MINOR errno = saved_errno; #else errno = 0; #endif return (time_t) -1; } #if 0 /* just an idea */ time_t vbi_pil_year_to_time (vbi_pil pil, int year, const char * tz) { } time_t vbi_pil_year_lto_to_time (vbi_pil pil, int year, int seconds_east) { } #endif static vbi_bool pty_utc_validity_window (time_t * begin, time_t * end, time_t time) { struct tm tm; unsigned int seconds_since_midnight; unsigned int duration; memset (&tm, 0, sizeof (tm)); errno = 0; if (unlikely (NULL == gmtime_r (&time, &tm))) return FALSE; seconds_since_midnight = + tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec; /* This is safe because UTC does not observe DST and POSIX time_t does not count leap seconds. (When a leap second is inserted in UTC, POSIX time "freezes" for one second. When a leap second is removed POSIX time jumps forward one second.) The result is unambiguous because leap seconds are inserted or removed just before midnight and we only return full hours. */ duration = + 4 * 7 * 24 * 60 * 60 + (24 + 4) * 60 * 60 - seconds_since_midnight; if (unlikely (time > (time_t)(TIME_MAX - duration))) { errno = EOVERFLOW; return FALSE; } *begin = time; *end = time + duration; return TRUE; } /** * @param begin The begin of the validity of the PTY will be stored * here. * @param end The end of the validity of the PTY will be stored here. * @param last_transm The last time when a program ID with the PTY * in question was broadcast by the network. * @param tz A time zone name in the same format as the TZ environment * variable. If @c NULL the current value of TZ will be used. * * This function calculates the validity time window of a Program Type * (PTY) code according to EN 300 231. That is the time window where a * network can be expected to broadcast another program with the same * PTY, approximately up to four weeks after its last * transmission. When the PTY is a series code (>= 0x80) and not * transmitted again before @a end, the network may assign the code to * another series. * * @a tz is the time zone of the intended audience of the program. * Ideally the time zone would be specified as a geographic area like * "Europe/London", such that the function can determine if * daylight-saving time is in effect at @a time or at the end of the * validity time window. See the documentation of the localtime() * function and the TZ environment variable for details. If no time * zone name is available "UTC" should be specified, the returned * @a end time may be off by one hour in this case. * * @returns * On error the function returns @c FALSE and @a *begin and @a *end * remain unchanged: * - @a tz is empty or contains an equal sign '='. * - The @a end time cannot be represented as a time_t value * (December 2037 is closer than you think!). * - Insufficient memory was available. * * @since 0.2.34 * * @bug * This function is not thread safe unless @a tz is @c NULL. * That is a limitation of the C library which permits the conversion * of a broken-down time in an arbitrary time zone only by setting * the TZ environment variable. The function may also fail to restore * the value of TZ if insufficient memory is available. */ vbi_bool vbi_pty_validity_window (time_t * begin, time_t * end, time_t last_transm, const char * tz) { char *old_tz; struct tm tm; time_t stop; int saved_errno; assert (NULL != begin); assert (NULL != end); if (NULL != tz && 0 == strcmp (tz, "UTC")) { vbi_bool success; success = pty_utc_validity_window (begin, end, last_transm); #if 2 == VBI_VERSION_MINOR errno = 0; #endif return success; } if (unlikely (!localtime_tz (&tm, &old_tz, last_transm, tz))) goto failed; tm.tm_mday += 4 * 7 + 1; tm.tm_hour = 4; tm.tm_min = 0; tm.tm_sec = 0; tm.tm_isdst = -1; /* unknown */ stop = mktime (&tm); if (unlikely ((time_t) -1 == stop)) { saved_errno = errno; if (unlikely (!restore_tz (&old_tz, tz))) return FALSE; #if 3 == VBI_VERSION_MINOR errno = saved_errno; #else errno = 0; #endif return FALSE; } if (unlikely (!restore_tz (&old_tz, tz))) goto failed; *begin = last_transm; *end = stop; return TRUE; failed: #if 2 == VBI_VERSION_MINOR errno = 0; #endif return FALSE; } static vbi_bool valid_pil_lto_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, int seconds_east) { time_t t; t = valid_pil_lto_to_time (pil & VBI_PIL (/* month */ 15, /* day */ 31, /* hour */ 0, /* minute */ 0), start, seconds_east); if (unlikely ((time_t) -1 == t)) { if (VBI_ERR_INVALID_PIL == errno) { /* Annex F: "Invalid days - indefinite time window". */ *begin = TIME_MIN; *end = TIME_MAX; return TRUE; } else { return FALSE; } } /* EN 300 231 Section 9.3. */ /* Just adding a number of seconds is safe because UTC does not observe DST and POSIX time_t does not count leap seconds. The results are unambiguous because leap seconds are inserted or removed just before midnight and we only return full hours. */ if (unlikely (t > TIME_MAX - 28 * 60 * 60)) { errno = EOVERFLOW; return FALSE; } if (VBI_PIL_HOUR (pil) < 4) { if (unlikely (t < 4 * 60 * 60)) { errno = EOVERFLOW; return FALSE; } *begin = t - 4 * 60 * 60; } else { *begin = t; } *end = t + 28 * 60 * 60; return TRUE; } /** * @param begin The start of the validity of the PIL will be stored * here. * @param end The end of the validity of the PIL will be stored here. * @param pil Program Identification Label (PIL). * @param start The most recently announced start time of the * program. If zero the current system time will be used. * @param seconds_east A time zone specified as an offset in seconds * east of UTC, for example +1 * 60 * 60 for CET. @a seconds_east * may include a daylight-saving time (DST) offset. * * This function calculates the validity time window of a PIL * according to EN 300 231. That is the time window where a network * can be expected to broadcast this PIL, usually from 00:00 on the * same day until exclusive 04:00 on the next day. * * Since PILs do not contain a year field, the year is determined from * the @a start parameter, that is the most recently announced start * time of the program or "AT-1" in EN 300 231 parlance. If @a pil * contains a month more than five months after @a start, @a pil is * assumed to refer to an earlier date than @a start. * * @a pil is assumed to be a time in the time zone specified by @a * seconds_east. @a start will be converted to a local time in the * same time zone to determine the correct year. * * Teletext packet 8/30 format 2, VPS and DVB PDC descriptors give a * PIL relative to the time zone of the intended audience of the * program. An offset from UTC including the DST offset in effect at * the specified date may be available on Teletext program * announcement pages (see struct vbi_preselection). Another offset * from UTC including the current DST offset is available as @c * VBI_EVENT_LOCAL_TIME. But of course these offsets are insufficient * to determine if DST is in effect at any given date, so the returned * @a begin or @a end may be off by one hour if the validity window * straddles a DST discontinuity. * * If @a pil is @c VBI_PIL_NSPV this function ignores @a seconds_east * and returns the same values as vbi_pty_validity_window(). * * @returns * On error the function returns @c FALSE: * - @a pil is not @c VBI_PIL_NSPV and does not contain a * valid date or time. February 29th is a valid date only if the * estimated year is a leap year. * - @a start is zero and the current system time could not be * determined. * - The time specified by @a pil, @a start and @a seconds_east cannot * be represented as a time_t value. * * @since 0.2.34 * * @bug * This function is not thread safe. That is a limitation of the C * library which permits the conversion of a broken-down time in an * arbitrary time zone only by setting the TZ environment variable. * The function may also fail to restore the value of TZ if * insufficient memory is available. */ vbi_bool vbi_pil_lto_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, int seconds_east) { unsigned int month; assert (NULL != begin); assert (NULL != end); month = VBI_PIL_MONTH (pil); if (0 == month) { /* EN 300 231 Annex F: "Unallocated". */ #if 3 == VBI_VERSION_MINOR errno = VBI_ERR_INVALID_PIL; #else errno = 0; #endif return FALSE; } else if (month <= 12) { unsigned int day; vbi_bool success; day = VBI_PIL_DAY (pil); if (day - 1 >= month_days[month - 1]) { /* Annex F: "Invalid days - indefinite time window". */ *begin = TIME_MIN; *end = TIME_MAX; return TRUE; } success = valid_pil_lto_validity_window (begin, end, pil, start, seconds_east); #if 2 == VBI_VERSION_MINOR errno = 0; #endif return success; } else if (month <= 14) { /* Annex F: "Indefinite time window". */ *begin = TIME_MIN; *end = TIME_MAX; return TRUE; } else { vbi_bool success; /* Annex F: "Unallocated except for the following * service codes (for which there is no restriction to * the time window of validity)". */ switch (pil) { case VBI_PIL_TIMER_CONTROL: case VBI_PIL_INHIBIT_TERMINATE: case VBI_PIL_INTERRUPTION: case VBI_PIL_CONTINUE: *begin = TIME_MIN; *end = TIME_MAX; return TRUE; /* EN 300 231 Section 9.3, Annex E.3. */ case VBI_PIL_NSPV: success = pty_utc_validity_window (begin, end, start); #if 2 == VBI_VERSION_MINOR errno = 0; #endif return success; default: #if 3 == VBI_VERSION_MINOR errno = VBI_ERR_INVALID_PIL; #else errno = 0; #endif return FALSE; } } } static vbi_bool valid_pil_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, const char * tz) { char *old_tz; struct tm tm; struct tm tm2; time_t stop; int saved_errno; /* EN 300 231 Section 9.3 and Annex F. */ old_tz = NULL; if (NULL != tz && 0 == strcmp (tz, "UTC")) { return valid_pil_lto_validity_window (begin, end, pil, start, /* seconds_east */ 0); } if (unlikely (!localtime_tz (&tm, &old_tz, start, tz))) return FALSE; if (unlikely (!tm_mon_mday_from_pil (&tm, pil))) { saved_errno = EOVERFLOW; goto failed; } if (unlikely (!tm_leap_day_check (&tm))) { /* Annex F: "Invalid days - indefinite time window". */ if (!restore_tz (&old_tz, tz)) return FALSE; *begin = TIME_MIN; *end = TIME_MAX; return TRUE; } tm.tm_hour = 0; tm.tm_min = 0; tm.tm_sec = 0; tm.tm_isdst = -1; /* unknown */ tm2 = tm; if (VBI_PIL_HOUR (pil) < 4) { --tm.tm_mday; tm.tm_hour = 20; } start = mktime (&tm); if (unlikely ((time_t) -1 == start)) goto failed; tm2.tm_mday += 1; tm2.tm_hour = 4; stop = mktime (&tm2); if (unlikely ((time_t) -1 == stop)) goto failed; if (unlikely (!restore_tz (&old_tz, tz))) return FALSE; *begin = start; *end = stop; return TRUE; failed: if (unlikely (!restore_tz (&old_tz, tz))) return FALSE; errno = saved_errno; return FALSE; } /** * @param begin The start of the validity of the PIL will be stored * here. * @param end The end of the validity of the PIL will be stored here. * @param pil Program Identification Label (PIL). * @param start The most recently announced start time of the program. * If zero the current system time will be used. * @param tz A time zone name in the same format as the TZ environment * variable. If @c NULL the current value of TZ will be used. * * This function calculates the validity time window of a PIL * according to EN 300 231. That is the time window where a network * can be expected to broadcast this PIL, usually from 00:00 on the * same day until 04:00 on the next day. * * Since PILs do not contain a year field, the year is determined from * the @a start parameter, that is the most recently announced start * time of the program or "AT-1" in EN 300 231 parlance. If @a pil * contains a month more than five months after @a start, @a pil is * assumed to refer to an earlier date than @a start. * * @a pil is assumed to be a time in the time zone specified by @a * seconds_east. @a start will be converted to a local time in the * same time zone to determine the correct year. * * Teletext packet 8/30 format 2, VPS and DVB PDC descriptors give a * PIL relative to the time zone of the intended audience of the * program. Ideally the time zone would be specified as a geographic * area like "Europe/London", such that the function can determine the * correct offset from UTC and if daylight-saving time is in effect at * any time within the validity window. See the documentation of the * localtime() function and the TZ environment variable for details. * * If @a pil is @c VBI_PIL_NSPV this function returns the same values * as vbi_pty_validity_window(). * * @returns * On error the function returns @c FALSE: * - @a pil is not @c VBI_PIL_NSPV and does not contain a * valid date or time. February 29th is a valid date only if the * estimated year is a leap year. * - @a tz is empty or contains an equal sign '='. * - @a start is zero and the current system time could not be * determined. * - The time specified by @a pil, @a start and @a tz cannot be * represented as a time_t value. * - Insufficient memory was available. * * @since 0.2.34 * * @bug * This function is not thread safe unless @a tz is @c NULL. * That is a limitation of the C library which permits the conversion * of a broken-down time in an arbitrary time zone only by setting * the TZ environment variable. The function may also fail to restore * the value of TZ if insufficient memory is available. */ vbi_bool vbi_pil_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, const char * tz) { unsigned int month; assert (NULL != begin); assert (NULL != end); month = VBI_PIL_MONTH (pil); if (0 == month) { /* EN 300 231 Annex F: "Unallocated". */ #if 3 == VBI_VERSION_MINOR errno = VBI_ERR_INVALID_PIL; #else errno = 0; #endif return FALSE; } else if (month <= 12) { unsigned int day; vbi_bool success; day = VBI_PIL_DAY (pil); if (day - 1 >= month_days[month - 1]) { /* Annex F: "Invalid days - indefinite time window". */ *begin = TIME_MIN; *end = TIME_MAX; return TRUE; } success = valid_pil_validity_window (begin, end, pil, start, tz); #if 2 == VBI_VERSION_MINOR errno = 0; #endif return success; } else if (month <= 14) { /* Annex F: "Indefinite time window". */ *begin = TIME_MIN; *end = TIME_MAX; return TRUE; } else { vbi_bool success; /* Annex F: "Unallocated except for the following * service codes (for which there is no restriction to * the time window of validity)". */ switch (pil) { case VBI_PIL_TIMER_CONTROL: case VBI_PIL_INHIBIT_TERMINATE: case VBI_PIL_INTERRUPTION: case VBI_PIL_CONTINUE: *begin = TIME_MIN; *end = TIME_MAX; return TRUE; /* EN 300 231 Section 9.3, Annex E.3. */ case VBI_PIL_NSPV: success = vbi_pty_validity_window (begin, end, start, tz); #if 2 == VBI_VERSION_MINOR errno = 0; #endif return success; default: #if 3 == VBI_VERSION_MINOR errno = VBI_ERR_INVALID_PIL; #else errno = 0; #endif return FALSE; } } } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/pdc.h000066400000000000000000000306711476363111200142740ustar00rootroot00000000000000/* * libzvbi - Program Delivery Control * * Copyright (C) 2000-2004 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* $Id: pdc.h,v 1.6 2009-03-23 01:30:19 mschimek Exp $ */ #ifndef __ZVBI_PDC_H__ #define __ZVBI_PDC_H__ #include /* FILE */ #include /* uint8_t */ #include "version.h" #include "macros.h" #include "bcd.h" /* vbi_pgno */ #include "network.h" /* vbi_cni_type */ VBI_BEGIN_DECLS /* Public */ #ifdef _WIN32 #define __STDC_WANT_LIB_EXT1__ 1 #endif #include /* time_t */ /** * @addtogroup ProgramID * @{ */ /** * @brief Program Identification Label. * * This is a packed representation of the originally announced start * date and time ("AT-2" in EN 300 231 parlance, "Scheduled Start * Time" in EIA 608-B) of a program. * * @since 0.2.34 */ typedef unsigned int vbi_pil; /** * @brief Macro to create a PIL. * * Valid values for @a month are 1 ... 12, for @a day 1 ... 31, for * @a hour 0 ... 23 and for @a minute 0 ... 59. * * Note in the PDC system (EN 300 231) networks may also transmit * unreal dates or times like 14-00 25:63. You can determine if a PIL * represents a valid date and time with the vbi_pil_is_valid_date() * function. * * @since 0.2.34 */ #define VBI_PIL(month, day, hour, minute) \ (((day) << 15) | ((month) << 11) | ((hour) << 6) | (minute)) /** Extract the month from a PIL. Valid values are in range 1 ... 12. */ #define VBI_PIL_MONTH(pil) (((pil) >> 11) & 15) /** Extract the day from a PIL. Valid values are in range 1 ... 31. */ #define VBI_PIL_DAY(pil) (((pil) >> 15) & 31) /** Extract the hour from a PIL. Valid values are in range 0 ... 23. */ #define VBI_PIL_HOUR(pil) (((pil) >> 6) & 31) /** Extract the minute from a PIL. Valid values are in range 0 ... 59. */ #define VBI_PIL_MINUTE(pil) ((pil) & 63) /** * @brief PIL Service Codes. * * PILs can be zero, or specify a valid date and time, or an unreal * time such as 31:00 if a new label has been assigned to a program * but no transmission time has been decided yet. Some PILs with * unreal date and time have a special meaning. * * These codes are defined in EN 300 231 Section 6.2, Annex E.3 and * Annex F, and in EIA 608-B Section 9.5.1.1. * * @since 0.2.34 */ enum { /** * Only in Teletext packets 8/30 format 2, VPS packets and DVB * PDC descriptors: "Timer Control". * * No program IDs are available, use the timer to control * recording. */ VBI_PIL_TIMER_CONTROL = VBI_PIL (15, 0, 31, 63), /** * Teletext, VPS, DVB: "Recording Inhibit/Terminate". * * When the PIL changes from a valid program label to @c * VBI_PIL_INHIBIT_TERMINATE the current program has ended and * the next program has not started yet. VCRs recording the * current program shall stop recording and remove the program * from their schedule. VCRs waiting for a new PIL shall * continue waiting. */ VBI_PIL_INHIBIT_TERMINATE = VBI_PIL (15, 0, 30, 63), /** * Teletext, VPS, DVB: "Interruption". * * When the PIL changes from a valid program label to @c * VBI_PIL_INTERRUPTION, the current program has stopped but * will continue later. This code is transmitted for example * at the start of a halftime pause or at a film break. VCRs * recording the current program shall stop recording, but not * delete the program from their schedule. The network may * broadcast other programs with different PILs before the * interrupted program continues. VCRs waiting for a new PIL * shall continue waiting. */ VBI_PIL_INTERRUPTION = VBI_PIL (15, 0, 29, 63), /** * Teletext, VPS, DVB: "Continuation code". * * This code is transmitted during a service interruption, * the PDC service should resume operation shortly. VCRs * recording the current program shall continue recording, * VCRs waiting for a new PIL shall continue waiting. */ VBI_PIL_CONTINUE = VBI_PIL (15, 0, 28, 63), /** * Teletext, VPS, DVB: "No Specific PIL Value". * * Networks may transmit this label with an unplanned program * such as an emergency message. The program type (PTY) field * may still be valid. */ VBI_PIL_NSPV = VBI_PIL (15, 15, 31, 63), /** * Only in XDS Current Program ID packets: * * The current program has ended and the next program not * started yet. */ VBI_PIL_END = VBI_PIL (15, 15, 31, 63) }; extern vbi_bool vbi_pil_is_valid_date (vbi_pil pil); extern time_t vbi_pil_to_time (vbi_pil pil, time_t start, const char * tz); extern time_t vbi_pil_lto_to_time (vbi_pil pil, time_t start, int seconds_east); extern vbi_bool vbi_pty_validity_window (time_t * begin, time_t * end, time_t time, const char * tz) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_pil_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, const char * tz) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_pil_lto_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, int seconds_east) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; /* Private */ /** @} */ extern void _vbi_pil_dump (vbi_pil pil, FILE * fp) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((2)) #endif ; extern vbi_bool _vbi_pil_from_string (vbi_pil * pil, const char ** inout_s) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; /** * @addtogroup ProgramID * @{ */ /* Public */ /** * @brief Sources of PIDs. * * A program identification can be transmitted on different logical * channels. Teletext packet 8/30 format 2 contains a Label Channel * Identifier. XDS Program ID packets can refer to the current or next * program. * * This information is returned in struct @a vbi_program_id by the low * level functions vbi_decode_teletext_8302_pdc(), * vbi_decode_vps_pdc(), vbi_decode_dvb_pdc_descriptor() and the @a * vbi_decoder event @c VBI_EVENT_PROG_ID. * * @since 0.2.34 */ typedef enum { /** * Data from Teletext packet 8/30 format 2, Label Channel 0 * (EN 300 706 section 9.8.2, EN 300 231 section 8.2.1). * * Teletext packets contain a CNI, PIL, LUF, MI, PRF, PCS and * PTY. They are transmitted once per second for each Label * Channel in use. * * The purpose of Label Channels is to transmit overlapping * PIDs, for example one referring the current program and * another announcing the imminent start of the next * program. Programs can also have multiple PIDs, for example * a sports magazine with several segments, where the entire * program has a PID and each segment has its own PID, * transmitted on a different Label Channel. * * Label Channels are used in no particular order or * hierarchy. */ VBI_PID_CHANNEL_LCI_0 = 0, /** Data from Teletext packet 8/30 format 2, Label Channel 1. */ VBI_PID_CHANNEL_LCI_1, /** Data from Teletext packet 8/30 format 2, Label Channel 2. */ VBI_PID_CHANNEL_LCI_2, /** Data from Teletext packet 8/30 format 2, Label Channel 3. */ VBI_PID_CHANNEL_LCI_3, /** * Data from a VPS packet (EN 300 231). * * These packets contain a CNI, PIL, PCS and PTY. They are * transmitted once in each frame, i.e. 25 times per second. */ VBI_PID_CHANNEL_VPS, /** * Data from a DVB PDC descriptor (EN 300 468 Section 6.2.29). * * DVB PDC descriptors contain the same PIL as a VPS packet, * but no CNI, PCS or PTY. */ VBI_PID_CHANNEL_PDC_DESCRIPTOR, /** * Data from an XDS Current Program ID packet (EIA 608-B * Section 9). * * XDS Current/Future Program ID packets contain a PIL and * tape-delayed flag. Current class packets refer to the * currently transmitted program, Future class packets to the * next program. * * Decoding of XDS Current/Future Program ID packets is not * implemented yet. */ VBI_PID_CHANNEL_XDS_CURRENT, /** * Data from an XDS Future Program ID packet. */ VBI_PID_CHANNEL_XDS_FUTURE, /** Note this value may change. */ VBI_MAX_PID_CHANNELS } vbi_pid_channel; /** * @brief PDC Program Control Status - Audio. * * This information is available with Teletext and VPS program IDs and * returned in struct vbi_program_id by the low level functions * vbi_decode_teletext_8302_pdc(), vbi_decode_vps_pdc() and the @a * vbi_decoder event @c VBI_EVENT_PROG_ID. * * @since 0.2.34 */ typedef enum { /** Nothing known about audio channels. */ VBI_PCS_AUDIO_UNKNOWN = 0, /** Mono audio is broadcast. */ VBI_PCS_AUDIO_MONO, /** Stereo audio. */ VBI_PCS_AUDIO_STEREO, /** Primary language on left channel, secondary on right. */ VBI_PCS_AUDIO_BILINGUAL } vbi_pcs_audio; /** * @brief Program Identification. * * This structure contains a Program ID received via Teletext packet * 8/30 format 2, VPS, a DVB PDC descriptor or an XDS Current/Future * Program ID packet. When the source does not provide all this * information, libzvbi initializes the respective fields with an * appropriate value. * * @since 0.2.34 */ typedef struct { /** Source of this PID. */ vbi_pid_channel channel; /** * Network identifier type, one of * - @c VBI_CNI_TYPE_NONE, * - @c VBI_CNI_TYPE_8302 or * - @c VBI_CNI_TYPE_VPS. */ vbi_cni_type cni_type; /** * Country and Network Identifier provided by Teletext packet * 8/30 format 2 and VPS. Note when the source is Teletext and * the LUF flag is set, this CNI may refer to a different * network than the one transmitting the PID. */ unsigned int cni; /** * Program Identification Label. This is the only information * available from all PID sources. */ vbi_pil pil; /** * PDC Label Update Flag (only transmitted in Teletext * packets). When this flag is set, the PID is intended to * update VCR memory, it does not refer to the current * program. According to the examples in EN 300 231 Annex E.3 * however VCRs should probably also handle the PID as if a @c * VBI_PIL_INHIBIT_TERMINATE service code was transmitted. * * This flag is used to announce a new PIL for the current * program. The CNI may refer to a different network than the * one transmitting the PID, for example when a program is * about to overrun and will continue on a different network. * If a program is postponed and no transmission time has been * decided yet the new PIL may contain an arbitrary or unreal * time. */ vbi_bool luf; /** * PDC Mode Identifier (Teletext). When @c TRUE the end of * transmission of this PIL coincides exactly with the end of * the program. When @c FALSE the program ends 30 seconds * after the PIL is no longer transmitted. Note the flag * applies to all valid PILs as well as the @c * VBI_PIL_INHIBIT_TERMINATE and @c VBI_PIL_INTERRUPTION * service codes. */ vbi_bool mi; /** * PDC Prepare to Record Flag (Teletext). When @c TRUE the * program identified by this PID is about to start. A * transition to @c FALSE indicates the immediate start of the * program, regardless of the state of the MI flag. */ vbi_bool prf; /** PDC Program Control Status - Audio (Teletext and VPS). */ vbi_pcs_audio pcs_audio; /** * PDC Program Type code (Teletext and VPS), can be 0 or 0xFF * if none or unknown. */ unsigned int pty; /** * XDS T flag. @c TRUE if a program is routinely tape delayed * (for mountain and pacific time zones). @c FALSE if not or * this is unknown. * * This flag is used to determine if an offset is necessary * because of local station tape delays. The amount of tape * delay used for a given time zone is transmitted in a XDS * Channel Tape Delay packet. */ vbi_bool tape_delayed; void * _reserved2[2]; int _reserved3[4]; } vbi_program_id; /** @} */ /* Private */ extern void _vbi_program_id_dump (const vbi_program_id * pid, FILE * fp) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; VBI_END_DECLS #endif /* __ZVBI_PDC_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/pfc_demux.c000066400000000000000000000214621476363111200154710ustar00rootroot00000000000000/* * libzvbi -- Teletext Page Format Clear packet demultiplexer * * Copyright (C) 2003, 2004, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: pfc_demux.c,v 1.11 2013-07-10 11:37:33 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "misc.h" #include "hamm.h" /* vbi_iham8(), vbi_iham16p() */ #include "pfc_demux.h" #define BLOCK_SEPARATOR 0x0C #define FILLER_BYTE 0x03 /** @internal */ void _vbi_pfc_block_dump (const vbi_pfc_block * pb, FILE * fp, vbi_bool binary) { assert (NULL != pb); assert (NULL != fp); fprintf (fp, "PFC pgno=%x stream=%u id=%u size=%u\n", pb->pgno, pb->stream, pb->application_id, pb->block_size); if (binary) { fwrite (pb->block, sizeof (pb->block[0]), pb->block_size, fp); } else { unsigned int i; for (i = 0; i < pb->block_size; ++i) { fputc (_vbi_to_ascii (pb->block[i]), fp); if ((i % 75) == 75) fputc ('\n', fp); } if ((i % 75) != 75) fputc ('\n', fp); } } /** * @param dx PFC demultiplexer context allocated with vbi_pfc_demux_new(). * * Resets the PFC demux context, useful for example after a channel * change. */ void vbi_pfc_demux_reset (vbi_pfc_demux * dx) { assert (NULL != dx); dx->ci = 256; /* normally 0 ... 15 */ dx->packet = 256; /* normally 1 ... 25 */ dx->n_packets = 0; /* discard all */ dx->bi = 0; /* empty buffer */ dx->left = 0; dx->block.application_id = (unsigned int) -1; /* expect SH next */ } /** @internal */ vbi_bool _vbi_pfc_demux_decode (vbi_pfc_demux * dx, const uint8_t buffer[42]) { unsigned int col; int bp; bp = vbi_unham8 (buffer[2]) * 3; if (bp < 0 || bp > 39) { /* Invalid pointer or hamming error (-1). */ goto desynced; } col = 3; while (col < 42) { int bs; if (dx->left > 0) { unsigned int size; size = MIN (dx->left, 42 - col); memcpy (dx->block.block + dx->bi, buffer + col, size); dx->bi += size; dx->left -= size; if (dx->left > 0) { /* Packet done, block unfinished. */ return TRUE; } col += size; if ((int) dx->block.application_id < 0) { int sh; /* structure header */ sh = vbi_unham16p (dx->block.block) + vbi_unham16p (dx->block.block + 2) * 256; if (sh < 0) { /* Hamming error. */ goto desynced; } dx->block.application_id = sh & 0x1F; dx->block.block_size = sh >> 5; dx->bi = 0; dx->left = dx->block.block_size; continue; } else { if (!dx->callback (dx, dx->user_data, &dx->block)) { goto desynced; } } } if (col <= 3) { if (bp >= 39) { /* No new block starts in this packet. */ return TRUE; } col = bp + 4; /* 2 pmag, 1 bp, 1 bs */ bs = vbi_unham8 (buffer[col - 1]); } else { while (FILLER_BYTE == (bs = vbi_unham8 (buffer[col++]))) { if (col >= 42) { /* No more data in this packet. */ return TRUE; } } } if (BLOCK_SEPARATOR != bs) { /* BP must point to a block separator. */ goto desynced; } /* First with application_id == -1 we read 4 bytes structure header into block[], then with application_id >= 0 block_size data bytes. */ dx->bi = 0; dx->left = 4; dx->block.application_id = (unsigned int) -1; } return TRUE; desynced: /* Incorrectable error, discard current block. */ vbi_pfc_demux_reset (dx); return FALSE; } /** * @param dx PFC demultiplexer context allocated with vbi_pfc_demux_new(). * @param buffer Teletext packet (last 42 bytes, i. e. without clock * run-in and framing code), as in struct vbi_sliced. * * This function takes a raw stream of Teletext packets, filters out the page * and stream requested with vbi_pfc_demux_new() and assembles the * data transmitted in this page in a buffer. When a data block is complete * it calls the output function given to vbi_pfc_demux_new(). * * @returns * FALSE if the packet contained uncorrectable errors. */ vbi_bool vbi_pfc_demux_feed (vbi_pfc_demux * dx, const uint8_t buffer[42]) { int pmag; vbi_pgno pgno; vbi_subno subno; unsigned int packet; assert (NULL != dx); assert (NULL != buffer); /* Packet filter. */ if ((pmag = vbi_unham16p (buffer)) < 0) goto desynced; pgno = pmag & 7; if (0 == pgno) pgno = 0x800; else pgno <<= 8; packet = pmag >> 3; if (0 == packet) { unsigned int stream; unsigned int ci; pgno |= vbi_unham16p (buffer + 2); if (pgno < 0) goto desynced; if (pgno != dx->block.pgno) { dx->n_packets = 0; return TRUE; } subno = vbi_unham16p (buffer + 4) + vbi_unham16p (buffer + 6) * 256; if (subno < 0) goto desynced; stream = (subno >> 8) & 15; if (stream != dx->block.stream) { dx->n_packets = 0; return TRUE; } ci = subno & 15; if (ci != dx->ci) { /* Page continuity lost, wait for new block. */ vbi_pfc_demux_reset (dx); } dx->ci = (ci + 1) & 15; /* next ci expected */ dx->packet = 1; dx->n_packets = ((subno >> 4) & 7) + ((subno >> 9) & 0x18); return TRUE; } else { /* In case 0 == C11 parallel page transmission. */ if ((pgno ^ dx->block.pgno) & 0xF00) { /* Not dx->block.pgno. */ return TRUE; } } if (0 == dx->n_packets) { /* Not dx->block.pgno. */ return TRUE; } if (packet > 25) { /* Stuffing packets, whatever. */ return TRUE; } if (packet != dx->packet || packet > dx->n_packets) { /* Packet continuity lost, wait for new block and page header. */ vbi_pfc_demux_reset (dx); return TRUE; } dx->packet = packet + 1; /* next packet expected */ /* Now the actual decoding. */ return _vbi_pfc_demux_decode (dx, buffer); desynced: /* Incorrectable error, discard current block. */ vbi_pfc_demux_reset (dx); return FALSE; } /** * @param dx PFC demultiplexer context allocated with vbi_pfc_demux_new(). * @param sliced Sliced VBI data. * @param n_lines Number of lines in the @a sliced array. * * This function works like vbi_pfc_demux_feed() but operates * on sliced VBI data and filters out @c VBI_SLICED_TELETEXT_B_625. * * @returns * FALSE if any Teletext lines contained uncorrectable errors. * * @since 0.2.26 */ vbi_bool vbi_pfc_demux_feed_frame (vbi_pfc_demux * dx, const vbi_sliced * sliced, unsigned int n_lines) { const vbi_sliced *end; assert (NULL != dx); assert (NULL != sliced); for (end = sliced + n_lines; sliced < end; ++sliced) { if (sliced->id & VBI_SLICED_TELETEXT_B_625) { if (!vbi_pfc_demux_feed (dx, sliced->data)) return FALSE; } } return TRUE; } /** * @internal */ void _vbi_pfc_demux_destroy (vbi_pfc_demux * dx) { assert (NULL != dx); CLEAR (*dx); } /** * @internal */ vbi_bool _vbi_pfc_demux_init (vbi_pfc_demux * dx, vbi_pgno pgno, unsigned int stream, vbi_pfc_demux_cb * callback, void * user_data) { assert (NULL != dx); assert (NULL != callback); vbi_pfc_demux_reset (dx); dx->callback = callback; dx->user_data = user_data; dx->block.pgno = pgno; dx->block.stream = stream; return TRUE; } /** * @param dx PFC demultiplexer context allocated with * vbi_pfc_demux_new(), can be @c NULL. * * Frees all resources associated with @a dx. */ void vbi_pfc_demux_delete (vbi_pfc_demux * dx) { if (NULL == dx) return; _vbi_pfc_demux_destroy (dx); vbi_free (dx); } /** * @param pgno Page to take PFC data from. * @param stream PFC stream to be demultiplexed. * @param callback Function to be called by vbi_pfc_demux_feed() when * a new data block is available. * @param user_data User pointer passed through to @a cb function. * * Allocates a new Page Function Clear (ETS 300 708 section 4) * demultiplexer. * * @returns * Pointer to newly allocated PFC demux context which must be * freed with vbi_pfc_demux_delete() when done. @c NULL on failure * (out of memory). */ vbi_pfc_demux * vbi_pfc_demux_new (vbi_pgno pgno, unsigned int stream, vbi_pfc_demux_cb * callback, void * user_data) { vbi_pfc_demux *dx; if (!(dx = vbi_malloc (sizeof (*dx)))) { return NULL; } if (!_vbi_pfc_demux_init (dx, pgno, stream, callback, user_data)) { vbi_free (dx); dx = NULL; } return dx; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/pfc_demux.h000066400000000000000000000110021476363111200154630ustar00rootroot00000000000000/* * libzvbi -- Teletext Page Format Clear packet demultiplexer * * Copyright (C) 2003, 2004, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: pfc_demux.h,v 1.14 2008-02-24 14:17:10 mschimek Exp $ */ #ifndef __ZVBI_PFC_DEMUX_H__ #define __ZVBI_PFC_DEMUX_H__ #include /* uint8_t */ #include /* FILE */ #include "bcd.h" /* vbi_pgno */ #include "sliced.h" VBI_BEGIN_DECLS /* Public */ /** * @addtogroup PFCDemux Teletext PFC packet demultiplexer * @ingroup LowDec * @brief Functions to decode data transmissions in Teletext * Page Function Clear packets (EN 300 708 section 4). * @{ */ /** * @brief One block of data returned by vbi_pfc_demux_cb(). */ typedef struct { /** Source page as requested with vbi_pfc_demux_new(). */ vbi_pgno pgno; /** Source stream as requested with vbi_pfc_demux_new(). */ unsigned int stream; /** Application ID transmitted with this data block. */ unsigned int application_id; /** Size of the data block in bytes, 1 ... 2048. */ unsigned int block_size; /** Data block. */ uint8_t block[2048]; } vbi_pfc_block; /** * @brief PFC demultiplexer context. * * The contents of this structure are private. * * Call vbi_pfc_demux_new() to allocate a PFC * demultiplexer context. */ typedef struct _vbi_pfc_demux vbi_pfc_demux; /** * @param dx PFC demultiplexer context returned by * vbi_pfx_demux_new() and given to vbi_pfc_demux_feed(). * @param user_data User pointer given to vbi_pfc_demux_new(). * @param block Structure describing the received data block. * * Function called by vbi_pfc_demux_feed() when a * new data block is available. * * @returns * FALSE on error, will be returned by vbi_pfc_demux_feed(). * * @bug * vbi_pfc_demux_feed() returns the @a user_data pointer as second * parameter the @a block pointer as third parameter, but prior to * version 0.2.26 this function incorrectly defined @a block as * second and @a user_data as third parameter. */ typedef vbi_bool vbi_pfc_demux_cb (vbi_pfc_demux * dx, void * user_data, const vbi_pfc_block * block); extern void vbi_pfc_demux_reset (vbi_pfc_demux * dx) _vbi_nonnull ((1)); extern vbi_bool vbi_pfc_demux_feed (vbi_pfc_demux * dx, const uint8_t buffer[42]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_pfc_demux_feed_frame (vbi_pfc_demux * dx, const vbi_sliced * sliced, unsigned int n_lines) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern void vbi_pfc_demux_delete (vbi_pfc_demux * dx); extern vbi_pfc_demux * vbi_pfc_demux_new (vbi_pgno pgno, unsigned int stream, vbi_pfc_demux_cb * callback, void * user_data) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_alloc _vbi_nonnull ((3)) #endif ; /** @} */ /* Private */ /** * @internal */ struct _vbi_pfc_demux { /** Expected next continuity index. */ unsigned int ci; /** Expected next packet. */ unsigned int packet; /** Expected number of packets. */ unsigned int n_packets; /** Block index. */ unsigned int bi; /** Expected number of block bytes. */ unsigned int left; vbi_pfc_demux_cb * callback; void * user_data; vbi_pfc_block block; }; extern void _vbi_pfc_block_dump (const vbi_pfc_block * pb, FILE * fp, vbi_bool binary) _vbi_nonnull ((1, 2)); extern vbi_bool _vbi_pfc_demux_decode (vbi_pfc_demux * dx, const uint8_t buffer[42]) _vbi_nonnull ((1, 2)); extern void _vbi_pfc_demux_destroy (vbi_pfc_demux * dx) _vbi_nonnull ((1)); extern vbi_bool _vbi_pfc_demux_init (vbi_pfc_demux * dx, vbi_pgno pgno, unsigned int stream, vbi_pfc_demux_cb * callback, void * user_data) _vbi_nonnull ((1, 4)); VBI_END_DECLS #endif /* __ZVBI_PFC_DEMUX_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/proxy-client.c000066400000000000000000001666471476363111200161730ustar00rootroot00000000000000/* * libzvbi -- VBI proxy client * * Copyright (C) 2003, 2004 Tom Zoerner * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: proxy-client.c,v 1.18 2008-02-19 00:35:21 mschimek Exp $ */ static const char rcsid[] = "$Id: proxy-client.c,v 1.18 2008-02-19 00:35:21 mschimek Exp $"; #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include "vbi.h" #include "inout.h" #include "bcd.h" #include "misc.h" #include "proxy-msg.h" #include "proxy-client.h" #ifdef ENABLE_PROXY #define dprintf1(fmt, arg...) do {if (vpc->trace >= 1) fprintf(stderr, "proxy-client: " fmt, ## arg);} while(0) #define dprintf2(fmt, arg...) do {if (vpc->trace >= 2) fprintf(stderr, "proxy-client: " fmt, ## arg);} while(0) /* ---------------------------------------------------------------------------- ** Declaration of types of internal state variables */ typedef enum { CLNT_STATE_NULL, CLNT_STATE_ERROR, CLNT_STATE_WAIT_CON_CNF, CLNT_STATE_WAIT_IDLE, CLNT_STATE_WAIT_SRV_CNF, CLNT_STATE_WAIT_RPC_REPLY, CLNT_STATE_CAPTURING, } PROXY_CLIENT_STATE; struct vbi_proxy_client { unsigned int services; int strict; int buffer_count; int scanning; unsigned int trace; VBI_PROXY_CLIENT_FLAGS client_flags; VBI_PROXY_DAEMON_FLAGS daemon_flags; VBI_DRIVER_API_REV vbi_api_revision; vbi_raw_decoder dec; int chn_scanning; int chn_prio; vbi_bool has_token; vbi_bool sliced_ind; vbi_capture_buffer raw_buf; vbi_capture_buffer slice_buf; vbi_capture capt_api; VBI_PROXY_EV_TYPE ev_mask; PROXY_CLIENT_STATE state; VBIPROXY_MSG_STATE io; VBIPROXY_MSG * p_client_msg; int max_client_msg_size; vbi_bool endianSwap; unsigned long rxTotal; unsigned long rxStartTime; char * p_srv_host; char * p_srv_port; char * p_client_name; char * p_errorstr; VBI_PROXY_CLIENT_CALLBACK * p_callback_func; void * p_callback_data; }; /* timeout for RPC to proxy daemon (for parameter changes) */ #define RPC_TIMEOUT_MSECS 5000 /* timeout for waiting until ongoing read is completed ** used to "free" the socket before sending parameter requests */ #define IDLE_TIMEOUT_MSECS 2000 /* helper macro */ #define VBI_RAW_SERVICES(SRV) (((SRV) & (VBI_SLICED_VBI_625 | VBI_SLICED_VBI_525)) != 0) /* ---------------------------------------------------------------------------- ** Open client connection ** - automatically chooses the optimum transport: TCP/IP or pipe for local ** - since the socket is made non-blocking, the result of the connect is not ** yet available when the function finishes; the caller has to wait for ** completion with select() and then query the socket error status */ static vbi_bool proxy_client_connect_server( vbi_proxy_client * vpc ) { vbi_bool use_tcp_ip; int sock_fd; vbi_bool result = FALSE; use_tcp_ip = FALSE; /* check if a server address has been configured */ if ( ((vpc->p_srv_host != NULL) || (use_tcp_ip == FALSE)) && (vpc->p_srv_port != NULL)) { sock_fd = vbi_proxy_msg_connect_to_server(use_tcp_ip, vpc->p_srv_host, vpc->p_srv_port, &vpc->p_errorstr); if (sock_fd != -1) { /* initialize IO state */ memset(&vpc->io, 0, sizeof(vpc->io)); vpc->io.sock_fd = sock_fd; vpc->io.lastIoTime = time(NULL); vpc->rxStartTime = vpc->io.lastIoTime; vpc->rxTotal = 0; result = TRUE; } } else { dprintf1("connect_server: hostname or port not configured\n"); if (use_tcp_ip && (vpc->p_srv_host == NULL)) asprintf(&vpc->p_errorstr, _("Server hostname not configured.")); else if (vpc->p_srv_port == NULL) asprintf(&vpc->p_errorstr, _("Server port not configured.")); } return result; } /* ---------------------------------------------------------------------------- ** Allocate buffer for client/servier message exchange ** - buffer is allocated statically, large enough for all expected messages */ static vbi_bool proxy_client_alloc_msg_buf( vbi_proxy_client * vpc ) { vbi_bool result; size_t msg_size; msg_size = sizeof(VBIPROXY_MSG_BODY); if ( (vpc->state == CLNT_STATE_CAPTURING) && (vpc->services != 0) ) { /* XXX TODO allow both raw and sliced */ if (VBI_RAW_SERVICES(vpc->services)) msg_size = VBIPROXY_SLICED_IND_SIZE(0, vpc->dec.count[0] + vpc->dec.count[1]); else msg_size = VBIPROXY_SLICED_IND_SIZE(vpc->dec.count[0] + vpc->dec.count[1], 0); if (msg_size < sizeof(VBIPROXY_MSG_BODY)) msg_size = sizeof(VBIPROXY_MSG_BODY); } else msg_size = sizeof(VBIPROXY_MSG_BODY); msg_size += VBIPROXY_MSG_BODY_OFFSET; if (((int) msg_size != vpc->max_client_msg_size) || (vpc->p_client_msg == NULL)) { if (vpc->p_client_msg != NULL) free(vpc->p_client_msg); dprintf2("alloc_msg_buf: allocate buffer for " "max. %lu bytes\n", (unsigned long) msg_size); vpc->max_client_msg_size = msg_size; vpc->p_client_msg = malloc(msg_size); if (vpc->p_client_msg == NULL) { asprintf(&vpc->p_errorstr, _("Virtual memory exhausted.")); result = FALSE; } else result = TRUE; } else result = TRUE; return result; } /* ---------------------------------------------------------------------------- ** Checks the size of a message from server to client */ static vbi_bool proxy_client_check_msg( vbi_proxy_client * vpc, uint len, VBIPROXY_MSG * pMsg ) { VBIPROXY_MSG_HEADER * pHead = &pMsg->head; VBIPROXY_MSG_BODY * pBody = &pMsg->body; vbi_bool result = FALSE; /*if (vpc->p_client_msg->head.type != MSG_TYPE_SLICED_IND) */ dprintf2("check_msg: recv msg type %d, len %d (%s)\n", pHead->type, pHead->len, vbi_proxy_msg_debug_get_type_str(pHead->type)); switch (pHead->type) { case MSG_TYPE_CONNECT_CNF: if ( (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->connect_cnf)) && (memcmp(pBody->connect_cnf.magics.protocol_magic, VBIPROXY_MAGIC_STR, VBIPROXY_MAGIC_LEN) == 0) ) { if (pBody->connect_cnf.magics.endian_magic == VBIPROXY_ENDIAN_MAGIC) { /* endian type matches -> no swapping required */ vpc->endianSwap = FALSE; } else if (pBody->connect_cnf.magics.endian_magic == VBIPROXY_ENDIAN_MISMATCH) { /* endian type does not match -> convert "endianess" of all msg elements > 1 byte */ /* enable byte swapping for all following messages */ vpc->endianSwap = TRUE; } result = TRUE; } break; case MSG_TYPE_CONNECT_REJ: result = ( (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->connect_rej)) && (memcmp(pBody->connect_rej.magics.protocol_magic, VBIPROXY_MAGIC_STR, VBIPROXY_MAGIC_LEN) == 0) ); break; case MSG_TYPE_SLICED_IND: result = (len == sizeof(VBIPROXY_MSG_HEADER) + VBIPROXY_SLICED_IND_SIZE(pBody->sliced_ind.sliced_lines, pBody->sliced_ind.raw_lines)); break; case MSG_TYPE_SERVICE_CNF: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->service_cnf)); break; case MSG_TYPE_SERVICE_REJ: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->service_rej)); break; case MSG_TYPE_CLOSE_REQ: result = (len == sizeof(VBIPROXY_MSG_HEADER)); break; case MSG_TYPE_CHN_TOKEN_CNF: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_token_cnf)); break; case MSG_TYPE_CHN_TOKEN_IND: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_token_ind)); break; case MSG_TYPE_CHN_NOTIFY_CNF: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_notify_cnf)); break; case MSG_TYPE_CHN_SUSPEND_CNF: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_suspend_cnf)); break; case MSG_TYPE_CHN_SUSPEND_REJ: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_suspend_rej)); break; case MSG_TYPE_CHN_IOCTL_CNF: result = (len == sizeof(VBIPROXY_MSG_HEADER) + VBIPROXY_CHN_IOCTL_CNF_SIZE(pBody->chn_ioctl_cnf.arg_size)); break; case MSG_TYPE_CHN_IOCTL_REJ: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_ioctl_rej)); break; case MSG_TYPE_CHN_RECLAIM_REQ: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_reclaim_req)); break; case MSG_TYPE_CHN_CHANGE_IND: result = (len == sizeof(VBIPROXY_MSG_HEADER) + sizeof(pBody->chn_change_ind)); break; case MSG_TYPE_CONNECT_REQ: case MSG_TYPE_SERVICE_REQ: case MSG_TYPE_CHN_TOKEN_REQ: case MSG_TYPE_CHN_RECLAIM_CNF: case MSG_TYPE_CHN_NOTIFY_REQ: case MSG_TYPE_CHN_SUSPEND_REQ: case MSG_TYPE_CHN_IOCTL_REQ: case MSG_TYPE_DAEMON_PID_REQ: case MSG_TYPE_DAEMON_PID_CNF: dprintf1("check_msg: recv server msg type %d (%s)\n", pHead->type, vbi_proxy_msg_debug_get_type_str(pHead->type)); result = FALSE; break; default: dprintf1("check_msg: unknown msg type %d\n", pHead->type); result = FALSE; break; } if (result == FALSE) { dprintf1("check_msg: illegal msg len %d for type %d (%s)\n", len, pHead->type, vbi_proxy_msg_debug_get_type_str(pHead->type)); errno = EMSGSIZE; } return result; } /* ---------------------------------------------------------------------------- ** Handle asynchronous messages from server */ static vbi_bool proxy_client_take_message( vbi_proxy_client * vpc ) { VBIPROXY_MSG_BODY * pMsg = &vpc->p_client_msg->body; vbi_bool result = FALSE; switch (vpc->p_client_msg->head.type) { case MSG_TYPE_SLICED_IND: if (vpc->state == CLNT_STATE_CAPTURING) { /* XXX TODO check raw */ if ((int) pMsg->sliced_ind.sliced_lines > vpc->dec.count[0] + vpc->dec.count[1]) { /* more lines than req. for service -> would overflow the allocated slicer buffer ** -> discard extra lines (should never happen; proxy checks for line counts) */ dprintf1("take_message: SLICED_IND: too many lines: %d > %d\n", pMsg->sliced_ind.sliced_lines, vpc->dec.count[0] + vpc->dec.count[1]); pMsg->sliced_ind.sliced_lines = vpc->dec.count[0] + vpc->dec.count[1]; } /*assert(vpc->sliced_ind == FALSE);*/ vpc->sliced_ind = TRUE; result = TRUE; } else if ( (vpc->state == CLNT_STATE_WAIT_IDLE) || (vpc->state == CLNT_STATE_WAIT_SRV_CNF) || (vpc->state == CLNT_STATE_WAIT_RPC_REPLY) ) { /* discard incoming data during service changes */ result = TRUE; } break; case MSG_TYPE_CHN_TOKEN_IND: if ( (vpc->state == CLNT_STATE_CAPTURING) || (vpc->state == CLNT_STATE_WAIT_IDLE) || (vpc->state == CLNT_STATE_WAIT_RPC_REPLY) ) { /* XXX check if we're currently waiting for CNF for chn param change? */ vpc->has_token = TRUE; vpc->ev_mask |= VBI_PROXY_EV_CHN_GRANTED; result = TRUE; } break; case MSG_TYPE_CHN_RECLAIM_REQ: if (vpc->state >= CLNT_STATE_WAIT_IDLE) { /* XXX FIXME: if no callback registered reply immediately */ /* XXX FIXME? handle "has_token == FALSE": reply immediately? */ vpc->ev_mask |= VBI_PROXY_EV_CHN_RECLAIMED; vpc->ev_mask &= ~VBI_PROXY_EV_CHN_GRANTED; result = TRUE; } break; case MSG_TYPE_CHN_CHANGE_IND: dprintf1("channel change indication: new scanning %d\n", pMsg->chn_change_ind.scanning); vpc->chn_scanning = pMsg->chn_change_ind.scanning; /* schedule callback to be invoked for this event */ if ((pMsg->chn_change_ind.notify_flags & VBI_PROXY_CHN_FLUSH) != 0) vpc->ev_mask |= VBI_PROXY_EV_CHN_CHANGED; if ((pMsg->chn_change_ind.notify_flags & VBI_PROXY_CHN_NORM) != 0) vpc->ev_mask |= VBI_PROXY_EV_NORM_CHANGED; result = TRUE; break; case MSG_TYPE_CLOSE_REQ: result = FALSE; break; case MSG_TYPE_CONNECT_CNF: case MSG_TYPE_CONNECT_REJ: case MSG_TYPE_SERVICE_CNF: case MSG_TYPE_SERVICE_REJ: case MSG_TYPE_CHN_TOKEN_CNF: case MSG_TYPE_CHN_NOTIFY_CNF: case MSG_TYPE_CHN_SUSPEND_CNF: case MSG_TYPE_CHN_SUSPEND_REJ: case MSG_TYPE_CHN_IOCTL_CNF: case MSG_TYPE_CHN_IOCTL_REJ: /* synchronous message - internal error */ dprintf1("take_message: error: handler called for RPC message reply %d (%s)\n", vpc->p_client_msg->head.type, vbi_proxy_msg_debug_get_type_str(vpc->p_client_msg->head.type)); result = FALSE; break; default: break; } if ((result == FALSE) && (vpc->p_errorstr == NULL)) { dprintf1("take_message: message type %d (len %d) not expected in state %d\n", vpc->p_client_msg->head.type, vpc->p_client_msg->head.len, vpc->state); asprintf(&vpc->p_errorstr, _("Protocol error (unexpected message).")); } return result; } /* ---------------------------------------------------------------------------- ** Close client connection */ static void proxy_client_close( vbi_proxy_client * vpc ) { int save_errno; if (vpc != NULL) { save_errno = errno; vbi_proxy_msg_close_io(&vpc->io); memset(&vpc->io, 0, sizeof(vpc->io)); vpc->io.sock_fd = -1; vpc->io.lastIoTime = time(NULL); if (vpc->state != CLNT_STATE_NULL) { vpc->state = CLNT_STATE_ERROR; } errno = save_errno; } else dprintf1("proxy_client-close: illegal NULL ptr param"); } /* ---------------------------------------------------------------------------- ** Wait for I/O event on socket with the given timeout */ static int proxy_client_wait_select( vbi_proxy_client * vpc, struct timeval * timeout ) { struct timeval tv_start; struct timeval tv; fd_set fd_rd; fd_set fd_wr; int ret; if (vpc->io.sock_fd != -1) { do { #ifdef HAVE_LIBPTHREAD pthread_testcancel(); #endif FD_ZERO(&fd_rd); FD_ZERO(&fd_wr); if (vpc->io.writeLen > 0) FD_SET(vpc->io.sock_fd, &fd_wr); else FD_SET(vpc->io.sock_fd, &fd_rd); if ( ((vpc->client_flags & VBI_PROXY_CLIENT_NO_TIMEOUTS) == 0) && ((vpc->daemon_flags & VBI_PROXY_DAEMON_NO_TIMEOUTS) == 0) ) { tv = *timeout; /* Linux kernel overwrites this */ gettimeofday(&tv_start, NULL); ret = select(vpc->io.sock_fd + 1, &fd_rd, &fd_wr, NULL, &tv); vbi_capture_io_update_timeout(timeout, &tv_start); } else ret = select(vpc->io.sock_fd + 1, &fd_rd, &fd_wr, NULL, NULL); } while ((ret < 0) && (errno == EINTR)); if (ret > 0) { dprintf2("wait_select: waited for %c -> sock r/w %d/%d\n", (vpc->io.writeLen > 0) ? 'w':'r', (int) FD_ISSET(vpc->io.sock_fd, &fd_rd), (int) FD_ISSET(vpc->io.sock_fd, &fd_wr)); } else if (ret == 0) { dprintf1("wait_select: timeout\n"); } else { dprintf1("wait_select: error %d (%s)\n", errno, strerror(errno)); } } else { dprintf1("wait_select: socket not open\n"); ret = -1; } return ret; } /* ---------------------------------------------------------------------------- ** Call remote procedure, i.e. write message then wait for reply ** - message must already have been written prior to calling this function ** - this is a synchronous message exchange with the daemon, i.e. the function ** does not return until a reply is available or a timeout occured (in which ** case the connection is dropped.) */ static vbi_bool proxy_client_rpc( vbi_proxy_client * vpc, VBIPROXY_MSG_TYPE reply1, VBIPROXY_MSG_TYPE reply2 ) { struct timeval tv; vbi_bool io_blocked; assert (vpc->state != CLNT_STATE_ERROR); assert (vpc->io.sock_fd != -1); tv.tv_sec = RPC_TIMEOUT_MSECS / 1000; tv.tv_usec = (RPC_TIMEOUT_MSECS % 1000) * 1000; /* wait for write to finish */ do { if (proxy_client_wait_select(vpc, &tv) <= 0) goto failure; if (vbi_proxy_msg_handle_write(&vpc->io, &io_blocked) == FALSE) goto failure; } while (vpc->io.writeLen > 0); /* wait for reply message */ while (1) { assert (vbi_proxy_msg_is_idle(&vpc->io)); do { if (proxy_client_wait_select(vpc, &tv) <= 0) goto failure; if (vbi_proxy_msg_handle_read(&vpc->io, &io_blocked, TRUE, vpc->p_client_msg, vpc->max_client_msg_size) == FALSE) goto failure; } while ((vpc->io.readOff == 0) || (vpc->io.readOff < vpc->io.readLen)); /* perform security checks on received message */ if (proxy_client_check_msg(vpc, vpc->io.readLen, vpc->p_client_msg) == FALSE) goto failure; vpc->rxTotal += vpc->p_client_msg->head.len; vbi_proxy_msg_close_read(&vpc->io); /* if it's the expected reply, we're finished */ if ( (vpc->p_client_msg->head.type != reply1) && (vpc->p_client_msg->head.type != reply2) ) { /* process asynchronous message (e.g. slicer data or another IND message) */ if (proxy_client_take_message(vpc) == FALSE) goto failure; } else break; } return TRUE; failure: asprintf(&vpc->p_errorstr, _("Connection lost due to I/O error.")); return FALSE; } /* ---------------------------------------------------------------------------- ** Read a message from the socket ** - if no data is available in the socket buffer the function blocks; ** when the timeout is reached the function returns 0 */ static int proxy_client_read_message( vbi_proxy_client * vpc, struct timeval * p_timeout ) { vbi_bool io_blocked; int ret; /* simultaneous read and write is not supported */ assert (vpc->io.writeLen == 0); assert ((vpc->io.readOff == 0) || (vpc->io.readLen < vpc->io.readOff)); if (proxy_client_alloc_msg_buf(vpc) == FALSE) goto failure; do { ret = proxy_client_wait_select(vpc, p_timeout); if (ret < 0) goto failure; if (ret == 0) break; if (vbi_proxy_msg_handle_read(&vpc->io, &io_blocked, TRUE, vpc->p_client_msg, vpc->max_client_msg_size) == FALSE) goto failure; } while (vpc->io.readOff < vpc->io.readLen); if (ret > 0) { /* perform security checks on received message */ if (proxy_client_check_msg(vpc, vpc->io.readLen, vpc->p_client_msg) == FALSE) goto failure; vpc->rxTotal += vpc->p_client_msg->head.len; vbi_proxy_msg_close_read(&vpc->io); /* process the message - frees the buffer if neccessary */ if (proxy_client_take_message(vpc) == FALSE) goto failure; } return ret; failure: asprintf(&vpc->p_errorstr, _("Connection lost due to I/O error.")); proxy_client_close(vpc); return -1; } /* ---------------------------------------------------------------------------- ** Wait until ongoing read is finished ** - incoming data is discarded */ static vbi_bool proxy_client_wait_idle( vbi_proxy_client * vpc ) { PROXY_CLIENT_STATE old_state; struct timeval tv; vbi_bool io_blocked; assert (vpc->io.writeLen == 0); if (vpc->io.readOff > 0) { /* set intermediate state so that incoming data is discarded in the handler */ tv.tv_sec = IDLE_TIMEOUT_MSECS / 1000; tv.tv_usec = IDLE_TIMEOUT_MSECS * 1000; while (vpc->io.readOff < vpc->io.readLen) { if (proxy_client_wait_select(vpc, &tv) <= 0) goto failure; if (vbi_proxy_msg_handle_read(&vpc->io, &io_blocked, TRUE, vpc->p_client_msg, vpc->max_client_msg_size) == FALSE) goto failure; } /* perform security checks on received message */ if (proxy_client_check_msg(vpc, vpc->io.readLen, vpc->p_client_msg) == FALSE) goto failure; vpc->rxTotal += vpc->p_client_msg->head.len; vbi_proxy_msg_close_read(&vpc->io); old_state = vpc->state; vpc->state = CLNT_STATE_WAIT_IDLE; if (proxy_client_take_message(vpc) == FALSE) goto failure; vpc->state = old_state; } return TRUE; failure: return FALSE; } /* ---------------------------------------------------------------------------- ** Start VBI acquisition, i.e. open connection to proxy daemon */ static vbi_bool proxy_client_start_acq( vbi_proxy_client * vpc ) { VBIPROXY_CONNECT_REQ * p_req_msg; VBIPROXY_CONNECT_CNF * p_cnf_msg; VBIPROXY_CONNECT_REJ * p_rej_msg; struct timeval tv; assert(vpc->state == CLNT_STATE_NULL); if (proxy_client_connect_server(vpc) == FALSE) goto failure; /* fake write request: make select to wait for socket to become writable */ vpc->io.writeLen = 1; tv.tv_sec = 4; tv.tv_usec = 0; /* wait for socket to reach connected state */ if (proxy_client_wait_select(vpc, &tv) <= 0) goto failure; vpc->io.writeLen = 0; if (vbi_proxy_msg_finish_connect(vpc->io.sock_fd, &vpc->p_errorstr) == FALSE) goto failure; if (proxy_client_alloc_msg_buf(vpc) == FALSE) goto failure; /* write service request parameters */ p_req_msg = &vpc->p_client_msg->body.connect_req; vbi_proxy_msg_fill_magics(&p_req_msg->magics); strlcpy((char *) p_req_msg->client_name, vpc->p_client_name, VBIPROXY_CLIENT_NAME_MAX_LENGTH); p_req_msg->client_name[VBIPROXY_CLIENT_NAME_MAX_LENGTH - 1] = 0; p_req_msg->pid = getpid(); p_req_msg->client_flags = vpc->client_flags; p_req_msg->scanning = vpc->scanning; p_req_msg->services = vpc->services; p_req_msg->strict = vpc->strict; p_req_msg->buffer_count = vpc->buffer_count; /* send the connect request message to the proxy server */ vbi_proxy_msg_write(&vpc->io, MSG_TYPE_CONNECT_REQ, sizeof(p_req_msg[0]), vpc->p_client_msg, FALSE); vpc->state = CLNT_STATE_WAIT_CON_CNF; /* send message and wait for reply */ if (proxy_client_rpc(vpc, MSG_TYPE_CONNECT_CNF, MSG_TYPE_CONNECT_REJ) == FALSE) goto failure; if (vpc->p_client_msg->head.type == MSG_TYPE_CONNECT_CNF) { p_cnf_msg = &vpc->p_client_msg->body.connect_cnf; /* first server message received: contains version info */ /* note: nxtvepg and endian magics are already checked */ if (p_cnf_msg->magics.protocol_compat_version != VBIPROXY_COMPAT_VERSION) { dprintf1("take_message: CONNECT_CNF: reply version %x, protocol %x\n", p_cnf_msg->magics.protocol_version, p_cnf_msg->magics.protocol_compat_version); asprintf (&vpc->p_errorstr, _("Incompatible server version %u.%u.%u."), ((p_cnf_msg->magics.protocol_compat_version >> 16) & 0xff), ((p_cnf_msg->magics.protocol_compat_version >> 8) & 0xff), ((p_cnf_msg->magics.protocol_compat_version ) & 0xff)); goto failure; } else if (vpc->endianSwap) { /* endian swapping currently unsupported */ asprintf(&vpc->p_errorstr, _("Incompatible server architecture (endianness mismatch).")); goto failure; } else { /* version ok -> request block forwarding */ dprintf1("Successfully connected to proxy (version %x.%x.%x, protocol %x.%x.%x)\n", (p_cnf_msg->magics.protocol_version >> 16) & 0xff, (p_cnf_msg->magics.protocol_version >> 8) & 0xff, (p_cnf_msg->magics.protocol_version) & 0xff, (p_cnf_msg->magics.protocol_compat_version >> 16) & 0xff, (p_cnf_msg->magics.protocol_compat_version >> 8) & 0xff, (p_cnf_msg->magics.protocol_compat_version) & 0xff); vpc->dec = p_cnf_msg->dec; vpc->services = p_cnf_msg->services; vpc->daemon_flags = p_cnf_msg->daemon_flags; vpc->vbi_api_revision = p_cnf_msg->vbi_api_revision; vpc->state = CLNT_STATE_CAPTURING; } } else { p_rej_msg = &vpc->p_client_msg->body.connect_rej; dprintf2("take_message: CONNECT_REJ: reply version %x, protocol %x\n", p_rej_msg->magics.protocol_version, p_rej_msg->magics.protocol_compat_version); if (vpc->p_errorstr != NULL) { free(vpc->p_errorstr); vpc->p_errorstr = NULL; } if (p_rej_msg->errorstr[0] != 0) vpc->p_errorstr = strdup((char *) p_rej_msg->errorstr); goto failure; } return TRUE; failure: /* failed to establish a connection to the server */ proxy_client_close(vpc); return FALSE; } /* ---------------------------------------------------------------------------- ** Stop acquisition, i.e. close connection */ static void proxy_client_stop_acq( vbi_proxy_client * vpc ) { if (vpc->state != CLNT_STATE_NULL) { /* note: set the new state first to prevent callback from close function */ vpc->state = CLNT_STATE_NULL; proxy_client_close(vpc); } else dprintf1("stop_acq: acq not enabled\n"); } /* ---------------------------------------------------------------------------- ** Process pending callbacks ** - returns FALSE if caller should return from loop */ static void vbi_proxy_process_callbacks( vbi_proxy_client * vpc ) { VBI_PROXY_EV_TYPE ev_mask; if (vpc->ev_mask != VBI_PROXY_EV_NONE) { ev_mask = vpc->ev_mask; vpc->ev_mask = VBI_PROXY_EV_NONE; if (vpc->p_callback_func != NULL) { vpc->p_callback_func(vpc->p_callback_data, ev_mask); } else { if (ev_mask & VBI_PROXY_EV_CHN_RECLAIMED) { } } } } /* ---------------------------------------------------------------------------- ** E X P O R T E D F U N C T I O N S ** --------------------------------------------------------------------------*/ /* document below */ int vbi_proxy_client_channel_request( vbi_proxy_client * vpc, VBI_CHN_PRIO chn_prio, vbi_channel_profile * p_chn_profile ) { VBIPROXY_CHN_TOKEN_REQ * p_req; int result; if (vpc != NULL) { if (vpc->state == CLNT_STATE_ERROR) return -1; dprintf1("Request for channel token: prio=%d\n", chn_prio); assert(vpc->state == CLNT_STATE_CAPTURING); if (proxy_client_alloc_msg_buf(vpc) == FALSE) goto failure; /* wait for ongoing read to complete (XXX FIXME: don't discard messages) */ if (proxy_client_wait_idle(vpc) == FALSE) goto failure; /* reset token in any case because prio or profile may have changed */ vpc->has_token = FALSE; vpc->ev_mask &= ~VBI_PROXY_EV_CHN_GRANTED; vpc->chn_prio = chn_prio; vpc->state = CLNT_STATE_WAIT_RPC_REPLY; /* send channel change request to proxy daemon */ p_req = &vpc->p_client_msg->body.chn_token_req; memset(p_req, 0, sizeof(p_req[0])); p_req->chn_prio = chn_prio; p_req->chn_profile = *p_chn_profile; vbi_proxy_msg_write(&vpc->io, MSG_TYPE_CHN_TOKEN_REQ, sizeof(p_req[0]), vpc->p_client_msg, FALSE); /* send message and wait for reply */ if (proxy_client_rpc(vpc, MSG_TYPE_CHN_TOKEN_CNF, -1) == FALSE) goto failure; /* process reply message */ vpc->has_token = vpc->p_client_msg->body.chn_token_cnf.token_ind; if (vpc->has_token) { vpc->ev_mask |= VBI_PROXY_EV_CHN_GRANTED; } vpc->state = CLNT_STATE_CAPTURING; result = (vpc->has_token ? 1 : 0); /* invoke callback in case TOKEN_IND was piggy-backed */ vbi_proxy_process_callbacks(vpc); return result; } failure: proxy_client_close(vpc); return -1; } /* document below */ int vbi_proxy_client_channel_notify( vbi_proxy_client * vpc, VBI_PROXY_CHN_FLAGS notify_flags, unsigned int scanning ) { VBIPROXY_CHN_NOTIFY_REQ * p_msg; if (vpc != NULL) { if (vpc->state == CLNT_STATE_ERROR) return -1; assert(vpc->state == CLNT_STATE_CAPTURING); if (proxy_client_alloc_msg_buf(vpc) == FALSE) goto failure; /* wait for ongoing read to complete (XXX FIXME: don't discard messages) */ if (proxy_client_wait_idle(vpc) == FALSE) goto failure; dprintf1("Send channel notification: flags 0x%X, scanning %d (prio=%d, has_token=%d)\n", notify_flags, scanning, vpc->chn_prio, vpc->has_token); memset(vpc->p_client_msg, 0, sizeof(vpc->p_client_msg[0])); p_msg = &vpc->p_client_msg->body.chn_notify_req; p_msg->notify_flags = notify_flags; p_msg->scanning = scanning; vbi_proxy_msg_write(&vpc->io, MSG_TYPE_CHN_NOTIFY_REQ, sizeof(p_msg[0]), vpc->p_client_msg, FALSE); vpc->state = CLNT_STATE_WAIT_RPC_REPLY; /* send message and wait for reply */ if (proxy_client_rpc(vpc, MSG_TYPE_CHN_NOTIFY_CNF, -1) == FALSE) goto failure; /* process reply message */ /* XXX TODO */ vpc->state = CLNT_STATE_CAPTURING; } /* invoke callback in case TOKEN_IND was piggy-backed */ vbi_proxy_process_callbacks(vpc); return 0; failure: proxy_client_close(vpc); return -1; } /* document below */ int vbi_proxy_client_channel_suspend( vbi_proxy_client * vpc, VBI_PROXY_SUSPEND cmd ) { /* XXX TODO */ vpc = vpc; cmd = cmd; return -1; } /* document below */ int vbi_proxy_client_device_ioctl( vbi_proxy_client * vpc, int request, void * p_arg ) { VBIPROXY_MSG * p_msg; vbi_bool req_perm; int size; int result = -1; if (vpc != NULL) { if (vpc->state == CLNT_STATE_CAPTURING) { /* determine size of the argument */ size = vbi_proxy_msg_check_ioctl(vpc->vbi_api_revision, request, p_arg, &req_perm); if (size >= 0) { /* XXX TODO: for GET type calls on v4l2 use local device */ if ( (req_perm == FALSE) || (vpc->chn_prio > VBI_CHN_PRIO_BACKGROUND) || vpc->has_token ) { /* wait for ongoing read to complete (XXX FIXME: don't discard messages) */ if (proxy_client_wait_idle(vpc) == FALSE) goto failure; dprintf1("Forwarding ioctl: 0x%X, argp=0x%lX\n", request, (long)p_arg); p_msg = malloc(VBIPROXY_MSG_BODY_OFFSET + VBIPROXY_CHN_IOCTL_REQ_SIZE(size)); if (p_msg == NULL) goto failure; p_msg->body.chn_ioctl_req.request = request; p_msg->body.chn_ioctl_req.arg_size = size; if (size > 0) memcpy(p_msg->body.chn_ioctl_req.arg_data, p_arg, size); vbi_proxy_msg_write(&vpc->io, MSG_TYPE_CHN_IOCTL_REQ, VBIPROXY_CHN_IOCTL_REQ_SIZE(size), p_msg, TRUE); /* send message and wait for reply */ if (proxy_client_rpc(vpc, MSG_TYPE_CHN_IOCTL_CNF, MSG_TYPE_CHN_IOCTL_REJ) == FALSE) goto failure; /* process reply message */ if (vpc->p_client_msg->head.type == MSG_TYPE_CHN_IOCTL_CNF) { if (size > 0) memcpy(p_arg, vpc->p_client_msg->body.chn_ioctl_req.arg_data, size); result = vpc->p_client_msg->body.chn_ioctl_cnf.result; errno = vpc->p_client_msg->body.chn_ioctl_cnf.errcode; } else { errno = EBUSY; result = -1; } vpc->state = CLNT_STATE_CAPTURING; } else { dprintf1("vbi_proxy-client_ioctl: request not allowed without obtaining token first\n"); errno = EBUSY; } } else { dprintf1("vbi_proxy-client_ioctl: unknown or not allowed request: 0x%X\n", request); errno = EINVAL; } } else dprintf1("vbi_proxy-client_ioctl: client in invalid state %d\n", vpc->state); vbi_proxy_process_callbacks(vpc); } else dprintf1("vbi_proxy-client_ioctl: invalid NULL ptr param\n"); failure: return result; } /* document below */ int vbi_proxy_client_get_channel_desc( vbi_proxy_client * vpc, unsigned int * p_scanning, vbi_bool * p_granted ) { if (vpc != NULL) { if (p_scanning != NULL) *p_scanning = vpc->scanning; if (p_granted != NULL) *p_granted = vpc->has_token; return 0; } else return -1; } /* document below */ vbi_bool vbi_proxy_client_has_channel_control( vbi_proxy_client * vpc ) { if (vpc != NULL) { return (vpc->has_token); } else { dprintf1("vbi_proxy_client-has_channel_token: NULL client param"); return FALSE; } } /* document below */ VBI_DRIVER_API_REV vbi_proxy_client_get_driver_api( vbi_proxy_client * vpc ) { if (vpc != NULL) { return vpc->vbi_api_revision; } else return VBI_API_UNKNOWN; } /* document below */ VBI_PROXY_CLIENT_CALLBACK * vbi_proxy_client_set_callback( vbi_proxy_client * vpc, VBI_PROXY_CLIENT_CALLBACK * p_callback, void * p_data ) { VBI_PROXY_CLIENT_CALLBACK * p_prev_cb = NULL; if (vpc != NULL) { p_prev_cb = vpc->p_callback_func; vpc->p_callback_func = p_callback; vpc->p_callback_data = p_data; } else dprintf1("vbi_proxy_client-set_callback: invalid pointer arg\n"); return p_prev_cb; } /* document below */ vbi_capture * vbi_proxy_client_get_capture_if( vbi_proxy_client * vpc ) { if (vpc != NULL) { return &vpc->capt_api; } else return NULL; } /* ---------------------------------------------------------------------------- ** D E V I C E C A P T U R E A P I ** --------------------------------------------------------------------------*/ /** * @internal * * @param vpc Pointer to initialized proxy client context * * @return * Pointer to a vbi_raw_decoder structure, read only. Returns @c NULL * upon error (i.e. if the client is not connected to the daemon) */ static vbi_raw_decoder * vbi_proxy_client_get_dec_params( vbi_capture * vc ) { vbi_proxy_client * vpc = PARENT(vc, vbi_proxy_client, capt_api); if (vc != NULL) return &vpc->dec; else return NULL; } /** * @internal * * @param vpc Pointer to initialized proxy client context * * @return * File descriptor of the socket used to connect to the proxy daemon or * -1 upon error (i.e. if the client is not connected to the daemon) * The descriptor can only be used for select() by caller, i.e. not for * read/write and must never be closed (call the close function instead) */ static int vbi_proxy_client_get_fd( vbi_capture * vc ) { vbi_proxy_client * vpc = PARENT(vc, vbi_proxy_client, capt_api); if (vc != NULL) { return vpc->io.sock_fd; } else return -1; } /** * @internal * * Read one frame's worth of VBI data. If asynchronous events occur, * the callback is invoked before the call returns. * * Note: This function may indicate a timeout (i.e. return 0) even * if a previous select indicated readability. This will occur when * asynchronous messages (e.g. channel change indications) arrive. * Proxy clients should be prepared for this. Channel change * indications can be supressed with VBI_PROXY_CLIENT_NO_STATUS_IND * in client flags during creation of the proxy, but there may still * be asynchronous messages when a token is granted. */ static int vbi_proxy_client_read( vbi_capture * vc, struct vbi_capture_buffer **pp_raw_buf, struct vbi_capture_buffer **pp_slice_buf, const struct timeval * p_timeout ) { vbi_proxy_client * vpc = PARENT(vc, vbi_proxy_client, capt_api); struct timeval timeout = *p_timeout; int lines; int result; if ((vc != NULL) && (vpc->state == CLNT_STATE_CAPTURING)) { vpc->sliced_ind = FALSE; /* wait for message & read it (note: may also be some status ind) */ result = proxy_client_read_message(vpc, &timeout); if (result > 0) { if (vpc->sliced_ind != FALSE) { if (pp_raw_buf != NULL) { lines = vpc->p_client_msg->body.sliced_ind.raw_lines; if (*pp_raw_buf != NULL) { /* XXX optimization possible: read sliced msg into buffer to avoid memcpy */ memcpy( (*pp_raw_buf)->data, vpc->p_client_msg->body.sliced_ind.u.raw, lines * VBIPROXY_RAW_LINE_SIZE ); } else { *pp_raw_buf = &vpc->raw_buf; (*pp_raw_buf)->data = vpc->p_client_msg->body.sliced_ind.u.raw; } (*pp_raw_buf)->size = lines * VBIPROXY_RAW_LINE_SIZE; (*pp_raw_buf)->timestamp = vpc->p_client_msg->body.sliced_ind.timestamp; } if (pp_slice_buf != NULL) { lines = vpc->p_client_msg->body.sliced_ind.sliced_lines; if (*pp_slice_buf != NULL) { /* XXX optimization possible: read sliced msg into buffer to avoid memcpy */ memcpy( (*pp_slice_buf)->data, vpc->p_client_msg->body.sliced_ind.u.sliced, lines * sizeof(vbi_sliced) ); } else { *pp_slice_buf = &vpc->slice_buf; (*pp_slice_buf)->data = vpc->p_client_msg->body.sliced_ind.u.sliced; } (*pp_slice_buf)->size = lines * sizeof(vbi_sliced); (*pp_slice_buf)->timestamp = vpc->p_client_msg->body.sliced_ind.timestamp; } } else { /* not a slicer data unit */ result = 0; } vbi_proxy_process_callbacks(vpc); } return result; } errno = EBADF; return -1; } /** * @internal * * Add and/or remove one or more services to an already initialized * capture context. * * Note the "commit" parameter is currently not applicable to proxy clients. */ static unsigned int vbi_proxy_client_update_services( vbi_capture * vc, vbi_bool reset, vbi_bool commit, unsigned int services, int strict, char ** pp_errorstr ) { vbi_proxy_client * vpc = PARENT(vc, vbi_proxy_client, capt_api); if (vc != NULL) { if (vpc->state == CLNT_STATE_ERROR) return 0; assert(vpc->state == CLNT_STATE_CAPTURING); if (proxy_client_alloc_msg_buf(vpc) == FALSE) goto failure; /* wait for ongoing read to complete */ if (proxy_client_wait_idle(vpc) == FALSE) goto failure; vpc->state = CLNT_STATE_WAIT_SRV_CNF; dprintf1("update_services: send service req: srv %d, strict %d\n", services, strict); /* send service request to proxy daemon */ vpc->p_client_msg->body.service_req.reset = reset; vpc->p_client_msg->body.service_req.commit = commit; vpc->p_client_msg->body.service_req.services = services; vpc->p_client_msg->body.service_req.strict = strict; vbi_proxy_msg_write(&vpc->io, MSG_TYPE_SERVICE_REQ, sizeof(vpc->p_client_msg->body.service_req), vpc->p_client_msg, FALSE); /* send message and wait for reply */ if (proxy_client_rpc(vpc, MSG_TYPE_SERVICE_CNF, MSG_TYPE_SERVICE_REJ) == FALSE) goto failure; if (vpc->p_client_msg->head.type == MSG_TYPE_SERVICE_CNF) { memset(&vpc->dec, 0, sizeof(vpc->dec)); vpc->services = vpc->p_client_msg->body.service_cnf.services; memcpy(&vpc->dec, &vpc->p_client_msg->body.service_cnf.dec, sizeof(vpc->dec)); dprintf1("service cnf: granted service %d\n", vpc->dec.services); } else { /* process the message */ if ( (vpc->p_client_msg->body.service_rej.errorstr[0] != 0) && (pp_errorstr != NULL) ) { *pp_errorstr = strdup((char *) vpc->p_client_msg->body.service_rej.errorstr); } } vpc->state = CLNT_STATE_CAPTURING; return services & vpc->services; } failure: if (vpc->p_errorstr != NULL) { if (pp_errorstr != NULL) *pp_errorstr = vpc->p_errorstr; else free(vpc->p_errorstr); vpc->p_errorstr = NULL; } proxy_client_close(vpc); return 0; } /** * @internal * * Note this function is only present because it's part of the capture * device API. Proxy-aware clients should use the proxy client API * function vbi_proxy_client_channel_notify() instead of this one, because * it allows to return the channel control "token" at the same time. */ static void vbi_proxy_client_flush( vbi_capture * vc ) { vbi_proxy_client * vpc = PARENT(vc, vbi_proxy_client, capt_api); if (vc != NULL) { vbi_proxy_client_channel_notify(vpc, VBI_PROXY_CHN_FLUSH, 0); } } /** * @internal * * @param vpc Pointer to initialized proxy client context * * Queries properties of the exported "capture device" file handle. */ static VBI_CAPTURE_FD_FLAGS vbi_proxy_client_get_fd_flags(vbi_capture *vc) { vc = vc; return VBI_FD_HAS_SELECT; } /** * @internal * * @param vpc Pointer to initialized proxy client context * * Close connection to the proxy daemon. The proxy client context * can be re-used for another connection later. */ static void vbi_proxy_client_stop( vbi_capture * vc ) { vbi_proxy_client * vpc = PARENT(vc, vbi_proxy_client, capt_api); if (vc != NULL) { proxy_client_stop_acq(vpc); } } /* document below */ vbi_capture * vbi_capture_proxy_new( struct vbi_proxy_client * vpc, int buffers, int scanning, unsigned int *p_services, int strict, char **pp_errorstr ) { if (vpc != NULL) { if ( (vpc->state == CLNT_STATE_NULL) || (vpc->state == CLNT_STATE_ERROR) ) { if (scanning != 525 && scanning != 625) scanning = 0; if (buffers < 1) buffers = 1; if (strict < -1) strict = -1; else if (strict > 2) strict = 2; /* check and copy parameters into state struct */ assert((p_services == NULL) || (*p_services != 0)); vpc->buffer_count = buffers; vpc->scanning = scanning, vpc->services = ((p_services != NULL) ? *p_services : 0); vpc->strict = strict; /* reset state if in error state (e.g. previous connect failed) */ vpc->state = CLNT_STATE_NULL; /* send params to daemon and wait for reply */ if ( proxy_client_start_acq(vpc) ) { assert(vpc->state == CLNT_STATE_CAPTURING); assert((p_services == NULL) || (vpc->services != 0)); if (p_services != NULL) *p_services = vpc->services; return &vpc->capt_api; } } else dprintf1("vbi_proxy-client_start: illegal state %d for start\n", vpc->state); } else dprintf1("vbi_proxy-client_start: illegal NULL ptr param\n"); if (pp_errorstr != NULL) *pp_errorstr = vpc->p_errorstr; else free(vpc->p_errorstr); vpc->p_errorstr = NULL; return NULL; } void vbi_proxy_client_destroy( vbi_proxy_client * vpc ) { if (vpc != NULL) { /* close the connection (during normal shutdown it should already be closed) */ if (vpc->state != CLNT_STATE_NULL) proxy_client_stop_acq(vpc); if (vpc->p_srv_host != NULL) free(vpc->p_srv_host); if (vpc->p_srv_port != NULL) free(vpc->p_srv_port); if (vpc->p_client_msg != NULL) free(vpc->p_client_msg); if (vpc->p_errorstr != NULL) free(vpc->p_errorstr); free(vpc); } } /* document below */ vbi_proxy_client * vbi_proxy_client_create( const char *p_dev_name, const char *p_client_name, VBI_PROXY_CLIENT_FLAGS client_flags, char **pp_errorstr, int trace_level ) { vbi_proxy_client * vpc; if (trace_level) { fprintf(stderr, "Creating vbi proxy client, rev.\n%s\n", rcsid); vbi_proxy_msg_set_debug_level(trace_level); } vpc = (vbi_proxy_client *) calloc(1, sizeof(vpc[0])); if (vpc != NULL) { /* fill capture interface struct */ vpc->capt_api.parameters = vbi_proxy_client_get_dec_params; vpc->capt_api._delete = vbi_proxy_client_stop; vpc->capt_api.get_fd = vbi_proxy_client_get_fd; vpc->capt_api.get_fd_flags = vbi_proxy_client_get_fd_flags; vpc->capt_api.read = vbi_proxy_client_read; vpc->capt_api.update_services = vbi_proxy_client_update_services; vpc->capt_api.flush = vbi_proxy_client_flush; /* initialize client state with given parameters */ vpc->p_client_name = strdup(p_client_name); vpc->client_flags = client_flags; vpc->p_srv_port = vbi_proxy_msg_get_socket_name(p_dev_name); vpc->p_srv_host = NULL; vpc->trace = trace_level; vpc->state = CLNT_STATE_NULL; vpc->io.sock_fd = -1; } else { asprintf(pp_errorstr, _("Virtual memory exhausted.")); } return vpc; } #else /* !ENABLE_PROXY */ /** * @addtogroup Proxy VBI capture proxy interface * @ingroup Raw * @brief Receiving sliced or raw data from VBI proxy daemon * * Using the VBI proxy daemon instead of capturing directly from a * VBI device allows multiple clients to capture concurrently, e.g. * to decode multiple data services. */ /** * @param vpc Pointer to initialized proxy client context * @param chn_prio Channel change priority level. If there are other clients * with higher priority the client will be refused any channel changes. * @param p_chn_profile Channel profile for scheduling at background * priority level. * * This function is used to request permission to switch channels or norm. * Since the VBI device can be shared with other proxy clients, clients should * wait for permission, so that the proxy daemon can fairly schedule channel * requests. * * Scheduling differs at the 3 priority levels. For an explanation of * priorities see enum VBI_CHN_PRIO. At background level channel changes * are coordinated by introduction of a virtual token: only the * one client which holds the token is allowed to switch channels. The daemon * will wait for the token to be returned before it's granted to another * client. This way conflicting channel changes are avoided. * * At the upper level the latest request always wins. To avoid interference * the application still might wait until it gets indicated that the token * has been returned to the daemon. * * The token may be granted right away or at a later time, e.g. when it has * to be reclaimed from another client first, or if there are other clients * with higher priority. If a callback has been registered, it will be * invoked when the token arrives; otherwise * vbi_proxy_client_has_channel_control() * can be used to poll for it. * * Note: to set the priority level to "background" only without requesting * a channel, set the is_valid member in the profile to @c FALSE. * * @return * 1 if change is allowed, 0 if not allowed, * -1 on error, examine @c errno for details. * * @since 0.2.9 */ /* XXX TODO improve description */ int vbi_proxy_client_channel_request( vbi_proxy_client * vpc, VBI_CHN_PRIO chn_prio, vbi_channel_profile * p_chn_profile ) { errno = 0; return -1; } /** * @param vpc Pointer to initialized proxy client context * @param notify_flags Combination of event notification bits * @param scanning New norm, if norm event bit is set * * Send channel control request to proxy daemon. * See description of the flags for details. * * @return * 0 upon success, -1 on error, examine @c errno for details. * * @since 0.2.9 */ int vbi_proxy_client_channel_notify( vbi_proxy_client * vpc, VBI_PROXY_CHN_FLAGS notify_flags, unsigned int scanning ) { return -1; } /** * @param vpc Pointer to initialized proxy client context * @param cmd Control command * * Request to temporarily suspend capturing * * @return * 0 upon success, -1 on error, examine @c errno for details. * * @since 0.2.9 */ int vbi_proxy_client_channel_suspend( vbi_proxy_client * vpc, VBI_PROXY_SUSPEND cmd ) { return -1; } /** * @param vpc Pointer to initialized proxy client context * @param request Ioctl request code to be passed to driver * @param p_arg Ioctl argument buffer to be passed to driver. * For ioctls which return data, the buffer will by modified by * the call (i.e. same as if the ioctl had ben called directly) * Note the required buffer size depends on the request code. * * @brief Wrapper for ioctl requests on the VBI device * * This function allows to manipulate parameters of the underlying * VBI device. Not all ioctls are allowed here. It's mainly intended * to be used for channel enumeration and channel/norm changes. * The request codes and parameters are the same as for the actual device. * The caller has to query the driver API first and use the respective * ioctl codes, same as if the device would be used directly. * * @return * Same as for the ioctl, i.e. -1 on error and errno set appropriately. * The funtion also will fail with errno @c EBUSY if the client doesn't * have permission to control the channel. * * @since 0.2.9 */ int vbi_proxy_client_device_ioctl( vbi_proxy_client * vpc, int request, void * p_arg ) { return -1; } /** * @param vpc Pointer to initialized proxy client context * @param p_scanning Returns new scanning after channel change * @param p_granted Returns@c TRUE if client is currently allowed to * switch channels * * Retrieve info sent by the proxy daemon in a channel change indication. * * @return * 0 upon success, -1 on error. * * @since 0.2.9 */ int vbi_proxy_client_get_channel_desc( vbi_proxy_client * vpc, unsigned int * p_scanning, vbi_bool * p_granted ) { return -1; } /** * @param vpc Pointer to initialized proxy client context * * @brief Query if the client is currently allowed to switch channels * * @return * Returns @c TRUE if client is currently allowed to switch channels. * * @since 0.2.9 */ vbi_bool vbi_proxy_client_has_channel_control( vbi_proxy_client * vpc ) { return FALSE; } /** * @param vpc Pointer to initialized proxy client context * * @brief Returns the driver type behind the actual capture device * * This function can be used to query which driver is behind the * device which is currently opened by the VBI proxy daemon. * Applications which use libzvbi's capture API only need not * care about this. The information is only relevant to applications * which need to change channels or norms. * * The function will fail if the client is currently not connected * to the daemon, i.e. VPI capture has to be started first. * * @return * Driver type or -1 on error. * * @since 0.2.9 */ VBI_DRIVER_API_REV vbi_proxy_client_get_driver_api( vbi_proxy_client * vpc ) { return VBI_API_UNKNOWN; } /** * @param vpc Pointer to initialized proxy client context * @param p_callback Pointer to callback function * @param p_data Void pointer which will be passed through to the * callback function unmodified. * * @brief Installs callback function for asynchronous events * * This function installs a callback function which will be invoked * upon asynchronous events (e.g. channel changes by other clients.) * Since the proxy client has no "life" on it's own (i.e. * it's not using an internal thread or process) callbacks will only * occur from inside other proxy client function calls. The client's * file description will become readable when an asynchronous message * has arrived from the daemon. Typically the application then will * call read to obtain sliced data and the callback will be invoked * from inside the read function. Usually in this case the read call * will return zero, i.e. indicate an timeout since no actual sliced * data has arrived. * * Note for channel requests the callback to grant channel control may * be invoked before the request function returns. * Note you can call any interface function from inside the callback, * including the destroy operator. * * @return * Returns pointer to the previous callback or @c NULL if none. * * @since 0.2.9 */ VBI_PROXY_CLIENT_CALLBACK * vbi_proxy_client_set_callback( vbi_proxy_client * vpc, VBI_PROXY_CLIENT_CALLBACK * p_callback, void * p_data ) { return NULL; } /** * @param vpc Pointer to initialized and active proxy client context * * @brief Returns capture interface for an initialized proxy client * * This function is for convenience only: it returns the same pointer * as the previous call to vbi_capture_proxy_new(), so that the client * need not store it. This pointer is required for function calls * through the capture device API (e.g. reading raw or sliced data) * * @return * Pointer to a vbi_capture structure, should be treated as void * by * caller, i.e. acessed neither for read nor write. Returns @c NULL * upon error (i.e. if the client is not connected to the daemon) * * @since 0.2.9 */ vbi_capture * vbi_proxy_client_get_capture_if( vbi_proxy_client * vpc ) { return NULL; } /** * @ingroup Device * * @param p_proxy_client Reference to an initialized proxy client * context. * @param buffers Number of intermediate buffers on server side * of the proxy socket connection. (Note this is not related to the * device buffer count parameter of @a v4l2_new et.al.) * @param scanning This indicates the current norm: 625 for PAL and * 525 for NTSC; set to 0 if you don't know (you should not attempt * to query the device for the norm, as this parameter is only required * for v4l1 drivers which don't support video standard query ioctls) * @param p_services This must point to a set of @ref VBI_SLICED_ * symbols describing the * data services to be decoded. On return the services actually * decodable will be stored here. See vbi_raw_decoder_add() * for details. If you want to capture raw data only, set to * @c VBI_SLICED_VBI_525, @c VBI_SLICED_VBI_625 or both. * If this parameter is @c NULL, no services will be installed. * You can do so later with vbi_capture_update_services(); note the * reset parameter must be set to @c TRUE in this case. * @param strict Will be passed to vbi_raw_decoder_add(). * @param pp_errorstr If not @c NULL this function stores a pointer to an error * description here. You must free() this string when no longer needed. * * Open a new connection to a VBI proxy to open a VBI device for the * given services. On side of the proxy daemon, one of the regular * capture context creation functions (e.g. v4l2_new) is invoked. * If the creation succeeds, and any of the requested services are * available, capturing is started and all captured data is forwarded * transparently to the client. * * Whenever possible the proxy should be used instead of opening the device * directly, since it allows the user to start multiple VBI clients in * parallel. When this function fails (usually because the user hasn't * started the proxy daemon) applications should automatically fall back * to opening the device directly. * * @return * Initialized vbi_capture context, @c NULL on failure. * * @since 0.2.9 */ vbi_capture * vbi_capture_proxy_new( struct vbi_proxy_client *p_proxy_client, int buffers, int scanning, unsigned int *p_services, int strict, char **pp_errorstr ) { pthread_once (&vbi_init_once, vbi_init); asprintf(pp_errorstr, _("Proxy client interface not compiled.")); return NULL; } /** * @param vpc Pointer to initialized proxy client context * * This function closes the connection to the proxy daemon and frees * all resources. The given context must no longer be used after this * function was called. If the context was used via the capture device * interface, the vbi_capture context must be destroyed first. * * @since 0.2.9 */ void vbi_proxy_client_destroy( vbi_proxy_client * vpc ) { vpc = vpc; } /** * @param p_dev_name Name of the device to open, usually one of * @c /dev/vbi or @c /dev/vbi0 and up. Note: should be the same path as * used by the proxy daemon, else the client may not be able to connect. * @param p_client_name Name of the client application, typically identical * to argv[0] (without the path though) Can be used by the proxy daemon * to fine-tune scheduling or to present the user with a list of * currently connected applications. * @param client_flags Can contain one or more members of * VBI_PROXY_CLIENT_FLAGS * @param pp_errorstr If not @c NULL this function stores a pointer to an error * description here. You must free() this string when no longer needed. * @param trace_level Enable debug output to stderr if non-zero. * Larger values produce more output. * * This function initializes a proxy daemon client context with the given * parameters. (Note this function does not yet connect the daemon.) * * @return * Initialized proxy client context, @c NULL on failure * * @since 0.2.9 */ vbi_proxy_client * vbi_proxy_client_create(const char *p_dev_name, const char *p_client_name, VBI_PROXY_CLIENT_FLAGS client_flags, char **pp_errorstr, int trace_level) { asprintf(pp_errorstr, _("Proxy client interface not compiled.")); return NULL; } #endif /* !ENABLE_PROXY */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/proxy-client.h000066400000000000000000000126521476363111200161620ustar00rootroot00000000000000/* * libzvbi -- VBI proxy client * * Copyright (C) 2003, 2004 Tom Zoerner * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: proxy-client.h,v 1.5 2008-02-19 00:35:21 mschimek Exp $ */ #ifndef PROXY_CLIENT_H #define PROXY_CLIENT_H /* Public */ #include /* struct timeval */ /** * @ingroup Proxy * @brief Proxy client context * * A reference to this anonymous structure is returned by * vbi_proxy_client_create and must be passed to the device capture * interface and/or all subsequent calls to proxy client interface * functions. The contents of this structure are private and must * not be accessed or changed by the caller. */ typedef struct vbi_proxy_client vbi_proxy_client; /** * @ingroup Proxy * @brief Bits in event mask parameter to proxy client callback function */ typedef enum { /** * Channel control token was granted, client may now change the channel. * Note: client should return the token after the channel change was * completed (the channel will still remain reserved for the requested * time) */ VBI_PROXY_EV_CHN_GRANTED = 1<<0, /** * Channel (e.g. TV tuner frequency) was changed by another client. */ VBI_PROXY_EV_CHN_CHANGED = 1<<1, /** * Norm was changed by another client (in a way which affects VBI, * e.g. changes between PAL/SECAM are ignored.) The client must update * its services, else no data will be forwarded by the proxy until * the norm is changed back. */ VBI_PROXY_EV_NORM_CHANGED = 1<<2, /** * Proxy requests to return the channel control token. The client is no * longer allowed to switch the channel and must immediately reply with * a channel notification with flag @c VBI_PROXY_CHN_TOKEN */ VBI_PROXY_EV_CHN_RECLAIMED = 1<<3, /** * Empty event mask */ VBI_PROXY_EV_NONE = 0 } VBI_PROXY_EV_TYPE; /** * @ingroup Proxy * @brief Function prototype for proxy client callback * * The first parameter is the value which the client passed when installing * the callback; it's just passed through to the callback unmodified. * The second parameter contains one or more bits to describe which events * occured wince the last call. */ typedef void VBI_PROXY_CLIENT_CALLBACK ( void * p_client_data, VBI_PROXY_EV_TYPE ev_mask ); /* forward declaration from io.h */ struct vbi_capture_buffer; /** * @addtogroup Proxy * @{ */ extern vbi_proxy_client * vbi_proxy_client_create( const char *dev_name, const char *p_client_name, VBI_PROXY_CLIENT_FLAGS client_flags, char **pp_errorstr, int trace_level ); extern void vbi_proxy_client_destroy( vbi_proxy_client * vpc ); extern vbi_capture * vbi_proxy_client_get_capture_if( vbi_proxy_client * vpc ); extern VBI_PROXY_CLIENT_CALLBACK * vbi_proxy_client_set_callback( vbi_proxy_client * vpc, VBI_PROXY_CLIENT_CALLBACK * p_callback, void * p_data ); extern VBI_DRIVER_API_REV vbi_proxy_client_get_driver_api( vbi_proxy_client * vpc ); extern int vbi_proxy_client_channel_request( vbi_proxy_client * vpc, VBI_CHN_PRIO chn_prio, vbi_channel_profile * chn_profile ); extern int vbi_proxy_client_channel_notify( vbi_proxy_client * vpc, VBI_PROXY_CHN_FLAGS notify_flags, unsigned int scanning ); /** * @brief Modes for channel suspend requests. */ typedef enum { /** * Request proxy daemon to stop acquisition (e.g. required by some * device drivers to allow a norm change.) Depending on the driver * this may result in the proxy closing the device file handle * or just stopping the VBI data stream. * Note this command is only allowed when the client is in control * of the channel. */ VBI_PROXY_SUSPEND_START, /** * Restart data acquisition after a previous suspension. */ VBI_PROXY_SUSPEND_STOP } VBI_PROXY_SUSPEND; extern int vbi_proxy_client_channel_suspend( vbi_proxy_client * vpc, VBI_PROXY_SUSPEND cmd ); int vbi_proxy_client_device_ioctl( vbi_proxy_client * vpc, int request, void * p_arg ); extern int vbi_proxy_client_get_channel_desc( vbi_proxy_client * vpc, unsigned int * p_scanning, vbi_bool * p_granted ); extern vbi_bool vbi_proxy_client_has_channel_control( vbi_proxy_client * vpc ); /** @} */ /* Private */ #endif /* PROXY_CLIENT_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/proxy-msg.c000066400000000000000000001366711476363111200154750ustar00rootroot00000000000000/* * libvbi -- Basic I/O between VBI proxy client & server * * Copyright (C) 2003, 2004 Tom Zoerner * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* * Description: * * This module contains a collection of functions for lower-level * socket I/O which are shared between proxy daemon and clients. * Error output is different for daemon and clients: daemon logs * to a file or syslog facility, while the client returns error * strings to the caller, which can be passed to the upper levels * (e.g. the user interface) * * Both UNIX domain and IPv4 and IPv6 sockets are implemented, but * the latter ones are currently not officially supported. */ /* * $Id: proxy-msg.c,v 1.21 2013-08-28 14:45:06 mschimek Exp $ * * $Log: not supported by cvs2svn $ * Revision 1.20 2008/02/19 00:35:21 mschimek * *** empty log message *** * * Revision 1.19 2007/11/27 18:31:07 mschimek * Updated the FSF address in the copyright notice. * * Revision 1.18 2007/08/27 10:17:50 mschimek * *** empty log message *** * * Revision 1.17 2007/08/27 06:44:40 mschimek * vbi_proxy_msg_get_local_socket_addr, vbi_proxy_msg_accept_connection, * vbi_proxy_msg_resolve_symlinks: Replaced strncpy() by the faster a * safer strlcpy(). * vbi_proxy_msg_logger, vbi_proxy_msg_accept_connection: Replaced * sprintf() by the safer snprintf(). * * Revision 1.16 2007/07/23 20:01:18 mschimek * *** empty log message *** * * Revision 1.15 2006/05/22 09:08:46 mschimek * *** empty log message *** * * Revision 1.14 2006/02/10 06:25:37 mschimek * *** empty log message *** * * Revision 1.13 2004/12/30 02:25:29 mschimek * printf ptrdiff_t fixes. * * Revision 1.12 2004/12/13 07:17:09 mschimek * *** empty log message *** * * Revision 1.11 2004/10/25 16:56:29 mschimek * *** empty log message *** * * Revision 1.10 2004/10/24 18:33:47 tomzo * - cleaned up socket I/O interface functions * - added defines for norm change events * * Revision 1.7 2003/06/07 09:42:53 tomzo * Optimized message writing to socket in vbi_proxy_msg_handle_io(): * - keep message header and body in one struct VBIPROXY_MSG (for both read and * write) to be able to write it in complete to the pipe in one syscall * - before, the client usually was woken up after only the header was sent, i.e. * only a partial message was available for reading; this was problematic for * clients which polled the socket with a zero timeout, and is solved now. * * Revision 1.6 2003/06/01 19:36:09 tomzo * Optimization of read message handling: * - use static buffer to read messages instead of dynamic malloc() * - added pointer to read buffer as parameters to _handle_io() * * Revision 1.5 2003/05/24 12:19:11 tomzo * - renamed MSG_TYPE_DATA_IND into _SLICED_IND in preparation for raw data * * Revision 1.4 2003/05/10 13:30:51 tomzo * Reduced default debug level of proxy_msg_trace from 1 to 0 * * Revision 1.3 2003/05/03 12:05:26 tomzo * - use new function vbi_proxy_msg_resolve_symlinks() to get unique device path, * e.g. allow both /dev/vbi and /dev/vbi0 to work as proxy device args * - added new func vbi_proxy_msg_set_debug_level() * - fixed debug output level in various dprintf statements * - fixed copyright headers, added description to file headers * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef ENABLE_PROXY #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_SYS_MMAN_H #include #endif #include "bcd.h" #include "vbi.h" #include "inout.h" #include "misc.h" #include "proxy-msg.h" #ifdef ENABLE_V4L2 #include /* __u8 and friends for videodev2k.h */ #ifndef HAVE_S64_U64 # include /* Linux 2.6.x asm/types.h defines __s64 and __u64 only if __GNUC__ is defined. */ typedef int64_t __s64; typedef uint64_t __u64; #endif #include "videodev2k.h" #endif #ifdef ENABLE_V4L #include "videodev.h" #endif #define dprintf1(fmt, arg...) do {if (proxy_msg_trace >= 1) fprintf(stderr, "proxy_msg: " fmt, ## arg);} while(0) #define dprintf2(fmt, arg...) do {if (proxy_msg_trace >= 2) fprintf(stderr, "proxy_msg: " fmt, ## arg);} while(0) static int proxy_msg_trace = 0; /* settings for log output - only used by the daemon */ static struct { vbi_bool do_logtty; int sysloglev; int fileloglev; char * pLogfileName; } proxy_msg_logcf = { FALSE, 0, 0, NULL }; /* ---------------------------------------------------------------------------- ** Local settings */ #define SRV_IO_TIMEOUT 60 #define SRV_LISTEN_BACKLOG_LEN 10 #define SRV_CLNT_SOCK_BASE_PATH "/tmp/vbiproxy" /* ---------------------------------------------------------------------------- ** Append entry to logfile */ void vbi_proxy_msg_logger( int level, int clnt_fd, int errCode, const char * pText, ... ) { va_list argl; char timestamp[32], fdstr[20]; const char *argv[10]; uint32_t argc, idx; int fd; time_t now = time(NULL); if (pText != NULL) { /* open the logfile, if one is configured */ if ( (level <= proxy_msg_logcf.fileloglev) && (proxy_msg_logcf.pLogfileName != NULL) ) { fd = open(proxy_msg_logcf.pLogfileName, O_WRONLY|O_CREAT|O_APPEND, 0666); if (fd >= 0) { /* each line in the file starts with a timestamp */ strftime(timestamp, sizeof(timestamp) - 1, "[%d/%b/%Y:%H:%M:%S +0000] ", gmtime(&now)); if (write(fd, timestamp, strlen(timestamp)) == -1) fprintf(stderr, "Failed to write timestamp to file\n"); } } else fd = -1; if (proxy_msg_logcf.do_logtty && (level <= LOG_WARNING)) fprintf(stderr, "vbiproxy: "); argc = 0; memset(argv, 0, sizeof(argv)); /* add pointer to file descriptor (for client requests) or pid (for general infos) */ if (clnt_fd != -1) snprintf(fdstr, sizeof (fdstr), "fd %d: ", clnt_fd); else { snprintf(fdstr, sizeof (fdstr), "pid %d: ", (int)getpid()); } argv[argc++] = fdstr; /* add pointer to first log output string */ argv[argc++] = pText; /* append pointers to the rest of the log strings */ va_start(argl, pText); while ((argc < 5) && ((pText = va_arg(argl, char *)) != NULL)) { argv[argc++] = pText; } va_end(argl); /* add system error message */ if (errCode != 0) { argv[argc++] = strerror(errCode); } /* print the strings to the file and/or stderr */ for (idx=0; idx < argc; idx++) { if (fd >= 0) if (write(fd, argv[idx], strlen(argv[idx])) == -1) fprintf(stderr, "Failed to write %s to file\n", argv[idx]); if (proxy_msg_logcf.do_logtty && (level <= LOG_WARNING)) fprintf(stderr, "%s", argv[idx]); } /* terminate the line with a newline character and close the file */ if (fd >= 0) { if (write(fd, "\n", 1) == -1) fprintf(stderr, "Failed to write '\\n' to file\n"); close(fd); } if (proxy_msg_logcf.do_logtty && (level <= LOG_WARNING)) { fprintf(stderr, "\n"); fflush(stderr); } /* syslog output */ if (level <= proxy_msg_logcf.sysloglev) { switch (argc) { case 1: syslog(level, "%s", argv[0]); break; case 2: syslog(level, "%s%s", argv[0], argv[1]); break; case 3: syslog(level, "%s%s%s", argv[0], argv[1],argv[2]); break; case 4: syslog(level, "%s%s%s%s", argv[0], argv[1],argv[2],argv[3]); break; } } } } /* ---------------------------------------------------------------------------- ** Set parameters for event logging ** - loglevel usage ** ERR : fatal errors (which lead to program termination) ** WARNING: this shouldn't happen error (OS failure or internal errors) ** NOTICE : start/stop of the daemon ** INFO : connection establishment and shutdown */ void vbi_proxy_msg_set_logging( vbi_bool do_logtty, int sysloglev, int fileloglev, const char * pLogfileName ) { /* free the memory allocated for the old config strings */ if (proxy_msg_logcf.pLogfileName != NULL) { free(proxy_msg_logcf.pLogfileName); proxy_msg_logcf.pLogfileName = NULL; } proxy_msg_logcf.do_logtty = do_logtty; /* make a copy of the new config strings */ if (pLogfileName != NULL) { proxy_msg_logcf.pLogfileName = malloc(strlen(pLogfileName) + 1); strcpy(proxy_msg_logcf.pLogfileName, pLogfileName); proxy_msg_logcf.fileloglev = ((fileloglev > 0) ? (fileloglev + LOG_ERR) : -1); } else proxy_msg_logcf.fileloglev = -1; if (sysloglev && !proxy_msg_logcf.sysloglev) { openlog("vbiproxy", LOG_PID, LOG_DAEMON); } else if (!sysloglev && proxy_msg_logcf.sysloglev) { } /* convert GUI log-level setting to syslog enum value */ proxy_msg_logcf.sysloglev = ((sysloglev > 0) ? (sysloglev + LOG_ERR) : -1); } /* ---------------------------------------------------------------------------- ** Enable debug output */ void vbi_proxy_msg_set_debug_level( int level ) { proxy_msg_trace = level; } /* ---------------------------------------------------------------------------- ** Print mesage type name ** - must be kept in sync with message type enum */ const char * vbi_proxy_msg_debug_get_type_str( VBIPROXY_MSG_TYPE type ) { const char * pTypeStr; static const struct { VBIPROXY_MSG_TYPE type; const char * const name; } names[] = { #define DEBUG_STR_MSG_TYPE(TYPE) { TYPE, #TYPE + 9}, DEBUG_STR_MSG_TYPE(MSG_TYPE_CONNECT_REQ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CONNECT_CNF) DEBUG_STR_MSG_TYPE(MSG_TYPE_CONNECT_REJ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CLOSE_REQ) DEBUG_STR_MSG_TYPE(MSG_TYPE_SLICED_IND) DEBUG_STR_MSG_TYPE(MSG_TYPE_SERVICE_REQ) DEBUG_STR_MSG_TYPE(MSG_TYPE_SERVICE_CNF) DEBUG_STR_MSG_TYPE(MSG_TYPE_SERVICE_REJ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_TOKEN_REQ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_TOKEN_CNF) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_TOKEN_IND) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_NOTIFY_REQ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_NOTIFY_CNF) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_RECLAIM_REQ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_RECLAIM_CNF) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_SUSPEND_REQ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_SUSPEND_CNF) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_SUSPEND_REJ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_IOCTL_REQ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_IOCTL_CNF) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_IOCTL_REJ) DEBUG_STR_MSG_TYPE(MSG_TYPE_CHN_CHANGE_IND) DEBUG_STR_MSG_TYPE(MSG_TYPE_DAEMON_PID_REQ) DEBUG_STR_MSG_TYPE(MSG_TYPE_DAEMON_PID_CNF) #undef DEBUG_STR_MSG_TYPE }; assert(MSG_TYPE_COUNT == (sizeof(names)/sizeof(names[0]))); if (type < MSG_TYPE_COUNT) { assert(names[type].type == type); pTypeStr = names[type].name; } else pTypeStr = "*INVALID*"; return pTypeStr; } /* ---------------------------------------------------------------------------- ** Check for incomplete read or write buffer */ vbi_bool vbi_proxy_msg_read_idle( VBIPROXY_MSG_STATE * pIO ) { assert((pIO->readOff == 0) || (pIO->readOff == pIO->readLen)); return (pIO->readOff == 0); } vbi_bool vbi_proxy_msg_write_idle( VBIPROXY_MSG_STATE * pIO ) { return (pIO->writeLen == 0); } vbi_bool vbi_proxy_msg_is_idle( VBIPROXY_MSG_STATE * pIO ) { assert((pIO->readOff == 0) || (pIO->readOff == pIO->readLen)); return ((pIO->writeLen == 0) && (pIO->readOff == 0)); } void vbi_proxy_msg_close_read( VBIPROXY_MSG_STATE * pIO ) { assert((pIO->readOff == 0) || (pIO->readOff == pIO->readLen)); pIO->readOff = 0; pIO->readLen = 0; } /* ---------------------------------------------------------------------------- ** Check for I/O timeout ** - returns TRUE in case of timeout */ vbi_bool vbi_proxy_msg_check_timeout( VBIPROXY_MSG_STATE * pIO, time_t now ) { return ( (now > pIO->lastIoTime + SRV_IO_TIMEOUT) && (vbi_proxy_msg_is_idle(pIO) == FALSE) ); } /* ---------------------------------------------------------------------------- ** Write a message to the socket ** - write(2) is called only once per call to this function ** - after errors the I/O state (indicated by FALSE result) is not reset, because ** the caller is expected to close the connection. */ vbi_bool vbi_proxy_msg_handle_write( VBIPROXY_MSG_STATE * pIO, vbi_bool * pBlocked ) { ssize_t len; vbi_bool result = TRUE; assert(pIO->writeLen >= sizeof(VBIPROXY_MSG_HEADER)); assert(pIO->writeOff < pIO->writeLen); *pBlocked = FALSE; len = send(pIO->sock_fd, ((char *)pIO->pWriteBuf) + pIO->writeOff, pIO->writeLen - pIO->writeOff, 0); if (len > 0) { pIO->lastIoTime = time(NULL); pIO->writeOff += len; if (pIO->writeOff >= pIO->writeLen) { /* all data has been written -> free the buffer; reset write state */ if (pIO->freeWriteBuf) free(pIO->pWriteBuf); pIO->freeWriteBuf = FALSE; pIO->pWriteBuf = NULL; pIO->writeLen = 0; } else { /* not all data could be written */ *pBlocked = TRUE; } } else if (len < 0) { if ((errno != EAGAIN) && (errno != EINTR)) { /* network error -> close the connection */ dprintf1("handle_io: write error on fd %d: %s\n", pIO->sock_fd, strerror(errno)); result = FALSE; } else if (errno == EAGAIN) { *pBlocked = TRUE; } } else /* if (len == 0) */ { /* no data was written (actually in this case -1/EAGAIN should be returned) */ *pBlocked = TRUE; } return result; } /* ---------------------------------------------------------------------------- ** Read a message from the network socket ** - read(2) is called only once per call to this function ** - reading is done in 2 phases: first the length of the message is read into ** a small buffer; then a buffer is allocated for the complete message and the ** length variable copied into it and the rest of the message read afterwords. ** - a closed network connection is indicated by a 0 read from a readable socket. ** Readability is indicated by the select syscall and passed here via ** parameter closeOnZeroRead. ** - after errors the I/O state (indicated by FALSE result) is not reset, because ** the caller is expected to close the connection. */ vbi_bool vbi_proxy_msg_handle_read( VBIPROXY_MSG_STATE * pIO, vbi_bool * pBlocked, vbi_bool closeOnZeroRead, VBIPROXY_MSG * pReadBuf, int max_read_len ) { ssize_t len; vbi_bool err; time_t now = time(NULL); vbi_bool result = TRUE; assert(pIO->writeLen == 0); if (pReadBuf != NULL) { err = FALSE; len = 0; /* compiler dummy */ if (pIO->readOff < sizeof(VBIPROXY_MSG_HEADER)) { /* in read phase one: read the message length */ assert (pIO->readLen == 0); len = recv(pIO->sock_fd, (char *)pReadBuf + pIO->readOff, sizeof(VBIPROXY_MSG_HEADER) - pIO->readOff, 0); if (len > 0) { closeOnZeroRead = FALSE; pIO->lastIoTime = now; pIO->readOff += len; if (pIO->readOff >= sizeof(VBIPROXY_MSG_HEADER)) { /* message length variable has been read completely */ /* convert from network byte order (big endian) to host byte order */ pIO->readLen = ntohl(pReadBuf->head.len); pReadBuf->head.len = pIO->readLen; pReadBuf->head.type = ntohl(pReadBuf->head.type); /* dprintf1("handle_io: fd %d: new block: size %d\n", pIO->sock_fd, pIO->readLen); */ if ((pIO->readLen > (size_t) max_read_len) || (pIO->readLen < sizeof(VBIPROXY_MSG_HEADER))) { /* illegal message size -> protocol error */ dprintf1("handle_io: fd %d: illegal block size %d: " "outside limits [%ld..%ld]\n", pIO->sock_fd, pIO->readLen, (long) sizeof(VBIPROXY_MSG_HEADER), max_read_len + (long) sizeof(VBIPROXY_MSG_HEADER)); result = FALSE; } } else *pBlocked = TRUE; } else err = TRUE; } if ((err == FALSE) && (pIO->readOff >= sizeof(VBIPROXY_MSG_HEADER))) { /* in read phase two: read the complete message into the allocated buffer */ assert (pIO->readLen <= (size_t) max_read_len); len = recv(pIO->sock_fd, (char*)pReadBuf + pIO->readOff, pIO->readLen - pIO->readOff, 0); if (len > 0) { pIO->lastIoTime = now; pIO->readOff += len; } else err = TRUE; } if (err == FALSE) { if (pIO->readOff < pIO->readLen) { /* not all data has been read yet */ *pBlocked = TRUE; } } else { if ((len == 0) && closeOnZeroRead) { /* zero bytes read after select returned readability -> network error or connection closed by peer */ dprintf1("handle_io: zero len read on fd %d\n", (int) pIO->sock_fd); errno = ECONNRESET; result = FALSE; } else if ((len < 0) && (errno != EAGAIN) && (errno != EINTR)) { /* network error -> close the connection */ dprintf1("handle_io: read error on fd %d: len=%ld, %s\n", pIO->sock_fd, (long) len, strerror(errno)); result = FALSE; } else if (errno == EAGAIN) { *pBlocked = TRUE; } } } return result; } /* ---------------------------------------------------------------------------- ** Free resources allocated for IO */ void vbi_proxy_msg_close_io( VBIPROXY_MSG_STATE * pIO ) { if (pIO->sock_fd != -1) { close(pIO->sock_fd); pIO->sock_fd = -1; } if (pIO->pWriteBuf != NULL) { if (pIO->freeWriteBuf) free(pIO->pWriteBuf); pIO->pWriteBuf = NULL; } } /* ---------------------------------------------------------------------------- ** Fill a magic header struct with protocol constants */ void vbi_proxy_msg_fill_magics( VBIPROXY_MAGICS * p_magic ) { memcpy(p_magic->protocol_magic, VBIPROXY_MAGIC_STR, VBIPROXY_MAGIC_LEN); p_magic->protocol_compat_version = VBIPROXY_COMPAT_VERSION; p_magic->protocol_version = VBIPROXY_VERSION; p_magic->endian_magic = VBIPROXY_ENDIAN_MAGIC; } /* ---------------------------------------------------------------------------- ** Create a new message and prepare the I/O state for writing ** - length and pointer of the body may be zero (no payload) */ void vbi_proxy_msg_write( VBIPROXY_MSG_STATE * p_io, VBIPROXY_MSG_TYPE type, uint32_t msgLen, VBIPROXY_MSG * pMsg, vbi_bool freeBuf ) { assert((p_io->readOff == 0) && (p_io->readLen == 0)); /* I/O must be idle */ assert(p_io->writeLen == 0); assert((msgLen == 0) || (pMsg != NULL)); dprintf2("write: len %ld, msg type %d (%s)\n", (long) sizeof(VBIPROXY_MSG_HEADER) + msgLen, type, vbi_proxy_msg_debug_get_type_str(type)); p_io->pWriteBuf = pMsg; p_io->freeWriteBuf = freeBuf; p_io->writeLen = sizeof(VBIPROXY_MSG_HEADER) + msgLen; p_io->writeOff = 0; p_io->lastIoTime = time(NULL); /* message header: length is coded in network byte order (i.e. big endian) */ pMsg->head.len = htonl(p_io->writeLen); pMsg->head.type = htonl(type); } /* ---------------------------------------------------------------------------- ** Implementation of the C library address handling functions ** - for platforms which to not have them in libc ** - documentation see the manpages */ #ifndef HAVE_GETADDRINFO #ifndef AI_PASSIVE # define AI_PASSIVE 1 #endif struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; struct sockaddr * ai_addr; int ai_addrlen; }; enum { GAI_UNSUP_FAM = -1, GAI_NO_SERVICE_NAME = -2, GAI_UNKNOWN_SERVICE = -3, GAI_UNKNOWN_HOST = -4, }; static int getaddrinfo( const char * pHostName, const char * pServiceName, const struct addrinfo * pInParams, struct addrinfo ** ppResult ) { struct servent * pServiceEntry; struct hostent * pHostEntry; struct addrinfo * res; char * pServiceNumEnd; uint32_t port; int result; res = malloc(sizeof(struct addrinfo)); *ppResult = res; memset(res, 0, sizeof(*res)); res->ai_socktype = pInParams->ai_socktype; res->ai_family = pInParams->ai_family; res->ai_protocol = pInParams->ai_protocol; if (pInParams->ai_family == PF_INET) { if ((pServiceName != NULL) || (*pServiceName == 0)) { port = strtol(pServiceName, &pServiceNumEnd, 0); if (*pServiceNumEnd != 0) { pServiceEntry = getservbyname(pServiceName, "tcp"); if (pServiceEntry != NULL) port = ntohs(pServiceEntry->s_port); else port = 0; } if (port != 0) { if (pHostName != NULL) pHostEntry = gethostbyname(pHostName); else pHostEntry = NULL; if ((pHostName == NULL) || (pHostEntry != NULL)) { struct sockaddr_in * iad; iad = malloc(sizeof(struct sockaddr_in)); res->ai_addr = (struct sockaddr *) iad; res->ai_addrlen = sizeof(struct sockaddr_in); iad->sin_family = AF_INET; iad->sin_port = htons(port); if (pHostName != NULL) memcpy(&iad->sin_addr, (char *) pHostEntry->h_addr, pHostEntry->h_length); else iad->sin_addr.s_addr = INADDR_ANY; result = 0; } else result = GAI_UNKNOWN_HOST; } else result = GAI_UNKNOWN_SERVICE; } else result = GAI_NO_SERVICE_NAME; } else result = GAI_UNSUP_FAM; if (result != 0) { free(res); *ppResult = NULL; } return result; } static void freeaddrinfo( struct addrinfo * res ) { if (res->ai_addr != NULL) free(res->ai_addr); free(res); } static char * gai_strerror( int errCode ) { switch (errCode) { case GAI_UNSUP_FAM: return "unsupported protocol family"; case GAI_NO_SERVICE_NAME: return "missing service name or port number for TCP/IP"; case GAI_UNKNOWN_SERVICE: return "unknown service name"; case GAI_UNKNOWN_HOST: return "unknown host"; default: return "internal or unknown error"; } } #endif /* HAVE_GETADDRINFO */ /* ---------------------------------------------------------------------------- ** Get socket address for PF_UNIX aka PF_LOCAL address family ** - result is in the same format as from getaddrinfo ** - note: Linux getaddrinfo currently supports PF_UNIX queries too, however ** this feature is not standardized and hence not portable (e.g. to NetBSD) */ static int vbi_proxy_msg_get_local_socket_addr( const char * pPathName, const struct addrinfo * pInParams, struct addrinfo ** ppResult ) { struct addrinfo * res; struct sockaddr_un * saddr; if ((pInParams->ai_family == PF_UNIX) && (pPathName != NULL)) { /* note: use regular malloc instead of malloc in case memory is freed by the libc internal freeaddrinfo */ res = malloc(sizeof(struct addrinfo)); *ppResult = res; memset(res, 0, sizeof(*res)); res->ai_socktype = pInParams->ai_socktype; res->ai_family = pInParams->ai_family; res->ai_protocol = pInParams->ai_protocol; saddr = malloc(sizeof(struct sockaddr_un)); res->ai_addr = (struct sockaddr *) saddr; res->ai_addrlen = sizeof(struct sockaddr_un); strlcpy(saddr->sun_path, pPathName, sizeof(saddr->sun_path) - 1); saddr->sun_path[sizeof(saddr->sun_path) - 1] = 0; saddr->sun_family = AF_UNIX; return 0; } else return -1; } /* ---------------------------------------------------------------------------- ** Open socket for listening */ int vbi_proxy_msg_listen_socket( vbi_bool is_tcp_ip, const char * listen_ip, const char * listen_port ) { struct addrinfo ask, *res; int opt, rc; int sock_fd; vbi_bool result = FALSE; memset(&ask, 0, sizeof(ask)); ask.ai_flags = AI_PASSIVE; ask.ai_socktype = SOCK_STREAM; sock_fd = -1; res = NULL; #ifdef PF_INET6 if (is_tcp_ip) { /* try IP-v6: not supported everywhere yet, so errors must be silently ignored */ ask.ai_family = PF_INET6; rc = getaddrinfo(listen_ip, listen_port, &ask, &res); if (rc == 0) { sock_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock_fd == -1) { dprintf2("listen_socket: socket (ipv6)\n"); freeaddrinfo(res); res = NULL; } } else dprintf2("listen_socket: getaddrinfo (ipv6): %s\n", gai_strerror(rc)); } #endif if (sock_fd == -1) { if (is_tcp_ip) { /* IP-v4 (IP-address is optional, defaults to localhost) */ ask.ai_family = PF_INET; rc = getaddrinfo(listen_ip, listen_port, &ask, &res); } else { /* UNIX domain socket: named pipe located in /tmp directory */ ask.ai_family = PF_UNIX; rc = vbi_proxy_msg_get_local_socket_addr(listen_port, &ask, &res); } if (rc == 0) { sock_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock_fd == -1) { vbi_proxy_msg_logger(LOG_ERR, -1, errno, "socket create failed: ", NULL); } } else vbi_proxy_msg_logger(LOG_ERR, -1, 0, "Invalid hostname or service/port: ", gai_strerror(rc), NULL); } if (sock_fd != -1) { /* allow immediate reuse of the port (e.g. after server stop and restart) */ opt = 1; if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)) == 0) { /* make the socket non-blocking */ if (fcntl(sock_fd, F_SETFL, O_NONBLOCK) == 0) { /* bind the socket */ if (bind(sock_fd, res->ai_addr, res->ai_addrlen) == 0) { #ifdef linux /* set socket permissions: r/w allowed to everyone */ if ( (is_tcp_ip == FALSE) && (chmod(listen_port, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) != 0) ) vbi_proxy_msg_logger(LOG_WARNING, -1, errno, "chmod failed for named socket: ", NULL); #endif /* enable listening for new connections */ if (listen(sock_fd, SRV_LISTEN_BACKLOG_LEN) == 0) { /* finished without errors */ result = TRUE; } else { vbi_proxy_msg_logger(LOG_ERR, -1, errno, "socket listen failed: ", NULL); if ((is_tcp_ip == FALSE) && (listen_port != NULL)) unlink(listen_port); } } else vbi_proxy_msg_logger(LOG_ERR, -1, errno, "socket bind failed: ", NULL); } else vbi_proxy_msg_logger(LOG_ERR, -1, errno, "failed to set socket non-blocking: ", NULL); } else vbi_proxy_msg_logger(LOG_ERR, -1, errno, "socket setsockopt(SOL_SOCKET=SO_REUSEADDR) failed: ", NULL); } if (res != NULL) freeaddrinfo(res); if ((result == FALSE) && (sock_fd != -1)) { close(sock_fd); sock_fd = -1; } return sock_fd; } /* ---------------------------------------------------------------------------- ** Stop listening a socket */ void vbi_proxy_msg_stop_listen( vbi_bool is_tcp_ip, int sock_fd, char * pSrvPort ) { if (sock_fd != -1) { if (is_tcp_ip == FALSE) unlink(pSrvPort); close(sock_fd); sock_fd = -1; } } /* ---------------------------------------------------------------------------- ** Accept a new connection */ int vbi_proxy_msg_accept_connection( int listen_fd ) { struct hostent * hent; char hname_buf[129]; uint32_t length, maxLength; struct { /* allocate enough room for all possible types of socket address structs */ struct sockaddr sa; char padding[64]; } peerAddr; int sock_fd; vbi_bool result = FALSE; maxLength = length = sizeof(peerAddr); sock_fd = accept(listen_fd, &peerAddr.sa, &length); if (sock_fd != -1) { if (length <= maxLength) { if (fcntl(sock_fd, F_SETFL, O_NONBLOCK) == 0) { if (peerAddr.sa.sa_family == AF_INET) { hent = gethostbyaddr((void *) &peerAddr.sa, maxLength, AF_INET); if (hent != NULL) { strlcpy(hname_buf, hent->h_name, sizeof(hname_buf) -1); hname_buf[sizeof(hname_buf) - 1] = 0; } else { struct sockaddr_in *sa; sa = (struct sockaddr_in *) &peerAddr.sa; snprintf(hname_buf, sizeof (hname_buf), "%s, port %d", inet_ntoa(sa->sin_addr), sa->sin_port); } vbi_proxy_msg_logger(LOG_INFO, sock_fd, 0, "new connection from ", hname_buf, NULL); result = TRUE; } #ifdef HAVE_GETADDRINFO else if (peerAddr.sa.sa_family == AF_INET6) { if (getnameinfo(&peerAddr.sa, length, hname_buf, sizeof(hname_buf) - 1, NULL, 0, 0) == 0) { /* address could be resolved to hostname */ vbi_proxy_msg_logger(LOG_INFO, sock_fd, 0, "new connection from ", hname_buf, NULL); result = TRUE; } else if (getnameinfo(&peerAddr.sa, length, hname_buf, sizeof(hname_buf) - 1, NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV) == 0) { /* resolver failed - but numeric conversion was successful */ dprintf2("accept_connection: IPv6 resolver failed for %s\n", hname_buf); vbi_proxy_msg_logger(LOG_INFO, sock_fd, 0, "new connection from ", hname_buf, NULL); result = TRUE; } else { /* neither name looup nor numeric name output succeeded -> fatal error */ vbi_proxy_msg_logger(LOG_INFO, sock_fd, errno, "new connection: failed to get IPv6 peer name or IP-addr: ", NULL); result = FALSE; } } #endif else if (peerAddr.sa.sa_family == AF_UNIX) { vbi_proxy_msg_logger(LOG_INFO, sock_fd, 0, "new connection from localhost via named socket", NULL); result = TRUE; } else { /* neither INET nor named socket -> internal error */ snprintf(hname_buf, sizeof (hname_buf), "%d", peerAddr.sa.sa_family); vbi_proxy_msg_logger(LOG_WARNING, -1, 0, "new connection via unexpected protocol family ", hname_buf, NULL); } } else { /* fcntl failed: OS error (should never happen) */ vbi_proxy_msg_logger(LOG_WARNING, -1, errno, "new connection: failed to set socket to non-blocking: ", NULL); } } else { /* socket address buffer too small: internal error */ snprintf(hname_buf, sizeof (hname_buf), "need %d, have %d", length, maxLength); vbi_proxy_msg_logger(LOG_WARNING, -1, 0, "new connection: saddr buffer too small: ", hname_buf, NULL); } if (result == FALSE) { /* error -> drop the connection */ close(sock_fd); sock_fd = -1; } } else { /* connect accept failed: remote host may already have closed again */ if (errno == EAGAIN) vbi_proxy_msg_logger(LOG_INFO, -1, errno, "accept failed: ", NULL); } return sock_fd; } /* ---------------------------------------------------------------------------- ** Follow path through symlinks (in an attempt to get a unique path) ** - note: "." and ".." in relative symlinks appear to be resolved by Linux ** already when creating the symlink */ static char * vbi_proxy_msg_resolve_symlinks( const char * p_dev_name ) { struct stat stbuf; char * p_path; char * p_tmp; char * p_tmp2; int name_len; int res; int slink_idx; p_path = strdup(p_dev_name); for (slink_idx = 0; slink_idx < 100; slink_idx++) { res = lstat(p_path, &stbuf); if ((res == 0) && S_ISLNK(stbuf.st_mode)) { char link_name[stbuf.st_size + 1]; name_len = readlink(p_path, link_name, sizeof(link_name)); if ((name_len > 0) && (name_len < (int) sizeof(link_name))) { link_name[name_len] = 0; dprintf2("resolve_symlinks: following symlink %s to: %s\n", p_path, link_name); if (link_name[0] != '/') { /* relative path -> replace only last path element */ p_tmp = malloc(strlen(p_path) + name_len + 1 + 1); p_tmp2 = strrchr(p_path, '/'); if (p_tmp2 != NULL) { /* copy former path up to and including the separator character */ p_tmp2 += 1; strlcpy(p_tmp, p_path, p_tmp2 - p_path); } else { /* no path separator in the former path -> replace completely */ p_tmp2 = p_path; } /* append the path read from the symlink file */ strcpy(p_tmp + (p_tmp2 - p_path), link_name); } else { /* absolute path -> replace symlink completely */ p_tmp = strdup(link_name); } free((void *) p_path); p_path = p_tmp; } else { /* symlink string too long for the buffer */ if (name_len > 0) { link_name[sizeof(link_name) - 1] = 0; dprintf1("resolve_symlinks: abort: symlink too long: %s\n", link_name); } else dprintf1("resolve_symlinks: zero length symlink - abort\n"); break; } } else break; } if (slink_idx >= 100) dprintf1("resolve_symlinks: symlink level too deep: abort after %d\n", slink_idx); return p_path; } /* ---------------------------------------------------------------------------- ** Derive file name for socket from device path */ char * vbi_proxy_msg_get_socket_name( const char * p_dev_name ) { char * p_real_dev_name; char * p_sock_path; char * po; const char * ps; char c; int name_len; if (p_dev_name != NULL) { p_real_dev_name = vbi_proxy_msg_resolve_symlinks(p_dev_name); name_len = strlen(SRV_CLNT_SOCK_BASE_PATH) + strlen(p_real_dev_name) + 1; p_sock_path = malloc(name_len); if (p_sock_path != NULL) { strcpy(p_sock_path, SRV_CLNT_SOCK_BASE_PATH); po = p_sock_path + strlen(SRV_CLNT_SOCK_BASE_PATH); ps = p_real_dev_name; while ((c = *(ps++)) != 0) { if (c == '/') *(po++) = '-'; else *(po++) = c; } *po = 0; } free(p_real_dev_name); } else p_sock_path = NULL; return p_sock_path; } /* ---------------------------------------------------------------------------- ** Attempt to connect to an already running server */ vbi_bool vbi_proxy_msg_check_connect( const char * p_sock_path ) { VBIPROXY_MSG_HEADER msgCloseInd; struct sockaddr_un saddr; int fd; vbi_bool result = FALSE; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd != -1) { saddr.sun_family = AF_UNIX; strcpy(saddr.sun_path, p_sock_path); if (connect(fd, (struct sockaddr *) &saddr, sizeof(saddr)) != -1) { msgCloseInd.len = htonl(sizeof(VBIPROXY_MSG_HEADER)); msgCloseInd.type = htonl(MSG_TYPE_CLOSE_REQ); if (write(fd, &msgCloseInd, sizeof(msgCloseInd)) == sizeof(msgCloseInd)) { result = TRUE; } } close(fd); } /* if no server is listening, remove the socket from the file system */ if (result == FALSE) unlink(p_sock_path); return result; } /* ---------------------------------------------------------------------------- ** Open client connection ** - since the socket is made non-blocking, the result of the connect is not ** yet available when the function finishes; the caller has to wait for ** completion with select() and then query the socket error status */ int vbi_proxy_msg_connect_to_server( vbi_bool use_tcp_ip, const char * pSrvHost, const char * pSrvPort, char ** ppErrorText ) { struct addrinfo ask, *res; int sock_fd; int rc; rc = 0; res = NULL; sock_fd = -1; memset(&ask, 0, sizeof(ask)); ask.ai_flags = 0; ask.ai_socktype = SOCK_STREAM; #ifdef PF_INET6 if (use_tcp_ip) { /* try IP-v6: not supported everywhere yet, so errors must be silently ignored */ ask.ai_family = PF_INET6; rc = getaddrinfo(pSrvHost, pSrvPort, &ask, &res); if (rc == 0) { sock_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock_fd == -1) { freeaddrinfo(res); res = NULL; /*dprintf2("socket (ipv6)\n"); */ } } else dprintf2("getaddrinfo (ipv6): %s\n", gai_strerror(rc)); } #endif if (sock_fd == -1) { if (use_tcp_ip) { ask.ai_family = PF_INET; rc = getaddrinfo(pSrvHost, pSrvPort, &ask, &res); } else { ask.ai_family = PF_UNIX; rc = vbi_proxy_msg_get_local_socket_addr(pSrvPort, &ask, &res); } if (rc == 0) { sock_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock_fd == -1) { dprintf1("socket (ipv4): error %d, %s\n", errno, strerror(errno)); asprintf(ppErrorText, _("Cannot create socket: %s."), strerror(errno)); } } else { dprintf1("getaddrinfo (ipv4): %s\n", gai_strerror(rc)); asprintf(ppErrorText, _("Invalid hostname or port: %s."), gai_strerror(rc)); } } if (sock_fd != -1) { if (fcntl(sock_fd, F_SETFL, O_NONBLOCK) == 0) { /* connect to the server socket */ if ( (connect(sock_fd, res->ai_addr, res->ai_addrlen) == 0) || (errno == EINPROGRESS) ) { /* all ok: result is in sock_fd */ } else { dprintf1("connect: error %d, %s\n", errno, strerror(errno)); if (use_tcp_ip) asprintf(ppErrorText, _("Connection via TCP/IP failed, server not running or unreachable.")); else asprintf(ppErrorText, _("Connection via socket failed, server not running.")); close(sock_fd); sock_fd = -1; } } else { dprintf1("fcntl (F_SETFL=O_NONBLOCK): error %d, %s\n", errno, strerror(errno)); asprintf(ppErrorText, _("Socket I/O error: %s."), strerror(errno)); close(sock_fd); sock_fd = -1; } } if (res != NULL) freeaddrinfo(res); return sock_fd; } /* ---------------------------------------------------------------------------- ** Check for the result of the connect syscall ** - UNIX: called when select() indicates writability ** - Win32: called when select() indicates writablility (successful connected) ** or an exception (connect failed) */ vbi_bool vbi_proxy_msg_finish_connect( int sock_fd, char ** ppErrorText ) { vbi_bool result = FALSE; int sockerr; socklen_t sockerrlen; sockerrlen = sizeof(sockerr); if (getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen) == 0) { if (sockerr == 0) { /* success -> send the first message of the startup protocol to the server */ dprintf2("finish_connect: socket connect succeeded\n"); result = TRUE; } else { /* failed to establish a connection to the server */ dprintf1("finish_connect: socket connect failed: %s\n", strerror(sockerr)); asprintf(ppErrorText, _("Cannot connect to server: %s."), strerror(sockerr)); } } else { dprintf1("finish_connect: getsockopt: %s\n", strerror(errno)); asprintf(ppErrorText, _("Socket I/O error: %s."), strerror(errno)); } return result; } /* ---------------------------------------------------------------------------- ** Query size and character of an ioctl request for v4l1 drivers */ static int vbi_proxy_msg_v4l_ioctl( unsigned int request, void * p_arg, vbi_bool * req_perm ) { p_arg = p_arg; switch (request) { #ifdef ENABLE_V4L case VIDIOCGCAP: dprintf2("v4l_ioctl CGCAP, arg size %ld\n", (long) sizeof(struct video_capability)); return sizeof(struct video_capability); case VIDIOCGCHAN: dprintf2("v4l_ioctl CGCHAN, arg size %ld\n", (long) sizeof(struct video_channel)); return sizeof(struct video_channel); case VIDIOCSCHAN: dprintf2("v4l_ioctl CSCHAN, arg size %ld\n", (long) sizeof(struct video_channel)); *req_perm = TRUE; return sizeof(struct video_channel); case VIDIOCGTUNER: dprintf2("v4l_ioctl CGTUNER, arg size %ld\n", (long) sizeof(struct video_tuner)); return sizeof(struct video_tuner); case VIDIOCSTUNER: dprintf2("v4l_ioctl CSTUNER, arg size %ld\n", (long) sizeof(struct video_tuner)); *req_perm = TRUE; return sizeof(struct video_tuner); case VIDIOCGFREQ: dprintf2("v4l_ioctl CGFREQ, arg size %ld\n", (long) sizeof(unsigned long)); return sizeof(unsigned long); case VIDIOCSFREQ: dprintf2("v4l_ioctl CSFREQ, arg size %ld\n", (long) sizeof(unsigned long)); *req_perm = TRUE; return sizeof(unsigned long); case VIDIOCGUNIT: dprintf2("v4l_ioctl CGUNIT, arg size %ld\n", (long) sizeof(struct video_unit)); return sizeof(struct video_unit); #endif default: return -1; } } /* ---------------------------------------------------------------------------- ** Query size and character of an ioctl request for v4l2 drivers */ static int vbi_proxy_msg_v4l2_ioctl( unsigned int request, void * p_arg, vbi_bool * req_perm ) { switch (request) { #ifdef ENABLE_V4L2 case VIDIOC_QUERYCAP: dprintf2("v4l2_ioctl QUERYCAP, arg size %ld\n", (long) sizeof(struct v4l2_capability)); return sizeof(struct v4l2_capability); case VIDIOC_QUERYSTD: dprintf2("v4l2_ioctl QUERYSTD, arg size %ld\n", (long) sizeof(v4l2_std_id)); return sizeof(v4l2_std_id); case VIDIOC_G_STD: dprintf2("v4l2_ioctl G_STD, arg size %ld\n", (long) sizeof(v4l2_std_id)); return sizeof(v4l2_std_id); case VIDIOC_S_STD: dprintf2("v4l2_ioctl S_STD, arg size %ld\n", (long) sizeof(v4l2_std_id)); *req_perm = TRUE; return sizeof(v4l2_std_id); case VIDIOC_ENUMSTD: dprintf2("v4l2_ioctl ENUMSTD, arg size %ld\n", (long) sizeof(struct v4l2_standard)); return sizeof(struct v4l2_standard); case VIDIOC_ENUMINPUT: dprintf2("v4l2_ioctl ENUMINPUT, arg size %ld\n", (long) sizeof(struct v4l2_input)); return sizeof(struct v4l2_input); case VIDIOC_G_CTRL: dprintf2("v4l2_ioctl G_CTRL, arg size %ld\n", (long) sizeof(struct v4l2_control)); return sizeof(struct v4l2_control); case VIDIOC_S_CTRL: dprintf2("v4l2_ioctl S_CTRL, arg size %ld\n", (long) sizeof(struct v4l2_control)); return sizeof(struct v4l2_control); case VIDIOC_G_TUNER: dprintf2("v4l2_ioctl G_TUNER, arg size %ld\n", (long) sizeof(struct v4l2_tuner)); return sizeof(struct v4l2_tuner); case VIDIOC_S_TUNER: dprintf2("v4l2_ioctl S_TUNER, arg size %ld\n", (long) sizeof(struct v4l2_tuner)); *req_perm = TRUE; return sizeof(struct v4l2_tuner); case VIDIOC_QUERYCTRL: dprintf2("v4l2_ioctl QUERYCTRL, arg size %ld\n", (long) sizeof(struct v4l2_queryctrl)); return sizeof(struct v4l2_queryctrl); case VIDIOC_QUERYMENU: dprintf2("v4l2_ioctl QUERYMENU, arg size %ld\n", (long) sizeof(struct v4l2_querymenu)); return sizeof(struct v4l2_querymenu); case VIDIOC_G_INPUT: dprintf2("v4l2_ioctl G_INPUT, arg size %ld\n", (long) sizeof(int)); return sizeof(int); case VIDIOC_S_INPUT: dprintf2("v4l2_ioctl S_INPUT, arg size %ld\n", (long) sizeof(int)); *req_perm = TRUE; return sizeof(int); case VIDIOC_G_MODULATOR: dprintf2("v4l2_ioctl G_MODULATOR, arg size %ld\n", (long) sizeof(struct v4l2_modulator)); return sizeof(struct v4l2_modulator); case VIDIOC_S_MODULATOR: dprintf2("v4l2_ioctl S_MODULATOR, arg size %ld\n", (long) sizeof(struct v4l2_modulator)); *req_perm = TRUE; return sizeof(struct v4l2_modulator); case VIDIOC_G_FREQUENCY: dprintf2("v4l2_ioctl G_FREQUENCY, arg size %ld\n", (long) sizeof(struct v4l2_frequency)); return sizeof(struct v4l2_frequency); case VIDIOC_S_FREQUENCY: dprintf2("v4l2_ioctl S_FREQUENCY, arg size %ld\n", (long) sizeof(struct v4l2_frequency)); *req_perm = TRUE; return sizeof(struct v4l2_frequency); #endif default: return vbi_proxy_msg_v4l_ioctl(request, p_arg, req_perm); } } /* ---------------------------------------------------------------------------- ** Query size and character of an ioctl request */ int vbi_proxy_msg_check_ioctl( VBI_DRIVER_API_REV vbi_api, int request, void * p_arg, vbi_bool * req_perm ) { *req_perm = FALSE; switch (vbi_api) { case VBI_API_V4L1: return vbi_proxy_msg_v4l_ioctl(request, p_arg, req_perm); case VBI_API_V4L2: return vbi_proxy_msg_v4l2_ioctl(request, p_arg, req_perm); default: dprintf1("v4l2_ioctl: API #%d not supported\n", vbi_api); return -1; } } #endif /* ENABLE_PROXY */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/proxy-msg.h000066400000000000000000000530611476363111200154710ustar00rootroot00000000000000/* * libzvbi -- Messages and basic I/O functions between * VBI proxy client & server * * Copyright (C) 2003, 2004 Tom Zoerner * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* * $Id: proxy-msg.h,v 1.13 2008-02-19 00:35:21 mschimek Exp $ * * $Log: not supported by cvs2svn $ * Revision 1.12 2007/11/27 18:31:07 mschimek * Updated the FSF address in the copyright notice. * * Revision 1.11 2007/11/20 21:43:46 tomzo * Improvements and corrections in the proxy API documentation. * * Revision 1.10 2007/07/23 20:01:18 mschimek * *** empty log message *** * * Revision 1.9 2004/10/24 18:33:47 tomzo * - cleaned up socket I/O interface functions * - added defines for norm change events * * Revision 1.8 2004/10/04 20:50:24 mschimek * *** empty log message *** * * Revision 1.6 2003/06/07 09:43:08 tomzo * - added new message types MSG_TYPE_DAEMON_PID_REQ,CNF * - added new struct VBIPROXY_MSG: holds message header and body structs * * Revision 1.5 2003/06/01 19:36:23 tomzo * Implemented server-side TV channel switching * - implemented messages MSG_TYPE_CHN_CHANGE_REQ/CNF/REJ; IND is still TODO * - removed obsolete PROFILE messages: profile is included with CHN_CHANGE_REQ * Also: added VBI API identifier and device path to CONNECT_CNF (for future use) * * Revision 1.4 2003/05/24 12:19:29 tomzo * - added VBIPROXY_SERVICE_REQ/_CNF/_REJ messages * - prepared channel change request and profile request * - renamed MSG_TYPE_DATA_IND into _SLICED_IND in preparation for raw data * * Revision 1.3 2003/05/03 12:04:52 tomzo * - added new macro VBIPROXY_ENDIAN_MISMATCH to replace use of swap32() * - added declaration for new func vbi_proxy_msg_set_debug_level() * - fixed copyright headers, added description to file headers * */ #ifndef PROXY_MSG_H #define PROXY_MSG_H #ifdef HAVE_SYSLOG_H #include #endif /* Public */ /** * @ingroup Proxy * @brief Priority levels for channel switching (equivalent to enum v4l2_priority) * * These priorities are used to cooperativly resolve conflicts between * channel requests of multiple capture applications. While a capture * application with a higher priority has opened a device, channel change * requests of applications with lower priority will fail with error "EBUSY". */ typedef enum { /** * Priority level to be used for non-interactive, background data * harvesting, i.e. applications which permanently run in the * background (e.g. teletext cache, EPG data acquisition) */ VBI_CHN_PRIO_BACKGROUND = 1, /** * Interactive (default): should be used when channels are changed * on request of the user (e.g. TV viewer, Radio, teletext reader) */ VBI_CHN_PRIO_INTERACTIVE = 2, /** * Default priority for client which have not (yet) set a priority. */ VBI_CHN_PRIO_DEFAULT = VBI_CHN_PRIO_INTERACTIVE, /** * Scheduled recording (e.g. PVR): usually only one application * should run at this level (although this is not enforced by * the proxy daemon, must be checked by the user or applications) */ VBI_CHN_PRIO_RECORD = 3 } VBI_CHN_PRIO; /** * @ingroup Proxy * @brief Sub-priorities for channel scheduling at "background" priority * * This enum describes recommended sub-priority levels for channel profiles. * They're intended for channel switching through a VBI proxy at background * priority level. The daemon uses this priority to decide which request * to grant first if there are multiple outstanding requests. To the daemon * these are just numbers (highest wins) but for successful cooperation * clients need to use agree on values for similar tasks. Hence the following * values are recommended: */ typedef enum { /** * Minimal priority level. Client will get channel control only * after all other clients. */ VBI_CHN_SUBPRIO_MINIMAL = 0x00, /** * After phases "initial" or "check" are completed, clients can use * this level to continuously check for change marks. */ VBI_CHN_SUBPRIO_CHECK = 0x10, /** * A change in the data transmission has been detected or a long * time has passed since the initial reading, so data needs to be * read newly. */ VBI_CHN_SUBPRIO_UPDATE = 0x20, /** * Initial reading of data after program start (and long pause since * last start); once all data is read the client should lower it's * priority. */ VBI_CHN_SUBPRIO_INITIAL = 0x30, /** * Scanning for VPS/PDC labels to wait for the start of a recording. */ VBI_CHN_SUBPRIO_VPS_PDC = 0x40 } VBI_CHN_SUBPRIO; /** * @ingroup Proxy * @brief Proxy scheduler parameters for background channel switching * * This structure is passed along with channel change requests for * clients with priority @c VBI_CHN_PRIO_BACKGROUND. The parameters * are used by the proxy daemon to share channel control between * multiple clients with background priority. */ typedef struct { /** * Boolean: Ignore contents of this struct unless TRUE */ uint8_t is_valid; /** * Sub-priority for channel scheduling at "background" priority. * You can use aribtrary values in the range 0 ... 256, but as * this value is only meaningful in relation to priorities used * by other clients, you should stick to the scale defined by * @ref VBI_CHN_SUBPRIO */ uint8_t sub_prio; /** * Boolean: Set to FALSE if your capture client needs an * atomic time slice (i.e. would need to restart capturing * from the beginning it it was interrupted.) */ uint8_t allow_suspend; uint8_t reserved0; /** * Minimum time slice your capture client requires. This value * is used when multiple clients have the same sub-priority * to give all clients channel control in a round-robin manner. */ time_t min_duration; /** * Expected duration of use of that channel */ time_t exp_duration; uint8_t reserved1[16]; } vbi_channel_profile; /** * @ingroup Proxy * @brief General flags sent by the proxy daemon to clients during connect */ typedef enum { /** * Don't drop connection upon timeouts in socket I/O or message response; * Intended for debugging, i.e. when remote party runs in a debugger */ VBI_PROXY_DAEMON_NO_TIMEOUTS = 1<<0 } VBI_PROXY_DAEMON_FLAGS; /** * @ingroup Proxy * @brief General flags sent by clients to the proxy daemon during connect */ typedef enum { /** * Don't drop connection upon timeouts in socket I/O or message response * (e.g. when waiting for connect confirm) * Intended for debugging, i.e. when remote party runs in a debugger */ VBI_PROXY_CLIENT_NO_TIMEOUTS = 1<<0, /** * Suppress sending of channel change and similar indications, i.e. limit * messages to slicer data forward and synchronous messages (i.e. RPC reply). * Used to make sure that the proxy client socket only becomes readable * when data is available for applications which are not proxy-aware. */ VBI_PROXY_CLIENT_NO_STATUS_IND = 1<<1 } VBI_PROXY_CLIENT_FLAGS; /** * @ingroup Proxy * @brief Channel notification flags */ typedef enum { /** * Revoke a previous channel request and return the channel switch * token to the daemon. */ VBI_PROXY_CHN_RELEASE = 1<<0, /** * Return the channel token to the daemon without releasing the * channel; This should always be done when the channel switch has * been completed to allow faster scheduling in the daemon (i.e. the * daemon can grant the token to a different client without having * to reclaim it first.) */ VBI_PROXY_CHN_TOKEN = 1<<1, /** * Indicate that the channel was changed and VBI buffer queue * must be flushed; Should be called as fast as possible after * the channel and/or norm was changed. Note this affects other * clients' capturing too, so use with care. Other clients will * be informed about this change by a channel change indication. */ VBI_PROXY_CHN_FLUSH = 1<<2, /** * Indicate a norm change. The new norm should be supplied in * the scanning parameter in cae the daemon is not able to * determine it from the device directly. */ VBI_PROXY_CHN_NORM = 1<<3, /** * Indicate that the client failed to switch the channel because * the device was busy. Used to notify the channel scheduler that * the current time slice cannot be used by the client. If the * client isn't able to schedule periodic re-attempts it should * also return the token. */ VBI_PROXY_CHN_FAIL = 1<<4, VBI_PROXY_CHN_NONE = 0 } VBI_PROXY_CHN_FLAGS; /** * @ingroup Proxy * @brief Identification of the VBI device driver type */ typedef enum { /** * Unknown device API - only used in error cases. Normally * the proxy will always be aware of the driver API as it's * determined by the type of capture context creation function * used when the device is opened. */ VBI_API_UNKNOWN, /** * Video4Linux version 1 (i.e. Linux kernels 2.4 or older * or old device drivers which have not been ported yet) */ VBI_API_V4L1, /** * Video4Linux version 2 (i.e. Linux kernels 2.6 and later) */ VBI_API_V4L2, /** * BSD Brooktree capture driver. */ VBI_API_BKTR } VBI_DRIVER_API_REV; /** * @ingroup Proxy * @brief Proxy protocol version: major, minor and patchlevel */ #define VBIPROXY_VERSION 0x00000100 #define VBIPROXY_COMPAT_VERSION 0x00000100 /* Private */ /* ---------------------------------------------------------------------------- ** Declaration of message IDs and the common header struct */ typedef enum { MSG_TYPE_CONNECT_REQ, MSG_TYPE_CONNECT_CNF, MSG_TYPE_CONNECT_REJ, MSG_TYPE_CLOSE_REQ, MSG_TYPE_SLICED_IND, MSG_TYPE_SERVICE_REQ, MSG_TYPE_SERVICE_CNF, MSG_TYPE_SERVICE_REJ, MSG_TYPE_CHN_TOKEN_REQ, MSG_TYPE_CHN_TOKEN_CNF, MSG_TYPE_CHN_TOKEN_IND, MSG_TYPE_CHN_NOTIFY_REQ, MSG_TYPE_CHN_NOTIFY_CNF, MSG_TYPE_CHN_RECLAIM_REQ, MSG_TYPE_CHN_RECLAIM_CNF, MSG_TYPE_CHN_SUSPEND_REQ, MSG_TYPE_CHN_SUSPEND_CNF, MSG_TYPE_CHN_SUSPEND_REJ, MSG_TYPE_CHN_IOCTL_REQ, MSG_TYPE_CHN_IOCTL_CNF, MSG_TYPE_CHN_IOCTL_REJ, MSG_TYPE_CHN_CHANGE_IND, MSG_TYPE_DAEMON_PID_REQ, MSG_TYPE_DAEMON_PID_CNF, MSG_TYPE_COUNT } VBIPROXY_MSG_TYPE; typedef struct { uint32_t len; uint32_t type; } VBIPROXY_MSG_HEADER; #define VBIPROXY_MAGIC_STR "LIBZVBI VBIPROXY" #define VBIPROXY_MAGIC_LEN 16 #define VBIPROXY_ENDIAN_MAGIC 0x11223344 #define VBIPROXY_ENDIAN_MISMATCH 0x44332211 #define VBIPROXY_CLIENT_NAME_MAX_LENGTH 64 #define VBIPROXY_DEV_NAME_MAX_LENGTH 128 #define VBIPROXY_ERROR_STR_MAX_LENGTH 128 typedef struct { uint8_t protocol_magic[VBIPROXY_MAGIC_LEN]; uint32_t protocol_compat_version; uint32_t protocol_version; uint32_t endian_magic; } VBIPROXY_MAGICS; typedef struct { VBIPROXY_MAGICS magics; uint8_t client_name[VBIPROXY_CLIENT_NAME_MAX_LENGTH]; int32_t pid; uint32_t client_flags; uint32_t scanning; uint8_t buffer_count; uint32_t services; int8_t strict; uint32_t reserved[32]; /* set to zero */ } VBIPROXY_CONNECT_REQ; typedef struct { VBIPROXY_MAGICS magics; uint8_t dev_vbi_name[VBIPROXY_DEV_NAME_MAX_LENGTH]; int32_t pid; uint32_t vbi_api_revision; uint32_t daemon_flags; uint32_t services; /* all services, including raw */ vbi_raw_decoder dec; /* VBI format, e.g. VBI line counts */ uint32_t reserved[32]; /* set to zero */ } VBIPROXY_CONNECT_CNF; typedef struct { VBIPROXY_MAGICS magics; uint8_t errorstr[VBIPROXY_ERROR_STR_MAX_LENGTH]; } VBIPROXY_CONNECT_REJ; typedef struct { double timestamp; uint32_t sliced_lines; uint32_t raw_lines; union { vbi_sliced sliced[1]; int8_t raw[1]; } u; } VBIPROXY_SLICED_IND; #define VBIPROXY_RAW_LINE_SIZE 2048 #define VBIPROXY_SLICED_IND_SIZE(S,R) ( sizeof(VBIPROXY_SLICED_IND) \ - sizeof(vbi_sliced) \ + ((S) * sizeof(vbi_sliced)) \ + ((R) * VBIPROXY_RAW_LINE_SIZE) ) typedef struct { uint8_t reset; uint8_t commit; int8_t strict; uint32_t services; } VBIPROXY_SERVICE_REQ; typedef struct { uint32_t services; /* all services, including raw */ vbi_raw_decoder dec; /* VBI format, e.g. VBI line counts */ } VBIPROXY_SERVICE_CNF; typedef struct { uint8_t errorstr[VBIPROXY_ERROR_STR_MAX_LENGTH]; } VBIPROXY_SERVICE_REJ; typedef struct { uint32_t chn_prio; vbi_channel_profile chn_profile; } VBIPROXY_CHN_TOKEN_REQ; typedef struct { vbi_bool token_ind; /* piggy-back TOKEN_IND (bg. prio only) */ vbi_bool permitted; /* change allowed by prio (non-bg prio) */ vbi_bool non_excl; /* there are other clients at the same prio */ } VBIPROXY_CHN_TOKEN_CNF; typedef struct { } VBIPROXY_CHN_TOKEN_IND; typedef struct { VBI_PROXY_CHN_FLAGS notify_flags; uint32_t scanning; /* new norm after flush; zero if unknown */ uint32_t cause; /* currently always zero */ uint8_t reserved[32]; } VBIPROXY_CHN_NOTIFY_REQ; typedef struct { uint32_t scanning; uint8_t reserved[32]; } VBIPROXY_CHN_NOTIFY_CNF; typedef struct { vbi_bool enable; uint32_t cause; } VBIPROXY_CHN_SUSPEND_REQ; typedef struct { } VBIPROXY_CHN_SUSPEND_CNF; typedef struct { } VBIPROXY_CHN_SUSPEND_REJ; typedef struct { uint32_t request; uint32_t reserved_0; uint32_t reserved_1; uint32_t arg_size; uint8_t arg_data[0]; /* warning: must have same offset as in CNF message */ } VBIPROXY_CHN_IOCTL_REQ; #define VBIPROXY_CHN_IOCTL_REQ_SIZE(SIZE) (sizeof(VBIPROXY_CHN_IOCTL_REQ) + (SIZE) - 1) typedef struct { uint32_t reserved_0; int32_t result; int32_t errcode; uint32_t arg_size; uint8_t arg_data[0]; } VBIPROXY_CHN_IOCTL_CNF; #define VBIPROXY_CHN_IOCTL_CNF_SIZE(SIZE) (sizeof(VBIPROXY_CHN_IOCTL_CNF) + (SIZE) - 1) typedef struct { } VBIPROXY_CHN_IOCTL_REJ; typedef struct { } VBIPROXY_CHN_RECLAIM_REQ; typedef struct { } VBIPROXY_CHN_RECLAIM_CNF; typedef struct { VBI_PROXY_CHN_FLAGS notify_flags; uint32_t scanning; uint8_t reserved[32]; /* always zero */ } VBIPROXY_CHN_CHANGE_IND; typedef struct { VBIPROXY_MAGICS magics; } VBIPROXY_DAEMON_PID_REQ; typedef struct { VBIPROXY_MAGICS magics; int32_t pid; } VBIPROXY_DAEMON_PID_CNF; typedef union { VBIPROXY_CONNECT_REQ connect_req; VBIPROXY_CONNECT_CNF connect_cnf; VBIPROXY_CONNECT_REJ connect_rej; VBIPROXY_SLICED_IND sliced_ind; VBIPROXY_SERVICE_REQ service_req; VBIPROXY_SERVICE_CNF service_cnf; VBIPROXY_SERVICE_REJ service_rej; VBIPROXY_CHN_TOKEN_REQ chn_token_req; VBIPROXY_CHN_TOKEN_CNF chn_token_cnf; VBIPROXY_CHN_TOKEN_IND chn_token_ind; VBIPROXY_CHN_RECLAIM_REQ chn_reclaim_req; VBIPROXY_CHN_RECLAIM_CNF chn_reclaim_cnf; VBIPROXY_CHN_NOTIFY_REQ chn_notify_req; VBIPROXY_CHN_NOTIFY_CNF chn_notify_cnf; VBIPROXY_CHN_SUSPEND_REQ chn_suspend_req; VBIPROXY_CHN_SUSPEND_CNF chn_suspend_cnf; VBIPROXY_CHN_SUSPEND_REJ chn_suspend_rej; VBIPROXY_CHN_IOCTL_REQ chn_ioctl_req; VBIPROXY_CHN_IOCTL_CNF chn_ioctl_cnf; VBIPROXY_CHN_IOCTL_REJ chn_ioctl_rej; VBIPROXY_CHN_CHANGE_IND chn_change_ind; VBIPROXY_DAEMON_PID_REQ daemon_pid_req; VBIPROXY_DAEMON_PID_CNF daemon_pid_cnf; } VBIPROXY_MSG_BODY; typedef struct { VBIPROXY_MSG_HEADER head; VBIPROXY_MSG_BODY body; } VBIPROXY_MSG; #define VBIPROXY_MSG_BODY_OFFSET ((long)&(((VBIPROXY_MSG*)NULL)->body)) /* ---------------------------------------------------------------------------- ** Declaration of the IO state struct */ typedef struct { int sock_fd; /* socket file handle or -1 if closed */ time_t lastIoTime; /* timestamp of last i/o (for timeouts) */ uint32_t writeLen; /* number of bytes in write buffer, including header */ uint32_t writeOff; /* number of already written bytes, including header */ VBIPROXY_MSG * pWriteBuf; /* data to be written */ vbi_bool freeWriteBuf; /* TRUE if the buffer shall be freed by the I/O handler */ uint32_t readLen; /* length of incoming message (including itself) */ uint32_t readOff; /* number of already read bytes */ } VBIPROXY_MSG_STATE; /* ---------------------------------------------------------------------------- ** Declaration of the service interface functions */ void vbi_proxy_msg_logger( int level, int clnt_fd, int errCode, const char * pText, ... ); void vbi_proxy_msg_set_logging( vbi_bool do_logtty, int sysloglev, int fileloglev, const char * pLogfileName ); void vbi_proxy_msg_set_debug_level( int level ); const char * vbi_proxy_msg_debug_get_type_str( VBIPROXY_MSG_TYPE type ); vbi_bool vbi_proxy_msg_read_idle( VBIPROXY_MSG_STATE * pIO ); vbi_bool vbi_proxy_msg_write_idle( VBIPROXY_MSG_STATE * pIO ); vbi_bool vbi_proxy_msg_is_idle( VBIPROXY_MSG_STATE * pIO ); vbi_bool vbi_proxy_msg_check_timeout( VBIPROXY_MSG_STATE * pIO, time_t now ); vbi_bool vbi_proxy_msg_handle_write( VBIPROXY_MSG_STATE * pIO, vbi_bool * pBlocked ); void vbi_proxy_msg_close_read( VBIPROXY_MSG_STATE * pIO ); vbi_bool vbi_proxy_msg_handle_read( VBIPROXY_MSG_STATE * pIO, vbi_bool * pBlocked, vbi_bool closeOnZeroRead, VBIPROXY_MSG * pReadBuf, int max_read_len ); void vbi_proxy_msg_close_io( VBIPROXY_MSG_STATE * pIO ); void vbi_proxy_msg_fill_magics( VBIPROXY_MAGICS * p_magic ); void vbi_proxy_msg_write( VBIPROXY_MSG_STATE * p_io, VBIPROXY_MSG_TYPE type, uint32_t msgLen, VBIPROXY_MSG * pMsg, vbi_bool freeBuf ); int vbi_proxy_msg_listen_socket( vbi_bool is_tcp_ip, const char * listen_ip, const char * listen_port ); void vbi_proxy_msg_stop_listen( vbi_bool is_tcp_ip, int sock_fd, char * pSrvPort ); int vbi_proxy_msg_accept_connection( int listen_fd ); char * vbi_proxy_msg_get_socket_name( const char * p_dev_name ); vbi_bool vbi_proxy_msg_check_connect( const char * p_sock_path ); int vbi_proxy_msg_connect_to_server( vbi_bool use_tcp_ip, const char * pSrvHost, const char * pSrvPort, char ** ppErrorText ); vbi_bool vbi_proxy_msg_finish_connect( int sock_fd, char ** ppErrorText ); int vbi_proxy_msg_check_ioctl( VBI_DRIVER_API_REV vbi_api, int request, void * p_arg, vbi_bool * req_perm ); #endif /* PROXY_MSG_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/raw_decoder.c000066400000000000000000000744711476363111200160050ustar00rootroot00000000000000/* * libzvbi -- Raw VBI decoder * * Copyright (C) 2000-2004 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: raw_decoder.c,v 1.24 2008-08-19 10:04:46 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "misc.h" #include "raw_decoder.h" #ifndef RAW_DECODER_PATTERN_DUMP # define RAW_DECODER_PATTERN_DUMP 0 #endif #if 2 == VBI_VERSION_MINOR # define sp_sample_format sampling_format #else # define sp_sample_format sample_format #endif /** * $addtogroup RawDecoder Raw VBI decoder * $ingroup Raw * $brief Converting a raw VBI image to sliced VBI data. */ /* Missing: VITC PAL 6-22 11.2us 1.8125 Mbit NRZ two start bits + CRC VITC NTSC 10-21 ditto CGMS NTSC 20 11us .450450 Mbit NRZ ? MOJI */ const _vbi_service_par _vbi_service_table [] = { { VBI_SLICED_TELETEXT_A, /* UNTESTED */ "Teletext System A", VBI_VIDEOSTD_SET_625_50, { 6, 318 }, { 22, 335 }, 10500, 6203125, 6203125, /* 397 x FH */ 0x00AAAAE7, 0xFFFF, 18, 6, 37 * 8, VBI_MODULATION_NRZ_LSB, 0, /* probably */ }, { VBI_SLICED_TELETEXT_B_L10_625, "Teletext System B 625 Level 1.5", VBI_VIDEOSTD_SET_625_50, { 7, 320 }, { 22, 335 }, 10300, 6937500, 6937500, /* 444 x FH */ 0x00AAAAE4, 0xFFFF, 18, 6, 42 * 8, VBI_MODULATION_NRZ_LSB, 0, }, { VBI_SLICED_TELETEXT_B, "Teletext System B, 625", VBI_VIDEOSTD_SET_625_50, { 6, 318 }, { 22, 335 }, 10300, 6937500, 6937500, /* 444 x FH */ 0x00AAAAE4, 0xFFFF, 18, 6, 42 * 8, VBI_MODULATION_NRZ_LSB, 0, }, { VBI_SLICED_TELETEXT_C_625, /* UNTESTED */ "Teletext System C 625", VBI_VIDEOSTD_SET_625_50, { 6, 318 }, { 22, 335 }, 10480, 5734375, 5734375, /* 367 x FH */ 0x00AAAAE7, 0xFFFF, 18, 6, 33 * 8, VBI_MODULATION_NRZ_LSB, 0, }, { VBI_SLICED_TELETEXT_D_625, /* UNTESTED */ "Teletext System D 625", VBI_VIDEOSTD_SET_625_50, { 6, 318 }, { 22, 335 }, 10500, /* or 10970 depending on field order */ 5642787, 5642787, /* 14/11 x FSC (color subcarrier) */ 0x00AAAAE5, 0xFFFF, 18, 6, 34 * 8, VBI_MODULATION_NRZ_LSB, 0, }, { VBI_SLICED_VPS, "Video Program System", VBI_VIDEOSTD_SET_PAL_BG, { 16, 0 }, { 16, 0 }, 12500, 5000000, 2500000, /* 160 x FH */ 0xAAAA8A99, 0xFFFFFF, 32, 0, 13 * 8, VBI_MODULATION_BIPHASE_MSB, _VBI_SP_FIELD_NUM, }, { VBI_SLICED_VPS_F2, "Pseudo-VPS on field 2", VBI_VIDEOSTD_SET_PAL_BG, { 0, 329 }, { 0, 329 }, 12500, 5000000, 2500000, /* 160 x FH */ 0xAAAA8A99, 0xFFFFFF, 32, 0, 13 * 8, VBI_MODULATION_BIPHASE_MSB, _VBI_SP_FIELD_NUM, }, { VBI_SLICED_WSS_625, "Wide Screen Signalling 625", VBI_VIDEOSTD_SET_625_50, { 23, 0 }, { 23, 0 }, 11000, 5000000, 833333, /* 160/3 x FH */ /* ...1000 111 / 0 0011 1100 0111 1000 0011 111x */ /* ...0010 010 / 0 1001 1001 0011 0011 1001 110x */ 0x8E3C783E, 0x2499339C, 32, 0, 14 * 1, VBI_MODULATION_BIPHASE_LSB, /* Hm. Too easily confused with caption?? */ _VBI_SP_FIELD_NUM | _VBI_SP_LINE_NUM, }, { VBI_SLICED_CAPTION_625_F1, "Closed Caption 625, field 1", VBI_VIDEOSTD_SET_625_50, { 22, 0 }, { 22, 0 }, 10500, 1000000, 500000, /* 32 x FH */ 0x00005551, 0x7FF, 14, 2, 2 * 8, VBI_MODULATION_NRZ_LSB, _VBI_SP_FIELD_NUM, }, { VBI_SLICED_CAPTION_625_F2, "Closed Caption 625, field 2", VBI_VIDEOSTD_SET_625_50, { 0, 335 }, { 0, 335 }, 10500, 1000000, 500000, /* 32 x FH */ 0x00005551, 0x7FF, 14, 2, 2 * 8, VBI_MODULATION_NRZ_LSB, _VBI_SP_FIELD_NUM, }, { VBI_SLICED_VBI_625, "VBI 625", /* Blank VBI */ VBI_VIDEOSTD_SET_625_50, { 6, 318 }, { 22, 335 }, 10000, 1510000, 1510000, 0, 0, 0, 0, 10 * 8, 0, /* 10.0-2 ... 62.9+1 us */ 0, }, { VBI_SLICED_TELETEXT_B_525, /* UNTESTED */ "Teletext System B 525", VBI_VIDEOSTD_SET_525_60, { 10, 272 }, { 21, 284 }, 10500, 5727272, 5727272, /* 364 x FH */ 0x00AAAAE4, 0xFFFF, 18, 6, 34 * 8, VBI_MODULATION_NRZ_LSB, 0, }, { VBI_SLICED_TELETEXT_C_525, /* UNTESTED */ "Teletext System C 525", VBI_VIDEOSTD_SET_525_60, { 10, 272 }, { 21, 284 }, 10480, 5727272, 5727272, /* 364 x FH */ 0x00AAAAE7, 0xFFFF, 18, 6, 33 * 8, VBI_MODULATION_NRZ_LSB, 0, }, { VBI_SLICED_TELETEXT_D_525, /* UNTESTED */ "Teletext System D 525", VBI_VIDEOSTD_SET_525_60, { 10, 272 }, { 21, 284 }, 9780, 5727272, 5727272, /* 364 x FH */ 0x00AAAAE5, 0xFFFF, 18, 6, 34 * 8, VBI_MODULATION_NRZ_LSB, 0, }, { #if 0 /* FIXME probably wrong */ VBI_SLICED_WSS_CPR1204, /* NOT CONFIRMED (EIA-J CPR-1204) */ "Wide Screen Signalling 525", VBI_VIDEOSTD_SET_NTSC_M_JP, { 20, 283 }, { 20, 283 }, 11200, 1789773, 447443, /* 1/8 x FSC */ 0x000000F0, 0xFF, 8, 0, 20 * 1, VBI_MODULATION_NRZ_MSB, /* No useful FRC, but a six bit CRC */ 0, }, { #endif VBI_SLICED_CAPTION_525_F1, "Closed Caption 525, field 1", VBI_VIDEOSTD_SET_525_60, { 21, 0 }, { 21, 0 }, 10500, 1006976, 503488, /* 32 x FH */ /* Test of CRI bits has been removed to handle the incorrect signal observed by Rich Kandel (see _VBI_RAW_SHIFT_CC_CRI). */ 0x03, 0x0F, 4, 0, 2 * 8, VBI_MODULATION_NRZ_LSB, /* 0x00005551, 0x7FF, 14, 2, 2 * 8, VBI_MODULATION_NRZ_LSB, */ /* I've seen CC signals on other lines and there's no way to distinguish from the transmitted data. */ _VBI_SP_FIELD_NUM | _VBI_SP_LINE_NUM, }, { VBI_SLICED_CAPTION_525_F2, "Closed Caption 525, field 2", VBI_VIDEOSTD_SET_525_60, { 0, 284 }, { 0, 284 }, 10500, 1006976, 503488, /* 32 x FH */ 0x03, 0x0F, 4, 0, 2 * 8, VBI_MODULATION_NRZ_LSB, /* 0x00005551, 0x7FF, 14, 2, 2 * 8, VBI_MODULATION_NRZ_LSB, */ _VBI_SP_FIELD_NUM | _VBI_SP_LINE_NUM, }, { VBI_SLICED_2xCAPTION_525, /* NOT CONFIRMED */ "2xCaption 525", VBI_VIDEOSTD_SET_525_60, { 10, 0 }, { 21, 0 }, 10500, 1006976, 1006976, /* 64 x FH */ 0x000554ED, 0xFFFF, 12, 8, 4 * 8, VBI_MODULATION_NRZ_LSB, /* Tb. */ _VBI_SP_FIELD_NUM, }, { VBI_SLICED_VBI_525, "VBI 525", /* Blank VBI */ VBI_VIDEOSTD_SET_525_60, { 10, 272 }, { 21, 284 }, 9500, 1510000, 1510000, 0, 0, 0, 0, 10 * 8, 0, /* 9.5-1 ... 62.4+1 us */ 0, }, { 0, NULL, VBI_VIDEOSTD_SET_EMPTY, { 0, 0 }, { 0, 0 }, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }; _vbi_inline const _vbi_service_par * find_service_par (unsigned int service) { unsigned int i; for (i = 0; _vbi_service_table[i].id; ++i) if (service == _vbi_service_table[i].id) return _vbi_service_table + i; return NULL; } /** * $ingroup Sliced * $param service A data service identifier, for example from a * vbi_sliced structure. * * $return * Name of the $a service, in ASCII, or $c NULL if unknown. */ const char * vbi_sliced_name (vbi_service_set service) { const _vbi_service_par *par; /* These are ambiguous */ if (service == VBI_SLICED_CAPTION_525) return "Closed Caption 525"; if (service == VBI_SLICED_CAPTION_625) return "Closed Caption 625"; if (service == (VBI_SLICED_VPS | VBI_SLICED_VPS_F2)) return "Video Program System"; if (service == VBI_SLICED_TELETEXT_B_L25_625) return "Teletext System B 625 Level 2.5"; /* Incorrect, no longer in table */ if (service == VBI_SLICED_TELETEXT_BD_525) return "Teletext System B/D"; if ((par = find_service_par (service))) return par->label; return NULL; } /** * @ingroup Sliced * @param service A data service identifier, for example from a * vbi_sliced structure. * * @return * Number of payload bits, @c 0 if the service is unknown. */ unsigned int vbi_sliced_payload_bits (unsigned int service) { const _vbi_service_par *par; /* These are ambiguous */ if (service == VBI_SLICED_CAPTION_525) return 16; if (service == VBI_SLICED_CAPTION_625) return 16; if (service == (VBI_SLICED_VPS | VBI_SLICED_VPS_F2)) return 13 * 8; if (service == VBI_SLICED_TELETEXT_B_L25_625) return 42 * 8; /* Incorrect, no longer in table */ if (service == VBI_SLICED_TELETEXT_BD_525) return 34 * 8; if ((par = find_service_par (service))) return par->payload; return 0; } static void dump_pattern_line (const vbi3_raw_decoder *rd, unsigned int row, FILE * fp) { const vbi_sampling_par *sp; unsigned int line; unsigned int i; sp = &rd->sampling; if (sp->interlaced) { unsigned int field = row & 1; if (0 == sp->start[field]) line = 0; else line = sp->start[field] + (row >> 1); } else { if (row >= (unsigned int) sp->count[0]) { if (0 == sp->start[1]) line = 0; else line = sp->start[1] + row - sp->count[0]; } else { if (0 == sp->start[0]) line = 0; else line = sp->start[0] + row; } } fprintf (fp, "scan line %3u: ", line); for (i = 0; i < _VBI3_RAW_DECODER_MAX_WAYS; ++i) { unsigned int pos; pos = row * _VBI3_RAW_DECODER_MAX_WAYS; fprintf (fp, "%02x ", (uint8_t) rd->pattern[pos + i]); } fputc ('\n', fp); } void _vbi3_raw_decoder_dump (const vbi3_raw_decoder *rd, FILE * fp) { const vbi_sampling_par *sp; unsigned int i; assert (NULL != fp); fprintf (fp, "vbi3_raw_decoder %p\n", rd); if (NULL == rd) return; fprintf (fp, " services 0x%08x\n", rd->services); for (i = 0; i < rd->n_jobs; ++i) fprintf (fp, " job %u: 0x%08x (%s)\n", i + 1, rd->jobs[i].id, vbi_sliced_name (rd->jobs[i].id)); if (!rd->pattern) { fprintf (fp, " no pattern\n"); return; } sp = &rd->sampling; for (i = 0; i < ((unsigned int) sp->count[0] + (unsigned int) sp->count[1]); ++i) { fputs (" ", fp); dump_pattern_line (rd, i, fp); } } _vbi_inline int cpr1204_crc (const vbi_sliced * sliced) { const int poly = (1 << 6) + (1 << 1) + 1; int crc, i; crc = (+ (sliced->data[0] << 12) + (sliced->data[1] << 4) + (sliced->data[2])); crc |= (((1 << 6) - 1) << (14 + 6)); for (i = 14 + 6 - 1; i >= 0; i--) { if (crc & ((1 << 6) << i)) crc ^= poly << i; } return crc; } static vbi_bool slice (vbi3_raw_decoder * rd, vbi_sliced * sliced, _vbi3_raw_decoder_job *job, unsigned int i, const uint8_t * raw) { if (rd->debug && NULL != rd->sp_lines) { return vbi3_bit_slicer_slice_with_points (&job->slicer, sliced->data, sizeof (sliced->data), rd->sp_lines[i].points, &rd->sp_lines[i].n_points, N_ELEMENTS (rd->sp_lines[i].points), raw); } else { return vbi3_bit_slicer_slice (&job->slicer, sliced->data, sizeof (sliced->data), raw); } } _vbi_inline vbi_sliced * decode_pattern (vbi3_raw_decoder * rd, vbi_sliced * sliced, int8_t * pattern, unsigned int i, const uint8_t * raw) { vbi_sampling_par *sp; int8_t *pat; sp = &rd->sampling; for (pat = pattern;; ++pat) { int j; j = *pat; /* data service n, blank 0, or counter -n */ if (j > 0) { _vbi3_raw_decoder_job *job; job = rd->jobs + j - 1; if (!slice (rd, sliced, job, i, raw)) { continue; /* no match, try next data service */ } /* FIXME probably wrong */ if (0 && VBI_SLICED_WSS_CPR1204 == job->id) { const int poly = (1 << 6) + (1 << 1) + 1; int crc, j; crc = (sliced->data[0] << 12) + (sliced->data[1] << 4) + sliced->data[2]; crc |= (((1 << 6) - 1) << (14 + 6)); for (j = 14 + 6 - 1; j >= 0; j--) { if (crc & ((1 << 6) << j)) crc ^= poly << j; } if (crc) continue; /* no match */ } /* Positive match, output decoded line. */ /* FIXME: if we have a field number we should really only set the service id of one field. */ sliced->id = job->id; sliced->line = 0; if (i >= (unsigned int) sp->count[0]) { if (sp->synchronous && 0 != sp->start[1]) sliced->line = sp->start[1] + i - sp->count[0]; } else { if (sp->synchronous && 0 != sp->start[0]) sliced->line = sp->start[0] + i; } if (0) fprintf (stderr, "%2d %s\n", sliced->line, vbi_sliced_name (sliced->id)); ++sliced; /* Predict line as non-blank, force testing for all data services in the next 128 frames. */ pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1] = -128; } else if (pat == pattern) { /* Line was predicted as blank, once in 16 frames look for data services. */ if (0 == rd->readjust) { unsigned int size; size = sizeof (*pattern) * (_VBI3_RAW_DECODER_MAX_WAYS - 1); j = pattern[0]; memmove (&pattern[0], &pattern[1], size); pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1] = j; } break; } else if ((j = pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1]) < 0) { /* Increment counter, when zero predict line as blank and stop looking for data services until 0 == rd->readjust. */ /* Disabled because we may miss caption/subtitles when the signal inserter is disabled during silent periods for more than 4-5 seconds. */ /* pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1] = j + 1; */ break; } else { /* found nothing, j = 0 */ } /* Try the found data service first next time. */ *pat = pattern[0]; pattern[0] = j; break; /* line done */ } return sliced; } /** * $param rd Pointer to vbi3_raw_decoder object allocated with * vbi3_raw_decoder_new(). * $param sliced Buffer to store the decoded vbi_sliced data. Since every * vbi scan line may contain data, this should be an array of vbi_sliced * with the same number of elements as scan lines in the raw image * (vbi_sampling_parameters.count[0] + .count[1]). * $param max_lines Size of $a sliced data array, in lines, not bytes. * $param raw A raw vbi image as described by the vbi_sampling_par * associated with $a rd. * * Decodes a raw vbi image, consisting of several scan lines of raw vbi data, * to sliced vbi data. The output is sorted by ascending line number. * * Note this function attempts to learn which lines carry which data * service, or if any, to speed up decoding. You should avoid using the same * vbi3_raw_decoder object for different sources. * * $return * The number of lines decoded, i. e. the number of vbi_sliced records * written. */ unsigned int vbi3_raw_decoder_decode (vbi3_raw_decoder * rd, vbi_sliced * sliced, unsigned int max_lines, const uint8_t * raw) { vbi_sampling_par *sp; unsigned int scan_lines; unsigned int pitch; int8_t *pattern; const uint8_t *raw1; vbi_sliced *sliced_begin; vbi_sliced *sliced_end; unsigned int i; if (!rd->services) return 0; sp = &rd->sampling; scan_lines = sp->count[0] + sp->count[1]; pitch = sp->bytes_per_line << sp->interlaced; pattern = rd->pattern; raw1 = raw; sliced_begin = sliced; sliced_end = sliced + max_lines; if (RAW_DECODER_PATTERN_DUMP) _vbi3_raw_decoder_dump (rd, stderr); for (i = 0; i < scan_lines; ++i) { if (sliced >= sliced_end) break; if (sp->interlaced && i == (unsigned int) sp->count[0]) raw = raw1 + sp->bytes_per_line; sliced = decode_pattern (rd, sliced, pattern, i, raw); pattern += _VBI3_RAW_DECODER_MAX_WAYS; raw += pitch; } rd->readjust = (rd->readjust + 1) & 15; return sliced - sliced_begin; } /** * $param rd Pointer to vbi3_raw_decoder object allocated with * vbi3_raw_decoder_new(). * * Resets a vbi3_raw_decoder object, removing all services added * with vbi3_raw_decoder_add_services(). */ void vbi3_raw_decoder_reset (vbi3_raw_decoder * rd) { assert (NULL != rd); if (rd->pattern) { vbi_free (rd->pattern); rd->pattern = NULL; } rd->services = 0; rd->n_jobs = 0; rd->readjust = 1; CLEAR (rd->jobs); } static void remove_job_from_pattern (vbi3_raw_decoder * rd, int job_num) { int8_t *pattern; unsigned int scan_lines; job_num += 1; /* index into rd->jobs, 0 means no job */ pattern = rd->pattern; scan_lines = rd->sampling.count[0] + rd->sampling.count[1]; /* For each scan line. */ while (scan_lines-- > 0) { int8_t *dst; int8_t *src; int8_t *end; dst = pattern; end = pattern + _VBI3_RAW_DECODER_MAX_WAYS; /* Remove jobs with job_num, fill up pattern with 0. Jobs above job_num move down in rd->jobs. */ for (src = dst; src < end; ++src) { int8_t num = *src; if (num > job_num) *dst++ = num - 1; else if (num != job_num) *dst++ = num; } while (dst < end) *dst++ = 0; pattern = end; } } /** * $param rd Pointer to vbi3_raw_decoder object allocated with * vbi3_raw_decoder_new(). * $param services Set of data services. * * Removes one or more data services to be decoded from the * vbi3_raw_decoder object. * * $return * Set describing the remaining data services $a rd will decode. */ vbi_service_set vbi3_raw_decoder_remove_services (vbi3_raw_decoder * rd, vbi_service_set services) { _vbi3_raw_decoder_job *job; unsigned int job_num; assert (NULL != rd); job = rd->jobs; job_num = 0; while (job_num < rd->n_jobs) { if (job->id & services) { if (rd->pattern) remove_job_from_pattern (rd, job_num); memmove (job, job + 1, (rd->n_jobs - job_num - 1) * sizeof (*job)); --rd->n_jobs; CLEAR (rd->jobs[rd->n_jobs]); } else { ++job_num; } } rd->services &= ~services; return rd->services; } static vbi_bool add_job_to_pattern (vbi3_raw_decoder * rd, int job_num, unsigned int * start, unsigned int * count) { int8_t *pattern_end; unsigned int scan_lines; unsigned int field; job_num += 1; /* index into rd->jobs, 0 means no job */ scan_lines = rd->sampling.count[0] + rd->sampling.count[1]; pattern_end = rd->pattern + scan_lines * _VBI3_RAW_DECODER_MAX_WAYS; for (field = 0; field < 2; ++field) { int8_t *pattern; unsigned int i; pattern = rd->pattern + start[field] * _VBI3_RAW_DECODER_MAX_WAYS; /* For each line where we may find the data. */ for (i = 0; i < count[field]; ++i) { unsigned int free; int8_t *dst; int8_t *src; int8_t *end; assert (pattern < pattern_end); dst = pattern; end = pattern + _VBI3_RAW_DECODER_MAX_WAYS; free = 0; for (src = dst; src < end; ++src) { int8_t num = *src; if (num <= 0) { ++free; continue; } else { free += (num == job_num); *dst++ = num; } } while (dst < end) *dst++ = 0; if (free <= 1) /* reserve a NULL way */ return FALSE; pattern = end; } } for (field = 0; field < 2; ++field) { int8_t *pattern; unsigned int i; pattern = rd->pattern + start[field] * _VBI3_RAW_DECODER_MAX_WAYS; /* For each line where we may find the data. */ for (i = 0; i < count[field]; ++i) { unsigned int way; for (way = 0; pattern[way] > 0; ++way) if (pattern[way] == job_num) break; pattern[way] = job_num; pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1] = -128; pattern += _VBI3_RAW_DECODER_MAX_WAYS; } } return TRUE; } static void lines_containing_data (unsigned int start[2], unsigned int count[2], const vbi_sampling_par *sp, const _vbi_service_par *par) { unsigned int field; start[0] = 0; start[1] = sp->count[0]; count[0] = sp->count[0]; count[1] = sp->count[1]; if (!sp->synchronous) { /* XXX Scanning all lines isn't always necessary. */ return; } for (field = 0; field < 2; ++field) { unsigned int first; unsigned int last; if (0 == par->first[field] || 0 == par->last[field]) { /* No data on this field. */ count[field] = 0; continue; } first = sp->start[field]; last = first + sp->count[field] - 1; if (first > 0 && sp->count[field] > 0) { assert (par->first[field] <= par->last[field]); if ((unsigned int) par->first[field] > last || (unsigned int) par->last[field] < first) continue; first = MAX (first, (unsigned int) par->first[field]); last = MIN ((unsigned int) par->last[field], last); start[field] += first - sp->start[field]; count[field] = last + 1 - first; } } } /** * $param rd Pointer to vbi3_raw_decoder object allocated with * vbi3_raw_decoder_new(). * $param services Set of data services. * $param strict A value of 0, 1 or 2 requests loose, reliable or strict * matching of sampling parameters. For example if the data service * requires knowledge of line numbers, $c 0 will always accept the * service (which may work if the scan lines are populated in a * non-confusing way) but $c 1 or $c 2 will not. If the data service * might use more lines than are sampled, $c 1 will accept but $c 2 * will not. If unsure, set to $c 1. * * Adds one or more data services to be decoded. Currently the libzvbi * raw vbi decoder can decode up to eight data services in parallel. * * $return * Set describing the data services $a rd will decode. The function * eliminates services which cannot be decoded with the current * sampling parameters, or when they exceed the decoder capacity. */ /* Attn: strict must be int for compatibility with libzvbi 0.2 (-1 == 0) */ vbi_service_set vbi3_raw_decoder_add_services (vbi3_raw_decoder * rd, vbi_service_set services, int strict) { const _vbi_service_par *par; double min_offset; assert (NULL != rd); services &= ~(VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625); if (rd->services & services) { info (&rd->log, "Already decoding services 0x%08x.", rd->services & services); services &= ~rd->services; } if (0 == services) { info (&rd->log, "No services to add."); return rd->services; } if (!rd->pattern) { unsigned int scan_lines; unsigned int scan_ways; unsigned int size; scan_lines = rd->sampling.count[0] + rd->sampling.count[1]; scan_ways = scan_lines * _VBI3_RAW_DECODER_MAX_WAYS; size = scan_ways * sizeof (rd->pattern[0]); rd->pattern = (int8_t *) vbi_malloc (size); if (NULL == rd->pattern) { error (&rd->log, "Out of memory."); return rd->services; } memset (rd->pattern, 0, scan_ways * sizeof (rd->pattern[0])); } #if 2 == VBI_VERSION_MINOR if (525 == rd->sampling.scanning) { #else if (VBI3_VIDEOSTD_SET_525_60 & rd->sampling.videostd_set) { #endif min_offset = 7.9e-6; } else { min_offset = 8.0e-6; } for (par = _vbi_service_table; par->id; ++par) { vbi_sampling_par *sp; _vbi3_raw_decoder_job *job; unsigned int start[2]; unsigned int count[2]; unsigned int sample_offset; unsigned int samples_per_line; unsigned int cri_end; unsigned int j; if (0 == (par->id & services)) continue; job = rd->jobs; /* Some jobs can be merged, otherwise we add a new job. */ for (j = 0; j < rd->n_jobs; ++j) { unsigned int id = job->id | par->id; /* Level 1.0 and 2.5 */ if (0 == (id & ~VBI_SLICED_TELETEXT_B) /* Field 1 and 2 */ || 0 == (id & ~VBI_SLICED_CAPTION_525) || 0 == (id & ~VBI_SLICED_CAPTION_625) || 0 == (id & ~(VBI_SLICED_VPS | VBI_SLICED_VPS_F2))) break; ++job; } if (j >= _VBI3_RAW_DECODER_MAX_JOBS) { error (&rd->log, "Set 0x%08x exceeds number of " "simultaneously decodable " "services (%u).", services, _VBI3_RAW_DECODER_MAX_WAYS); break; } else if (j >= rd->n_jobs) { job->id = 0; } sp = &rd->sampling; if (!_vbi_sampling_par_check_services_log (sp, par->id, strict, &rd->log)) continue; sample_offset = 0; /* Skip color burst. */ /* Offsets aren't that reliable, sigh. */ if (0 && sp->offset > 0 && strict > 0) { double offset; offset = sp->offset / (double) sp->sampling_rate; if (offset < min_offset) sample_offset = (int)(min_offset * sp->sampling_rate); } if (VBI_SLICED_WSS_625 & par->id) { /* TODO: WSS 625 occupies only first half of line, we can abort earlier. */ cri_end = ~0; } else { cri_end = ~0; } #if 2 == VBI_VERSION_MINOR samples_per_line = sp->bytes_per_line / VBI_PIXFMT_BPP (sp->sp_sample_format); #else samples_per_line = sp->samples_per_line; #endif if (!_vbi3_bit_slicer_init (&job->slicer)) { assert (!"bit_slicer_init"); } if (!vbi3_bit_slicer_set_params (&job->slicer, sp->sp_sample_format, sp->sampling_rate, sample_offset, samples_per_line, par->cri_frc >> par->frc_bits, par->cri_frc_mask >> par->frc_bits, par->cri_bits, par->cri_rate, cri_end, (par->cri_frc & ((1U << par->frc_bits) - 1)), par->frc_bits, par->payload, par->bit_rate, par->modulation)) { assert (!"bit_slicer_set_params"); } vbi3_bit_slicer_set_log_fn (&job->slicer, rd->log.mask, rd->log.fn, rd->log.user_data); lines_containing_data (start, count, sp, par); if (!add_job_to_pattern (rd, job - rd->jobs, start, count)) { error (&rd->log, "Out of decoder pattern space for " "service 0x%08x (%s).", par->id, par->label); continue; } job->id |= par->id; if (job >= rd->jobs + rd->n_jobs) ++rd->n_jobs; rd->services |= par->id; } return rd->services; } vbi_bool vbi3_raw_decoder_sampling_point (vbi3_raw_decoder * rd, vbi3_bit_slicer_point *point, unsigned int row, unsigned int nth_bit) { assert (NULL != rd); assert (NULL != point); if (row >= rd->n_sp_lines) return FALSE; if (nth_bit >= rd->sp_lines[row].n_points) return FALSE; *point = rd->sp_lines[row].points[nth_bit]; return TRUE; } vbi_bool vbi3_raw_decoder_debug (vbi3_raw_decoder * rd, vbi_bool enable) { _vbi3_raw_decoder_sp_line *sp_lines; unsigned int n_lines; vbi_bool r; assert (NULL != rd); sp_lines = NULL; r = TRUE; rd->debug = !!enable; n_lines = 0; if (enable) { n_lines = rd->sampling.count[0] + rd->sampling.count[1]; } switch (rd->sampling.sp_sample_format) { #if 3 == VBI_VERSION_MINOR case VBI_PIXFMT_YUV444: case VBI_PIXFMT_YVU444: case VBI_PIXFMT_YUV422: case VBI_PIXFMT_YVU422: case VBI_PIXFMT_YUV411: case VBI_PIXFMT_YVU411: case VBI_PIXFMT_YVU420: case VBI_PIXFMT_YUV410: case VBI_PIXFMT_YVU410: case VBI_PIXFMT_Y8: case VBI_PIXFMT_UNKNOWN: #endif case VBI_PIXFMT_YUV420: break; default: /* Not implemented. */ n_lines = 0; r = FALSE; break; } if (rd->n_sp_lines == n_lines) return r; vbi_free (rd->sp_lines); rd->sp_lines = NULL; rd->n_sp_lines = 0; if (n_lines > 0) { rd->sp_lines = calloc (n_lines, sizeof (*rd->sp_lines)); if (NULL == rd->sp_lines) return FALSE; rd->n_sp_lines = n_lines; } return r; } vbi_service_set vbi3_raw_decoder_services (vbi3_raw_decoder * rd) { assert (NULL != rd); return rd->services; } /** * $param rd Pointer to a vbi3_raw_decoder object allocated with * vbi3_raw_decoder_new(). * $param sp New sampling parameters. * $param strict See vbi3_raw_decoder_add_services(). * * Changes the sampling parameters used by $a rd. This will * remove all services which have been added with * vbi3_raw_decoder_add_services() but cannot be decoded with * the new sampling parameters. * * $return * Set of data services $rd will be decode after the change. * Can be zero if the sampling parameters are invalid or some * other error occured. */ /* Attn: strict must be int for compatibility with libzvbi 0.2 (-1 == 0) */ vbi_service_set vbi3_raw_decoder_set_sampling_par (vbi3_raw_decoder * rd, const vbi_sampling_par *sp, int strict) { unsigned int services; assert (NULL != rd); assert (NULL != sp); services = rd->services; vbi3_raw_decoder_reset (rd); if (!_vbi_sampling_par_valid_log (sp, &rd->log)) { CLEAR (rd->sampling); return 0; } rd->sampling = *sp; /* Error ignored. */ vbi3_raw_decoder_debug (rd, rd->debug); return vbi3_raw_decoder_add_services (rd, services, strict); } /** * $param rd Pointer to a vbi3_raw_decoder object allocated with * vbi3_raw_decoder_new(). * $param sp Sampling parameters will be stored here. * * Returns sampling parameters used by $a rd. */ void vbi3_raw_decoder_get_sampling_par (const vbi3_raw_decoder *rd, vbi_sampling_par * sp) { assert (NULL != rd); assert (NULL != sp); *sp = rd->sampling; } void vbi3_raw_decoder_set_log_fn (vbi3_raw_decoder * rd, vbi_log_fn * log_fn, void * user_data, vbi_log_mask mask) { unsigned int i; assert (NULL != rd); if (NULL == log_fn) mask = 0; rd->log.mask = mask; rd->log.fn = log_fn; rd->log.user_data = user_data; for (i = 0; i < _VBI3_RAW_DECODER_MAX_JOBS; ++i) { vbi3_bit_slicer_set_log_fn (&rd->jobs[i].slicer, mask, log_fn, user_data); } } /** * @internal * * Free all resources associated with @a rd. */ void _vbi3_raw_decoder_destroy (vbi3_raw_decoder * rd) { vbi3_raw_decoder_reset (rd); vbi3_raw_decoder_debug (rd, FALSE); /* Make unusable. */ CLEAR (*rd); } /** * @internal * * See vbi3_raw_decoder_new(). */ vbi_bool _vbi3_raw_decoder_init (vbi3_raw_decoder * rd, const vbi_sampling_par *sp) { CLEAR (*rd); vbi3_raw_decoder_reset (rd); if (NULL != sp) { if (!_vbi_sampling_par_valid_log (sp, &rd->log)) return FALSE; rd->sampling = *sp; } return TRUE; } /** * $param rd Pointer to a vbi3_raw_decoder object allocated with * vbi3_raw_decoder_new(), can be NULL * * Deletes a vbi3_raw_decoder object. */ void vbi3_raw_decoder_delete (vbi3_raw_decoder * rd) { if (NULL == rd) return; _vbi3_raw_decoder_destroy (rd); vbi_free (rd); } /** * $param sp VBI sampling parameters describing the raw VBI image * to decode, can be $c NULL. If they are negotiatable you can determine * suitable parameters with vbi_sampling_par_from_services(). You can * change the sampling parameters later with * vbi3_raw_decoder_set_sampling_par(). * * Allocates a vbi3_raw_decoder object. To actually decode data * services you must request the data with vbi3_raw_decoder_add_services(). * * $returns * NULL when out of memory or the sampling parameters are invalid, * Otherwise a pointer to an opaque vbi3_raw_decoder object which must * be deleted with vbi3_raw_decoder_delete() when done. */ vbi3_raw_decoder * vbi3_raw_decoder_new (const vbi_sampling_par *sp) { vbi3_raw_decoder *rd; rd = vbi_malloc (sizeof (*rd)); if (NULL == rd) { errno = ENOMEM; return NULL; } if (!_vbi3_raw_decoder_init (rd, sp)) { vbi_free (rd); rd = NULL; } return rd; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/raw_decoder.h000066400000000000000000000121661476363111200160030ustar00rootroot00000000000000/* * libzvbi -- Raw VBI decoder * * Copyright (C) 2000-2004 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: raw_decoder.h,v 1.12 2008-02-19 00:35:21 mschimek Exp $ */ #ifndef __ZVBI_RAW_DECODER_H__ #define __ZVBI_RAW_DECODER_H__ #include #include "decoder.h" #include "sampling_par.h" #include "bit_slicer.h" VBI_BEGIN_DECLS /* * $ingroup RawDecoder * $brief Raw VBI decoder. * * The contents of this structure are private. * Call vbi3_raw_decoder_new() to allocate a raw VBI decoder. */ typedef struct _vbi3_raw_decoder vbi3_raw_decoder; /* * $addtogroup RawDecoder * ${ */ extern vbi_bool vbi3_raw_decoder_sampling_point (vbi3_raw_decoder * rd, vbi3_bit_slicer_point *point, unsigned int row, unsigned int nth_bit); extern unsigned int vbi3_raw_decoder_decode (vbi3_raw_decoder * rd, vbi_sliced * sliced, unsigned int sliced_lines, const uint8_t * raw); extern void vbi3_raw_decoder_reset (vbi3_raw_decoder * rd); extern vbi_service_set vbi3_raw_decoder_services (vbi3_raw_decoder * rd); extern vbi_service_set vbi3_raw_decoder_remove_services (vbi3_raw_decoder * rd, vbi_service_set services); extern vbi_service_set vbi3_raw_decoder_add_services (vbi3_raw_decoder * rd, vbi_service_set services, int strict); extern vbi_bool vbi3_raw_decoder_debug (vbi3_raw_decoder * rd, vbi_bool enable); extern vbi_service_set vbi3_raw_decoder_set_sampling_par (vbi3_raw_decoder * rd, const vbi_sampling_par *sp, int strict); extern void vbi3_raw_decoder_get_sampling_par (const vbi3_raw_decoder *rd, vbi_sampling_par * sp); extern void vbi3_raw_decoder_set_log_fn (vbi3_raw_decoder * rd, vbi_log_fn * log_fn, void * user_data, vbi_log_mask mask); extern void vbi3_raw_decoder_delete (vbi3_raw_decoder * rd); extern vbi3_raw_decoder * vbi3_raw_decoder_new (const vbi_sampling_par *sp); /* $} */ /* Private */ /** @internal */ #define _VBI3_RAW_DECODER_MAX_JOBS 8 /** @internal */ #define _VBI3_RAW_DECODER_MAX_WAYS 8 /** @internal */ typedef struct { vbi_service_set id; vbi3_bit_slicer slicer; } _vbi3_raw_decoder_job; /** @internal */ typedef struct { vbi3_bit_slicer_point points[512]; unsigned int n_points; } _vbi3_raw_decoder_sp_line; /** * @internal * Don't dereference pointers to this structure. * I guarantee it will change. */ struct _vbi3_raw_decoder { vbi_sampling_par sampling; vbi_service_set services; _vbi_log_hook log; vbi_bool debug; unsigned int n_jobs; unsigned int n_sp_lines; int readjust; int8_t * pattern; /* n scan lines * MAX_WAYS */ _vbi3_raw_decoder_job jobs[_VBI3_RAW_DECODER_MAX_JOBS]; _vbi3_raw_decoder_sp_line *sp_lines; }; /** @internal */ typedef enum { /** Requires field line numbers. */ _VBI_SP_LINE_NUM = (1 << 0), /** Requires field numbers. */ _VBI_SP_FIELD_NUM = (1 << 1), } _vbi_service_par_flag; typedef struct _vbi_service_par _vbi_service_par; /** @internal */ struct _vbi_service_par { vbi_service_set id; const char * label; /** * Video standard * - 525 lines, FV = 59.94 Hz, FH = 15734 Hz * - 625 lines, FV = 50 Hz, FH = 15625 Hz */ vbi_videostd_set videostd_set; /** * Most scan lines used by the data service, first and last * line of first and second field. ITU-R numbering scheme. * Zero if no data from this field, requires field sync. */ unsigned int first[2]; unsigned int last[2]; /** * Leading edge hsync to leading edge first CRI one bit, * half amplitude points, in nanoseconds. */ unsigned int offset; unsigned int cri_rate; /**< Hz */ unsigned int bit_rate; /**< Hz */ /** Clock Run In and FRaming Code, LSB last txed bit of FRC. */ unsigned int cri_frc; /** CRI and FRC bits significant for identification. */ unsigned int cri_frc_mask; /** * Number of significat cri_bits (at cri_rate), * frc_bits (at bit_rate). */ unsigned int cri_bits; unsigned int frc_bits; unsigned int payload; /**< bits */ vbi_modulation modulation; _vbi_service_par_flag flags; }; extern const _vbi_service_par _vbi_service_table []; extern void _vbi3_raw_decoder_dump (const vbi3_raw_decoder *rd, FILE * fp); extern void _vbi3_raw_decoder_destroy (vbi3_raw_decoder * rd); extern vbi_bool _vbi3_raw_decoder_init (vbi3_raw_decoder * rd, const vbi_sampling_par *sp); VBI_END_DECLS #endif /* __ZVBI_RAW_DECODER_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/sampling_par.c000066400000000000000000000374251476363111200162010ustar00rootroot00000000000000/* * libzvbi -- Raw VBI sampling parameters * * Copyright (C) 2000-2004 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: sampling_par.c,v 1.12 2013-08-28 14:45:00 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "misc.h" #include "raw_decoder.h" #include "sampling_par.h" #include "sliced.h" #include "version.h" #if 2 == VBI_VERSION_MINOR # define vbi_pixfmt_bytes_per_pixel VBI_PIXFMT_BPP # define sp_sample_format sampling_format #else # define sp_sample_format sample_format #endif /** * @addtogroup Sampling Raw VBI sampling * @ingroup Raw * @brief Raw VBI data sampling interface. */ /** * @internal * Compatibility. */ vbi_videostd_set _vbi_videostd_set_from_scanning (int scanning) { switch (scanning) { case 525: return VBI_VIDEOSTD_SET_525_60; case 625: return VBI_VIDEOSTD_SET_625_50; default: break; } return 0; } _vbi_inline vbi_bool range_check (unsigned int start, unsigned int count, unsigned int min, unsigned int max) { /* Check bounds and overflow. */ return (start >= min && (start + count) <= max && (start + count) >= start); } /** * @internal * @param sp Sampling parameters to verify. * * @return * TRUE if the sampling parameters are valid (as far as we can tell). */ vbi_bool _vbi_sampling_par_valid_log (const vbi_sampling_par *sp, _vbi_log_hook * log) { vbi_videostd_set videostd_set; unsigned int bpp; assert (NULL != sp); switch (sp->sp_sample_format) { case VBI_PIXFMT_YUV420: #if 2 == VBI_VERSION_MINOR /* This conflicts with the ivtv driver, which returns an odd number of bytes per line. The driver format is _GREY but libzvbi 0.2 has no VBI_PIXFMT_Y8. */ #else if (sp->samples_per_line & 1) goto bad_samples; /* fall through */ case VBI3_PIXFMT_Y8: /* very common */ if (sp->samples_per_line > sp->bytes_per_line) goto too_many_samples; #endif break; default: bpp = vbi_pixfmt_bytes_per_pixel (sp->sp_sample_format); #if 2 == VBI_VERSION_MINOR if (0 != (sp->bytes_per_line % bpp)) goto bad_samples; #else if (sp->samples_per_line * bpp > sp->bytes_per_line) goto too_many_samples; #endif break; } #if 2 == VBI_VERSION_MINOR if (0 == sp->bytes_per_line) goto no_samples; #else if (0 == sp->samples_per_line) goto no_samples; #endif if (0 == sp->count[0] && 0 == sp->count[1]) goto bad_range; #if 2 == VBI_VERSION_MINOR videostd_set = _vbi_videostd_set_from_scanning (sp->scanning); #else videostd_set = sp->videostd_set; #endif if (VBI_VIDEOSTD_SET_525_60 & videostd_set) { if (VBI_VIDEOSTD_SET_625_50 & videostd_set) goto ambiguous; if (0 != sp->start[0] && !range_check (sp->start[0], sp->count[0], 1, 262)) goto bad_range; if (0 != sp->start[1] && !range_check (sp->start[1], sp->count[1], 263, 525)) goto bad_range; } else if (VBI_VIDEOSTD_SET_625_50 & videostd_set) { if (0 != sp->start[0] && !range_check (sp->start[0], sp->count[0], 1, 311)) goto bad_range; if (0 != sp->start[1] && !range_check (sp->start[1], sp->count[1], 312, 625)) goto bad_range; } else { ambiguous: info (log, "Ambiguous videostd_set 0x%lx.", (unsigned long) videostd_set); return FALSE; } if (sp->interlaced && (sp->count[0] != sp->count[1] || 0 == sp->count[0])) { info (log, "Line counts %u, %u must be equal and " "non-zero when raw VBI data is interlaced.", sp->count[0], sp->count[1]); return FALSE; } return TRUE; no_samples: info (log, "samples_per_line is zero."); return FALSE; #if 3 == VBI_VERSION_MINOR too_many_samples: info (log, "samples_per_line %u times bytes per samples %u is " "greater than bytes_per_line %u.", sp->samples_per_line, vbi3_pixfmt_bytes_per_pixel (sp->sp_sample_format), sp->bytes_per_line); return FALSE; #endif bad_samples: info (log, "bytes_per_line value %u is no multiple of " "the sample size %u.", sp->bytes_per_line, vbi_pixfmt_bytes_per_pixel (sp->sp_sample_format)); return FALSE; bad_range: info (log, "Invalid VBI scan range %u-%u (%u lines), " "%u-%u (%u lines).", sp->start[0], sp->start[0] + sp->count[0] - 1, sp->count[0], sp->start[1], sp->start[1] + sp->count[1] - 1, sp->count[1]); return FALSE; } static vbi_bool _vbi_sampling_par_permit_service (const vbi_sampling_par *sp, const _vbi_service_par *par, unsigned int strict, _vbi_log_hook * log) { const unsigned int unknown = 0; double signal; unsigned int field; unsigned int samples_per_line; vbi_videostd_set videostd_set; assert (NULL != sp); assert (NULL != par); #if 2 == VBI_VERSION_MINOR videostd_set = _vbi_videostd_set_from_scanning (sp->scanning); #else videostd_set = sp->videostd_set; #endif if (0 == (par->videostd_set & videostd_set)) { info (log, "Service 0x%08x (%s) requires " "videostd_set 0x%lx, " "have 0x%lx.", par->id, par->label, (unsigned long) par->videostd_set, (unsigned long) videostd_set); return FALSE; } if (par->flags & _VBI_SP_LINE_NUM) { if ((par->first[0] > 0 && unknown == (unsigned int) sp->start[0]) || (par->first[1] > 0 && unknown == (unsigned int) sp->start[1])) { info (log, "Service 0x%08x (%s) requires known " "line numbers.", par->id, par->label); return FALSE; } } { unsigned int rate; rate = MAX (par->cri_rate, par->bit_rate); switch (par->id) { case VBI_SLICED_WSS_625: /* Effective bit rate is just 1/3 max_rate, so 1 * max_rate should suffice. */ break; default: rate = (rate * 3) >> 1; break; } if (rate > (unsigned int) sp->sampling_rate) { info (log, "Sampling rate %f MHz too low " "for service 0x%08x (%s).", sp->sampling_rate / 1e6, par->id, par->label); return FALSE; } } signal = par->cri_bits / (double) par->cri_rate + (par->frc_bits + par->payload) / (double) par->bit_rate; #if 2 == VBI_VERSION_MINOR samples_per_line = sp->bytes_per_line / VBI_PIXFMT_BPP (sp->sampling_format); #else samples_per_line = sp->samples_per_line; #endif if (0 && sp->offset > 0 && strict > 0) { double sampling_rate; double offset; double end; sampling_rate = (double) sp->sampling_rate; offset = sp->offset / sampling_rate; end = (sp->offset + samples_per_line) / sampling_rate; if (offset > (par->offset / 1e3 - 0.5e-6)) { info (log, "Sampling starts at 0H + %f us, too " "late for service 0x%08x (%s) at " "%f us.", offset * 1e6, par->id, par->label, par->offset / 1e3); return FALSE; } if (end < (par->offset / 1e9 + signal + 0.5e-6)) { info (log, "Sampling ends too early at 0H + " "%f us for service 0x%08x (%s) " "which ends at %f us", end * 1e6, par->id, par->label, par->offset / 1e3 + signal * 1e6 + 0.5); return FALSE; } } else { double samples; samples = samples_per_line / (double) sp->sampling_rate; if (strict > 0) samples -= 1e-6; /* headroom */ if (samples < signal) { info (log, "Service 0x%08x (%s) signal length " "%f us exceeds %f us sampling length.", par->id, par->label, signal * 1e6, samples * 1e6); return FALSE; } } if ((par->flags & _VBI_SP_FIELD_NUM) && !sp->synchronous) { info (log, "Service 0x%08x (%s) requires " "synchronous field order.", par->id, par->label); return FALSE; } for (field = 0; field < 2; ++field) { unsigned int start; unsigned int end; start = sp->start[field]; end = start + sp->count[field] - 1; if (0 == par->first[field] || 0 == par->last[field]) { /* No data on this field. */ continue; } if (0 == sp->count[field]) { info (log, "Service 0x%08x (%s) requires " "data from field %u", par->id, par->label, field + 1); return FALSE; } /* (int) <= 0 for compatibility with libzvbi 0.2.x */ if ((int) strict <= 0 || 0 == sp->start[field]) continue; if (1 == strict && par->first[field] > par->last[field]) { /* May succeed if not all scanning lines available for the service are actually used. */ continue; } if (start > par->first[field] || end < par->last[field]) { info (log, "Service 0x%08x (%s) requires " "lines %u-%u, have %u-%u.", par->id, par->label, par->first[field], par->last[field], start, end); return FALSE; } } return TRUE; } /** * @internal */ vbi_service_set _vbi_sampling_par_check_services_log (const vbi_sampling_par *sp, vbi_service_set services, unsigned int strict, _vbi_log_hook * log) { const _vbi_service_par *par; vbi_service_set rservices; assert (NULL != sp); rservices = 0; for (par = _vbi_service_table; par->id; ++par) { if (0 == (par->id & services)) continue; if (_vbi_sampling_par_permit_service (sp, par, strict, log)) rservices |= par->id; } return rservices; } /** * @internal */ vbi_service_set _vbi_sampling_par_from_services_log (vbi_sampling_par * sp, unsigned int * max_rate, vbi_videostd_set videostd_set_req, vbi_service_set services, _vbi_log_hook * log) { const _vbi_service_par *par; vbi_service_set rservices; vbi_videostd_set videostd_set; unsigned int rate; unsigned int samples_per_line; assert (NULL != sp); videostd_set = 0; if (0 != videostd_set_req) { if (0 == (VBI_VIDEOSTD_SET_ALL & videostd_set_req) || ((VBI_VIDEOSTD_SET_525_60 & videostd_set_req) && (VBI_VIDEOSTD_SET_625_50 & videostd_set_req))) { warning (log, "Ambiguous videostd_set 0x%lx.", (unsigned long) videostd_set_req); CLEAR (*sp); return 0; } videostd_set = videostd_set_req; } samples_per_line = 0; sp->sampling_rate = 27000000; /* ITU-R BT.601 */ sp->offset = (int)(64e-6 * sp->sampling_rate); sp->start[0] = 30000; sp->count[0] = 0; sp->start[1] = 30000; sp->count[1] = 0; sp->interlaced = FALSE; sp->synchronous = TRUE; rservices = 0; rate = 0; for (par = _vbi_service_table; par->id; ++par) { double margin; double signal; int offset; unsigned int samples; unsigned int i; if (0 == (par->id & services)) continue; if (0 == videostd_set_req) { vbi_videostd_set set; set = par->videostd_set | videostd_set; if (0 == (set & ~VBI_VIDEOSTD_SET_525_60) || 0 == (set & ~VBI_VIDEOSTD_SET_625_50)) videostd_set |= par->videostd_set; } if (VBI_VIDEOSTD_SET_525_60 & videostd_set) margin = 1.0e-6; else margin = 2.0e-6; if (0 == (par->videostd_set & videostd_set)) { info (log, "Service 0x%08x (%s) requires " "videostd_set 0x%lx, " "have 0x%lx.", par->id, par->label, (unsigned long) par->videostd_set, (unsigned long) videostd_set); continue; } rate = MAX (rate, par->cri_rate); rate = MAX (rate, par->bit_rate); signal = par->cri_bits / (double) par->cri_rate + ((par->frc_bits + par->payload) / (double) par->bit_rate); offset = (int)((par->offset / 1e9) * sp->sampling_rate); samples = (int)((signal + 1.0e-6) * sp->sampling_rate); sp->offset = MIN (sp->offset, offset); samples_per_line = MAX (samples_per_line + sp->offset, samples + offset) - sp->offset; for (i = 0; i < 2; ++i) if (par->first[i] > 0 && par->last[i] > 0) { sp->start[i] = MIN ((unsigned int) sp->start[i], (unsigned int) par->first[i]); sp->count[i] = MAX ((unsigned int) sp->start[i] + sp->count[i], (unsigned int) par->last[i] + 1) - sp->start[i]; } rservices |= par->id; } if (0 == rservices) { CLEAR (*sp); return 0; } if (0 == sp->count[1]) { sp->start[1] = 0; if (0 == sp->count[0]) { sp->start[0] = 0; sp->offset = 0; } } else if (0 == sp->count[0]) { sp->start[0] = 0; } #if 3 == VBI_VERSION_MINOR sp->videostd_set = videostd_set; sp->sp_sample_format = VBI_PIXFMT_Y8; sp->samples_per_line = samples_per_line; #else sp->scanning = (videostd_set & VBI_VIDEOSTD_SET_525_60) ? 525 : 625; sp->sp_sample_format = VBI_PIXFMT_YUV420; #endif /* Note bpp is 1. */ sp->bytes_per_line = MAX (1440U, samples_per_line); if (max_rate) *max_rate = rate; return rservices; } /** * @param sp Sampling parameters to check against. * @param services Set of data services. * @param strict See description of vbi_raw_decoder_add_services(). * * Check which of the given services can be decoded with the given * sampling parameters at the given strictness level. * * @return * Subset of @a services decodable with the given sampling parameters. */ vbi_service_set vbi_sampling_par_check_services (const vbi_sampling_par *sp, vbi_service_set services, unsigned int strict) { return _vbi_sampling_par_check_services_log (sp, services, strict, /* log_hook */ NULL); } /** * @param sp Sampling parameters calculated by this function * will be stored here. * @param max_rate If not NULL, the highest data bit rate in Hz of * all services requested will be stored here. The sampling rate * should be at least twice as high; @sp sampling_rate will * be set to a more reasonable value of 27 MHz, which is twice * the video sampling rate defined by ITU-R Rec. BT.601. * @param videostd_set Create sampling parameters matching these * video standards. When 0 determine video standard from requested * services. * @param services Set of VBI_SLICED_ symbols. Here (and only here) you * can add @c VBI_SLICED_VBI_625 or @c VBI_SLICED_VBI_525 to include all * vbi scan lines in the calculated sampling parameters. * * Calculate the sampling parameters required to receive and decode the * requested data @a services. The @a sp sampling_format will be * @c VBI_PIXFMT_Y8, offset and bytes_per_line will be set to * reasonable minimums. This function can be used to initialize hardware * prior to creating a vbi_raw_decoder object. * * @return * Subset of @a services covered by the calculated sampling parameters. */ vbi_service_set vbi_sampling_par_from_services (vbi_sampling_par * sp, unsigned int * max_rate, vbi_videostd_set videostd_set, vbi_service_set services) { return _vbi_sampling_par_from_services_log (sp, max_rate, videostd_set, services, /* log_hook */ NULL); } #if 3 == VBI_VERSION_MINOR /** * @param videostd A video standard number. * * Returns the name of a video standard like VBI_VIDEOSTD_PAL_B -> * "PAL_B". This is mainly intended for debugging. * * @return * Static ASCII string, NULL if @a videostd is a custom standard * or invalid. */ const char * _vbi_videostd_name (vbi_videostd videostd) { switch (videostd) { #undef CASE #define CASE(std) case VBI_VIDEOSTD_##std : return #std ; CASE (NONE) CASE (PAL_B) CASE (PAL_B1) CASE (PAL_G) CASE (PAL_H) CASE (PAL_I) CASE (PAL_D) CASE (PAL_D1) CASE (PAL_K) CASE (PAL_M) CASE (PAL_N) CASE (PAL_NC) CASE (PAL_60) CASE (NTSC_M) CASE (NTSC_M_JP) CASE (NTSC_M_KR) CASE (NTSC_443) CASE (SECAM_B) CASE (SECAM_D) CASE (SECAM_G) CASE (SECAM_H) CASE (SECAM_K) CASE (SECAM_K1) CASE (SECAM_L) CASE (SECAM_LC) } return NULL; } #endif /* 3 == VBI_VERSION_MINOR */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/sampling_par.h000066400000000000000000000053501476363111200161760ustar00rootroot00000000000000/* * libzvbi -- Raw VBI sampling parameters * * Copyright (C) 2000-2004 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: sampling_par.h,v 1.9 2008-02-24 14:17:06 mschimek Exp $ */ #ifndef __SAMPLING_PAR_H__ #define __SAMPLING_PAR_H__ #include "decoder.h" VBI_BEGIN_DECLS /* Public */ typedef vbi_raw_decoder vbi_sampling_par; #define VBI_VIDEOSTD_SET_EMPTY 0 #define VBI_VIDEOSTD_SET_PAL_BG 1 #define VBI_VIDEOSTD_SET_625_50 1 #define VBI_VIDEOSTD_SET_525_60 2 #define VBI_VIDEOSTD_SET_ALL 3 typedef uint64_t vbi_videostd_set; /* Private */ extern vbi_service_set vbi_sampling_par_from_services (vbi_sampling_par * sp, unsigned int * max_rate, vbi_videostd_set videostd_set, vbi_service_set services); extern vbi_service_set vbi_sampling_par_check_services (const vbi_sampling_par *sp, vbi_service_set services, unsigned int strict) _vbi_pure; extern vbi_videostd_set _vbi_videostd_set_from_scanning (int scanning); extern vbi_service_set _vbi_sampling_par_from_services_log (vbi_sampling_par * sp, unsigned int * max_rate, vbi_videostd_set videostd_set, vbi_service_set services, _vbi_log_hook * log); extern vbi_service_set _vbi_sampling_par_check_services_log (const vbi_sampling_par *sp, vbi_service_set services, unsigned int strict, _vbi_log_hook * log) _vbi_pure; extern vbi_bool _vbi_sampling_par_valid_log (const vbi_sampling_par *sp, _vbi_log_hook * log) _vbi_pure; VBI_END_DECLS #endif /* __SAMPLING_PAR_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/search.c000066400000000000000000000355361476363111200147730ustar00rootroot00000000000000/* * libzvbi -- Teletext page cache search functions * * Copyright (C) 2000, 2001, 2002 Michael H. Schimek * Copyright (C) 2000, 2001 I�aki G. Etxebarria * * Originally based on AleVT 1.5.1 by Edgar Toernig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: search.c,v 1.15 2008-02-19 00:35:21 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "lang.h" #include "cache.h" #include "search.h" #include "ure.h" #include "vbi.h" #include "teletext_decoder.h" /** * @addtogroup Search * @ingroup Cache * @brief Search the Teletext page cache. */ #if defined(HAVE_GLIBC21) || defined(HAVE_LIBUNICODE) struct vbi_search { vbi_decoder * vbi; int start_pgno; int start_subno; int stop_pgno[2]; int stop_subno[2]; int row[2], col[2]; int dir; vbi_bool (* progress)(vbi_page *pg); vbi_page pg; ure_buffer_t ub; ure_dfa_t ud; ucs2_t haystack[25 * (40 + 1) + 1]; }; #define SEPARATOR 0x000A #define FIRST_ROW 1 #define LAST_ROW 24 static void highlight(struct vbi_search *s, cache_page *vtp, ucs2_t *first, long ms, long me) { vbi_page *pg = &s->pg; ucs2_t *hp; int i, j; hp = s->haystack; s->start_pgno = vtp->pgno; s->start_subno = vtp->subno; s->row[0] = LAST_ROW + 1; s->col[0] = 0; for (i = FIRST_ROW; i < LAST_ROW; i++) { vbi_char *acp = &pg->text[i * pg->columns]; for (j = 0; j < 40; acp++, j++) { int offset = hp - first; if (offset >= me) { s->row[0] = i; s->col[0] = j; return; } if (offset < ms) { if (j == 39) { s->row[1] = i + 1; s->col[1] = 0; } else { s->row[1] = i; s->col[1] = j + 1; } } switch (acp->size) { case VBI_DOUBLE_SIZE: if (offset >= ms) { acp[pg->columns].foreground = 32 + VBI_BLACK; acp[pg->columns].background = 32 + VBI_YELLOW; acp[pg->columns + 1].foreground = 32 + VBI_BLACK; acp[pg->columns + 1].background = 32 + VBI_YELLOW; } /* fall through */ case VBI_DOUBLE_WIDTH: if (offset >= ms) { acp[0].foreground = 32 + VBI_BLACK; acp[0].background = 32 + VBI_YELLOW; acp[1].foreground = 32 + VBI_BLACK; acp[1].background = 32 + VBI_YELLOW; } hp++; acp++; j++; break; case VBI_DOUBLE_HEIGHT: if (offset >= ms) { acp[pg->columns].foreground = 32 + VBI_BLACK; acp[pg->columns].background = 32 + VBI_YELLOW; } /* fall through */ case VBI_NORMAL_SIZE: if (offset >= ms) { acp[0].foreground = 32 + VBI_BLACK; acp[0].background = 32 + VBI_YELLOW; } hp++; break; default: /* skipped */ /* hp++; */ break; } } hp++; } } static int search_page_fwd(cache_page *vtp, vbi_bool wrapped, void *p) { vbi_search *s = p; vbi_char *acp; int row, _this, start, stop; ucs2_t *hp, *first; unsigned long ms, me; int flags, i, j; _this = (vtp->pgno << 16) + vtp->subno; start = (s->start_pgno << 16) + s->start_subno; stop = (s->stop_pgno[0] << 16) + s->stop_subno[0]; if (start >= stop) { if (wrapped && _this >= stop) return -1; /* all done, abort */ } else if (_this < start || _this >= stop) return -1; /* all done, abort */ if (vtp->function != PAGE_FUNCTION_LOP) return 0; /* try next */ if (!vbi_format_vt_page(s->vbi, &s->pg, vtp, s->vbi->vt.max_level, 25, 1)) return -3; /* formatting error, abort */ if (s->progress) if (!s->progress(&s->pg)) { if (_this != start) { s->start_pgno = vtp->pgno; s->start_subno = vtp->subno; s->row[0] = FIRST_ROW; s->row[1] = LAST_ROW + 1; s->col[0] = s->col[1] = 0; } return -2; /* canceled */ } /* To Unicode */ hp = s->haystack; first = hp; row = (_this == start) ? s->row[0] : -1; flags = 0; if (row > LAST_ROW) return 0; /* try next page */ for (i = FIRST_ROW; i < LAST_ROW; i++) { acp = &s->pg.text[i * s->pg.columns]; for (j = 0; j < 40; acp++, j++) { if (i == row && j <= s->col[0]) first = hp; if (acp->size == VBI_DOUBLE_WIDTH || acp->size == VBI_DOUBLE_SIZE) { /* "ZZAAPPZILLA" -> "ZAPZILLA" */ acp++; /* skip left half */ j++; } else if (acp->size > VBI_DOUBLE_SIZE) { /* skip */ /* *hp++ = 0x0020; */ continue; } *hp++ = acp->unicode; flags = URE_NOTBOL; } *hp++ = SEPARATOR; flags = 0; } /* Search */ if (first >= hp) return 0; /* try next page */ /* fprintf(stderr, "exec: %x/%x; start %d,%d; %c%c%c...\n", vtp->pgno, vtp->subno, s->row[0], s->col[0], _vbi_to_ascii (first[0]), _vbi_to_ascii (first[1]), _vbi_to_ascii (first[2]) ); */ if (!ure_exec(s->ud, flags, first, hp - first, &ms, &me)) return 0; /* try next page */ highlight(s, vtp, first, ms, me); return 1; /* success, abort */ } static int search_page_rev(cache_page *vtp, vbi_bool wrapped, void *p) { vbi_search *s = p; vbi_char *acp; int row, this, start, stop; unsigned long ms, me; ucs2_t *hp; int flags, i, j; this = (vtp->pgno << 16) + vtp->subno; start = (s->start_pgno << 16) + s->start_subno; stop = (s->stop_pgno[1] << 16) + s->stop_subno[1]; if (start <= stop) { if (wrapped && this <= stop) return -1; /* all done, abort */ } else if (this > start || this <= stop) return -1; /* all done, abort */ if (vtp->function != PAGE_FUNCTION_LOP) return 0; /* try next page */ if (!vbi_format_vt_page(s->vbi, &s->pg, vtp, s->vbi->vt.max_level, 25, 1)) return -3; /* formatting error, abort */ if (s->progress) if (!s->progress(&s->pg)) { if (this != start) { s->start_pgno = vtp->pgno; s->start_subno = vtp->subno; s->row[0] = FIRST_ROW; s->row[1] = LAST_ROW + 1; s->col[0] = s->col[1] = 0; } return -2; /* canceled */ } /* To Unicode */ hp = s->haystack; row = (this == start) ? s->row[1] : 100; flags = 0; if (row < FIRST_ROW) goto break2; for (i = FIRST_ROW; i < LAST_ROW; i++) { acp = &s->pg.text[i * s->pg.columns]; for (j = 0; j < 40; acp++, j++) { if (i == row && j >= s->col[1]) goto break2; if (acp->size == VBI_DOUBLE_WIDTH || acp->size == VBI_DOUBLE_SIZE) { /* "ZZAAPPZILLA" -> "ZAPZILLA" */ acp++; /* skip left half */ j++; } else if (acp->size > VBI_DOUBLE_SIZE) { /* skip */ /* *hp++ = 0x0020; */ continue; } *hp++ = acp->unicode; flags = URE_NOTEOL; } *hp++ = SEPARATOR; flags = 0; } break2: if (hp <= s->haystack) return 0; /* try next page */ /* Search */ ms = me = 0; for (i = 0; s->haystack + me < hp; i++) { unsigned long ms1, me1; /* fprintf(stderr, "exec: %x/%x; %d, %d; '%c%c%c...'\n", vtp->pgno, vtp->subno, i, me, _vbi_to_ascii (s->haystack[me + 0]), _vbi_to_ascii (s->haystack[me + 1]), _vbi_to_ascii (s->haystack[me + 2]) ); */ if (!ure_exec(s->ud, (me > 0) ? (flags | URE_NOTBOL) : flags, s->haystack + me, hp - s->haystack - me, &ms1, &me1)) break; ms = me + ms1; me = me + me1; } if (i == 0) return 0; /* try next page */ highlight(s, vtp, s->haystack, ms, me); return 1; /* success, abort */ } /** * @param search vbi_search context. * * Delete the search context created by vbi_search_new(). */ void vbi_search_delete(vbi_search *search) { if (!search) return; if (search->ud) ure_dfa_free(search->ud); if (search->ub) ure_buffer_free(search->ub); free(search); } static size_t ucs2_strlen(const void *string) { const ucs2_t *p = (const ucs2_t *) string; size_t i = 0; if (!string) return 0; for (i = 0; *p; i++) p++; return i; } /** * @param vbi Initialized vbi decoding context. * @param pgno * @param subno Page and subpage number of the first (forward) or * last (backward) page to visit. Optional @c VBI_ANY_SUBNO. * @param pattern The Unicode (UCS-2, not UTF-16) search * pattern, a 0-terminated string. * @param casefold Boolean, search case insensitive. * @param regexp Boolean, the search pattern is a regular expression. * @param progress A function called for each page scanned, can be * \c NULL. Shall return @c FALSE to abort the search. @a pg is valid * for display (e. g. @a pg->pgno), do not call * vbi_unref_page() or modify this page. * * Allocate a vbi_search context and prepare for searching * the Teletext page cache. The context must be freed with * vbi_search_delete(). * * Regular expression searching supports the standard set * of operators and constants, with these extensions: * * * * * * * * * * * *
\x....hexadecimal number of up to 4 digits
\X....hexadecimal number of up to 4 digits
\u....hexadecimal number of up to 4 digits
\U....hexadecimal number of up to 4 digits
:title:Unicode specific character class
:gfx:Teletext G1 or G3 graphics
:drcs:Teletext DRCS
\pN1,N2,...,NnCharacter properties class
\PN1,N2,...,NnNegated character properties class
* * * * * * * * * * * * * * * * * * * * *
NProperty
1alphanumeric
2alpha
3control
4digit
5graphical
6lowercase
7printable
8punctuation
9space
10uppercase
11hex digit
12title
13defined
14wide
15nonspacing
16Teletext G1 or G3 graphics
17Teletext DRCS
* * Character classes can contain literals, constants, and character * property classes. Example: [abc\U10A\p1,3,4]. Note double height * and size characters will match twice, on the upper and lower row, * and double width and size characters count as one (reducing the * line width) so one can find combinations of normal and enlarged * characters. * * @bug * In a multithreaded application the data service decoder may receive * and cache new pages during a search session. When these page numbers * have been visited already the pages are not searched. At a channel * switch (and in future at any time) pages can be removed from cache. * All this has yet to be addressed. * * @return * A vbi_search context or @c NULL on error or pattern string length * is too large. */ vbi_search * vbi_search_new(vbi_decoder *vbi, vbi_pgno pgno, vbi_subno subno, uint16_t *pattern, vbi_bool casefold, vbi_bool regexp, int (* progress)(vbi_page *pg)) { vbi_search *s; ucs2_t *esc_pat = NULL; int i, j, pat_len = ucs2_strlen(pattern); if (pat_len <= 0) return NULL; if (!(s = calloc(1, sizeof(*s)))) return NULL; if (!regexp) { unsigned int check_size = (sizeof(ucs2_t) * pat_len * 2); if (pat_len > check_size) { free(s); return NULL; } if (!(esc_pat = malloc(check_size))) { free(s); return NULL; } for (i = j = 0; i < pat_len; i++) { if (strchr("!\"#$%&()*+,-./:;=?@[\\]^_{|}~", pattern[i])) esc_pat[j++] = '\\'; esc_pat[j++] = pattern[i]; } pattern = esc_pat; pat_len = j; } if (!(s->ub = ure_buffer_create())) goto abort; if (!(s->ud = ure_compile(pattern, pat_len, casefold, s->ub))) { abort: vbi_search_delete(s); if (!regexp) free(esc_pat); return NULL; } if (!regexp) free(esc_pat); s->stop_pgno[0] = pgno; s->stop_subno[0] = (subno == VBI_ANY_SUBNO) ? 0 : subno; if (subno <= 0) { s->stop_pgno[1] = (pgno <= 0x100) ? 0x8FF : pgno - 1; s->stop_subno[1] = 0x3F7E; } else { s->stop_pgno[1] = pgno; if ((subno & 0x7F) == 0) s->stop_subno[1] = (subno - 0x100) | 0x7E; else s->stop_subno[1] = subno - 1; } s->vbi = vbi; s->progress = progress; return s; } /** * @param search Initialized search context. * @param pg Place to store the formatted (as with vbi_fetch_vt_page()) * Teletext page containing the found pattern. Do not * call vbi_unref_page() for this page. Also the page must not * be modified. See vbi_search_status for semantics. * @param dir Search direction +1 forward or -1 backward. * * Find the next occurence of the search pattern. * * @return * vbi_search_status. */ /* XXX fix return type */ int vbi_search_next(vbi_search *search, vbi_page **pg, int dir) { *pg = NULL; dir = (dir > 0) ? +1 : -1; if (!search->dir) { search->dir = dir; if (dir > 0) { search->start_pgno = search->stop_pgno[0]; search->start_subno = search->stop_subno[0]; } else { search->start_pgno = search->stop_pgno[1]; search->start_subno = search->stop_subno[1]; } search->row[0] = FIRST_ROW; search->row[1] = LAST_ROW + 1; search->col[0] = search->col[1] = 0; } #if 1 /* should switch to a 'two frontiers meet' model, but ok for now */ else if (dir != search->dir) { search->dir = dir; search->stop_pgno[0] = search->start_pgno; search->stop_subno[0] = (search->start_subno == VBI_ANY_SUBNO) ? 0 : search->start_subno; search->stop_pgno[1] = search->start_pgno; search->stop_subno[1] = search->start_subno; } #endif switch (_vbi_cache_foreach_page (search->vbi->ca, search->vbi->cn, search->start_pgno, search->start_subno, dir, (dir > 0) ? search_page_fwd : search_page_rev, /* user_data */ search)) { case 1: *pg = &search->pg; return VBI_SEARCH_SUCCESS; case 0: return VBI_SEARCH_CACHE_EMPTY; case -1: search->dir = 0; return VBI_SEARCH_NOT_FOUND; case -2: return VBI_SEARCH_CANCELED; default: break; } return VBI_SEARCH_ERROR; } #else /* !HAVE_GLIBC21 && !HAVE_LIBUNICODE */ vbi_search * vbi_search_new(vbi_decoder *vbi, vbi_pgno pgno, vbi_subno subno, uint16_t *pattern, vbi_bool casefold, vbi_bool regexp, int (* progress)(vbi_page *pg)) { return NULL; } int vbi_search_next(vbi_search *search, vbi_page **pg, int dir) { return VBI_SEARCH_ERROR; } void vbi_search_delete(vbi_search *search) { } #endif /* !HAVE_GLIBC21 && !HAVE_LIBUNICODE */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/search.h000066400000000000000000000051061476363111200147660ustar00rootroot00000000000000/* * libzvbi -- Teletext page cache search functions * * Copyright (C) 2000, 2001, 2002 Michael H. Schimek * Copyright (C) 2000, 2001 Iñaki G. Etxebarria * * Originally based on AleVT 1.5.1 by Edgar Toernig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: search.h,v 1.5 2008-02-19 00:35:22 mschimek Exp $ */ #ifndef SEARCH_H #define SEARCH_H #ifndef VBI_DECODER #define VBI_DECODER typedef struct vbi_decoder vbi_decoder; #endif /* Public */ /** * @ingroup Search * @brief Return codes of the vbi_search_next() function. */ typedef enum { /** * Pattern not found, @a pg is invalid. Another vbi_search_next() * will restart from the original starting point. */ VBI_SEARCH_ERROR = -3, /** * The search has been canceled by the progress function. * @a pg points to the current page as in success case, * except for the highlighting. Another vbi_search_next() * continues from this page. */ VBI_SEARCH_CACHE_EMPTY, /** * No pages in the cache, @a pg is invalid. */ VBI_SEARCH_CANCELED, /** * Some error occured, condition unclear. Call vbi_search_delete(). */ VBI_SEARCH_NOT_FOUND = 0, /** * Pattern found. @a pg points to the page ready for display with the pattern * highlighted, @a pg->pgno etc. */ VBI_SEARCH_SUCCESS } vbi_search_status; /** * @ingroup Search * @brief Opaque search context. */ typedef struct vbi_search vbi_search; /** * @addtogroup Search * @{ */ extern vbi_search * vbi_search_new(vbi_decoder *vbi, vbi_pgno pgno, vbi_subno subno, uint16_t *pattern, vbi_bool casefold, vbi_bool regexp, int (* progress)(vbi_page *pg)); extern void vbi_search_delete(vbi_search *search); extern vbi_search_status vbi_search_next(vbi_search *search, vbi_page **pg, int dir); /** @} */ /* Private */ #endif /* SEARCH_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/sliced.h000066400000000000000000000237431476363111200147730ustar00rootroot00000000000000/* * libzvbi -- Sliced VBI data * * Copyright (C) 2000, 2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: sliced.h,v 1.11 2008-02-24 14:17:02 mschimek Exp $ */ #ifndef SLICED_H #define SLICED_H #ifdef __cplusplus extern "C" { #endif /* Public */ #include /** * @addtogroup Sliced Sliced VBI data * @ingroup Raw * @brief Definition of sliced VBI data. * * The output of the libzvbi raw VBI decoder, and input to the data * service decoder, is VBI data in binary format as defined in this * section. It is similar to the output of hardware VBI decoders * and VBI data transmitted in digital TV streams. */ /** * @name Data service symbols * @ingroup Sliced * @{ */ /** * @anchor VBI_SLICED_ * No data service, blank vbi_sliced structure. */ #define VBI_SLICED_NONE 0 /** * Unknown data service (vbi_dvb_demux). * @since 0.2.10 */ #define VBI_SLICED_UNKNOWN 0 /** * Antiope a.k.a. Teletext System A * * Reference: ITU-R BT.653 * "Teletext Systems" * * vbi_sliced payload: Last 37 bytes, without clock run-in and * framing code, lsb first transmitted. * * @since 0.2.10 */ #define VBI_SLICED_ANTIOPE 0x00002000 /** * Synonym of VBI_SLICED_ANTIOPE. * @since 0.2.10 */ #define VBI_SLICED_TELETEXT_A 0x00002000 #define VBI_SLICED_TELETEXT_B_L10_625 0x00000001 #define VBI_SLICED_TELETEXT_B_L25_625 0x00000002 /** * Teletext System B for 625 line systems * * Note this is separated into Level 1.0 and Level 2.5+ since the latter * permits occupation of scan line 6 which is frequently out of * range of raw VBI capture drivers. Clients should request decoding of both, * may then verify Level 2.5 is covered. vbi_sliced id can be * VBI_SLICED_TELETEXT_B, _B_L10_625 or _B_L25_625 regardless of line number. * * Reference: EN 300 706 * "Enhanced Teletext specification", * ITU-R BT.653 "Teletext Systems" * * vbi_sliced payload: Last 42 of the 45 byte Teletext packet, that is * without clock run-in and framing code, lsb first transmitted. */ #define VBI_SLICED_TELETEXT_B (VBI_SLICED_TELETEXT_B_L10_625 | \ VBI_SLICED_TELETEXT_B_L25_625) /** * Synonym of VBI_SLICED_TELETEXT_B. * @since 0.2.10 */ #define VBI_SLICED_TELETEXT_B_625 VBI_SLICED_TELETEXT_B /** * Teletext System C for 625 line systems * * Reference: ITU-R BT.653 * "Teletext Systems" * * vbi_sliced payload: Last 33 bytes, without clock run-in and * framing code, lsb first transmitted. * * @since 0.2.10 */ #define VBI_SLICED_TELETEXT_C_625 0x00004000 /** * Teletext System D for 625 line systems * * Reference: ITU-R BT.653 * "Teletext Systems" * * vbi_sliced payload: Last 34 bytes, without clock run-in and * framing code, lsb first transmitted. * * @since 0.2.10 */ #define VBI_SLICED_TELETEXT_D_625 0x00008000 /** * Video Program System * * Reference: ETS 300 231 * "Specification of the domestic video Programme * Delivery Control system (PDC)", * IRT 8R2 "Video-Programm-System (VPS)". * * vbi_sliced payload: Byte number 3 to 15 according to ETS 300 231 * Figure 9, lsb first transmitted. */ #define VBI_SLICED_VPS 0x00000004 /** * Pseudo-VPS signal transmitted on field 2 * * vbi_sliced payload: 13 bytes. * * @since 0.2.10 */ #define VBI_SLICED_VPS_F2 0x00001000 #define VBI_SLICED_CAPTION_625_F1 0x00000008 #define VBI_SLICED_CAPTION_625_F2 0x00000010 /** * Closed Caption for 625 line systems * * Note this is split into field one and two services since for basic * caption decoding only field one is required. vbi_sliced id can be * VBI_SLICED_CAPTION_625, _625_F1 or _625_F2 regardless of line number. * * Reference: EIA 608 * "Recommended Practice for Line 21 Data Service". * * vbi_sliced payload: First and second byte including parity, * lsb first transmitted. */ #define VBI_SLICED_CAPTION_625 (VBI_SLICED_CAPTION_625_F1 | \ VBI_SLICED_CAPTION_625_F2) /** * Wide Screen Signalling for 625 line systems * * Reference: EN 300 294 * "625-line television Wide Screen Signalling (WSS)". * * vbi_sliced payload: *
 * Byte         0                  1
 *       msb         lsb  msb             lsb
 * bit   7 6 5 4 3 2 1 0  x x 13 12 11 10 9 8
* according to EN 300 294, Table 1, lsb first transmitted. */ #define VBI_SLICED_WSS_625 0x00000400 #define VBI_SLICED_CAPTION_525_F1 0x00000020 #define VBI_SLICED_CAPTION_525_F2 0x00000040 /** * Closed Caption for 525 line systems (NTSC). * * Note this is split into field one and two services since for basic * caption decoding only field one is required. vbi_sliced id can be * VBI_SLICED_CAPTION_525, _525_F1 or _525_F2 regardless of line number. * * VBI_SLICED_CAPTION_525 also covers XDS (Extended Data Service), * V-Chip data and ITV / WebTV data. * * Reference: EIA 608 * "Recommended Practice for Line 21 Data Service". * * vbi_sliced payload: First and second byte including parity, * lsb first transmitted. */ #define VBI_SLICED_CAPTION_525 (VBI_SLICED_CAPTION_525_F1 | \ VBI_SLICED_CAPTION_525_F2) /** * Closed Caption at double bit rate for 525 line systems. * * Reference: ? * * vbi_sliced payload: First to fourth byte including parity bit, * lsb first transmitted. */ #define VBI_SLICED_2xCAPTION_525 0x00000080 /** * Teletext System B for 525 line systems * * Reference: ITU-R BT.653 * "Teletext Systems" * * vbi_sliced payload: Last 34 bytes, without clock run-in and * framing code, lsb first transmitted. * * @since 0.2.10 */ #define VBI_SLICED_TELETEXT_B_525 0x00010000 /** * North American Basic Teletext Specification * a.k.a. Teletext System C for 525 line systems * * Reference: EIA-516 * "North American Basic Teletext Specification (NABTS)", * ITU-R BT.653 "Teletext Systems" * * vbi_sliced payload: Last 33 bytes, without clock run-in and * framing code, lsb first transmitted. * * @since 0.2.10 */ #define VBI_SLICED_NABTS 0x00000100 /** * Synonym of VBI_SLICED_NABTS. * @since 0.2.10 */ #define VBI_SLICED_TELETEXT_C_525 0x00000100 /** * Misdefined. * * vbi_sliced payload: 34 bytes. * * @deprecated * This service was misdefined. * Use VBI_SLICED_TELETEXT_B_525 or VBI_SLICED_TELETEXT_D_525 in new code. */ #define VBI_SLICED_TELETEXT_BD_525 0x00000200 /** * Teletext System D for 525 line systems * * Reference: ITU-R BT.653 * "Teletext Systems" * * vbi_sliced payload: Last 34 bytes, without clock run-in and * framing code, lsb first transmitted. * * @since 0.2.10 */ #define VBI_SLICED_TELETEXT_D_525 0x00020000 /** * Wide Screen Signalling for NTSC Japan * * Reference: EIA-J CPR-1204 * * vbi_sliced payload: *
 * Byte         0                    1                  2
 *       msb         lsb  msb               lsb  msb             lsb
 * bit   7 6 5 4 3 2 1 0  15 14 13 12 11 10 9 8  x x x x 19 18 17 16
 * 
*/ #define VBI_SLICED_WSS_CPR1204 0x00000800 /** * No actual data service. This symbol is used to request capturing * of all PAL/SECAM VBI data lines from the libzvbi driver interface, * as opposed to just those lines used to transmit the requested * data services. */ #define VBI_SLICED_VBI_625 0x20000000 /** * No actual data service. This symbol is used to request capturing * of all NTSC VBI data lines from the libzvbi driver interface, * as opposed to just those lines used to transmit the requested * data services. */ #define VBI_SLICED_VBI_525 0x40000000 /** @} */ typedef unsigned int vbi_service_set; /** * @ingroup Sliced * @brief This structure holds one scan line of sliced vbi data. * * For example the contents of NTSC line 21, two bytes of Closed Caption * data. Usually an array of vbi_sliced is used, covering all * VBI lines of the two fields of a video frame. */ typedef struct { /** * A @ref VBI_SLICED_ symbol identifying the data service. Under cirumstances * (see VBI_SLICED_TELETEXT_B) this can be a set of VBI_SLICED_ symbols. */ uint32_t id; /** * Source line number according to the ITU-R line numbering scheme, * a value of @c 0 if the exact line number is unknown. Note that some * data services cannot be reliable decoded without line number. * * @image html zvbi_625.gif "ITU-R PAL/SECAM line numbering scheme" * @image html zvbi_525.gif "ITU-R NTSC line numbering scheme" */ uint32_t line; /** * The actual payload. See the documentation of @ref VBI_SLICED_ symbols * for details. */ uint8_t data[56]; } vbi_sliced; /** * @addtogroup Sliced * @{ */ extern const char * vbi_sliced_name (vbi_service_set service) _vbi_const; extern unsigned int vbi_sliced_payload_bits (vbi_service_set service) _vbi_const; /** @} */ /* Private */ #ifdef __cplusplus } #endif #endif /* SLICED_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/sliced_filter.c000066400000000000000000000363331476363111200163320ustar00rootroot00000000000000/* * libzvbi -- Sliced VBI data filter * * Copyright (C) 2006, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: sliced_filter.c,v 1.6 2008-02-19 00:35:22 mschimek Exp $ */ /* XXX UNTESTED */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "misc.h" #include "version.h" #include "hamm.h" /* vbi_unham16p() */ #include "event.h" /* VBI_SERIAL */ #include "sliced_filter.h" #include "page_table.h" #ifndef VBI_SERIAL # define VBI_SERIAL 0x100000 #endif /* XXX Later. */ enum { VBI_ERR_INVALID_PGNO = 0, VBI_ERR_INVALID_SUBNO = 0, VBI_ERR_BUFFER_OVERFLOW = 0, VBI_ERR_PARITY = 0, }; /* XXX Later. */ #undef _ #define _(x) (x) /* 0 ... (VBI_ANY_SUBNO = 0x3F7F) - 1. */ #define MAX_SUBNO 0x3F7E struct _vbi_sliced_filter { vbi_page_table * keep_ttx_pages; vbi_bool keep_ttx_system_pages; vbi_sliced * output_buffer; unsigned int output_max_lines; unsigned int keep_mag_set_next; vbi_bool start; vbi_service_set keep_services; char * errstr; _vbi_log_hook log; vbi_sliced_filter_cb * callback; void * user_data; }; static void set_errstr (vbi_sliced_filter * sf, const char * templ, ...) { va_list ap; vbi_free (sf->errstr); sf->errstr = NULL; va_start (ap, templ); /* Error ignored. */ vasprintf (&sf->errstr, templ, ap); va_end (ap); } static void no_mem_error (vbi_sliced_filter * sf) { vbi_free (sf->errstr); /* Error ignored. */ sf->errstr = strdup (_("Out of memory.")); errno = ENOMEM; } #if 0 /* to do */ vbi_bool vbi_sliced_filter_drop_cc_channel (vbi_sliced_filter * sf, vbi_pgno channel) { } vbi_bool vbi_sliced_filter_keep_cc_channel (vbi_sliced_filter * sf, vbi_pgno channel) { } #endif void vbi_sliced_filter_keep_ttx_system_pages (vbi_sliced_filter * sf, vbi_bool keep) { assert (NULL != sf); sf->keep_ttx_system_pages = !!keep; } static __inline__ vbi_bool valid_ttx_page (vbi_pgno pgno) { return ((unsigned int) pgno - 0x100 < 0x800); } static vbi_bool valid_ttx_subpage_range (vbi_sliced_filter * sf, vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) { if (unlikely (!valid_ttx_page (pgno))) { set_errstr (sf, _("Invalid Teletext page number %x."), pgno); errno = VBI_ERR_INVALID_PGNO; return FALSE; } if (likely ((unsigned int) first_subno <= MAX_SUBNO && (unsigned int) last_subno <= MAX_SUBNO)) return TRUE; if (first_subno == last_subno) { set_errstr (sf, _("Invalid Teletext subpage number %x."), first_subno); } else { set_errstr (sf, _("Invalid Teletext subpage range %x-%x."), first_subno, last_subno); } errno = VBI_ERR_INVALID_SUBNO; return FALSE; } vbi_bool vbi_sliced_filter_drop_ttx_subpages (vbi_sliced_filter * sf, vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) { assert (NULL != sf); if (VBI_ANY_SUBNO == first_subno && VBI_ANY_SUBNO == last_subno) return vbi_sliced_filter_drop_ttx_pages (sf, pgno, pgno); if (unlikely (!valid_ttx_subpage_range (sf, pgno, first_subno, last_subno))) return FALSE; if (sf->keep_services & VBI_SLICED_TELETEXT_B_625) { vbi_page_table_add_all_pages (sf->keep_ttx_pages); sf->keep_services &= ~VBI_SLICED_TELETEXT_B_625; } return vbi_page_table_remove_subpages (sf->keep_ttx_pages, pgno, first_subno, last_subno); } vbi_bool vbi_sliced_filter_keep_ttx_subpages (vbi_sliced_filter * sf, vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) { assert (NULL != sf); if (VBI_ANY_SUBNO == first_subno && VBI_ANY_SUBNO == last_subno) return vbi_sliced_filter_keep_ttx_pages (sf, pgno, pgno); if (unlikely (!valid_ttx_subpage_range (sf, pgno, first_subno, last_subno))) return FALSE; if (sf->keep_services & VBI_SLICED_TELETEXT_B_625) return TRUE; return vbi_page_table_add_subpages (sf->keep_ttx_pages, pgno, first_subno, last_subno); } static vbi_bool valid_ttx_page_range (vbi_sliced_filter * sf, vbi_pgno first_pgno, vbi_pgno last_pgno) { if (likely (valid_ttx_page (first_pgno) && valid_ttx_page (last_pgno))) return TRUE; if (first_pgno == last_pgno) { set_errstr (sf, _("Invalid Teletext page number %x."), first_pgno); } else { set_errstr (sf, _("Invalid Teletext page range %x-%x."), first_pgno, last_pgno); } errno = VBI_ERR_INVALID_PGNO; return FALSE; } vbi_bool vbi_sliced_filter_drop_ttx_pages (vbi_sliced_filter * sf, vbi_pgno first_pgno, vbi_pgno last_pgno) { assert (NULL != sf); if (unlikely (!valid_ttx_page_range (sf, first_pgno, last_pgno))) return FALSE; if (sf->keep_services & VBI_SLICED_TELETEXT_B_625) { vbi_page_table_add_all_pages (sf->keep_ttx_pages); sf->keep_services &= ~VBI_SLICED_TELETEXT_B_625; } return vbi_page_table_remove_pages (sf->keep_ttx_pages, first_pgno, last_pgno); } vbi_bool vbi_sliced_filter_keep_ttx_pages (vbi_sliced_filter * sf, vbi_pgno first_pgno, vbi_pgno last_pgno) { assert (NULL != sf); if (unlikely (!valid_ttx_page_range (sf, first_pgno, last_pgno))) return FALSE; if (sf->keep_services & VBI_SLICED_TELETEXT_B_625) return TRUE; return vbi_page_table_add_pages (sf->keep_ttx_pages, first_pgno, last_pgno); } vbi_service_set vbi_sliced_filter_drop_services (vbi_sliced_filter * sf, vbi_service_set services) { assert (NULL != sf); if (services & VBI_SLICED_TELETEXT_B_625) vbi_page_table_remove_all_pages (sf->keep_ttx_pages); return sf->keep_services &= ~services; } vbi_service_set vbi_sliced_filter_keep_services (vbi_sliced_filter * sf, vbi_service_set services) { assert (NULL != sf); if (services & VBI_SLICED_TELETEXT_B_625) vbi_page_table_remove_all_pages (sf->keep_ttx_pages); return sf->keep_services |= services; } void vbi_sliced_filter_reset (vbi_sliced_filter * sf) { assert (NULL != sf); sf->keep_mag_set_next = 0; sf->start = TRUE; } static vbi_bool decode_teletext_packet_0 (vbi_sliced_filter * sf, unsigned int * keep_mag_set, const uint8_t buffer[42], unsigned int magazine) { int page; int flags; vbi_pgno pgno; unsigned int mag_set; page = vbi_unham16p (buffer + 2); if (unlikely (page < 0)) { set_errstr (sf, _("Hamming error in Teletext " "page number.")); errno = VBI_ERR_PARITY; return FALSE; } if (0xFF == page) { /* Filler, discard. */ *keep_mag_set = 0; return TRUE; } pgno = magazine * 0x100 + page; flags = vbi_unham16p (buffer + 4) | (vbi_unham16p (buffer + 6) << 8) | (vbi_unham16p (buffer + 8) << 16); if (unlikely (flags < 0)) { set_errstr (sf, _("Hamming error in Teletext " "packet flags.")); errno = VBI_ERR_PARITY; return FALSE; } /* Blank lines are not transmitted and there's no page end mark, so Teletext decoders wait for another page before displaying the previous one. In serial transmission mode that is any page, in parallel mode a page of the same magazine. */ if (flags & VBI_SERIAL) { mag_set = -1; } else { mag_set = 1 << magazine; } if (!vbi_is_bcd (pgno)) { /* Page inventories and TOP pages (e.g. to find subtitles), DRCS and object pages, etc. */ if (sf->keep_ttx_system_pages) goto match; } else { vbi_subno subno; subno = flags & 0x3F7F; if (vbi_page_table_contains_subpage (sf->keep_ttx_pages, pgno, subno)) goto match; } if (*keep_mag_set & mag_set) { /* To terminate the previous page we keep the header packet of this page (keep_mag_set) but discard all following packets (keep_mag_set_next). */ sf->keep_mag_set_next = *keep_mag_set & ~mag_set; } else if (sf->start) { /* Keep the very first page header and its timestamp, which is important for subtitle timing. */ *keep_mag_set = mag_set; sf->keep_mag_set_next = 0; } else { /* Discard this and following packets until we find another header packet. */ *keep_mag_set &= ~mag_set; sf->keep_mag_set_next = *keep_mag_set; } sf->start = FALSE; return TRUE; match: /* Keep this and following packets. */ *keep_mag_set |= mag_set; sf->keep_mag_set_next = *keep_mag_set; sf->start = FALSE; return TRUE; } static vbi_bool decode_teletext (vbi_sliced_filter * sf, vbi_bool * keep, const uint8_t buffer[42], unsigned int line) { int pmag; unsigned int magazine; unsigned int packet; unsigned int keep_mag_set; line = line; keep_mag_set = sf->keep_mag_set_next; pmag = vbi_unham16p (buffer); if (unlikely (pmag < 0)) { set_errstr (sf, _("Hamming error in Teletext " "packet/magazine number.")); errno = VBI_ERR_PARITY; return FALSE; } magazine = pmag & 7; if (0 == magazine) magazine = 8; packet = pmag >> 3; switch (packet) { case 0: /* page header */ if (!decode_teletext_packet_0 (sf, &keep_mag_set, buffer, magazine)) return FALSE; break; case 1 ... 25: /* page body */ break; case 26: /* page enhancement packet */ case 27: /* page linking */ case 28: case 29: /* level 2.5/3.5 enhancement */ break; case 30: case 31: /* IDL packet (ETS 300 708). */ *keep = FALSE; return TRUE; default: assert (0); } *keep = !!(keep_mag_set & (1 << magazine)); return TRUE; } /** * @brief Sliced VBI filter coroutine. * @param sf Sliced VBI filter context allocated with * vbi_sliced_filter_new(). * @param sliced_out Filtered sliced data will be stored here. * @a sliced_out and @a sliced_in can be the same. * @param n_lines_out The number of sliced lines in the * @a sliced_out buffer will be stored here. * @param max_lines_out The maximum number of sliced lines this * function may store in the @a sliced_out buffer. * @param sliced_in The sliced data to be filtered. * @param n_lines_in Pointer to a variable which contains the * number of sliced lines to be read from the @a sliced_in buffer. * When the function fails, it stores here the number of sliced * lines successfully read so far. * * This function takes one video frame worth of sliced VBI data and * filters out the lines which match the selected criteria. * * @returns * @c TRUE on success. @c FALSE if there is not enough room in the * output buffer to store the filtered data, or when the function * detects an error in the sliced input data. On failure the * @a sliced_out buffer will contain the data successfully filtered * so far, @a *n_lines_out will be valid, and @a *n_lines_in will * contain the number of lines read so far. * * @since 99.99.99 */ vbi_bool vbi_sliced_filter_cor (vbi_sliced_filter * sf, vbi_sliced * sliced_out, unsigned int * n_lines_out, unsigned int max_lines_out, const vbi_sliced * sliced_in, unsigned int * n_lines_in) { unsigned int in; unsigned int out; assert (NULL != sf); assert (NULL != sliced_out); assert (NULL != n_lines_out); assert (NULL != sliced_in); errno = 0; out = 0; for (in = 0; in < *n_lines_in; ++in) { vbi_bool pass_through; pass_through = FALSE; if (sliced_in[in].id & sf->keep_services) { pass_through = TRUE; } else { switch (sliced_in[in].id) { case VBI_SLICED_TELETEXT_B_L10_625: case VBI_SLICED_TELETEXT_B_L25_625: case VBI_SLICED_TELETEXT_B_625: if (!decode_teletext (sf, &pass_through, sliced_in[in].data, sliced_in[in].line)) goto failed; break; default: break; } } if (pass_through) { if (out >= max_lines_out) { set_errstr (sf, _("Output buffer overflow.")); errno = VBI_ERR_BUFFER_OVERFLOW; goto failed; } memcpy (&sliced_out[out], &sliced_in[in], sizeof (*sliced_out)); ++out; } } *n_lines_out = out; return TRUE; failed: *n_lines_in = in; *n_lines_out = out; return FALSE; } /** * @brief Feeds the sliced VBI filter with data. * @param sf Sliced VBI filter context allocated with * vbi_sliced_filter_new(). * @param sliced The sliced data to be filtered. * @param n_lines Pointer to a variable which contains the * number of sliced lines to be read from the @a sliced buffer. * When the function fails, it stores here the number of sliced * lines successfully read so far. * * This function takes one video frame worth of sliced VBI data and * filters out the lines which match the selected criteria. Then if * no error occurred it calls the callback function passed to * vbi_sliced_filter_new() with a pointer to the filtered lines. * * @returns * @c TRUE on success. @c FALSE if the function detects an error in * the sliced input data, and @a *n_lines_in will contain the lines * successfully read so far. * * @since 99.99.99 */ vbi_bool vbi_sliced_filter_feed (vbi_sliced_filter * sf, const vbi_sliced * sliced, unsigned int * n_lines) { unsigned int n_lines_out; assert (NULL != sf); assert (NULL != sliced); assert (NULL != n_lines); assert (*n_lines <= UINT_MAX / sizeof (*sf->output_buffer)); if (unlikely (sf->output_max_lines < *n_lines)) { vbi_sliced *s; unsigned int n; n = MIN (*n_lines, 50U); s = vbi_realloc (sf->output_buffer, n * sizeof (*sf->output_buffer)); if (unlikely (NULL == s)) { no_mem_error (sf); return FALSE; } sf->output_buffer = s; sf->output_max_lines = n; } if (!vbi_sliced_filter_cor (sf, sf->output_buffer, &n_lines_out, sf->output_max_lines, sliced, n_lines)) { return FALSE; } if (NULL != sf->callback) { return sf->callback (sf, sf->output_buffer, n_lines_out, sf->user_data); } return TRUE; } const char * vbi_sliced_filter_errstr (vbi_sliced_filter * sf) { assert (NULL != sf); return sf->errstr; } void vbi_sliced_filter_set_log_fn (vbi_sliced_filter * sf, vbi_log_mask mask, vbi_log_fn * log_fn, void * user_data) { assert (NULL != sf); if (NULL == log_fn) mask = 0; sf->log.mask = mask; sf->log.fn = log_fn; sf->log.user_data = user_data; } void vbi_sliced_filter_delete (vbi_sliced_filter * sf) { if (NULL == sf) return; vbi_page_table_delete (sf->keep_ttx_pages); vbi_free (sf->output_buffer); vbi_free (sf->errstr); CLEAR (*sf); vbi_free (sf); } vbi_sliced_filter * vbi_sliced_filter_new (vbi_sliced_filter_cb *callback, void * user_data) { vbi_sliced_filter *sf; sf = vbi_malloc (sizeof (*sf)); if (NULL == sf) { return NULL; } CLEAR (*sf); sf->keep_ttx_pages = vbi_page_table_new (); if (NULL == sf->keep_ttx_pages) { vbi_free (sf); return NULL; } vbi_sliced_filter_reset (sf); sf->callback = callback; sf->user_data = user_data; return sf; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/sliced_filter.h000066400000000000000000000101441476363111200163270ustar00rootroot00000000000000/* * libzvbi -- Sliced VBI data filter * * Copyright (C) 2006, 2007 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: sliced_filter.h,v 1.5 2008-02-24 14:16:58 mschimek Exp $ */ #ifndef __ZVBI_SLICED_FILTER_H__ #define __ZVBI_SLICED_FILTER_H__ #include "macros.h" #include "bcd.h" #include "sliced.h" /* vbi_sliced, vbi_service_set */ VBI_BEGIN_DECLS typedef struct _vbi_sliced_filter vbi_sliced_filter; typedef vbi_bool vbi_sliced_filter_cb (vbi_sliced_filter * sf, const vbi_sliced * sliced, unsigned int n_lines, void * user_data); extern vbi_service_set vbi_sliced_filter_keep_services (vbi_sliced_filter * sf, vbi_service_set services) _vbi_nonnull ((1)); extern vbi_service_set vbi_sliced_filter_drop_services (vbi_sliced_filter * sf, vbi_service_set services) _vbi_nonnull ((1)); extern vbi_bool vbi_sliced_filter_keep_ttx_pages (vbi_sliced_filter * sf, vbi_pgno first_pgno, vbi_pgno last_pgno) _vbi_nonnull ((1)); extern vbi_bool vbi_sliced_filter_drop_ttx_pages (vbi_sliced_filter * sf, vbi_pgno first_pgno, vbi_pgno last_pgno) _vbi_nonnull ((1)); static __inline__ vbi_bool vbi_sliced_filter_keep_ttx_page (vbi_sliced_filter * sf, vbi_pgno pgno) { return vbi_sliced_filter_keep_ttx_pages (sf, pgno, pgno); } static __inline__ vbi_bool vbi_sliced_filter_drop_ttx_page (vbi_sliced_filter * sf, vbi_pgno pgno) { return vbi_sliced_filter_drop_ttx_pages (sf, pgno, pgno); } extern vbi_bool vbi_sliced_filter_keep_ttx_subpages (vbi_sliced_filter * sf, vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) _vbi_nonnull ((1)); extern vbi_bool vbi_sliced_filter_drop_ttx_subpages (vbi_sliced_filter * sf, vbi_pgno pgno, vbi_subno first_subno, vbi_subno last_subno) _vbi_nonnull ((1)); static __inline__ vbi_bool vbi_sliced_filter_keep_ttx_subpage (vbi_sliced_filter * sf, vbi_pgno pgno, vbi_subno subno) { return vbi_sliced_filter_keep_ttx_subpages (sf, pgno, subno, subno); } static __inline__ vbi_bool vbi_sliced_filter_drop_ttx_subpage (vbi_sliced_filter * sf, vbi_pgno pgno, vbi_subno subno) { return vbi_sliced_filter_drop_ttx_subpages (sf, pgno, subno, subno); } extern void vbi_sliced_filter_keep_ttx_system_pages (vbi_sliced_filter * sf, vbi_bool keep) _vbi_nonnull ((1)); extern void vbi_sliced_filter_reset (vbi_sliced_filter * sf) _vbi_nonnull ((1)); extern vbi_bool vbi_sliced_filter_cor (vbi_sliced_filter * sf, vbi_sliced * sliced_out, unsigned int * n_lines_out, unsigned int max_lines_out, const vbi_sliced * sliced_in, unsigned int * n_lines_in) _vbi_nonnull ((1, 2, 3, 5, 6)); extern vbi_bool vbi_sliced_filter_feed (vbi_sliced_filter * sf, const vbi_sliced * sliced, unsigned int * n_lines) _vbi_nonnull ((1, 2, 3)); extern const char * vbi_sliced_filter_errstr (vbi_sliced_filter * sf) _vbi_nonnull ((1)); extern void vbi_sliced_filter_set_log_fn (vbi_sliced_filter * sf, vbi_log_mask mask, vbi_log_fn * log_fn, void * user_data) _vbi_nonnull ((1)); extern void vbi_sliced_filter_delete (vbi_sliced_filter * sf); extern vbi_sliced_filter * vbi_sliced_filter_new (vbi_sliced_filter_cb *callback, void * user_data) _vbi_alloc; VBI_END_DECLS #endif /* __ZVBI_SLICED_FILTER_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/strptime.c000066400000000000000000000076351476363111200153740ustar00rootroot00000000000000/* * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * FFmpeg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "strptime.h" // Implementation of small strptime from FFmpeg // https://github.com/FFmpeg/FFmpeg/blob/64634e809f2e7b6c1a1ea3f6952a17c5915c3f22/libavutil/parseutils.c#L491 static const char *months[12] = { "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december" }; static int date_get_num(const char **pp, int n_min, int n_max, int len_max) { int i, val, c; const char *p; p = *pp; val = 0; for(i = 0; i < len_max; i++) { c = *p; if (!isdigit(c)) break; val = (val * 10) + c - '0'; p++; } /* no number read ? */ if (p == *pp) return -1; if (val < n_min || val > n_max) return -1; *pp = p; return val; } static int date_get_month(const char **pp) { int i = 0; for (; i < 12; i++) { if (!strncasecmp(*pp, months[i], 3)) { const char *mo_full = months[i] + 3; int len = strlen(mo_full); *pp += 3; if (len > 0 && !strncasecmp(*pp, mo_full, len)) *pp += len; return i; } } return -1; } char *strptime(const char *p, const char *fmt, struct tm *dt) { int c, val; while((c = *fmt++)) { if (c != '%') { if (isspace(c)) for (; *p && isspace(*p); p++); else if (*p != c) return NULL; else p++; continue; } c = *fmt++; switch(c) { case 'H': case 'J': val = date_get_num(&p, 0, c == 'H' ? 23 : INT_MAX, c == 'H' ? 2 : 4); if (val == -1) return NULL; dt->tm_hour = val; break; case 'M': val = date_get_num(&p, 0, 59, 2); if (val == -1) return NULL; dt->tm_min = val; break; case 'S': val = date_get_num(&p, 0, 59, 2); if (val == -1) return NULL; dt->tm_sec = val; break; case 'Y': val = date_get_num(&p, 0, 9999, 4); if (val == -1) return NULL; dt->tm_year = val - 1900; break; case 'm': val = date_get_num(&p, 1, 12, 2); if (val == -1) return NULL; dt->tm_mon = val - 1; break; case 'd': val = date_get_num(&p, 1, 31, 2); if (val == -1) return NULL; dt->tm_mday = val; break; case 'T': p = strptime(p, "%H:%M:%S", dt); if (!p) return NULL; break; case 'b': case 'B': case 'h': val = date_get_month(&p); if (val == -1) return NULL; dt->tm_mon = val; break; case '%': if (*p++ != '%') return NULL; break; default: return NULL; } } return (char*)p; } zvbi-0.2.44/src/strptime.h000066400000000000000000000045421476363111200153730ustar00rootroot00000000000000/* * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * FFmpeg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #ifndef STRPTIME_H #define STRPTIME_H #ifdef __cplusplus extern "C" { #endif /** * Simplified version of strptime * * Parse the input string p according to the format string fmt and * store its results in the structure dt. * This implementation supports only a subset of the formats supported * by the standard strptime(). * * The supported input field descriptors are listed below. * - `%%H`: the hour as a decimal number, using a 24-hour clock, in the * range '00' through '23' * - `%%J`: hours as a decimal number, in the range '0' through INT_MAX * - `%%M`: the minute as a decimal number, using a 24-hour clock, in the * range '00' through '59' * - `%%S`: the second as a decimal number, using a 24-hour clock, in the * range '00' through '59' * - `%%Y`: the year as a decimal number, using the Gregorian calendar * - `%%m`: the month as a decimal number, in the range '1' through '12' * - `%%d`: the day of the month as a decimal number, in the range '1' * through '31' * - `%%T`: alias for `%%H:%%M:%%S` * - `%%`: a literal `%` * * @return a pointer to the first character not processed in this function * call. In case the input string contains more characters than * required by the format string the return value points right after * the last consumed input character. In case the whole input string * is consumed the return value points to the null byte at the end of * the string. On failure NULL is returned. */ char *strptime(const char *p, const char *fmt, struct tm *dt); #ifdef __cplusplus } #endif #endif /* STRPTIME_H */ zvbi-0.2.44/src/structpr.pl000077500000000000000000000432071476363111200156020ustar00rootroot00000000000000#!/usr/bin/perl # # Copyright (C) 2002-2004 Michael H. Schimek # inspired by a LXR script http://lxr.linux.no/ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # -------------------------------------------------------------------------- # # This script turns a C header file into functions printing # and checking ioctl arguments. It's part of the debugging # routines of the Zapping tv viewer http://zapping.sf.net. # # Perl and C gurus cover your eyes. This is one of my first # attempts in this funny tongue and far from a proper C parser. # $Id: structpr.pl,v 1.7 2007-11-27 17:55:04 mschimek Exp $ $number = '[0-9]+'; $ident = '\~?_*[a-zA-Z][a-zA-Z0-9_]*'; $signed = '((signed)?(char|short|int|long))|__s8|__s16|__s32|__s64|signed'; $unsigned = '(((unsigned\s*)|u|u_)(char|short|int|long))|__u8|__u16|__u32|__u64|unsigned'; $define = '^\s*\#\s*define\s+'; $printfn = 'fprint_ioctl_arg'; # # Syntax of arguments, in brief: # # "struct" is the name of a structure. "\.field" is the name of a field # of this structure, can be "(\.substruct)*\.field" too. # # struct.field=SYM_ # struct.field contains symbolic values starting with SYM_. Only needed # for flags, automatically determined if struct.field is an enum type. # FIXME we must permit more than one prefix. # struct.field=string|hex|fourcc # Print that field appropriately. If not given the script tries to # guess from the field name. # typedef=blah # As above, for simple typedef'ed types. # struct=mode # struct.substruct=mode # struct.field=mode # If ioctl is WR, this is an R (input) or W (output parameter) # or WR (both). If ioctl is R or W, all parameters are input or output # respectively. # struct.field=FOO:foo # Only when struct.field == FOO, print member foo. # struct.field=R,SYM_, SYM_FOO:foo # Combines the hints above. # struct={ fprintf(fp, "<$s>", t->foo); } # Print like this. # while (@ARGV) { $arg = shift (@ARGV); while ("," eq substr ($arg, -1) && @ARGV) { $arg .= shift (@ARGV); } if ($arg =~ m/printfn\=($ident)/) { $printfn = $1; } elsif ($arg =~ m/(($ident)(\.$ident)?)\={(.*)}/) { $print_func{$1} = $4; } elsif ($arg =~ m/(($ident)(\.($ident))?)\=(.*)/) { $item = $1; $container = $2; $member = $4; foreach (split (',', $5)) { if ($_ =~ m/($ident):(($ident)\.($ident))\s*/) { # print "$member == $1 -> $container.$2\n"; $selector{"$container.$2"} = { key => $member, symbol => $1 }; } elsif ($_ eq "WR" || $_ eq "R" || $_ eq "W") { $mode_hint{$item} = $_; } else { $symbolic{$item} = $_; } } } else { print "$arg ??\n"; exit 1; } } $_ = $/; undef($/); $contents = <>; $/ = $_; # # Step I - comb the source and filter out #defines # sub wash { my $t = $_[0]; $t =~ s/[^\n]+//gs; return ($t); } # Remove comments. $contents =~ s/\/\*(.*?)\*\//&wash($1)/ges; $contents =~ s/\/\/[^\n]*//g; # C++ # Unwrap continuation lines. $contents =~ s/\\\s*\n/$1\05/gs; while ($contents =~ s/\05([^\n\05]+)\05/$1\05\05/gs) {} $contents =~ s/(\05+)([^\n]*)/"$2"."\n" x length($1)/ges; sub add_ioctl_check { my ($name, $dir, $type) = @_; $ioctl_check .= "static __inline__ void IOCTL_ARG_TYPE_CHECK_$name "; if ($dir eq "W") { $ioctl_check .= "(const $type *arg __attribute__ ((unused))) {}\n"; } else { $ioctl_check .= "($type *arg __attribute__ ((unused))) {}\n"; } } sub add_ioctl { my ($name, $dir, $i_type, $real_type) = @_; $ioctl_cases{$i_type} .= "case $name:\n" . "if (!arg) { fputs (\"$name\", fp); return; }\n"; &add_ioctl_check ($name, $dir, $real_type); } # Find macro definitions, create ioctl & symbol table. $t = ""; $skip = 0; foreach ($contents =~ /^(.*)/gm) { if ($skip) { if (/^\s*#\s*endif/) { $skip = 0; } next; } # #if 0 if (/^\s*#\s*if\s+0/) { $skip = 1; # Ioctls } elsif (/$define($ident)\s+_IO(WR|R|W).*\(.*,\s*$number\s*,\s*(struct|union)\s*($ident)\s*\)\s*$/) { &add_ioctl ($1, $2, "$3 $4", "$3 $4"); } elsif (/$define($ident)\s+_IO(WR|R|W).*\(.*,\s*$number\s*,\s*(($signed)|($unsigned))\s*\)\s*$/) { if ($symbolic{$1}) { $int_ioctls{$1} = $3; &add_ioctl ($1, $2, $1, $3); } else { &add_ioctl ($1, $2, $3, $3); } } elsif (/$define($ident)\s+_IO(WR|R|W).*\(.*,\s*$number\s*,\s*($ident)\s*\)\s*$/) { &add_ioctl ($1, $2, $3, $3); } elsif (/$define($ident)\s+_IO(WR|R|W).*\(.*,\s*$number\s*,\s*([^*]+)\s*\)\s*$/) { &add_ioctl_check ($1, $2, $3); # Define } elsif (/$define($ident)/) { push @global_symbols, $1; # Other text } elsif (!/^\s*\#/) { $_ =~ s/\s+/ /g; $t="$t$_ "; } } # Split field lists: struct { ... } foo, bar; int x, y; $t =~ s/({|;)\s*((struct\s*{[^}]*})\s*($ident))\s*,/\1 \2; \3 /gm; $t =~ s/({|;)\s*(([^,;}]*)\s+($ident))\s*,/\1 \2; \3 /gm; # Function pointers are just pointers. $t =~ s/\(\s*\*\s*($ident)\s*\)\s*\([^)]*\)\s*;/void *\1;/gm; # Split after ,;{ $t =~ s/(,|;|{)/\1\n/gm; @contents = split ('\n', $t); # # Step II - parse structs, unions and enums # # fieldn = structname\.(field1\.)*fieldn sub field { my ($item) = @_; $item =~ s/^($ident\.)*//; return $item; } # (field1\.)*fieldn = structname\.(field1\.)*fieldn sub trail { my ($item) = @_; $item =~ s/^$ident\.//; return $item; } sub test_cond { my ($text, $item) = @_; my ($mode, $key, $sym, $sel, $i); $mode = "WR"; $i = "$item.dummy"; while ($i =~ s/\.$ident$//) { if ($mode_hint{$i}) { $mode = $mode_hint{$i}; last; } } $key = "0"; $sym = "0"; if ($selector{$item}) { $key = $selector{$item}->{key}; $sym = $selector{$item}->{symbol}; $sel = "$sym == t->$key"; } # print "test_cond $item: $mode $key $sym (was $last_cond)\n"; if ($last_cond ne "$mode $key $sym") { $$text .= &flush_args; if ($last_cond ne "WR 0 0") { $$text .= "}\n"; } if ("R" eq $mode) { if ($selector{$item}) { $$text .= "if ((1 & rw) && $sel) {\n"; } else { $$text .= "if (1 & rw) {\n"; } } elsif ("W" eq $mode) { if ($selector{$item}) { $$text .= "if ((2 & rw) && $sel) {\n"; } else { $$text .= "if (2 & rw) {\n"; } } elsif ($selector{$item}) { $$text .= "if ($sel) {\n"; } $last_cond = "$mode $key $sym"; } } # Build a fprintf() with $templ and $args. &flush_args finalizes # the function. # text .= "unsigned int", "structname.field1.flags", "%x" sub add_arg { my ($text, $type, $item, $template) = @_; my $flush = 0; $templ .= &field ($item) . "=$template "; $args .= "($type) t->" . &trail ($item) . ", "; } # text .= "unsigned int", "structname.field1.flags", "%x" sub add_ref_arg { my ($text, $type, $item, $template) = @_; my $flush = 0; $templ .= &field ($item) . "=$template "; $args .= "($type) & t->" . &trail ($item) . ", "; } # text .= functions this depends upon, "struct foo", "structname.field1.foo" sub add_arg_func { my ($text, $deps, $type, $item) = @_; my $flush = 0; if ($funcs{$type}) { my ($lp, $rp, $ref); if ($type =~ m/^(struct|union)/) { $lp = "{"; $rp = "}"; $ref = "&"; } else { $lp = ""; $rp = ""; $ref = ""; } push @$deps, $type; $type =~ s/ /_/g; $templ .= &field ($item) . "=$lp"; $$text .= &flush_args; &test_cond ($text, $item); $$text .= "fprint_$type (fp, rw, " . $ref . "t->" . &trail ($item) . ");\n"; $templ .= "$rp "; } else { &test_cond ($text, $item); $templ .= &field ($item) . "=? "; } } # text .= functions this depends upon, # enum mode (see fprint_symbolic()), # "FLAG_", "structname.field1.flags" sub add_symbolic { my ($text, $deps, $enum_mode, $prefix, $item) = @_; my ($sbody, $count); $count = 0; foreach (@global_symbols) { if (/^$prefix/) { $str = $_; $str =~ s/^$prefix//; $sbody .= "\"$str\", (unsigned long) $_,\n"; ++$count; } } $prefix = lc $prefix; if ($count > 3) { my $type = "symbol $prefix"; # No switch() such that fprint_symbolic() can determine if # these are flags or enum. $funcs{$type} = { text => "static void\n" . "fprint_symbol_$prefix (FILE *fp, " . "int rw __attribute__ ((unused)), unsigned long value)\n" . "{\nfprint_symbolic (fp, $enum_mode, value,\n" . $sbody . "(void *) 0);\n}\n\n", deps => [] }; &add_arg_func ($text, $deps, $type, $item); } else { # Inline symbolic $templ .= &field ($item) . "="; $$text .= &flush_args; &test_cond ($text, $item); $templ .= " "; $$text .= "fprint_symbolic (fp, $enum_mode, t->" . &trail ($item) . ",\n" . $sbody . "(void *) 0);\n"; } } sub flush_args { my $text; $templ =~ s/^ (\"\n\")/ /; $templ =~ s/(\"\n\")$//; $args =~ s/^(\s|\n)+//; $args =~ s/,?(\s|\n)+$//; $text = ""; if ($templ) { if ($args) { $text .= "fprintf (fp, \"$templ\",\n$args);\n"; } else { $text .= "fputs (\"$templ\", fp);\n"; } } $templ = ""; $args = ""; # print "flush >>$text<<\n"; return $text; } # text .= functions this depends upon, # "struct", "v4l_foo", "WR", 0 # (name can be structname(\.field)+ if nested inline struct or union) sub aggregate_body { my ($text, $deps, $kind, $name, $skip) = @_; if ($name ne "?" && $print_func{$name}) { $$text .= $print_func{$name} . "\n"; $skip = 1; } while (@contents) { $_ = shift (@contents); # print "<<$name<<$_<<\n"; # End of aggregate if (/^\s*}\s*;/) { $$text .= &flush_args; return ""; # End of substruct or union } if (/^\s*}\s*($ident)\s*;/) { $$text .= &flush_args; return $1; # Enum. } elsif (/^\s*enum\s+($ident)\s+($ident);/) { if (!$skip) { &test_cond ($text, "$name.$2"); &add_arg_func ($text, $deps, "enum $1", "$name.$2"); } # Substruct or union. } elsif (/^\s*(struct|union)\s+($ident)\s+($ident);/) { if (!$skip) { &test_cond ($text, "$name.$3"); &add_arg_func ($text, $deps, "$1 $2", "$name.$3"); } # Substruct or union inline definition w/o declaration # Why don't you just shoot me... } elsif (/^\s*(struct|union)\s+{/) { my $kind = $1; my ($field, $subtext, @temp); $$text .= &flush_args; $subtext = ""; @temp = @contents; # skip to determine field name $field = &aggregate_body (\$subtext, $deps, $kind, "?", 1); if ($skip) { next; } if ($field ne "") { $subtext = ""; @contents = @temp; &test_cond ($text, "$name.$field"); $templ .= "$field={"; &aggregate_body (\$subtext, $deps, $kind, "$name.$field", 0); $$text .= &flush_args . $subtext; &test_cond ($text, "$name.$field"); $templ .= "} "; } else { $templ .= "? "; } # Other stuff, simplified } elsif (/^\s*($ident(\s+$ident)*)(\*|\s)+($ident)\s*(\[([a-zA-Z0-9_]+)\]*\s*)?;/) { my $type = $1; my $ptr = $3; my $field = $4; my $size = $6; my $hint = ""; my $item = "$name.$field"; if ($typedefs{$type}) { $hint = $symbolic{$type}; $type = $typedefs{$type}; } elsif ($symbolic{$item}) { $hint = $symbolic{$item}; } # print "$type $ptr $name.$field [$size] $hint\n"; if ($skip) { next; } &test_cond ($text, $item); if (0) { # Wisdom: a reserved fieldcontains nothing useful. } elsif ($field =~ "^reserved.*") { if ($size ne "") { $templ .= "$field\[\] "; } else { $templ .= "$field "; } # Pointer } elsif ($ptr eq "*") { # Array of pointers? if ($size ne "") { # Not smart enough, ignore $templ .= "$field\[\]=? "; # Wisdom: char pointer is probably a string. } elsif ($type eq "char" || $field eq "name" || $hint eq "string") { &add_arg ($text, "const char *", $item, "\\\"%s\\\""); # Other pointer } else { &add_arg ($text, "const void *", $item, "%p"); } # Array of something } elsif ($size ne "") { # Wisdom: a char array contains a string. # "Names" are also commonly strings. if ($type eq "char" || $field eq "name" || $hint eq "string") { $args .= "$size, "; &add_arg ($text, "const char *", $item, "\\\"%.*s\\\""); # So this is some other kind of array, what now? } else { # ignore $templ .= "$field\[\]=? "; } # Wisdom: a field named flags typically contains flags. } elsif ($field eq "flags") { if ($hint ne "") { &add_symbolic ($text, $deps, 2, $hint, $item); } else { # flags in hex &add_arg ($text, "unsigned long", $item, "0x%lx"); } # Hint: something funny } elsif ($hint eq "hex") { &add_arg ($text, "unsigned long", $item, "0x%lx"); } elsif ($hint eq "fourcc") { &add_ref_arg ($text, "const char *", $item, "\\\"%.4s\\\"=0x%lx"); $args .= "(unsigned long) t->$field, "; # Field contains symbols, could be flags or enum or both } elsif ($hint ne "") { &add_symbolic ($text, $deps, 0, $hint, $item); # Miscellaneous integers. Suffice to distinguish signed and # unsigned, compiler will convert to long automatically } elsif ($type =~ m/$unsigned/) { &add_arg ($text, "unsigned long", $item, "%lu"); } elsif ($type =~ m/$signed/) { &add_arg ($text, "long", $item, "%ld"); # The Spanish Inquisition. } else { $templ .= "$field=? "; } $templ .= "\"\n\""; $args .= "\n"; } } } sub aggregate { my ($kind, $name) = @_; my ($text, @deps); my $type = "$kind $name"; $funcs{$type} = { text => "static void\nfprint_$kind\_$name " . "(FILE *fp, int rw __attribute__ ((unused)), const $type *t)\n{\n", deps => [] }; $last_cond = "WR 0 0"; aggregate_body (\$funcs{$type}->{text}, $funcs{$type}->{deps}, $kind, $name, 0); if ($last_cond ne "WR 0 0") { $funcs{$type}->{text} .= "}\n"; } $funcs{$type}->{text} .= "}\n\n"; } sub common_prefix { my $prefix = @_[0]; my $symbol; foreach $symbol (@_) { while (length ($prefix) > 0) { if (index ($symbol, $prefix) == 0) { last; } else { $prefix = substr ($prefix, 0, -1); } } } return ($prefix); } sub enumeration { my $name = @_[0]; my $type = "enum $name"; my @symbols; $funcs{$type} = { text => "static void\nfprint_enum_$name (FILE *fp, " . "int rw __attribute__ ((unused)), int value)\n" . "{\nfprint_symbolic (fp, 1, value,\n", deps => [] }; while (@contents) { $_ = shift(@contents); if (/^\s*\}\s*;/) { last; } elsif (/^\s*($ident)\s*(=\s*.*)\,/) { push @symbols, $1; } } $prefix = &common_prefix (@symbols); foreach $symbol (@symbols) { $funcs{$type}->{text} .= "\"" . substr ($symbol, length ($prefix)) . "\", (unsigned long) $symbol,\n"; } $funcs{$type}->{text} .= "(void *) 0);\n}\n\n"; } # Let's parse while (@contents) { $_ = shift(@contents); # print ">>$_<<\n"; if (/^\s*(struct|union)\s*($ident)\s*\{/) { &aggregate ($1, $2); } elsif (/^\s*enum\s*($ident)\s*\{/) { &enumeration ($1); } elsif (/^\s*typedef\s*([^;]+)\s+($ident)\s*;/) { $typedefs{$2} = $1; } } # # Step III - create the file # print "/* Generated file, do not edit! */ #include #include \"io.h\" #ifndef __GNUC__ #undef __attribute__ #define __attribute__(x) #endif "; while (($name, $type) = each %int_ioctls) { my $prefix; my $sbody; $prefix = $symbolic{$name}; foreach (@global_symbols) { if (/^$prefix/) { $str = $_; $str =~ s/^$prefix//; $sbody .= "\"$str\", (unsigned long) $_,\n"; } } # No switch() such that fprint_symbolic() can determine if # these are flags or enum. $funcs{$name} = { text => "static void\n" . "fprint_$name (FILE *fp, " . "int rw __attribute__ ((unused)), $type *arg)\n" . "{\nfprint_symbolic (fp, 0, (unsigned long) *arg,\n" . $sbody . "(void *) 0);\n}\n\n", deps => [] }; } sub print_type { my ($type) = @_; if (!$printed{$type}) { foreach $dependency (@{$funcs{$type}->{deps}}) { &print_type ($dependency); } print $funcs{$type}->{text}; $printed{$type} = TRUE; } } $text = "static void\n$printfn (FILE *fp, unsigned int cmd, int rw, void *arg)\n" . "{\nswitch (cmd) {\n"; while (($type, $case) = each %ioctl_cases) { if ($typedefs{$type}) { if ($symbolic{$type}) { &print_type ($type); $prefix = lc $symbolic{$type}; $type = $typedefs{$type}; $text .= "$case fprint_symbol_$prefix "; $text .= "(fp, rw, * ($type *) arg);\nbreak;\n"; next; } $type = $typedefs{$type}; } if ($funcs{$type}) { &print_type ($type); $type =~ s/ /_/; $text .= "$case fprint_$type (fp, rw, arg);\nbreak;\n"; } elsif ($type =~ m/$unsigned/) { $text .= "$case fprintf (fp, \"%lu\", " . "(unsigned long) * ($type *) arg);\nbreak;\n"; } elsif ($type =~ m/$signed/) { $text .= "$case fprintf (fp, \"%ld\", " . "(long) * ($type *) arg);\nbreak;\n"; } else { $text .= "$case break; /* $type */\n"; } } $text .= "\tdefault:\n" . "\t\tif (!arg) { fprint_unknown_ioctl (fp, cmd, arg); return; }\n" . "\t\tbreak;\n"; $text .= "\t}\n\}\n\n"; print $text; print $ioctl_check; print "\n"; zvbi-0.2.44/src/tables.c000066400000000000000000000176401476363111200147740ustar00rootroot00000000000000/* * libzvbi -- Tables * * PDC and VPS CNI codes rev. 5 from * TR 101 231 EBU (2004-04a): www.ebu.ch * Programme type tables PDC/EPG, XDS * * Copyright (C) 1999-2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: tables.c,v 1.11 2008-02-19 00:35:22 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "tables.h" /* * ISO 3166-1 country codes */ enum { AT, BE, HR, CZ, DK, FI, FR, DE, GR, HU, IS, IE, IT, LU, NL, NO, PL, PT, SM, SK, SI, ES, SE, CH, TR, GB, UA }; const char * vbi_country_names_en[] = { "Austria", "Belgium", "Croatia", "Czech Republic", "Denmark", "Finland", "France", "Germany", "Greece", "Hungary", "Iceland", "Ireland", "Italy", "Luxembourg", "Netherlands", "Norway", "Poland", "Portugal", "San Marino", "Slovakia", "Slovenia", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "Ukraine" }; /* CNI sources: Packet 8/30 f1 Byte 13 Byte 14 Bit (tx order) 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 CNI --------------- 15:8 --------------- 7:0 Packet 8/30 f2 Byte 15 Byte 16 Byte 21 Byte 22 Byte 23 Bit (tx order) 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 VPS Byte 5 Byte 11 Byte 13 Byte 14 Bit (tx order) 4 5 6 7 0 1 6 7 0 1 2 3 4 5 6 7 Country ------- 15:12 / 7:4 ------------------- 11:8 / 3:0 Network --- 7:6 5:0 ------------------- Packet X/26 Address Mode Data Bit (tx order) 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N Data Word A P P - P ----- P 1 1 0:5 (0x3n) Mode 0 0 0 1 0 P 0:5 ("Country & Programme Source") Data Word B ------------- P 0:6 */ #include "network-table.h" #if 1 /* * ETS 300 231 Table 3: Codes for programme type (PTY) Principle of classification */ const char * ets_program_class[16] = { "undefined content", "drama & films", "news/current affairs/social", "show/game show/leisure hobbies", "sports", "children/youth/education/science", "music/ballet/Dance", "arts/culture (without music)", "series code", "series code", "series code", "series code", "series code", "series code", "series code", "series code", }; #endif /* * ETS 300 231 Table 3: Codes for programme type (PTY) Principle of classification */ const char * ets_program_type[8][16] = { { 0 }, { "movie (general)", "detective/thriller", "adventure/western/war", "science fiction/fantasy/horror", "comedy", "soap/melodrama/folklore", "romance", "serious/classical/religious/historical drama", "adult movie" }, { "news/current affairs (general)", "news/weather report", "news magazine", "documentary", "discussion/interview/debate", "social/political issues/economics (general)", "magazines/reports/documentary", "economics/social advisory", "remarkable people" }, { "show/game show (general)", "game show/quiz/contest", "variety show", "talk show", "leisure hobbies (general)", "tourism/travel", "handicraft", "motoring", "fitness & health", "cooking", "advertisement/shopping", 0, 0, 0, 0, "alarm/emergency identification" }, { "sports (general)" "special event (Olympic Games, World Cup etc.)", "sports magazine", "football/soccer", "tennish/squash", "team sports (excluding football)", "athletics", "motor sport", "water sport", "winter sports", "equestrian", "martial sports", "local sports" }, { "children's/youth programmes (general)", "pre-school children's programmes", "entertainment programmes for 6 to 14", "entertainment programmes for 10 to 16", "informational/educational/school programmes", "cartoons/puppets", "education/science/factual topics (general)", "nature/animals/environement", "technology/natural sciences", "medicine/physiology/psychology", "foreign countries/expeditions", "social/spiritual sciences", "further education", "languages" }, { "music/ballet/dance (general)", "rock/Pop", "serious music/classical Music", "folk/traditional music", "jazz", "musical/opera", "ballet" }, { "arts/culture (general)", "performing arts", "fine arts", "religion", "popular culture/traditional arts", "literature", "film/cinema", "experimental film/video", "broadcasting/press", "new media", "arts/culture magazines", "fashion" } }; static const char * eia608_program_type[96] = { "education", "entertainment", "movie", "news", "religious", "sports", "other", "action", "advertisement", "animated", "anthology", "automobile", "awards", "baseball", "basketball", "bulletin", "business", "classical", "college", "combat", "comedy", "commentary", "concert", "consumer", "contemporary", "crime", "dance", "documentary", "drama", "elementary", "erotica", "exercise", "fantasy", "farm", "fashion", "fiction", "food", "football", "foreign", "fund raiser", "game/quiz", "garden", "golf", "government", "health", "high school", "history", "hobby", "hockey", "home", "horror", "information", "instruction", "international", "interview", "language", "legal", "live", "local", "math", "medical", "meeting", "military", "miniseries", "music", "mystery", "national", "nature", "police", "politics", "premiere", "prerecorded", "product", "professional", "public", "racing", "reading", "repair", "repeat", "review", "romance", "science", "series", "service", "shopping", "soap opera", "special", "suspense", "talk", "technical", "tennis", "travel", "variety", "video", "weather", "western" }; /** * @param auth From vbi_program_info.rating_auth. * @param id From vbi_program_info.rating_id. * * Translate a vbi_program_info program rating code into a * Latin-1 string, native language. * * @a return * Static pointer to the string (don't free()), or @c NULL if * this code is undefined. */ const char * vbi_rating_string(vbi_rating_auth auth, int id) { static const char *ratings[4][8] = { { NULL, "G", "PG", "PG-13", "R", "NC-17", "X", "Not rated" }, { "Not rated", "TV-Y", "TV-Y7", "TV-G", "TV-PG", "TV-14", "TV-MA", "Not rated" }, { "Exempt", "C", "C8+", "G", "PG", "14+", "18+", NULL }, { "Exempt", "G", "8 ans +", "13 ans +", "16 ans +", "18 ans +", NULL, NULL }, }; if (id < 0 || id > 7) return NULL; switch (auth) { case VBI_RATING_AUTH_MPAA: return ratings[0][id]; case VBI_RATING_AUTH_TV_US: return ratings[1][id]; case VBI_RATING_AUTH_TV_CA_EN: return ratings[2][id]; case VBI_RATING_AUTH_TV_CA_FR: return ratings[3][id]; default: return NULL; } } /** * @param classf From vbi_program_info.type_classf. * @param id From vbi_program_info.type_id. * * Translate a vbi_program_info program type code into a * Latin-1 string, currently English only. * * @return * Static pointer to the string (don't free()), or @c NULL if * this code is undefined. */ const char * vbi_prog_type_string(vbi_prog_classf classf, int id) { switch (classf) { case VBI_PROG_CLASSF_EIA_608: if (id < 0x20 || id > 0x7F) return NULL; return eia608_program_type[id - 0x20]; case VBI_PROG_CLASSF_ETS_300231: if (id < 0x00 || id > 0x7F) return NULL; return ets_program_type[0][id]; default: return NULL; } } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/tables.h000066400000000000000000000033171476363111200147750ustar00rootroot00000000000000/* * libzvbi -- Tables * * Copyright (C) 1999-2002 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: tables.h,v 1.10 2008-02-19 00:35:22 mschimek Exp $ */ #ifndef TABLES_H #define TABLES_H #include #include "event.h" /* vbi_rating_auth, vbi_prog_classf */ extern const char *vbi_country_names_en[]; struct vbi_cni_entry { int16_t id; /* arbitrary */ const char * country; /* RFC 1766 / ISO 3166-1 alpha-2 */ const char * name; /* UTF-8 */ uint16_t cni1; /* Teletext packet 8/30 format 1 */ uint16_t cni2; /* Teletext packet 8/30 format 2 */ uint16_t cni3; /* PDC Method B */ uint16_t cni4; /* VPS */ }; extern const struct vbi_cni_entry vbi_cni_table[]; /* Public */ /** * @addtogroup Event * @{ */ extern const char * vbi_rating_string(vbi_rating_auth auth, int id); extern const char * vbi_prog_type_string(vbi_prog_classf classf, int id); /** @} */ /* Private */ #endif /* TABLES_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/teletext.c000066400000000000000000002021701476363111200153520ustar00rootroot00000000000000/* * libzvbi -- Teletext decoder backend * * Copyright (C) 2000, 2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: teletext.c,v 1.34 2013-08-28 14:44:55 mschimek Exp $ */ #include "site_def.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include /* strncasecmp */ #include #include #include "bcd.h" #include "vt.h" #include "export.h" #include "vbi.h" #include "hamm.h" #include "lang.h" #include "teletext_decoder.h" extern const char _zvbi_intl_domainname[]; #include "intl-priv.h" #ifndef TELETEXT_DEBUG # define TELETEXT_DEBUG 0 #endif #define printv(templ, args...) \ do { \ if (TELETEXT_DEBUG) \ fprintf (stderr, templ ,##args); \ } while (0) #define ROWS 25 #define COLUMNS 40 #define EXT_COLUMNS 41 #define LAST_ROW ((ROWS - 1) * EXT_COLUMNS) /* * FLOF navigation */ static const vbi_color flof_link_col[4] = { VBI_RED, VBI_GREEN, VBI_YELLOW, VBI_CYAN }; static inline void flof_navigation_bar(vbi_page *pg, cache_page *vtp) { vbi_char ac; int n, i, k, ii; memset(&ac, 0, sizeof(ac)); ac.foreground = VBI_WHITE; ac.background = VBI_BLACK; ac.opacity = pg->page_opacity[1]; ac.unicode = 0x0020; for (i = 0; i < EXT_COLUMNS; i++) pg->text[LAST_ROW + i] = ac; ac.link = TRUE; for (i = 0; i < 4; i++) { ii = i * 10 + 3; for (k = 0; k < 3; k++) { n = ((vtp->data.lop.link[i].pgno >> ((2 - k) * 4)) & 15) + '0'; if (n > '9') n += 'A' - '9'; ac.unicode = n; ac.foreground = flof_link_col[i]; pg->text[LAST_ROW + ii + k] = ac; pg->nav_index[ii + k] = i; } pg->nav_link[i].pgno = vtp->data.lop.link[i].pgno; pg->nav_link[i].subno = vtp->data.lop.link[i].subno; } } static inline void flof_links(vbi_page *pg, cache_page *vtp) { vbi_char *acp = pg->text + LAST_ROW; int i, j, k, col = -1, start = 0; for (i = 0; i < COLUMNS + 1; i++) { if (i == COLUMNS || (acp[i].foreground & 7) != col) { for (k = 0; k < 4; k++) if ((int) flof_link_col[k] == col) break; if (k < 4 && !NO_PAGE(vtp->data.lop.link[k].pgno)) { /* Leading and trailing spaces not sensitive */ for (j = i - 1; j >= start && acp[j].unicode == 0x0020; j--); for (; j >= start; j--) { acp[j].link = TRUE; pg->nav_index[j] = k; } pg->nav_link[k].pgno = vtp->data.lop.link[k].pgno; pg->nav_link[k].subno = vtp->data.lop.link[k].subno; } if (i >= COLUMNS) break; col = acp[i].foreground & 7; start = i; } if (start == i && acp[i].unicode == 0x0020) start++; } } /* * TOP navigation */ static void character_set_designation(struct vbi_font_descr **font, struct ttx_extension *ext, cache_page *vtp); static void screen_color(vbi_page *pg, int flags, int color); static vbi_bool top_label(vbi_decoder *vbi, vbi_page *pg, struct vbi_font_descr *font, int index, int pgno, int foreground, int ff) { int column = index * 13 + 1; vbi_char *acp; struct ttx_ait_title *ait; int i, j; acp = &pg->text[LAST_ROW + column]; for (i = 0; i < 8; i++) if (PAGE_FUNCTION_AIT == vbi->cn->btt_link[i].function) { cache_page *vtp; vtp = _vbi_cache_get_page (vbi->ca, vbi->cn, vbi->cn->btt_link[i].pgno, vbi->cn->btt_link[i].subno, /* subno_mask */ 0x3f7f); if (!vtp) { printv ("top ait page %x not cached\n", vbi->cn->btt_link[i].pgno); continue; } else if (vtp->function != PAGE_FUNCTION_AIT) { printv("no ait page %x\n", vtp->pgno); cache_page_unref (vtp); vtp = NULL; continue; } for (ait = vtp->data.ait.title, j = 0; j < 46; ait++, j++) { if (ait->link.pgno == pgno) { pg->nav_link[index].pgno = pgno; pg->nav_link[index].subno = VBI_ANY_SUBNO; for (i = 11; i >= 0; i--) if (ait->text[i] > 0x20) break; if (ff && (i <= (11 - ff))) { acp += (11 - ff - i) >> 1; column += (11 - ff - i) >> 1; acp[i + 1].link = TRUE; pg->nav_index[column + i + 1] = index; acp[i + 2].unicode = 0x003E; acp[i + 2].foreground = foreground; acp[i + 2].link = TRUE; pg->nav_index[column + i + 2] = index; if (ff > 1) { acp[i + 3].unicode = 0x003E; acp[i + 3].foreground = foreground; acp[i + 3].link = TRUE; pg->nav_index[column + i + 3] = index; } } else { acp += (11 - i) >> 1; column += (11 - i) >> 1; } for (; i >= 0; i--) { acp[i].unicode = vbi_teletext_unicode(font->G0, font->subset, (ait->text[i] < 0x20) ? 0x20 : ait->text[i]); acp[i].foreground = foreground; acp[i].link = TRUE; pg->nav_index[column + i] = index; } cache_page_unref (vtp); vtp = NULL; return TRUE; } } cache_page_unref (vtp); vtp = NULL; } return FALSE; } static __inline__ vbi_pgno add_modulo (vbi_pgno pgno, int incr) { return ((pgno - 0x100 + incr) & 0x7FF) + 0x100; } static inline void top_navigation_bar(vbi_decoder *vbi, vbi_page *pg, cache_page *vtp) { struct ttx_page_stat *ps; vbi_char ac; vbi_pgno pgno1; int i, got; ps = cache_network_page_stat (vbi->cn, vtp->pgno); printv("PAGE MIP/BTT: %d\n", ps->page_type); memset(&ac, 0, sizeof(ac)); ac.foreground = 32 + VBI_WHITE; ac.background = 32 + VBI_BLACK; ac.opacity = pg->page_opacity[1]; ac.unicode = 0x0020; for (i = 0; i < EXT_COLUMNS; i++) pg->text[LAST_ROW + i] = ac; if (pg->page_opacity[1] != VBI_OPAQUE) return; pgno1 = add_modulo (vtp->pgno, 1); for (i = vtp->pgno; i != pgno1; i = add_modulo (i, -1)) { struct ttx_page_stat *ps; ps = cache_network_page_stat (vbi->cn, i); if (ps->page_type == VBI_TOP_BLOCK || ps->page_type == VBI_TOP_GROUP) { top_label(vbi, pg, pg->font[0], 0, i, 32 + VBI_WHITE, 0); break; } } for (i = pgno1, got = FALSE; i != vtp->pgno; i = add_modulo (i, 1)) { struct ttx_page_stat *ps; ps = cache_network_page_stat (vbi->cn, i); switch (ps->page_type) { case VBI_TOP_BLOCK: top_label(vbi, pg, pg->font[0], 2, i, 32 + VBI_YELLOW, 2); return; case VBI_TOP_GROUP: if (!got) { top_label(vbi, pg, pg->font[0], 1, i, 32 + VBI_GREEN, 1); got = TRUE; } break; } } } static struct ttx_ait_title * next_ait(vbi_decoder *vbi, int pgno, int subno, cache_page **mvtp) { struct ttx_ait_title *ait, *mait = NULL; int mpgno = 0xFFF, msubno = 0xFFFF; int i, j; *mvtp = NULL; for (i = 0; i < 8; i++) { if (PAGE_FUNCTION_AIT == vbi->cn->btt_link[i].function) { cache_page *vtp; vtp = _vbi_cache_get_page (vbi->ca, vbi->cn, vbi->cn->btt_link[i].pgno, vbi->cn->btt_link[i].subno, /* subno_mask */ 0x3f7f); if (!vtp) { printv("top ait page %x not cached\n", vbi->cn->btt_link[i].pgno); continue; } else if (vtp->function != PAGE_FUNCTION_AIT) { printv("no ait page %x\n", vtp->pgno); cache_page_unref (vtp); vtp = NULL; continue; } for (ait = vtp->data.ait.title, j = 0; j < 46; ait++, j++) { if (!ait->link.pgno) break; if (ait->link.pgno < pgno || (ait->link.pgno == pgno && ait->link.subno <= subno)) continue; if (ait->link.pgno > mpgno || (ait->link.pgno == mpgno && ait->link.subno > msubno)) continue; mait = ait; mpgno = ait->link.pgno; msubno = ait->link.subno; if (NULL != *mvtp) cache_page_unref (*mvtp); *mvtp = vtp; } } } return mait; } static int top_index(vbi_decoder *vbi, vbi_page *pg, int subno) { cache_page *vtp = NULL; vbi_char ac, *acp; struct ttx_ait_title *ait; int i, j, k, n, lines; int xpgno, xsubno; struct ttx_extension *ext; char *index_str; pg->vbi = vbi; subno = vbi_bcd2dec(subno); pg->rows = ROWS; pg->columns = EXT_COLUMNS; pg->dirty.y0 = 0; pg->dirty.y1 = ROWS - 1; pg->dirty.roll = 0; ext = &cache_network_magazine (vbi->cn, 0x100)->extension; screen_color(pg, 0, 32 + VBI_BLUE); vbi_transp_colormap(vbi, pg->color_map, ext->color_map, 40); pg->drcs_clut = ext->drcs_clut; pg->page_opacity[0] = VBI_OPAQUE; pg->page_opacity[1] = VBI_OPAQUE; pg->boxed_opacity[0] = VBI_OPAQUE; pg->boxed_opacity[1] = VBI_OPAQUE; memset(pg->drcs, 0, sizeof(pg->drcs)); memset(&ac, 0, sizeof(ac)); ac.foreground = VBI_BLACK; // 32 + VBI_BLACK; ac.background = 32 + VBI_BLUE; ac.opacity = VBI_OPAQUE; ac.unicode = 0x0020; ac.size = VBI_NORMAL_SIZE; for (i = 0; i < EXT_COLUMNS * ROWS; i++) pg->text[i] = ac; ac.size = VBI_DOUBLE_SIZE; /* FIXME */ /* TRANSLATORS: Title of TOP Index page, for now please Latin-1 or ASCII only */ index_str = _("TOP Index"); for (i = 0; index_str[i]; i++) { ac.unicode = index_str[i]; pg->text[1 * EXT_COLUMNS + 2 + i * 2] = ac; } ac.size = VBI_NORMAL_SIZE; acp = &pg->text[4 * EXT_COLUMNS]; lines = 17; xpgno = 0; xsubno = 0; while ((ait = next_ait(vbi, xpgno, xsubno, &vtp))) { struct ttx_page_stat *ps; xpgno = ait->link.pgno; xsubno = ait->link.subno; /* No docs, correct? */ character_set_designation(pg->font, ext, vtp); if (subno > 0) { if (lines-- == 0) { subno--; lines = 17; } cache_page_unref (vtp); vtp = NULL; continue; } else if (lines-- <= 0) { cache_page_unref (vtp); vtp = NULL; continue; } for (i = 11; i >= 0; i--) if (ait->text[i] > 0x20) break; ps = cache_network_page_stat (vbi->cn, ait->link.pgno); switch (ps->page_type) { case VBI_TOP_GROUP: k = 3; break; default: k = 1; } for (j = 0; j <= i; j++) { acp[k + j].unicode = vbi_teletext_unicode(pg->font[0]->G0, pg->font[0]->subset, (ait->text[j] < 0x20) ? 0x20 : ait->text[j]); } for (k += i + 2; k <= 33; k++) acp[k].unicode = '.'; for (j = 0; j < 3; j++) { n = ((ait->link.pgno >> ((2 - j) * 4)) & 15) + '0'; if (n > '9') n += 'A' - '9'; acp[j + 35].unicode = n; } acp += EXT_COLUMNS; cache_page_unref (vtp); } cache_page_unref (vtp); vtp = NULL; return 1; } struct pex26 { signed month : 8; /* 0 ... 11 */ signed day : 8; /* 0 ... 30 */ signed at1 : 16; /* min since 00:00 */ signed at2 : 16; /* min since 00:00 */ signed length : 16; /* min */ unsigned x26_cni : 16; /* see tables.c */ unsigned pty : 8; signed lto : 8; /* +- 1/4 hr */ signed row : 8; /* title 1 ... 23 */ signed column : 8; /* title 0 ... 39 */ unsigned caf : 1; unsigned : 15; }; static void dump_pex26(struct pex26 *pt, int n) { int i; for (i = 0; i < n; i++, pt++) fprintf(stderr, "%2d: %02d-%02d %d:%02d (%d:%02d) +%d, " "cni=%04x pty=%02x lto=%d tit=%d:%d caf=%d\n", i, pt->month, pt->day, pt->at1 / 60, pt->at1 % 60, pt->at2 / 60, pt->at2 % 60, pt->length, pt->x26_cni, pt->pty, pt->lto, pt->row, pt->column, pt->caf); } #if 0 /* type pre text ____ post ____ / \ AT-1 + zz.zz + % < AT-1 + zz.zz-zz.zz + % < PTL ++ title ++ %% :: < AT-1 % zz.zz + % < PW*) % hh LTO % 0zz + % < LTO % 9zz + % < AT-2 % zzzz + % < CNI*) % hhzzz + % < AD*) % zzzzzz + % < PTL %% title ++ %% :: < AT-2 : zzzz + % < AD : zzzzzz + % < PW :% hh + % < PTY :% Fhh + % < AT-2 :% zzzz + % < CNI :% hhzzz + % < AD :% zzzzzz + % < + colour code : magenta % conceal *) permitted when CNI, AD, PW combine Note ETS 300 231 Table 4 is wrong: '%' = 0x18; ',' = '+' | '%' | '<' */ /* to be rewritten */ struct program_entry { int start; int stop; int at2; int ad; int cni; int pty; int lto; uint16_t title[200]; }; #define PMA_COLOUR /* 0x01, 0x02, 0x03, 0x04, 0x06, 0x07 */ #define PMA_MAGENTA 0x05 #define PMA_CONCEAL 0x18 #define IS_PMA_CTRL(c) (((c) >= 0x01 && (c) <= 0x07) || (c) == PMA_CONCEAL) static int bcd2time(int bcd) { int sec = bcd & 15; int min = (bcd >> 8) & 15; if (sec > 9 || min > 9 || (bcd & 0x00FF) > 0x0059) return -1; //#warning hour check return sec * 1 + min * 60 + ((bcd >> 4) & 15) * 10 + ((bcd >> 12) & 15) * 600; } static int pdc_method_a(vbi_page *pg, cache_page *vtp, struct program_entry *pe) { int row, column; int i; // memset(pe, -1, sizeof(*pe)); i = 40; for (row = 1; row <= 23; row++) { for (column = 0; column <= 38;) { int ctrl1 = vbi_parity(vtp->data.lop.raw[row][column]); int ctrl2 = vbi_parity(vtp->data.lop.raw[row][column + 1]); fprintf(stderr, "%d %d %02x %02x\n", row, column, ctrl1, ctrl2); if ((ctrl1 | ctrl2) < 0) { return 0; /* hamming error */ } else if (!IS_PMA_CTRL(ctrl1)) { column++; continue; } if (ctrl1 == ctrl2 && ctrl1 != PMA_MAGENTA) { fprintf(stderr, "PTL %d %d\n", row, column); /* title */ column += 2;fprintf(stderr, "%d %d %02x %02x\n", row, column, ctrl1, ctrl2); } else { /* numeral */ int digits, sep, value; fprintf(stderr, "NUM %d %d\n", row, column); column += (ctrl1 == PMA_MAGENTA && ctrl2 == PMA_CONCEAL) ? 2 : 1; sep = 0; value = 0; for (digits = 0; column < 40; column++) { int c = vbi_parity(vtp->data.lop.raw[row][column]); if (IS_PMA_CTRL(c)) { break; } else if (c >= 0x30 && c <= 0x39) { value = value * 16 + c - 0x30; digits++; } else if (c >= 0x41 && c <= 0x46) { if (digits >= 3) goto invalid_pattern; value = value * 16 + c + (0x0A - 0x41); digits++; } else if (c == 0x2E) { if (digits != 2 && digits != 6) goto invalid_pattern; sep |= 1 << digits; } else if (c == 0x2D) { if (digits != 4) goto invalid_pattern; sep |= 1 << 4; } else goto invalid_pattern; } if (sep) { if (ctrl1 == PMA_MAGENTA) goto invalid_pattern; if (ctrl1 == PMA_CONCEAL && digits != 4) goto invalid_pattern; } switch (digits) { int start, stop; case 2: /* Actually ctrl1 only permitted when combined */ if (ctrl1 != PMA_CONCEAL && ctrl2 != PMA_CONCEAL) goto invalid_pattern; fprintf(stderr, "PW %02x\n", value); /* PW */ break; case 3: if (ctrl1 == PMA_CONCEAL) { if (value >= 0x100 && value < 0x900) goto invalid_pattern; fprintf(stderr, "LTO %03x\n", value); /* LTO */ } else if (ctrl2 == PMA_CONCEAL) { if ((value -= 0xF00) < 0) goto invalid_pattern; fprintf(stderr, "PTY %02x\n", value); /* PTY */ } else goto invalid_pattern; case 4: start = bcd2time(value); if (start < 0) goto invalid_pattern; if (sep) { if (ctrl1 == PMA_MAGENTA) goto invalid_pattern; fprintf(stderr, "AT-1 %04x\n", value); ; /* AT-1 short */ } else if (ctrl1 == PMA_MAGENTA || ctrl1 == PMA_CONCEAL) { fprintf(stderr, "AT-2 %04x\n", value); ; /* AT-2 */ } else goto invalid_pattern; break; case 5: /* Actually ctrl1 only permitted when combined */ if ((ctrl1 != PMA_CONCEAL && ctrl2 != PMA_CONCEAL) || (value & 0x00F00) > 0x00900) goto invalid_pattern; /* CNI */ fprintf(stderr, "CNI %05x\n", value); break; case 6: /* Actually ctrl1 only permitted when combined */ if (ctrl1 != PMA_CONCEAL && ctrl2 != PMA_CONCEAL) goto invalid_pattern; /* AD */ fprintf(stderr, "AD %06x\n", value); break; case 8: start = bcd2time(value >> 16); stop = bcd2time(value); if ((start | stop) < 0 || ctrl1 == PMA_MAGENTA || ctrl1 == PMA_CONCEAL || sep != ((1 << 2) + (1 << 4) + (1 << 6))) goto invalid_pattern; /* AT-1 long */ fprintf(stderr, "AT1 %08x\n", value); break; default: invalid_pattern: continue; } } } } return 0; /* invalid */ } #endif /* * Zapzilla navigation */ static int keyword(vbi_link *ld, uint8_t *p, int column, int pgno, int subno, int *back) { uint8_t *s = p + column; int i, j, k, l; ld->type = VBI_LINK_NONE; ld->name[0] = 0; ld->url[0] = 0; ld->pgno = 0; ld->subno = VBI_ANY_SUBNO; *back = 0; if (isdigit(*s)) { for (i = 0; isdigit(s[i]); i++) ld->pgno = ld->pgno * 16 + (s[i] & 15); if (isdigit(s[-1]) || i > 3) return i; if (i == 3) { if (ld->pgno >= 0x100 && ld->pgno <= 0x899) ld->type = VBI_LINK_PAGE; return i; } if (s[i] != '/' && s[i] != ':') return i; s += i += 1; for (ld->subno = j = 0; isdigit(s[j]); j++) ld->subno = ld->subno * 16 + (s[j] & 15); if (j > 1 || subno != ld->pgno || ld->subno > 0x99) return i + j; if (ld->pgno == ld->subno) ld->subno = 0x01; else ld->subno = vbi_add_bcd(ld->pgno, 0x01); ld->type = VBI_LINK_SUBPAGE; ld->pgno = pgno; return i + j; } else if (!strncasecmp((char *) s, "https://", i = 8)) { ld->type = VBI_LINK_HTTP; } else if (!strncasecmp((char *) s, "http://", i = 7)) { ld->type = VBI_LINK_HTTP; } else if (!strncasecmp((char *) s, "www.", i = 4)) { ld->type = VBI_LINK_HTTP; strcpy((char *) ld->url, "http://"); } else if (!strncasecmp((char *) s, "ftp://", i = 6)) { ld->type = VBI_LINK_FTP; } else if (*s == '@' || *s == 0xA7) { ld->type = VBI_LINK_EMAIL; strcpy((char *) ld->url, "mailto:"); i = 1; } else if (!strncasecmp((char *) s, "(at)", i = 4)) { ld->type = VBI_LINK_EMAIL; strcpy((char *) ld->url, "mailto:"); } else if (!strncasecmp((char *) s, "(a)", i = 3)) { ld->type = VBI_LINK_EMAIL; strcpy((char *) ld->url, "mailto:"); } else return 1; for (j = k = l = 0;;) { // RFC 1738 while (isalnum(s[i + j]) || strchr("%&/=?+-~:;@_", s[i + j])) { j++; l++; } if (s[i + j] == '.') { if (l < 1) return i; l = 0; j++; k++; } else break; } if (k < 1 || l < 1) { ld->type = VBI_LINK_NONE; return i; } k = 0; if (ld->type == VBI_LINK_EMAIL) { for (; isalnum(s[k - 1]) || strchr("-~._", s[k - 1]); k--); if (k == 0) { ld->type = VBI_LINK_NONE; return i; } *back = k; strncat((char *) ld->url, (char *) s + k, -k); strcat((char *) ld->url, "@"); strncat((char *) ld->url, (char *) s + i, j); } else strncat((char *) ld->url, (char *) s + k, i + j - k); return i + j; } static inline void zap_links(vbi_page *pg, int row) { unsigned char buffer[43]; /* One row, two spaces on the sides and NUL */ vbi_link ld; vbi_char *acp; vbi_bool link[43]; int i, j, n, b; acp = &pg->text[row * EXT_COLUMNS]; for (i = j = 0; i < COLUMNS; i++) { if (acp[i].size == VBI_OVER_TOP || acp[i].size == VBI_OVER_BOTTOM) continue; buffer[j + 1] = (acp[i].unicode >= 0x20 && acp[i].unicode <= 0xFF) ? acp[i].unicode : 0x20; j++; } buffer[0] = ' '; buffer[j + 1] = ' '; buffer[j + 2] = 0; for (i = 0; i < COLUMNS; i += n) { n = keyword(&ld, buffer, i + 1, pg->pgno, pg->subno, &b); for (j = b; j < n; j++) link[i + j] = (ld.type != VBI_LINK_NONE); } for (i = j = 0; i < COLUMNS; i++) { acp[i].link = link[j]; if (acp[i].size == VBI_OVER_TOP || acp[i].size == VBI_OVER_BOTTOM) continue; j++; } } /** * @param pg With vbi_fetch_vt_page() obtained vbi_page. * @param column Column 0 ... pg->columns - 1 of the character in question. * @param row Row 0 ... pg->rows - 1 of the character in question. * @param ld Place to store information about the link. * * A vbi_page (in practice only Teletext pages) may contain hyperlinks * such as HTTP URLs, e-mail addresses or links to other pages. Characters * being part of a hyperlink have a set vbi_char->link flag, this function * returns a more verbose description of the link. */ void vbi_resolve_link(vbi_page *pg, int column, int row, vbi_link *ld) { unsigned char buffer[43]; vbi_char *acp; int i, j, b; assert(column >= 0 && column < EXT_COLUMNS); ld->nuid = pg->nuid; acp = &pg->text[row * EXT_COLUMNS]; if (row == (ROWS - 1) && acp[column].link) { i = pg->nav_index[column]; ld->type = VBI_LINK_PAGE; ld->pgno = pg->nav_link[i].pgno; ld->subno = pg->nav_link[i].subno; return; } if (row < 1 || row > 23 || column >= COLUMNS || pg->pgno < 0x100) { ld->type = VBI_LINK_NONE; return; } for (i = j = b = 0; i < COLUMNS; i++) { if (acp[i].size == VBI_OVER_TOP || acp[i].size == VBI_OVER_BOTTOM) continue; if (i < column && !acp[i].link) j = b = -1; buffer[j + 1] = (acp[i].unicode >= 0x20 && acp[i].unicode <= 0xFF) ? acp[i].unicode : 0x20; if (b <= 0) { if (buffer[j + 1] == ')' && j > 2) { if (!strncasecmp((char *) buffer + j + 1 - 3, "(at", 3)) b = j - 3; else if (!strncasecmp((char *) buffer + j + 1 - 2, "(a", 2)) b = j - 2; } else if (buffer[j + 1] == '@' || buffer[j + 1] == 167) b = j; } j++; } buffer[0] = ' '; buffer[j + 1] = ' '; buffer[j + 2] = 0; keyword(ld, buffer, 1, pg->pgno, pg->subno, &i); if (ld->type == VBI_LINK_NONE) keyword(ld, buffer, b + 1, pg->pgno, pg->subno, &i); } /** * @param pg With vbi_fetch_vt_page() obtained vbi_page. * @param ld Place to store information about the link. * * All Teletext pages have a built-in home link, by default * page 100, but can also be the magazine intro page or another * page selected by the editor. */ void vbi_resolve_home(vbi_page *pg, vbi_link *ld) { if (pg->pgno < 0x100) { ld->type = VBI_LINK_NONE; return; } ld->type = VBI_LINK_PAGE; ld->pgno = pg->nav_link[5].pgno; ld->subno = pg->nav_link[5].subno; } static inline void ait_title(vbi_decoder *vbi, cache_page *vtp, struct ttx_ait_title *ait, char *buf) { struct ttx_magazine *mag; struct vbi_font_descr *font[2]; int i; mag = cache_network_magazine (vbi->cn, 0x100); character_set_designation (font, &mag->extension, vtp); for (i = 11; i >= 0; i--) if (ait->text[i] > 0x20) break; buf[i + 1] = 0; for (; i >= 0; i--) { unsigned int unicode = vbi_teletext_unicode( font[0]->G0, font[0]->subset, (ait->text[i] < 0x20) ? 0x20 : ait->text[i]); buf[i] = (unicode >= 0x20 && unicode <= 0xFF) ? unicode : 0x20; } } /** * @param vbi Initialized vbi decoding context. * @param pgno Page number, see vbi_pgno. * @param subno Subpage number. * @param buf Place to store the title, Latin-1 format, at least * 41 characters including the terminating zero. * * Given a Teletext page number this function tries to deduce a * page title for bookmarks or other purposes, mainly from navigation * data. (XXX TODO: FLOF) * * @return * @c TRUE if a title has been found. */ vbi_bool vbi_page_title(vbi_decoder *vbi, int pgno, int subno, char *buf) { struct ttx_ait_title *ait; int i, j; subno = subno; if (vbi->cn->have_top) { for (i = 0; i < 8; i++) if (PAGE_FUNCTION_AIT == vbi->cn->btt_link[i].function) { cache_page *vtp; vtp = _vbi_cache_get_page (vbi->ca, vbi->cn, vbi->cn->btt_link[i].pgno, vbi->cn->btt_link[i].subno, /* subno_mask */ 0x3f7f); if (!vtp) { printv("p/t top ait page %x not cached\n", vbi->cn->btt_link[i].pgno); continue; } else if (vtp->function != PAGE_FUNCTION_AIT) { printv("p/t no ait page %x\n", vtp->pgno); cache_page_unref (vtp); vtp = NULL; continue; } for (ait = vtp->data.ait.title, j = 0; j < 46; ait++, j++) { if (ait->link.pgno == pgno) { ait_title(vbi, vtp, ait, buf); cache_page_unref (vtp); vtp = NULL; return TRUE; } } cache_page_unref (vtp); vtp = NULL; } } else { /* find a FLOF link and the corresponding label */ } return FALSE; } /* * Teletext page formatting */ static void character_set_designation(struct vbi_font_descr **font, struct ttx_extension *ext, cache_page *vtp) { int i; #ifdef libzvbi_TTX_OVERRIDE_CHAR_SET font[0] = vbi_font_descriptors + libzvbi_TTX_OVERRIDE_CHAR_SET; font[1] = vbi_font_descriptors + libzvbi_TTX_OVERRIDE_CHAR_SET; fprintf(stderr, "override char set with %d\n", libzvbi_TTX_OVERRIDE_CHAR_SET); #else font[0] = vbi_font_descriptors + 0; font[1] = vbi_font_descriptors + 0; for (i = 0; i < 2; i++) { int charset_code = ext->charset_code[i]; if (VALID_CHARACTER_SET(charset_code)) font[i] = vbi_font_descriptors + charset_code; charset_code = (charset_code & ~7) + vtp->national; if (VALID_CHARACTER_SET(charset_code)) font[i] = vbi_font_descriptors + charset_code; } #endif } static void screen_color(vbi_page *pg, int flags, int color) { pg->screen_color = color; if (color == VBI_TRANSPARENT_BLACK || (flags & (C5_NEWSFLASH | C6_SUBTITLE))) pg->screen_opacity = VBI_TRANSPARENT_SPACE; else pg->screen_opacity = VBI_OPAQUE; } #define elements(array) (sizeof(array) / sizeof(array[0])) static struct ttx_triplet * resolve_obj_address (vbi_decoder * vbi, cache_page ** vtpp, enum ttx_object_type type, vbi_pgno pgno, ttx_object_address address, enum ttx_page_function function, int * remaining) { int s1, packet, pointer; cache_page *vtp; struct ttx_triplet *trip; int i; s1 = address & 15; packet = ((address >> 7) & 3); i = ((address >> 5) & 3) * 3 + type; printv("obj invocation, source page %03x/%04x, " "pointer packet %d triplet %d\n", pgno, s1, packet + 1, i); vtp = _vbi_cache_get_page (vbi->ca, vbi->cn, pgno, s1, 0x000F); if (!vtp) { printv("... page not cached\n"); return 0; } if (vtp->function == PAGE_FUNCTION_UNKNOWN) { cache_page *new_cp; new_cp = vbi_convert_page(vbi, vtp, TRUE, function); if (NULL == new_cp) { printv("... no g/pop page or hamming error\n"); cache_page_unref (vtp); vtp = NULL; return 0; } else { vtp = new_cp; } } else if (vtp->function == PAGE_FUNCTION_POP) vtp->function = function; else if (vtp->function != function) { printv("... source page wrong function %d, expected %d\n", vtp->function, function); cache_page_unref (vtp); vtp = NULL; return 0; } pointer = vtp->data.pop.pointer[packet * 24 + i * 2 + ((address >> 4) & 1)]; printv("... triplet pointer %d\n", pointer); if (pointer > 506) { printv("... triplet pointer out of bounds (%d)\n", pointer); cache_page_unref (vtp); vtp = NULL; return 0; } if (TELETEXT_DEBUG) { packet = (pointer / 13) + 3; if (packet <= 25) printv("... object start in packet %d, triplet %d (pointer %d)\n", packet, pointer % 13, pointer); else printv("... object start in packet 26/%d, triplet %d (pointer %d)\n", packet - 26, pointer % 13, pointer); } trip = vtp->data.pop.triplet + pointer; *remaining = elements(vtp->data.pop.triplet) - (pointer+1); printv("... obj def: ad 0x%02x mo 0x%04x dat %d=0x%x\n", trip->address, trip->mode, trip->data, trip->data); address ^= trip->address << 7; address ^= trip->data; if (trip->mode != (type + 0x14) || (address & 0x1FF)) { printv("... no object definition\n"); cache_page_unref (vtp); vtp = NULL; return 0; } *vtpp = vtp; return trip + 1; } struct enhance_state { const cache_page * vtp; enum ttx_object_type type; vbi_char ac, mac, *acp; int inv_row, inv_column; int active_row, active_column; int row_color; int next_row_color; int row_color_transparent; int invert; }; static void enhance_flush(struct enhance_state *es, int column) { int row = es->inv_row + es->active_row; int i; if (row >= ROWS) return; if (es->type == OBJECT_TYPE_PASSIVE && !es->mac.unicode) { es->active_column = column; return; } printv("flush [%04x%c,F%d%c,B%d%c,S%d%c,O%d%c,H%d%c] %d ... %d\n", es->ac.unicode, es->mac.unicode ? '*' : ' ', es->ac.foreground, es->mac.foreground ? '*' : ' ', es->ac.background, es->mac.background ? '*' : ' ', es->ac.size, es->mac.size ? '*' : ' ', es->ac.opacity, es->mac.opacity ? '*' : ' ', es->ac.flash, es->mac.flash ? '*' : ' ', es->active_column, column - 1); for (i = es->inv_column + es->active_column; i < es->inv_column + column;) { vbi_char c; if (i > 39) break; c = es->acp[i]; if (es->mac.underline) { int u = es->ac.underline; if (!es->mac.unicode) es->ac.unicode = c.unicode; /* G1 block mosaic character */ if (vbi_is_gfx(es->ac.unicode) && es->ac.unicode < 0xEF00) { if (u) es->ac.unicode &= ~0x20; /* separated */ else es->ac.unicode |= 0x20; /* contiguous */ es->mac.unicode = ~0; u = 0; } c.underline = u; } if (es->mac.foreground) c.foreground = (es->ac.foreground != VBI_TRANSPARENT_BLACK) ? es->ac.foreground : (es->row_color_transparent) ? VBI_TRANSPARENT_BLACK : es->row_color; if (es->mac.background) c.background = (es->ac.background != VBI_TRANSPARENT_BLACK) ? es->ac.background : (es->row_color_transparent) ? VBI_TRANSPARENT_BLACK : es->row_color; if (es->invert) { int t = c.foreground; c.foreground = c.background; c.background = t; } if (es->mac.opacity) c.opacity = es->ac.opacity; if (es->mac.flash) c.flash = es->ac.flash; if (es->mac.conceal) c.conceal = es->ac.conceal; if (es->mac.unicode) { c.unicode = es->ac.unicode; es->mac.unicode = 0; if (es->mac.size) c.size = es->ac.size; else if (c.size > VBI_DOUBLE_SIZE) c.size = VBI_NORMAL_SIZE; } es->acp[i] = c; if (es->type == OBJECT_TYPE_PASSIVE) break; i++; if (es->type != OBJECT_TYPE_PASSIVE && es->type != OBJECT_TYPE_ADAPTIVE) { int raw; raw = (row == 0 && i < 9) ? 0x20 : vbi_unpar8 (es->vtp->data.lop.raw[row][i - 1]); /* set-after spacing attributes cancelling non-spacing */ switch (raw) { case 0x00 ... 0x07: /* alpha + foreground color */ case 0x10 ... 0x17: /* mosaic + foreground color */ printv("... fg term %d %02x\n", i, raw); es->mac.foreground = 0; es->mac.conceal = 0; break; case 0x08: /* flash */ es->mac.flash = 0; break; case 0x0A: /* end box */ case 0x0B: /* start box */ if (i < COLUMNS && vbi_unpar8 (es->vtp->data.lop.raw[row][i]) == raw) { printv("... boxed term %d %02x\n", i, raw); es->mac.opacity = 0; } break; case 0x0D: /* double height */ case 0x0E: /* double width */ case 0x0F: /* double size */ printv("... size term %d %02x\n", i, raw); es->mac.size = 0; break; } if (i > 39) break; raw = (row == 0 && i < 8) ? 0x20 : vbi_unpar8 (es->vtp->data.lop.raw[row][i]); /* set-at spacing attributes cancelling non-spacing */ switch (raw) { case 0x09: /* steady */ es->mac.flash = 0; break; case 0x0C: /* normal size */ printv("... size term %d %02x\n", i, raw); es->mac.size = 0; break; case 0x18: /* conceal */ es->mac.conceal = 0; break; /* * Non-spacing underlined/separated display attribute * cannot be cancelled by a subsequent spacing attribute. */ case 0x1C: /* black background */ case 0x1D: /* new background */ printv("... bg term %d %02x\n", i, raw); es->mac.background = 0; break; } } } es->active_column = column; } static void enhance_flush_row(struct enhance_state *es) { int column; if (es->type == OBJECT_TYPE_PASSIVE || es->type == OBJECT_TYPE_ADAPTIVE) column = es->active_column + 1; else column = COLUMNS; enhance_flush (es, column); if (es->type != OBJECT_TYPE_PASSIVE) memset (&es->mac, 0, sizeof (es->mac)); } /* FIXME: panels */ static vbi_bool enhance(vbi_decoder *vbi, struct ttx_magazine *mag, struct ttx_extension *ext, vbi_page *pg, cache_page *vtp, enum ttx_object_type type, struct ttx_triplet *p, int max_triplets, int inv_row, int inv_column, vbi_wst_level max_level, vbi_bool header_only, struct pex26 *ptable) { struct enhance_state es; int offset_column, offset_row; struct vbi_font_descr *font; int drcs_s1[2]; struct pex26 *pt, ptmp; int pdc_hr; es.vtp = vtp; es.type = type; es.inv_row = inv_row; es.inv_column = inv_column; es.active_column = 0; es.active_row = 0; es.acp = &pg->text[(inv_row + 0) * EXT_COLUMNS]; offset_column = 0; offset_row = 0; es.row_color = es.next_row_color = ext->def_row_color; es.row_color_transparent = FALSE; drcs_s1[0] = 0; /* global */ drcs_s1[1] = 0; /* normal */ memset (&es.ac, 0, sizeof (es.ac)); memset (&es.mac, 0, sizeof (es.mac)); es.invert = 0; if (type == OBJECT_TYPE_PASSIVE) { es.ac.foreground = VBI_WHITE; es.ac.background = VBI_BLACK; es.ac.opacity = pg->page_opacity[1]; es.mac.foreground = ~0; es.mac.background = ~0; es.mac.opacity = ~0; es.mac.size = ~0; es.mac.underline = ~0; es.mac.conceal = ~0; es.mac.flash = ~0; } font = pg->font[0]; if (ptable) { ptmp.month = -1; ptmp.at1 = -1; /* n/a */ ptmp.length = 0; ptmp.x26_cni = 0; ptmp.pty = 0; ptmp.lto = 0; pt = ptable - 1; } else pt = &ptmp; pdc_hr = 0; for (; max_triplets>0; p++, max_triplets--) { if (p->address >= COLUMNS) { /* * Row address triplets */ int s = p->data >> 5; int row = (p->address - COLUMNS) ? : (ROWS - 1); int column = 0; if (pdc_hr) return FALSE; /* invalid */ switch (p->mode) { case 0x00: /* full screen color */ if (max_level >= VBI_WST_LEVEL_2p5 && s == 0 && type <= OBJECT_TYPE_ACTIVE) screen_color(pg, vtp->flags, p->data & 0x1F); break; case 0x07: /* address display row 0 */ if (p->address != 0x3F) break; /* reserved, no position */ row = 0; /* fall through */ case 0x01: /* full row color */ es.row_color = es.next_row_color; if (s == 0) { es.row_color = p->data & 0x1F; es.next_row_color = ext->def_row_color; } else if (s == 3) { es.row_color = es.next_row_color = p->data & 0x1F; } goto set_active; case 0x02: /* reserved */ case 0x03: /* reserved */ break; case 0x04: /* set active position */ if (max_level >= VBI_WST_LEVEL_2p5) { if (p->data >= COLUMNS) break; /* reserved */ column = p->data; } if (row > es.active_row) es.row_color = es.next_row_color; set_active: if (header_only && row > 0) { for (;max_triplets>1; p++, max_triplets--) if (p[1].address >= COLUMNS) { if (p[1].mode == 0x07) break; else if ((unsigned int) p[1].mode >= 0x1F) goto terminate; } break; } printv("enh set_active row %d col %d\n", row, column); if (row > es.active_row) enhance_flush_row (&es); else enhance_flush (&es, es.active_column + 1); es.active_row = row; es.active_column = column; es.acp = &pg->text[(es.inv_row + es.active_row) * EXT_COLUMNS]; break; case 0x05: /* reserved */ case 0x06: /* reserved */ break; case 0x08: /* PDC data - Country of Origin and Programme Source */ ptmp.x26_cni = p->address * 256 + p->data; break; case 0x09: /* PDC data - Month and Day */ ptmp.month = (p->address & 15) - 1; ptmp.day = (p->data >> 4) * 10 + (p->data & 15) - 1; break; case 0x0A: /* PDC data - Cursor Row and Announced Starting Time Hours */ if (!ptable) { break; } else if ((ptmp.month | ptmp.x26_cni) < 0) { return FALSE; } else if ((ptable - pt) > 22) { return FALSE; } *++pt = ptmp; /* fall through */ case 0x0B: /* PDC data - Cursor Row and Announced Finishing Time Hours */ s = (p->data & 15) * 60; if (p->mode == 0x0A) { pt->at2 = ((p->data & 0x30) >> 4) * 600 + s; pt->length = 0; pt->row = row; pt->caf = !!(p->data & 0x40); } else { pt->length = ((p->data & 0x70) >> 4) * 600 + s; } pdc_hr = p->mode; break; case 0x0C: /* PDC data - Cursor Row and Local Time Offset */ ptmp.lto = (p->data & 0x40) ? ((~0x7F) | p->data) : p->data; break; case 0x0D: /* PDC data - Series Identifier and Series Code */ if (p->address == 0x30) { break; } pt->pty = 0x80 + p->data; break; case 0x0E: /* reserved */ case 0x0F: /* reserved */ break; case 0x10: /* origin modifier */ if (max_level < VBI_WST_LEVEL_2p5) break; if (p->data >= 72) break; /* invalid */ offset_column = p->data; offset_row = p->address - COLUMNS; printv("enh origin modifier col %+d row %+d\n", offset_column, offset_row); break; case 0x11 ... 0x13: /* object invocation */ { int source = (p->address >> 3) & 3; enum ttx_object_type new_type = p->mode & 3; cache_page *trip_cp = NULL; struct ttx_triplet *trip; int remaining_max_triplets = 0; if (max_level < VBI_WST_LEVEL_2p5) break; printv("enh obj invocation " "source %d type %d\n", source, new_type); if (new_type <= type) { /* 13.2++ */ printv("... priority violation\n"); break; } if (source == 0) /* illegal */ break; else if (source == 1) { /* local */ int designation = (p->data >> 4) + ((p->address & 1) << 4); int triplet = p->data & 15; if (type != LOCAL_ENHANCEMENT_DATA || triplet > 12) break; /* invalid */ printv("... local obj %d/%d\n", designation, triplet); if (!(vtp->x26_designations & 1)) { printv("... no packet %d\n", designation); return FALSE; } trip = vtp->data.enh_lop.enh + designation * 13 + triplet; remaining_max_triplets = elements(vtp->data.enh_lop.enh) - (designation* 13 + triplet); } else /* global / public */ { enum ttx_page_function function; int pgno, i = 0; if (source == 3) { function = PAGE_FUNCTION_GPOP; pgno = vtp->data.lop.link[24].pgno; if (NO_PAGE(pgno)) { if (max_level < VBI_WST_LEVEL_3p5 || NO_PAGE(pgno = mag->pop_link[1][0].pgno)) pgno = mag->pop_link[0][0].pgno; } else printv("... X/27/4 GPOP overrides MOT\n"); } else { function = PAGE_FUNCTION_POP; pgno = vtp->data.lop.link[25].pgno; if (NO_PAGE(pgno)) { if ((i = mag->pop_lut[vtp->pgno & 0xFF]) == 0) { printv("... MOT pop_lut empty\n"); return FALSE; /* has no link (yet) */ } if (max_level < VBI_WST_LEVEL_3p5 || NO_PAGE(pgno = mag->pop_link[1][i].pgno)) pgno = mag->pop_link[0][i].pgno; } else printv("... X/27/4 POP overrides MOT\n"); } if (NO_PAGE(pgno)) { printv("... dead MOT link %d\n", i); return FALSE; /* has no link (yet) */ } printv("... %s obj\n", (source == 3) ? "global" : "public"); trip = resolve_obj_address (vbi, &trip_cp, new_type, pgno, (p->address << 7) + p->data, function, &remaining_max_triplets); if (!trip) return FALSE; } row = es.inv_row + es.active_row; column = es.inv_column + es.active_column; if (!enhance(vbi, mag, ext, pg, vtp, new_type, trip, remaining_max_triplets, row + offset_row, column + offset_column, max_level, header_only, NULL)) { cache_page_unref (trip_cp); trip_cp = NULL; return FALSE; } printv("... object done\n"); cache_page_unref (trip_cp); trip_cp = NULL; offset_row = 0; offset_column = 0; break; } case 0x14: /* reserved */ break; case 0x15 ... 0x17: /* object definition */ enhance_flush_row (&es); printv("enh obj definition 0x%02x 0x%02x\n", p->mode, p->data); printv("enh terminated\n"); goto swedish; case 0x18: /* drcs mode */ printv("enh DRCS mode 0x%02x\n", p->data); drcs_s1[p->data >> 6] = p->data & 15; break; case 0x19 ... 0x1E: /* reserved */ break; case 0x1F: /* termination marker */ default: terminate: enhance_flush_row (&es); printv("enh terminated %02x\n", p->mode); goto swedish; } } else { /* * Column address triplets */ int s = p->data >> 5; int column = p->address; int unicode; switch (p->mode) { case 0x00: /* foreground color */ if (max_level >= VBI_WST_LEVEL_2p5 && s == 0) { if (column > es.active_column) enhance_flush (&es, column); es.ac.foreground = p->data & 0x1F; es.mac.foreground = ~0; printv("enh col %d foreground %d\n", es.active_column, es.ac.foreground); } break; case 0x01: /* G1 block mosaic character */ if (max_level >= VBI_WST_LEVEL_2p5) { if (column > es.active_column) enhance_flush (&es, column); if (p->data & 0x20) { unicode = 0xEE00 + p->data; /* G1 contiguous */ goto store; } else if (p->data >= 0x40) { unicode = vbi_teletext_unicode( font->G0, NO_SUBSET, p->data); goto store; } } break; case 0x0B: /* G3 smooth mosaic or line drawing character */ if (max_level < VBI_WST_LEVEL_2p5) break; /* fall through */ case 0x02: /* G3 smooth mosaic or line drawing character */ if (p->data >= 0x20) { if (column > es.active_column) enhance_flush (&es, column); unicode = 0xEF00 + p->data; goto store; } break; case 0x03: /* background color */ if (max_level >= VBI_WST_LEVEL_2p5 && s == 0) { if (column > es.active_column) enhance_flush (&es, column); es.ac.background = p->data & 0x1F; es.mac.background = ~0; printv("enh col %d background %d\n", es.active_column, es.ac.background); } break; case 0x04: /* reserved */ case 0x05: /* reserved */ break; case 0x06: /* PDC data - Cursor Column and Announced Starting */ /* and Finishing Time Minutes */ if (!ptable) break; s = (p->data >> 4) * 10 + (p->data & 15); if (pdc_hr == 0x0A) { pt->at2 += s; if (pt > ptable && pt[-1].length == 0) { pt[-1].length = pt->at2 - pt[-1].at2; if (pt->at2 < pt[-1].at2) pt[-1].length += 24 * 60; if (pt[-1].length >= 12 * 60) { /* bullshit */ pt[-1] = pt[0]; pt--; } } } else if (pdc_hr == 0x0B) { pt->length += s; if (pt->length >= 4 * 600) { pt->length -= 4 * 600; } else { if (pt->length < pt->at2) pt->length += 24 * 60; pt->length -= pt->at2; } } else { return FALSE; } pt->column = column; pdc_hr = 0; break; case 0x07: /* additional flash functions */ if (max_level >= VBI_WST_LEVEL_2p5) { if (column > es.active_column) enhance_flush (&es, column); /* * Only one flash function (if any) implemented: * Mode 1 - Normal flash to background color * Rate 0 - Slow rate (1 Hz) */ es.ac.flash = !!(p->data & 3); es.mac.flash = ~0; printv("enh col %d flash 0x%02x\n", es.active_column, p->data); } break; case 0x08: /* modified G0 and G2 character set designation */ if (max_level >= VBI_WST_LEVEL_2p5) { if (column > es.active_column) enhance_flush (&es, column); if (VALID_CHARACTER_SET(p->data)) font = vbi_font_descriptors + p->data; else font = pg->font[0]; printv("enh col %d modify character set %d\n", es.active_column, p->data); } break; case 0x09: /* G0 character */ if (max_level >= VBI_WST_LEVEL_2p5 && p->data >= 0x20) { if (column > es.active_column) enhance_flush (&es, column); unicode = vbi_teletext_unicode(font->G0, NO_SUBSET, p->data); goto store; } break; case 0x0A: /* reserved */ break; case 0x0C: /* display attributes */ if (max_level < VBI_WST_LEVEL_2p5) break; if (column > es.active_column) enhance_flush (&es, column); es.ac.size = ((p->data & 0x40) ? VBI_DOUBLE_WIDTH : 0) + ((p->data & 1) ? VBI_DOUBLE_HEIGHT : 0); es.mac.size = ~0; if (vtp->flags & (C5_NEWSFLASH | C6_SUBTITLE)) { if (p->data & 2) { es.ac.opacity = VBI_SEMI_TRANSPARENT; } else { es.ac.opacity = pg->page_opacity[1]; } es.mac.opacity = ~0; } else { es.row_color_transparent = p->data & 2; } es.ac.conceal = !!(p->data & 4); es.mac.conceal = ~0; /* (p->data & 8) reserved */ es.invert = p->data & 0x10; es.ac.underline = !!(p->data & 0x20); es.mac.underline = ~0; printv("enh col %d display attr 0x%02x\n", es.active_column, p->data); break; case 0x0D: /* drcs character invocation */ { int normal = p->data >> 6; int offset = p->data & 0x3F; enum ttx_page_function function; int pgno, page, i = 0; if (max_level < VBI_WST_LEVEL_2p5) break; if (offset >= 48) break; /* invalid */ if (column > es.active_column) enhance_flush (&es, column); page = normal * 16 + drcs_s1[normal]; printv("enh col %d DRCS %d/0x%02x\n", es.active_column, page, p->data); /* if (!pg->drcs[page]) */ { cache_page *dvtp; if (!normal) { function = PAGE_FUNCTION_GDRCS; pgno = vtp->data.lop.link[26].pgno; if (NO_PAGE(pgno)) { if (max_level < VBI_WST_LEVEL_3p5 || NO_PAGE(pgno = mag->drcs_link[1][0])) pgno = mag->drcs_link[0][0]; } else printv("... X/27/4 GDRCS overrides MOT\n"); } else { function = PAGE_FUNCTION_DRCS; pgno = vtp->data.lop.link[25].pgno; if (NO_PAGE(pgno)) { if ((i = mag->drcs_lut[vtp->pgno & 0xFF]) == 0) { printv("... MOT drcs_lut empty\n"); return FALSE; /* has no link (yet) */ } if (max_level < VBI_WST_LEVEL_3p5 || NO_PAGE(pgno = mag->drcs_link[1][i])) pgno = mag->drcs_link[0][i]; } else printv("... X/27/4 DRCS overrides MOT\n"); } if (NO_PAGE(pgno)) { printv("... dead MOT link %d\n", i); return FALSE; /* has no link (yet) */ } printv("... %s drcs from page %03x/%04x\n", normal ? "normal" : "global", pgno, drcs_s1[normal]); dvtp = _vbi_cache_get_page (vbi->ca, vbi->cn, pgno, drcs_s1[normal], /* subno_mask */ 0x000F); if (!dvtp) { printv("... page not cached\n"); return FALSE; } if (dvtp->function == PAGE_FUNCTION_UNKNOWN) { cache_page *new_cp; new_cp = vbi_convert_page (vbi, dvtp, TRUE, function); if (NULL == new_cp) { printv("... no g/drcs page or hamming error\n"); cache_page_unref (dvtp); dvtp = NULL; return FALSE; } dvtp = new_cp; } else if (dvtp->function == PAGE_FUNCTION_DRCS) { dvtp->function = function; } else if (dvtp->function != function) { printv("... source page wrong function %d, expected %d\n", dvtp->function, function); cache_page_unref (dvtp); dvtp = NULL; return FALSE; } if (dvtp->data.drcs.invalid & (1ULL << offset)) { printv("... invalid drcs, prob. tx error\n"); cache_page_unref (dvtp); dvtp = NULL; return FALSE; } pg->drcs[page] = dvtp->data.drcs.chars[0]; cache_page_unref (dvtp); dvtp = NULL; } unicode = 0xF000 + (page << 6) + offset; goto store; } case 0x0E: /* font style */ { int italic, bold, proportional; int col, row, count; vbi_char *acp; if (max_level < VBI_WST_LEVEL_3p5) break; row = es.inv_row + es.active_row; count = (p->data >> 4) + 1; acp = &pg->text[row * EXT_COLUMNS]; proportional = (p->data >> 0) & 1; bold = (p->data >> 1) & 1; italic = (p->data >> 2) & 1; while (row < ROWS && count > 0) { for (col = inv_column + column; col < COLUMNS; col++) { acp[col].italic = italic; acp[col].bold = bold; acp[col].proportional = proportional; } acp += EXT_COLUMNS; row++; count--; } printv("enh col %d font style 0x%02x\n", es.active_column, p->data); break; } case 0x0F: /* G2 character */ if (p->data >= 0x20) { if (column > es.active_column) enhance_flush (&es, column); unicode = vbi_teletext_unicode(font->G2, NO_SUBSET, p->data); goto store; } break; case 0x10 ... 0x1F: /* characters including diacritical marks */ if (p->data >= 0x20) { if (column > es.active_column) enhance_flush (&es, column); unicode = vbi_teletext_composed_unicode( p->mode - 0x10, p->data); store: printv("enh row %d col %d print 0x%02x/0x%02x -> 0x%04x\n", es.active_row, es.active_column, p->mode, p->data, unicode); es.ac.unicode = unicode; es.mac.unicode = ~0; } break; } } } swedish: if (ptable) { if (pt >= ptable && (pdc_hr || pt->length == 0)) pt--; /* incomplete start or end tag */ if (1) dump_pex26(ptable, pt - ptable + 1); } if (0) { es.acp = pg->text; for (es.active_row = 0; es.active_row < ROWS; es.active_row++) { printv("%2d: ", es.active_row); for (es.active_column = 0; es.active_column < COLUMNS; es.acp++, es.active_column++) { printv("%04x ", es.acp->unicode); } printv("\n"); es.acp += EXT_COLUMNS - COLUMNS; } } return TRUE; } static void post_enhance(vbi_page *pg, int display_rows) { int last_row = MIN(display_rows, ROWS) - 2; vbi_char ac, *acp; int column, row; acp = pg->text; for (row = 0; row <= last_row; row++) { for (column = 0; column < COLUMNS; acp++, column++) { if (1) printv("%c", _vbi_to_ascii (acp->unicode)); else printv("%04xF%dB%dS%dO%d ", acp->unicode, acp->foreground, acp->background, acp->size, acp->opacity); if (acp->opacity == VBI_TRANSPARENT_SPACE || (acp->foreground == VBI_TRANSPARENT_BLACK && acp->background == VBI_TRANSPARENT_BLACK)) { acp->opacity = VBI_TRANSPARENT_SPACE; acp->unicode = 0x0020; } else if (acp->background == VBI_TRANSPARENT_BLACK) { acp->opacity = VBI_SEMI_TRANSPARENT; } /* transparent foreground not implemented */ switch (acp->size) { case VBI_NORMAL_SIZE: if (row < last_row && (acp[EXT_COLUMNS].size == VBI_DOUBLE_HEIGHT2 || acp[EXT_COLUMNS].size == VBI_DOUBLE_SIZE2)) { acp[EXT_COLUMNS].unicode = 0x0020; acp[EXT_COLUMNS].size = VBI_NORMAL_SIZE; } if (column < 39 && (acp[1].size == VBI_OVER_TOP || acp[1].size == VBI_OVER_BOTTOM)) { acp[1].unicode = 0x0020; acp[1].size = VBI_NORMAL_SIZE; } break; case VBI_DOUBLE_HEIGHT: if (row < last_row) { ac = acp[0]; ac.size = VBI_DOUBLE_HEIGHT2; acp[EXT_COLUMNS] = ac; } break; case VBI_DOUBLE_SIZE: if (row < last_row) { ac = acp[0]; ac.size = VBI_DOUBLE_SIZE2; acp[EXT_COLUMNS] = ac; ac.size = VBI_OVER_BOTTOM; acp[EXT_COLUMNS + 1] = ac; } /* fall through */ case VBI_DOUBLE_WIDTH: if (column < 39) { ac = acp[0]; ac.size = VBI_OVER_TOP; acp[1] = ac; } break; default: break; } } printv("\n"); acp += EXT_COLUMNS - COLUMNS; } } static inline vbi_bool default_object_invocation (vbi_decoder * vbi, struct ttx_magazine * mag, struct ttx_extension * ext, vbi_page * pg, cache_page * vtp, vbi_wst_level max_level, vbi_bool header_only) { struct ttx_pop_link *pop; int i, order; if (!(i = mag->pop_lut[vtp->pgno & 0xFF])) return FALSE; /* has no link (yet) */ pop = &mag->pop_link[1][i]; if (max_level < VBI_WST_LEVEL_3p5 || NO_PAGE(pop->pgno)) { pop = &mag->pop_link[0][i]; if (NO_PAGE(pop->pgno)) { printv("default object has dead MOT pop link %d\n", i); return FALSE; } } order = pop->default_obj[0].type > pop->default_obj[1].type; for (i = 0; i < 2; i++) { enum ttx_object_type type = pop->default_obj[i ^ order].type; cache_page *trip_cp = NULL; struct ttx_triplet *trip; int remaining_max_triplets; if (type == OBJECT_TYPE_NONE) continue; printv("default object #%d invocation, type %d\n", i ^ order, type); trip = resolve_obj_address(vbi, &trip_cp, type, pop->pgno, pop->default_obj[i ^ order].address, PAGE_FUNCTION_POP, &remaining_max_triplets); if (!trip) return FALSE; if (!enhance(vbi, mag, ext, pg, vtp, type, trip, remaining_max_triplets, 0, 0, max_level, header_only, NULL)) { cache_page_unref (trip_cp); return FALSE; } cache_page_unref (trip_cp); } return TRUE; } /** * @internal * * Artificial 41st column. Often column 0 of a LOP contains only set-after * attributes and thus all black spaces, unlike column 39. To balance the * view we add a black column 40. If OTOH column 0 has been modified using * enhancement we extend column 39. */ static void column_41 (vbi_page * pg, struct ttx_extension * ext) { vbi_char *acp; unsigned int row; vbi_bool black0; vbi_bool cont39; if (41 != pg->columns) return; acp = pg->text; /* Header. */ acp[40] = acp[39]; acp[40].unicode = 0x0020; if (1 == pg->rows) return; /* Body. */ acp += 41; black0 = TRUE; cont39 = TRUE; for (row = 1; row <= 24; ++row) { if (0x0020 != acp[0].unicode || (VBI_BLACK != acp[0].background && 32 != acp[0].background)) { black0 = FALSE; } if (vbi_is_gfx (acp[39].unicode)) { if (acp[38].unicode != acp[39].unicode || acp[38].foreground != acp[39].foreground || acp[38].background != acp[39].background) { cont39 = FALSE; } } acp += 41; } acp = pg->text + 41; if (!black0 && cont39) { for (row = 1; row <= 24; ++row) { acp[40] = acp[39]; if (!vbi_is_gfx (acp[39].unicode)) acp[40].unicode = 0x0020; acp += 41; } } else { vbi_char ac; CLEAR (ac); ac.unicode = 0x0020; ac.foreground = ext->foreground_clut + VBI_WHITE; ac.background = ext->background_clut + VBI_BLACK; ac.opacity = pg->page_opacity[1]; for (row = 1; row <= 24; ++row) { acp[40] = ac; acp += 41; } } /* Navigation bar. */ acp[40] = acp[39]; acp[40].unicode = 0x0020; } /** * @internal * @param vbi Initialized vbi_decoder context. * @param pg Place to store the formatted page. * @param vtp Raw Teletext page. * @param max_level Format the page at this Teletext implementation level. * @param display_rows Number of rows to format, between 1 ... 25. * @param navigation Analyse the page and add navigation links, * including TOP and FLOF. * * Format a page @a pg from a raw Teletext page @a vtp. This function is * used internally by libzvbi only. * * @return * @c TRUE if the page could be formatted. */ int vbi_format_vt_page(vbi_decoder *vbi, vbi_page *pg, cache_page *vtp, vbi_wst_level max_level, int display_rows, vbi_bool navigation) { char buf[16]; struct ttx_magazine *mag; struct ttx_extension *ext; int column, row, i; if (vtp->function != PAGE_FUNCTION_LOP && vtp->function != PAGE_FUNCTION_EACEM_TRIGGER) return FALSE; printv("\nFormatting page %03x/%04x pg=%p lev=%d rows=%d nav=%d\n", vtp->pgno, vtp->subno, pg, max_level, display_rows, navigation); display_rows = SATURATE(display_rows, 1, ROWS); pg->vbi = vbi; pg->nuid = vbi->network.ev.network.nuid; pg->pgno = vtp->pgno; pg->subno = vtp->subno; pg->rows = display_rows; pg->columns = EXT_COLUMNS; pg->dirty.y0 = 0; pg->dirty.y1 = ROWS - 1; pg->dirty.roll = 0; mag = (max_level <= VBI_WST_LEVEL_1p5) ? &vbi->vt.default_magazine : cache_network_magazine (vbi->cn, vtp->pgno); if (vtp->x28_designations & 0x11) ext = &vtp->data.ext_lop.ext; else ext = &mag->extension; /* Character set designation */ character_set_designation(pg->font, ext, vtp); /* Colors */ screen_color(pg, vtp->flags, ext->def_screen_color); vbi_transp_colormap(vbi, pg->color_map, ext->color_map, 40); pg->drcs_clut = ext->drcs_clut; /* Opacity */ pg->page_opacity[1] = (vtp->flags & (C5_NEWSFLASH | C6_SUBTITLE | C10_INHIBIT_DISPLAY)) ? VBI_TRANSPARENT_SPACE : VBI_OPAQUE; pg->boxed_opacity[1] = (vtp->flags & C10_INHIBIT_DISPLAY) ? VBI_TRANSPARENT_SPACE : VBI_SEMI_TRANSPARENT; if (vtp->flags & C7_SUPPRESS_HEADER) { pg->page_opacity[0] = VBI_TRANSPARENT_SPACE; pg->boxed_opacity[0] = VBI_TRANSPARENT_SPACE; } else { pg->page_opacity[0] = pg->page_opacity[1]; pg->boxed_opacity[0] = pg->boxed_opacity[1]; } /* DRCS */ memset(pg->drcs, 0, sizeof(pg->drcs)); /* Current page number in header */ snprintf (buf, sizeof (buf), "\2%x.%02x\7", vtp->pgno, vtp->subno & 0xff); /* Level 1 formatting */ i = 0; pg->double_height_lower = 0; for (row = 0; row < display_rows; row++) { struct vbi_font_descr *font; int mosaic_unicodes; /* 0xEE00 separate, 0xEE20 contiguous */ int held_mosaic_unicode; int esc; vbi_bool hold, mosaic; vbi_bool double_height, wide_char; vbi_char ac, *acp = &pg->text[row * EXT_COLUMNS]; held_mosaic_unicode = 0xEE20; /* G1 block mosaic, blank, contiguous */ memset(&ac, 0, sizeof(ac)); ac.unicode = 0x0020; ac.foreground = ext->foreground_clut + VBI_WHITE; ac.background = ext->background_clut + VBI_BLACK; mosaic_unicodes = 0xEE20; /* contiguous */ ac.opacity = pg->page_opacity[row > 0]; font = pg->font[0]; esc = 0; hold = FALSE; mosaic = FALSE; double_height = FALSE; wide_char = FALSE; acp[COLUMNS] = ac; /* artificial column 41 */ for (column = 0; column < COLUMNS; ++column) { int raw; if (row == 0 && column < 8) { raw = buf[column]; i++; } else if ((raw = vbi_unpar8 (vtp->data.lop.raw[0][i++])) < 0) raw = ' '; /* set-at spacing attributes */ switch (raw) { case 0x09: /* steady */ ac.flash = FALSE; break; case 0x0C: /* normal size */ ac.size = VBI_NORMAL_SIZE; break; case 0x18: /* conceal */ ac.conceal = TRUE; break; case 0x19: /* contiguous mosaics */ mosaic_unicodes = 0xEE20; break; case 0x1A: /* separated mosaics */ mosaic_unicodes = 0xEE00; break; case 0x1C: /* black background */ ac.background = ext->background_clut + VBI_BLACK; break; case 0x1D: /* new background */ ac.background = ext->background_clut + (ac.foreground & 7); break; case 0x1E: /* hold mosaic */ hold = TRUE; break; } if (raw <= 0x1F) { ac.unicode = (hold & mosaic) ? held_mosaic_unicode : 0x0020; } else { if (mosaic && (raw & 0x20)) { held_mosaic_unicode = mosaic_unicodes + raw - 0x20; ac.unicode = held_mosaic_unicode; } else ac.unicode = vbi_teletext_unicode(font->G0, font->subset, raw); } if (wide_char) { wide_char = FALSE; } else { acp[column] = ac; wide_char = /*!!*/(ac.size & VBI_DOUBLE_WIDTH); if (wide_char) { if (column < (COLUMNS - 1)) { acp[column + 1] = ac; acp[column + 1].size = VBI_OVER_TOP; } else { acp[column].size = VBI_NORMAL_SIZE; wide_char = FALSE; } } } /* set-after spacing attributes */ switch (raw) { case 0x00 ... 0x07: /* alpha + foreground color */ ac.foreground = ext->foreground_clut + (raw & 7); ac.conceal = FALSE; mosaic = FALSE; break; case 0x08: /* flash */ ac.flash = TRUE; break; case 0x0A: /* end box */ if (column < (COLUMNS - 1) && vbi_unpar8 (vtp->data.lop.raw[0][i]) == 0x0a) ac.opacity = pg->page_opacity[row > 0]; break; case 0x0B: /* start box */ if (column < (COLUMNS - 1) && vbi_unpar8 (vtp->data.lop.raw[0][i]) == 0x0b) ac.opacity = pg->boxed_opacity[row > 0]; break; case 0x0D: /* double height */ if (row <= 0 || row >= 23) break; ac.size = VBI_DOUBLE_HEIGHT; double_height = TRUE; break; case 0x0E: /* double width */ printv("spacing col %d row %d double width\n", column, row); if (column < (COLUMNS - 1)) ac.size = VBI_DOUBLE_WIDTH; break; case 0x0F: /* double size */ printv("spacing col %d row %d double size\n", column, row); if (column >= (COLUMNS - 1) || row <= 0 || row >= 23) break; ac.size = VBI_DOUBLE_SIZE; double_height = TRUE; break; case 0x10 ... 0x17: /* mosaic + foreground color */ ac.foreground = ext->foreground_clut + (raw & 7); ac.conceal = FALSE; mosaic = TRUE; break; case 0x1F: /* release mosaic */ hold = FALSE; break; case 0x1B: /* ESC */ font = pg->font[esc ^= 1]; break; } } if (double_height) { for (column = 0; column < EXT_COLUMNS; column++) { ac = acp[column]; switch (ac.size) { case VBI_DOUBLE_HEIGHT: ac.size = VBI_DOUBLE_HEIGHT2; acp[EXT_COLUMNS + column] = ac; break; case VBI_DOUBLE_SIZE: ac.size = VBI_DOUBLE_SIZE2; acp[EXT_COLUMNS + column] = ac; ac.size = VBI_OVER_BOTTOM; acp[EXT_COLUMNS + (++column)] = ac; break; default: /* NORMAL, DOUBLE_WIDTH, OVER_TOP */ ac.size = VBI_NORMAL_SIZE; ac.unicode = 0x0020; acp[EXT_COLUMNS + column] = ac; break; } } i += COLUMNS; row++; pg->double_height_lower |= 1 << row; } } if (0) { if (row < ROWS) { vbi_char ac; memset(&ac, 0, sizeof(ac)); ac.foreground = ext->foreground_clut + VBI_WHITE; ac.background = ext->background_clut + VBI_BLACK; ac.opacity = pg->page_opacity[1]; ac.unicode = 0x0020; for (i = row * EXT_COLUMNS; i < ROWS * EXT_COLUMNS; i++) pg->text[i] = ac; } } /* Local enhancement data and objects */ if (max_level >= VBI_WST_LEVEL_1p5 && display_rows > 0) { vbi_page page; vbi_bool success; memcpy(&page, pg, sizeof(page)); if (!(vtp->flags & (C5_NEWSFLASH | C6_SUBTITLE))) { pg->boxed_opacity[0] = VBI_TRANSPARENT_SPACE; pg->boxed_opacity[1] = VBI_TRANSPARENT_SPACE; } if (vtp->x26_designations & 1) { printv("enhancement packets %08x\n", vtp->x26_designations); success = enhance(vbi, mag, ext, pg, vtp, LOCAL_ENHANCEMENT_DATA, vtp->data.enh_lop.enh, elements(vtp->data.enh_lop.enh), 0, 0, max_level, display_rows == 1, NULL); } else success = default_object_invocation(vbi, mag, ext, pg, vtp, max_level, display_rows == 1); if (success) { if (max_level >= VBI_WST_LEVEL_2p5) post_enhance(pg, display_rows); } else memcpy(pg, &page, sizeof(*pg)); } /* Navigation */ if (navigation) { pg->nav_link[5].pgno = vbi->cn->initial_page.pgno; pg->nav_link[5].subno = vbi->cn->initial_page.subno; for (row = 1; row < MIN(ROWS - 1, display_rows); row++) zap_links(pg, row); if (display_rows >= ROWS) { if (vtp->data.lop.have_flof) { if (vtp->data.lop.link[5].pgno >= 0x100 && vtp->data.lop.link[5].pgno <= 0x899 && (vtp->data.lop.link[5].pgno & 0xFF) != 0xFF) { pg->nav_link[5].pgno = vtp->data.lop.link[5].pgno; pg->nav_link[5].subno = vtp->data.lop.link[5].subno; } if (vtp->lop_packets & (1 << 24)) flof_links(pg, vtp); else flof_navigation_bar(pg, vtp); } else if (vbi->cn->have_top) top_navigation_bar(vbi, pg, vtp); // pdc_method_a(pg, vtp, NULL); } } column_41 (pg, ext); if (0) { vbi_char *acp; unsigned int i; for (row = 0, acp = pg->text + EXT_COLUMNS * row; row < ROWS; row++) { fprintf(stderr, "%2d: ", row); for (column = 0; column < COLUMNS; acp++, column++) { fprintf(stderr, "%04x ", acp->unicode); } fprintf(stderr, "\n"); acp += EXT_COLUMNS - COLUMNS; } for (i = 0; i < N_ELEMENTS (pg->color_map); ++i) { fprintf (stderr, "%08x ", pg->color_map[i]); if (3 == (i & 3)) fputc ('\n', stderr); } } return TRUE; } /** * @param vbi Initialized vbi_decoder context. * @param pg Place to store the formatted page. * @param pgno Page number of the page to fetch, see vbi_pgno. * @param subno Subpage number to fetch (optional @c VBI_ANY_SUBNO). * @param max_level Format the page at this Teletext implementation level. * @param display_rows Number of rows to format, between 1 ... 25. * @param navigation Analyse the page and add navigation links, * including TOP and FLOF. * * Fetches a Teletext page designated by @a pgno and @a subno from the * cache, formats and stores it in @a pg. Formatting is limited to row * 0 ... @a display_rows - 1 inclusive. The really useful values * are 1 (format header only) or 25 (everything). Likewise * @a navigation can be used to save unnecessary formatting time. * * Although safe to do, this function is not supposed to be called from * an event handler since rendering may block decoding for extended * periods of time. * * @return * @c FALSE if the page is not cached or could not be formatted * for other reasons, for instance is a data page not intended for * display. Level 2.5/3.5 pages which could not be formatted e. g. * due to referencing data pages not in cache are formatted at a * lower level. */ vbi_bool vbi_fetch_vt_page(vbi_decoder *vbi, vbi_page *pg, vbi_pgno pgno, vbi_subno subno, vbi_wst_level max_level, int display_rows, vbi_bool navigation) { cache_page *vtp; vbi_bool success; int row; switch (pgno) { case 0x900: if (subno == VBI_ANY_SUBNO) subno = 0; if (!vbi->cn->have_top || !top_index(vbi, pg, subno)) return FALSE; pg->nuid = vbi->network.ev.network.nuid; pg->pgno = 0x900; pg->subno = subno; post_enhance(pg, ROWS); for (row = 1; row < ROWS; row++) zap_links(pg, row); return TRUE; default: vtp = _vbi_cache_get_page (vbi->ca, vbi->cn, pgno, subno, -1); if (!vtp) return FALSE; success = vbi_format_vt_page(vbi, pg, vtp, max_level, display_rows, navigation); cache_page_unref (vtp); return success; } } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/teletext_decoder.h000066400000000000000000000066311476363111200170500ustar00rootroot00000000000000/* * libzvbi -- Teletext decoder internals * * Copyright (C) 2000, 2001, 2008 Michael H. Schimek * * Originally based on AleVT 1.5.1 by Edgar Toernig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: teletext_decoder.h,v 1.3 2013-07-02 02:32:41 mschimek Exp $ */ #ifndef TELETEXT_H #define TELETEXT_H #include "cache-priv.h" struct raw_page { cache_page page[1]; uint8_t lop_raw[26][40]; uint8_t drcs_mode[48]; unsigned int lop_packets; int num_triplets; int ait_page; }; /* Public */ /** * @ingroup HiDec * @brief Teletext implementation level. */ typedef enum { VBI_WST_LEVEL_1, /**< 1 - Basic Teletext pages */ VBI_WST_LEVEL_1p5, /**< 1.5 - Additional national and graphics characters */ /** * 2.5 - Additional text styles, more colors and DRCS. You should * enable Level 2.5 only if you can render and/or export such pages. */ VBI_WST_LEVEL_2p5, VBI_WST_LEVEL_3p5 /**< 3.5 - Multicolor DRCS, proportional script */ } vbi_wst_level; /* Private */ struct teletext { vbi_wst_level max_level; struct ttx_page_link header_page; uint8_t header[40]; struct ttx_magazine default_magazine; int region; struct raw_page raw_page[8]; struct raw_page *current; }; /* Public */ /** * @addtogroup Service * @{ */ extern void vbi_teletext_set_default_region(vbi_decoder *vbi, int default_region); extern void vbi_teletext_set_level(vbi_decoder *vbi, int level); /** @} */ /** * @addtogroup Cache * @{ */ extern vbi_bool vbi_fetch_vt_page(vbi_decoder *vbi, vbi_page *pg, vbi_pgno pgno, vbi_subno subno, vbi_wst_level max_level, int display_rows, vbi_bool navigation); extern int vbi_page_title(vbi_decoder *vbi, int pgno, int subno, char *buf); /** @} */ /** * @addtogroup Event * @{ */ extern void vbi_resolve_link(vbi_page *pg, int column, int row, vbi_link *ld); extern void vbi_resolve_home(vbi_page *pg, vbi_link *ld); /** @} */ /* Private */ /* packet.c */ extern void vbi_teletext_init(vbi_decoder *vbi); extern void vbi_teletext_destroy(vbi_decoder *vbi); extern vbi_bool vbi_decode_teletext(vbi_decoder *vbi, uint8_t *p); extern void vbi_teletext_desync(vbi_decoder *vbi); extern void vbi_teletext_channel_switched(vbi_decoder *vbi); extern cache_page * vbi_convert_page(vbi_decoder *vbi, cache_page *vtp, vbi_bool cached, enum ttx_page_function new_function); extern void vbi_decode_vps(vbi_decoder *vbi, uint8_t *p); /* teletext.c */ extern vbi_bool vbi_format_vt_page(vbi_decoder *, vbi_page *, cache_page *, vbi_wst_level max_level, int display_rows, vbi_bool navigation); #endif /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/trigger.c000066400000000000000000000352451476363111200151660ustar00rootroot00000000000000/* * libzvbi -- Triggers * * Implementation of EACEM TP 14-99-16 "Data Broadcasting", rev 0.8; * ATVEF "Enhanced Content Specification", v1.1 (www.atvef.com); * and http://developer.webtv.net * * Copyright (C) 2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: trigger.c,v 1.14 2008-02-19 00:35:22 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "misc.h" #include "trigger.h" #include "tables.h" #include "vbi.h" struct vbi_trigger { vbi_trigger * next; vbi_link link; double fire; unsigned char view; vbi_bool _delete; }; static vbi_bool verify_checksum(const char *s, int count, int checksum) { register unsigned long sum2, sum1 = checksum; for (; count > 1; count -= 2) { sum1 += (unsigned long) *s++ << 8; sum1 += (unsigned long) *s++; } sum2 = sum1; /* * There seems to be confusion about how left-over * bytes shall be added, the example C code in * RFC 1071 subclause 4.1 contradicts the definition * in subclause 1 (zero pad to 16 bit). */ if (count > 0) { sum1 += (unsigned long) *s << 8; /* correct */ sum2 += (unsigned long) *s << 0; /* wrong */ } while (sum1 >= (1 << 16)) sum1 = (sum1 & 0xFFFFUL) + (sum1 >> 16); while (sum2 >= (1 << 16)) sum2 = (sum2 & 0xFFFFUL) + (sum2 >> 16); return sum1 == 0xFFFFUL || sum2 == 0xFFFFUL; } static int parse_dec(const char *s, int digits) { int n = 0; while (digits-- > 0) { if (!isdigit(*s)) return -1; n = n * 10 + *s++ - '0'; } return n; } static int parse_hex(const char *s, int digits) { int n = 0; while (digits-- > 0) { if (!isxdigit(*s)) return -1; n = n * 16 + (*s & 15) + ((*s > '9') ? 9 : 0); s++; } return n; } /* * XXX http://developer.webtv.net/itv/tvlink/main.htm adds more ...??? */ static time_t parse_date(const char *s) { struct tm tm; memset(&tm, 0, sizeof(tm)); if ((tm.tm_year = parse_dec(s + 0, 4)) < 0 || (tm.tm_mon = parse_dec(s + 4, 2)) < 0 || (tm.tm_mday = parse_dec(s + 6, 2)) < 0) return (time_t) -1; if (s[8]) { if (s[8] != 'T' || (tm.tm_hour = parse_dec(s + 9, 2)) < 0 || (tm.tm_min = parse_dec(s + 11, 2)) < 0) return (time_t) -1; if (s[13] && (tm.tm_sec = parse_dec(s + 13, 2)) < 0) return (time_t) -1; } tm.tm_year -= 1900; return mktime(&tm); } static int parse_time(const char *s) { int seconds, frames = 0; seconds = strtoul(s, (char **) &s, 10); if (*s) if (*s != 'F' || (frames = parse_dec(s + 1, 2)) < 0) return -1; return seconds * 25 + frames; } static int parse_bool(char *s) { return (strcmp(s, "1") == 0) || (strcasecmp(s, "true") == 0); } static int keyword(char *s, const char **keywords, int num) { int i; if (!s[0]) return -1; else if (!s[1]) { for (i = 0; i < num; i++) if (tolower(s[0]) == keywords[i][0]) return i; } else for (i = 0; i < num; i++) if (strcasecmp(s, keywords[i]) == 0) return i; return -1; } static char * parse_eacem(vbi_trigger *t, char *s1, unsigned int nuid, double now) { static const char *attributes[] = { "active", "countdown", "delete", "expires", "name", "priority", "script" }; char buf[256]; char *s, *e, *d, *dx; int active, countdown; int c; t->link.url[0] = 0; t->link.name[0] = 0; t->link.script[0] = 0; t->link.priority = 9; t->link.expires = 0.0; t->link.autoload = FALSE; t->_delete = FALSE; t->fire = now; t->view = 'w'; t->link.itv_type = 0; active = INT_MAX; for (s = s1;; s++) { e = s; c = *s; if (c == '<') { if (s != s1) return NULL; d = (char *) t->link.url; dx = d + sizeof(t->link.url) - 2; for (s++; (c = *s) != '>'; s++) if (c && d < dx) *d++ = c; else return NULL; *d++ = 0; } else if (c == '[' || c == '(') { int delim = (c == '[') ? ']' : ')'; char *attr, *text = ""; vbi_bool quote = FALSE; attr = d = buf; dx = d + sizeof(buf) - 2; for (s++; c = *s, c != ':' && c != delim; s++) { if (c == '%') { if ((c = parse_hex(s + 1, 2)) < 0x20) return NULL; s += 2; } if (c && d < dx) *d++ = c; else return NULL; } *d++ = 0; if (!attr[0]) return NULL; s++; if (c != ':') { if (!verify_checksum(s1, e - s1, strtoul(attr, NULL, 16))) { if (0) fprintf(stderr, "checksum mismatch\n"); return NULL; } break; } for (text = d; quote || (c = *s) != delim; s++) { if (c == '"') quote ^= TRUE; else if (c == '%') { if ((c = parse_hex(s + 1, 2)) < 0x20) return NULL; s += 2; } if (c && d < dx) *d++ = c; else return NULL; } *d++ = 0; switch (keyword(attr, attributes, sizeof(attributes) / sizeof(attributes[0]))) { case 0: /* active */ active = parse_time(text); if (active < 0) return NULL; break; case 1: /* countdown */ countdown = parse_time(text); if (countdown < 0) return NULL; t->fire = now + countdown / 25.0; break; case 2: /* delete */ t->_delete = TRUE; break; case 3: /* expires */ t->link.expires = parse_date(text); if (t->link.expires == (time_t) -1) return NULL; break; case 4: /* name */ strlcpy((char *) t->link.name, text, sizeof(t->link.name) - 1); t->link.name[sizeof(t->link.name) - 1] = 0; break; case 5: /* priority */ t->link.priority = strtoul(text, NULL, 10); if (t->link.priority > 9) return NULL; break; case 6: /* script */ strlcpy((char *) t->link.script, text, sizeof(t->link.script) - 1); t->link.script[sizeof(t->link.script) - 1] = 0; break; default: /* ignored */ break; } } else if (c == 0) break; else return NULL; } if (t->link.expires <= 0.0) t->link.expires = t->fire + active / 25.0; /* EACEM eqv PAL/SECAM land, 25 fps */ if (!t->link.url) return NULL; if (strncmp((char *) t->link.url, "http://", 7) == 0) t->link.type = VBI_LINK_HTTP; else if (strncmp((char *) t->link.url, "lid://", 6) == 0) t->link.type = VBI_LINK_LID; else if (strncmp((char *) t->link.url, "tw://", 5) == 0) t->link.type = VBI_LINK_TELEWEB; else if (strncmp((char *) t->link.url, "dummy", 5) == 0) { t->link.pgno = parse_dec((char *) t->link.url + 5, 2); if (!t->link.name || t->link.pgno < 0 || t->link.url[7]) return NULL; t->link.type = VBI_LINK_MESSAGE; } else if (strncmp((char *) t->link.url, "ttx://", 6) == 0) { const struct vbi_cni_entry *p; int cni; cni = parse_hex((char *) t->link.url + 6, 4); if (cni < 0 || t->link.url[10] != '/') return NULL; t->link.pgno = parse_hex((char *) t->link.url + 11, 3); if (t->link.pgno < 0x100 || t->link.url[14] != '/') return NULL; t->link.subno = parse_hex((char *) t->link.url + 15, 4); if (t->link.subno < 0) return NULL; if (cni > 0) { for (p = vbi_cni_table; p->name; p++) if (p->cni1 == cni || p->cni4 == cni) break; if (!p->name) return NULL; t->link.nuid = p->id; } else t->link.nuid = nuid; t->link.type = VBI_LINK_PAGE; } else return NULL; return s; } static char * parse_atvef(vbi_trigger *t, char *s1, double now) { static const char *attributes[] = { "auto", "expires", "name", "script", "type" /* "t" */, "time", "tve", "tve-level", "view" /* "v" */ }; static const char *type_attrs[] = { "program", "network", "station", "sponsor", "operator", "tve" }; char buf[256]; char *s, *e, *d, *dx; int c; t->link.url[0] = 0; t->link.name[0] = 0; t->link.script[0] = 0; t->link.priority = 9; t->fire = now; t->link.expires = 0.0; t->link.autoload = FALSE; t->_delete = FALSE; t->view = 'w'; t->link.itv_type = 0; for (s = s1;; s++) { e = s; c = *s; if (c == '<') { if (s != s1) return NULL; d = (char *) t->link.url; dx = (char *) d + sizeof(t->link.url) - 1; for (s++; (c = *s) != '>'; s++) if (c && d < dx) *d++ = c; else return NULL; *d++ = 0; } else if (c == '[') { char *attr, *text = ""; vbi_bool quote = FALSE; attr = d = buf; dx = d + sizeof(buf) - 2; for (s++; c = *s, c != ':' && c != ']'; s++) { if (c == '%') { if ((c = parse_hex(s + 1, 2)) < 0x20) return NULL; s += 2; } if (c && d < dx) *d++ = c; else return NULL; } *d++ = 0; if (!attr[0]) return NULL; s++; if (c != ':') { unsigned int i; for (i = 1; i < (sizeof(type_attrs) / sizeof(type_attrs[0]) - 1); i++) if (strcasecmp(type_attrs[i], attr) == 0) break; if (i < (sizeof(type_attrs) / sizeof(type_attrs[0]) - 1)) { t->link.itv_type = i + 1; continue; } if (!verify_checksum(s1, e - s1, strtoul(attr, NULL, 16))) return NULL; break; } for (text = d; quote || (c = *s) != ']'; s++) { if (c == '"') quote ^= TRUE; else if (c == '%') { if ((c = parse_hex(s + 1, 2)) < 0x20) return NULL; s += 2; } if (c && d < dx) *d++ = c; else return NULL; } *d++ = 0; switch (keyword(attr, attributes, sizeof(attributes) / sizeof(attributes[0]))) { case 0: /* auto */ t->link.autoload = parse_bool(text); break; case 1: /* expires */ t->link.expires = parse_date(text); if (t->link.expires < 0.0) return NULL; break; case 2: /* name */ strlcpy((char *) t->link.name, text, sizeof(t->link.name) - 1); t->link.name[sizeof(t->link.name) - 1] = 0; break; case 3: /* script */ strlcpy((char *) t->link.script, text, sizeof(t->link.script)); t->link.script[sizeof(t->link.script) - 1] = 0; break; case 4: /* type */ t->link.itv_type = keyword(text, type_attrs, sizeof(type_attrs) / sizeof(type_attrs[0])) + 1; break; case 5: /* time */ t->fire = parse_date(text); if (t->fire < 0.0) return NULL; break; case 6: /* tve */ case 7: /* tve-level */ /* ignored */ break; case 8: /* view (tve == v) */ t->view = *text; break; default: /* ignored */ break; } } else if (c == 0) break; else return NULL; } if (!t->link.url) return NULL; if (strncmp((char *) t->link.url, "http://", 7) == 0) t->link.type = VBI_LINK_HTTP; else if (strncmp((char *) t->link.url, "lid://", 6) == 0) t->link.type = VBI_LINK_LID; else return NULL; return s; } /** * vbi_trigger_flush: * @param vbi Initialized vbi decoding context. * * Discard all triggers stored to fire at a later time. This function * must be called before deleting the @vbi context. **/ void vbi_trigger_flush(vbi_decoder *vbi) { vbi_trigger *t; while ((t = vbi->triggers)) { vbi->triggers = t->next; free(t); } } /** * vbi_deferred_trigger: * @param vbi Initialized vbi decoding context. * * This function must be called at regular intervals, * preferably once per video frame, to fire (send a trigger * event) previously received triggers which reached their * fire time. 'Now' is supposed to be @vbi->time. **/ void vbi_deferred_trigger(vbi_decoder *vbi) { vbi_trigger *t, **tp; for (tp = &vbi->triggers; (t = *tp); tp = &t->next) if (t->fire <= vbi->time) { vbi_event ev; ev.type = VBI_EVENT_TRIGGER; ev.ev.trigger = &t->link; vbi_send_event(vbi, &ev); *tp = t->next; free(t); } else tp = &t->next; } static void add_trigger(vbi_decoder *vbi, vbi_trigger *a) { vbi_trigger *t; if (a->_delete) { vbi_trigger **tp; for (tp = &vbi->triggers; (t = *tp); tp = &t->next) if (strcmp((char *) a->link.url, (char *) t->link.url) == 0 && fabs(a->fire - t->fire) < 0.1) { *tp = t->next; free(t); } else tp = &t->next; return; } for (t = vbi->triggers; t; t = t->next) if (strcmp((char *) a->link.url, (char *) t->link.url) == 0 && fabs(a->fire - t->fire) < 0.1) return; if (a->fire <= vbi->time) { vbi_event ev; ev.type = VBI_EVENT_TRIGGER; ev.ev.trigger = &a->link; vbi_send_event(vbi, &ev); return; } if (!(t = malloc(sizeof(*t)))) return; t->next = vbi->triggers; vbi->triggers = t; } /** * vbi_atvef_trigger: * @param vbi Initialized vbi decoding context. * @param s EACEM string (supposedly ASCII). * * Parse an EACEM string and add it to the trigger list (where it * may fire immediately or at a later time). **/ void vbi_eacem_trigger(vbi_decoder *vbi, unsigned char *s) { vbi_trigger t; char *r; r = (char *) s; while ((r = parse_eacem(&t, r, vbi->network.ev.network.nuid, vbi->time))) { if (0) fprintf(stderr, "At %f eacem link type %d '%s', <%s> '%s', " "%08x %03x.%04x, exp %f, pri %d %d, auto %d; " "fire %f view %d del %d\n", vbi->time, t.link.type, t.link.name, t.link.url, t.link.script, t.link.nuid, t.link.pgno, t.link.subno, t.link.expires, t.link.priority, t.link.itv_type, t.link.autoload, t.fire, t.view, t._delete); t.link.eacem = TRUE; if (t.link.type == VBI_LINK_LID || t.link.type == VBI_LINK_TELEWEB) return; add_trigger(vbi, &t); } } /** * vbi_atvef_trigger: * @param vbi Initialized vbi context. * @param s ATVEF string (ASCII). * * Parse an ATVEF string and add it to the trigger list (where it * may fire immediately or at a later time). **/ void vbi_atvef_trigger(vbi_decoder *vbi, unsigned char *s) { vbi_trigger t; if (parse_atvef(&t, (char *) s, vbi->time)) { if (0) fprintf(stderr, "At %f atvef link type %d '%s', <%s> '%s', " "%08x %03x.%04x, exp %f, pri %d %d, auto %d; " "fire %f view %d del %d\n", vbi->time, t.link.type, t.link.name, t.link.url, t.link.script, t.link.nuid, t.link.pgno, t.link.subno, t.link.expires, t.link.priority, t.link.itv_type, t.link.autoload, t.fire, t.view, t._delete); t.link.eacem = FALSE; if (t.view == 't' /* WebTV */ || strchr((char *) t.link.url, '*') /* trigger matching */ || t.link.type == VBI_LINK_LID) return; add_trigger(vbi, &t); } } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/trigger.h000066400000000000000000000026031476363111200151630ustar00rootroot00000000000000/* * libzvbi -- Triggers * * Copyright (C) 2001 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: trigger.h,v 1.5 2008-02-19 00:35:22 mschimek Exp $ */ #ifndef TRIGGER_H #define TRIGGER_H #ifndef VBI_DECODER #define VBI_DECODER typedef struct vbi_decoder vbi_decoder; #endif /* Private */ typedef struct vbi_trigger vbi_trigger; extern void vbi_trigger_flush(vbi_decoder *vbi); extern void vbi_deferred_trigger(vbi_decoder *vbi); extern void vbi_eacem_trigger(vbi_decoder *vbi, unsigned char *s); extern void vbi_atvef_trigger(vbi_decoder *vbi, unsigned char *s); #endif /* TRIGGER_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/ure.c000066400000000000000000001540751476363111200143210ustar00rootroot00000000000000/* * Copyright 1997, 1998, 1999 Computing Research Labs, * New Mexico State University * * Modifications and fixes for the Zapping 0.5 release by * Iaki Garca Etxebarrria * * Modifications by Michael H. Schimek * for libzvbi 0.1: Added character classes :gfx: and :drcs:, * commented out the surrogate expansion and IGNORE_NONSPACING * in ure_exec since we don't need that. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COMPUTING RESEARCH LAB OR NEW MEXICO STATE UNIVERSITY BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* $Id: ure.c,v 1.9 2013-08-28 14:44:49 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #if defined(HAVE_GLIBC21) #include /* wint_t */ #include /* isw...() */ #define unicode_isalnum(c) iswalnum((wint_t)(c)) #define unicode_isalpha(c) iswalpha((wint_t)(c)) #define unicode_iscntrl(c) iswcntrl((wint_t)(c)) #define unicode_isdigit(c) iswdigit((wint_t)(c)) #define unicode_isgraph(c) iswgraph((wint_t)(c)) #define unicode_islower(c) iswlower((wint_t)(c)) #define unicode_isprint(c) iswprint((wint_t)(c)) #define unicode_ispunct(c) iswpunct((wint_t)(c)) #define unicode_isspace(c) iswspace((wint_t)(c)) #define unicode_isupper(c) iswupper((wint_t)(c)) #define unicode_isxdigit(c) iswxdigit((wint_t)(c)) #define unicode_tolower(c) towlower((wint_t)(c)) #elif defined(HAVE_LIBUNICODE) #include #endif #if defined(HAVE_GLIBC21) || defined(HAVE_LIBUNICODE) #include "ure.h" /* * Set of character class flags. */ #define _URE_ALNUM (1<<0) #define _URE_ALPHA (1<<1) #define _URE_CNTRL (1<<2) #define _URE_DIGIT (1<<3) #define _URE_GRAPH (1<<4) #define _URE_LOWER (1<<5) #define _URE_PRINT (1<<6) #define _URE_PUNCT (1<<7) #define _URE_SPACE (1<<8) #define _URE_UPPER (1<<9) #define _URE_XDIGIT (1<<10) /* These are unused yet */ #define _URE_TITLE (1<<11) #define _URE_DEFINED (1<<12) #define _URE_WIDE (1<<13) #define _URE_NONSPACING (1<<14) #define _URE_SEPARATOR (1<<15) #define _URE_ZVBI_GFX (1<<16) #define _URE_ZVBI_DRCS (1<<17) /* * Flags used internally in the DFA. */ #define _URE_DFA_CASEFOLD 0x01 #define _URE_DFA_BLANKLINE 0x02 /* * Symbol types for the DFA. */ #define _URE_ANY_CHAR 1 #define _URE_CHAR 2 #define _URE_CCLASS 3 #define _URE_NCCLASS 4 #define _URE_BOL_ANCHOR 5 #define _URE_EOL_ANCHOR 6 /* * Op codes for converting the NFA to a DFA. */ #define _URE_SYMBOL 10 #define _URE_PAREN 11 #define _URE_QUEST 12 #define _URE_STAR 13 #define _URE_PLUS 14 #define _URE_ONE 15 #define _URE_AND 16 #define _URE_OR 17 #define _URE_NOOP 0xffff #define _URE_REGSTART 0x8000 #define _URE_REGEND 0x4000 /* * This routine takes a set of URE character property flags (see ure.h) along * with a character and tests to see if the character has one or more of those * properties. */ static inline int #ifdef __STDC__ _ure_matches_properties(unsigned long props, ucs4_t c) #else _ure_matches_properties(props, c) unsigned long props; ucs4_t c; #endif { if ((props & _URE_ALNUM) && (unicode_isalnum(c))) return 1; if ((props & _URE_ALPHA) && (unicode_isalpha(c))) return 1; if ((props & _URE_CNTRL) && (unicode_iscntrl(c))) return 1; if ((props & _URE_DIGIT) && (unicode_isdigit(c))) return 1; if ((props & _URE_GRAPH) && (unicode_isgraph(c))) return 1; if ((props & _URE_LOWER) && (unicode_islower(c))) return 1; if ((props & _URE_PRINT) && (unicode_isprint(c))) return 1; if ((props & _URE_PUNCT) && (unicode_ispunct(c))) return 1; if ((props & _URE_SPACE) && (unicode_isspace(c))) return 1; if ((props & _URE_UPPER) && (unicode_isupper(c))) return 1; if ((props & _URE_XDIGIT) && (unicode_isxdigit(c))) return 1; /* No such characters in libzvbi if ((props & _URE_TITLE) && (unicode_istitle(c))) return 1; if ((props & _URE_DEFINED) && (unicode_isdefined(c))) return 1; if ((props & _URE_WIDE) && (unicode_iswide(c))) return 1; if ((props & _URE_SEPARATOR)) { int type = unicode_type(c); if (type >= UNICODE_LINE_SEPARATOR) return 1; } if (props & _URE_NONSPACING) { int type = unicode_type(c); if (type < UNICODE_LINE_SEPARATOR) return 1; } */ if (props & _URE_NONSPACING) return 1; if (props & _URE_ZVBI_GFX) { if (c >= 0xEE00 && c <= 0xEE7F) return 1; /* Teletext G1 Block Mosaic */ if (c >= 0xEF20 && c <= 0xEF7F) return 1; /* Teletext G3 Smooth Mosaic and Line Drawing */ } if ((props & _URE_ZVBI_DRCS) && (c >= 0xF000 && c <= 0xF7FF)) return 1; /* Teletext DRCS */ return 0; } /* * Structure used to handle a compacted range of characters. */ typedef struct { ucs4_t min_code; ucs4_t max_code; } _ure_range_t; typedef struct { _ure_range_t *ranges; ucs2_t ranges_used; ucs2_t ranges_size; } _ure_ccl_t; typedef union { ucs4_t chr; _ure_ccl_t ccl; } _ure_sym_t; /* * This is a general element structure used for expressions and stack * elements. */ typedef struct { ucs2_t reg; ucs2_t onstack; ucs2_t type; ucs2_t lhs; ucs2_t rhs; } _ure_elt_t; /* * This is a structure used to track a list or a stack of states. */ typedef struct { ucs2_t *slist; ucs2_t slist_size; ucs2_t slist_used; } _ure_stlist_t; /* * Structure to track the list of unique states for a symbol * during reduction. */ typedef struct { ucs2_t id; ucs2_t type; unsigned long mods; unsigned long props; _ure_sym_t sym; _ure_stlist_t states; } _ure_symtab_t; /* * Structure to hold a single state. */ typedef struct { ucs2_t id; ucs2_t accepting; ucs2_t pad; _ure_stlist_t st; _ure_elt_t *trans; ucs2_t trans_size; ucs2_t trans_used; } _ure_state_t; /* * Structure used for keeping lists of states. */ typedef struct { _ure_state_t *states; ucs2_t states_size; ucs2_t states_used; } _ure_statetable_t; /* * Structure to track pairs of DFA states when equivalent states are * merged. */ typedef struct { ucs2_t l; ucs2_t r; } _ure_equiv_t; /* * Structure used for constructing the NFA and reducing to a minimal DFA. */ typedef struct _ure_buffer_t { int reducing; int error; unsigned long flags; _ure_stlist_t stack; /* * Table of unique symbols encountered. */ _ure_symtab_t *symtab; ucs2_t symtab_size; ucs2_t symtab_used; /* * Tracks the unique expressions generated for the NFA and when the NFA is * reduced. */ _ure_elt_t *expr; ucs2_t expr_used; ucs2_t expr_size; /* * The reduced table of unique groups of NFA states. */ _ure_statetable_t states; /* * Tracks states when equivalent states are merged. */ _ure_equiv_t *equiv; ucs2_t equiv_used; ucs2_t equiv_size; } _ure_buffer_t; typedef struct { ucs2_t symbol; ucs2_t next_state; } _ure_trans_t; typedef struct { ucs2_t accepting; ucs2_t ntrans; _ure_trans_t *trans; } _ure_dstate_t; typedef struct _ure_dfa_t { unsigned long flags; _ure_symtab_t *syms; ucs2_t nsyms; _ure_dstate_t *states; ucs2_t nstates; _ure_trans_t *trans; ucs2_t ntrans; } _ure_dfa_t; /************************************************************************* * * Functions. * *************************************************************************/ static void #ifdef __STDC__ _ure_push(ucs2_t v, _ure_buffer_t *b) #else _ure_push(v, b) ucs2_t v; _ure_buffer_t *b; #endif { _ure_stlist_t *s; if (b == 0) return; /* * If the `reducing' parameter is non-zero, check to see if the value * passed is already on the stack. */ if (b->reducing != 0 && b->expr[v].onstack != 0) return; s = &b->stack; if (s->slist_used == s->slist_size) { if (s->slist_size == 0) s->slist = (ucs2_t *) malloc(sizeof(ucs2_t) << 3); else s->slist = (ucs2_t *) realloc((char *) s->slist, sizeof(ucs2_t) * (s->slist_size + 8)); s->slist_size += 8; } s->slist[s->slist_used++] = v; /* * If the `reducing' parameter is non-zero, flag the element as being on * the stack. */ if (b->reducing != 0) b->expr[v].onstack = 1; } static ucs2_t #ifdef __STDC__ _ure_peek(_ure_buffer_t *b) #else _ure_peek(b) _ure_buffer_t *b; #endif { if (b == 0 || b->stack.slist_used == 0) return _URE_NOOP; return b->stack.slist[b->stack.slist_used - 1]; } static ucs2_t #ifdef __STDC__ _ure_pop(_ure_buffer_t *b) #else _ure_pop(b) _ure_buffer_t *b; #endif { ucs2_t v; if (b == 0 || b->stack.slist_used == 0) return _URE_NOOP; v = b->stack.slist[--b->stack.slist_used]; if (b->reducing) b->expr[v].onstack = 0; return v; } /************************************************************************* * * Start symbol parse functions. * *************************************************************************/ static unsigned long cclass_flags[] = { 0, _URE_ALNUM, _URE_ALPHA, _URE_CNTRL, _URE_DIGIT, _URE_GRAPH, _URE_LOWER, _URE_PRINT, _URE_PUNCT, _URE_SPACE, _URE_UPPER, _URE_XDIGIT, _URE_TITLE, _URE_DEFINED, _URE_WIDE, _URE_NONSPACING, _URE_ZVBI_GFX, _URE_ZVBI_DRCS }; /* * Parse a comma-separated list of integers that represent character * properties. Combine them into a mask that is returned in the `mask' * variable, and return the number of characters consumed. */ static unsigned long #ifdef __STDC__ _ure_prop_list(ucs2_t *pp, unsigned long limit, unsigned long *mask, _ure_buffer_t *b) #else _ure_prop_list(pp, limit, mask, b) ucs2_t *pp; unsigned long limit, *mask; _ure_buffer_t *b; #endif { unsigned long n, m; ucs2_t *sp, *ep; sp = pp; ep = sp + limit; for (m = n = 0; b->error == _URE_OK && sp < ep; sp++) { if (*sp == ',') { /* * Encountered a comma, so select the next character property flag * and reset the number. */ m |= cclass_flags[n]; n = 0; } else if (*sp >= '0' && *sp <= '9') /* * Encountered a digit, so start or continue building the cardinal * that represents the character property flag. */ n = (n * 10) + (*sp - '0'); else /* * Encountered something that is not part of the property list. * Indicate that we are done. */ break; /* * If a property number greater than 32 occurs, then there is a * problem. Most likely a missing comma separator. */ if (n > 32) b->error = _URE_INVALID_PROPERTY; } if (n != 0) m |= cclass_flags[n]; /* * Set the mask that represents the group of character properties. */ *mask = m; /* * Return the number of characters consumed. */ return sp - pp; } /* * Collect a hex number with 1 to 4 digits and return the number * of characters used. */ static unsigned long #ifdef __STDC__ _ure_hex(ucs2_t *np, unsigned long limit, ucs4_t *n) #else _ure_hex(np, limit, n) ucs2_t *np; unsigned long limit; ucs4_t *n; #endif { ucs2_t i; ucs2_t *sp, *ep; ucs4_t nn; sp = np; ep = sp + limit; for (nn = 0, i = 0; i < 4 && sp < ep; i++, sp++) { if (*sp >= '0' && *sp <= '9') nn = (nn << 4) + (*sp - '0'); else if (*sp >= 'A' && *sp <= 'F') nn = (nn << 4) + ((*sp - 'A') + 10); else if (*sp >= 'a' && *sp <= 'f') nn = (nn << 4) + ((*sp - 'a') + 10); else /* * Encountered something that is not a hex digit. */ break; } /* * Assign the character code collected and return the number of * characters used. */ *n = nn; return sp - np; } /* * Insert a range into a character class, removing duplicates and ordering * them in increasing range-start order. */ static void #ifdef __STDC__ _ure_add_range(_ure_ccl_t *ccl, _ure_range_t *r, _ure_buffer_t *b) #else _ure_add_range(ccl, r, b) _ure_ccl_t *ccl; _ure_range_t *r; _ure_buffer_t *b; #endif { ucs2_t i; ucs4_t tmp; _ure_range_t *rp; /* * If the `casefold' flag is set, then make sure both endpoints of the * range are converted to lower case. */ if (b->flags & _URE_DFA_CASEFOLD) { r->min_code = unicode_tolower(r->min_code); r->max_code = unicode_tolower(r->max_code); } /* * Swap the range endpoints if they are not in increasing order. */ if (r->min_code > r->max_code) { tmp = r->min_code; r->min_code = r->max_code; r->max_code = tmp; } for (i = 0, rp = ccl->ranges; i < ccl->ranges_used && r->min_code < rp->min_code; i++, rp++) ; /* * Check for a duplicate. */ if (i < ccl->ranges_used && r->min_code == rp->min_code && r->max_code == rp->max_code) return; if (ccl->ranges_used == ccl->ranges_size) { if (ccl->ranges_size == 0) ccl->ranges = (_ure_range_t *) malloc(sizeof(_ure_range_t) << 3); else ccl->ranges = (_ure_range_t *) realloc((char *) ccl->ranges, sizeof(_ure_range_t) * (ccl->ranges_size + 8)); ccl->ranges_size += 8; } rp = ccl->ranges + i; if (i < ccl->ranges_used) memmove((char *) (rp + 1), (char *) rp, sizeof(_ure_range_t) * (ccl->ranges_used - i)); ccl->ranges_used++; rp->min_code = r->min_code; rp->max_code = r->max_code; } typedef struct { ucs2_t key; unsigned long len; unsigned long next; unsigned long mask; } _ure_trie_t; static _ure_trie_t cclass_trie[] = { {':', 1, 1, 0}, /* 0 */ {'a', 10, 11, 0}, /* 1 a (0/10) */ {'c', 9, 20, 0}, /* 2 c (1/10) */ {'d', 8, 79, 0}, /* {'d', 8, 25, 0}, */ {'g', 7, 71, 0}, /* {'g', 7, 30, 0}, */ {'l', 6, 35, 0}, {'p', 5, 40, 0}, {'s', 4, 50, 0}, {'u', 3, 55, 0}, {'x', 2, 60, 0}, {'t', 1, 66, 0}, /* 10 t (9/10) */ {'l', 1, 12, 0}, /* 11 al (0/1) */ {'n', 2, 14, 0}, /* 12 aln (0/2) */ {'p', 1, 17, 0}, /* 13 alp (1/2) */ {'u', 1, 15, 0}, /* alnu */ {'m', 1, 16, 0}, /* alnum */ {':', 1, 17, _URE_ALNUM}, {'h', 1, 18, 0}, {'a', 1, 19, 0}, {':', 1, 20, _URE_ALPHA}, {'n', 1, 21, 0}, /* 20 <- cn */ {'t', 1, 22, 0}, {'r', 1, 23, 0}, {'l', 1, 24, 0}, {':', 1, 25, _URE_CNTRL}, {'i', 1, 26, 0}, {'g', 1, 27, 0}, {'i', 1, 28, 0}, {'t', 1, 29, 0}, {':', 1, 30, _URE_DIGIT}, {'r', 1, 31, 0}, {'a', 1, 32, 0}, {'p', 1, 33, 0}, {'h', 1, 34, 0}, {':', 1, 35, _URE_GRAPH}, {'o', 1, 36, 0}, {'w', 1, 37, 0}, {'e', 1, 38, 0}, {'r', 1, 39, 0}, {':', 1, 40, _URE_LOWER}, {'r', 2, 42, 0}, {'u', 1, 46, 0}, {'i', 1, 43, 0}, {'n', 1, 44, 0}, {'t', 1, 45, 0}, {':', 1, 46, _URE_PRINT}, {'n', 1, 47, 0}, {'c', 1, 48, 0}, {'t', 1, 49, 0}, {':', 1, 50, _URE_PUNCT}, {'p', 1, 51, 0}, {'a', 1, 52, 0}, {'c', 1, 53, 0}, {'e', 1, 54, 0}, {':', 1, 55, _URE_SPACE}, {'p', 1, 56, 0}, {'p', 1, 57, 0}, {'e', 1, 58, 0}, {'r', 1, 59, 0}, {':', 1, 60, _URE_UPPER}, {'d', 1, 61, 0}, {'i', 1, 62, 0}, {'g', 1, 63, 0}, {'i', 1, 64, 0}, {'t', 1, 65, 0}, {':', 1, 66, _URE_XDIGIT}, {'i', 1, 67, 0}, {'t', 1, 68, 0}, {'l', 1, 69, 0}, {'e', 1, 70, 0}, {':', 1, 71, _URE_TITLE}, /* mhs: duplicated, so I dont have to renumber everything */ {'f', 1, 77, 0}, {'r', 2, 73, 0}, {'a', 1, 74, 0}, {'p', 1, 75, 0}, {'h', 1, 76, 0}, {':', 1, 77, _URE_GRAPH}, {'x', 1, 78, 0}, {':', 1, 79, _URE_ZVBI_GFX}, {'i', 2, 81, 0}, {'r', 1, 85, 0}, {'g', 1, 82, 0}, {'i', 1, 83, 0}, {'t', 1, 84, 0}, {':', 1, 85, _URE_DIGIT}, {'c', 1, 86, 0}, {'s', 1, 87, 0}, {':', 1, 88, _URE_ZVBI_DRCS} }; /* * Probe for one of the POSIX colon delimited character classes in the static * trie. */ static unsigned long #ifdef __STDC__ _ure_posix_ccl(ucs2_t *cp, unsigned long limit, _ure_symtab_t *sym, _ure_buffer_t *b) #else _ure_posix_ccl(cp, limit, sym, b) ucs2_t *cp; unsigned long limit; _ure_symtab_t *sym; _ure_buffer_t *b; #endif { int i; unsigned long n; _ure_trie_t *tp; ucs2_t *sp, *ep; b = b; /* * If the number of characters left is less than 7, then this cannot be * interpreted as one of the colon delimited classes. */ if (limit < 7) return 0; sp = cp; ep = sp + limit; tp = cclass_trie; for (i = 0; sp < ep && i < 8; i++, sp++) { n = tp->len; for (; n > 0 && tp->key != *sp; tp++, n--) ; if (n == 0) return 0; if (*sp == ':' && (i == 6 || i == 7)) { sp++; break; } if (sp + 1 < ep) tp = cclass_trie + tp->next; } if (!tp->mask) return 0; sym->props |= tp->mask; return sp - cp; } /* * Construct a list of ranges and return the number of characters consumed. */ static unsigned long #ifdef __STDC__ _ure_cclass(ucs2_t *cp, unsigned long limit, _ure_symtab_t *symp, _ure_buffer_t *b) #else _ure_cclass(cp, limit, symp, b) ucs2_t *cp; unsigned long limit; _ure_symtab_t *symp; _ure_buffer_t *b; #endif { int range_end; unsigned long n; ucs2_t *sp, *ep; ucs4_t c, last; _ure_ccl_t *cclp; _ure_range_t range; sp = cp; ep = sp + limit; if (*sp == '^') { symp->type = _URE_NCCLASS; sp++; } else symp->type = _URE_CCLASS; for (last = 0, range_end = 0; b->error == _URE_OK && sp < ep && *sp != ']'; ) { c = *sp++; if (c == '\\') { if (sp == ep) { /* * The EOS was encountered when expecting the reverse solidus * to be followed by the character it is escaping. Set an * error code and return the number of characters consumed up * to this point. */ b->error = _URE_UNEXPECTED_EOS; return sp - cp; } c = *sp++; switch (c) { case 'a': c = 0x07; break; case 'b': c = 0x08; break; case 'f': c = 0x0c; break; case 'n': c = 0x0a; break; case 'r': c = 0x0d; break; case 't': c = 0x09; break; case 'v': c = 0x0b; break; case 'p': case 'P': sp += _ure_prop_list(sp, ep - sp, &symp->props, b); /* * Invert the bit mask of the properties if this is a negated * character class or if 'P' is used to specify a list of * character properties that should *not* match in a * character class. */ if (c == 'P') symp->props = ~symp->props; continue; break; case 'x': case 'X': case 'u': case 'U': if (sp < ep && ((*sp >= '0' && *sp <= '9') || (*sp >= 'A' && *sp <= 'F') || (*sp >= 'a' && *sp <= 'f'))) sp += _ure_hex(sp, ep - sp, &c); } } else if (c == ':') { /* * Probe for a POSIX colon delimited character class. */ sp--; if ((n = _ure_posix_ccl(sp, ep - sp, symp, b)) == 0) sp++; else { sp += n; continue; } } cclp = &symp->sym.ccl; /* * Check to see if the current character is a low surrogate that needs * to be combined with a preceding high surrogate. */ if (last != 0) { if (c >= 0xdc00 && c <= 0xdfff) /* * Construct the UTF16 character code. */ c = 0x10000 + (((last & 0x03ff) << 10) | (c & 0x03ff)); else { /* * Add the isolated high surrogate to the range. */ if (range_end == 1) range.max_code = last & 0xffff; else range.min_code = range.max_code = last & 0xffff; _ure_add_range(cclp, &range, b); range_end = 0; } } /* * Clear the last character code. */ last = 0; /* * This slightly awkward code handles the different cases needed to * construct a range. */ if (c >= 0xd800 && c <= 0xdbff) { /* * If the high surrogate is followed by a range indicator, simply * add it as the range start. Otherwise, save it in case the next * character is a low surrogate. */ if (*sp == '-') { sp++; range.min_code = c; range_end = 1; } else last = c; } else if (range_end == 1) { range.max_code = c; _ure_add_range(cclp, &range, b); range_end = 0; } else { range.min_code = range.max_code = c; if (*sp == '-') { sp++; range_end = 1; } else _ure_add_range(cclp, &range, b); } } if (sp < ep && *sp == ']') sp++; else /* * The parse was not terminated by the character class close symbol * (']'), so set an error code. */ b->error = _URE_CCLASS_OPEN; return sp - cp; } /* * Probe for a low surrogate hex code. */ static unsigned long #ifdef __STDC__ _ure_probe_ls(ucs2_t *ls, unsigned long limit, ucs4_t *c) #else _ure_probe_ls(ls, limit, c) ucs2_t *ls; unsigned long limit; ucs4_t *c; #endif { ucs4_t i, code; ucs2_t *sp, *ep; for (i = code = 0, sp = ls, ep = sp + limit; i < 4 && sp < ep; sp++) { if (*sp >= '0' && *sp <= '9') code = (code << 4) + (*sp - '0'); else if (*sp >= 'A' && *sp <= 'F') code = (code << 4) + ((*sp - 'A') + 10); else if (*sp >= 'a' && *sp <= 'f') code = (code << 4) + ((*sp - 'a') + 10); else break; } *c = code; return (0xdc00 <= code && code <= 0xdfff) ? sp - ls : 0; } static unsigned long #ifdef __STDC__ _ure_compile_symbol(ucs2_t *sym, unsigned long limit, _ure_symtab_t *symp, _ure_buffer_t *b) #else _ure_compile_symbol(sym, limit, symp, b) ucs2_t *sym; unsigned long limit; _ure_symtab_t *symp; _ure_buffer_t *b; #endif { ucs4_t c; ucs2_t *sp, *ep; sp = sym; ep = sym + limit; if ((c = *sp++) == '\\') { if (sp == ep) { /* * The EOS was encountered when expecting the reverse solidus to * be followed by the character it is escaping. Set an error code * and return the number of characters consumed up to this point. */ b->error = _URE_UNEXPECTED_EOS; return sp - sym; } c = *sp++; switch (c) { case 'p': case 'P': symp->type = (c == 'p') ? _URE_CCLASS : _URE_NCCLASS; sp += _ure_prop_list(sp, ep - sp, &symp->props, b); break; case 'a': symp->type = _URE_CHAR; symp->sym.chr = 0x07; break; case 'b': symp->type = _URE_CHAR; symp->sym.chr = 0x08; break; case 'f': symp->type = _URE_CHAR; symp->sym.chr = 0x0c; break; case 'n': symp->type = _URE_CHAR; symp->sym.chr = 0x0a; break; case 'r': symp->type = _URE_CHAR; symp->sym.chr = 0x0d; break; case 't': symp->type = _URE_CHAR; symp->sym.chr = 0x09; break; case 'v': symp->type = _URE_CHAR; symp->sym.chr = 0x0b; break; case 'x': case 'X': case 'u': case 'U': /* * Collect between 1 and 4 digits representing a UCS2 code. Fall * through to the next case. */ if (sp < ep && ((*sp >= '0' && *sp <= '9') || (*sp >= 'A' && *sp <= 'F') || (*sp >= 'a' && *sp <= 'f'))) sp += _ure_hex(sp, ep - sp, &c); /* FALLTHROUGH */ default: /* * Simply add an escaped character here. */ symp->type = _URE_CHAR; symp->sym.chr = c; } } else if (c == '^' || c == '$') /* * Handle the BOL and EOL anchors. This actually consists simply of * setting a flag that indicates that the user supplied anchor match * function should be called. This needs to be done instead of simply * matching line/paragraph separators because beginning-of-text and * end-of-text tests are needed as well. */ symp->type = (c == '^') ? _URE_BOL_ANCHOR : _URE_EOL_ANCHOR; else if (c == '[') /* * Construct a character class. */ sp += _ure_cclass(sp, ep - sp, symp, b); else if (c == '.') symp->type = _URE_ANY_CHAR; else { symp->type = _URE_CHAR; symp->sym.chr = c; } /* * If the symbol type happens to be a character and is a high surrogate, * then probe forward to see if it is followed by a low surrogate that * needs to be added. */ if (sp < ep && symp->type == _URE_CHAR && 0xd800 <= symp->sym.chr && symp->sym.chr <= 0xdbff) { if (0xdc00 <= *sp && *sp <= 0xdfff) { symp->sym.chr = 0x10000 + (((symp->sym.chr & 0x03ff) << 10) | (*sp & 0x03ff)); sp++; } else if (*sp == '\\' && (*(sp + 1) == 'x' || *(sp + 1) == 'X' || *(sp + 1) == 'u' || *(sp + 1) == 'U')) { sp += _ure_probe_ls(sp + 2, ep - (sp + 2), &c); if (0xdc00 <= c && c <= 0xdfff) { /* * Take into account the \[xu] in front of the hex code. */ sp += 2; symp->sym.chr = 0x10000 + (((symp->sym.chr & 0x03ff) << 10) | (c & 0x03ff)); } } } /* * Last, make sure any _URE_CHAR type symbols are changed to lower case if * the `casefold' flag is set. */ if ((b->flags & _URE_DFA_CASEFOLD) && symp->type == _URE_CHAR) symp->sym.chr = unicode_tolower(symp->sym.chr); /* * If the symbol constructed is anything other than one of the anchors, * make sure the _URE_DFA_BLANKLINE flag is removed. */ if (symp->type != _URE_BOL_ANCHOR && symp->type != _URE_EOL_ANCHOR) b->flags &= ~_URE_DFA_BLANKLINE; /* * Return the number of characters consumed. */ return sp - sym; } static int #ifdef __STDC__ _ure_sym_neq(_ure_symtab_t *a, _ure_symtab_t *b) #else _ure_sym_neq(a, b) _ure_symtab_t *a, *b; #endif { if (a->type != b->type || a->mods != b->mods || a->props != b->props) return 1; if (a->type == _URE_CCLASS || a->type == _URE_NCCLASS) { if (a->sym.ccl.ranges_used != b->sym.ccl.ranges_used) return 1; if (a->sym.ccl.ranges_used > 0 && memcmp((char *) a->sym.ccl.ranges, (char *) b->sym.ccl.ranges, sizeof(_ure_range_t) * a->sym.ccl.ranges_used) != 0) return 1; } else if (a->type == _URE_CHAR && a->sym.chr != b->sym.chr) return 1; return 0; } /* * Construct a symbol, but only keep unique symbols. */ static ucs2_t #ifdef __stdc__ _ure_make_symbol(ucs2_t *sym, unsigned long limit, unsigned long *consumed, _ure_buffer_t *b) #else _ure_make_symbol(sym, limit, consumed, b) ucs2_t *sym; unsigned long limit, *consumed; _ure_buffer_t *b; #endif { ucs2_t i; _ure_symtab_t *sp, symbol; /* * Build the next symbol so we can test to see if it is already in the * symbol table. */ (void) memset((char *) &symbol, 0, sizeof(_ure_symtab_t)); *consumed = _ure_compile_symbol(sym, limit, &symbol, b); /* * Check to see if the symbol exists. */ for (i = 0, sp = b->symtab; i < b->symtab_used && _ure_sym_neq(&symbol, sp); i++, sp++) ; if (i < b->symtab_used) { /* * Free up any ranges used for the symbol. */ if ((symbol.type == _URE_CCLASS || symbol.type == _URE_NCCLASS) && symbol.sym.ccl.ranges_size > 0) free((char *) symbol.sym.ccl.ranges); return b->symtab[i].id; } /* * Need to add the new symbol. */ if (b->symtab_used == b->symtab_size) { if (b->symtab_size == 0) b->symtab = (_ure_symtab_t *) malloc(sizeof(_ure_symtab_t) << 3); else b->symtab = (_ure_symtab_t *) realloc((char *) b->symtab, sizeof(_ure_symtab_t) * (b->symtab_size + 8)); sp = b->symtab + b->symtab_size; (void) memset((char *) sp, 0, sizeof(_ure_symtab_t) << 3); b->symtab_size += 8; } symbol.id = b->symtab_used++; (void) memcpy((char *) &b->symtab[symbol.id], (char *) &symbol, sizeof(_ure_symtab_t)); return symbol.id; } /************************************************************************* * * End symbol parse functions. * *************************************************************************/ static ucs2_t #ifdef __stdc__ _ure_make_expr(ucs2_t type, ucs2_t lhs, ucs2_t rhs, _ure_buffer_t *b) #else _ure_make_expr(type, lhs, rhs, b) ucs2_t type, lhs, rhs; _ure_buffer_t *b; #endif { ucs2_t i; if (b == 0) return _URE_NOOP; /* * Determine if the expression already exists or not. */ for (i = 0; i < b->expr_used; i++) { if (b->expr[i].type == type && b->expr[i].lhs == lhs && b->expr[i].rhs == rhs) break; } if (i < b->expr_used) return i; /* * Need to add a new expression. */ if (b->expr_used == b->expr_size) { if (b->expr_size == 0) b->expr = (_ure_elt_t *) malloc(sizeof(_ure_elt_t) << 3); else b->expr = (_ure_elt_t *) realloc((char *) b->expr, sizeof(_ure_elt_t) * (b->expr_size + 8)); b->expr_size += 8; } b->expr[b->expr_used].onstack = 0; b->expr[b->expr_used].type = type; b->expr[b->expr_used].lhs = lhs; b->expr[b->expr_used].rhs = rhs; return b->expr_used++; } static unsigned char spmap[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; #define _ure_isspecial(cc) ((cc) > 0x20 && (cc) < 0x7f && \ (spmap[(cc) >> 3] & (1 << ((cc) & 7)))) /* * Convert the regular expression into an NFA in a form that will be easy to * reduce to a DFA. The starting state for the reduction will be returned. */ static ucs2_t #ifdef __STDC__ _ure_re2nfa(ucs2_t *re, unsigned long relen, _ure_buffer_t *b) #else _ure_re2nfa(re, relen, b) ucs2_t *re; unsigned long relen; _ure_buffer_t *b; #endif { ucs2_t c, state, top, sym, *sp, *ep, p; unsigned long used; state = _URE_NOOP; sp = re; ep = sp + relen; while (b->error == _URE_OK && sp < ep) { c = *sp++; switch (c) { case '(': _ure_push(_URE_PAREN, b); break; case ')': /* * Check for the case of too many close parentheses. */ if (_ure_peek(b) == _URE_NOOP) { b->error = _URE_UNBALANCED_GROUP; break; } while ((top = _ure_peek(b)) == _URE_AND || top == _URE_OR) /* * Make an expression with the AND or OR operator and its right * hand side. */ { p = _ure_pop(b); state = _ure_make_expr(p, _ure_pop(b), state, b); } /* * Remove the _URE_PAREN off the stack. */ (void) _ure_pop(b); break; case '|': while ((top = _ure_peek(b)) == _URE_AND || top == _URE_OR) /* * Make an expression with the AND or OR operator and its right * hand side. */ { p = _ure_pop(b); state = _ure_make_expr(p, _ure_pop(b), state, b); } _ure_push(state, b); _ure_push(_URE_OR, b); break; case '*': /* If this is the first char, treat as escaped */ if (re+1 != sp) { state = _ure_make_expr(_URE_STAR, state, _URE_NOOP, b); break; } case '+': if (re+1 != sp) { state = _ure_make_expr(_URE_PLUS, state, _URE_NOOP, b); break; } case '?': if (re+1 != sp) { state = _ure_make_expr(_URE_QUEST, state, _URE_NOOP, b); break; } default: sp--; sym = _ure_make_symbol(sp, ep - sp, &used, b); sp += used; state = _ure_make_expr(_URE_SYMBOL, sym, _URE_NOOP, b); break; } if (c != '(' && c != '|' && sp < ep && (!_ure_isspecial(*sp) || *sp == '(')) { _ure_push(state, b); _ure_push(_URE_AND, b); } } while ((top = _ure_peek(b)) == _URE_AND || top == _URE_OR) /* * Make an expression with the AND or OR operator and its right * hand side. */ { p = _ure_pop(b); state = _ure_make_expr(p, _ure_pop(b), state, b); } if (b->stack.slist_used > 0) b->error = _URE_UNBALANCED_GROUP; return (b->error == _URE_OK) ? state : _URE_NOOP; } static void #ifdef __STDC__ _ure_add_symstate(ucs2_t sym, ucs2_t state, _ure_buffer_t *b) #else _ure_add_symstate(sym, state, b) ucs2_t sym, state; _ure_buffer_t *b; #endif { ucs2_t i, *stp; _ure_symtab_t *sp; /* * Locate the symbol in the symbol table so the state can be added. * If the symbol doesn't exist, then a real problem exists. */ for (i = 0, sp = b->symtab; i < b->symtab_used && sym != sp->id; i++, sp++) ; /* * Now find out if the state exists in the symbol's state list. */ for (i = 0, stp = sp->states.slist; i < sp->states.slist_used && state > *stp; i++, stp++) ; if (i == sp->states.slist_used || state < *stp) { /* * Need to add the state in order. */ if (sp->states.slist_used == sp->states.slist_size) { if (sp->states.slist_size == 0) sp->states.slist = (ucs2_t *) malloc(sizeof(ucs2_t) << 3); else sp->states.slist = (ucs2_t *) realloc((char *) sp->states.slist, sizeof(ucs2_t) * (sp->states.slist_size + 8)); sp->states.slist_size += 8; } if (i < sp->states.slist_used) (void) memmove((char *) (sp->states.slist + i + 1), (char *) (sp->states.slist + i), sizeof(ucs2_t) * (sp->states.slist_used - i)); sp->states.slist[i] = state; sp->states.slist_used++; } } static ucs2_t #ifdef __STDC__ _ure_add_state(ucs2_t nstates, ucs2_t *states, _ure_buffer_t *b) #else _ure_add_state(nstates, states, b) ucs2_t nstates, *states; _ure_buffer_t *b; #endif { ucs2_t i; _ure_state_t *sp; for (i = 0, sp = b->states.states; i < b->states.states_used; i++, sp++) { if (sp->st.slist_used == nstates && memcmp((char *) states, (char *) sp->st.slist, sizeof(ucs2_t) * nstates) == 0) break; } if (i == b->states.states_used) { /* * Need to add a new DFA state (set of NFA states). */ if (b->states.states_used == b->states.states_size) { if (b->states.states_size == 0) b->states.states = (_ure_state_t *) malloc(sizeof(_ure_state_t) << 3); else b->states.states = (_ure_state_t *) realloc((char *) b->states.states, sizeof(_ure_state_t) * (b->states.states_size + 8)); sp = b->states.states + b->states.states_size; (void) memset((char *) sp, 0, sizeof(_ure_state_t) << 3); b->states.states_size += 8; } sp = b->states.states + b->states.states_used++; sp->id = i; if (sp->st.slist_used + nstates > sp->st.slist_size) { if (sp->st.slist_size == 0) sp->st.slist = (ucs2_t *) malloc(sizeof(ucs2_t) * (sp->st.slist_used + nstates)); else sp->st.slist = (ucs2_t *) realloc((char *) sp->st.slist, sizeof(ucs2_t) * (sp->st.slist_used + nstates)); sp->st.slist_size = sp->st.slist_used + nstates; } sp->st.slist_used = nstates; (void) memcpy((char *) sp->st.slist, (char *) states, sizeof(ucs2_t) * nstates); } /* * Return the ID of the DFA state representing a group of NFA states. */ return i; } static void #ifdef __STDC__ _ure_reduce(ucs2_t start, _ure_buffer_t *b) #else _ure_reduce(start, b) ucs2_t start; _ure_buffer_t *b; #endif { ucs2_t i, j, state, eval, syms, rhs; ucs2_t s1, s2, ns1, ns2; _ure_state_t *sp; _ure_symtab_t *smp; b->reducing = 1; /* * Add the starting state for the reduction. */ _ure_add_state(1, &start, b); /* * Process each set of NFA states that get created. */ for (i = 0; i < b->states.states_used; i++) { sp = b->states.states + i; /* * Push the current states on the stack. */ for (j = 0; j < sp->st.slist_used; j++) _ure_push(sp->st.slist[j], b); /* * Reduce the NFA states. */ for (j = sp->accepting = syms = 0; j < b->stack.slist_used; j++) { state = b->stack.slist[j]; eval = 1; /* * This inner loop is the iterative equivalent of recursively * reducing subexpressions generated as a result of a reduction. */ while (eval) { switch (b->expr[state].type) { case _URE_SYMBOL: ns1 = _ure_make_expr(_URE_ONE, _URE_NOOP, _URE_NOOP, b); _ure_add_symstate(b->expr[state].lhs, ns1, b); syms++; eval = 0; break; case _URE_ONE: sp->accepting = 1; eval = 0; break; case _URE_QUEST: s1 = b->expr[state].lhs; ns1 = _ure_make_expr(_URE_ONE, _URE_NOOP, _URE_NOOP, b); state = _ure_make_expr(_URE_OR, ns1, s1, b); break; case _URE_PLUS: s1 = b->expr[state].lhs; ns1 = _ure_make_expr(_URE_STAR, s1, _URE_NOOP, b); state = _ure_make_expr(_URE_AND, s1, ns1, b); break; case _URE_STAR: s1 = b->expr[state].lhs; ns1 = _ure_make_expr(_URE_ONE, _URE_NOOP, _URE_NOOP, b); ns2 = _ure_make_expr(_URE_PLUS, s1, _URE_NOOP, b); state = _ure_make_expr(_URE_OR, ns1, ns2, b); break; case _URE_OR: s1 = b->expr[state].lhs; s2 = b->expr[state].rhs; _ure_push(s1, b); _ure_push(s2, b); eval = 0; break; case _URE_AND: s1 = b->expr[state].lhs; s2 = b->expr[state].rhs; switch (b->expr[s1].type) { case _URE_SYMBOL: _ure_add_symstate(b->expr[s1].lhs, s2, b); syms++; eval = 0; break; case _URE_ONE: state = s2; break; case _URE_QUEST: ns1 = b->expr[s1].lhs; ns2 = _ure_make_expr(_URE_AND, ns1, s2, b); state = _ure_make_expr(_URE_OR, s2, ns2, b); break; case _URE_PLUS: ns1 = b->expr[s1].lhs; ns2 = _ure_make_expr(_URE_OR, s2, state, b); state = _ure_make_expr(_URE_AND, ns1, ns2, b); break; case _URE_STAR: ns1 = b->expr[s1].lhs; ns2 = _ure_make_expr(_URE_AND, ns1, state, b); state = _ure_make_expr(_URE_OR, s2, ns2, b); break; case _URE_OR: ns1 = b->expr[s1].lhs; ns2 = b->expr[s1].rhs; ns1 = _ure_make_expr(_URE_AND, ns1, s2, b); ns2 = _ure_make_expr(_URE_AND, ns2, s2, b); state = _ure_make_expr(_URE_OR, ns1, ns2, b); break; case _URE_AND: ns1 = b->expr[s1].lhs; ns2 = b->expr[s1].rhs; ns2 = _ure_make_expr(_URE_AND, ns2, s2, b); state = _ure_make_expr(_URE_AND, ns1, ns2, b); break; } } } } /* * Clear the state stack. */ while (_ure_pop(b) != _URE_NOOP) ; /* * Reset the state pointer because the reduction may have moved it * during a reallocation. */ sp = b->states.states + i; /* * Generate the DFA states for the symbols collected during the * current reduction. */ if (sp->trans_used + syms > sp->trans_size) { if (sp->trans_size == 0) sp->trans = (_ure_elt_t *) malloc(sizeof(_ure_elt_t) * (sp->trans_used + syms)); else sp->trans = (_ure_elt_t *) realloc((char *) sp->trans, sizeof(_ure_elt_t) * (sp->trans_used + syms)); sp->trans_size = sp->trans_used + syms; } /* * Go through the symbol table and generate the DFA state transitions * for each symbol that has collected NFA states. */ for (j = syms = 0, smp = b->symtab; j < b->symtab_used; j++, smp++) { sp = b->states.states + i; if (smp->states.slist_used > 0) { sp->trans[syms].lhs = smp->id; rhs = _ure_add_state(smp->states.slist_used, smp->states.slist, b); /* * Reset the state pointer in case the reallocation moves it * in memory. */ sp = b->states.states + i; sp->trans[syms].rhs = rhs; smp->states.slist_used = 0; syms++; } } /* * Set the number of transitions actually used. */ sp->trans_used = syms; } b->reducing = 0; } static void #ifdef __STDC__ _ure_add_equiv(ucs2_t l, ucs2_t r, _ure_buffer_t *b) #else _ure_add_equiv(l, r, b) ucs2_t l, r; _ure_buffer_t *b; #endif { ucs2_t tmp; l = b->states.states[l].id; r = b->states.states[r].id; if (l == r) return; if (l > r) { tmp = l; l = r; r = tmp; } /* * Check to see if the equivalence pair already exists. */ for (tmp = 0; tmp < b->equiv_used && (b->equiv[tmp].l != l || b->equiv[tmp].r != r); tmp++) ; if (tmp < b->equiv_used) return; if (b->equiv_used == b->equiv_size) { if (b->equiv_size == 0) b->equiv = (_ure_equiv_t *) malloc(sizeof(_ure_equiv_t) << 3); else b->equiv = (_ure_equiv_t *) realloc((char *) b->equiv, sizeof(_ure_equiv_t) * (b->equiv_size + 8)); b->equiv_size += 8; } b->equiv[b->equiv_used].l = l; b->equiv[b->equiv_used].r = r; b->equiv_used++; } /* * Merge the DFA states that are equivalent. */ static void #ifdef __STDC__ _ure_merge_equiv(_ure_buffer_t *b) #else _ure_merge_equiv(b) _ure_buffer_t *b; #endif { ucs2_t i, j, k, eq, done; _ure_state_t *sp1, *sp2, *ls, *rs; for (i = 0; i < b->states.states_used; i++) { sp1 = b->states.states + i; if (sp1->id != i) continue; for (j = 0; j < i; j++) { sp2 = b->states.states + j; if (sp2->id != j) continue; b->equiv_used = 0; _ure_add_equiv(i, j, b); for (eq = 0, done = 0; eq < b->equiv_used; eq++) { ls = b->states.states + b->equiv[eq].l; rs = b->states.states + b->equiv[eq].r; if (ls->accepting != rs->accepting || ls->trans_used != rs->trans_used) { done = 1; break; } for (k = 0; k < ls->trans_used && ls->trans[k].lhs == rs->trans[k].lhs; k++) ; if (k < ls->trans_used) { done = 1; break; } for (k = 0; k < ls->trans_used; k++) _ure_add_equiv(ls->trans[k].rhs, rs->trans[k].rhs, b); } if (done == 0) break; } for (eq = 0; j < i && eq < b->equiv_used; eq++) b->states.states[b->equiv[eq].r].id = b->states.states[b->equiv[eq].l].id; } /* * Renumber the states appropriately. */ for (i = eq = 0, sp1 = b->states.states; i < b->states.states_used; sp1++, i++) sp1->id = (sp1->id == i) ? eq++ : b->states.states[sp1->id].id; } /************************************************************************* * * API. * *************************************************************************/ ure_buffer_t #ifdef __STDC__ ure_buffer_create(void) #else ure_buffer_create() #endif { ure_buffer_t b; b = (ure_buffer_t) calloc(1, sizeof(_ure_buffer_t)); return b; } void #ifdef __STDC__ ure_buffer_free(ure_buffer_t buf) #else ure_buffer_free(buf) ure_buffer_t buf; #endif { unsigned long i; if (buf == 0) return; if (buf->stack.slist_size > 0) free((char *) buf->stack.slist); if (buf->expr_size > 0) free((char *) buf->expr); for (i = 0; i < buf->symtab_size; i++) { if (buf->symtab[i].states.slist_size > 0) free((char *) buf->symtab[i].states.slist); } if (buf->symtab_size > 0) free((char *) buf->symtab); for (i = 0; i < buf->states.states_size; i++) { if (buf->states.states[i].trans_size > 0) free((char *) buf->states.states[i].trans); if (buf->states.states[i].st.slist_size > 0) free((char *) buf->states.states[i].st.slist); } if (buf->states.states_size > 0) free((char *) buf->states.states); if (buf->equiv_size > 0) free((char *) buf->equiv); free((char *) buf); } ure_dfa_t #ifdef __STDC__ ure_compile(ucs2_t *re, unsigned long relen, int casefold, ure_buffer_t buf) #else ure_compile(re, relen, casefold, buf) ucs2_t *re; unsigned long relen; int casefold; ure_buffer_t buf; #endif { ucs2_t i, j, state; _ure_state_t *sp; _ure_dstate_t *dsp; _ure_trans_t *tp; ure_dfa_t dfa; if (re == 0 || *re == 0 || relen == 0 || buf == 0) return 0; /* * Reset the various fields of the compilation buffer. Default the flags * to indicate the presense of the "^$" pattern. If any other pattern * occurs, then this flag will be removed. This is done to catch this * special pattern and handle it specially when matching. */ buf->flags = _URE_DFA_BLANKLINE | ((casefold) ? _URE_DFA_CASEFOLD : 0); buf->reducing = 0; buf->stack.slist_used = 0; buf->expr_used = 0; for (i = 0; i < buf->symtab_used; i++) buf->symtab[i].states.slist_used = 0; buf->symtab_used = 0; for (i = 0; i < buf->states.states_used; i++) { buf->states.states[i].st.slist_used = 0; buf->states.states[i].trans_used = 0; } buf->states.states_used = 0; /* * Construct the NFA. If this stage returns a 0, then an error occured or * an empty expression was passed. */ if ((state = _ure_re2nfa(re, relen, buf)) == _URE_NOOP) return 0; /* * Do the expression reduction to get the initial DFA. */ _ure_reduce(state, buf); /* * Merge all the equivalent DFA states. */ _ure_merge_equiv(buf); /* * Construct the minimal DFA. */ dfa = (ure_dfa_t) malloc(sizeof(_ure_dfa_t)); (void) memset((char *) dfa, 0, sizeof(_ure_dfa_t)); dfa->flags = buf->flags & (_URE_DFA_CASEFOLD|_URE_DFA_BLANKLINE); /* * Free up the NFA state groups and transfer the symbols from the buffer * to the DFA. */ for (i = 0; i < buf->symtab_size; i++) { if (buf->symtab[i].states.slist_size > 0) free((char *) buf->symtab[i].states.slist); } dfa->syms = buf->symtab; dfa->nsyms = buf->symtab_used; buf->symtab_used = buf->symtab_size = 0; /* * Collect the total number of states and transitions needed for the DFA. */ for (i = state = 0, sp = buf->states.states; i < buf->states.states_used; i++, sp++) { if (sp->id == state) { dfa->nstates++; dfa->ntrans += sp->trans_used; state++; } } /* * Allocate enough space for the states and transitions. */ dfa->states = (_ure_dstate_t *) malloc(sizeof(_ure_dstate_t) * dfa->nstates); dfa->trans = (_ure_trans_t *) malloc(sizeof(_ure_trans_t) * dfa->ntrans); /* * Actually transfer the DFA states from the buffer. */ dsp = dfa->states; tp = dfa->trans; for (i = state = 0, sp = buf->states.states; i < buf->states.states_used; i++, sp++) { if (sp->id == state) { dsp->trans = tp; dsp->ntrans = sp->trans_used; dsp->accepting = sp->accepting; /* * Add the transitions for the state. */ for (j = 0; j < dsp->ntrans; j++, tp++) { tp->symbol = sp->trans[j].lhs; tp->next_state = buf->states.states[sp->trans[j].rhs].id; } dsp++; state++; } } return dfa; } void #ifdef __STDC__ ure_dfa_free(ure_dfa_t dfa) #else ure_dfa_free(dfa) ure_dfa_t dfa; #endif { ucs2_t i; if (dfa == 0) return; for (i = 0; i < dfa->nsyms; i++) { if ((dfa->syms[i].type == _URE_CCLASS || dfa->syms[i].type == _URE_NCCLASS) && dfa->syms[i].sym.ccl.ranges_size > 0) free((char *) dfa->syms[i].sym.ccl.ranges); } if (dfa->nsyms > 0) free((char *) dfa->syms); if (dfa->nstates > 0) free((char *) dfa->states); if (dfa->ntrans > 0) free((char *) dfa->trans); free((char *) dfa); } void #ifdef __STDC__ ure_write_dfa(ure_dfa_t dfa, FILE *out) #else ure_write_dfa(dfa, out) ure_dfa_t dfa; FILE *out; #endif { ucs2_t i, j, k, h, l; _ure_dstate_t *sp; _ure_symtab_t *sym; _ure_range_t *rp; if (dfa == 0 || out == 0) return; /* * Write all the different character classes. */ for (i = 0, sym = dfa->syms; i < dfa->nsyms; i++, sym++) { if (sym->type == _URE_CCLASS || sym->type == _URE_NCCLASS) { fprintf(out, "C%hd = ", sym->id); if (sym->sym.ccl.ranges_used > 0) { putc('[', out); if (sym->type == _URE_NCCLASS) putc('^', out); } if (sym->props != 0) { if (sym->type == _URE_NCCLASS) fprintf(out, "\\P"); else fprintf(out, "\\p"); for (k = h = 0; k < 32; k++) { if (sym->props & (1 << k)) { if (h != 0) putc(',', out); fprintf(out, "%hd", (short)(k + 1)); h = 1; } } } /* * Dump the ranges. */ for (k = 0, rp = sym->sym.ccl.ranges; k < sym->sym.ccl.ranges_used; k++, rp++) { /* * Check for UTF16 characters. */ if (0x10000 <= rp->min_code && rp->min_code <= 0x10ffff) { h = ((rp->min_code - 0x10000) >> 10) + 0xd800; l = ((rp->min_code - 0x10000) & 1023) + 0xdc00; fprintf(out, "\\x%04X\\x%04X", (unsigned) h, (unsigned) l); } else fprintf(out, "\\x%04lX", (unsigned long)(rp->min_code & 0xffff)); if (rp->max_code != rp->min_code) { putc('-', out); if (rp->max_code >= 0x10000 && rp->max_code <= 0x10ffff) { h = ((rp->max_code - 0x10000) >> 10) + 0xd800; l = ((rp->max_code - 0x10000) & 1023) + 0xdc00; fprintf(out, "\\x%04hX\\x%04hX", h, l); } else fprintf(out, "\\x%04lX", (unsigned long) rp->max_code & 0xffff); } } if (sym->sym.ccl.ranges_used > 0) putc(']', out); putc('\n', out); } } for (i = 0, sp = dfa->states; i < dfa->nstates; i++, sp++) { fprintf(out, "S%hd = ", i); if (sp->accepting) { fprintf(out, "1 "); if (sp->ntrans) fprintf(out, "| "); } for (j = 0; j < sp->ntrans; j++) { if (j > 0) fprintf(out, "| "); sym = dfa->syms + sp->trans[j].symbol; switch (sym->type) { case _URE_CHAR: if (0x10000 <= sym->sym.chr && sym->sym.chr <= 0x10ffff) { /* * Take care of UTF16 characters. */ h = ((sym->sym.chr - 0x10000) >> 10) + 0xd800; l = ((sym->sym.chr - 0x10000) & 1023) + 0xdc00; fprintf(out, "\\x%04hX\\x%04hX ", h, l); } else // fprintf(out, "\\x%04lX ", sym->sym.chr & 0xffff); fprintf(out, "%c ", (char)sym->sym.chr); break; case _URE_ANY_CHAR: fprintf(out, " "); break; case _URE_BOL_ANCHOR: fprintf(out, " "); break; case _URE_EOL_ANCHOR: fprintf(out, " "); break; case _URE_CCLASS: case _URE_NCCLASS: fprintf(out, "[C%hd] ", sym->id); break; } fprintf(out, "S%hd", sp->trans[j].next_state); if (j + 1 < sp->ntrans) putc(' ', out); } putc('\n', out); } } #define _ure_issep(cc) _ure_matches_properties(cc, _URE_SEPARATOR) #define _ure_isbrk(cc) ((cc) == '\n' || (cc) == '\r' || (cc) == 0x2028 ||\ (cc) == 0x2029) int #ifdef __STDC__ ure_exec(ure_dfa_t dfa, int flags, ucs2_t *text, unsigned long textlen, unsigned long *match_start, unsigned long *match_end) #else ure_exec(dfa, flags, text, textlen, match_start, match_end) ure_dfa_t dfa; int flags; ucs2_t *text; unsigned long textlen, *match_start, *match_end; #endif { int i, j, matched, found, skip; unsigned long ms, me; ucs4_t c; ucs2_t *sp, *ep, *lp; _ure_dstate_t *stp; _ure_symtab_t *sym; _ure_range_t *rp; if (dfa == 0 || text == 0 || match_start == 0 || match_end == 0) return 0; /* * Handle the special case of an empty string matching the "^$" pattern. */ if (textlen == 0 && (dfa->flags & _URE_DFA_BLANKLINE)) { *match_start = *match_end = 0; return 1; } sp = text; ep = sp + textlen; ms = me = ~0; stp = dfa->states; for (found = skip = 0; found == 0 && sp < ep; ) { lp = sp; c = *sp++; #if 0 /* zvbi: never */ /* * Check to see if this is a high surrogate that should be * combined with a following low surrogate. */ if (sp < ep && 0xd800 <= c && c <= 0xdbff && 0xdc00 <= *sp && *sp <= 0xdfff) c = 0x10000 + (((c & 0x03ff) << 10) | (*sp++ & 0x03ff)); /* * Determine if the character is non-spacing and should be skipped. */ if ((flags & URE_IGNORE_NONSPACING) && (_ure_matches_properties(_URE_NONSPACING, c))) continue; #endif if (dfa->flags & _URE_DFA_CASEFOLD) c = unicode_tolower(c); /* * See if one of the transitions matches. */ for (i = 0, matched = 0; matched == 0 && i < stp->ntrans; i++) { sym = dfa->syms + stp->trans[i].symbol; switch (sym->type) { case _URE_ANY_CHAR: if ((flags & URE_DOT_MATCHES_SEPARATORS) || !_ure_issep(c)) matched = 1; break; case _URE_CHAR: if (c == sym->sym.chr) matched = 1; break; case _URE_BOL_ANCHOR: if (flags & URE_NOTBOL) break; if (lp == text) { sp = lp; matched = 1; } else if (_ure_isbrk(c)) { if (c == '\r' && sp < ep && *sp == '\n') sp++; lp = sp; matched = 1; } break; case _URE_EOL_ANCHOR: if (flags & URE_NOTEOL) break; if (_ure_isbrk(c)) { /* * Put the pointer back before the separator so the match * end position will be correct. This case will also * cause the `sp' pointer to be advanced over the current * separator once the match end point has been recorded. */ sp = lp; matched = 1; } break; case _URE_CCLASS: case _URE_NCCLASS: if (sym->props != 0) matched = _ure_matches_properties(sym->props, c); for (j = 0, rp = sym->sym.ccl.ranges; j < sym->sym.ccl.ranges_used; j++, rp++) { if (rp->min_code <= c && c <= rp->max_code) matched = 1; } if (sym->type == _URE_NCCLASS) { matched = !matched; if (matched && _ure_issep(c) && (!(flags & URE_DOT_MATCHES_SEPARATORS))) matched = 0; } break; } if (matched) { me = sp - text; if (ms == (unsigned long) ~0) ms = lp - text; stp = dfa->states + stp->trans[i].next_state; /* * If the match was an EOL anchor, adjust the pointer past the * separator that caused the match. The correct match * position has been recorded already. */ if (sym->type == _URE_EOL_ANCHOR) { /* * Skip the character that caused the match. */ sp++; /* * Handle the infamous CRLF situation. */ if (sp < ep && c == '\r' && *sp == '\n') sp++; } } } if (matched == 0) { if (stp->accepting == 0) { /* * If the last state was not accepting, then reset * and start over. */ stp = dfa->states; ms = me = ~0; } else /* * The last state was accepting, so terminate the matching * loop to avoid more work. */ found = 1; } else if (sp == ep) { if (!stp->accepting) { /* * This ugly hack is to make sure the end-of-line anchors * match when the source text hits the end. This is only done * if the last subexpression matches. */ for (i = 0; found == 0 && i < stp->ntrans; i++) { sym = dfa->syms + stp->trans[i].symbol; if (sym->type ==_URE_EOL_ANCHOR) { stp = dfa->states + stp->trans[i].next_state; if (stp->accepting) { me = sp - text; found = 1; } else break; } } } else { /* * Make sure any conditions that match all the way to the end * of the string match. */ found = 1; me = sp - text; } } } if (found == 0) ms = me = ~0; *match_start = ms; *match_end = me; return (ms != (unsigned long) ~0) ? 1 : 0; } #endif /* HAVE_GLIBC21 || HAVE_LIBUNICODE */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/ure.h000066400000000000000000000101751476363111200143160ustar00rootroot00000000000000/* * Copyright 1997, 1998, 1999 Computing Research Labs, * New Mexico State University * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COMPUTING RESEARCH LAB OR NEW MEXICO STATE UNIVERSITY BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* $Id: ure.h,v 1.9 2008-02-19 00:35:22 mschimek Exp $ */ #ifndef _h_ure #define _h_ure #ifdef HAVE_CONFIG_H # include "config.h" #endif #if defined(HAVE_GLIBC21) || defined(HAVE_LIBUNICODE) #include #include #ifdef __cplusplus extern "C" { #endif #undef __ #ifdef __STDC__ #define __(x) x #else #define __(x) () #endif /* * Error codes. */ #define _URE_OK 0 #define _URE_UNEXPECTED_EOS -1 #define _URE_CCLASS_OPEN -2 #define _URE_UNBALANCED_GROUP -3 #define _URE_INVALID_PROPERTY -4 /* * Options that can be combined for searching. */ /* mhs: not used, disabled #define URE_IGNORE_NONSPACING 0x01 */ #define URE_DOT_MATCHES_SEPARATORS 0x02 #define URE_NOTBOL 0x04 #define URE_NOTEOL 0x08 typedef uint32_t ucs4_t; typedef uint16_t ucs2_t; /* * Opaque type for memory used when compiling expressions. */ typedef struct _ure_buffer_t *ure_buffer_t; /* * Opaque type for the minimal DFA used when matching. */ typedef struct _ure_dfa_t *ure_dfa_t; /************************************************************************* * * API. * *************************************************************************/ /** * @internal * * Alloc memory for the regex internal buffer, NULL on error. * Use ure_buffer_free to free the returned buffer. * * @return * ure_buffer_t. */ extern ure_buffer_t ure_buffer_create __((void)); extern void ure_buffer_free __((ure_buffer_t buf)); /** * @internal * @param re Buffer containing the UCS-2 regexp. * @param relen Size in characters of the regexp. * @param casefold @c TRUE for matching disregarding case. * @param buf The regexp buffer. * * Compile the given expression into a dfa. * * @return * The compiled DFA, @c NULL on error. */ extern ure_dfa_t ure_compile __((ucs2_t *re, unsigned long relen, int casefold, ure_buffer_t buf)); extern void ure_dfa_free __((ure_dfa_t dfa)); extern void ure_write_dfa __((ure_dfa_t dfa, FILE *out)); /** * @internal * @param dfa The compiled expression. * @param flags Or'ed * @c URE_IGNORED_NONSPACING: Set if nonspacing chars should be ignored. * @c URE_DOT_MATCHES_SEPARATORS: Set if dot operator matches * separator characters too. * @param text UCS-2 text to run the compiled regexp against. * @param textlen Size in characters of the text. * @param match_start Index in text of the first matching char. * @param match_end Index in text of the first non-matching char after the * matching characters. * * Run the compiled regexp search on the given text. * * @return * @c TRUE if the search suceeded. */ extern int ure_exec __((ure_dfa_t dfa, int flags, ucs2_t *text, unsigned long textlen, unsigned long *match_start, unsigned long *match_end)); #undef __ #ifdef __cplusplus } #endif #endif /* HAVE_GLIBC21 || HAVE_LIBUNICODE */ #endif /* _h_ure */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/vbi.c000066400000000000000000000625551476363111200143070ustar00rootroot00000000000000/* * libzvbi -- VBI decoding library * * Copyright (C) 2000-2008 Michael H. Schimek * Copyright (C) 2000, 2001 Iñaki García Etxebarria * Copyright (C) 2003, 2004 Tom Zoerner * * Originally based on AleVT 1.5.1 by Edgar Toernig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: vbi.c,v 1.27 2013-08-28 14:45:48 mschimek Exp $ */ #include "site_def.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #include #include #include "misc.h" #include "version.h" #include "vbi.h" #include "hamm.h" #include "lang.h" #include "export.h" #include "tables.h" #include "format.h" #include "wss.h" /** * @mainpage ZVBI - VBI Decoding Library * * @author Iñaki García Etxebarria
* Michael H. Schimek
* Tom Zoerner
* based on AleVT by Edgar Toernig * * @section intro Introduction * * The ZVBI library provides routines to access raw VBI sampling devices * (currently the Linux V4L and * and V4L2 API and the FreeBSD * bktr driver API * are supported), a versatile raw VBI bit slicer, * decoders for various data services and basic search, * render and export functions for text pages. The library was written for * the Zapping TV viewer and * Zapzilla Teletext browser. * * @section feedback Feedback * * If you have any ideas, questions, patches or bug reports please see * the README file included with the source code or visit our home page at * http://zapping.sourceforge.net. */ /** @defgroup Basic Basic types */ /** @defgroup Raw Raw VBI */ /** @defgroup LowDec Low Level Decoding */ /** @defgroup HiDec High Level Decoding */ /** * @defgroup Service Data Service Decoder * @ingroup HiDec */ pthread_once_t vbi_init_once = PTHREAD_ONCE_INIT; void vbi_init (void) { #ifdef ENABLE_NLS bindtextdomain (PACKAGE, PACKAGE_LOCALE_DIR); #endif } /** * @ingroup Basic * @param mask Which kind of information to log. Can be @c 0. * @param log_fn This function is called with log messages. Consider * vbi_log_on_stderr(). Can be @c NULL to disable logging. * @param user_data User pointer passed through to the @a log_fn function. * * Various library functions can print warnings, errors and * information useful to debug the library. With this function you can * enable these messages and designate a function to print them. * * This function enables logging globally. You can also call the * set_log_fn() function of individual modules to reduce the scope or * redirect messages from that module to another log function. * * @note * The number of contents of messages may change in the future. * * @since 0.2.22 */ void vbi_set_log_fn (vbi_log_mask mask, vbi_log_fn * log_fn, void * user_data) { if (NULL == log_fn) mask = 0; _vbi_global_log.mask = mask; _vbi_global_log.fn = log_fn; _vbi_global_log.user_data = user_data; } /* * Events */ /* Should this be public? */ static void vbi_event_enable(vbi_decoder *vbi, int mask) { int activate; activate = mask & ~vbi->event_mask; if (activate & VBI_EVENT_TTX_PAGE) vbi_teletext_channel_switched(vbi); if (activate & VBI_EVENT_CAPTION) vbi_caption_channel_switched(vbi); if (activate & (VBI_EVENT_NETWORK | VBI_EVENT_NETWORK_ID)) memset(&vbi->network, 0, sizeof(vbi->network)); if (activate & VBI_EVENT_TRIGGER) vbi_trigger_flush(vbi); if (activate & (VBI_EVENT_ASPECT | VBI_EVENT_PROG_INFO)) { if (!(vbi->event_mask & (VBI_EVENT_ASPECT | VBI_EVENT_PROG_INFO))) { vbi_reset_prog_info(&vbi->prog_info[0]); vbi_reset_prog_info(&vbi->prog_info[1]); vbi->prog_info[1].future = TRUE; vbi->prog_info[0].future = FALSE; vbi->aspect_source = 0; } } if (activate & VBI_EVENT_PROG_ID) CLEAR (vbi->vps_pid); vbi->event_mask = mask; } /** * @param vbi Initialized vbi decoding context. * @param event_mask Events the handler is waiting for. * @param handler Event handler function. * @param user_data Pointer passed to the handler. * * @deprecated * Replaces all existing handlers with this @a handler function, * ignoring @a user_data. Use vbi_event_handler_register() in new code. * * @return * FALSE on failure. */ vbi_bool vbi_event_handler_add(vbi_decoder *vbi, int event_mask, vbi_event_handler handler, void *user_data) { struct event_handler *eh, **ehp; int found = 0, mask = 0, was_locked; /* If was_locked we're a handler, no recursion. */ was_locked = pthread_mutex_trylock(&vbi->event_mutex); ehp = &vbi->handlers; while ((eh = *ehp)) { if (eh->handler == handler) { found = 1; if (!event_mask) { *ehp = eh->next; if (vbi->next_handler == eh) vbi->next_handler = eh->next; /* in event send loop */ free(eh); continue; } else eh->event_mask = event_mask; } mask |= eh->event_mask; ehp = &eh->next; } if (!found && event_mask) { if (!(eh = (struct event_handler *) calloc(1, sizeof(*eh)))) return FALSE; eh->event_mask = event_mask; mask |= event_mask; eh->handler = handler; eh->user_data = user_data; *ehp = eh; } vbi_event_enable(vbi, mask); if (!was_locked) pthread_mutex_unlock(&vbi->event_mutex); return TRUE; } /** * @param vbi Initialized vbi decoding context. * @param handler Event handler function. * * @deprecated * This functions lacks a user_data parameter. * Use vbi_event_handler_register() in new code. */ void vbi_event_handler_remove(vbi_decoder *vbi, vbi_event_handler handler) { vbi_event_handler_add(vbi, 0, handler, NULL); } /** * @param vbi Initialized vbi decoding context. * @param event_mask Events the handler is waiting for. * @param handler Event handler function. * @param user_data Pointer passed to the handler. * * Registers a new event handler. @a event_mask can be any 'or' of VBI_EVENT_ * symbols, -1 for all events and 0 for none. When the @a handler with * @a user_data is already registered, its event_mask will be changed. Any * number of handlers can be registered, also different handlers for the same * event which will be called in registration order. * * Apart of adding handlers this function also enables and disables decoding * of data services depending on the presence of at least one handler for the * respective data. A @c VBI_EVENT_TTX_PAGE handler for example enables Teletext * decoding. * * This function can be safely called at any time, even from a handler. * * @return * @c FALSE on failure. */ vbi_bool vbi_event_handler_register(vbi_decoder *vbi, int event_mask, vbi_event_handler handler, void *user_data) { struct event_handler *eh, **ehp; int found = 0, mask = 0, was_locked; /* If was_locked we're a handler, no recursion. */ was_locked = pthread_mutex_trylock(&vbi->event_mutex); ehp = &vbi->handlers; while ((eh = *ehp)) { if (eh->handler == handler && eh->user_data == user_data) { found = 1; if (!event_mask) { *ehp = eh->next; if (vbi->next_handler == eh) vbi->next_handler = eh->next; /* in event send loop */ free(eh); continue; } else eh->event_mask = event_mask; } mask |= eh->event_mask; ehp = &eh->next; } if (!found && event_mask) { if (!(eh = (struct event_handler *) calloc(1, sizeof(*eh)))) return FALSE; eh->event_mask = event_mask; mask |= event_mask; eh->handler = handler; eh->user_data = user_data; *ehp = eh; } vbi_event_enable(vbi, mask); if (!was_locked) pthread_mutex_unlock(&vbi->event_mutex); return TRUE; } /** * @param vbi Initialized vbi decoding context. * @param handler Event handler function. * @param user_data Pointer passed to the handler. * * Unregisters an event handler. * * Apart of removing a handler this function also disables decoding * of data services when no handler is registered to consume the * respective data. Removing the last @c VBI_EVENT_TTX_PAGE handler for * example disables Teletext decoding. * * This function can be safely called at any time, even from a handler * removing itself or another handler, and regardless if the @a handler * has been successfully registered. **/ void vbi_event_handler_unregister(vbi_decoder *vbi, vbi_event_handler handler, void *user_data) { vbi_event_handler_register(vbi, 0, handler, user_data); } /** * @internal * @param vbi Initialized vbi decoding context. * @param ev The event to send. * * Traverses the list of event handlers and calls each handler waiting * * for this @a ev->type of event, passing @a ev as parameter. * * This function is reentrant, but not supposed to be called from * different threads to ensure correct event order. */ void vbi_send_event(vbi_decoder *vbi, vbi_event *ev) { struct event_handler *eh; pthread_mutex_lock(&vbi->event_mutex); for (eh = vbi->handlers; eh; eh = vbi->next_handler) { vbi->next_handler = eh->next; if (eh->event_mask & ev->type) eh->handler(ev, eh->user_data); } pthread_mutex_unlock(&vbi->event_mutex); } /* * VBI Decoder */ static inline double current_time(void) { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + tv.tv_usec * (1 / 1e6); } /** * @param vbi Initialized vbi decoding context as returned by vbi_decoder_new(). * @param sliced Array of vbi_sliced data packets to be decoded. * @param lines Number of vbi_sliced data packets, i. e. VBI lines. * @param time Timestamp associated with all sliced data packets. * This is the time in seconds and fractions since 1970-01-01 00:00, * for example from function gettimeofday(). @a time should only * increment, the latest time entered is considered the current time * for activity calculation. * * @brief Main function of the data service decoder. * * Decodes zero or more lines of sliced VBI data from the same video * frame, updates the decoder state and calls event handlers. * * @a timestamp shall advance by 1/60 to 1/12.5 seconds whenever calling this * function. Failure to do so will be interpreted as frame dropping, which * starts a resynchronization cycle, eventually a channel switch may be assumed * which resets even more decoder state. So even if a frame did not contain * any useful data this function must be called, with @a lines set to zero. * * @note This is one of the few not reentrant libzvbi functions. If multiple * threads call this with the same @a vbi context you must implement your * own locking mechanism. Never call this function from an event handler. */ void vbi_decode(vbi_decoder *vbi, vbi_sliced *sliced, int lines, double time) { double d; d = time - vbi->time; if (vbi->time > 0 && (d < 0.015 || d > 0.085)) { /* * Since (dropped >= channel switch) we give * ~1.5 s, then assume a switch. */ pthread_mutex_lock(&vbi->chswcd_mutex); if (vbi->chswcd == 0) vbi->chswcd = 40; pthread_mutex_unlock(&vbi->chswcd_mutex); if (0) fprintf(stderr, "vbi frame/s dropped at %f, D=%f\n", time, time - vbi->time); if (vbi->event_mask & (VBI_EVENT_TTX_PAGE | VBI_EVENT_NETWORK | VBI_EVENT_NETWORK_ID | VBI_EVENT_LOCAL_TIME | VBI_EVENT_PROG_ID)) vbi_teletext_desync(vbi); if (vbi->event_mask & (VBI_EVENT_CAPTION | VBI_EVENT_NETWORK | VBI_EVENT_NETWORK_ID | VBI_EVENT_LOCAL_TIME | VBI_EVENT_PROG_ID)) vbi_caption_desync(vbi); } else { pthread_mutex_lock(&vbi->chswcd_mutex); if (vbi->chswcd > 0 && --vbi->chswcd == 0) { pthread_mutex_unlock(&vbi->chswcd_mutex); vbi_chsw_reset(vbi, 0); } else pthread_mutex_unlock(&vbi->chswcd_mutex); } if (time > vbi->time) vbi->time = time; while (lines) { if (sliced->id & VBI_SLICED_TELETEXT_B) vbi_decode_teletext(vbi, sliced->data); else if (sliced->id & (VBI_SLICED_CAPTION_525 | VBI_SLICED_CAPTION_625)) vbi_decode_caption(vbi, sliced->line, sliced->data); else if (sliced->id & VBI_SLICED_VPS) vbi_decode_vps(vbi, sliced->data); else if (sliced->id & VBI_SLICED_WSS_625) vbi_decode_wss_625(vbi, sliced->data, time); else if (sliced->id & VBI_SLICED_WSS_CPR1204) vbi_decode_wss_cpr1204(vbi, sliced->data); sliced++; lines--; } if (vbi->event_mask & VBI_EVENT_TRIGGER) vbi_deferred_trigger(vbi); if (0 && (rand() % 511) == 0) vbi_eacem_trigger(vbi, (unsigned char *) /* Latin-1 */ "[n:Zapping][5450]"); } void vbi_chsw_reset(vbi_decoder *vbi, vbi_nuid identified) { vbi_nuid old_nuid; old_nuid = vbi->network.ev.network.nuid; if (0) fprintf(stderr, "*** chsw identified=%d old nuid=%d\n", identified, old_nuid); cache_network_unref (vbi->cn); vbi->cn = _vbi_cache_add_network (vbi->ca, /* nk */ NULL, VBI_VIDEOSTD_SET_625_50); assert (NULL != vbi->cn); vbi_teletext_channel_switched(vbi); vbi_caption_channel_switched(vbi); if (identified == 0) { memset(&vbi->network, 0, sizeof(vbi->network)); if (old_nuid != 0) { vbi->network.type = VBI_EVENT_NETWORK; vbi_send_event(vbi, &vbi->network); } } /* else already identified */ vbi_trigger_flush(vbi); /* sic? */ if (vbi->aspect_source > 0) { vbi_event e; e.ev.aspect.first_line = (vbi->aspect_source == 1) ? 23 : 22; e.ev.aspect.last_line = (vbi->aspect_source == 1) ? 310 : 262; e.ev.aspect.ratio = 1.0; e.ev.aspect.film_mode = 0; e.ev.aspect.open_subtitles = VBI_SUBT_UNKNOWN; e.type = VBI_EVENT_ASPECT; vbi_send_event(vbi, &e); } vbi_reset_prog_info(&vbi->prog_info[0]); vbi_reset_prog_info(&vbi->prog_info[1]); /* XXX event? */ vbi->prog_info[1].future = TRUE; vbi->prog_info[0].future = FALSE; vbi->aspect_source = 0; vbi->wss_last[0] = 0; vbi->wss_last[1] = 0; vbi->wss_rep_ct = 0; vbi->wss_time = 0.0; vbi->vt.header_page.pgno = 0; pthread_mutex_lock(&vbi->chswcd_mutex); vbi->chswcd = 0; pthread_mutex_unlock(&vbi->chswcd_mutex); } /** * @param vbi VBI decoding context. * @param nuid Set to zero for now. * * Call this after switching away from the channel (RF channel, * video input line, precisely: the network) from which this context * used to receive vbi data, to reset the decoding context accordingly. * This includes deletion of all cached Teletext and Closed Caption pages. * * The decoder attempts to detect channel switches automatically, but this * is not 100 % reliable, especially without receiving and decoding Teletext * or VPS which frequently transmit network identifiers. * * Note the reset is not executed until the next frame is about to be * decoded, so you may still receive "old" events after calling this. You * may also receive blank events (e. g. unknown network, unknown aspect * ratio) revoking a previously sent event, until new information becomes * available. */ void vbi_channel_switched(vbi_decoder *vbi, vbi_nuid nuid) { /* XXX nuid */ nuid = nuid; pthread_mutex_lock(&vbi->chswcd_mutex); vbi->chswcd = 1; pthread_mutex_unlock(&vbi->chswcd_mutex); } static inline uint8_t transp(uint8_t val, uint8_t brig, int8_t cont) { int32_t r = ((((int32_t)val - 128) * cont) / 64) + brig; return SATURATE(r, 0, 255); } /** * @internal * @param vbi Initialized vbi decoding context. * @param d Destination palette. * @param s Source palette. * @param entries Size of source and destination palette. * * Transposes the source palette by @a vbi->brightness and @a vbi->contrast. */ void vbi_transp_colormap(vbi_decoder *vbi, vbi_rgba *d, vbi_rgba *s, int entries) { uint8_t brig = SATURATE(vbi->brightness, 0, 255); int8_t cont = SATURATE(vbi->contrast, -128, +127); while (entries--) { *d++ = VBI_RGBA(transp(VBI_R(*s), brig, cont), transp(VBI_G(*s), brig, cont), transp(VBI_B(*s), brig, cont)); s++; } } /** * @param vbi Initialized vbi decoding context. * @param brightness 0 dark ... 255 bright, default 128. * * Change brightness of text pages, this affects the * color palette of pages fetched with vbi_fetch_vt_page() and * vbi_fetch_cc_page(). */ void vbi_set_brightness(vbi_decoder *vbi, int brightness) { vbi->brightness = brightness; vbi_caption_color_level(vbi); } /** * @param vbi Initialized vbi decoding context. * @param contrast -128 inverse ... 0 none ... 127 maximum, default 64. * * Change contrast of text pages, this affects the * color palette of pages fetched with vbi_fetch_vt_page() and * vbi_fetch_cc_page(). */ void vbi_set_contrast(vbi_decoder *vbi, int contrast) { vbi->contrast = contrast; vbi_caption_color_level(vbi); } /** * @param vbi Initialized vbi decoding context. * @param pgno Teletext or Closed Caption page to examine, see vbi_pgno. * @param subno The highest subpage number of this page will be * stored here. @a subno can be @c NULL. * @param language If it is possible to determine the language a page * is written in, a pointer to the language name (Latin-1) will * be stored here, @c NULL if the language is unknown. @a language * can be @c NULL if this information is not needed. * * Returns information about the page. * * For Closed Caption pages (@a pgno 1 ... 8) @a subno will always * be zero, @a language set or @c NULL. The return value will be * @c VBI_SUBTITLE_PAGE for page 1 ... 4 (Closed Caption * channel 1 ... 4), @c VBI_NORMAL_PAGE for page 5 ... 8 (Text channel * 1 ... 4), or @c VBI_NO_PAGE if no data is currently transmitted on * the channel. * * For Teletext pages (@a pgno 0x100 ... 0x8FF) @a subno returns * the highest subpage number used. Note this number can be larger * (but not smaller) than the number of subpages actually received * and cached. Still there is no guarantee the advertised subpages * will ever appear or stay in cache. * * * * * * * * *
subnomeaning
0single page, no subpages
1never
2 ... 0x3F7Fhas subpages 1 ... @a subno
0xFFFEhas unknown number (two or more) of subpages
0xFFFFpresence of subpages unknown
* * @a language currently returns the language of subtitle pages, @c NULL * if unknown or the page is not classified as @c VBI_SUBTITLE_PAGE. * * Other page types are: * * * * * * * * * * * * * *
VBI_NO_PAGEPage is not in transmission
VBI_NORMAL_PAGE 
VBI_SUBTITLE_PAGE 
VBI_SUBTITLE_INDEXList of subtitle pages
VBI_NONSTD_SUBPAGESFor example a world time page
VBI_PROGR_WARNINGProgram related warning (perhaps * schedule change anouncements, the Teletext specification does not * elaborate on this)
VBI_CURRENT_PROGRInformation about the * current program
VBI_NOW_AND_NEXTBrief information about the * current and next program
VBI_PROGR_INDEXProgram index page (perhaps the front * page of all program related pages)
VBI_PROGR_SCHEDULEProgram schedule page
VBI_UNKNOWN_PAGE 
* * @note The results of this function are volatile: As more information * becomes available and pages are edited (e. g. activation of subtitles, * news updates, program related pages) subpage numbers can grow, page * types, subno 0xFFFE and 0xFFFF and languages can change. * * @return * Page type. */ vbi_page_type vbi_classify_page(vbi_decoder *vbi, vbi_pgno pgno, vbi_subno *subno, char **language) { struct ttx_page_stat *ps; int code, subc; char *lang; if (!subno) subno = &subc; if (!language) language = ⟨ *subno = 0; *language = NULL; if (pgno < 1) { return VBI_UNKNOWN_PAGE; } else if (pgno <= 8) { if ((current_time() - vbi->cc.channel[pgno - 1].time) > 20) return VBI_NO_PAGE; *language = (char *) vbi->cc.channel[pgno - 1].language; return (pgno <= 4) ? VBI_SUBTITLE_PAGE : VBI_NORMAL_PAGE; } else if (pgno < 0x100 || pgno > 0x8FF) { return VBI_UNKNOWN_PAGE; } ps = cache_network_page_stat (vbi->cn, pgno); code = ps->page_type; if (code != VBI_UNKNOWN_PAGE) { if (code == VBI_SUBTITLE_PAGE) { if (ps->charset_code != 0xFF) *language = vbi_font_descriptors[ps->charset_code].label; } else if (code == VBI_TOP_BLOCK || code == VBI_TOP_GROUP) code = VBI_NORMAL_PAGE; else if (code == VBI_NOT_PUBLIC || code > 0xE0) return VBI_UNKNOWN_PAGE; *subno = ps->subcode; return code; } if ((pgno & 0xFF) <= 0x99) { *subno = 0xFFFF; return VBI_NORMAL_PAGE; /* wild guess */ } return VBI_UNKNOWN_PAGE; } /** * @param pi * * Convenience function to set a vbi_program_info * structure to defaults. */ void vbi_reset_prog_info(vbi_program_info *pi) { int i; /* PID */ pi->month = -1; pi->day = -1; pi->hour = -1; pi->min = -1; pi->tape_delayed = 0; /* PL */ pi->length_hour = -1; pi->length_min = -1; pi->elapsed_hour = -1; pi->elapsed_min = -1; pi->elapsed_sec = -1; /* PN */ pi->title[0] = 0; /* PT */ pi->type_classf = VBI_PROG_CLASSF_NONE; /* PR */ pi->rating_auth = VBI_RATING_AUTH_NONE; /* PAS */ pi->audio[0].mode = VBI_AUDIO_MODE_UNKNOWN; pi->audio[0].language = NULL; pi->audio[1].mode = VBI_AUDIO_MODE_UNKNOWN; pi->audio[1].language = NULL; /* PCS */ pi->caption_services = -1; for (i = 0; i < 8; i++) pi->caption_language[i] = NULL; /* CGMS */ pi->cgms_a = -1; /* AR */ pi->aspect.first_line = -1; pi->aspect.last_line = -1; pi->aspect.ratio = 0.0; pi->aspect.film_mode = 0; pi->aspect.open_subtitles = VBI_SUBT_UNKNOWN; /* PD */ for (i = 0; i < 8; i++) pi->description[i][0] = 0; } /** * @param vbi Decoder structure allocated with vbi_decoder_new(). * @brief Delete a data service decoder instance. */ void vbi_decoder_delete(vbi_decoder *vbi) { struct event_handler *eh; if (NULL == vbi) return; vbi_trigger_flush(vbi); vbi_caption_destroy(vbi); while (NULL != (eh = vbi->handlers)) { vbi_event_handler_unregister (vbi, eh->handler, eh->user_data); } pthread_mutex_destroy(&vbi->prog_info_mutex); pthread_mutex_destroy(&vbi->event_mutex); pthread_mutex_destroy(&vbi->chswcd_mutex); cache_network_unref (vbi->cn); vbi_cache_delete (vbi->ca); CLEAR (*vbi); free (vbi); } /** * @brief Allocate a new data service decoder instance. * * @return * vbi_decoder pointer or @c NULL on failure, probably due to lack * of memory. */ vbi_decoder * vbi_decoder_new(void) { vbi_decoder *vbi; pthread_once (&vbi_init_once, vbi_init); vbi = (vbi_decoder *) calloc (1, sizeof (*vbi)); if (NULL == vbi) goto failed; vbi->ca = vbi_cache_new (); if (NULL == vbi->ca) goto failed; vbi->cn = _vbi_cache_add_network (vbi->ca, /* nk */ NULL, VBI_VIDEOSTD_SET_625_50); if (NULL == vbi->cn) goto failed; pthread_mutex_init(&vbi->chswcd_mutex, NULL); pthread_mutex_init(&vbi->event_mutex, NULL); pthread_mutex_init(&vbi->prog_info_mutex, NULL); vbi->time = 0.0; vbi->brightness = 128; vbi->contrast = 64; vbi_teletext_init(vbi); vbi_teletext_set_level(vbi, VBI_WST_LEVEL_2p5); vbi_caption_init(vbi); return vbi; failed: if (NULL != vbi) { cache_network_unref (vbi->cn); vbi_cache_delete (vbi->ca); CLEAR (*vbi); free (vbi); } return NULL; } /** * @ingroup Basic * * @param major Store major number here, can be NULL. * @param minor Store minor number here, can be NULL. * @param micro Store micro number here, can be NULL. * * Returns the library version defined in the libzvbi.h header file * when the library was compiled. * * @since 0.2.5 */ void vbi_version (unsigned int * major, unsigned int * minor, unsigned int * micro) { if (major) *major = VBI_VERSION_MAJOR; if (minor) *minor = VBI_VERSION_MINOR; if (micro) *micro = VBI_VERSION_MICRO; } /** * @param vbi * @param pgno * @param subno * * @deprecated At the moment pages can only be added to the * cache but not removed unless the decoder is reset. That * will change, making the result volatile in a multithreaded * environment. * * @returns * @c TRUE if the given page is cached. */ int vbi_is_cached (vbi_decoder * vbi, int pgno, int subno) { cache_page *cp; cp = _vbi_cache_get_page (vbi->ca, vbi->cn, pgno, subno, /* subno_mask */ -1); cache_page_unref (cp); return NULL != cp; } /** * @param vbi * @param pgno * * @deprecated Rationale same as vbi_is_cached(). * * @returns * Highest cached subpage of this page. */ int vbi_cache_hi_subno (vbi_decoder * vbi, int pgno) { const struct ttx_page_stat *ps; ps = cache_network_const_page_stat (vbi->cn, pgno); return ps->subno_max; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/vbi.h000066400000000000000000000116571476363111200143110ustar00rootroot00000000000000/* * libzvbi -- VBI decoding library * * Copyright (C) 2000, 2001, 2002 Michael H. Schimek * Copyright (C) 2000, 2001 Iñaki García Etxebarria * * Originally based on AleVT 1.5.1 by Edgar Toernig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: vbi.h,v 1.17 2009-03-04 21:47:56 mschimek Exp $ */ #ifndef VBI_H #define VBI_H #include #include "teletext_decoder.h" #include "cc.h" #include "decoder.h" #include "event.h" #include "cache-priv.h" #include "trigger.h" #include "pfc_demux.h" #include "pdc.h" struct event_handler { struct event_handler * next; int event_mask; vbi_event_handler handler; void * user_data; }; struct vbi_decoder { double time; pthread_mutex_t chswcd_mutex; int chswcd; vbi_event network; vbi_trigger * triggers; pthread_mutex_t prog_info_mutex; vbi_program_info prog_info[2]; int aspect_source; int brightness; int contrast; struct teletext vt; struct caption cc; cache_network * cn; vbi_cache * ca; vbi_pfc_demux epg_pc[2]; /* preliminary */ int pageref; pthread_mutex_t event_mutex; int event_mask; struct event_handler * handlers; struct event_handler * next_handler; unsigned char wss_last[2]; int wss_rep_ct; double wss_time; vbi_program_id vps_pid; }; #ifndef VBI_DECODER #define VBI_DECODER /** * @ingroup HiDec * @brief Opaque VBI data service decoder object. * * Allocate with vbi_decoder_new(). */ typedef struct vbi_decoder vbi_decoder; #endif /* * vbi_page_type, the page identification codes, * are derived from the MIP code scheme: * * MIP 0x01 ... 0x51 -> 0x01 (subpages) * MIP 0x70 ... 0x77 -> 0x70 (language) * MIP 0x7B -> 0x7C (subpages) * MIP 0x7E -> 0x7F (subpages) * MIP 0x81 ... 0xD1 -> 0x81 (subpages) * MIP reserved -> 0xFF (VBI_UNKNOWN_PAGE) * * MIP 0x80 and 0xE0 ... 0xFE are not returned by * vbi_classify_page(). * * TOP BTT mapping: * * BTT 0 -> 0x00 (VBI_NOPAGE) * BTT 1 -> 0x70 (VBI_SUBTITLE_PAGE) * BTT 2 ... 3 -> 0x7F (VBI_PROGR_INDEX) * BTT 4 ... 5 -> 0xFA (VBI_TOP_BLOCK -> VBI_NORMAL_PAGE) * BTT 6 ... 7 -> 0xFB (VBI_TOP_GROUP -> VBI_NORMAL_PAGE) * BTT 8 ... 11 -> 0x01 (VBI_NORMAL_PAGE) * BTT 12 ... 15 -> 0xFF (VBI_UNKNOWN_PAGE) * * 0xFA, 0xFB, 0xFF are reserved MIP codes used * by libzvbi to identify TOP and unknown pages. */ /* Public */ /** * @ingroup HiDec * @brief Page classification. * * See vbi_classify_page(). */ typedef enum { VBI_NO_PAGE = 0x00, VBI_NORMAL_PAGE = 0x01, VBI_SUBTITLE_PAGE = 0x70, VBI_SUBTITLE_INDEX = 0x78, VBI_NONSTD_SUBPAGES = 0x79, VBI_PROGR_WARNING = 0x7A, VBI_CURRENT_PROGR = 0x7C, VBI_NOW_AND_NEXT = 0x7D, VBI_PROGR_INDEX = 0x7F, VBI_PROGR_SCHEDULE = 0x81, VBI_UNKNOWN_PAGE = 0xFF /* Private */ #ifndef DOXYGEN_SHOULD_SKIP_THIS , VBI_NOT_PUBLIC = 0x80, VBI_CA_DATA_BROADCAST = 0xE0, VBI_EPG_DATA = 0xE3, VBI_SYSTEM_PAGE = 0xE7, VBI_DISP_SYSTEM_PAGE = 0xF7, VBI_KEYWORD_SEARCH_LIST = 0xF9, VBI_TOP_BLOCK = 0xFA, VBI_TOP_GROUP = 0xFB, VBI_TRIGGER_DATA = 0xFC, VBI_ACI = 0xFD, VBI_TOP_PAGE = 0xFE #endif /* Public */ } vbi_page_type; /** * @addtogroup Render * @{ */ extern void vbi_set_brightness(vbi_decoder *vbi, int brightness); extern void vbi_set_contrast(vbi_decoder *vbi, int contrast); /** @} */ /** * @addtogroup Service * @{ */ extern vbi_decoder * vbi_decoder_new(void); extern void vbi_decoder_delete(vbi_decoder *vbi); extern void vbi_decode(vbi_decoder *vbi, vbi_sliced *sliced, int lines, double timestamp); extern void vbi_channel_switched(vbi_decoder *vbi, vbi_nuid nuid); extern vbi_page_type vbi_classify_page(vbi_decoder *vbi, vbi_pgno pgno, vbi_subno *subno, char **language); extern void vbi_version(unsigned int *major, unsigned int *minor, unsigned int *micro); extern void vbi_set_log_fn (vbi_log_mask mask, vbi_log_fn * log_fn, void * user_data); /** @} */ /* Private */ extern pthread_once_t vbi_init_once; extern void vbi_init(void); extern void vbi_transp_colormap(vbi_decoder *vbi, vbi_rgba *d, vbi_rgba *s, int entries); extern void vbi_chsw_reset(vbi_decoder *vbi, vbi_nuid nuid); #endif /* VBI_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/version.h000066400000000000000000000002051476363111200152010ustar00rootroot00000000000000/* TODO: Generate these based on configure.ac*/ #define VBI_VERSION_MAJOR 0 #define VBI_VERSION_MINOR 2 #define VBI_VERSION_MICRO 44 zvbi-0.2.44/src/videodev.h000066400000000000000000000040611476363111200153250ustar00rootroot00000000000000/* * libzvbi -- Video For Linux API definitions * * Author Michael H. Schimek * * This file was not copied from the Linux kernel sources and in * author's opinion contains only uncopyrightable facts which are * necessary for interoperability. */ #ifndef VIDEODEV_H #define VIDEODEV_H #include #ifndef __LINUX_VIDEODEV2_H /* type */ # define VID_TYPE_CAPTURE 0x0001 # define VID_TYPE_TELETEXT 0x0004 #endif struct video_capability { char name[32]; int type; int channels; int audios; int maxwidth; int maxheight; int minwidth; int minheight; }; /* flags */ #define VIDEO_VC_TUNER 0x0001 /* type */ #define VIDEO_TYPE_TV 0x0001 struct video_channel { int channel; char name[32]; int tuners; uint32_t flags; uint16_t type; uint16_t norm; }; /* mode */ enum { VIDEO_MODE_PAL = 0, VIDEO_MODE_NTSC, VIDEO_MODE_SECAM }; struct video_tuner { int tuner; char name[32]; unsigned long rangelow; unsigned long rangehigh; uint32_t flags; uint16_t mode; uint16_t signal; }; struct video_unit { int video; int vbi; int radio; int audio; int teletext; }; /* sample_format */ #define VIDEO_PALETTE_RAW 12 /* flags */ #define VBI_UNSYNC 0x0001 #define VBI_INTERLACED 0x0002 struct vbi_format { uint32_t sampling_rate; uint32_t samples_per_line; uint32_t sample_format; int32_t start[2]; uint32_t count[2]; uint32_t flags; }; #define VIDIOCGCAP _IOR ('v', 1, struct video_capability) #define VIDIOCGCHAN _IOWR ('v', 2, struct video_channel) #define VIDIOCSCHAN _IOW ('v', 3, struct video_channel) #define VIDIOCGTUNER _IOWR ('v', 4, struct video_tuner) #define VIDIOCSTUNER _IOW ('v', 5, struct video_tuner) #define VIDIOCGFREQ _IOR ('v', 14, unsigned long) #define VIDIOCSFREQ _IOW ('v', 15, unsigned long) #define VIDIOCGUNIT _IOR ('v', 21, struct video_unit) #define VIDIOCGVBIFMT _IOR ('v', 28, struct vbi_format) #define VIDIOCSVBIFMT _IOW ('v', 29, struct vbi_format) #define BASE_VIDIOCPRIVATE 192 #endif /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/videodev2.h000066400000000000000000000013021476363111200154020ustar00rootroot00000000000000/* * libzvbi -- Video For Linux Two API 0.20 definitions * * Author Michael H. Schimek * * This file was not copied from the Linux kernel sources and in * author's opinion contains only uncopyrightable facts which are * necessary for interoperability. */ #ifndef VIDEODEV2_H #define VIDEODEV2_H #include struct v4l2_capability { char name[32]; int type; int inputs; int outputs; int audios; int maxwidth; int maxheight; int minwidth; int minheight; int maxframerate; uint32_t flags; uint32_t reserved[4]; }; #define VIDIOC_QUERYCAP _IOR ('V', 0, struct v4l2_capability) #endif /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/videodev2k.h000066400000000000000000001357361476363111200156000ustar00rootroot00000000000000/* * Video for Linux Two header file * * Copyright (C) 1999-2007 the contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Alternatively you can redistribute this file under the terms of the * BSD license as stated below: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. The names of its contributors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Header file for v4l or V4L2 drivers and applications * with public API. * All kernel-specific stuff were moved to media/v4l2-dev.h, so * no #if __KERNEL tests are allowed here * * See http://linuxtv.org for more info * * Author: Bill Dirks * Justin Schoeman * Hans Verkuil * et al. */ #ifndef __LINUX_VIDEODEV2_H #define __LINUX_VIDEODEV2_H #define __user /* * Common stuff for both V4L1 and V4L2 * Moved from videodev.h */ #define VIDEO_MAX_FRAME 32 #define VID_TYPE_CAPTURE 1 /* Can capture */ #define VID_TYPE_TUNER 2 /* Can tune */ #define VID_TYPE_TELETEXT 4 /* Does teletext */ #define VID_TYPE_OVERLAY 8 /* Overlay onto frame buffer */ #define VID_TYPE_CHROMAKEY 16 /* Overlay by chromakey */ #define VID_TYPE_CLIPPING 32 /* Can clip */ #define VID_TYPE_FRAMERAM 64 /* Uses the frame buffer memory */ #define VID_TYPE_SCALES 128 /* Scalable */ #define VID_TYPE_MONOCHROME 256 /* Monochrome only */ #define VID_TYPE_SUBCAPTURE 512 /* Can capture subareas of the image */ #define VID_TYPE_MPEG_DECODER 1024 /* Can decode MPEG streams */ #define VID_TYPE_MPEG_ENCODER 2048 /* Can encode MPEG streams */ #define VID_TYPE_MJPEG_DECODER 4096 /* Can decode MJPEG streams */ #define VID_TYPE_MJPEG_ENCODER 8192 /* Can encode MJPEG streams */ /* * M I S C E L L A N E O U S */ /* Four-character-code (FOURCC) */ #define v4l2_fourcc(a,b,c,d)\ (((__u32)(a)<<0)|((__u32)(b)<<8)|((__u32)(c)<<16)|((__u32)(d)<<24)) /* * E N U M S */ enum v4l2_field { V4L2_FIELD_ANY = 0, /* driver can choose from none, top, bottom, interlaced depending on whatever it thinks is approximate ... */ V4L2_FIELD_NONE = 1, /* this device has no fields ... */ V4L2_FIELD_TOP = 2, /* top field only */ V4L2_FIELD_BOTTOM = 3, /* bottom field only */ V4L2_FIELD_INTERLACED = 4, /* both fields interlaced */ V4L2_FIELD_SEQ_TB = 5, /* both fields sequential into one buffer, top-bottom order */ V4L2_FIELD_SEQ_BT = 6, /* same as above + bottom-top order */ V4L2_FIELD_ALTERNATE = 7, /* both fields alternating into separate buffers */ V4L2_FIELD_INTERLACED_TB = 8, /* both fields interlaced, top field first and the top field is transmitted first */ V4L2_FIELD_INTERLACED_BT = 9, /* both fields interlaced, top field first and the bottom field is transmitted first */ }; #define V4L2_FIELD_HAS_TOP(field) \ ((field) == V4L2_FIELD_TOP ||\ (field) == V4L2_FIELD_INTERLACED ||\ (field) == V4L2_FIELD_INTERLACED_TB ||\ (field) == V4L2_FIELD_INTERLACED_BT ||\ (field) == V4L2_FIELD_SEQ_TB ||\ (field) == V4L2_FIELD_SEQ_BT) #define V4L2_FIELD_HAS_BOTTOM(field) \ ((field) == V4L2_FIELD_BOTTOM ||\ (field) == V4L2_FIELD_INTERLACED ||\ (field) == V4L2_FIELD_INTERLACED_TB ||\ (field) == V4L2_FIELD_INTERLACED_BT ||\ (field) == V4L2_FIELD_SEQ_TB ||\ (field) == V4L2_FIELD_SEQ_BT) #define V4L2_FIELD_HAS_BOTH(field) \ ((field) == V4L2_FIELD_INTERLACED ||\ (field) == V4L2_FIELD_INTERLACED_TB ||\ (field) == V4L2_FIELD_INTERLACED_BT ||\ (field) == V4L2_FIELD_SEQ_TB ||\ (field) == V4L2_FIELD_SEQ_BT) enum v4l2_buf_type { V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, V4L2_BUF_TYPE_VIDEO_OVERLAY = 3, V4L2_BUF_TYPE_VBI_CAPTURE = 4, V4L2_BUF_TYPE_VBI_OUTPUT = 5, V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6, V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7, #if 1 /*KEEP*/ /* Experimental */ V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, #endif V4L2_BUF_TYPE_PRIVATE = 0x80, }; enum v4l2_ctrl_type { V4L2_CTRL_TYPE_INTEGER = 1, V4L2_CTRL_TYPE_BOOLEAN = 2, V4L2_CTRL_TYPE_MENU = 3, V4L2_CTRL_TYPE_BUTTON = 4, V4L2_CTRL_TYPE_INTEGER64 = 5, V4L2_CTRL_TYPE_CTRL_CLASS = 6, }; enum v4l2_tuner_type { V4L2_TUNER_RADIO = 1, V4L2_TUNER_ANALOG_TV = 2, V4L2_TUNER_DIGITAL_TV = 3, }; enum v4l2_memory { V4L2_MEMORY_MMAP = 1, V4L2_MEMORY_USERPTR = 2, V4L2_MEMORY_OVERLAY = 3, }; /* see also http://vektor.theorem.ca/graphics/ycbcr/ */ enum v4l2_colorspace { /* ITU-R 601 -- broadcast NTSC/PAL */ V4L2_COLORSPACE_SMPTE170M = 1, /* 1125-Line (US) HDTV */ V4L2_COLORSPACE_SMPTE240M = 2, /* HD and modern captures. */ V4L2_COLORSPACE_REC709 = 3, /* broken BT878 extents (601, luma range 16-253 instead of 16-235) */ V4L2_COLORSPACE_BT878 = 4, /* These should be useful. Assume 601 extents. */ V4L2_COLORSPACE_470_SYSTEM_M = 5, V4L2_COLORSPACE_470_SYSTEM_BG = 6, /* I know there will be cameras that send this. So, this is * unspecified chromaticities and full 0-255 on each of the * Y'CbCr components */ V4L2_COLORSPACE_JPEG = 7, /* For RGB colourspaces, this is probably a good start. */ V4L2_COLORSPACE_SRGB = 8, }; enum v4l2_priority { V4L2_PRIORITY_UNSET = 0, /* not initialized */ V4L2_PRIORITY_BACKGROUND = 1, V4L2_PRIORITY_INTERACTIVE = 2, V4L2_PRIORITY_RECORD = 3, V4L2_PRIORITY_DEFAULT = V4L2_PRIORITY_INTERACTIVE, }; struct v4l2_rect { __s32 left; __s32 top; __s32 width; __s32 height; }; struct v4l2_fract { __u32 numerator; __u32 denominator; }; /* * D R I V E R C A P A B I L I T I E S */ struct v4l2_capability { __u8 driver[16]; /* i.e. "bttv" */ __u8 card[32]; /* i.e. "Hauppauge WinTV" */ __u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */ __u32 version; /* should use KERNEL_VERSION() */ __u32 capabilities; /* Device capabilities */ __u32 reserved[4]; }; /* Values for 'capabilities' field */ #define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */ #define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* Is a video output device */ #define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* Can do video overlay */ #define V4L2_CAP_VBI_CAPTURE 0x00000010 /* Is a raw VBI capture device */ #define V4L2_CAP_VBI_OUTPUT 0x00000020 /* Is a raw VBI output device */ #define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 /* Is a sliced VBI capture device */ #define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 /* Is a sliced VBI output device */ #define V4L2_CAP_RDS_CAPTURE 0x00000100 /* RDS data capture */ #define V4L2_CAP_VIDEO_OUTPUT_POS 0x00000200 /* Video output can have x,y coords */ #define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000400 /* Can do video output overlay */ #define V4L2_CAP_TUNER 0x00010000 /* has a tuner */ #define V4L2_CAP_AUDIO 0x00020000 /* has audio support */ #define V4L2_CAP_RADIO 0x00040000 /* is a radio device */ #define V4L2_CAP_READWRITE 0x01000000 /* read/write systemcalls */ #define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */ #define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */ /* * V I D E O I M A G E F O R M A T */ struct v4l2_pix_format { __u32 width; __u32 height; __u32 pixelformat; enum v4l2_field field; __u32 bytesperline; /* for padding, zero if unused */ __u32 sizeimage; enum v4l2_colorspace colorspace; __u32 priv; /* private data, depends on pixelformat */ __u32 left; /* only valid if V4L2_CAP_VIDEO_OUTPUT_POS is set */ __u32 top; /* only valid if V4L2_CAP_VIDEO_OUTPUT_POS is set */ }; /* Pixel format FOURCC depth Description */ #define V4L2_PIX_FMT_RGB332 v4l2_fourcc('R','G','B','1') /* 8 RGB-3-3-2 */ #define V4L2_PIX_FMT_RGB555 v4l2_fourcc('R','G','B','O') /* 16 RGB-5-5-5 */ #define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R','G','B','P') /* 16 RGB-5-6-5 */ #define V4L2_PIX_FMT_RGB555X v4l2_fourcc('R','G','B','Q') /* 16 RGB-5-5-5 BE */ #define V4L2_PIX_FMT_RGB565X v4l2_fourcc('R','G','B','R') /* 16 RGB-5-6-5 BE */ #define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B','G','R','3') /* 24 BGR-8-8-8 */ #define V4L2_PIX_FMT_RGB24 v4l2_fourcc('R','G','B','3') /* 24 RGB-8-8-8 */ #define V4L2_PIX_FMT_BGR32 v4l2_fourcc('B','G','R','4') /* 32 BGR-8-8-8-8 */ #define V4L2_PIX_FMT_RGB32 v4l2_fourcc('R','G','B','4') /* 32 RGB-8-8-8-8 */ #define V4L2_PIX_FMT_GREY v4l2_fourcc('G','R','E','Y') /* 8 Greyscale */ #define V4L2_PIX_FMT_YVU410 v4l2_fourcc('Y','V','U','9') /* 9 YVU 4:1:0 */ #define V4L2_PIX_FMT_YVU420 v4l2_fourcc('Y','V','1','2') /* 12 YVU 4:2:0 */ #define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y','U','Y','V') /* 16 YUV 4:2:2 */ #define V4L2_PIX_FMT_UYVY v4l2_fourcc('U','Y','V','Y') /* 16 YUV 4:2:2 */ #define V4L2_PIX_FMT_YUV422P v4l2_fourcc('4','2','2','P') /* 16 YVU422 planar */ #define V4L2_PIX_FMT_YUV411P v4l2_fourcc('4','1','1','P') /* 16 YVU411 planar */ #define V4L2_PIX_FMT_Y41P v4l2_fourcc('Y','4','1','P') /* 12 YUV 4:1:1 */ /* two planes -- one Y, one Cr + Cb interleaved */ #define V4L2_PIX_FMT_NV12 v4l2_fourcc('N','V','1','2') /* 12 Y/CbCr 4:2:0 */ #define V4L2_PIX_FMT_NV21 v4l2_fourcc('N','V','2','1') /* 12 Y/CrCb 4:2:0 */ /* The following formats are not defined in the V4L2 specification */ #define V4L2_PIX_FMT_YUV410 v4l2_fourcc('Y','U','V','9') /* 9 YUV 4:1:0 */ #define V4L2_PIX_FMT_YUV420 v4l2_fourcc('Y','U','1','2') /* 12 YUV 4:2:0 */ #define V4L2_PIX_FMT_YYUV v4l2_fourcc('Y','Y','U','V') /* 16 YUV 4:2:2 */ #define V4L2_PIX_FMT_HI240 v4l2_fourcc('H','I','2','4') /* 8 8-bit color */ #define V4L2_PIX_FMT_HM12 v4l2_fourcc('H','M','1','2') /* 8 YUV 4:2:0 16x16 macroblocks */ #define V4L2_PIX_FMT_RGB444 v4l2_fourcc('R','4','4','4') /* 16 xxxxrrrr ggggbbbb */ /* see http://www.siliconimaging.com/RGB%20Bayer.htm */ #define V4L2_PIX_FMT_SBGGR8 v4l2_fourcc('B','A','8','1') /* 8 BGBG.. GRGR.. */ /* compressed formats */ #define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M','J','P','G') /* Motion-JPEG */ #define V4L2_PIX_FMT_JPEG v4l2_fourcc('J','P','E','G') /* JFIF JPEG */ #define V4L2_PIX_FMT_DV v4l2_fourcc('d','v','s','d') /* 1394 */ #define V4L2_PIX_FMT_MPEG v4l2_fourcc('M','P','E','G') /* MPEG-1/2/4 */ /* Vendor-specific formats */ #define V4L2_PIX_FMT_WNVA v4l2_fourcc('W','N','V','A') /* Winnov hw compress */ #define V4L2_PIX_FMT_SN9C10X v4l2_fourcc('S','9','1','0') /* SN9C10x compression */ #define V4L2_PIX_FMT_PWC1 v4l2_fourcc('P','W','C','1') /* pwc older webcam */ #define V4L2_PIX_FMT_PWC2 v4l2_fourcc('P','W','C','2') /* pwc newer webcam */ #define V4L2_PIX_FMT_ET61X251 v4l2_fourcc('E','6','2','5') /* ET61X251 compression */ /* * F O R M A T E N U M E R A T I O N */ struct v4l2_fmtdesc { __u32 index; /* Format number */ enum v4l2_buf_type type; /* buffer type */ __u32 flags; __u8 description[32]; /* Description string */ __u32 pixelformat; /* Format fourcc */ __u32 reserved[4]; }; #define V4L2_FMT_FLAG_COMPRESSED 0x0001 #if 1 /*KEEP*/ /* Experimental Frame Size and frame rate enumeration */ /* * F R A M E S I Z E E N U M E R A T I O N */ enum v4l2_frmsizetypes { V4L2_FRMSIZE_TYPE_DISCRETE = 1, V4L2_FRMSIZE_TYPE_CONTINUOUS = 2, V4L2_FRMSIZE_TYPE_STEPWISE = 3, }; struct v4l2_frmsize_discrete { __u32 width; /* Frame width [pixel] */ __u32 height; /* Frame height [pixel] */ }; struct v4l2_frmsize_stepwise { __u32 min_width; /* Minimum frame width [pixel] */ __u32 max_width; /* Maximum frame width [pixel] */ __u32 step_width; /* Frame width step size [pixel] */ __u32 min_height; /* Minimum frame height [pixel] */ __u32 max_height; /* Maximum frame height [pixel] */ __u32 step_height; /* Frame height step size [pixel] */ }; struct v4l2_frmsizeenum { __u32 index; /* Frame size number */ __u32 pixel_format; /* Pixel format */ __u32 type; /* Frame size type the device supports. */ union { /* Frame size */ struct v4l2_frmsize_discrete discrete; struct v4l2_frmsize_stepwise stepwise; } u; __u32 reserved[2]; /* Reserved space for future use */ }; /* * F R A M E R A T E E N U M E R A T I O N */ enum v4l2_frmivaltypes { V4L2_FRMIVAL_TYPE_DISCRETE = 1, V4L2_FRMIVAL_TYPE_CONTINUOUS = 2, V4L2_FRMIVAL_TYPE_STEPWISE = 3, }; struct v4l2_frmival_stepwise { struct v4l2_fract min; /* Minimum frame interval [s] */ struct v4l2_fract max; /* Maximum frame interval [s] */ struct v4l2_fract step; /* Frame interval step size [s] */ }; struct v4l2_frmivalenum { __u32 index; /* Frame format index */ __u32 pixel_format; /* Pixel format */ __u32 width; /* Frame width */ __u32 height; /* Frame height */ __u32 type; /* Frame interval type the device supports. */ union { /* Frame interval */ struct v4l2_fract discrete; struct v4l2_frmival_stepwise stepwise; } u; __u32 reserved[2]; /* Reserved space for future use */ }; #endif /* * T I M E C O D E */ struct v4l2_timecode { __u32 type; __u32 flags; __u8 frames; __u8 seconds; __u8 minutes; __u8 hours; __u8 userbits[4]; }; /* Type */ #define V4L2_TC_TYPE_24FPS 1 #define V4L2_TC_TYPE_25FPS 2 #define V4L2_TC_TYPE_30FPS 3 #define V4L2_TC_TYPE_50FPS 4 #define V4L2_TC_TYPE_60FPS 5 /* Flags */ #define V4L2_TC_FLAG_DROPFRAME 0x0001 /* "drop-frame" mode */ #define V4L2_TC_FLAG_COLORFRAME 0x0002 #define V4L2_TC_USERBITS_field 0x000C #define V4L2_TC_USERBITS_USERDEFINED 0x0000 #define V4L2_TC_USERBITS_8BITCHARS 0x0008 /* The above is based on SMPTE timecodes */ struct v4l2_jpegcompression { int quality; int APPn; /* Number of APP segment to be written, * must be 0..15 */ int APP_len; /* Length of data in JPEG APPn segment */ char APP_data[60]; /* Data in the JPEG APPn segment. */ int COM_len; /* Length of data in JPEG COM segment */ char COM_data[60]; /* Data in JPEG COM segment */ __u32 jpeg_markers; /* Which markers should go into the JPEG * output. Unless you exactly know what * you do, leave them untouched. * Inluding less markers will make the * resulting code smaller, but there will * be fewer aplications which can read it. * The presence of the APP and COM marker * is influenced by APP_len and COM_len * ONLY, not by this property! */ #define V4L2_JPEG_MARKER_DHT (1<<3) /* Define Huffman Tables */ #define V4L2_JPEG_MARKER_DQT (1<<4) /* Define Quantization Tables */ #define V4L2_JPEG_MARKER_DRI (1<<5) /* Define Restart Interval */ #define V4L2_JPEG_MARKER_COM (1<<6) /* Comment segment */ #define V4L2_JPEG_MARKER_APP (1<<7) /* App segment, driver will * allways use APP0 */ }; /* * M E M O R Y - M A P P I N G B U F F E R S */ struct v4l2_requestbuffers { __u32 count; enum v4l2_buf_type type; enum v4l2_memory memory; __u32 reserved[2]; }; struct v4l2_buffer { __u32 index; enum v4l2_buf_type type; __u32 bytesused; __u32 flags; enum v4l2_field field; struct timeval timestamp; struct v4l2_timecode timecode; __u32 sequence; /* memory location */ enum v4l2_memory memory; union { __u32 offset; unsigned long userptr; } m; __u32 length; __u32 input; __u32 reserved; }; /* Flags for 'flags' field */ #define V4L2_BUF_FLAG_MAPPED 0x0001 /* Buffer is mapped (flag) */ #define V4L2_BUF_FLAG_QUEUED 0x0002 /* Buffer is queued for processing */ #define V4L2_BUF_FLAG_DONE 0x0004 /* Buffer is ready */ #define V4L2_BUF_FLAG_KEYFRAME 0x0008 /* Image is a keyframe (I-frame) */ #define V4L2_BUF_FLAG_PFRAME 0x0010 /* Image is a P-frame */ #define V4L2_BUF_FLAG_BFRAME 0x0020 /* Image is a B-frame */ #define V4L2_BUF_FLAG_TIMECODE 0x0100 /* timecode field is valid */ #define V4L2_BUF_FLAG_INPUT 0x0200 /* input field is valid */ /* * O V E R L A Y P R E V I E W */ struct v4l2_framebuffer { __u32 capability; __u32 flags; /* FIXME: in theory we should pass something like PCI device + memory * region + offset instead of some physical address */ void* base; struct v4l2_pix_format fmt; }; /* Flags for the 'capability' field. Read only */ #define V4L2_FBUF_CAP_EXTERNOVERLAY 0x0001 #define V4L2_FBUF_CAP_CHROMAKEY 0x0002 #define V4L2_FBUF_CAP_LIST_CLIPPING 0x0004 #define V4L2_FBUF_CAP_BITMAP_CLIPPING 0x0008 #define V4L2_FBUF_CAP_LOCAL_ALPHA 0x0010 #define V4L2_FBUF_CAP_GLOBAL_ALPHA 0x0020 /* Flags for the 'flags' field. */ #define V4L2_FBUF_FLAG_PRIMARY 0x0001 #define V4L2_FBUF_FLAG_OVERLAY 0x0002 #define V4L2_FBUF_FLAG_CHROMAKEY 0x0004 #define V4L2_FBUF_FLAG_LOCAL_ALPHA 0x0008 #define V4L2_FBUF_FLAG_GLOBAL_ALPHA 0x0010 struct v4l2_clip { struct v4l2_rect c; struct v4l2_clip __user *next; }; struct v4l2_window { struct v4l2_rect w; enum v4l2_field field; __u32 chromakey; struct v4l2_clip __user *clips; __u32 clipcount; void __user *bitmap; __u8 global_alpha; }; /* * C A P T U R E P A R A M E T E R S */ struct v4l2_captureparm { __u32 capability; /* Supported modes */ __u32 capturemode; /* Current mode */ struct v4l2_fract timeperframe; /* Time per frame in .1us units */ __u32 extendedmode; /* Driver-specific extensions */ __u32 readbuffers; /* # of buffers for read */ __u32 reserved[4]; }; /* Flags for 'capability' and 'capturemode' fields */ #define V4L2_MODE_HIGHQUALITY 0x0001 /* High quality imaging mode */ #define V4L2_CAP_TIMEPERFRAME 0x1000 /* timeperframe field is supported */ struct v4l2_outputparm { __u32 capability; /* Supported modes */ __u32 outputmode; /* Current mode */ struct v4l2_fract timeperframe; /* Time per frame in seconds */ __u32 extendedmode; /* Driver-specific extensions */ __u32 writebuffers; /* # of buffers for write */ __u32 reserved[4]; }; /* * I N P U T I M A G E C R O P P I N G */ struct v4l2_cropcap { enum v4l2_buf_type type; struct v4l2_rect bounds; struct v4l2_rect defrect; struct v4l2_fract pixelaspect; }; struct v4l2_crop { enum v4l2_buf_type type; struct v4l2_rect c; }; /* * A N A L O G V I D E O S T A N D A R D */ typedef __u64 v4l2_std_id; /* one bit for each */ #define V4L2_STD_PAL_B ((v4l2_std_id)0x00000001) #define V4L2_STD_PAL_B1 ((v4l2_std_id)0x00000002) #define V4L2_STD_PAL_G ((v4l2_std_id)0x00000004) #define V4L2_STD_PAL_H ((v4l2_std_id)0x00000008) #define V4L2_STD_PAL_I ((v4l2_std_id)0x00000010) #define V4L2_STD_PAL_D ((v4l2_std_id)0x00000020) #define V4L2_STD_PAL_D1 ((v4l2_std_id)0x00000040) #define V4L2_STD_PAL_K ((v4l2_std_id)0x00000080) #define V4L2_STD_PAL_M ((v4l2_std_id)0x00000100) #define V4L2_STD_PAL_N ((v4l2_std_id)0x00000200) #define V4L2_STD_PAL_Nc ((v4l2_std_id)0x00000400) #define V4L2_STD_PAL_60 ((v4l2_std_id)0x00000800) #define V4L2_STD_NTSC_M ((v4l2_std_id)0x00001000) #define V4L2_STD_NTSC_M_JP ((v4l2_std_id)0x00002000) #define V4L2_STD_NTSC_443 ((v4l2_std_id)0x00004000) #define V4L2_STD_NTSC_M_KR ((v4l2_std_id)0x00008000) #define V4L2_STD_SECAM_B ((v4l2_std_id)0x00010000) #define V4L2_STD_SECAM_D ((v4l2_std_id)0x00020000) #define V4L2_STD_SECAM_G ((v4l2_std_id)0x00040000) #define V4L2_STD_SECAM_H ((v4l2_std_id)0x00080000) #define V4L2_STD_SECAM_K ((v4l2_std_id)0x00100000) #define V4L2_STD_SECAM_K1 ((v4l2_std_id)0x00200000) #define V4L2_STD_SECAM_L ((v4l2_std_id)0x00400000) #define V4L2_STD_SECAM_LC ((v4l2_std_id)0x00800000) /* ATSC/HDTV */ #define V4L2_STD_ATSC_8_VSB ((v4l2_std_id)0x01000000) #define V4L2_STD_ATSC_16_VSB ((v4l2_std_id)0x02000000) /* FIXME: Although std_id is 64 bits, there is an issue on PPC32 architecture that makes switch(__u64) to break. So, there's a hack on v4l2-common.c rounding this value to 32 bits. As, currently, the max value is for V4L2_STD_ATSC_16_VSB (30 bits wide), it should work fine. However, if needed to add more than two standards, v4l2-common.c should be fixed. */ /* some merged standards */ #define V4L2_STD_MN (V4L2_STD_PAL_M|V4L2_STD_PAL_N|V4L2_STD_PAL_Nc|V4L2_STD_NTSC) #define V4L2_STD_B (V4L2_STD_PAL_B|V4L2_STD_PAL_B1|V4L2_STD_SECAM_B) #define V4L2_STD_GH (V4L2_STD_PAL_G|V4L2_STD_PAL_H|V4L2_STD_SECAM_G|V4L2_STD_SECAM_H) #define V4L2_STD_DK (V4L2_STD_PAL_DK|V4L2_STD_SECAM_DK) /* some common needed stuff */ #define V4L2_STD_PAL_BG (V4L2_STD_PAL_B |\ V4L2_STD_PAL_B1 |\ V4L2_STD_PAL_G) #define V4L2_STD_PAL_DK (V4L2_STD_PAL_D |\ V4L2_STD_PAL_D1 |\ V4L2_STD_PAL_K) #define V4L2_STD_PAL (V4L2_STD_PAL_BG |\ V4L2_STD_PAL_DK |\ V4L2_STD_PAL_H |\ V4L2_STD_PAL_I) #define V4L2_STD_NTSC (V4L2_STD_NTSC_M |\ V4L2_STD_NTSC_M_JP |\ V4L2_STD_NTSC_M_KR) #define V4L2_STD_SECAM_DK (V4L2_STD_SECAM_D |\ V4L2_STD_SECAM_K |\ V4L2_STD_SECAM_K1) #define V4L2_STD_SECAM (V4L2_STD_SECAM_B |\ V4L2_STD_SECAM_G |\ V4L2_STD_SECAM_H |\ V4L2_STD_SECAM_DK |\ V4L2_STD_SECAM_L |\ V4L2_STD_SECAM_LC) #define V4L2_STD_525_60 (V4L2_STD_PAL_M |\ V4L2_STD_PAL_60 |\ V4L2_STD_NTSC |\ V4L2_STD_NTSC_443) #define V4L2_STD_625_50 (V4L2_STD_PAL |\ V4L2_STD_PAL_N |\ V4L2_STD_PAL_Nc |\ V4L2_STD_SECAM) #define V4L2_STD_ATSC (V4L2_STD_ATSC_8_VSB |\ V4L2_STD_ATSC_16_VSB) #define V4L2_STD_UNKNOWN 0 #define V4L2_STD_ALL (V4L2_STD_525_60 |\ V4L2_STD_625_50) struct v4l2_standard { __u32 index; v4l2_std_id id; __u8 name[24]; struct v4l2_fract frameperiod; /* Frames, not fields */ __u32 framelines; __u32 reserved[4]; }; /* * V I D E O I N P U T S */ struct v4l2_input { __u32 index; /* Which input */ __u8 name[32]; /* Label */ __u32 type; /* Type of input */ __u32 audioset; /* Associated audios (bitfield) */ __u32 tuner; /* Associated tuner */ v4l2_std_id std; __u32 status; __u32 reserved[4]; }; /* Values for the 'type' field */ #define V4L2_INPUT_TYPE_TUNER 1 #define V4L2_INPUT_TYPE_CAMERA 2 /* field 'status' - general */ #define V4L2_IN_ST_NO_POWER 0x00000001 /* Attached device is off */ #define V4L2_IN_ST_NO_SIGNAL 0x00000002 #define V4L2_IN_ST_NO_COLOR 0x00000004 /* field 'status' - analog */ #define V4L2_IN_ST_NO_H_LOCK 0x00000100 /* No horizontal sync lock */ #define V4L2_IN_ST_COLOR_KILL 0x00000200 /* Color killer is active */ /* field 'status' - digital */ #define V4L2_IN_ST_NO_SYNC 0x00010000 /* No synchronization lock */ #define V4L2_IN_ST_NO_EQU 0x00020000 /* No equalizer lock */ #define V4L2_IN_ST_NO_CARRIER 0x00040000 /* Carrier recovery failed */ /* field 'status' - VCR and set-top box */ #define V4L2_IN_ST_MACROVISION 0x01000000 /* Macrovision detected */ #define V4L2_IN_ST_NO_ACCESS 0x02000000 /* Conditional access denied */ #define V4L2_IN_ST_VTR 0x04000000 /* VTR time constant */ /* * V I D E O O U T P U T S */ struct v4l2_output { __u32 index; /* Which output */ __u8 name[32]; /* Label */ __u32 type; /* Type of output */ __u32 audioset; /* Associated audios (bitfield) */ __u32 modulator; /* Associated modulator */ v4l2_std_id std; __u32 reserved[4]; }; /* Values for the 'type' field */ #define V4L2_OUTPUT_TYPE_MODULATOR 1 #define V4L2_OUTPUT_TYPE_ANALOG 2 #define V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY 3 /* * C O N T R O L S */ struct v4l2_control { __u32 id; __s32 value; }; struct v4l2_ext_control { __u32 id; __u32 reserved2[2]; union { __s32 value; __s64 value64; void *reserved; }; } __attribute__ ((packed)); struct v4l2_ext_controls { __u32 ctrl_class; __u32 count; __u32 error_idx; __u32 reserved[2]; struct v4l2_ext_control *controls; }; /* Values for ctrl_class field */ #define V4L2_CTRL_CLASS_USER 0x00980000 /* Old-style 'user' controls */ #define V4L2_CTRL_CLASS_MPEG 0x00990000 /* MPEG-compression controls */ #define V4L2_CTRL_ID_MASK (0x0fffffff) #define V4L2_CTRL_ID2CLASS(id) ((id) & 0x0fff0000UL) #define V4L2_CTRL_DRIVER_PRIV(id) (((id) & 0xffff) >= 0x1000) /* Used in the VIDIOC_QUERYCTRL ioctl for querying controls */ struct v4l2_queryctrl { __u32 id; enum v4l2_ctrl_type type; __u8 name[32]; /* Whatever */ __s32 minimum; /* Note signedness */ __s32 maximum; __s32 step; __s32 default_value; __u32 flags; __u32 reserved[2]; }; /* Used in the VIDIOC_QUERYMENU ioctl for querying menu items */ struct v4l2_querymenu { __u32 id; __u32 index; __u8 name[32]; /* Whatever */ __u32 reserved; }; /* Control flags */ #define V4L2_CTRL_FLAG_DISABLED 0x0001 #define V4L2_CTRL_FLAG_GRABBED 0x0002 #define V4L2_CTRL_FLAG_READ_ONLY 0x0004 #define V4L2_CTRL_FLAG_UPDATE 0x0008 #define V4L2_CTRL_FLAG_INACTIVE 0x0010 #define V4L2_CTRL_FLAG_SLIDER 0x0020 /* Query flag, to be ORed with the control ID */ #define V4L2_CTRL_FLAG_NEXT_CTRL 0x80000000 /* User-class control IDs defined by V4L2 */ #define V4L2_CID_BASE (V4L2_CTRL_CLASS_USER | 0x900) #define V4L2_CID_USER_BASE V4L2_CID_BASE /* IDs reserved for driver specific controls */ #define V4L2_CID_PRIVATE_BASE 0x08000000 #define V4L2_CID_USER_CLASS (V4L2_CTRL_CLASS_USER | 1) #define V4L2_CID_BRIGHTNESS (V4L2_CID_BASE+0) #define V4L2_CID_CONTRAST (V4L2_CID_BASE+1) #define V4L2_CID_SATURATION (V4L2_CID_BASE+2) #define V4L2_CID_HUE (V4L2_CID_BASE+3) #define V4L2_CID_AUDIO_VOLUME (V4L2_CID_BASE+5) #define V4L2_CID_AUDIO_BALANCE (V4L2_CID_BASE+6) #define V4L2_CID_AUDIO_BASS (V4L2_CID_BASE+7) #define V4L2_CID_AUDIO_TREBLE (V4L2_CID_BASE+8) #define V4L2_CID_AUDIO_MUTE (V4L2_CID_BASE+9) #define V4L2_CID_AUDIO_LOUDNESS (V4L2_CID_BASE+10) #define V4L2_CID_BLACK_LEVEL (V4L2_CID_BASE+11) #define V4L2_CID_AUTO_WHITE_BALANCE (V4L2_CID_BASE+12) #define V4L2_CID_DO_WHITE_BALANCE (V4L2_CID_BASE+13) #define V4L2_CID_RED_BALANCE (V4L2_CID_BASE+14) #define V4L2_CID_BLUE_BALANCE (V4L2_CID_BASE+15) #define V4L2_CID_GAMMA (V4L2_CID_BASE+16) #define V4L2_CID_WHITENESS (V4L2_CID_GAMMA) /* ? Not sure */ #define V4L2_CID_EXPOSURE (V4L2_CID_BASE+17) #define V4L2_CID_AUTOGAIN (V4L2_CID_BASE+18) #define V4L2_CID_GAIN (V4L2_CID_BASE+19) #define V4L2_CID_HFLIP (V4L2_CID_BASE+20) #define V4L2_CID_VFLIP (V4L2_CID_BASE+21) #define V4L2_CID_HCENTER (V4L2_CID_BASE+22) #define V4L2_CID_VCENTER (V4L2_CID_BASE+23) #define V4L2_CID_LASTP1 (V4L2_CID_BASE+24) /* last CID + 1 */ /* MPEG-class control IDs defined by V4L2 */ #define V4L2_CID_MPEG_BASE (V4L2_CTRL_CLASS_MPEG | 0x900) #define V4L2_CID_MPEG_CLASS (V4L2_CTRL_CLASS_MPEG | 1) /* MPEG streams */ #define V4L2_CID_MPEG_STREAM_TYPE (V4L2_CID_MPEG_BASE+0) enum v4l2_mpeg_stream_type { V4L2_MPEG_STREAM_TYPE_MPEG2_PS = 0, /* MPEG-2 program stream */ V4L2_MPEG_STREAM_TYPE_MPEG2_TS = 1, /* MPEG-2 transport stream */ V4L2_MPEG_STREAM_TYPE_MPEG1_SS = 2, /* MPEG-1 system stream */ V4L2_MPEG_STREAM_TYPE_MPEG2_DVD = 3, /* MPEG-2 DVD-compatible stream */ V4L2_MPEG_STREAM_TYPE_MPEG1_VCD = 4, /* MPEG-1 VCD-compatible stream */ V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD = 5, /* MPEG-2 SVCD-compatible stream */ }; #define V4L2_CID_MPEG_STREAM_PID_PMT (V4L2_CID_MPEG_BASE+1) #define V4L2_CID_MPEG_STREAM_PID_AUDIO (V4L2_CID_MPEG_BASE+2) #define V4L2_CID_MPEG_STREAM_PID_VIDEO (V4L2_CID_MPEG_BASE+3) #define V4L2_CID_MPEG_STREAM_PID_PCR (V4L2_CID_MPEG_BASE+4) #define V4L2_CID_MPEG_STREAM_PES_ID_AUDIO (V4L2_CID_MPEG_BASE+5) #define V4L2_CID_MPEG_STREAM_PES_ID_VIDEO (V4L2_CID_MPEG_BASE+6) #define V4L2_CID_MPEG_STREAM_VBI_FMT (V4L2_CID_MPEG_BASE+7) enum v4l2_mpeg_stream_vbi_fmt { V4L2_MPEG_STREAM_VBI_FMT_NONE = 0, /* No VBI in the MPEG stream */ V4L2_MPEG_STREAM_VBI_FMT_IVTV = 1, /* VBI in private packets, IVTV format */ }; /* MPEG audio */ #define V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ (V4L2_CID_MPEG_BASE+100) enum v4l2_mpeg_audio_sampling_freq { V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100 = 0, V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000 = 1, V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000 = 2, }; #define V4L2_CID_MPEG_AUDIO_ENCODING (V4L2_CID_MPEG_BASE+101) enum v4l2_mpeg_audio_encoding { V4L2_MPEG_AUDIO_ENCODING_LAYER_1 = 0, V4L2_MPEG_AUDIO_ENCODING_LAYER_2 = 1, V4L2_MPEG_AUDIO_ENCODING_LAYER_3 = 2, }; #define V4L2_CID_MPEG_AUDIO_L1_BITRATE (V4L2_CID_MPEG_BASE+102) enum v4l2_mpeg_audio_l1_bitrate { V4L2_MPEG_AUDIO_L1_BITRATE_32K = 0, V4L2_MPEG_AUDIO_L1_BITRATE_64K = 1, V4L2_MPEG_AUDIO_L1_BITRATE_96K = 2, V4L2_MPEG_AUDIO_L1_BITRATE_128K = 3, V4L2_MPEG_AUDIO_L1_BITRATE_160K = 4, V4L2_MPEG_AUDIO_L1_BITRATE_192K = 5, V4L2_MPEG_AUDIO_L1_BITRATE_224K = 6, V4L2_MPEG_AUDIO_L1_BITRATE_256K = 7, V4L2_MPEG_AUDIO_L1_BITRATE_288K = 8, V4L2_MPEG_AUDIO_L1_BITRATE_320K = 9, V4L2_MPEG_AUDIO_L1_BITRATE_352K = 10, V4L2_MPEG_AUDIO_L1_BITRATE_384K = 11, V4L2_MPEG_AUDIO_L1_BITRATE_416K = 12, V4L2_MPEG_AUDIO_L1_BITRATE_448K = 13, }; #define V4L2_CID_MPEG_AUDIO_L2_BITRATE (V4L2_CID_MPEG_BASE+103) enum v4l2_mpeg_audio_l2_bitrate { V4L2_MPEG_AUDIO_L2_BITRATE_32K = 0, V4L2_MPEG_AUDIO_L2_BITRATE_48K = 1, V4L2_MPEG_AUDIO_L2_BITRATE_56K = 2, V4L2_MPEG_AUDIO_L2_BITRATE_64K = 3, V4L2_MPEG_AUDIO_L2_BITRATE_80K = 4, V4L2_MPEG_AUDIO_L2_BITRATE_96K = 5, V4L2_MPEG_AUDIO_L2_BITRATE_112K = 6, V4L2_MPEG_AUDIO_L2_BITRATE_128K = 7, V4L2_MPEG_AUDIO_L2_BITRATE_160K = 8, V4L2_MPEG_AUDIO_L2_BITRATE_192K = 9, V4L2_MPEG_AUDIO_L2_BITRATE_224K = 10, V4L2_MPEG_AUDIO_L2_BITRATE_256K = 11, V4L2_MPEG_AUDIO_L2_BITRATE_320K = 12, V4L2_MPEG_AUDIO_L2_BITRATE_384K = 13, }; #define V4L2_CID_MPEG_AUDIO_L3_BITRATE (V4L2_CID_MPEG_BASE+104) enum v4l2_mpeg_audio_l3_bitrate { V4L2_MPEG_AUDIO_L3_BITRATE_32K = 0, V4L2_MPEG_AUDIO_L3_BITRATE_40K = 1, V4L2_MPEG_AUDIO_L3_BITRATE_48K = 2, V4L2_MPEG_AUDIO_L3_BITRATE_56K = 3, V4L2_MPEG_AUDIO_L3_BITRATE_64K = 4, V4L2_MPEG_AUDIO_L3_BITRATE_80K = 5, V4L2_MPEG_AUDIO_L3_BITRATE_96K = 6, V4L2_MPEG_AUDIO_L3_BITRATE_112K = 7, V4L2_MPEG_AUDIO_L3_BITRATE_128K = 8, V4L2_MPEG_AUDIO_L3_BITRATE_160K = 9, V4L2_MPEG_AUDIO_L3_BITRATE_192K = 10, V4L2_MPEG_AUDIO_L3_BITRATE_224K = 11, V4L2_MPEG_AUDIO_L3_BITRATE_256K = 12, V4L2_MPEG_AUDIO_L3_BITRATE_320K = 13, }; #define V4L2_CID_MPEG_AUDIO_MODE (V4L2_CID_MPEG_BASE+105) enum v4l2_mpeg_audio_mode { V4L2_MPEG_AUDIO_MODE_STEREO = 0, V4L2_MPEG_AUDIO_MODE_JOINT_STEREO = 1, V4L2_MPEG_AUDIO_MODE_DUAL = 2, V4L2_MPEG_AUDIO_MODE_MONO = 3, }; #define V4L2_CID_MPEG_AUDIO_MODE_EXTENSION (V4L2_CID_MPEG_BASE+106) enum v4l2_mpeg_audio_mode_extension { V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_4 = 0, V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_8 = 1, V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_12 = 2, V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_16 = 3, }; #define V4L2_CID_MPEG_AUDIO_EMPHASIS (V4L2_CID_MPEG_BASE+107) enum v4l2_mpeg_audio_emphasis { V4L2_MPEG_AUDIO_EMPHASIS_NONE = 0, V4L2_MPEG_AUDIO_EMPHASIS_50_DIV_15_uS = 1, V4L2_MPEG_AUDIO_EMPHASIS_CCITT_J17 = 2, }; #define V4L2_CID_MPEG_AUDIO_CRC (V4L2_CID_MPEG_BASE+108) enum v4l2_mpeg_audio_crc { V4L2_MPEG_AUDIO_CRC_NONE = 0, V4L2_MPEG_AUDIO_CRC_CRC16 = 1, }; #define V4L2_CID_MPEG_AUDIO_MUTE (V4L2_CID_MPEG_BASE+109) /* MPEG video */ #define V4L2_CID_MPEG_VIDEO_ENCODING (V4L2_CID_MPEG_BASE+200) enum v4l2_mpeg_video_encoding { V4L2_MPEG_VIDEO_ENCODING_MPEG_1 = 0, V4L2_MPEG_VIDEO_ENCODING_MPEG_2 = 1, }; #define V4L2_CID_MPEG_VIDEO_ASPECT (V4L2_CID_MPEG_BASE+201) enum v4l2_mpeg_video_aspect { V4L2_MPEG_VIDEO_ASPECT_1x1 = 0, V4L2_MPEG_VIDEO_ASPECT_4x3 = 1, V4L2_MPEG_VIDEO_ASPECT_16x9 = 2, V4L2_MPEG_VIDEO_ASPECT_221x100 = 3, }; #define V4L2_CID_MPEG_VIDEO_B_FRAMES (V4L2_CID_MPEG_BASE+202) #define V4L2_CID_MPEG_VIDEO_GOP_SIZE (V4L2_CID_MPEG_BASE+203) #define V4L2_CID_MPEG_VIDEO_GOP_CLOSURE (V4L2_CID_MPEG_BASE+204) #define V4L2_CID_MPEG_VIDEO_PULLDOWN (V4L2_CID_MPEG_BASE+205) #define V4L2_CID_MPEG_VIDEO_BITRATE_MODE (V4L2_CID_MPEG_BASE+206) enum v4l2_mpeg_video_bitrate_mode { V4L2_MPEG_VIDEO_BITRATE_MODE_VBR = 0, V4L2_MPEG_VIDEO_BITRATE_MODE_CBR = 1, }; #define V4L2_CID_MPEG_VIDEO_BITRATE (V4L2_CID_MPEG_BASE+207) #define V4L2_CID_MPEG_VIDEO_BITRATE_PEAK (V4L2_CID_MPEG_BASE+208) #define V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION (V4L2_CID_MPEG_BASE+209) #define V4L2_CID_MPEG_VIDEO_MUTE (V4L2_CID_MPEG_BASE+210) #define V4L2_CID_MPEG_VIDEO_MUTE_YUV (V4L2_CID_MPEG_BASE+211) /* MPEG-class control IDs specific to the CX2584x driver as defined by V4L2 */ #define V4L2_CID_MPEG_CX2341X_BASE (V4L2_CTRL_CLASS_MPEG | 0x1000) #define V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE (V4L2_CID_MPEG_CX2341X_BASE+0) enum v4l2_mpeg_cx2341x_video_spatial_filter_mode { V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_MANUAL = 0, V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_AUTO = 1, }; #define V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER (V4L2_CID_MPEG_CX2341X_BASE+1) #define V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE (V4L2_CID_MPEG_CX2341X_BASE+2) enum v4l2_mpeg_cx2341x_video_luma_spatial_filter_type { V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_OFF = 0, V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_HOR = 1, V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_VERT = 2, V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_HV_SEPARABLE = 3, V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_SYM_NON_SEPARABLE = 4, }; #define V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE (V4L2_CID_MPEG_CX2341X_BASE+3) enum v4l2_mpeg_cx2341x_video_chroma_spatial_filter_type { V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_OFF = 0, V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_1D_HOR = 1, }; #define V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE (V4L2_CID_MPEG_CX2341X_BASE+4) enum v4l2_mpeg_cx2341x_video_temporal_filter_mode { V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_MANUAL = 0, V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_AUTO = 1, }; #define V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER (V4L2_CID_MPEG_CX2341X_BASE+5) #define V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE (V4L2_CID_MPEG_CX2341X_BASE+6) enum v4l2_mpeg_cx2341x_video_median_filter_type { V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_OFF = 0, V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR = 1, V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_VERT = 2, V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR_VERT = 3, V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_DIAG = 4, }; #define V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM (V4L2_CID_MPEG_CX2341X_BASE+7) #define V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP (V4L2_CID_MPEG_CX2341X_BASE+8) #define V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM (V4L2_CID_MPEG_CX2341X_BASE+9) #define V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP (V4L2_CID_MPEG_CX2341X_BASE+10) #define V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS (V4L2_CID_MPEG_CX2341X_BASE+11) /* * T U N I N G */ struct v4l2_tuner { __u32 index; __u8 name[32]; enum v4l2_tuner_type type; __u32 capability; __u32 rangelow; __u32 rangehigh; __u32 rxsubchans; __u32 audmode; __s32 signal; __s32 afc; __u32 reserved[4]; }; struct v4l2_modulator { __u32 index; __u8 name[32]; __u32 capability; __u32 rangelow; __u32 rangehigh; __u32 txsubchans; __u32 reserved[4]; }; /* Flags for the 'capability' field */ #define V4L2_TUNER_CAP_LOW 0x0001 #define V4L2_TUNER_CAP_NORM 0x0002 #define V4L2_TUNER_CAP_STEREO 0x0010 #define V4L2_TUNER_CAP_LANG2 0x0020 #define V4L2_TUNER_CAP_SAP 0x0020 #define V4L2_TUNER_CAP_LANG1 0x0040 /* Flags for the 'rxsubchans' field */ #define V4L2_TUNER_SUB_MONO 0x0001 #define V4L2_TUNER_SUB_STEREO 0x0002 #define V4L2_TUNER_SUB_LANG2 0x0004 #define V4L2_TUNER_SUB_SAP 0x0004 #define V4L2_TUNER_SUB_LANG1 0x0008 /* Values for the 'audmode' field */ #define V4L2_TUNER_MODE_MONO 0x0000 #define V4L2_TUNER_MODE_STEREO 0x0001 #define V4L2_TUNER_MODE_LANG2 0x0002 #define V4L2_TUNER_MODE_SAP 0x0002 #define V4L2_TUNER_MODE_LANG1 0x0003 #define V4L2_TUNER_MODE_LANG1_LANG2 0x0004 struct v4l2_frequency { __u32 tuner; enum v4l2_tuner_type type; __u32 frequency; __u32 reserved[8]; }; /* * A U D I O */ struct v4l2_audio { __u32 index; __u8 name[32]; __u32 capability; __u32 mode; __u32 reserved[2]; }; /* Flags for the 'capability' field */ #define V4L2_AUDCAP_STEREO 0x00001 #define V4L2_AUDCAP_AVL 0x00002 /* Flags for the 'mode' field */ #define V4L2_AUDMODE_AVL 0x00001 struct v4l2_audioout { __u32 index; __u8 name[32]; __u32 capability; __u32 mode; __u32 reserved[2]; }; /* * M P E G S E R V I C E S * * NOTE: EXPERIMENTAL API */ #if 1 /*KEEP*/ #define V4L2_ENC_IDX_FRAME_I (0) #define V4L2_ENC_IDX_FRAME_P (1) #define V4L2_ENC_IDX_FRAME_B (2) #define V4L2_ENC_IDX_FRAME_MASK (0xf) struct v4l2_enc_idx_entry { __u64 offset; __u64 pts; __u32 length; __u32 flags; __u32 reserved[2]; }; #define V4L2_ENC_IDX_ENTRIES (64) struct v4l2_enc_idx { __u32 entries; __u32 entries_cap; __u32 reserved[4]; struct v4l2_enc_idx_entry entry[V4L2_ENC_IDX_ENTRIES]; }; #define V4L2_ENC_CMD_START (0) #define V4L2_ENC_CMD_STOP (1) #define V4L2_ENC_CMD_PAUSE (2) #define V4L2_ENC_CMD_RESUME (3) /* Flags for V4L2_ENC_CMD_STOP */ #define V4L2_ENC_CMD_STOP_AT_GOP_END (1 << 0) struct v4l2_encoder_cmd { __u32 cmd; __u32 flags; union { struct { __u32 data[8]; } raw; }; }; #endif /* * D A T A S E R V I C E S ( V B I ) * * Data services API by Michael Schimek */ /* Raw VBI */ struct v4l2_vbi_format { __u32 sampling_rate; /* in 1 Hz */ __u32 offset; __u32 samples_per_line; __u32 sample_format; /* V4L2_PIX_FMT_* */ __s32 start[2]; __u32 count[2]; __u32 flags; /* V4L2_VBI_* */ __u32 reserved[2]; /* must be zero */ }; /* VBI flags */ #define V4L2_VBI_UNSYNC (1<< 0) #define V4L2_VBI_INTERLACED (1<< 1) /* Sliced VBI * * This implements is a proposal V4L2 API to allow SLICED VBI * required for some hardware encoders. It should change without * notice in the definitive implementation. */ struct v4l2_sliced_vbi_format { __u16 service_set; /* service_lines[0][...] specifies lines 0-23 (1-23 used) of the first field service_lines[1][...] specifies lines 0-23 (1-23 used) of the second field (equals frame lines 313-336 for 625 line video standards, 263-286 for 525 line standards) */ __u16 service_lines[2][24]; __u32 io_size; __u32 reserved[2]; /* must be zero */ }; /* Teletext World System Teletext (WST), defined on ITU-R BT.653-2 */ #define V4L2_SLICED_TELETEXT_B (0x0001) /* Video Program System, defined on ETS 300 231*/ #define V4L2_SLICED_VPS (0x0400) /* Closed Caption, defined on EIA-608 */ #define V4L2_SLICED_CAPTION_525 (0x1000) /* Wide Screen System, defined on ITU-R BT1119.1 */ #define V4L2_SLICED_WSS_625 (0x4000) #define V4L2_SLICED_VBI_525 (V4L2_SLICED_CAPTION_525) #define V4L2_SLICED_VBI_625 (V4L2_SLICED_TELETEXT_B | V4L2_SLICED_VPS | V4L2_SLICED_WSS_625) struct v4l2_sliced_vbi_cap { __u16 service_set; /* service_lines[0][...] specifies lines 0-23 (1-23 used) of the first field service_lines[1][...] specifies lines 0-23 (1-23 used) of the second field (equals frame lines 313-336 for 625 line video standards, 263-286 for 525 line standards) */ __u16 service_lines[2][24]; enum v4l2_buf_type type; __u32 reserved[3]; /* must be 0 */ }; struct v4l2_sliced_vbi_data { __u32 id; __u32 field; /* 0: first field, 1: second field */ __u32 line; /* 1-23 */ __u32 reserved; /* must be 0 */ __u8 data[48]; }; /* * A G G R E G A T E S T R U C T U R E S */ /* Stream data format */ struct v4l2_format { enum v4l2_buf_type type; union { struct v4l2_pix_format pix; // V4L2_BUF_TYPE_VIDEO_CAPTURE struct v4l2_window win; // V4L2_BUF_TYPE_VIDEO_OVERLAY struct v4l2_vbi_format vbi; // V4L2_BUF_TYPE_VBI_CAPTURE struct v4l2_sliced_vbi_format sliced; // V4L2_BUF_TYPE_SLICED_VBI_CAPTURE __u8 raw_data[200]; // user-defined } fmt; }; /* Stream type-dependent parameters */ struct v4l2_streamparm { enum v4l2_buf_type type; union { struct v4l2_captureparm capture; struct v4l2_outputparm output; __u8 raw_data[200]; /* user-defined */ } parm; }; /* * A D V A N C E D D E B U G G I N G * * NOTE: EXPERIMENTAL API */ /* VIDIOC_DBG_G_REGISTER and VIDIOC_DBG_S_REGISTER */ #define V4L2_CHIP_MATCH_HOST 0 /* Match against chip ID on host (0 for the host) */ #define V4L2_CHIP_MATCH_I2C_DRIVER 1 /* Match against I2C driver ID */ #define V4L2_CHIP_MATCH_I2C_ADDR 2 /* Match against I2C 7-bit address */ struct v4l2_register { __u32 match_type; /* Match type */ __u32 match_chip; /* Match this chip, meaning determined by match_type */ __u64 reg; __u64 val; }; /* VIDIOC_G_CHIP_IDENT */ struct v4l2_chip_ident { __u32 match_type; /* Match type */ __u32 match_chip; /* Match this chip, meaning determined by match_type */ __u32 ident; /* chip identifier as specified in */ __u32 revision; /* chip revision, chip specific */ }; /* * I O C T L C O D E S F O R V I D E O D E V I C E S * */ #define VIDIOC_QUERYCAP _IOR ('V', 0, struct v4l2_capability) #define VIDIOC_RESERVED _IO ('V', 1) #define VIDIOC_ENUM_FMT _IOWR ('V', 2, struct v4l2_fmtdesc) #define VIDIOC_G_FMT _IOWR ('V', 4, struct v4l2_format) #define VIDIOC_S_FMT _IOWR ('V', 5, struct v4l2_format) #define VIDIOC_REQBUFS _IOWR ('V', 8, struct v4l2_requestbuffers) #define VIDIOC_QUERYBUF _IOWR ('V', 9, struct v4l2_buffer) #define VIDIOC_G_FBUF _IOR ('V', 10, struct v4l2_framebuffer) #define VIDIOC_S_FBUF _IOW ('V', 11, struct v4l2_framebuffer) #define VIDIOC_OVERLAY _IOW ('V', 14, int) #define VIDIOC_QBUF _IOWR ('V', 15, struct v4l2_buffer) #define VIDIOC_DQBUF _IOWR ('V', 17, struct v4l2_buffer) #define VIDIOC_STREAMON _IOW ('V', 18, int) #define VIDIOC_STREAMOFF _IOW ('V', 19, int) #define VIDIOC_G_PARM _IOWR ('V', 21, struct v4l2_streamparm) #define VIDIOC_S_PARM _IOWR ('V', 22, struct v4l2_streamparm) #define VIDIOC_G_STD _IOR ('V', 23, v4l2_std_id) #define VIDIOC_S_STD _IOW ('V', 24, v4l2_std_id) #define VIDIOC_ENUMSTD _IOWR ('V', 25, struct v4l2_standard) #define VIDIOC_ENUMINPUT _IOWR ('V', 26, struct v4l2_input) #define VIDIOC_G_CTRL _IOWR ('V', 27, struct v4l2_control) #define VIDIOC_S_CTRL _IOWR ('V', 28, struct v4l2_control) #define VIDIOC_G_TUNER _IOWR ('V', 29, struct v4l2_tuner) #define VIDIOC_S_TUNER _IOW ('V', 30, struct v4l2_tuner) #define VIDIOC_G_AUDIO _IOR ('V', 33, struct v4l2_audio) #define VIDIOC_S_AUDIO _IOW ('V', 34, struct v4l2_audio) #define VIDIOC_QUERYCTRL _IOWR ('V', 36, struct v4l2_queryctrl) #define VIDIOC_QUERYMENU _IOWR ('V', 37, struct v4l2_querymenu) #define VIDIOC_G_INPUT _IOR ('V', 38, int) #define VIDIOC_S_INPUT _IOWR ('V', 39, int) #define VIDIOC_G_OUTPUT _IOR ('V', 46, int) #define VIDIOC_S_OUTPUT _IOWR ('V', 47, int) #define VIDIOC_ENUMOUTPUT _IOWR ('V', 48, struct v4l2_output) #define VIDIOC_G_AUDOUT _IOR ('V', 49, struct v4l2_audioout) #define VIDIOC_S_AUDOUT _IOW ('V', 50, struct v4l2_audioout) #define VIDIOC_G_MODULATOR _IOWR ('V', 54, struct v4l2_modulator) #define VIDIOC_S_MODULATOR _IOW ('V', 55, struct v4l2_modulator) #define VIDIOC_G_FREQUENCY _IOWR ('V', 56, struct v4l2_frequency) #define VIDIOC_S_FREQUENCY _IOW ('V', 57, struct v4l2_frequency) #define VIDIOC_CROPCAP _IOWR ('V', 58, struct v4l2_cropcap) #define VIDIOC_G_CROP _IOWR ('V', 59, struct v4l2_crop) #define VIDIOC_S_CROP _IOW ('V', 60, struct v4l2_crop) #define VIDIOC_G_JPEGCOMP _IOR ('V', 61, struct v4l2_jpegcompression) #define VIDIOC_S_JPEGCOMP _IOW ('V', 62, struct v4l2_jpegcompression) #define VIDIOC_QUERYSTD _IOR ('V', 63, v4l2_std_id) #define VIDIOC_TRY_FMT _IOWR ('V', 64, struct v4l2_format) #define VIDIOC_ENUMAUDIO _IOWR ('V', 65, struct v4l2_audio) #define VIDIOC_ENUMAUDOUT _IOWR ('V', 66, struct v4l2_audioout) #define VIDIOC_G_PRIORITY _IOR ('V', 67, enum v4l2_priority) #define VIDIOC_S_PRIORITY _IOW ('V', 68, enum v4l2_priority) #define VIDIOC_G_SLICED_VBI_CAP _IOWR ('V', 69, struct v4l2_sliced_vbi_cap) #define VIDIOC_LOG_STATUS _IO ('V', 70) #define VIDIOC_G_EXT_CTRLS _IOWR ('V', 71, struct v4l2_ext_controls) #define VIDIOC_S_EXT_CTRLS _IOWR ('V', 72, struct v4l2_ext_controls) #define VIDIOC_TRY_EXT_CTRLS _IOWR ('V', 73, struct v4l2_ext_controls) #if 1 /*KEEP*/ #define VIDIOC_ENUM_FRAMESIZES _IOWR ('V', 74, struct v4l2_frmsizeenum) #define VIDIOC_ENUM_FRAMEINTERVALS _IOWR ('V', 75, struct v4l2_frmivalenum) #define VIDIOC_G_ENC_INDEX _IOR ('V', 76, struct v4l2_enc_idx) #define VIDIOC_ENCODER_CMD _IOWR ('V', 77, struct v4l2_encoder_cmd) #define VIDIOC_TRY_ENCODER_CMD _IOWR ('V', 78, struct v4l2_encoder_cmd) /* Experimental, only implemented if CONFIG_VIDEO_ADV_DEBUG is defined */ #define VIDIOC_DBG_S_REGISTER _IOW ('V', 79, struct v4l2_register) #define VIDIOC_DBG_G_REGISTER _IOWR ('V', 80, struct v4l2_register) #define VIDIOC_G_CHIP_IDENT _IOWR ('V', 81, struct v4l2_chip_ident) #endif #define BASE_VIDIOC_PRIVATE 192 /* 192-255 are private */ #endif /* __LINUX_VIDEODEV2_H */ /* * Local variables: * c-basic-offset: 8 * End: */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/vps.c000066400000000000000000000166141476363111200143320ustar00rootroot00000000000000/* * libzvbi -- Video Programming System * * Copyright (C) 2000-2004 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: vps.c,v 1.9 2009-03-04 21:48:37 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "misc.h" #include "vps.h" /** * @addtogroup VPS Video Program System (VPS) Decoder * @ingroup LowDec * @brief Functions to decode and encode VPS packets (EN 300 231, EN 300 468). */ /** * @param cni CNI of type VBI_CNI_TYPE_VPS will be stored here. * @param buffer VPS packet as defined for @c VBI_SLICED_VPS, * i.e. 13 bytes without clock run-in and start code. * * Decodes a VPS packet according to EN 300 231, returning the * contained 12 bit Country and Network Identifier in @a *cni. * * The code 0xDC3 is translated according to TR 101 231: "As this * code is used for a time in two networks a distinction for automatic * tuning systems is given in data line 16 [VPS]: bit 3 of byte 5 = 1 * for the ARD network / = 0 for the ZDF network." * * @returns * Always @c TRUE, no error checking possible. It may be prudent to * wait for a second transmission of the received CNI to ensure * correct reception. * * @since 0.2.20 */ vbi_bool vbi_decode_vps_cni (unsigned int * cni, const uint8_t buffer[13]) { unsigned int cni_value; assert (NULL != cni); assert (NULL != buffer); cni_value = (+ ((buffer[10] & 0x03) << 10) + ((buffer[11] & 0xC0) << 2) + (buffer[ 8] & 0xC0) + (buffer[11] & 0x3F)); if (unlikely (0x0DC3 == cni_value)) cni_value = (buffer[2] & 0x10) ? 0x0DC1 /* ARD */ : 0x0DC2 /* ZDF */; *cni = cni_value; return TRUE; } /** * @param pid PDC program ID will be stored here. * @param buffer VPS packet as defined for @c VBI_SLICED_VPS, * i.e. 13 bytes without clock run-in and start code. * * Decodes a VPS datagram according to EN 300 231, * storing PDC recording-control data in @a pid. * * @returns * @c FALSE if the buffer contains incorrect data. In this case * @a pid remains unmodified. * * @since 0.2.34 */ vbi_bool vbi_decode_vps_pdc (vbi_program_id * pid, const uint8_t buffer[13]) { vbi_pil pil; assert (NULL != pid); assert (NULL != buffer); pil = (+ ((buffer[ 8] & 0x3F) << 14) + (buffer[ 9] << 6) + (buffer[10] >> 2)); /* We cannot check the pil because unreal dates are valid too. */ CLEAR (*pid); pid->channel = VBI_PID_CHANNEL_VPS; pid->cni_type = VBI_CNI_TYPE_VPS; vbi_decode_vps_cni (&pid->cni, buffer); pid->pil = pil; pid->mi = TRUE; pid->pcs_audio = buffer[2] >> 6; pid->pty = buffer[12]; return TRUE; } /** * @param pid PDC program ID will be stored here. * @param buffer A DVB PDC descriptor as defined in EN 300 468, * including the descriptor_tag and descriptor_length bytes. * * Decodes a DVB PDC descriptor as defined in EN 300 468 and EN 300 231, * storing PDC recording-control data in @a pid. * * @returns * @c FALSE if the buffer contains an incorrect descriptor_tag, * descriptor_length or PIL. In this case @a pid remains unmodified. * * @since 0.2.34 */ vbi_bool vbi_decode_dvb_pdc_descriptor (vbi_program_id * pid, const uint8_t buffer[5]) { vbi_pil pil; assert (NULL != pid); assert (NULL != buffer); /* descriptor_tag [8], descriptor_length [8], reserved_future_use [4], programme_identification_label [20] -> day [5], month [4], hour [5], minute [6] */ /* EN 300 468 Section 6.1, 6.2. */ if (unlikely (0x69 != buffer[0] || 3 != buffer[1])) return FALSE; /* EN 300 468 Section 6.2.29. */ pil = (+ ((buffer[2] & 0x0F) << 16) + (buffer[3] << 8) + buffer[4]); CLEAR (*pid); pid->channel = VBI_PID_CHANNEL_PDC_DESCRIPTOR; pid->pil = pil; pid->mi = TRUE; return TRUE; } /** * @param buffer VPS packet as defined for @c VBI_SLICED_VPS, * i.e. 13 bytes without clock run-in and start code. * @param cni CNI of type VBI_CNI_TYPE_VPS. * * Stores the 12 bit Country and Network Identifier @a cni in * a VPS packet according to EN 300 231. * * @returns * @c FALSE if @a cni is invalid. In this case @a buffer remains * unmodified. * * @since 0.2.20 */ vbi_bool vbi_encode_vps_cni (uint8_t buffer[13], unsigned int cni) { assert (NULL != buffer); if (unlikely (cni > 0x0FFF)) return FALSE; buffer[8] = (buffer[8] & 0x3F) | (cni & 0xC0); buffer[10] = (buffer[10] & 0xFC) | (cni >> 10); buffer[11] = (cni & 0x3F) | ((cni >> 2) & 0xC0); return TRUE; } /** * @param buffer VPS packet as defined for @c VBI_SLICED_VPS, * i.e. 13 bytes without clock run-in and start code. * @param pid PDC data to encode. * * Stores PDC recording-control data (CNI, PIL, PCS audio, PTY) in * a VPS datagram according to EN 300 231. * * @returns * @c FALSE if any of the parameters to encode are invalid. In this * case @a buffer remains unmodified. * * @since 0.2.34 */ vbi_bool vbi_encode_vps_pdc (uint8_t buffer[13], const vbi_program_id * pid) { unsigned int pil; assert (NULL != buffer); assert (NULL != pid); if (unlikely ((unsigned int) pid->pty > 0xFF)) return FALSE; if (unlikely ((unsigned int) pid->pcs_audio > 3)) return FALSE; pil = pid->pil; if (pil > 0xFFFFF) return FALSE; if (!vbi_encode_vps_cni (buffer, pid->cni)) return FALSE; buffer[2] = (buffer[2] & 0x3F) | (pid->pcs_audio << 6); buffer[8] = (buffer[8] & 0xC0) | ((pil >> 14) & 0x3F); buffer[9] = pil >> 6; buffer[10] = (buffer[10] & 0x03) | (pil << 2); buffer[12] = pid->pty; return TRUE; } /** * @param buffer A DVB PDC descriptor as defined in EN 300 468, * including the descriptor_tag and descriptor_length bytes. * @param pid PDC data to encode. * * Stores PDC recording-control data (PIL only) in a DVB PDC descriptor * as defined in EN 300 468 and EN 300 231. * * @returns * @c FALSE if any of the parameters to encode are invalid. In this * case @a buffer remains unmodified. * * @since 0.2.34 */ vbi_bool vbi_encode_dvb_pdc_descriptor (uint8_t buffer[5], const vbi_program_id * pid) { unsigned int pil; assert (NULL != buffer); assert (NULL != pid); pil = pid->pil; if (pil > 0xFFFFF) return FALSE; /* descriptor_tag [8], descriptor_length [8], reserved_future_use [4], programme_identification_label [20] -> day [5], month [4], hour [5], minute [6] */ /* EN 300 468 Section 6.1, 6.2. */ buffer[0] = 0x69; buffer[1] = 3; /* EN 300 468 Section 6.2.29. */ /* EN 300 468 Section 3.1: "Unless otherwise specified within the present document all 'reserved_future_use' bits shall be set to '1'." */ buffer[2] = 0xF0 | (pil >> 16); buffer[3] = pil >> 8; buffer[4] = pil; return TRUE; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/vps.h000066400000000000000000000041641476363111200143340ustar00rootroot00000000000000/* * libzvbi -- Video Programming System * * Copyright (C) 2000-2004 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: vps.h,v 1.9 2009-03-04 21:48:35 mschimek Exp $ */ #ifndef __ZVBI_VPS_H__ #define __ZVBI_VPS_H__ #include /* uint8_t */ #include "macros.h" #include "pdc.h" /* vbi_program_id */ VBI_BEGIN_DECLS /* Public */ /** * @addtogroup VPS * @{ */ extern vbi_bool vbi_decode_vps_cni (unsigned int * cni, const uint8_t buffer[13]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_encode_vps_cni (uint8_t buffer[13], unsigned int cni) _vbi_nonnull ((1)); extern vbi_bool vbi_decode_vps_pdc (vbi_program_id * pid, const uint8_t buffer[13]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; extern vbi_bool vbi_encode_vps_pdc (uint8_t buffer[13], const vbi_program_id * pid) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; vbi_bool vbi_decode_dvb_pdc_descriptor (vbi_program_id * pid, const uint8_t buffer[5]) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; vbi_bool vbi_encode_dvb_pdc_descriptor (uint8_t buffer[5], const vbi_program_id * pid) #ifndef DOXYGEN_SHOULD_SKIP_THIS _vbi_nonnull ((1, 2)) #endif ; /** @} */ /* Private */ VBI_END_DECLS #endif /* __ZVBI_VPS_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/vt.h000066400000000000000000000345521476363111200141610ustar00rootroot00000000000000/* * libzvbi -- Teletext decoder internals * * Copyright (C) 2000, 2001, 2003, 2004, 2008 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: vt.h,v 1.13 2008-03-01 07:37:35 mschimek Exp $ */ #ifndef VT_H #define VT_H #include "version.h" #include "bcd.h" /* vbi_pgno, vbi_subno */ #if 2 == VBI_VERSION_MINOR # include "lang.h" # define VBI_TTX_CHARSET_CODE_NONE ((vbi_ttx_charset_code) -1) typedef unsigned int vbi_ttx_charset_code; typedef struct vbi_font_descr vbi_ttx_charset; _vbi_inline const vbi_ttx_charset * vbi_ttx_charset_from_code (vbi_ttx_charset_code code) { if (VALID_CHARACTER_SET (code)) return vbi_font_descriptors + code; else return NULL; } #elif 3 == VBI_VERSION_MINOR # include "lang.h" /* vbi_ttx_charset_code */ # include "page.h" /* vbi_color */ #else # error VBI_VERSION_MINOR == ? #endif /** * @internal * EN 300 706 Section 9.4.2, Table 3: Page function. * (Packet X/28/0 Format 1, X/28/3 and X/28/4.) */ enum ttx_page_function { /** * EN 300 706 annex L, EACEM/ECCA Automatic Channel * Installation data (libzvbi internal code). */ PAGE_FUNCTION_ACI = -5, /** * Data broadcasting page coded according to EN 300 708 * clause 4 (Page Format - Clear) containing Electronic * Programme Guide data according to EN 300 707 (NexTView). * (Libzvbi internal code.) */ PAGE_FUNCTION_EPG = -4, /** * Page contains trigger messages defined according to EACEM * TP 14-99-16 "Data Broadcasting", rev 0.8 (libzvbi internal * code). */ PAGE_FUNCTION_EACEM_TRIGGER = -3, /** Invalid data (libzvbi internal code). */ PAGE_FUNCTION_DISCARD = -2, /** Unknown page function (libzvbi internal code). */ PAGE_FUNCTION_UNKNOWN = -1, /** Basic level one page. */ PAGE_FUNCTION_LOP = 0, /** * Data broadcasting page coded according * to EN 300 708 Section 4 (Page Format - Clear). */ PAGE_FUNCTION_DATA, /** Global object definition page. */ PAGE_FUNCTION_GPOP, /** Normal object definition page. */ PAGE_FUNCTION_POP, /** Global DRCS downloading page. */ PAGE_FUNCTION_GDRCS, /** Normal DRCS downloading page. */ PAGE_FUNCTION_DRCS, /** Magazine Organization Table. */ PAGE_FUNCTION_MOT, /** Magazine Inventory Page. */ PAGE_FUNCTION_MIP, /** Basic TOP Table. */ PAGE_FUNCTION_BTT, /** TOP Additional Information Table. */ PAGE_FUNCTION_AIT, /** TOP Multi-Page Table. */ PAGE_FUNCTION_MPT, /** TOP Multi-Page Extension Table. */ PAGE_FUNCTION_MPT_EX, /** * Page contains trigger messages defined according to IEC/PAS * 62297 Edition 1.0 (2002-01): "Proposal for introducing a * trigger mechanism into TV transmissions". * * Might be the same as PAGE_FUNCTION_EACEM_TRIGGER, but author * got no copy of IEC/PAS 62297 to verify. */ PAGE_FUNCTION_IEC_TRIGGER }; _vbi_inline vbi_bool ttx_page_function_valid (enum ttx_page_function function) { return ((unsigned int) function <= (unsigned int) PAGE_FUNCTION_IEC_TRIGGER); } extern const char * ttx_page_function_name (enum ttx_page_function function); /** * @internal * EN 300 706 Section 9.4.2, Table 3: Page coding bits. * (Packet X/28/0 Format 1, X/28/3 and X/28/4.) */ enum ttx_page_coding { /** Unknown coding (libzvbi internal code). */ PAGE_CODING_UNKNOWN = -1, /** * 8 bit bytes with 7 data bits and one odd parity bit * in the most significant position. */ PAGE_CODING_ODD_PARITY, /** 8 bit bytes with 8 data bits. */ PAGE_CODING_UBYTES, /** Hamming 24/18 coded triplets (struct ttx_triplet). */ PAGE_CODING_TRIPLETS, /** Hamming 8/4 coded 8 bit bytes. */ PAGE_CODING_HAMMING84, /** Eight HAMMING84 bytes followed by twelve ODD_PARITY bytes. */ PAGE_CODING_AIT, /** * First byte is a Hamming 8/4 coded 4 bit ttx_page_coding * value describing the remaining 39 bytes. */ PAGE_CODING_META84 }; _vbi_inline vbi_bool ttx_page_coding_valid (enum ttx_page_coding coding) { return ((unsigned int) coding <= (unsigned int) PAGE_CODING_META84); } extern const char * ttx_page_coding_name (enum ttx_page_coding coding); /** * @internal * Page function coded in TOP BTT links to other TOP pages. * top_page_number() translates these codes to enum ttx_page_function. */ enum ttx_top_page_function { /** Multi-Page Table. */ TOP_PAGE_FUNCTION_MPT = 1, /** Additional Information Table. */ TOP_PAGE_FUNCTION_AIT, /** Multi-Page Extension Table. */ TOP_PAGE_FUNCTION_MPT_EX, }; /** * @internal * Page type coded in TOP BTT pages. decode_btt_page() translates * these codes to MIP page type (enum vbi_page_type). */ enum ttx_btt_page_type { BTT_NO_PAGE = 0, /** Subtitle page. */ BTT_SUBTITLE, /** Index page, single page. */ BTT_PROGR_INDEX_S, /** * Index page, multi-page * (number of subpages coded in MPT or MPT-EX). */ BTT_PROGR_INDEX_M, /** First page of a block, single page. */ BTT_BLOCK_S, /** First page of a block, multi-page. */ BTT_BLOCK_M, /** First page of a group, single page. */ BTT_GROUP_S, /** First page of a group, multi-page. */ BTT_GROUP_M, /** Normal page, single page. */ BTT_NORMAL_S, /** Unknown purpose. */ BTT_NORMAL_9, /** Normal page, multi-page. */ BTT_NORMAL_M, /** Unknown purpose. */ BTT_NORMAL_11, /** Unknown purpose. */ BTT_12, /** Unknown purpose. */ BTT_13, /** Unknown purpose. */ BTT_14, /** Unknown purpose. */ BTT_15 }; /** * @internal * EN 300 706 Section 12.3.1, Table 28: Enhancement object type. */ enum ttx_object_type { /** Depending on context. */ LOCAL_ENHANCEMENT_DATA = 0, OBJECT_TYPE_NONE = 0, OBJECT_TYPE_ACTIVE, OBJECT_TYPE_ADAPTIVE, OBJECT_TYPE_PASSIVE }; extern const char * ttx_object_type_name (enum ttx_object_type type); /** * @internal * EN 300 706 Section 14.2, Table 31: DRCS modes. * EN 300 706 Section 9.4.6, Table 9: Coding of Packet * X/28/3 for DRCS Downloading Pages. */ enum ttx_drcs_mode { DRCS_MODE_12_10_1 = 0, DRCS_MODE_12_10_2, DRCS_MODE_12_10_4, DRCS_MODE_6_5_4, DRCS_MODE_SUBSEQUENT_PTU = 14, DRCS_MODE_NO_DATA }; extern const char * ttx_drcs_mode_name (enum ttx_drcs_mode mode); /** @internal */ #define DRCS_PTUS_PER_PAGE 48 /** @internal */ #define NO_PAGE(pgno) (((pgno) & 0xFF) == 0xFF) /** * @internal * Teletext page link. */ struct ttx_page_link { /** Function of the target. */ enum ttx_page_function function; /** * Page number of the target. NO_PAGE (pgno) == TRUE when * this link is unused or broken. */ vbi_pgno pgno; /** * Subpage number of the target or VBI_NO_SUBNO. * * For X/27/4 ... 5 format 1 links (struct ttx_lop.link[]) * this is the set of required subpages (1 << (0 ... 15)) * instead. */ vbi_subno subno; }; extern void ttx_page_link_dump (const struct ttx_page_link *pl, FILE * fp); /* Level one page enhancement. */ /** * @internal * EN 300 706 Section 12.3.1: Packet X/26 code triplet. * Broken triplets have all fields set to -1. */ struct ttx_triplet { uint8_t address; uint8_t mode; uint8_t data; }; /** * @internal * Level one page enhancements triplets (packets X/26). */ typedef struct ttx_triplet ttx_enhancement[16 * 13 + 1]; /* Level one page extension. */ /** * @internal * EN 300 706 Section 9.4.2.2: X/28/0, X/28/4 and * EN 300 706 Section 10.6.4: MOT POP link fallback flags. */ struct ttx_ext_fallback { vbi_bool black_bg_substitution; int left_panel_columns; int right_panel_columns; }; /** * @internal * Index of the "transparent" color in the Level 2.5/3.5 color_map[]. */ #define VBI_TRANSPARENT_BLACK 8 /** * @internal * EN 300 706 Section 9.4.2: Packet X/28. * EN 300 706 Section 9.5: Packet M/29. */ struct ttx_extension { /** * We have data from packets X/28 (in LOP) or M/29 (in magazine) * with this set of designations. LOP pages without X/28 packets * should fall back to the magazine defaults unless these bits * are set. The extension data in struct ttx_magazine is always * valid, contains global defaults as specified in EN 300 706 * (see _vbi_teletext_decoder_default_magazine()) unless M/29 * packets have been received. * * - 1 << 4: color_map[0 ... 15] is valid * - 1 << 1: drcs_clut[] is valid * - 1 << 0 or 1 << 4: the remaining fields are valid. * * color_map[32 .. 39] is always valid. */ unsigned int designations; /** Primary and secondary character set. */ vbi_ttx_charset_code charset_code[2]; /** Default colors. */ unsigned int def_screen_color; unsigned int def_row_color; /** * Adding these values (0, 8, 16, 24) to character color * 0 ... 7 gives an index into color_map[] below. */ unsigned int foreground_clut; unsigned int background_clut; struct ttx_ext_fallback fallback; /** * DRCS color lookup table. Translates (G)DRCS pixel values to * indices into the color_map[]. * * - 2 dummy entries (12x10x1 (G)DRCS pixels are interpreted * like built-in character pixels, selecting the current * foreground or background color). * - 4 entries for 12x10x2 GDRCS pixel 0 ... 3 to color_map[]. * - 4 more for local DRCS * - 16 entries for 12x10x4 and 6x5x4 GDRCS pixel 0 ... 15 * to color_map[]. * - 16 more for local DRCS. */ #if 2 == VBI_VERSION_MINOR /* For compatibility with the drcs_clut pointer in vbi_page. */ uint8_t drcs_clut[2 + 2 * 4 + 2 * 16]; #elif 3 == VBI_VERSION_MINOR vbi_color drcs_clut[2 + 2 * 4 + 2 * 16]; #else # error VBI_VERSION_MINOR == ? #endif /** * Five palettes of 8 each colors each. * * Level 1.0 and 1.5 pages use only palette #0, Level 2.5 and * 3.5 pages use palette #0 ... #3. * * At Level 2.5 palette #2 and #3 are redefinable, except * color_map[8] which is "transparent" color * (VBI_TRANSPARENT_BLACK). At Level 3.5 palette #0 and #1 * are also redefinable. * * Palette #4 never changes and contains libzvbi internal * colors for navigation bars, search text highlighting etc. * These colors are roughly the same as the default palette #0, * see vbi_color. */ vbi_rgba color_map[40]; }; extern void ttx_extension_dump (const struct ttx_extension *ext, FILE * fp); /** * @internal * * EN 300 706 Section 12.3.1, Table 28: Mode 10001, 10101 - Object * invocation, object definition. See also triplet_object_address(). * * MOT default, POP and GPOP object address. * * n8 n7 n6 n5 n4 n3 n2 n1 n0 * packet triplet lsb ----- s1 ----- */ typedef int ttx_object_address; /** * @internal * Decoded TOP Additional Information Table entry. */ struct ttx_ait_title { struct ttx_page_link link; uint8_t text[12]; }; /* Basic level one page. */ /** * @internal * EN 300 706 Section 9.3.1.3: Control bits (~0xE03F7F), * EN 300 706 Section 15.2: National subset C12-C14, * EN 300 706 Appendix B.6: Transmission rules for enhancement data. */ enum ttx_flags { C4_ERASE_PAGE = 0x000080, C5_NEWSFLASH = 0x004000, C6_SUBTITLE = 0x008000, C7_SUPPRESS_HEADER = 0x010000, C8_UPDATE = 0x020000, C9_INTERRUPTED = 0x040000, C10_INHIBIT_DISPLAY = 0x080000, C11_MAGAZINE_SERIAL = 0x100000, C12_FRAGMENT = 0x200000, C13_PARTIAL_PAGE = 0x400000, C14_RESERVED = 0x800000 }; /** * @internal * Basic level one page. */ struct ttx_lop { /** Raw data as received. */ uint8_t raw[26][40]; /** Packet X/27/0-5 links. */ struct ttx_page_link link[6 * 6]; /** * Packet X/27 flag (ETR 287 section 10.4): * Have FLOF navigation, display row 24. */ vbi_bool have_flof; }; /* Magazine defaults. */ /** * @internal * EN 300 706 Section 10.6.4: MOT object links. */ struct ttx_pop_link { vbi_pgno pgno; struct ttx_ext_fallback fallback; struct { enum ttx_object_type type; ttx_object_address address; } default_obj[2]; }; /** * @internal * Magazine defaults. */ struct ttx_magazine { /** Default extension. */ struct ttx_extension extension; /** * Converts page number to index into pop_link[] for default * object invocation. Valid range 0 ... 7, -1 if broken. */ int8_t pop_lut[0x100]; /** * Converts page number to index into drcs_link[] for default * object invocation. Valid range 0 ... 7, -1 if broken. */ int8_t drcs_lut[0x100]; /** * Level 2.5 [0] or 3.5 [1], one global [0] and seven local links * to POP page. NO_PAGE(pop_link[][].pgno) == TRUE if the link * is unused or broken. */ struct ttx_pop_link pop_link[2][8]; /** * Level 2.5 [0] or 3.5 [1], one global [0] and seven local links * to DRCS page. NO_PAGE(drcs_link[][]) == TRUE if the link * is unused or broken. */ vbi_pgno drcs_link[2][8]; }; extern const struct ttx_magazine * _vbi_teletext_decoder_default_magazine (void); /* Network data. */ /** @internal */ #define SUBCODE_SINGLE_PAGE 0x0000 /** @internal */ #define SUBCODE_MULTI_PAGE 0xFFFE /** @internal */ #define SUBCODE_UNKNOWN 0xFFFF /** * @internal * Internal teletext page statistics. */ struct ttx_page_stat { /* Information gathered from MOT, MIP, BTT, G/POP pages. */ /** Actually vbi_page_type. */ uint8_t page_type; /** Actually vbi_ttx_charset_code, 0xFF if unknown. */ uint8_t charset_code; /** * Highest subpage number transmitted according to MOT, MIP, BTT. * - 0x0000 single page (SUBCODE_SINGLE_PAGE) * - 0x0002 - 0x0079 multi-page * - 0x0080 - 0x3F7F clock page, other pages with non-standard * subpages not to be cached * These codes were not transmitted but generated by libzvbi: * - 0xFFFE has 2+ subpages (SUBCODE_MULTI_PAGE) * - 0xFFFF unknown (SUBCODE_UNKNOWN) */ uint16_t subcode; /** Last received page ttx_flags (cache_page.flags). */ uint32_t flags; /* Cache statistics. */ /** Subpages cached now and ever. */ uint8_t n_subpages; uint8_t max_subpages; /** Subpage numbers actually received (0x00 ... 0x79). */ uint8_t subno_min; uint8_t subno_max; }; #endif /* VT_H */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/wss.c000066400000000000000000000204501476363111200143270ustar00rootroot00000000000000/* * libzvbi -- WSS decoder * * Copyright (C) 2001, 2002 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: wss.c,v 1.6 2008-02-19 00:35:23 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "misc.h" #include "vbi.h" #include "hamm.h" #include "wss.h" void vbi_decode_wss_625(vbi_decoder *vbi, uint8_t *buf, double time) { vbi_event e; vbi_aspect_ratio *r = &e.ev.aspect; int parity; memset(&e, 0, sizeof(e)); /* Two producers... */ if (time < vbi->wss_time) return; vbi->wss_time = time; if (buf[0] != vbi->wss_last[0] || buf[1] != vbi->wss_last[1]) { vbi->wss_last[0] = buf[0]; vbi->wss_last[1] = buf[1]; vbi->wss_rep_ct = 0; return; } if (++vbi->wss_rep_ct < 3) return; parity = buf[0] & 15; parity ^= parity >> 2; parity ^= parity >> 1; if (!(parity & 1)) return; r->ratio = 1.0; switch (buf[0] & 7) { case 0: /* 4:3 */ case 6: /* 14:9 soft matte */ r->first_line = 23; r->last_line = 310; break; case 1: /* 14:9 */ r->first_line = 41; r->last_line = 292; break; case 2: /* 14:9 top */ r->first_line = 23; r->last_line = 274; break; case 3: /* 16:9 */ case 5: /* "Letterbox > 16:9" */ r->first_line = 59; // 59.5 ? r->last_line = 273; break; case 4: /* 16:9 top */ r->first_line = 23; r->last_line = 237; break; case 7: /* 16:9 anamorphic */ r->first_line = 23; r->last_line = 310; r->ratio = 3.0 / 4.0; break; } r->film_mode = !!(buf[0] & 0x10); switch ((buf[1] >> 1) & 3) { case 0: r->open_subtitles = VBI_SUBT_NONE; break; case 1: r->open_subtitles = VBI_SUBT_ACTIVE; break; case 2: r->open_subtitles = VBI_SUBT_MATTE; break; case 3: r->open_subtitles = VBI_SUBT_UNKNOWN; break; } if (memcmp(r, &vbi->prog_info[0].aspect, sizeof(*r)) != 0) { vbi->prog_info[0].aspect = *r; vbi->aspect_source = 1; e.type = VBI_EVENT_ASPECT; vbi_send_event(vbi, &e); e.type = VBI_EVENT_PROG_INFO; e.ev.prog_info = &vbi->prog_info[0]; vbi_send_event(vbi, &e); } if (0) { static const char *formats[] = { "Full format 4:3, 576 lines", "Letterbox 14:9 centre, 504 lines", "Letterbox 14:9 top, 504 lines", "Letterbox 16:9 centre, 430 lines", "Letterbox 16:9 top, 430 lines", "Letterbox > 16:9 centre", "Full format 14:9 centre, 576 lines", "Anamorphic 16:9, 576 lines" }; static const char *subtitles[] = { "none", "in active image area", "out of active image area", "?" }; printf("WSS: %s; %s mode; %s color coding;\n" " %s helper; reserved b7=%d; %s\n" " open subtitles: %s; %scopyright %s; copying %s\n", formats[buf[0] & 7], (buf[0] & 0x10) ? "film" : "camera", (buf[0] & 0x20) ? "MA/CP" : "standard", (buf[0] & 0x40) ? "modulated" : "no", !!(buf[0] & 0x80), (buf[1] & 0x01) ? "have TTX subtitles; " : "", subtitles[(buf[1] >> 1) & 3], (buf[1] & 0x08) ? "surround sound; " : "", (buf[1] & 0x10) ? "asserted" : "unknown", (buf[1] & 0x20) ? "restricted" : "not restricted"); } } void vbi_decode_wss_cpr1204(vbi_decoder *vbi, uint8_t *buf) { int b0 = buf[0] & 0x80; int b1 = buf[0] & 0x40; vbi_event e; vbi_aspect_ratio *r = &e.ev.aspect; memset(&e, 0, sizeof(e)); if (b1) { r->first_line = 72; // wild guess r->last_line = 212; } else { r->first_line = 22; r->last_line = 262; } r->ratio = b0 ? 3.0 / 4.0 : 1.0; r->film_mode = 0; r->open_subtitles = VBI_SUBT_UNKNOWN; if (memcmp(r, &vbi->prog_info[0].aspect, sizeof(*r)) != 0) { vbi->prog_info[0].aspect = *r; vbi->aspect_source = 2; e.type = VBI_EVENT_ASPECT; vbi_send_event(vbi, &e); e.type = VBI_EVENT_PROG_INFO; e.ev.prog_info = &vbi->prog_info[0]; vbi_send_event(vbi, &e); } if (0) printf("CPR: %d %d\n", !!b0, !!b1); } #if 0 /* old stuff */ /* 0001 0100 000 000 == Full 4:3, PALPlus, other flags zero */ static const uint8_t genuine_pal_wss_green[720] = { 54, 76, 92, 82, 81, 73, 63, 91,200,254,253,183,162,233,246,195, 190,224,247,237,203,150, 80, 23, 53,103, 93, 38, 61,172,244,243, 240,202,214,251,233,125, 44, 34, 59, 95, 86, 40, 54,149,235,255, 240,208,206,247,249,164, 67, 28, 38, 82,100, 49, 26,121,227,249, 240,206,203,242,248,182, 87, 26, 33, 82, 94, 51, 37,102,197,250, 251,222,205,226,247,210,118, 38, 41, 78, 91, 67, 55,103,190,254, 252,240,220,206,211,229,245,254,207,108, 28, 42, 93,100, 60, 21, 120,200,245,241,223,221,220,216,222,233,234,188, 97, 27, 30, 70, 70, 68, 67, 70, 71, 67, 58, 49, 77,159,240,248,226,203,211,227, 233,221,208,201,221,245,196, 99, 22, 39, 73, 88, 54, 34, 92,181, 243,246,217,186,212,245,192, 95, 23, 40, 72, 81, 46, 25, 84,170, 250,246,213,194,225,251,211,136, 22, 39, 73, 91, 62, 38, 84,157, 236,247,241,208,201,226,235,219,222,233,222,208,230,250,220,158, 54, 36, 42, 74, 79, 58, 56, 76, 72, 49, 53, 80, 65, 20, 35,107, 246,249,239,225,217,221,226,227,218,225,220,215,231,250,236,200, 68, 39, 31, 51, 71, 66, 53, 46, 63, 60, 58, 67, 70, 54, 50, 66, 194,245,246,209,193,228,240,219,116, 35, 8, 64, 93, 58, 39, 61, 170,232,249,234,210,224,237,231,127, 64, 27, 52, 84, 77, 56, 46, 136,224,245,233,196,198,240,240,162, 74, 20, 48, 84, 72, 49, 46, 126,200,246,242,205,202,227,245,172, 87, 19, 29, 72, 83, 59, 36, 89,186,249,244,196,192,229,250,202, 94, 24, 37, 76, 95, 82, 49, 71,171,251,251,203,189,221,253,220,123, 36, 31, 75, 96, 76, 49, 79,160,247,252,232,199,207,234,233,138, 44, 28, 66, 91, 76, 53, 59,148,229,252,240,202,202,241,252,165, 64, 20, 39, 72, 78, 65, 57, 62, 63, 62, 64, 67, 62, 54, 58, 67, 74, 72, 65, 63, 62, 64, 57, 68, 67, 55, 54, 66, 68, 57, 57, 56, 60, 63, 55, 50, 64 }; static void wss_test_init(v4l_device *v4l) { static const int std_widths[] = { 768, 704, 640, 384, 352, 320, 192, 176, 160, -160 }; uint8_t *p = wss_test_data; int i, j, g, width, spl, bpp = 0; enum tveng_frame_pixformat fmt; int sampling_rate; fprintf(stderr, "WSS test enabled\n"); fmt = TVENG_PIX_RGB565; /* see below */ width = 352; /* pixels per line; assume a line length of 52 usec, i.e. no clipping */ /* use this for capture widths reported by the driver, not scaled images */ for (i = 0; width < ((std_widths[i] + std_widths[i + 1]) >> 1); i++); spl = std_widths[i]; /* samples in 52 usec */ sampling_rate = spl / 52.148e-6; memset(wss_test_data, 0, sizeof wss_test_data); for (i = 0; i < width; i++) { double off = i * 704 / (double) spl; j = off; off -= j; g = (genuine_pal_wss_green[j + 1] * off + genuine_pal_wss_green[j + 0] * (1 - off)); switch (fmt) { case TVENG_PIX_RGB24: case TVENG_PIX_BGR24: *p++ = rand(); *p++ = g; *p++ = rand(); bpp = 3; break; case TVENG_PIX_RGB32: /* RGBA / BGRA */ case TVENG_PIX_BGR32: *p++ = rand(); *p++ = g; *p++ = rand(); *p++ = rand(); bpp = 4; break; case TVENG_PIX_RGB565: g <<= 3; g ^= rand() & 0xF81F; *p++ = g; *p++ = g >> 8; bpp = 2; break; case TVENG_PIX_RGB555: g <<= 2; g ^= rand() & 0xFC1F; *p++ = g; *p++ = g >> 8; bpp = 2; break; case TVENG_PIX_YVU420: case TVENG_PIX_YUV420: *p++ = g; bpp = 1; break; case TVENG_PIX_YUYV: *p++ = g; *p++ = rand(); bpp = 2; break; case TVENG_PIX_UYVY: *p++ = rand(); *p++ = g; bpp = 2; break; } } v4l->wss_slicer_fn = init_bit_slicer(&v4l->wss_slicer, width, sampling_rate, /* cri_rate */ 5000000, /* bit_rate */ 833333, /* cri_frc */ 0xC71E3C1F, /* cri_mask */ 0x924C99CE, /* cri_bits */ 32, /* frc_bits */ 0, /* payload */ 14 * 1, MOD_BIPHASE_LSB_ENDIAN, fmt); } #endif /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/wss.h000066400000000000000000000021371476363111200143360ustar00rootroot00000000000000/* * libzvbi -- WSS decoder * * Copyright (C) 2001, 2002 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: wss.h,v 1.4 2008-02-19 00:35:23 mschimek Exp $ */ extern void vbi_decode_wss_625(vbi_decoder *vbi, uint8_t *buf, double time); extern void vbi_decode_wss_cpr1204(vbi_decoder *vbi, uint8_t *buf); /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/wstfont2.xbm000066400000000000000000004334531476363111200156600ustar00rootroot00000000000000/* */ /* libzvbi -- Teletext font */ /* */ /* Copyright (C) 2001 Michael H. Schimek */ /* */ /* This is an implementation of the Teletext fonts pictured in */ /* ETS 300 706, with some beautyfications. */ /* */ /* This library is free software; you can redistribute it and/or */ /* modify it under the terms of the GNU Library General Public */ /* License as published by the Free Software Foundation; either */ /* version 2 of the License, or (at your option) any later version. */ /* */ /* This library is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ /* Library General Public License for more details. */ /* */ /* You should have received a copy of the GNU Library General Public */ /* License along with this library; if not, write to the */ /* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, */ /* Boston, MA 02110-1301 USA. */ /* */ #define wstfont2_width 384 #define wstfont2_height 480 static unsigned char wstfont2_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x9c, 0x83, 0x19, 0xfc, 0xe3, 0x30, 0x78, 0x00, 0x0e, 0xe0, 0x00, 0x07, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xf8, 0x03, 0x0c, 0xf8, 0xc3, 0x7f, 0xc0, 0xc0, 0x7f, 0x60, 0xc0, 0x7f, 0xf8, 0x83, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x0e, 0x80, 0x1f, 0x00, 0x00, 0x06, 0x18, 0x83, 0x19, 0x66, 0xb6, 0x19, 0xcc, 0x00, 0x0c, 0x30, 0x00, 0x0c, 0x66, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0c, 0x06, 0x0f, 0x0c, 0x06, 0x30, 0x60, 0xc0, 0x00, 0x30, 0x00, 0x60, 0x0c, 0xc6, 0x60, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x38, 0xc0, 0x30, 0x00, 0x00, 0x06, 0x8c, 0xe1, 0x7f, 0x66, 0xe0, 0x0c, 0xcc, 0x00, 0x06, 0x18, 0x00, 0x18, 0x6c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x06, 0x0c, 0x00, 0x06, 0x18, 0x30, 0xc0, 0x3f, 0x18, 0x00, 0x30, 0x0c, 0xc6, 0x60, 0x30, 0x00, 0x03, 0x70, 0xc0, 0x3f, 0xe0, 0x00, 0x30, 0x00, 0x00, 0x06, 0x00, 0x80, 0x19, 0xfc, 0x03, 0x06, 0x78, 0x00, 0x00, 0x18, 0x00, 0x18, 0xf0, 0xc0, 0x3f, 0x00, 0xc0, 0x3f, 0x00, 0x00, 0x06, 0x0c, 0x06, 0x0c, 0x80, 0x03, 0x3c, 0x18, 0x03, 0x60, 0xfc, 0x03, 0x18, 0xf8, 0x83, 0x3f, 0x30, 0x00, 0x03, 0x1c, 0x00, 0x00, 0x80, 0x03, 0x18, 0x00, 0x00, 0x06, 0x00, 0xe0, 0x7f, 0x60, 0x06, 0x73, 0xcc, 0x0c, 0x00, 0x18, 0x00, 0x18, 0x6c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x06, 0x0c, 0xe0, 0x00, 0x60, 0x0c, 0x03, 0x60, 0x0c, 0x06, 0x0c, 0x0c, 0x06, 0x18, 0x00, 0x00, 0x00, 0x70, 0xc0, 0x3f, 0xe0, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0x66, 0x86, 0xd9, 0x86, 0x03, 0x00, 0x30, 0x00, 0x0c, 0x66, 0x06, 0x06, 0x1c, 0x00, 0x00, 0x0c, 0x80, 0x01, 0x0c, 0x06, 0x0c, 0x38, 0x00, 0x60, 0xfc, 0xc7, 0x60, 0x0c, 0x06, 0x06, 0x0c, 0x06, 0x0c, 0x30, 0x00, 0x03, 0xc0, 0x01, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x80, 0x19, 0xfc, 0xc3, 0x70, 0xfc, 0x0c, 0x00, 0xe0, 0x00, 0x07, 0x60, 0x00, 0x00, 0x18, 0x00, 0x00, 0x0c, 0xc0, 0x00, 0xf8, 0x03, 0x0c, 0xfc, 0xc7, 0x3f, 0x00, 0x83, 0x3f, 0xf8, 0x03, 0x03, 0xf8, 0x03, 0x06, 0x30, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x0f, 0xfc, 0x03, 0x3f, 0xfc, 0xc1, 0x7f, 0xfc, 0x87, 0x3f, 0x0c, 0xc6, 0x3f, 0x00, 0xc6, 0x70, 0x0c, 0xe0, 0x70, 0x0e, 0x86, 0x3f, 0xfc, 0x83, 0x3f, 0xfc, 0x83, 0x3f, 0xfe, 0xc7, 0x60, 0x06, 0x66, 0x60, 0x0c, 0x63, 0x60, 0xfc, 0x03, 0x06, 0x0c, 0x00, 0x06, 0x70, 0x00, 0x00, 0x0c, 0x86, 0x19, 0x0c, 0x86, 0x61, 0x0c, 0xc3, 0x00, 0x0c, 0xc0, 0x60, 0x0c, 0x06, 0x06, 0x00, 0xc6, 0x1c, 0x0c, 0xe0, 0x79, 0x1e, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x60, 0xc0, 0x60, 0x06, 0x66, 0x60, 0x98, 0xc1, 0x30, 0x80, 0x01, 0x06, 0x18, 0x00, 0x06, 0xd8, 0x00, 0x00, 0xc6, 0xc7, 0x30, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0x06, 0x06, 0x00, 0xc6, 0x0e, 0x0c, 0x60, 0x6f, 0x36, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x00, 0x60, 0xc0, 0x60, 0x0c, 0x63, 0x66, 0xf0, 0x80, 0x19, 0xc0, 0x00, 0x06, 0x30, 0x00, 0x06, 0x8c, 0x01, 0x00, 0x66, 0x66, 0x60, 0xfc, 0xc3, 0x00, 0x0c, 0xc6, 0x1f, 0xfc, 0xc1, 0x78, 0xfc, 0x07, 0x06, 0x00, 0xc6, 0x03, 0x0c, 0x60, 0x66, 0x66, 0xc6, 0x60, 0xfc, 0xc3, 0x60, 0xfc, 0x83, 0x3f, 0x60, 0xc0, 0x60, 0x0c, 0x63, 0x66, 0x60, 0x00, 0x0f, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x66, 0xe6, 0x7f, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x00, 0x0c, 0xc0, 0x60, 0x0c, 0x06, 0x06, 0x00, 0xc6, 0x0e, 0x0c, 0x60, 0x60, 0xc6, 0xc6, 0x60, 0x0c, 0xc0, 0x60, 0x8c, 0x01, 0x60, 0x60, 0xc0, 0x60, 0x98, 0x61, 0x6f, 0xf0, 0x00, 0x06, 0x30, 0x00, 0x06, 0xc0, 0x00, 0x06, 0x00, 0x00, 0x00, 0xc6, 0x67, 0x60, 0x0c, 0x86, 0x61, 0x0c, 0xc3, 0x00, 0x0c, 0xc0, 0x60, 0x0c, 0x06, 0x06, 0x0c, 0xc6, 0x1c, 0x0c, 0x60, 0x60, 0x86, 0xc7, 0x60, 0x0c, 0xc0, 0x6c, 0x0c, 0xc3, 0x60, 0x60, 0xc0, 0x60, 0xf0, 0xc0, 0x39, 0x98, 0x01, 0x06, 0x18, 0x00, 0x06, 0x80, 0x01, 0x06, 0x00, 0x00, 0x00, 0x0c, 0x60, 0x60, 0xfc, 0x03, 0x3f, 0xfc, 0xc1, 0x7f, 0x0c, 0x80, 0x3f, 0x0c, 0xc6, 0x3f, 0xf8, 0xc3, 0x70, 0xfc, 0x67, 0x60, 0x06, 0x87, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x86, 0x3f, 0x60, 0x80, 0x3f, 0x60, 0xc0, 0x30, 0x0c, 0x03, 0x06, 0xfc, 0x03, 0x06, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x80, 0x07, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0xe0, 0x01, 0x00, 0x0c, 0x00, 0x06, 0x00, 0xc6, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x06, 0x78, 0x06, 0x00, 0xc0, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x30, 0x03, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x06, 0xcc, 0x03, 0x00, 0x80, 0xc1, 0x1f, 0xec, 0x83, 0x3f, 0xf8, 0x86, 0x3f, 0x30, 0x80, 0x6f, 0xec, 0x03, 0x07, 0x80, 0xc7, 0x70, 0x60, 0xe0, 0x3b, 0xec, 0x83, 0x3f, 0xec, 0x83, 0x6f, 0xec, 0x83, 0x3f, 0xfc, 0xc1, 0x60, 0x06, 0x66, 0x60, 0x0e, 0xc7, 0x60, 0xfc, 0x07, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x1c, 0xc6, 0x60, 0x0c, 0xc7, 0x60, 0xfc, 0xc0, 0x70, 0x1c, 0x06, 0x06, 0x00, 0xc6, 0x18, 0x60, 0x60, 0x66, 0x1c, 0xc6, 0x60, 0x1c, 0xc6, 0x70, 0x1c, 0xc6, 0x00, 0x30, 0xc0, 0x60, 0x0c, 0x63, 0x66, 0x98, 0x81, 0x31, 0x80, 0x01, 0x03, 0x60, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x3f, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x7f, 0x30, 0xc0, 0x60, 0x0c, 0x06, 0x06, 0x00, 0xc6, 0x07, 0x60, 0x60, 0x66, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x80, 0x3f, 0x30, 0xc0, 0x60, 0x98, 0x61, 0x66, 0xf0, 0x00, 0x1b, 0xe0, 0x00, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x00, 0x30, 0x80, 0x7f, 0x0c, 0x06, 0x06, 0x00, 0xc6, 0x18, 0x60, 0x60, 0x66, 0x0c, 0xc6, 0x60, 0x1c, 0xc6, 0x70, 0x0c, 0x00, 0x60, 0x30, 0xc6, 0x70, 0xf0, 0xc0, 0x36, 0x98, 0x01, 0x0e, 0x30, 0x00, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x7f, 0xfc, 0x83, 0x7f, 0xf8, 0x87, 0x3f, 0x30, 0x00, 0x60, 0x0c, 0x86, 0x1f, 0x0c, 0xc6, 0x70, 0xf8, 0x61, 0x66, 0x0c, 0x86, 0x3f, 0xec, 0x83, 0x6f, 0x0c, 0x80, 0x3f, 0xe0, 0x83, 0x6f, 0x60, 0x80, 0x19, 0x0e, 0x07, 0x06, 0xfc, 0x07, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x1c, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1f, 0x18, 0x83, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x83, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x3f, 0x00, 0xc0, 0x30, 0x60, 0xc0, 0x30, 0x00, 0x40, 0x40, 0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x78, 0x00, 0x06, 0x1e, 0xe0, 0x01, 0xc0, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0x00, 0x06, 0xf0, 0x03, 0x00, 0x0e, 0xe2, 0x20, 0x1e, 0x02, 0x06, 0x00, 0x00, 0x00, 0x60, 0x80, 0x61, 0x0c, 0x83, 0x19, 0x60, 0xc0, 0x00, 0x00, 0x20, 0x8e, 0x00, 0x83, 0x19, 0x00, 0x00, 0x00, 0xf2, 0x09, 0x00, 0xcc, 0x00, 0x06, 0x30, 0x00, 0x03, 0x60, 0x00, 0x00, 0x66, 0x0e, 0x00, 0x00, 0x00, 0x07, 0x18, 0xc6, 0x0c, 0x0c, 0xc1, 0x10, 0x30, 0x01, 0x00, 0x00, 0x00, 0x06, 0xfc, 0x03, 0x03, 0xf8, 0x01, 0x0f, 0x60, 0x80, 0x1f, 0x00, 0x20, 0x91, 0xf8, 0xc3, 0x0c, 0x00, 0x00, 0x00, 0x12, 0x0b, 0x00, 0x78, 0xc0, 0x3f, 0x18, 0xc0, 0x01, 0x00, 0x60, 0x18, 0x66, 0x06, 0x00, 0x00, 0x00, 0x06, 0x18, 0x86, 0x19, 0x8c, 0xc0, 0x08, 0x9c, 0x00, 0x06, 0x00, 0x00, 0x06, 0x66, 0xc6, 0x0f, 0x0c, 0x03, 0x06, 0x00, 0xc0, 0x30, 0x00, 0x20, 0x81, 0x0c, 0x63, 0x06, 0xfc, 0xc3, 0x3f, 0xf2, 0x09, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x00, 0x03, 0x00, 0x60, 0x18, 0x7c, 0x06, 0x06, 0x00, 0x00, 0x06, 0x18, 0x06, 0x33, 0x4c, 0xce, 0x7c, 0x70, 0x87, 0x01, 0x00, 0x00, 0x06, 0x66, 0x00, 0x03, 0x0c, 0x83, 0x1f, 0x60, 0x80, 0x1f, 0x00, 0x20, 0x91, 0xf8, 0xc7, 0x0c, 0x00, 0x03, 0x00, 0x12, 0x09, 0x00, 0x00, 0x00, 0x06, 0x7e, 0xe0, 0x01, 0x00, 0x60, 0x18, 0x60, 0x06, 0x00, 0x00, 0x00, 0x06, 0xf0, 0x83, 0x19, 0x2c, 0xcd, 0xc2, 0xbe, 0xc6, 0x00, 0x00, 0x00, 0x06, 0x66, 0xc0, 0x67, 0xf8, 0x01, 0x06, 0x60, 0x00, 0x30, 0x00, 0x20, 0x8e, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x12, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x18, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0c, 0x90, 0x0c, 0x61, 0x50, 0xc6, 0x30, 0x00, 0x00, 0x06, 0xfc, 0xc7, 0x3c, 0x0c, 0x03, 0x06, 0x60, 0xc0, 0x30, 0x00, 0x40, 0x40, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x37, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0x00, 0xc8, 0x8f, 0x30, 0xe8, 0x87, 0x1f, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x06, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x4c, 0xf8, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x06, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x18, 0xe0, 0x80, 0x67, 0x18, 0x03, 0x1f, 0x00, 0x00, 0x00, 0x60, 0x00, 0x18, 0xe0, 0x80, 0x31, 0x30, 0x00, 0x0c, 0xf0, 0x80, 0x19, 0x00, 0x80, 0x67, 0x30, 0x00, 0x18, 0xe0, 0x80, 0x67, 0x18, 0x03, 0x00, 0x00, 0x00, 0x06, 0xc0, 0x00, 0x0e, 0x18, 0x03, 0x0c, 0x3c, 0x00, 0x00, 0x60, 0x00, 0x0c, 0xb0, 0xc1, 0x3c, 0x00, 0x80, 0x31, 0xf8, 0x07, 0x3f, 0xc0, 0x00, 0x0c, 0xb0, 0x01, 0x00, 0x60, 0x00, 0x06, 0x98, 0x01, 0x00, 0xfc, 0xc1, 0x3c, 0x60, 0x00, 0x0c, 0xb0, 0xc1, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x60, 0x00, 0x1b, 0x00, 0x60, 0x66, 0x18, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x1f, 0x6c, 0x80, 0x61, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0xf8, 0x81, 0x1f, 0x00, 0x80, 0x1f, 0x0c, 0xc3, 0x61, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x03, 0x00, 0xf8, 0xc3, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x30, 0xf8, 0x83, 0x31, 0x18, 0x83, 0x31, 0xf8, 0x83, 0x3f, 0x18, 0x83, 0x31, 0x66, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x60, 0x00, 0x06, 0xf8, 0x01, 0x06, 0x0c, 0xc6, 0x63, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x86, 0x19, 0x8c, 0xc7, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x86, 0x19, 0x18, 0xc6, 0x30, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xfe, 0xc3, 0x00, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x3e, 0xc6, 0x66, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x0f, 0xcc, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x0f, 0x18, 0xc6, 0x18, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0x66, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0xc6, 0x6c, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x06, 0x6c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x06, 0xf8, 0xc3, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x66, 0x80, 0x61, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0xc3, 0x78, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x0f, 0x3c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x06, 0x18, 0xc0, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xe6, 0x07, 0x3f, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0xf8, 0x81, 0x1f, 0xf8, 0x81, 0x1f, 0xfc, 0xc1, 0x70, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x19, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x03, 0x06, 0x3c, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x0c, 0x70, 0x80, 0x33, 0x98, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x60, 0x00, 0x0c, 0xe0, 0x80, 0x31, 0x30, 0x00, 0x0c, 0xe0, 0x80, 0x19, 0x80, 0x81, 0x67, 0x30, 0x00, 0x18, 0xe0, 0x80, 0x67, 0x18, 0x03, 0x00, 0x00, 0x00, 0x06, 0xc0, 0x00, 0x0e, 0x18, 0x03, 0x18, 0x00, 0xc0, 0x18, 0x60, 0x00, 0x06, 0xd8, 0xc0, 0x1c, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x06, 0xb0, 0x01, 0x00, 0x60, 0x00, 0x06, 0xb0, 0x01, 0x00, 0xe0, 0xcf, 0x3c, 0x60, 0x00, 0x0c, 0xb0, 0xc1, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x60, 0x00, 0x1b, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xbc, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0x70, 0x00, 0x07, 0x70, 0x00, 0x07, 0xf8, 0xc7, 0x3e, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x03, 0x06, 0xfc, 0xc3, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xf8, 0x63, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x30, 0x60, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0xc6, 0x61, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x00, 0xc6, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x86, 0x31, 0x18, 0xc6, 0x18, 0xfc, 0xc3, 0x3f, 0xfc, 0xc3, 0x3f, 0xfc, 0xc3, 0x3f, 0xfc, 0xc7, 0x00, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x3f, 0x66, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x1b, 0x18, 0x86, 0x0d, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x66, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x00, 0x36, 0xc6, 0x70, 0x0c, 0xc7, 0x70, 0x0c, 0x07, 0x0e, 0x18, 0x06, 0x07, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0xfc, 0x83, 0x7f, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x81, 0x1f, 0xf8, 0x81, 0x1f, 0xf8, 0xc7, 0x60, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x03, 0x06, 0xfc, 0x83, 0x6f, 0xf8, 0x86, 0x6f, 0xf8, 0x06, 0x06, 0xf8, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x18, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0xf8, 0xc3, 0x1f, 0x18, 0xc3, 0x18, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0xc0, 0x01, 0x0e, 0xc0, 0x00, 0x06, 0xb0, 0x01, 0x1b, 0xd8, 0x00, 0x1b, 0x00, 0x00, 0x00, 0xf8, 0x83, 0x3f, 0x18, 0x83, 0x31, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x1b, 0xe0, 0x00, 0x0e, 0x18, 0x83, 0x31, 0x00, 0x00, 0x00, 0xf0, 0x81, 0x0f, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x06, 0x60, 0x03, 0x1b, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x0e, 0x70, 0x00, 0x6e, 0xfc, 0x01, 0x60, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x0e, 0xb0, 0x01, 0x1b, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0xf0, 0x03, 0x00, 0xf0, 0x03, 0x00, 0xf0, 0x03, 0x00, 0xf0, 0x03, 0x00, 0xfc, 0x01, 0x60, 0x0c, 0x03, 0xf8, 0xfc, 0x07, 0x00, 0xfc, 0x07, 0x00, 0xfc, 0x07, 0x00, 0xfc, 0x07, 0x00, 0xfc, 0x07, 0x00, 0xf8, 0x03, 0x00, 0xf8, 0x03, 0x00, 0x18, 0xc3, 0x1f, 0xf8, 0xc3, 0x1f, 0x18, 0xc3, 0x1f, 0x18, 0x86, 0x3f, 0x18, 0x86, 0x3f, 0x18, 0x86, 0x3f, 0x18, 0x86, 0x3f, 0x0c, 0x83, 0x6f, 0x0c, 0x06, 0x60, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x86, 0x6f, 0x0c, 0x86, 0x6f, 0x0c, 0x06, 0x30, 0x0c, 0x06, 0x30, 0x0c, 0x06, 0x30, 0x0c, 0xc0, 0x60, 0x0c, 0xc0, 0x60, 0x0c, 0xc0, 0x60, 0x0c, 0xc0, 0x60, 0x0c, 0xc6, 0x70, 0x3e, 0x86, 0x7f, 0xfc, 0xc1, 0x60, 0xfc, 0xc1, 0x60, 0xfc, 0xc1, 0x60, 0xfc, 0xc1, 0x60, 0xfc, 0xc1, 0x60, 0x0c, 0xc0, 0x70, 0x0c, 0xc0, 0x70, 0xfc, 0xc7, 0x3f, 0xfc, 0xc7, 0x3f, 0xfc, 0xc7, 0x3f, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc0, 0x7f, 0x0c, 0xc0, 0x7f, 0x0c, 0xc0, 0x7f, 0x0c, 0xc0, 0x7f, 0x0c, 0xc0, 0x7f, 0x8c, 0xc7, 0x60, 0x8c, 0xc7, 0x60, 0x0c, 0x66, 0x30, 0x0c, 0x66, 0x30, 0x0c, 0x66, 0x30, 0x18, 0xc6, 0x00, 0x18, 0xc6, 0x00, 0x18, 0xc6, 0x00, 0x18, 0xc6, 0x00, 0x0c, 0xc3, 0x60, 0x0c, 0xc3, 0x60, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0x86, 0x7f, 0x0c, 0x86, 0x7f, 0x0c, 0xc6, 0x7f, 0x0c, 0xc6, 0x7f, 0x0c, 0xc6, 0x7f, 0xf0, 0x83, 0x7f, 0xf0, 0x83, 0x7f, 0xf0, 0x83, 0x7f, 0xf0, 0x83, 0x7f, 0xfc, 0x81, 0x7f, 0xfc, 0x81, 0x7f, 0xfc, 0x87, 0x3f, 0xfc, 0x87, 0x3f, 0xfc, 0x87, 0x3f, 0xfc, 0x87, 0x3f, 0xfc, 0x87, 0x3f, 0xf8, 0x03, 0x60, 0xf8, 0x03, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x0c, 0xe0, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x38, 0x03, 0x1b, 0xf8, 0x81, 0x1f, 0x0c, 0xc3, 0x30, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xb0, 0x01, 0x1b, 0x0c, 0xc6, 0x00, 0xcc, 0x81, 0x0d, 0x00, 0x00, 0x00, 0xf8, 0x81, 0x1f, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1e, 0x86, 0x61, 0xb0, 0x01, 0x1b, 0x0c, 0xc7, 0x00, 0x00, 0x00, 0x0c, 0x60, 0xc0, 0x00, 0x78, 0xc0, 0x18, 0x78, 0xc6, 0x00, 0xf8, 0x03, 0x00, 0xf8, 0x03, 0x00, 0x0c, 0xc6, 0x00, 0xfe, 0xef, 0x03, 0xf8, 0x01, 0x00, 0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0xf8, 0x01, 0x00, 0x0c, 0x06, 0x00, 0x00, 0x06, 0x00, 0xcc, 0xc1, 0x00, 0x00, 0xc0, 0x00, 0x78, 0xc0, 0x00, 0x60, 0xc0, 0x0c, 0x60, 0xc3, 0x00, 0x0c, 0x86, 0x6f, 0x0c, 0x86, 0x6f, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x00, 0x60, 0x00, 0x07, 0x60, 0x00, 0x07, 0xf8, 0x01, 0x07, 0x60, 0x00, 0x07, 0x60, 0x00, 0x07, 0x0c, 0xc6, 0x71, 0x00, 0x06, 0x78, 0xec, 0xc0, 0x70, 0x8c, 0xc7, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x18, 0x0c, 0xc0, 0x70, 0x0c, 0xc0, 0x70, 0xfc, 0xc7, 0x3e, 0xfc, 0xc7, 0x3e, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0x86, 0x61, 0x00, 0x06, 0x60, 0x3c, 0xc0, 0x18, 0xcc, 0xc1, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x8c, 0xc7, 0x60, 0x8c, 0xc7, 0x60, 0x0c, 0xc6, 0x61, 0x0c, 0xc6, 0x61, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0x86, 0x61, 0x00, 0x06, 0x60, 0xec, 0xc0, 0x07, 0x7c, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x0c, 0x86, 0x7f, 0x0c, 0x86, 0x7f, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0xcc, 0x86, 0x61, 0x0c, 0xc6, 0x60, 0xcc, 0xc1, 0x18, 0xcc, 0xc1, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0xf8, 0x03, 0x60, 0xf8, 0x03, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xf8, 0x81, 0x1f, 0xf8, 0x81, 0x1f, 0xf8, 0x81, 0x1f, 0xf8, 0x81, 0x1f, 0xf8, 0x81, 0x1f, 0x9e, 0xe3, 0x67, 0xf8, 0x83, 0x3f, 0x0c, 0xc7, 0x70, 0x8c, 0xc7, 0x7f, 0xf8, 0xc1, 0x7f, 0xf8, 0xc1, 0x7f, 0xf8, 0xc1, 0x7f, 0x00, 0x80, 0x3f, 0x60, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x18, 0x80, 0x01, 0x00, 0x00, 0x00, 0x1b, 0xb0, 0x61, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x83, 0x3f, 0x18, 0x83, 0x31, 0x60, 0x06, 0x66, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x1b, 0xc0, 0x00, 0x0c, 0xe0, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x60, 0xc0, 0x00, 0x78, 0x03, 0x0c, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xe0, 0xc0, 0x00, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x1f, 0x30, 0x03, 0x33, 0xf8, 0x07, 0x00, 0x60, 0x00, 0x0c, 0xfc, 0x03, 0x00, 0x70, 0x00, 0x0e, 0x60, 0x00, 0x06, 0xb0, 0x01, 0x1b, 0xf8, 0x03, 0x00, 0x60, 0xc3, 0x06, 0xe0, 0xc1, 0x61, 0x00, 0xc0, 0x61, 0x00, 0xc0, 0x61, 0x00, 0x00, 0x00, 0x1c, 0x06, 0x00, 0xf8, 0x03, 0x00, 0xf8, 0x03, 0x00, 0xf8, 0x03, 0x00, 0xcc, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x0c, 0x06, 0x00, 0xfc, 0x03, 0x00, 0xf8, 0x03, 0x00, 0xf8, 0x03, 0x00, 0x0c, 0x06, 0x00, 0x60, 0xc0, 0x03, 0xe0, 0xc0, 0x63, 0xec, 0xc3, 0x63, 0xec, 0xc3, 0x63, 0xec, 0xc3, 0x3e, 0x3c, 0xc6, 0x3e, 0x0c, 0x86, 0x3f, 0x0c, 0x86, 0x3f, 0x0c, 0x86, 0x3f, 0xc6, 0xc0, 0x3f, 0x0c, 0xc6, 0x3e, 0x0c, 0xc6, 0x3e, 0x0c, 0xc6, 0x3e, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x60, 0xc0, 0x01, 0x70, 0xc0, 0x66, 0x1c, 0xc6, 0x66, 0x1c, 0xc6, 0x66, 0x1c, 0xc6, 0x61, 0x6c, 0xc6, 0x61, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xc6, 0x63, 0x66, 0x0c, 0xc6, 0x61, 0xfc, 0xc3, 0x61, 0x0c, 0xc6, 0x61, 0xf8, 0xc3, 0x00, 0xf8, 0xc3, 0x00, 0xf8, 0xc3, 0x00, 0x60, 0xe0, 0x00, 0x78, 0xc0, 0x6c, 0x0c, 0xc6, 0x6c, 0x0c, 0xc6, 0x6c, 0x0c, 0xc6, 0x60, 0xcc, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xc6, 0x60, 0x7e, 0xfc, 0xc3, 0x00, 0x8c, 0xc1, 0x00, 0xfc, 0xc3, 0x00, 0x00, 0x86, 0x3f, 0x00, 0x86, 0x3f, 0x00, 0x86, 0x3f, 0x60, 0xf0, 0x00, 0x60, 0xc0, 0x78, 0x0c, 0xc6, 0x78, 0x0c, 0xc6, 0x78, 0x0c, 0xc6, 0x60, 0x8c, 0xc7, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xcc, 0x60, 0x06, 0x8c, 0xc1, 0x00, 0x0c, 0xc3, 0x00, 0x8c, 0xc1, 0x00, 0x0c, 0x06, 0x60, 0x0c, 0x06, 0x60, 0x0c, 0x06, 0x60, 0xf8, 0xc1, 0x7f, 0xf8, 0xc1, 0x70, 0x0c, 0xc6, 0x70, 0x0c, 0xc6, 0x70, 0x0c, 0xc6, 0x60, 0x0c, 0xc7, 0x60, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0xc7, 0x3f, 0x0c, 0xc7, 0x00, 0x0c, 0xc6, 0x00, 0x0c, 0xc7, 0x00, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x0e, 0xb0, 0x01, 0x1b, 0x00, 0x00, 0x00, 0x98, 0x81, 0x19, 0x00, 0x00, 0x00, 0x70, 0x03, 0x37, 0xf8, 0x83, 0x3f, 0x18, 0x83, 0x31, 0xe0, 0x00, 0x0e, 0x60, 0x06, 0x66, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0xf0, 0x00, 0x0e, 0x98, 0x01, 0x0c, 0xc0, 0x00, 0x06, 0x60, 0x00, 0x1b, 0xb0, 0x01, 0x00, 0xe0, 0x00, 0x0e, 0xfe, 0x07, 0x03, 0xf0, 0x00, 0x0f, 0xfe, 0x07, 0x03, 0xd8, 0x81, 0x1d, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x1f, 0xb0, 0x01, 0x1b, 0x30, 0x03, 0x33, 0x0c, 0x06, 0x00, 0x98, 0x81, 0x19, 0x98, 0x01, 0x1b, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xe0, 0x00, 0x0f, 0xf8, 0x03, 0x00, 0x60, 0x00, 0x03, 0xfe, 0x07, 0x03, 0x60, 0xc0, 0x1f, 0x0c, 0x06, 0x00, 0x0c, 0x06, 0x00, 0x0c, 0x06, 0x00, 0xec, 0x06, 0x0e, 0x0c, 0x06, 0x00, 0x0c, 0x06, 0x00, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc3, 0x3f, 0x00, 0xc0, 0x3f, 0x00, 0xc0, 0x3f, 0x00, 0x80, 0x19, 0x0c, 0x80, 0x3f, 0x60, 0xc0, 0x1f, 0x60, 0xc0, 0x1f, 0x60, 0x00, 0x03, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x66, 0x66, 0x60, 0x0c, 0xc3, 0x60, 0x98, 0x01, 0x0c, 0xfc, 0x07, 0x0c, 0xfc, 0x07, 0x0c, 0xfc, 0xc7, 0x01, 0xf8, 0xc3, 0x00, 0x60, 0x00, 0x03, 0x60, 0x00, 0x03, 0xf8, 0xc1, 0x1f, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x66, 0x66, 0x66, 0x98, 0x81, 0x31, 0xf0, 0x00, 0x06, 0x80, 0x01, 0x06, 0x80, 0x01, 0x06, 0x80, 0x81, 0x01, 0x00, 0x86, 0x3f, 0x60, 0x00, 0x03, 0x60, 0x00, 0x03, 0x60, 0x00, 0x03, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xf6, 0x66, 0x66, 0xf0, 0x00, 0x1b, 0x60, 0x00, 0x03, 0xe0, 0x00, 0x03, 0xe0, 0x00, 0x03, 0xe0, 0x80, 0x01, 0x0c, 0x06, 0x60, 0x60, 0x00, 0x63, 0x60, 0x00, 0x63, 0x60, 0x00, 0x63, 0x0c, 0xc6, 0x70, 0x0c, 0xc6, 0x70, 0x0c, 0xc6, 0x70, 0x0c, 0xc6, 0x70, 0x0c, 0xc6, 0x70, 0x0c, 0xc6, 0x70, 0x9c, 0xc3, 0x36, 0x60, 0x00, 0x0e, 0x60, 0x80, 0x01, 0x30, 0x80, 0x01, 0x30, 0x80, 0x01, 0x30, 0x80, 0x01, 0xf8, 0x83, 0x3f, 0x60, 0x00, 0x3e, 0x60, 0x00, 0x3e, 0x60, 0x00, 0x3e, 0xf8, 0x83, 0x6f, 0xf8, 0x83, 0x6f, 0xf8, 0x83, 0x6f, 0xf8, 0x83, 0x6f, 0xf8, 0x83, 0x6f, 0xf8, 0x83, 0x6f, 0x0c, 0x83, 0x19, 0x60, 0x00, 0x06, 0x60, 0xc0, 0x3f, 0xfc, 0xc7, 0x3f, 0xfc, 0xc7, 0x3f, 0xfc, 0xc7, 0x03, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x81, 0x0d, 0x00, 0x00, 0x00, 0x30, 0x03, 0x0e, 0xb0, 0x81, 0x3f, 0xc0, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x03, 0x06, 0xf0, 0x00, 0x00, 0x70, 0x03, 0x00, 0xc0, 0x00, 0x0e, 0x30, 0x83, 0x3b, 0x7e, 0xe0, 0x31, 0x00, 0xe0, 0x47, 0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x83, 0x19, 0xe0, 0x00, 0x07, 0x00, 0x00, 0x00, 0x98, 0x01, 0x1b, 0xe0, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x98, 0x01, 0x00, 0xd8, 0x01, 0x00, 0x60, 0x00, 0x0c, 0x98, 0x01, 0x33, 0x06, 0x20, 0x19, 0x00, 0x80, 0x6c, 0x0c, 0xe3, 0x20, 0x1e, 0xe2, 0x21, 0x80, 0x81, 0x19, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x06, 0xb8, 0x83, 0x19, 0xe6, 0xe7, 0x0d, 0xf8, 0x82, 0x7c, 0x06, 0xc6, 0x10, 0x30, 0x21, 0x10, 0xc0, 0x80, 0x19, 0x18, 0xc3, 0x1f, 0xf8, 0x83, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x06, 0x88, 0x82, 0x54, 0x06, 0xc6, 0x08, 0x9c, 0xe0, 0x09, 0xf8, 0x81, 0x19, 0x0c, 0x06, 0x30, 0x0c, 0xc6, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x03, 0x03, 0xa8, 0x82, 0x44, 0x06, 0xc6, 0x7c, 0x70, 0x8f, 0x7d, 0x30, 0x80, 0x19, 0xfc, 0xc7, 0x3f, 0xfc, 0x67, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x80, 0xf7, 0xa8, 0x82, 0x44, 0x0c, 0xc3, 0x4a, 0x3e, 0xe9, 0x4a, 0x18, 0x80, 0x19, 0x0c, 0x66, 0x30, 0x0c, 0xc0, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xc0, 0x95, 0x28, 0x02, 0x00, 0x98, 0x01, 0x79, 0x10, 0x0f, 0x79, 0xfc, 0x83, 0x19, 0x0c, 0xc6, 0x7f, 0xf8, 0x83, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x67, 0xf7, 0xe8, 0x03, 0x00, 0x9e, 0x87, 0x48, 0x08, 0x89, 0x48, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x78, 0x04, 0x4f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x02, 0x00, 0x60, 0x00, 0x00, 0x60, 0xe0, 0x7f, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x06, 0xf0, 0x00, 0x06, 0x60, 0xe0, 0x7f, 0x40, 0xc3, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x80, 0x03, 0xf8, 0x01, 0x1c, 0x60, 0xe0, 0x7f, 0x40, 0x02, 0x03, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xe7, 0xff, 0x6c, 0xf3, 0x7f, 0x60, 0xe0, 0x7f, 0x40, 0x00, 0x1b, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x84, 0x03, 0x60, 0x00, 0x1c, 0x6c, 0xe3, 0x7f, 0x78, 0x00, 0x1b, 0x60, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x07, 0x06, 0x60, 0x00, 0x06, 0xf8, 0xe1, 0x7f, 0x7c, 0x00, 0x1b, 0x30, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x04, 0x00, 0x60, 0x00, 0x00, 0xf0, 0xe0, 0x7f, 0x38, 0x00, 0x7b, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x07, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x01, 0x00, 0xfb, 0xb7, 0x61, 0xfb, 0x07, 0x00, 0xf3, 0x01, 0x00, 0x1b, 0x36, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x03, 0x00, 0x1b, 0xb4, 0x61, 0xc3, 0x00, 0x00, 0x1b, 0x03, 0x00, 0x33, 0xb3, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x06, 0x00, 0x18, 0x80, 0x61, 0xc0, 0x00, 0x00, 0x0c, 0x06, 0x00, 0xe0, 0xc1, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0xf8, 0x81, 0x7f, 0xc0, 0x00, 0x00, 0x0c, 0x06, 0x00, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x18, 0x80, 0x61, 0xc0, 0x00, 0x00, 0x0c, 0x06, 0x00, 0xc0, 0x80, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x18, 0x84, 0x61, 0xc0, 0x00, 0x00, 0x18, 0x03, 0x00, 0xc0, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0xf8, 0x87, 0x61, 0xf8, 0x07, 0x00, 0xf0, 0x01, 0x00, 0xe0, 0xc1, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x81, 0x19, 0xc0, 0x00, 0x18, 0xc0, 0x00, 0x0c, 0x6c, 0x03, 0x0f, 0xfe, 0xe1, 0x7f, 0xf0, 0xe0, 0x3f, 0xfe, 0xe3, 0x70, 0xfc, 0x83, 0x1f, 0x1e, 0x07, 0x0f, 0x0e, 0xe7, 0x70, 0xfe, 0xc7, 0x3f, 0xfe, 0xc7, 0x3f, 0x00, 0xe0, 0x7f, 0xfe, 0x67, 0x60, 0xfc, 0xe3, 0x70, 0x66, 0x86, 0x1f, 0x00, 0x00, 0x00, 0x60, 0x00, 0x0c, 0x60, 0x00, 0x06, 0x00, 0x80, 0x19, 0x0c, 0xc3, 0x40, 0x98, 0xc1, 0x20, 0xc2, 0xc1, 0x30, 0x06, 0x06, 0x06, 0x8c, 0x81, 0x19, 0x9e, 0xc7, 0x31, 0x02, 0x64, 0x60, 0x0c, 0xc3, 0x60, 0x00, 0xc0, 0x41, 0x62, 0xc4, 0x30, 0x66, 0xc6, 0x30, 0x66, 0xc6, 0x30, 0xf8, 0xc1, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xc0, 0x30, 0x0c, 0xc3, 0x00, 0x0c, 0xc3, 0x00, 0xe0, 0xc0, 0x30, 0x06, 0x06, 0x06, 0xcc, 0xc0, 0x30, 0xf6, 0xc6, 0x33, 0x00, 0x60, 0x60, 0x0c, 0xc3, 0x60, 0x00, 0x00, 0x03, 0x60, 0x80, 0x19, 0x66, 0x86, 0x19, 0x66, 0x66, 0x60, 0x60, 0x80, 0x19, 0x78, 0x86, 0x3f, 0xf6, 0x01, 0x07, 0x60, 0x60, 0x60, 0xfc, 0xc1, 0x00, 0x06, 0xc6, 0x07, 0x70, 0xc0, 0x3f, 0xfe, 0x07, 0x06, 0x7c, 0x60, 0x60, 0x66, 0xc6, 0x36, 0xf8, 0x61, 0x60, 0x0c, 0xc3, 0x3f, 0x00, 0x00, 0x06, 0x60, 0x00, 0x0f, 0x66, 0x06, 0x0f, 0x66, 0x66, 0x60, 0x60, 0x00, 0x0f, 0x8c, 0xc3, 0x00, 0x1c, 0x03, 0x06, 0x60, 0xe0, 0x7f, 0x0c, 0xc3, 0x00, 0x06, 0xc6, 0x00, 0x38, 0xc0, 0x30, 0x06, 0x06, 0x06, 0xcc, 0x60, 0x60, 0x06, 0xc6, 0x3c, 0x00, 0x60, 0x60, 0x0c, 0xc3, 0x00, 0x00, 0x00, 0x03, 0x60, 0x00, 0x06, 0xfc, 0x83, 0x19, 0xfc, 0xc3, 0x30, 0x60, 0x00, 0x06, 0x8c, 0x81, 0x0f, 0x0c, 0x03, 0x06, 0x60, 0x60, 0x60, 0x0c, 0xc3, 0x00, 0x06, 0xc6, 0x20, 0x1c, 0xc2, 0x30, 0x06, 0x06, 0x06, 0x8c, 0x61, 0x60, 0x06, 0xc6, 0x38, 0x02, 0x64, 0x60, 0x0c, 0xc3, 0x00, 0x00, 0xc0, 0x41, 0x60, 0x00, 0x06, 0x60, 0xc0, 0x30, 0x60, 0x80, 0x19, 0x60, 0x00, 0x06, 0x8c, 0xc3, 0x00, 0x0c, 0x03, 0x06, 0xf8, 0x61, 0x60, 0xfe, 0xe1, 0x00, 0xfe, 0xe7, 0x3f, 0xfe, 0xe3, 0x70, 0xfc, 0x83, 0x1f, 0x1e, 0x67, 0x60, 0x06, 0xe6, 0x70, 0xfe, 0xc7, 0x3f, 0x0e, 0xe7, 0x01, 0x00, 0xe0, 0x7f, 0xf0, 0x00, 0x0f, 0xf0, 0xe0, 0x70, 0xf0, 0xe0, 0x79, 0xf8, 0x01, 0x0f, 0x78, 0x86, 0x3f, 0x0e, 0x83, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x18, 0xc0, 0x00, 0x00, 0x6c, 0x06, 0x00, 0xf8, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x81, 0x31, 0xc0, 0x00, 0x0c, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x0c, 0x00, 0x00, 0x38, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xc6, 0x37, 0x8c, 0xe1, 0x60, 0x18, 0x80, 0x3f, 0xe0, 0x63, 0x1f, 0x0c, 0x03, 0x07, 0x0e, 0x03, 0x06, 0x0e, 0xe3, 0x70, 0x18, 0x80, 0x3f, 0xfe, 0x07, 0x3f, 0xf8, 0x81, 0x7f, 0xfc, 0xe3, 0x60, 0xf8, 0xe1, 0x70, 0x06, 0xc6, 0x30, 0x70, 0xe0, 0x60, 0xf8, 0xe3, 0x60, 0x0c, 0x03, 0x00, 0x0c, 0x66, 0x1c, 0xec, 0xa0, 0x31, 0xfc, 0xc1, 0x00, 0x30, 0xc0, 0x31, 0xf8, 0x03, 0x06, 0xcc, 0x01, 0x0f, 0x0c, 0xc3, 0x30, 0xf0, 0xc0, 0x60, 0x0c, 0x83, 0x61, 0x0c, 0xc0, 0x18, 0x60, 0xc0, 0x60, 0x6c, 0xc3, 0x39, 0x66, 0x66, 0x60, 0x60, 0xc0, 0x60, 0x0c, 0xc6, 0x60, 0x06, 0x06, 0x00, 0x0c, 0x66, 0x18, 0x8c, 0x03, 0x1b, 0x06, 0xc3, 0x0f, 0x18, 0xc0, 0x30, 0x0c, 0x03, 0x06, 0x7c, 0x80, 0x19, 0x1c, 0x83, 0x19, 0x18, 0xc0, 0x60, 0x0c, 0x83, 0x61, 0xf8, 0xc1, 0x30, 0x60, 0xc0, 0x60, 0x6c, 0x03, 0x0f, 0x66, 0x66, 0x66, 0x60, 0xc0, 0x60, 0x0c, 0xc6, 0x60, 0x66, 0x06, 0x00, 0x0c, 0x66, 0x3c, 0x0c, 0x03, 0x0e, 0x06, 0xc3, 0x00, 0x30, 0xc0, 0x30, 0x0c, 0x03, 0x06, 0xcc, 0xc1, 0x30, 0xec, 0x05, 0x0f, 0x0c, 0xc0, 0x60, 0x0c, 0x83, 0x3f, 0x00, 0xc3, 0x30, 0x60, 0xc0, 0x60, 0xf8, 0xc1, 0x39, 0xfc, 0x63, 0x6f, 0x60, 0xc0, 0x60, 0x0c, 0xc6, 0x60, 0xf6, 0x06, 0x00, 0xf8, 0xc3, 0x67, 0xec, 0x01, 0x07, 0xfc, 0x81, 0x3f, 0xe0, 0xe3, 0x30, 0xf8, 0x81, 0x1f, 0x0e, 0xe7, 0x70, 0x0c, 0x00, 0x06, 0xf8, 0x80, 0x3f, 0x0c, 0x83, 0x01, 0xf8, 0x81, 0x1f, 0xe0, 0x80, 0x3f, 0x60, 0xe0, 0x70, 0x60, 0xc0, 0x39, 0xf8, 0x81, 0x3f, 0xf8, 0x83, 0x3f, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x80, 0x31, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x03, 0x0c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x3f, 0x00, 0x0c, 0xf8, 0x83, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x86, 0x0f, 0xc6, 0xf0, 0x03, 0x6c, 0x00, 0x06, 0xf8, 0xc1, 0x30, 0xf0, 0xc0, 0x3f, 0xfc, 0xc3, 0x3f, 0xf0, 0xc1, 0x7f, 0x66, 0x86, 0x3f, 0x06, 0x07, 0x1f, 0x0c, 0x07, 0x1f, 0x0e, 0x67, 0x60, 0xf8, 0xc3, 0x3f, 0xfc, 0xc7, 0x7f, 0x0c, 0xc0, 0x7f, 0x0c, 0xc6, 0x60, 0x60, 0x80, 0x1f, 0x00, 0xc6, 0x0c, 0xc6, 0xc0, 0x00, 0x0c, 0x67, 0x78, 0x00, 0xc0, 0x30, 0x98, 0xc1, 0x00, 0x0c, 0xc6, 0x00, 0x98, 0xc1, 0x00, 0x66, 0xc6, 0x60, 0x86, 0x67, 0x78, 0x8c, 0x81, 0x19, 0x9e, 0x67, 0x60, 0x0c, 0xc6, 0x30, 0x0c, 0xc0, 0x00, 0xec, 0xc3, 0x00, 0x06, 0xc0, 0x00, 0x60, 0x00, 0x06, 0x00, 0xc6, 0x0c, 0xc6, 0xc0, 0x3e, 0x8c, 0x61, 0x6c, 0x0c, 0xc3, 0x30, 0x0c, 0xc3, 0x00, 0x0c, 0xc6, 0x00, 0x98, 0xc1, 0x00, 0x66, 0x06, 0x60, 0xc6, 0x66, 0x6c, 0xec, 0x80, 0x19, 0xf6, 0x66, 0x60, 0x0c, 0xc6, 0x30, 0xfc, 0xc1, 0x1f, 0x1c, 0xc6, 0x00, 0xfc, 0x80, 0x3f, 0x60, 0x00, 0x06, 0x00, 0xc6, 0x7c, 0xfe, 0xc7, 0x61, 0xec, 0x60, 0x66, 0x98, 0xc1, 0x30, 0x06, 0xc6, 0x3f, 0xfc, 0xc3, 0x00, 0x98, 0xc1, 0x0f, 0xf8, 0x01, 0x3e, 0x66, 0x66, 0x66, 0x3c, 0x80, 0x19, 0x66, 0xe6, 0x7f, 0x0c, 0xc6, 0x30, 0x0c, 0xc0, 0x00, 0x0c, 0xc6, 0x00, 0x06, 0x00, 0x60, 0x60, 0x00, 0x06, 0x00, 0xc6, 0xcc, 0xc6, 0xcc, 0x60, 0x3c, 0x60, 0x63, 0xf0, 0xc0, 0x30, 0xfe, 0xc7, 0x60, 0x0c, 0xc6, 0x00, 0x98, 0xc1, 0x00, 0x66, 0x06, 0x60, 0x36, 0x66, 0x63, 0xec, 0x80, 0x19, 0x06, 0x66, 0x60, 0x0c, 0xc6, 0x30, 0x0c, 0xc0, 0x00, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x60, 0x60, 0x00, 0x06, 0x0c, 0xc6, 0xcc, 0xc6, 0xcc, 0x60, 0xcc, 0xe1, 0x61, 0x60, 0xc0, 0x30, 0x06, 0xc6, 0x60, 0x0c, 0xc6, 0x00, 0x98, 0xc1, 0x00, 0x66, 0xc6, 0x60, 0x1e, 0xe6, 0x61, 0x8c, 0x81, 0x19, 0x06, 0x66, 0x60, 0x0c, 0xc6, 0x30, 0xfc, 0xc7, 0x7f, 0x0c, 0xc3, 0x00, 0xf8, 0x83, 0x3f, 0xfc, 0x83, 0x1f, 0xf8, 0xe3, 0x7c, 0xc6, 0xc7, 0x60, 0x0c, 0xe7, 0x60, 0x60, 0xc0, 0x3f, 0x06, 0xc6, 0x3f, 0xfc, 0xc3, 0x00, 0xfe, 0xc7, 0x7f, 0x66, 0x86, 0x3f, 0x0e, 0xe6, 0x60, 0x0c, 0xe7, 0x19, 0x06, 0x66, 0x60, 0xf8, 0xc3, 0x30, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x3f, 0xfc, 0xc3, 0x60, 0x60, 0xc0, 0x30, 0x8c, 0xc1, 0x60, 0x66, 0x66, 0x1b, 0x0e, 0x60, 0x60, 0x0c, 0xc0, 0x1f, 0xcc, 0x83, 0x7f, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x86, 0x61, 0x60, 0xc0, 0x60, 0xfc, 0x83, 0x19, 0x8c, 0xc1, 0x60, 0x66, 0x66, 0x1b, 0x0c, 0x60, 0x60, 0x0c, 0x60, 0x30, 0x6c, 0xc6, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc6, 0x00, 0x60, 0xc0, 0x60, 0x66, 0x06, 0x0f, 0x8c, 0xc1, 0x60, 0x66, 0x66, 0x1b, 0x0c, 0x60, 0x60, 0x0c, 0x00, 0x60, 0x6c, 0xc6, 0x60, 0xfc, 0x01, 0x1c, 0xfc, 0xc3, 0x1f, 0xf0, 0x81, 0x3f, 0x6c, 0x83, 0x3f, 0x0c, 0xc7, 0x70, 0x0c, 0x07, 0x3f, 0x0e, 0xc7, 0x60, 0xf8, 0xc3, 0x3f, 0xfc, 0xc3, 0x00, 0x60, 0x80, 0x7f, 0x66, 0x06, 0x06, 0x8c, 0x81, 0x7f, 0x66, 0x66, 0x1b, 0xfc, 0xe3, 0x67, 0xfc, 0x03, 0x3f, 0x7c, 0x86, 0x7f, 0x00, 0x83, 0x3f, 0x0c, 0xc6, 0x00, 0x98, 0xc1, 0x60, 0x6c, 0xc3, 0x60, 0x8c, 0xc6, 0x68, 0x8c, 0x81, 0x31, 0x9e, 0xc7, 0x60, 0x0c, 0xc6, 0x30, 0x0c, 0xc0, 0x00, 0x60, 0x00, 0x60, 0x66, 0x06, 0x0f, 0x8c, 0x01, 0x60, 0x66, 0x66, 0x1b, 0x0c, 0x66, 0x6c, 0x0c, 0x06, 0x60, 0x6c, 0x06, 0x6c, 0xfc, 0xc3, 0x60, 0xfc, 0xc3, 0x00, 0x98, 0xc1, 0x7f, 0xf0, 0x00, 0x3c, 0x4c, 0xc6, 0x64, 0x7c, 0x80, 0x31, 0xf6, 0xc6, 0x7f, 0x0c, 0xc6, 0x30, 0x0c, 0x80, 0x61, 0x60, 0x00, 0x60, 0x66, 0x86, 0x19, 0x8c, 0x01, 0x60, 0x66, 0x66, 0x1b, 0x0c, 0x66, 0x6c, 0x0c, 0x66, 0x30, 0x6c, 0x06, 0x66, 0x06, 0xc3, 0x60, 0x0c, 0xc6, 0x00, 0x98, 0xc1, 0x00, 0x6c, 0xc3, 0x60, 0x2c, 0xc6, 0x62, 0x8c, 0x81, 0x31, 0x66, 0xc6, 0x60, 0x0c, 0xc6, 0x30, 0x0c, 0x00, 0x3f, 0x60, 0xc0, 0x7f, 0xfc, 0xc3, 0x30, 0xfc, 0x07, 0x60, 0xfe, 0xe7, 0x7f, 0xfc, 0xe7, 0x67, 0xfc, 0xc7, 0x1f, 0xcc, 0xc3, 0x63, 0xfc, 0x87, 0x3f, 0xfc, 0xc3, 0x00, 0xfe, 0x87, 0x3f, 0x6c, 0x83, 0x3f, 0x1c, 0xc6, 0x61, 0x0c, 0xe7, 0x31, 0x06, 0xc6, 0x60, 0xf8, 0xc3, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x80, 0x31, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x06, 0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x80, 0x01, 0x60, 0x00, 0x0c, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x83, 0x3f, 0xfc, 0xc3, 0x60, 0xf8, 0x81, 0x19, 0x8c, 0xc1, 0x60, 0x66, 0x66, 0x1b, 0x0e, 0xc0, 0x60, 0x0c, 0xc0, 0x0f, 0xcc, 0x83, 0x7f, 0xf8, 0x83, 0x3f, 0x0c, 0xc0, 0x7f, 0xf0, 0x83, 0x3f, 0x70, 0x00, 0x07, 0x80, 0x87, 0x0f, 0xc6, 0x80, 0x01, 0x0c, 0xc7, 0x70, 0x0c, 0xc6, 0x30, 0x1c, 0xc6, 0x60, 0x60, 0x80, 0x31, 0x6c, 0x03, 0x0f, 0x8c, 0xc1, 0x60, 0x66, 0x66, 0x1b, 0x0c, 0xc0, 0x60, 0x0c, 0x60, 0x30, 0x6c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xec, 0xc1, 0x00, 0x0c, 0xc6, 0x00, 0x60, 0x00, 0x06, 0x00, 0xc6, 0x0c, 0xc6, 0x80, 0x3d, 0x8c, 0xc1, 0x68, 0x18, 0xc3, 0x30, 0x0c, 0xc6, 0x00, 0x60, 0x00, 0x1b, 0x6c, 0x03, 0x06, 0x8c, 0x81, 0x7f, 0x66, 0x66, 0x1b, 0xfc, 0xc3, 0x67, 0xfc, 0x03, 0x3e, 0x7c, 0x86, 0x7f, 0xfc, 0xc7, 0x7f, 0x1c, 0xc3, 0x00, 0x7c, 0x80, 0x3f, 0x60, 0x00, 0x06, 0x00, 0xc6, 0x7c, 0xfe, 0x87, 0x63, 0x7c, 0xc0, 0x64, 0xb0, 0xc1, 0x30, 0x1c, 0xc6, 0x00, 0x60, 0x00, 0x0e, 0x6c, 0x03, 0x0f, 0x8c, 0x01, 0x60, 0x66, 0x66, 0x1b, 0x0c, 0xc6, 0x6c, 0x0c, 0x66, 0x30, 0x6c, 0x06, 0x6c, 0x0c, 0xc0, 0x00, 0x0c, 0xc3, 0x00, 0x0c, 0x06, 0x60, 0x60, 0x00, 0x06, 0x00, 0xc6, 0xcc, 0xc6, 0x8c, 0x61, 0x8c, 0xc1, 0x62, 0xe0, 0xc0, 0x30, 0xec, 0x83, 0x7f, 0x60, 0x00, 0x06, 0xf8, 0x81, 0x19, 0xfc, 0x07, 0x60, 0xfe, 0xe7, 0x7f, 0xfc, 0xc3, 0x67, 0xfc, 0xc3, 0x0f, 0xcc, 0x83, 0x63, 0xf8, 0x83, 0x3f, 0x8c, 0xc1, 0x00, 0xf0, 0x83, 0x3f, 0xf8, 0x81, 0x1f, 0x0c, 0xe6, 0x7c, 0xc6, 0x87, 0x61, 0x0c, 0xc7, 0x61, 0x60, 0xc0, 0x3f, 0x0c, 0x00, 0x00, 0x00, 0x80, 0x03, 0x60, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x06, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x03, 0x00, 0xf8, 0xe1, 0x7f, 0xfe, 0x03, 0x0f, 0xf0, 0xe1, 0x3f, 0x86, 0x03, 0x0f, 0xfc, 0xe3, 0x3f, 0xfe, 0xe3, 0x3f, 0xf6, 0x00, 0x38, 0xe0, 0xe1, 0x1f, 0x9c, 0xe3, 0x3f, 0xfe, 0xe3, 0x38, 0x8c, 0xe3, 0x3f, 0xfe, 0x63, 0x66, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xe3, 0x1f, 0x80, 0x01, 0x18, 0x00, 0x03, 0x0c, 0x60, 0x60, 0x30, 0xc6, 0x03, 0x0c, 0x00, 0x03, 0x30, 0x00, 0x63, 0x30, 0x9c, 0x01, 0x30, 0x80, 0xc1, 0x30, 0x18, 0x63, 0x30, 0x06, 0xc3, 0x30, 0x18, 0x03, 0x30, 0x00, 0x63, 0x66, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x03, 0x18, 0x80, 0x01, 0x18, 0x00, 0x03, 0x0c, 0x60, 0x60, 0x30, 0x06, 0x03, 0x0c, 0x00, 0x03, 0x30, 0x00, 0x63, 0x30, 0x0e, 0x03, 0x30, 0x80, 0x61, 0x30, 0x18, 0x63, 0x30, 0x06, 0x83, 0x31, 0x30, 0xc3, 0x30, 0x00, 0x63, 0x66, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x03, 0x18, 0xe0, 0x01, 0x18, 0x06, 0x03, 0x0c, 0x60, 0x60, 0x30, 0x06, 0x03, 0x0c, 0x00, 0x03, 0x30, 0x00, 0x63, 0x30, 0x06, 0x03, 0x30, 0x80, 0x61, 0x30, 0x18, 0xe3, 0x31, 0x1e, 0x83, 0x39, 0xe0, 0xc1, 0x30, 0x00, 0x63, 0x66, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x01, 0x18, 0xb0, 0x01, 0x18, 0x06, 0x03, 0x0c, 0x60, 0x60, 0x30, 0x06, 0x03, 0x00, 0x00, 0x03, 0x30, 0x00, 0x63, 0x30, 0x06, 0x03, 0x30, 0x80, 0x61, 0x30, 0x18, 0x03, 0x30, 0x00, 0x83, 0x1f, 0xc0, 0xc0, 0x30, 0x00, 0x63, 0x66, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x18, 0x98, 0x01, 0x18, 0x06, 0x03, 0x0c, 0x60, 0x60, 0x30, 0x06, 0x03, 0x00, 0x00, 0x03, 0x30, 0x80, 0x61, 0x30, 0x06, 0x03, 0x30, 0x80, 0xc1, 0x18, 0x98, 0x01, 0x30, 0x00, 0x83, 0x01, 0x80, 0xc1, 0x18, 0x00, 0x63, 0x66, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xe3, 0x7f, 0x98, 0x01, 0x18, 0x06, 0x03, 0x0c, 0x60, 0x60, 0x30, 0xfe, 0x03, 0x00, 0x00, 0xe3, 0x1f, 0xe0, 0xe0, 0x3f, 0xe6, 0x03, 0x30, 0xf0, 0x81, 0x0f, 0xfe, 0x00, 0x30, 0xfe, 0x83, 0x01, 0xfc, 0xc3, 0x0e, 0x00, 0xc3, 0x3f, 0x1e, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x01, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x01, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xec, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xc3, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x80, 0x19, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x83, 0x01, 0x18, 0x03, 0x00, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x30, 0x80, 0x19, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x66, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x98, 0x01, 0x00, 0x00, 0x80, 0x01, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0x60, 0x00, 0x06, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0xfc, 0xc0, 0x0f, 0xfc, 0x00, 0x18, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x80, 0x87, 0x7d, 0xd8, 0x07, 0x1f, 0xf0, 0x01, 0x00, 0xfc, 0xc0, 0x0f, 0xfc, 0xe0, 0x7f, 0x18, 0x03, 0x07, 0x60, 0x30, 0xc0, 0x60, 0x30, 0xc0, 0x60, 0x00, 0x06, 0x60, 0xc0, 0x30, 0x60, 0x00, 0x06, 0x86, 0x61, 0x18, 0x86, 0x01, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x60, 0x8c, 0xc7, 0x78, 0xcc, 0x31, 0x1c, 0x03, 0x06, 0x80, 0x03, 0x38, 0x80, 0x83, 0x19, 0x8c, 0x87, 0x01, 0xf0, 0x30, 0xc0, 0xf0, 0x30, 0xc0, 0x60, 0x00, 0x06, 0xc0, 0x60, 0x60, 0xc0, 0x00, 0x0c, 0x00, 0x03, 0x30, 0x00, 0x03, 0x60, 0x00, 0x06, 0x70, 0x00, 0x37, 0xce, 0xe3, 0x3c, 0xc3, 0x33, 0xcc, 0xc3, 0x3c, 0xcc, 0x00, 0x0c, 0x00, 0x0f, 0xe0, 0x01, 0x1e, 0xe0, 0x81, 0x19, 0xf8, 0xcd, 0x07, 0x9f, 0xef, 0x7f, 0x9f, 0xef, 0x7f, 0xc0, 0x0f, 0x06, 0x7f, 0x80, 0x1f, 0x7f, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xf0, 0x0f, 0xf0, 0x00, 0xef, 0x7b, 0xbe, 0xe7, 0x7f, 0xfe, 0xf7, 0x7f, 0xff, 0xf7, 0x7f, 0xff, 0xf7, 0xf9, 0x3f, 0xff, 0xf3, 0x3f, 0xef, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x1f, 0xfc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x03, 0xf8, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x83, 0x19, 0xc0, 0x06, 0xc0, 0x60, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xe6, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x60, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0xfe, 0xe0, 0x0f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x60, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x30, 0xe6, 0x7f, 0x00, 0x00, 0x1e, 0xe0, 0x81, 0x03, 0x60, 0x00, 0x00, 0x00, 0xe0, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x63, 0x3c, 0xc6, 0x03, 0x00, 0x00, 0xc0, 0x1f, 0xfc, 0x01, 0x0f, 0x80, 0x07, 0x0f, 0x80, 0x87, 0xc1, 0x60, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x60, 0xe6, 0x7f, 0x00, 0x00, 0x33, 0x30, 0x03, 0x1e, 0x60, 0x00, 0x1f, 0x60, 0xe0, 0x36, 0xc0, 0x07, 0x00, 0x60, 0x30, 0xc0, 0xf0, 0x0f, 0xff, 0xf0, 0x0f, 0x06, 0xe0, 0x81, 0x0d, 0xd8, 0x80, 0x19, 0xc0, 0x8c, 0x19, 0xc0, 0xcc, 0xc7, 0x60, 0x40, 0xc0, 0xf8, 0x01, 0x78, 0x60, 0x00, 0x40, 0xc0, 0xe6, 0x7f, 0x00, 0x00, 0x3e, 0xe0, 0x03, 0x78, 0x60, 0xc0, 0x31, 0xc0, 0xc0, 0x63, 0x60, 0x0c, 0x78, 0xc0, 0x30, 0xc0, 0x3c, 0xc8, 0x83, 0x3c, 0x08, 0x0f, 0x32, 0x00, 0x07, 0x70, 0x00, 0x0f, 0x83, 0x0f, 0x0f, 0x82, 0x1f, 0xc0, 0x60, 0x60, 0xc0, 0x9c, 0x03, 0xcc, 0xf0, 0x40, 0xc0, 0x80, 0xe3, 0x7f, 0xff, 0xff, 0x0f, 0xff, 0xf0, 0xff, 0x3f, 0x70, 0x3f, 0x7f, 0xf0, 0x3e, 0xc0, 0x2f, 0xcc, 0x7f, 0xe0, 0x7f, 0x06, 0x60, 0x00, 0x06, 0xf0, 0xf9, 0x63, 0xf0, 0xfd, 0xdf, 0xff, 0xf9, 0x01, 0xfc, 0xf9, 0x03, 0xfc, 0xff, 0xbf, 0x6f, 0xc0, 0xf7, 0x0e, 0x7f, 0x9f, 0x6f, 0xc0, 0x60, 0xe6, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x18, 0x00, 0x00, 0x00, 0xc6, 0x60, 0x00, 0x06, 0x00, 0x00, 0xc3, 0x01, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x06, 0x06, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x03, 0x00, 0xc0, 0x60, 0xf0, 0xe7, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x30, 0x66, 0x00, 0x00, 0x06, 0x6c, 0xc0, 0x06, 0x6c, 0x06, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x80, 0x3f, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xe1, 0x1f, 0x00, 0x00, 0x00, 0xfc, 0xc7, 0x7f, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x07, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x03, 0x80, 0x01, 0x06, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xc0, 0xf1, 0x00, 0x00, 0x06, 0xfc, 0x61, 0x66, 0xf8, 0x00, 0x00, 0xfc, 0x63, 0x60, 0x60, 0x80, 0x0f, 0xc0, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x80, 0x0f, 0xc0, 0x07, 0x06, 0x7c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x73, 0x1c, 0x00, 0x00, 0x06, 0x0e, 0xe0, 0x7f, 0x0c, 0x00, 0x00, 0x00, 0xc3, 0x30, 0xf0, 0xc0, 0x18, 0x00, 0x00, 0x1e, 0x8c, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xf8, 0xc0, 0x07, 0xc6, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x70, 0x00, 0x06, 0x06, 0x60, 0x00, 0x18, 0x80, 0x1f, 0x00, 0xc3, 0x30, 0x98, 0x81, 0x1f, 0xf8, 0x03, 0x03, 0xd8, 0x03, 0x1f, 0xf0, 0x83, 0x1f, 0x00, 0x00, 0x03, 0x60, 0x00, 0x06, 0xc0, 0x07, 0x06, 0x60, 0x00, 0x78, 0x8c, 0x61, 0x0c, 0xf0, 0x0f, 0x06, 0x60, 0x30, 0xc0, 0x00, 0x00, 0x1c, 0x70, 0x00, 0x06, 0x06, 0x60, 0x00, 0x70, 0xc0, 0x30, 0x00, 0x83, 0x19, 0x98, 0x01, 0x18, 0xf0, 0x01, 0x03, 0x70, 0x83, 0x31, 0x18, 0xc3, 0x30, 0x00, 0x00, 0x3e, 0x60, 0x00, 0x06, 0x60, 0x0c, 0x06, 0xc0, 0x30, 0xcc, 0x00, 0x03, 0x38, 0x3c, 0x08, 0x0c, 0xf0, 0x30, 0xc0, 0x00, 0x07, 0x70, 0x70, 0x00, 0x06, 0x0c, 0x60, 0x00, 0x30, 0x60, 0x60, 0x00, 0x83, 0x19, 0x0c, 0x03, 0x18, 0x60, 0x07, 0x3e, 0x30, 0x83, 0x19, 0x8c, 0x67, 0x60, 0x00, 0x00, 0x03, 0x60, 0x00, 0x06, 0xc0, 0x0f, 0x00, 0x7f, 0x30, 0x98, 0xff, 0xf3, 0xff, 0x86, 0xf2, 0x07, 0x9f, 0xef, 0x7f, 0x00, 0xff, 0xff, 0x00, 0x00, 0x06, 0x18, 0x60, 0x00, 0x18, 0xc0, 0x30, 0x00, 0x03, 0x0f, 0x0c, 0x03, 0x18, 0x30, 0x0c, 0x03, 0x18, 0xf6, 0xff, 0xf8, 0xcd, 0x3f, 0x00, 0x80, 0x01, 0x60, 0x00, 0x06, 0x00, 0x06, 0x0e, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0x00, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x30, 0x60, 0x00, 0xf8, 0x83, 0x1f, 0x00, 0x03, 0x06, 0x06, 0x06, 0x18, 0x18, 0x80, 0x01, 0x0e, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x0c, 0xc3, 0x30, 0x06, 0x6c, 0x18, 0x0c, 0xc3, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x80, 0x01, 0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xf8, 0x81, 0x0f, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0xfc, 0x07, 0x03, 0x60, 0x00, 0x06, 0xfc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x7f, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0xfd, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0xfd, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0xf0, 0x03, 0xc0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0x3f, 0xf0, 0x03, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0xf0, 0x01, 0xc0, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0x1f, 0xf0, 0x01, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xc0, 0x07, 0x7c, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xe0, 0x0f, 0xc0, 0xc0, 0x0f, 0x80, 0xc0, 0x0f, 0x00, 0xfe, 0x1f, 0x80, 0x0c, 0x30, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7f, 0x30, 0x00, 0x3f, 0x10, 0x00, 0x3f, 0x00, 0x00, 0xff, 0xf7, 0xff, 0x00, 0xf3, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x30, 0x00, 0xf8, 0x0f, 0xfc, 0xe0, 0x0f, 0xe0, 0xe0, 0x0f, 0x00, 0xfc, 0x3f, 0xc0, 0x0c, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xc0, 0xff, 0xf1, 0x03, 0x7f, 0x70, 0x00, 0x7f, 0x00, 0x00, 0xff, 0xf3, 0xff, 0x00, 0xf3, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x70, 0x00, 0xfe, 0x8f, 0xff, 0xf0, 0x0f, 0xf8, 0xe0, 0x0f, 0x00, 0xf8, 0x7f, 0xe0, 0x0c, 0x30, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xe0, 0xff, 0xf7, 0x1f, 0xff, 0xf0, 0x01, 0x7f, 0x00, 0x00, 0xff, 0xf1, 0xff, 0x00, 0xf3, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xfc, 0xf0, 0x0f, 0x80, 0xf0, 0xff, 0xf0, 0x0c, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x07, 0xff, 0x10, 0x00, 0xff, 0xf0, 0xff, 0x00, 0xf3, 0xff, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x07, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xf8, 0x0f, 0xf0, 0xe0, 0xff, 0xf9, 0x0c, 0x30, 0x33, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x0e, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x1f, 0xff, 0xf1, 0x00, 0x7f, 0xf0, 0xff, 0x00, 0xf3, 0xff, 0x00, 0x00, 0x00, 0x03, 0x70, 0x00, 0x07, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xfe, 0xcf, 0xff, 0xf8, 0x0f, 0xff, 0xe0, 0xff, 0xff, 0x0c, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xe0, 0x00, 0x0e, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x7f, 0xff, 0xf1, 0x0f, 0x7f, 0xf0, 0xf9, 0x00, 0xf3, 0xff, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x01, 0x0f, 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xef, 0xff, 0xf0, 0xff, 0xff, 0x0c, 0x30, 0x33, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x7f, 0xff, 0xf0, 0xf0, 0x00, 0xf3, 0xff, 0x03, 0x70, 0x00, 0x0f, 0xf0, 0x07, 0x0f, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xf8, 0xff, 0xff, 0x0c, 0xc0, 0xcc, 0x00, 0x08, 0xe0, 0x00, 0x0f, 0xfe, 0x00, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0x71, 0xe0, 0x00, 0xf3, 0xff, 0x07, 0xf0, 0x03, 0x1f, 0xf0, 0x1f, 0x1f, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x0c, 0x30, 0x33, 0x00, 0x0e, 0xfc, 0x80, 0x8f, 0xff, 0x80, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0x33, 0xc0, 0x00, 0xf3, 0xff, 0x1f, 0xf0, 0x3f, 0x3f, 0xf0, 0x7f, 0x3f, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x0c, 0xc0, 0xcc, 0x80, 0xcf, 0xff, 0xc0, 0xef, 0xff, 0xc0, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x17, 0x80, 0x00, 0xf3, 0xff, 0x60, 0x00, 0x00, 0x60, 0x00, 0x06, 0x70, 0x00, 0x0e, 0x00, 0x00, 0x0f, 0x70, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x06, 0x18, 0x00, 0x18, 0x00, 0x80, 0x19, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0xf8, 0x81, 0x1f, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x06, 0x0c, 0x00, 0x30, 0x00, 0xc0, 0x30, 0x0c, 0x00, 0x30, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0xfe, 0xe7, 0x70, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x06, 0x60, 0x00, 0x03, 0xc0, 0x00, 0x0f, 0x60, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xe0, 0xff, 0x07, 0x06, 0x00, 0x60, 0x00, 0x60, 0x60, 0x06, 0x00, 0x60, 0x00, 0x00, 0x00, 0xff, 0x0f, 0x0f, 0xff, 0x3f, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x06, 0x60, 0x00, 0x0f, 0xf0, 0x80, 0x1f, 0x60, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xe0, 0xff, 0x07, 0x03, 0x00, 0xc0, 0x01, 0x38, 0xc0, 0x03, 0x00, 0xc0, 0x01, 0x00, 0x80, 0xff, 0x8f, 0x1f, 0xff, 0x3f, 0xc0, 0x60, 0xf0, 0xff, 0xe0, 0xff, 0x07, 0xe0, 0xff, 0x07, 0xe0, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0xcf, 0x3f, 0x60, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xe0, 0xff, 0x07, 0x03, 0x00, 0xc0, 0x03, 0x1c, 0x80, 0x01, 0x00, 0x80, 0x03, 0x00, 0xc0, 0xff, 0x8f, 0x1f, 0xff, 0x3f, 0xc0, 0x60, 0xf0, 0xff, 0xe0, 0xff, 0x07, 0xe0, 0xff, 0x07, 0xe0, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x0f, 0x06, 0x60, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xe0, 0xff, 0x07, 0x06, 0x00, 0x60, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x60, 0xff, 0x0f, 0x0f, 0xff, 0x3f, 0xc0, 0x60, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x00, 0x60, 0x00, 0x0f, 0xf0, 0x00, 0x06, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0x00, 0x30, 0x0c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x30, 0x60, 0x00, 0x00, 0xfe, 0xe7, 0x70, 0x60, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x00, 0x60, 0x00, 0x03, 0xc0, 0x00, 0x06, 0xf8, 0x01, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x06, 0x18, 0x00, 0x18, 0x98, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x60, 0x00, 0x00, 0xf8, 0x81, 0x1f, 0x60, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x06, 0x70, 0x00, 0x0e, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x0e, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0x1f, 0xf0, 0x3f, 0x3f, 0xf0, 0x7f, 0x3f, 0xf0, 0xff, 0x01, 0xe0, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x80, 0xcf, 0xff, 0xc0, 0xef, 0xff, 0xc0, 0xff, 0xff, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xcf, 0xff, 0x07, 0xf0, 0x03, 0x1f, 0xf0, 0x1f, 0x1f, 0xf0, 0xff, 0x03, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x3f, 0x00, 0x0e, 0xfc, 0x80, 0x8f, 0xff, 0x80, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x8f, 0xff, 0x01, 0x70, 0x00, 0x0f, 0xf0, 0x07, 0x1f, 0xf0, 0xff, 0x07, 0x80, 0x1f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x1f, 0x00, 0x08, 0xe0, 0x00, 0x0f, 0xfe, 0x80, 0xff, 0xff, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x03, 0x0f, 0xf0, 0xff, 0x0f, 0x00, 0x0f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xf8, 0x00, 0xef, 0xff, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xef, 0xff, 0xf8, 0x0f, 0xfe, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x07, 0xf0, 0x0f, 0x1f, 0x00, 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x7f, 0xff, 0xf1, 0x07, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xe0, 0x00, 0x0e, 0xff, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x8f, 0xff, 0xf8, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x01, 0x30, 0x00, 0x07, 0xf0, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x1f, 0xff, 0xf1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x0e, 0xf0, 0x80, 0x0f, 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xfe, 0xf0, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x07, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x80, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0xfc, 0x8f, 0xff, 0xf0, 0x0f, 0xf8, 0xf0, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf7, 0x1f, 0xff, 0xf0, 0x01, 0xff, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x8e, 0x1f, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0xfc, 0xe0, 0x0f, 0xe0, 0xe0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf1, 0x03, 0x7f, 0x70, 0x00, 0x7f, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0xcc, 0x3f, 0x00, 0x00, 0x00, 0xe0, 0x0f, 0xc0, 0xc0, 0x0f, 0x80, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x30, 0x00, 0x3f, 0x10, 0x00, 0x3f, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x38, 0x07, 0x33, 0xf8, 0xc7, 0x61, 0xf0, 0x00, 0x1c, 0xc0, 0x01, 0x0e, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xf0, 0x07, 0x18, 0xf0, 0x87, 0xff, 0x80, 0x81, 0xff, 0xc0, 0x80, 0xff, 0xf0, 0x07, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x1c, 0x00, 0x3f, 0x00, 0x00, 0x0c, 0x30, 0x06, 0x33, 0xcc, 0x6c, 0x33, 0x98, 0x01, 0x18, 0x60, 0x00, 0x18, 0xcc, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x0c, 0x1e, 0x18, 0x0c, 0x60, 0xc0, 0x80, 0x01, 0x60, 0x00, 0xc0, 0x18, 0x8c, 0xc1, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x70, 0x80, 0x61, 0x00, 0x00, 0x06, 0x8c, 0xe1, 0x7f, 0x66, 0xe0, 0x0c, 0xcc, 0x00, 0x06, 0x18, 0x00, 0x18, 0x6c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x06, 0x0c, 0x00, 0x06, 0x18, 0x30, 0xc0, 0x3f, 0x18, 0x00, 0x30, 0x0c, 0xc6, 0x60, 0x30, 0x00, 0x03, 0x70, 0xc0, 0x3f, 0xe0, 0x00, 0x30, 0x00, 0x00, 0x06, 0x00, 0x80, 0x19, 0xfc, 0x03, 0x06, 0x78, 0x00, 0x00, 0x18, 0x00, 0x18, 0xf0, 0xc0, 0x3f, 0x00, 0xc0, 0x3f, 0x00, 0x00, 0x06, 0x0c, 0x06, 0x0c, 0x80, 0x03, 0x3c, 0x18, 0x03, 0x60, 0xfc, 0x03, 0x18, 0xf8, 0x83, 0x3f, 0x30, 0x00, 0x03, 0x1c, 0x00, 0x00, 0x80, 0x03, 0x18, 0x00, 0x00, 0x06, 0x00, 0xe0, 0x7f, 0x60, 0x06, 0x73, 0xcc, 0x0c, 0x00, 0x18, 0x00, 0x18, 0x6c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x06, 0x0c, 0xe0, 0x00, 0x60, 0x0c, 0x03, 0x60, 0x0c, 0x06, 0x0c, 0x0c, 0x06, 0x18, 0x00, 0x00, 0x00, 0x70, 0xc0, 0x3f, 0xe0, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0c, 0x33, 0xc3, 0x6c, 0xc3, 0x01, 0x00, 0x18, 0x00, 0x06, 0x33, 0x03, 0x03, 0x0e, 0x00, 0x00, 0x06, 0xc0, 0x00, 0x06, 0x03, 0x06, 0x1c, 0x00, 0x30, 0xfe, 0x63, 0x30, 0x06, 0x03, 0x03, 0x06, 0x03, 0x06, 0x18, 0x80, 0x01, 0xe0, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xc0, 0x0c, 0xfe, 0x61, 0x38, 0x7e, 0x06, 0x00, 0x70, 0x80, 0x03, 0x30, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x06, 0x60, 0x00, 0xfc, 0x01, 0x06, 0xfe, 0xe3, 0x1f, 0x80, 0xc1, 0x1f, 0xfc, 0x81, 0x01, 0xfc, 0x01, 0x03, 0x18, 0x80, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x1e, 0xf8, 0x07, 0x7e, 0xf8, 0x83, 0xff, 0xf8, 0x0f, 0x7f, 0x18, 0x8c, 0x7f, 0x00, 0x8c, 0xe1, 0x18, 0xc0, 0xe1, 0x1c, 0x0c, 0x7f, 0xf8, 0x07, 0x7f, 0xf8, 0x07, 0x7f, 0xfc, 0x8f, 0xc1, 0x0c, 0xcc, 0xc0, 0x18, 0xc6, 0xc0, 0xf8, 0x07, 0x0c, 0x18, 0x00, 0x0c, 0xe0, 0x00, 0x00, 0x18, 0x0c, 0x33, 0x18, 0x0c, 0xc3, 0x18, 0x86, 0x01, 0x18, 0x80, 0xc1, 0x18, 0x0c, 0x0c, 0x00, 0x8c, 0x39, 0x18, 0xc0, 0xf3, 0x3c, 0x8c, 0xc1, 0x18, 0x8c, 0xc1, 0x18, 0x8c, 0xc1, 0xc0, 0x80, 0xc1, 0x0c, 0xcc, 0xc0, 0x30, 0x83, 0x61, 0x00, 0x03, 0x0c, 0x30, 0x00, 0x0c, 0xb0, 0x01, 0x00, 0xc6, 0xc7, 0x30, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0x06, 0x06, 0x00, 0xc6, 0x0e, 0x0c, 0x60, 0x6f, 0x36, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x00, 0x60, 0xc0, 0x60, 0x0c, 0x63, 0x66, 0xf0, 0x80, 0x19, 0xc0, 0x00, 0x06, 0x30, 0x00, 0x06, 0x8c, 0x01, 0x00, 0x66, 0x66, 0x60, 0xfc, 0xc3, 0x00, 0x0c, 0xc6, 0x1f, 0xfc, 0xc1, 0x78, 0xfc, 0x07, 0x06, 0x00, 0xc6, 0x03, 0x0c, 0x60, 0x66, 0x66, 0xc6, 0x60, 0xfc, 0xc3, 0x60, 0xfc, 0x83, 0x3f, 0x60, 0xc0, 0x60, 0x0c, 0x63, 0x66, 0x60, 0x00, 0x0f, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x66, 0xe6, 0x7f, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x00, 0x0c, 0xc0, 0x60, 0x0c, 0x06, 0x06, 0x00, 0xc6, 0x0e, 0x0c, 0x60, 0x60, 0xc6, 0xc6, 0x60, 0x0c, 0xc0, 0x60, 0x8c, 0x01, 0x60, 0x60, 0xc0, 0x60, 0x98, 0x61, 0x6f, 0xf0, 0x00, 0x06, 0x30, 0x00, 0x06, 0xc0, 0x00, 0x06, 0x00, 0x00, 0x00, 0xe3, 0x33, 0x30, 0x06, 0xc3, 0x30, 0x86, 0x61, 0x00, 0x06, 0x60, 0x30, 0x06, 0x03, 0x03, 0x06, 0x63, 0x0e, 0x06, 0x30, 0x30, 0xc3, 0x63, 0x30, 0x06, 0x60, 0x36, 0x86, 0x61, 0x30, 0x30, 0x60, 0x30, 0x78, 0xe0, 0x1c, 0xcc, 0x00, 0x03, 0x0c, 0x00, 0x03, 0xc0, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x30, 0x30, 0xfe, 0x81, 0x1f, 0xfe, 0xe0, 0x3f, 0x06, 0xc0, 0x1f, 0x06, 0xe3, 0x1f, 0xfc, 0x61, 0x38, 0xfe, 0x33, 0x30, 0x83, 0xc3, 0x1f, 0x06, 0xc0, 0x1f, 0x06, 0xc3, 0x1f, 0x30, 0xc0, 0x1f, 0x30, 0x60, 0x18, 0x86, 0x01, 0x03, 0xfe, 0x01, 0x03, 0x80, 0x01, 0x03, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xc0, 0x03, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 0xc0, 0x03, 0x00, 0x18, 0x00, 0x0c, 0x00, 0x8c, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xf0, 0x0c, 0x00, 0x80, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x60, 0x06, 0x00, 0x18, 0x00, 0x00, 0x00, 0x80, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0x98, 0x07, 0x00, 0x80, 0xc1, 0x1f, 0xec, 0x83, 0x3f, 0xf8, 0x86, 0x3f, 0x30, 0x80, 0x6f, 0xec, 0x03, 0x07, 0x80, 0xc7, 0x70, 0x60, 0xe0, 0x3b, 0xec, 0x83, 0x3f, 0xec, 0x83, 0x6f, 0xec, 0x83, 0x3f, 0xfc, 0xc1, 0x60, 0x06, 0x66, 0x60, 0x0e, 0xc7, 0x60, 0xfc, 0x07, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x1c, 0xc6, 0x60, 0x0c, 0xc7, 0x60, 0xfc, 0xc0, 0x70, 0x1c, 0x06, 0x06, 0x00, 0xc6, 0x18, 0x60, 0x60, 0x66, 0x1c, 0xc6, 0x60, 0x1c, 0xc6, 0x70, 0x1c, 0xc6, 0x00, 0x30, 0xc0, 0x60, 0x0c, 0x63, 0x66, 0x98, 0x81, 0x31, 0x80, 0x01, 0x03, 0x60, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x3f, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x7f, 0x30, 0xc0, 0x60, 0x0c, 0x06, 0x06, 0x00, 0xc6, 0x07, 0x60, 0x60, 0x66, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x80, 0x3f, 0x30, 0xc0, 0x60, 0x98, 0x61, 0x66, 0xf0, 0x00, 0x1b, 0xe0, 0x00, 0x06, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x06, 0x63, 0x00, 0x06, 0x63, 0x00, 0x18, 0xc0, 0x3f, 0x06, 0x03, 0x03, 0x00, 0x63, 0x0c, 0x30, 0x30, 0x33, 0x06, 0x63, 0x30, 0x0e, 0x63, 0x38, 0x06, 0x00, 0x30, 0x18, 0x63, 0x38, 0x78, 0x60, 0x1b, 0xcc, 0x00, 0x07, 0x18, 0x00, 0x03, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x3f, 0xfe, 0xc1, 0x3f, 0xfc, 0xc3, 0x1f, 0x18, 0x00, 0x30, 0x06, 0xc3, 0x0f, 0x06, 0x63, 0x38, 0xfc, 0x30, 0x33, 0x06, 0xc3, 0x1f, 0xf6, 0xc1, 0x37, 0x06, 0xc0, 0x1f, 0xf0, 0xc1, 0x37, 0x30, 0xc0, 0x0c, 0x87, 0x03, 0x03, 0xfe, 0x03, 0x03, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x0e, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x30, 0x06, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x00, 0x7e, 0x00, 0x80, 0x61, 0xc0, 0x80, 0x61, 0x00, 0x80, 0x80, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00, 0xf0, 0x00, 0x0c, 0x3c, 0xc0, 0x03, 0x80, 0x01, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0x0c, 0xe0, 0x07, 0x00, 0x1c, 0xc4, 0x41, 0x3c, 0x04, 0x0c, 0x00, 0x00, 0x00, 0xc0, 0x00, 0xc3, 0x18, 0x06, 0x33, 0xc0, 0x80, 0x01, 0x00, 0x40, 0x9c, 0x00, 0x06, 0x33, 0x00, 0x00, 0x00, 0xe4, 0x0b, 0x00, 0x98, 0x01, 0x0c, 0x60, 0x00, 0x06, 0xc0, 0x00, 0x00, 0xcc, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x30, 0x8c, 0x19, 0x18, 0x82, 0x21, 0x60, 0x02, 0x00, 0x00, 0x00, 0x06, 0xfc, 0x03, 0x03, 0xf8, 0x01, 0x0f, 0x60, 0x80, 0x1f, 0x00, 0x20, 0x91, 0xf8, 0xc3, 0x0c, 0x00, 0x00, 0x00, 0x12, 0x0b, 0x00, 0x78, 0xc0, 0x3f, 0x18, 0xc0, 0x01, 0x00, 0x60, 0x18, 0x66, 0x06, 0x00, 0x00, 0x00, 0x06, 0x18, 0x86, 0x19, 0x8c, 0xc0, 0x08, 0x9c, 0x00, 0x06, 0x00, 0x00, 0x06, 0x66, 0xc6, 0x0f, 0x0c, 0x03, 0x06, 0x00, 0xc0, 0x30, 0x00, 0x20, 0x81, 0x0c, 0x63, 0x06, 0xfc, 0xc3, 0x3f, 0xf2, 0x09, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x00, 0x03, 0x00, 0x60, 0x18, 0x7c, 0x06, 0x06, 0x00, 0x00, 0x06, 0x18, 0x06, 0x33, 0x4c, 0xce, 0x7c, 0x70, 0x87, 0x01, 0x00, 0x00, 0x06, 0x66, 0x00, 0x03, 0x0c, 0x83, 0x1f, 0x60, 0x80, 0x1f, 0x00, 0x20, 0x91, 0xf8, 0xc7, 0x0c, 0x00, 0x03, 0x00, 0x12, 0x09, 0x00, 0x00, 0x00, 0x06, 0x7e, 0xe0, 0x01, 0x00, 0x60, 0x18, 0x60, 0x06, 0x00, 0x00, 0x00, 0x06, 0xf0, 0x83, 0x19, 0x2c, 0xcd, 0xc2, 0xbe, 0xc6, 0x00, 0x00, 0x00, 0x03, 0x33, 0xe0, 0x33, 0xfc, 0x00, 0x03, 0x30, 0x00, 0x18, 0x00, 0x10, 0x47, 0x00, 0xc0, 0x0c, 0x00, 0x00, 0x00, 0x89, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0c, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x48, 0x86, 0x30, 0x28, 0x63, 0x18, 0x00, 0x00, 0x03, 0xfe, 0x63, 0x1e, 0x86, 0x01, 0x03, 0x30, 0x60, 0x18, 0x00, 0x20, 0x20, 0xfe, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0xe0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x1b, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0xe4, 0x47, 0x18, 0xf4, 0xc3, 0x0f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x30, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x26, 0x7c, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x30, 0x03, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0xc0, 0x01, 0xcf, 0x30, 0x06, 0x3e, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x30, 0xc0, 0x01, 0x63, 0x60, 0x00, 0x18, 0xe0, 0x01, 0x33, 0x00, 0x00, 0xcf, 0x60, 0x00, 0x30, 0xc0, 0x01, 0xcf, 0x30, 0x06, 0x00, 0x00, 0x00, 0x0c, 0x80, 0x01, 0x1c, 0x30, 0x06, 0x18, 0x78, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x60, 0x83, 0x79, 0x00, 0x00, 0x63, 0xf0, 0x0f, 0x7e, 0x80, 0x01, 0x18, 0x60, 0x03, 0x00, 0xc0, 0x00, 0x0c, 0x30, 0x03, 0x00, 0xf8, 0x83, 0x79, 0xc0, 0x00, 0x18, 0x60, 0x83, 0x79, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0xc0, 0x00, 0x36, 0x00, 0xc0, 0xcc, 0x30, 0x00, 0x3e, 0xe0, 0x03, 0x3e, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x3e, 0xd8, 0x00, 0xc3, 0xf8, 0x8f, 0xff, 0xf8, 0x8f, 0xff, 0xf0, 0x03, 0x3f, 0x00, 0x00, 0x3f, 0x18, 0x86, 0xc3, 0xf0, 0x07, 0x7f, 0xf0, 0x07, 0x7f, 0xf0, 0x07, 0x00, 0xf0, 0x87, 0xc1, 0x18, 0x8c, 0xc1, 0x18, 0x8c, 0x61, 0xf0, 0x07, 0x63, 0x18, 0x83, 0x31, 0xf8, 0x83, 0x3f, 0x18, 0x83, 0x31, 0x66, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x60, 0x00, 0x06, 0xf8, 0x01, 0x06, 0x0c, 0xc6, 0x63, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x86, 0x19, 0x8c, 0xc7, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x86, 0x19, 0x18, 0xc6, 0x30, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xfe, 0xc3, 0x00, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x3e, 0xc6, 0x66, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x0f, 0xcc, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x0f, 0x18, 0xc6, 0x18, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0x66, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0xc6, 0x6c, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x06, 0x6c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x06, 0xf8, 0xc3, 0x60, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x33, 0xc0, 0x30, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x86, 0x61, 0x3c, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x06, 0x83, 0x07, 0x1e, 0x63, 0x30, 0x06, 0x63, 0x30, 0x06, 0x03, 0x03, 0x0c, 0x60, 0x30, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0xf3, 0x83, 0x1f, 0xfe, 0xe3, 0x3f, 0xfe, 0xe3, 0x3f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfe, 0x60, 0x38, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x0c, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xfc, 0x01, 0x03, 0x1e, 0x60, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x18, 0xe0, 0x00, 0x67, 0x30, 0x03, 0x1e, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0xc0, 0x01, 0x63, 0x60, 0x00, 0x18, 0xc0, 0x01, 0x33, 0x00, 0x03, 0xcf, 0x60, 0x00, 0x30, 0xc0, 0x01, 0xcf, 0x30, 0x06, 0x00, 0x00, 0x00, 0x0c, 0x80, 0x01, 0x1c, 0x30, 0x06, 0x30, 0x00, 0x80, 0x31, 0xc0, 0x00, 0x0c, 0xb0, 0x81, 0x39, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x60, 0x03, 0x00, 0xc0, 0x00, 0x0c, 0x60, 0x03, 0x00, 0xc0, 0x8f, 0x79, 0xc0, 0x00, 0x18, 0x60, 0x83, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xc0, 0x00, 0x36, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xbc, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0x70, 0x00, 0x07, 0x70, 0x00, 0x07, 0xf8, 0xc7, 0x3e, 0xf8, 0x83, 0x3f, 0xf8, 0x83, 0x3f, 0xf8, 0x03, 0x06, 0xfc, 0xc3, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xf8, 0x63, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x30, 0x60, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0xc6, 0x61, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x00, 0xc6, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x86, 0x31, 0x18, 0xc6, 0x18, 0xfc, 0xc3, 0x3f, 0xfc, 0xc3, 0x3f, 0xfc, 0xc3, 0x3f, 0xfc, 0xc7, 0x00, 0xfc, 0xc7, 0x7f, 0xfc, 0xc7, 0x7f, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x3f, 0x66, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0x06, 0x1b, 0x18, 0x86, 0x0d, 0x83, 0x31, 0x18, 0x83, 0x31, 0x18, 0x83, 0x31, 0x18, 0x33, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x06, 0x03, 0x00, 0x1b, 0x63, 0x38, 0x86, 0x63, 0x38, 0x86, 0x03, 0x07, 0x0c, 0x83, 0x03, 0xfe, 0xe3, 0x3f, 0xfe, 0xe3, 0x3f, 0xfe, 0xe3, 0x3f, 0xfe, 0xc1, 0x3f, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0x63, 0x30, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xfc, 0x01, 0x03, 0xfe, 0xc1, 0x37, 0x7c, 0xc3, 0x37, 0x7c, 0x03, 0x03, 0xfc, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x0c, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0xf0, 0x87, 0x3f, 0x30, 0x86, 0x31, 0x00, 0x00, 0x00, 0x00, 0x03, 0x18, 0x80, 0x03, 0x1c, 0x80, 0x01, 0x0c, 0x60, 0x03, 0x36, 0xb0, 0x01, 0x36, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x7f, 0x30, 0x06, 0x63, 0xc0, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x60, 0x03, 0x36, 0xc0, 0x01, 0x1c, 0x30, 0x06, 0x63, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x1f, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0xc0, 0x06, 0x36, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x1c, 0xe0, 0x00, 0xdc, 0xf8, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x1c, 0x60, 0x03, 0x36, 0xe0, 0x03, 0x3e, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0xe0, 0x07, 0x00, 0xe0, 0x07, 0x00, 0xe0, 0x07, 0x00, 0xe0, 0x07, 0x00, 0xf8, 0x03, 0xc0, 0x18, 0x06, 0xf8, 0xf8, 0x0f, 0x00, 0xf8, 0x0f, 0x00, 0xf8, 0x0f, 0x00, 0xf8, 0x0f, 0x00, 0xf8, 0x0f, 0x00, 0xf0, 0x07, 0x00, 0xf0, 0x07, 0x00, 0x18, 0xc3, 0x1f, 0xf8, 0xc3, 0x1f, 0x18, 0xc3, 0x1f, 0x18, 0x86, 0x3f, 0x18, 0x86, 0x3f, 0x18, 0x86, 0x3f, 0x18, 0x86, 0x3f, 0x0c, 0x83, 0x6f, 0x0c, 0x06, 0x60, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x86, 0x6f, 0x0c, 0x86, 0x6f, 0x0c, 0x06, 0x30, 0x0c, 0x06, 0x30, 0x0c, 0x06, 0x30, 0x0c, 0xc0, 0x60, 0x0c, 0xc0, 0x60, 0x0c, 0xc0, 0x60, 0x0c, 0xc0, 0x60, 0x0c, 0xc6, 0x70, 0x3e, 0x86, 0x7f, 0xfc, 0xc1, 0x60, 0xfc, 0xc1, 0x60, 0xfc, 0xc1, 0x60, 0xfc, 0xc1, 0x60, 0xfc, 0xc1, 0x60, 0x0c, 0xc0, 0x70, 0x0c, 0xc0, 0x70, 0xfc, 0xc7, 0x3f, 0xfc, 0xc7, 0x3f, 0xfc, 0xc7, 0x3f, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc0, 0x7f, 0x0c, 0xc0, 0x7f, 0x0c, 0xc0, 0x7f, 0x0c, 0xc0, 0x7f, 0x0c, 0xc0, 0x7f, 0x8c, 0xc7, 0x60, 0x8c, 0xc7, 0x60, 0x06, 0x33, 0x18, 0x06, 0x33, 0x18, 0x06, 0x33, 0x18, 0x0c, 0x63, 0x00, 0x0c, 0x63, 0x00, 0x0c, 0x63, 0x00, 0x0c, 0x63, 0x00, 0x86, 0x61, 0x30, 0x86, 0x61, 0x30, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0xc3, 0x3f, 0x06, 0xc3, 0x3f, 0x06, 0xe3, 0x3f, 0x06, 0xe3, 0x3f, 0x06, 0xe3, 0x3f, 0xf8, 0xc1, 0x3f, 0xf8, 0xc1, 0x3f, 0xf8, 0xc1, 0x3f, 0xf8, 0xc1, 0x3f, 0xfe, 0xc0, 0x3f, 0xfe, 0xc0, 0x3f, 0xfe, 0xc3, 0x1f, 0xfe, 0xc3, 0x1f, 0xfe, 0xc3, 0x1f, 0xfe, 0xc3, 0x1f, 0xfe, 0xc3, 0x1f, 0xfc, 0x01, 0x30, 0xfc, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x1f, 0x00, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x07, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x0c, 0x00, 0x00, 0x18, 0xc0, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x70, 0x06, 0x36, 0xf0, 0x03, 0x3f, 0x18, 0x86, 0x61, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x60, 0x03, 0x36, 0x18, 0x8c, 0x01, 0x98, 0x03, 0x1b, 0x00, 0x00, 0x00, 0xf0, 0x03, 0x3f, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3c, 0x0c, 0xc3, 0x00, 0x0c, 0x00, 0x18, 0x8e, 0x01, 0x00, 0x00, 0x18, 0xc0, 0x80, 0x01, 0xf0, 0x80, 0x31, 0xf0, 0x8c, 0x01, 0xf0, 0x07, 0x00, 0xf0, 0x07, 0x00, 0x18, 0x8c, 0x01, 0xfc, 0xdf, 0x07, 0xf0, 0x03, 0x00, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x03, 0x00, 0xf0, 0x03, 0x00, 0x18, 0x0c, 0x00, 0x00, 0x0c, 0xf0, 0x98, 0x83, 0x01, 0x00, 0x80, 0x01, 0xf0, 0x80, 0x01, 0xc0, 0x80, 0x19, 0xc0, 0x86, 0x01, 0x0c, 0x86, 0x6f, 0x0c, 0x86, 0x6f, 0x0c, 0xc6, 0x00, 0x0c, 0xc6, 0x00, 0x60, 0x00, 0x07, 0x60, 0x00, 0x07, 0xf8, 0x01, 0x07, 0x60, 0x00, 0x07, 0x60, 0x00, 0x07, 0x0c, 0xc6, 0x71, 0x00, 0x06, 0x60, 0xec, 0xc0, 0x70, 0x8c, 0xc7, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x18, 0x0c, 0xc0, 0x70, 0x0c, 0xc0, 0x70, 0xfc, 0xc7, 0x3e, 0xfc, 0xc7, 0x3e, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0x86, 0x61, 0x00, 0x06, 0x60, 0x3c, 0xc0, 0x18, 0xcc, 0xc1, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x8c, 0xc7, 0x60, 0x8c, 0xc7, 0x60, 0x0c, 0xc6, 0x61, 0x0c, 0xc6, 0x61, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x60, 0x00, 0x06, 0x0c, 0x86, 0x61, 0x00, 0x06, 0x60, 0xec, 0xc0, 0x07, 0x7c, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x06, 0xc3, 0x3f, 0x06, 0xc3, 0x3f, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x30, 0x00, 0x03, 0x66, 0xc3, 0x30, 0x06, 0x63, 0x30, 0xe6, 0x60, 0x0c, 0xe6, 0x60, 0x00, 0x30, 0x60, 0x00, 0x30, 0x60, 0x00, 0x30, 0x60, 0x00, 0xfc, 0x01, 0x30, 0xfc, 0x01, 0x30, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xfc, 0xc0, 0x0f, 0xcf, 0xf1, 0x33, 0xfc, 0xc1, 0x1f, 0x86, 0x63, 0x38, 0xc6, 0xe3, 0x3f, 0xfc, 0xe0, 0x3f, 0xfc, 0xe0, 0x3f, 0xfc, 0xe0, 0x3f, 0x00, 0xc0, 0x1f, 0x30, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x60, 0xc3, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x7f, 0x30, 0x06, 0x63, 0xc0, 0x0c, 0xcc, 0x00, 0x00, 0x00, 0x80, 0x01, 0x30, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x36, 0x80, 0x01, 0x18, 0xc0, 0x01, 0x1c, 0x00, 0x00, 0x00, 0xc0, 0x80, 0x01, 0xf0, 0x06, 0x18, 0x80, 0x01, 0x00, 0x00, 0x00, 0x1c, 0xc0, 0x81, 0x01, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x3e, 0x60, 0x06, 0x66, 0xf0, 0x0f, 0x00, 0xc0, 0x00, 0x18, 0xf8, 0x07, 0x00, 0xe0, 0x00, 0x1c, 0xc0, 0x00, 0x0c, 0x60, 0x03, 0x36, 0xf0, 0x07, 0x00, 0xc0, 0x86, 0x0d, 0xc0, 0x83, 0xc3, 0x00, 0x80, 0xc3, 0x00, 0x80, 0xc3, 0x00, 0x00, 0x00, 0x38, 0x0c, 0x00, 0xf0, 0x07, 0x00, 0xf0, 0x07, 0x00, 0xf0, 0x07, 0x00, 0x98, 0x01, 0x00, 0xf8, 0x07, 0x00, 0x18, 0x0c, 0x00, 0xf8, 0x07, 0x00, 0xf0, 0x07, 0x00, 0xf0, 0x07, 0x00, 0x18, 0x0c, 0x00, 0x60, 0xc0, 0x03, 0xe0, 0xc0, 0x63, 0xec, 0xc3, 0x63, 0xec, 0xc3, 0x63, 0xec, 0xc3, 0x3e, 0x3c, 0xc6, 0x3e, 0x0c, 0x86, 0x3f, 0x0c, 0x86, 0x3f, 0x0c, 0x86, 0x3f, 0xc6, 0xc0, 0x3f, 0x0c, 0xc6, 0x3e, 0x0c, 0xc6, 0x3e, 0x0c, 0xc6, 0x3e, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x0c, 0x80, 0x3f, 0x60, 0xc0, 0x01, 0x70, 0xc0, 0x66, 0x1c, 0xc6, 0x66, 0x1c, 0xc6, 0x66, 0x1c, 0xc6, 0x61, 0x6c, 0xc6, 0x61, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xc6, 0x63, 0x66, 0x0c, 0xc6, 0x61, 0xfc, 0xc3, 0x61, 0x0c, 0xc6, 0x61, 0xf8, 0xc3, 0x00, 0xf8, 0xc3, 0x00, 0xf8, 0xc3, 0x00, 0x60, 0xe0, 0x00, 0x78, 0xc0, 0x6c, 0x0c, 0xc6, 0x6c, 0x0c, 0xc6, 0x6c, 0x0c, 0xc6, 0x60, 0xcc, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xc6, 0x60, 0x7e, 0xfc, 0xc3, 0x00, 0x8c, 0xc1, 0x00, 0xfc, 0xc3, 0x00, 0x00, 0x86, 0x3f, 0x00, 0x86, 0x3f, 0x00, 0x86, 0x3f, 0x30, 0x78, 0x00, 0x30, 0x60, 0x3c, 0x06, 0x63, 0x3c, 0x06, 0x63, 0x3c, 0x06, 0x63, 0x30, 0xc6, 0x63, 0x30, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x06, 0x63, 0x30, 0x66, 0x30, 0x03, 0xc6, 0x60, 0x00, 0x86, 0x61, 0x00, 0xc6, 0x60, 0x00, 0x06, 0x03, 0x30, 0x06, 0x03, 0x30, 0x06, 0x03, 0x30, 0xfc, 0xe0, 0x3f, 0xfc, 0x60, 0x38, 0x06, 0x63, 0x38, 0x06, 0x63, 0x38, 0x06, 0x63, 0x30, 0x86, 0x63, 0x30, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xfc, 0xe3, 0x1f, 0x86, 0x63, 0x00, 0x06, 0x63, 0x00, 0x86, 0x63, 0x00, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0xfc, 0xc1, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x07, 0x60, 0x03, 0x36, 0x00, 0x00, 0x00, 0x30, 0x03, 0x33, 0x00, 0x00, 0x00, 0xe0, 0x06, 0x6e, 0xf0, 0x07, 0x7f, 0x30, 0x06, 0x63, 0xc0, 0x01, 0x1c, 0xc0, 0x0c, 0xcc, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x1e, 0xe0, 0x01, 0x1c, 0x30, 0x03, 0x18, 0x80, 0x01, 0x0c, 0xc0, 0x00, 0x36, 0x60, 0x03, 0x00, 0xc0, 0x01, 0x1c, 0xfc, 0x0f, 0x06, 0xe0, 0x01, 0x1e, 0xfc, 0x0f, 0x06, 0xb0, 0x03, 0x3b, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x3e, 0x60, 0x03, 0x36, 0x60, 0x06, 0x66, 0x18, 0x0c, 0x00, 0x30, 0x03, 0x33, 0x30, 0x03, 0x36, 0x00, 0x00, 0x0c, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xc0, 0x01, 0x1e, 0xf0, 0x07, 0x00, 0xc0, 0x00, 0x06, 0xfc, 0x0f, 0x06, 0xc0, 0x80, 0x3f, 0x18, 0x0c, 0x00, 0x18, 0x0c, 0x00, 0x18, 0x0c, 0x00, 0xd8, 0x0d, 0x1c, 0x18, 0x0c, 0x00, 0x18, 0x0c, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x86, 0x7f, 0x00, 0x80, 0x7f, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x33, 0x0c, 0x80, 0x3f, 0x60, 0xc0, 0x1f, 0x60, 0xc0, 0x1f, 0x60, 0x00, 0x03, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x66, 0x66, 0x60, 0x0c, 0xc3, 0x60, 0x98, 0x01, 0x0c, 0xfc, 0x07, 0x0c, 0xfc, 0x07, 0x0c, 0xfc, 0xc7, 0x01, 0xf8, 0xc3, 0x00, 0x60, 0x00, 0x03, 0x60, 0x00, 0x03, 0xf8, 0xc1, 0x1f, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x66, 0x66, 0x66, 0x98, 0x81, 0x31, 0xf0, 0x00, 0x06, 0x80, 0x01, 0x06, 0x80, 0x01, 0x06, 0x80, 0x81, 0x01, 0x00, 0x86, 0x3f, 0x60, 0x00, 0x03, 0x60, 0x00, 0x03, 0x60, 0x00, 0x03, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xf6, 0x66, 0x66, 0xf0, 0x00, 0x1b, 0x60, 0x00, 0x03, 0xe0, 0x00, 0x03, 0xe0, 0x00, 0x03, 0xe0, 0x80, 0x01, 0x06, 0x03, 0x30, 0x30, 0x80, 0x31, 0x30, 0x80, 0x31, 0x30, 0x80, 0x31, 0x06, 0x63, 0x38, 0x06, 0x63, 0x38, 0x06, 0x63, 0x38, 0x06, 0x63, 0x38, 0x06, 0x63, 0x38, 0x06, 0x63, 0x38, 0xce, 0x61, 0x1b, 0x30, 0x00, 0x07, 0x30, 0xc0, 0x00, 0x18, 0xc0, 0x00, 0x18, 0xc0, 0x00, 0x18, 0xc0, 0x00, 0xfc, 0xc1, 0x1f, 0x30, 0x00, 0x1f, 0x30, 0x00, 0x1f, 0x30, 0x00, 0x1f, 0xfc, 0xc1, 0x37, 0xfc, 0xc1, 0x37, 0xfc, 0xc1, 0x37, 0xfc, 0xc1, 0x37, 0xfc, 0xc1, 0x37, 0xfc, 0xc1, 0x37, 0x86, 0xc1, 0x0c, 0x30, 0x00, 0x03, 0x30, 0xe0, 0x1f, 0xfe, 0xe3, 0x1f, 0xfe, 0xe3, 0x1f, 0xfe, 0xe3, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x1b, 0x00, 0x00, 0x00, 0x60, 0x06, 0x1c, 0x60, 0x03, 0x7f, 0x80, 0x01, 0x06, 0x00, 0x00, 0x00, 0x30, 0x06, 0x0c, 0xe0, 0x01, 0x00, 0xe0, 0x06, 0x00, 0x80, 0x01, 0x1c, 0x60, 0x06, 0x77, 0xfc, 0xc0, 0x63, 0x00, 0xc0, 0x8f, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0x33, 0xc0, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x30, 0x03, 0x36, 0xc0, 0x01, 0x00, 0xc0, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x30, 0x03, 0x00, 0xb0, 0x03, 0x00, 0xc0, 0x00, 0x18, 0x30, 0x03, 0x66, 0x0c, 0x40, 0x32, 0x00, 0x00, 0xd9, 0x18, 0xc6, 0x41, 0x3c, 0xc4, 0x43, 0x00, 0x03, 0x33, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x0c, 0x70, 0x07, 0x33, 0xcc, 0xcf, 0x1b, 0xf0, 0x05, 0xf9, 0x0c, 0x8c, 0x21, 0x60, 0x42, 0x20, 0xc0, 0x80, 0x19, 0x18, 0xc3, 0x1f, 0xf8, 0x83, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x06, 0x88, 0x82, 0x54, 0x06, 0xc6, 0x08, 0x9c, 0xe0, 0x09, 0xf8, 0x81, 0x19, 0x0c, 0x06, 0x30, 0x0c, 0xc6, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x03, 0x03, 0xa8, 0x82, 0x44, 0x06, 0xc6, 0x7c, 0x70, 0x8f, 0x7d, 0x30, 0x80, 0x19, 0xfc, 0xc7, 0x3f, 0xfc, 0x67, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x80, 0xf7, 0xa8, 0x82, 0x44, 0x0c, 0xc3, 0x4a, 0x3e, 0xe9, 0x4a, 0x0c, 0xc0, 0x0c, 0x06, 0x33, 0x18, 0x06, 0x60, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xe0, 0x4a, 0x14, 0x01, 0x00, 0xcc, 0x80, 0x3c, 0x88, 0x87, 0x3c, 0xfe, 0xc1, 0x0c, 0x06, 0xe3, 0x3f, 0xfc, 0xc1, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xb3, 0x7b, 0xf4, 0x01, 0x00, 0xcf, 0x43, 0x24, 0x84, 0x44, 0x24, 0x00, 0xc0, 0x0c, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x3c, 0x82, 0x27, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x04, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0xc0, 0xff, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x02, 0x0c, 0xe0, 0x01, 0x0c, 0xc0, 0xc0, 0xff, 0x80, 0x86, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x80, 0x03, 0xf8, 0x01, 0x1c, 0x60, 0xe0, 0x7f, 0x40, 0x02, 0x03, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xe7, 0xff, 0x6c, 0xf3, 0x7f, 0x60, 0xe0, 0x7f, 0x40, 0x00, 0x1b, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x84, 0x03, 0x60, 0x00, 0x1c, 0x6c, 0xe3, 0x7f, 0x78, 0x00, 0x1b, 0x60, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x03, 0x03, 0x30, 0x00, 0x03, 0xfc, 0xf0, 0x3f, 0x3e, 0x80, 0x0d, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x02, 0x00, 0x30, 0x00, 0x00, 0x78, 0xf0, 0x3f, 0x1c, 0x80, 0x3d, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x03, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x03, 0x00, 0xf6, 0x6f, 0xc3, 0xf6, 0x0f, 0x00, 0xe6, 0x03, 0x00, 0x36, 0x6c, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x06, 0x00, 0x36, 0x68, 0xc3, 0x86, 0x01, 0x00, 0x36, 0x06, 0x00, 0x66, 0x66, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x06, 0x00, 0x18, 0x80, 0x61, 0xc0, 0x00, 0x00, 0x0c, 0x06, 0x00, 0xe0, 0xc1, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0xf8, 0x81, 0x7f, 0xc0, 0x00, 0x00, 0x0c, 0x06, 0x00, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x18, 0x80, 0x61, 0xc0, 0x00, 0x00, 0x0c, 0x06, 0x00, 0xc0, 0x80, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x00, 0x0c, 0xc2, 0x30, 0x60, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x60, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x00, 0xfc, 0xc3, 0x30, 0xfc, 0x03, 0x00, 0xf8, 0x00, 0x00, 0xf0, 0xe0, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x30, 0x03, 0x33, 0x80, 0x01, 0x30, 0x80, 0x01, 0x18, 0xd8, 0x06, 0x1e, 0xfc, 0xc3, 0xff, 0xe0, 0xc1, 0x7f, 0xfc, 0xc7, 0xe1, 0xf8, 0x07, 0x3f, 0x3c, 0x0e, 0x1e, 0x1c, 0xce, 0xe1, 0xfc, 0x8f, 0x7f, 0xfc, 0x8f, 0x7f, 0x00, 0xc0, 0xff, 0xfc, 0xcf, 0xc0, 0xf8, 0xc7, 0xe1, 0xcc, 0x8c, 0x61, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0xc0, 0x00, 0x0c, 0x00, 0x00, 0x33, 0x18, 0x86, 0x81, 0x30, 0x83, 0x41, 0x84, 0x83, 0x61, 0x0c, 0x0c, 0x0c, 0x18, 0x03, 0x33, 0x3c, 0x8f, 0x63, 0x04, 0xc8, 0xc0, 0x18, 0x86, 0xc1, 0x00, 0x80, 0x83, 0xc4, 0x88, 0x61, 0xcc, 0x8c, 0x61, 0xcc, 0xcc, 0xc0, 0xf0, 0x83, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xc0, 0x30, 0x0c, 0xc3, 0x00, 0x0c, 0xc3, 0x00, 0xe0, 0xc0, 0x30, 0x06, 0x06, 0x06, 0xcc, 0xc0, 0x30, 0xf6, 0xc6, 0x33, 0x00, 0x60, 0x60, 0x0c, 0xc3, 0x60, 0x00, 0x00, 0x03, 0x60, 0x80, 0x19, 0x66, 0x86, 0x19, 0x66, 0x66, 0x60, 0x60, 0x80, 0x19, 0x78, 0x86, 0x3f, 0xf6, 0x01, 0x07, 0x60, 0x60, 0x60, 0xfc, 0xc1, 0x00, 0x06, 0xc6, 0x07, 0x70, 0xc0, 0x3f, 0xfe, 0x07, 0x06, 0x7c, 0x60, 0x60, 0x66, 0xc6, 0x36, 0xf8, 0x61, 0x60, 0x0c, 0xc3, 0x3f, 0x00, 0x00, 0x06, 0x60, 0x00, 0x0f, 0x66, 0x06, 0x0f, 0x66, 0x66, 0x60, 0x60, 0x00, 0x0f, 0x8c, 0xc3, 0x00, 0x1c, 0x03, 0x06, 0x60, 0xe0, 0x7f, 0x0c, 0xc3, 0x00, 0x06, 0xc6, 0x00, 0x38, 0xc0, 0x30, 0x06, 0x06, 0x06, 0xcc, 0x60, 0x60, 0x06, 0xc6, 0x3c, 0x00, 0x60, 0x60, 0x0c, 0xc3, 0x00, 0x00, 0x00, 0x03, 0x60, 0x00, 0x06, 0xfc, 0x83, 0x19, 0xfc, 0xc3, 0x30, 0x60, 0x00, 0x06, 0x8c, 0x81, 0x0f, 0x0c, 0x03, 0x06, 0x30, 0x30, 0x30, 0x86, 0x61, 0x00, 0x03, 0x63, 0x10, 0x0e, 0x61, 0x18, 0x03, 0x03, 0x03, 0xc6, 0x30, 0x30, 0x03, 0x63, 0x1c, 0x01, 0x32, 0x30, 0x86, 0x61, 0x00, 0x00, 0xe0, 0x20, 0x30, 0x00, 0x03, 0x30, 0x60, 0x18, 0x30, 0xc0, 0x0c, 0x30, 0x00, 0x03, 0xc6, 0x61, 0x00, 0x86, 0x01, 0x03, 0xfc, 0x30, 0x30, 0xff, 0x70, 0x00, 0xff, 0xf3, 0x1f, 0xff, 0x71, 0x38, 0xfe, 0xc1, 0x0f, 0x8f, 0x33, 0x30, 0x03, 0x73, 0x38, 0xff, 0xe3, 0x1f, 0x87, 0xf3, 0x00, 0x00, 0xf0, 0x3f, 0x78, 0x80, 0x07, 0x78, 0x70, 0x38, 0x78, 0xf0, 0x3c, 0xfc, 0x80, 0x07, 0x3c, 0xc3, 0x1f, 0x87, 0xc1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x80, 0x01, 0x00, 0xd8, 0x0c, 0x00, 0xf0, 0x01, 0x00, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x63, 0x80, 0x01, 0x18, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x03, 0x00, 0x18, 0x00, 0x00, 0x70, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xc6, 0x37, 0x8c, 0xe1, 0x60, 0x18, 0x80, 0x3f, 0xe0, 0x63, 0x1f, 0x0c, 0x03, 0x07, 0x0e, 0x03, 0x06, 0x0e, 0xe3, 0x70, 0x18, 0x80, 0x3f, 0xfe, 0x07, 0x3f, 0xf8, 0x81, 0x7f, 0xfc, 0xe3, 0x60, 0xf8, 0xe1, 0x70, 0x06, 0xc6, 0x30, 0x70, 0xe0, 0x60, 0xf8, 0xe3, 0x60, 0x0c, 0x03, 0x00, 0x0c, 0x66, 0x1c, 0xec, 0xa0, 0x31, 0xfc, 0xc1, 0x00, 0x30, 0xc0, 0x31, 0xf8, 0x03, 0x06, 0xcc, 0x01, 0x0f, 0x0c, 0xc3, 0x30, 0xf0, 0xc0, 0x60, 0x0c, 0x83, 0x61, 0x0c, 0xc0, 0x18, 0x60, 0xc0, 0x60, 0x6c, 0xc3, 0x39, 0x66, 0x66, 0x60, 0x60, 0xc0, 0x60, 0x0c, 0xc6, 0x60, 0x06, 0x06, 0x00, 0x0c, 0x66, 0x18, 0x8c, 0x03, 0x1b, 0x06, 0xc3, 0x0f, 0x18, 0xc0, 0x30, 0x0c, 0x03, 0x06, 0x7c, 0x80, 0x19, 0x1c, 0x83, 0x19, 0x18, 0xc0, 0x60, 0x0c, 0x83, 0x61, 0xf8, 0xc1, 0x30, 0x60, 0xc0, 0x60, 0x6c, 0x03, 0x0f, 0x66, 0x66, 0x66, 0x60, 0xc0, 0x60, 0x0c, 0xc6, 0x60, 0x66, 0x06, 0x00, 0x06, 0x33, 0x1e, 0x86, 0x01, 0x07, 0x83, 0x61, 0x00, 0x18, 0x60, 0x18, 0x86, 0x01, 0x03, 0xe6, 0x60, 0x18, 0xf6, 0x82, 0x07, 0x06, 0x60, 0x30, 0x86, 0xc1, 0x1f, 0x80, 0x61, 0x18, 0x30, 0x60, 0x30, 0xfc, 0xe0, 0x1c, 0xfe, 0xb1, 0x37, 0x30, 0x60, 0x30, 0x06, 0x63, 0x30, 0x7b, 0x03, 0x00, 0xfc, 0xe1, 0x33, 0xf6, 0x80, 0x03, 0xfe, 0xc0, 0x1f, 0xf0, 0x71, 0x18, 0xfc, 0xc0, 0x0f, 0x87, 0x73, 0x38, 0x06, 0x00, 0x03, 0x7c, 0xc0, 0x1f, 0x86, 0xc1, 0x00, 0xfc, 0xc0, 0x0f, 0x70, 0xc0, 0x1f, 0x30, 0x70, 0x38, 0x30, 0xe0, 0x1c, 0xfc, 0xc0, 0x1f, 0xfc, 0xc1, 0x1f, 0xce, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x63, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x06, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x7e, 0x00, 0x18, 0xf0, 0x07, 0x7f, 0xf8, 0x07, 0x00, 0x00, 0x0c, 0x1f, 0x8c, 0xe1, 0x07, 0xd8, 0x00, 0x0c, 0xf0, 0x83, 0x61, 0xe0, 0x81, 0x7f, 0xf8, 0x87, 0x7f, 0xe0, 0x83, 0xff, 0xcc, 0x0c, 0x7f, 0x0c, 0x0e, 0x3e, 0x18, 0x0e, 0x3e, 0x1c, 0xce, 0xc0, 0xf0, 0x87, 0x7f, 0xf8, 0x8f, 0xff, 0x18, 0x80, 0xff, 0x18, 0x8c, 0xc1, 0xc0, 0x00, 0x3f, 0x00, 0x8c, 0x19, 0x8c, 0x81, 0x01, 0x18, 0xce, 0xf0, 0x00, 0x80, 0x61, 0x30, 0x83, 0x01, 0x18, 0x8c, 0x01, 0x30, 0x83, 0x01, 0xcc, 0x8c, 0xc1, 0x0c, 0xcf, 0xf0, 0x18, 0x03, 0x33, 0x3c, 0xcf, 0xc0, 0x18, 0x8c, 0x61, 0x0c, 0xc0, 0x00, 0xec, 0xc3, 0x00, 0x06, 0xc0, 0x00, 0x60, 0x00, 0x06, 0x00, 0xc6, 0x0c, 0xc6, 0xc0, 0x3e, 0x8c, 0x61, 0x6c, 0x0c, 0xc3, 0x30, 0x0c, 0xc3, 0x00, 0x0c, 0xc6, 0x00, 0x98, 0xc1, 0x00, 0x66, 0x06, 0x60, 0xc6, 0x66, 0x6c, 0xec, 0x80, 0x19, 0xf6, 0x66, 0x60, 0x0c, 0xc6, 0x30, 0xfc, 0xc1, 0x1f, 0x1c, 0xc6, 0x00, 0xfc, 0x80, 0x3f, 0x60, 0x00, 0x06, 0x00, 0xc6, 0x7c, 0xfe, 0xc7, 0x61, 0xec, 0x60, 0x66, 0x98, 0xc1, 0x30, 0x06, 0xc6, 0x3f, 0xfc, 0xc3, 0x00, 0x98, 0xc1, 0x0f, 0xf8, 0x01, 0x3e, 0x66, 0x66, 0x66, 0x3c, 0x80, 0x19, 0x66, 0xe6, 0x7f, 0x0c, 0xc6, 0x30, 0x0c, 0xc0, 0x00, 0x0c, 0xc6, 0x00, 0x06, 0x00, 0x60, 0x60, 0x00, 0x06, 0x00, 0xc6, 0xcc, 0xc6, 0xcc, 0x60, 0x3c, 0x60, 0x63, 0xf0, 0xc0, 0x30, 0xfe, 0xc7, 0x60, 0x0c, 0xc6, 0x00, 0x98, 0xc1, 0x00, 0x66, 0x06, 0x60, 0x36, 0x66, 0x63, 0xec, 0x80, 0x19, 0x06, 0x66, 0x60, 0x0c, 0xc6, 0x30, 0x06, 0x60, 0x00, 0x06, 0x63, 0x00, 0x06, 0x63, 0x30, 0x30, 0x00, 0x03, 0x06, 0x63, 0x66, 0x63, 0x66, 0x30, 0xe6, 0xf0, 0x30, 0x30, 0x60, 0x18, 0x03, 0x63, 0x30, 0x06, 0x63, 0x00, 0xcc, 0x60, 0x00, 0x33, 0x63, 0x30, 0x0f, 0xf3, 0x30, 0xc6, 0xc0, 0x0c, 0x03, 0x33, 0x30, 0x06, 0x63, 0x18, 0xfe, 0xe3, 0x3f, 0x86, 0x61, 0x00, 0xfc, 0xc1, 0x1f, 0xfe, 0xc1, 0x0f, 0xfc, 0x71, 0x3e, 0xe3, 0x63, 0x30, 0x86, 0x73, 0x30, 0x30, 0xe0, 0x1f, 0x03, 0xe3, 0x1f, 0xfe, 0x61, 0x00, 0xff, 0xe3, 0x3f, 0x33, 0xc3, 0x1f, 0x07, 0x73, 0x30, 0x86, 0xf3, 0x0c, 0x03, 0x33, 0x30, 0xfc, 0x61, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0x7e, 0xf8, 0x87, 0xc1, 0xc0, 0x80, 0x61, 0x18, 0x83, 0xc1, 0xcc, 0xcc, 0x36, 0x1c, 0xc0, 0xc0, 0x18, 0x80, 0x3f, 0x98, 0x07, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0c, 0xc3, 0xc0, 0x80, 0xc1, 0xf8, 0x07, 0x33, 0x18, 0x83, 0xc1, 0xcc, 0xcc, 0x36, 0x18, 0xc0, 0xc0, 0x18, 0xc0, 0x60, 0xd8, 0x8c, 0xc1, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc6, 0x00, 0x60, 0xc0, 0x60, 0x66, 0x06, 0x0f, 0x8c, 0xc1, 0x60, 0x66, 0x66, 0x1b, 0x0c, 0x60, 0x60, 0x0c, 0x00, 0x60, 0x6c, 0xc6, 0x60, 0xfc, 0x01, 0x1c, 0xfc, 0xc3, 0x1f, 0xf0, 0x81, 0x3f, 0x6c, 0x83, 0x3f, 0x0c, 0xc7, 0x70, 0x0c, 0x07, 0x3f, 0x0e, 0xc7, 0x60, 0xf8, 0xc3, 0x3f, 0xfc, 0xc3, 0x00, 0x60, 0x80, 0x7f, 0x66, 0x06, 0x06, 0x8c, 0x81, 0x7f, 0x66, 0x66, 0x1b, 0xfc, 0xe3, 0x67, 0xfc, 0x03, 0x3f, 0x7c, 0x86, 0x7f, 0x00, 0x83, 0x3f, 0x0c, 0xc6, 0x00, 0x98, 0xc1, 0x60, 0x6c, 0xc3, 0x60, 0x8c, 0xc6, 0x68, 0x8c, 0x81, 0x31, 0x9e, 0xc7, 0x60, 0x0c, 0xc6, 0x30, 0x0c, 0xc0, 0x00, 0x60, 0x00, 0x60, 0x66, 0x06, 0x0f, 0x8c, 0x01, 0x60, 0x66, 0x66, 0x1b, 0x0c, 0x66, 0x6c, 0x0c, 0x06, 0x60, 0x6c, 0x06, 0x6c, 0xfc, 0xc3, 0x60, 0xfc, 0xc3, 0x00, 0x98, 0xc1, 0x7f, 0xf0, 0x00, 0x3c, 0x4c, 0xc6, 0x64, 0x7c, 0x80, 0x31, 0xf6, 0xc6, 0x7f, 0x0c, 0xc6, 0x30, 0x06, 0xc0, 0x30, 0x30, 0x00, 0x30, 0x33, 0xc3, 0x0c, 0xc6, 0x00, 0x30, 0x33, 0xb3, 0x0d, 0x06, 0x33, 0x36, 0x06, 0x33, 0x18, 0x36, 0x03, 0x33, 0x83, 0x61, 0x30, 0x06, 0x63, 0x00, 0xcc, 0x60, 0x00, 0xb6, 0x61, 0x30, 0x16, 0x63, 0x31, 0xc6, 0xc0, 0x18, 0x33, 0x63, 0x30, 0x06, 0x63, 0x18, 0x06, 0x80, 0x1f, 0x30, 0xe0, 0x3f, 0xfe, 0x61, 0x18, 0xfe, 0x03, 0x30, 0xff, 0xf3, 0x3f, 0xfe, 0xf3, 0x33, 0xfe, 0xe3, 0x0f, 0xe6, 0xe1, 0x31, 0xfe, 0xc3, 0x1f, 0xfe, 0x61, 0x00, 0xff, 0xc3, 0x1f, 0xb6, 0xc1, 0x1f, 0x0e, 0xe3, 0x30, 0x86, 0xf3, 0x18, 0x03, 0x63, 0x30, 0xfc, 0x61, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x63, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x30, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x18, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x83, 0x3f, 0xfc, 0xc3, 0x60, 0xf8, 0x81, 0x19, 0x8c, 0xc1, 0x60, 0x66, 0x66, 0x1b, 0x0e, 0xc0, 0x60, 0x0c, 0xc0, 0x0f, 0xcc, 0x83, 0x7f, 0xf8, 0x83, 0x3f, 0x0c, 0xc0, 0x7f, 0xf0, 0x83, 0x3f, 0x70, 0x00, 0x07, 0x80, 0x07, 0x1f, 0x8c, 0x81, 0x01, 0x0c, 0xc7, 0x70, 0x0c, 0xc6, 0x30, 0x1c, 0xc6, 0x60, 0x60, 0x80, 0x31, 0x6c, 0x03, 0x0f, 0x8c, 0xc1, 0x60, 0x66, 0x66, 0x1b, 0x0c, 0xc0, 0x60, 0x0c, 0x60, 0x30, 0x6c, 0xc6, 0x60, 0x0c, 0xc6, 0x60, 0xec, 0xc1, 0x00, 0x0c, 0xc6, 0x00, 0x60, 0x00, 0x06, 0x00, 0x86, 0x19, 0x8c, 0x81, 0x3d, 0x8c, 0xc1, 0x68, 0x18, 0xc3, 0x30, 0x0c, 0xc6, 0x00, 0x60, 0x00, 0x1b, 0x6c, 0x03, 0x06, 0x8c, 0x81, 0x7f, 0x66, 0x66, 0x1b, 0xfc, 0xc3, 0x67, 0xfc, 0x03, 0x3e, 0x7c, 0x86, 0x7f, 0xfc, 0xc7, 0x7f, 0x1c, 0xc3, 0x00, 0x7c, 0x80, 0x3f, 0x60, 0x00, 0x06, 0x00, 0x86, 0xf9, 0xfc, 0x8f, 0x63, 0x7c, 0xc0, 0x64, 0xb0, 0xc1, 0x30, 0x0e, 0x63, 0x00, 0x30, 0x00, 0x07, 0xb6, 0x81, 0x07, 0xc6, 0x00, 0x30, 0x33, 0xb3, 0x0d, 0x06, 0x63, 0x36, 0x06, 0x33, 0x18, 0x36, 0x03, 0x36, 0x06, 0x60, 0x00, 0x86, 0x61, 0x00, 0x06, 0x03, 0x30, 0x30, 0x00, 0x03, 0x00, 0xc3, 0xcc, 0xc6, 0xcc, 0x30, 0xc6, 0x60, 0x31, 0x70, 0x60, 0x18, 0xf6, 0xc1, 0x3f, 0x30, 0x00, 0x03, 0xfc, 0xc0, 0x0c, 0xfe, 0x03, 0x30, 0xff, 0xf3, 0x3f, 0xfe, 0xe1, 0x33, 0xfe, 0xe1, 0x07, 0xe6, 0xc1, 0x31, 0xfc, 0xc1, 0x1f, 0xc6, 0x60, 0x00, 0xf8, 0xc1, 0x1f, 0xfc, 0xc0, 0x0f, 0x06, 0xe3, 0x7c, 0xc6, 0xc7, 0x30, 0x86, 0xe3, 0x30, 0x30, 0xe0, 0x1f, 0x06, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x30, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; zvbi-0.2.44/src/xds_demux.c000066400000000000000000000550221476363111200155160ustar00rootroot00000000000000/* * libzvbi -- Extended Data Service demultiplexer * * Copyright (C) 2000-2005 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: xds_demux.c,v 1.11 2008-02-19 00:35:23 mschimek Exp $ */ #include "site_def.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "misc.h" /* vbi_log_printf() */ #include "hamm.h" /* vbi_ipar8() */ #include "tables.h" /* vbi_rating/prog_type_string() */ #include "xds_demux.h" /** * @addtogroup XDSDemux Extended Data Service (XDS) demultiplexer * @ingroup LowDec * @brief Separating XDS data from a Closed Caption stream * (EIA 608). */ #ifndef XDS_DEMUX_LOG #define XDS_DEMUX_LOG 0 #endif /* LOG (level, format, args...) */ #define log(format, args...) \ do { \ if (XDS_DEMUX_LOG) \ fprintf (stderr, format , ##args); \ } while (0) static void xdump (const vbi_xds_packet * xp, FILE * fp) { unsigned int i; for (i = 0; i < xp->buffer_size; ++i) fprintf (fp, " %02x", xp->buffer[i]); fputs (" '", fp); for (i = 0; i < xp->buffer_size; ++i) fputc (_vbi_to_ascii (xp->buffer[i]), fp); fputc ('\'', fp); } /** @internal */ void _vbi_xds_packet_dump (const vbi_xds_packet * xp, FILE * fp) { static const char *month_names [] = { "0?", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "13?", "14?", "15?" }; static const char *map_type [] = { "unknown", "mono", "simulated stereo", "stereo", "stereo surround", "data service", "unknown", "none" }; static const char *sap_type [] = { "unknown", "mono", "video descriptions", "non-program audio", "special effects", "data service", "unknown", "none" }; static const char *language [] = { "unknown", "English", "Spanish", "French", "German", "Italian", "Other", "none" }; static const char *cgmsa [] = { "copying permitted", "-", "one copy allowed", "no copying permitted" }; static const char *scrambling [] = { "no pseudo-sync pulse", "pseudo-sync pulse on; color striping off", "pseudo-sync pulse on; 2-line color striping on", "pseudo-sync pulse on; 4-line color striping on" }; static const char *day_names [] = { "0?", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; unsigned int i; assert (NULL != xp); assert (NULL != fp); fprintf (fp, "XDS packet 0x%02x%02x ", xp->xds_class * 2 + 1, xp->xds_subclass); switch (xp->xds_class) { case VBI_XDS_CLASS_CURRENT: fputs ("(cur. program ", fp); /* fall through */ case VBI_XDS_CLASS_FUTURE: if (VBI_XDS_CLASS_FUTURE == xp->xds_class) fputs ("(fut. program ", fp); switch (xp->xds_subclass) { case VBI_XDS_PROGRAM_ID: { unsigned int month, day, hour, min; fputs ("id)", fp); xdump (xp, fp); if (4 != xp->buffer_size) { invalid: fputs (" (invalid)", fp); break; } month = xp->buffer[3] & 15; day = xp->buffer[2] & 31; hour = xp->buffer[1] & 31; min = xp->buffer[0] & 63; if (month == 0 || month > 12 || day == 0 || day > 31 || hour > 23 || min > 59) goto invalid; fprintf (fp, " (%d %s %02d:%02d UTC,", day, month_names[month], hour, min); fprintf (fp, " D=%d L=%d Z=%d T=%d)", !!(xp->buffer[1] & 0x20), !!(xp->buffer[2] & 0x20), !!(xp->buffer[3] & 0x20), !!(xp->buffer[3] & 0x10)); break; } case VBI_XDS_PROGRAM_LENGTH: { unsigned int lhour, lmin; fputs ("length)", fp); xdump (xp, fp); switch (xp->buffer_size) { case 2: case 4: case 5: break; default: goto invalid; } lhour = xp->buffer[1] & 63; lmin = xp->buffer[0] & 63; if (lmin > 59) goto invalid; fprintf (fp, " (%02d:%02d", lhour, lmin); if (xp->buffer_size >= 4) { unsigned int ehour, emin; ehour = xp->buffer[3] & 63; emin = xp->buffer[2] & 63; if (emin > 59) goto invalid; fprintf (fp, " elapsed=%02d:%02d", ehour, emin); if (xp->buffer_size >= 5) { unsigned int esec; esec = xp->buffer[4] & 63; if (esec > 59) goto invalid; fprintf (fp, ":%02d", esec); } } fputc (')', fp); break; } case VBI_XDS_PROGRAM_NAME: fputs ("name)", fp); xdump (xp, fp); break; case VBI_XDS_PROGRAM_TYPE: { unsigned int i; fputs ("type)", fp); xdump (xp, fp); if (xp->buffer_size < 1) goto invalid; fputs (" (", fp); for (i = 0; i < xp->buffer_size; ++i) { fprintf (fp, (i > 0) ? ", %s" : "%s", vbi_prog_type_string (VBI_PROG_CLASSF_EIA_608, xp->buffer[i])); } fputc (')', fp); break; } case VBI_XDS_PROGRAM_RATING: { unsigned int r, g; fputs ("rating)", fp); xdump (xp, fp); if (2 != xp->buffer_size) goto invalid; r = xp->buffer[0] & 7; g = xp->buffer[1] & 7; fprintf (fp, " (movie: %s, tv: ", vbi_rating_string (VBI_RATING_AUTH_MPAA, r)); if (xp->buffer[0] & 0x10) { const char *s; if (xp->buffer[0] & 0x20) s = vbi_rating_string (VBI_RATING_AUTH_TV_CA_FR, g); else s = vbi_rating_string (VBI_RATING_AUTH_TV_CA_EN, g); fputs (s, fp); } else { fprintf (fp, "%s D=%d L=%d S=%d V=%d", vbi_rating_string (VBI_RATING_AUTH_TV_US, g), !!(xp->buffer[0] & 0x20), !!(xp->buffer[1] & 0x08), !!(xp->buffer[1] & 0x10), !!(xp->buffer[1] & 0x20)); } fputc (')', fp); break; } case VBI_XDS_PROGRAM_AUDIO_SERVICES: fputs ("audio services)", fp); xdump (xp, fp); if (2 != xp->buffer_size) goto invalid; fprintf (fp, " (main: %s, %s; second: %s, %s)", map_type[xp->buffer[0] & 7], language[(xp->buffer[0] >> 3) & 7], sap_type[xp->buffer[1] & 7], language[(xp->buffer[1] >> 3) & 7]); break; case VBI_XDS_PROGRAM_CAPTION_SERVICES: fputs ("caption services)", fp); xdump (xp, fp); if (xp->buffer_size < 1 || xp->buffer_size > 8) goto invalid; fputc ('(', fp); for (i = 0; i < xp->buffer_size; ++i) { fprintf (fp, "%sline=%u channel=%u %s %s", (0 == i) ? "" : ", ", (xp->buffer[i] & 4) ? 284 : 21, (xp->buffer[i] & 2) ? 2 : 1, (xp->buffer[i] & 1) ? "text" : "captioning", language[(xp->buffer[i] >> 3) & 7]); } fputc (')', fp); break; case VBI_XDS_PROGRAM_CGMS: fputs ("cgms)", fp); xdump (xp, fp); if (1 != xp->buffer_size) goto invalid; fprintf (fp, " (%s", cgmsa[(xp->buffer[0] >> 3) & 3]); if (xp->buffer[0] & 0x18) fprintf (fp, ", %s", scrambling[(xp->buffer[0] >> 1) & 3]); fprintf (fp, ", analog_source=%u)", xp->buffer[0] & 1); break; case VBI_XDS_PROGRAM_ASPECT_RATIO: { unsigned int first_line, last_line; fputs ("aspect)", fp); xdump (xp, fp); if (2 != xp->buffer_size && 3 != xp->buffer_size) goto invalid; first_line = 22 + (xp->buffer[0] & 63); last_line = 262 - (xp->buffer[1] & 63); fprintf (fp, " (active picture %u ... %u%s)", first_line, last_line, (3 == xp->buffer_size && (xp->buffer[2] & 1)) ? " anamorphic" : ""); break; } case VBI_XDS_PROGRAM_DATA: { unsigned int rating; unsigned int lhour, lmin; unsigned int ehour, emin; fputs ("data)", fp); xdump (xp, fp); /* XXX ok? */ if (xp->buffer_size < 10) goto invalid; rating = xp->buffer[5] & 7; lhour = xp->buffer[7] & 63; lmin = xp->buffer[6] & 63; if (lmin > 59) goto invalid; ehour = xp->buffer[9] & 63; emin = xp->buffer[8] & 63; if (emin > 59) goto invalid; fputs (" (type: ", fp); for (i = 0; i < 5; ++i) { fprintf (fp, (i > 0) ? ", %s" : "%s", vbi_prog_type_string (VBI_PROG_CLASSF_EIA_608, xp->buffer[i])); } fprintf (fp, "; rating: %s; " "length: %02d:%02d; " "elapsed: %02d:%02d)", vbi_rating_string (VBI_RATING_AUTH_MPAA, rating), lhour, lmin, ehour, emin); /* program name: buffer[10 ... 31] (xdump'ed) */ break; } case VBI_XDS_PROGRAM_MISC_DATA: { unsigned int month, day, hour, min; fputs ("misc data)", fp); xdump (xp, fp); /* XXX ok? */ if (14 != xp->buffer_size) goto invalid; month = xp->buffer[3] & 15; day = xp->buffer[2] & 31; hour = xp->buffer[1] & 31; min = xp->buffer[0] & 63; if (month == 0 || month > 12 || day == 0 || day > 31 || hour > 23 || min > 59) goto invalid; fprintf (fp, " (%d %s %02d:%02d UTC, ", day, month_names[month], hour, min); fprintf (fp, " D=%d L=%d Z=%d T=%d", !!(xp->buffer[1] & 0x20), !!(xp->buffer[2] & 0x20), !!(xp->buffer[3] & 0x20), !!(xp->buffer[3] & 0x10)); fprintf (fp, ", main audio: %s, %s; second: %s, %s;", map_type[xp->buffer[4] & 7], language[(xp->buffer[4] >> 3) & 7], sap_type[xp->buffer[5] & 7], language[(xp->buffer[5] >> 3) & 7]); for (i = 6; i < 8; ++i) { fprintf (fp, "%sline=%u channel=%u %s %s", (6 == i) ? " caption: " : ", ", (xp->buffer[i] & 4) ? 284 : 21, (xp->buffer[i] & 2) ? 2 : 1, (xp->buffer[i] & 1) ? "text" : "captioning", language[(xp->buffer[i] >> 3) & 7]); } fprintf (fp, ", call letters: "); for (i = 8; i < 12; ++i) { fputc (_vbi_to_ascii (xp->buffer[i]), fp); } /* 02 to 69 or 0x20 0x20 */ fprintf (fp, ", channel: "); for (i = 12; i < 14; ++i) { fputc (_vbi_to_ascii (xp->buffer[i]), fp); } fputc (')', fp); break; } case VBI_XDS_PROGRAM_DESCRIPTION_BEGIN ... VBI_XDS_PROGRAM_DESCRIPTION_END - 1: fprintf (fp, "description %u)", (unsigned int) xp->xds_subclass - (unsigned int) VBI_XDS_PROGRAM_DESCRIPTION_BEGIN); xdump (xp, fp); break; default: fputs ("?)", fp); xdump (xp, fp); break; } break; case VBI_XDS_CLASS_CHANNEL: fputs ("(channel ", fp); switch (xp->xds_subclass) { case VBI_XDS_CHANNEL_NAME: fputs ("name)", fp); xdump (xp, fp); break; case VBI_XDS_CHANNEL_CALL_LETTERS: fputs ("call letters)", fp); xdump (xp, fp); break; case VBI_XDS_CHANNEL_TAPE_DELAY: { unsigned int hour, min; fputs ("tape delay)", fp); xdump (xp, fp); if (2 != xp->buffer_size) goto invalid; hour = xp->buffer[1] & 31; min = xp->buffer[0] & 63; if (min > 59) goto invalid; fprintf (fp, " (%02d:%02d)", hour, min); break; } case VBI_XDS_CHANNEL_TSID: { unsigned int tsid; fputs ("transmission signal identifier)", fp); xdump (xp, fp); if (4 != xp->buffer_size) goto invalid; tsid = (xp->buffer[3] & 15) << 0; tsid += (xp->buffer[2] & 15) << 4; tsid += (xp->buffer[1] & 15) << 8; tsid += (xp->buffer[0] & 15) << 12; if (0 == tsid) goto invalid; fprintf (fp, " (0x%04x)", tsid); break; } default: fputs ("?)", fp); xdump (xp, fp); break; } break; case VBI_XDS_CLASS_MISC: fputs ("(misc: ", fp); switch (xp->xds_subclass) { case VBI_XDS_TIME_OF_DAY: fputs ("time of day)", fp); xdump (xp, fp); if (6 != xp->buffer_size) goto invalid; fprintf (fp, " (%s, %d %s %d", day_names [xp->buffer[4] & 7], xp->buffer[2] & 31, month_names[xp->buffer[3] & 15], 1990 + (xp->buffer[5] & 63)); fprintf (fp, " %02d:%02d UTC", xp->buffer[1] & 31, xp->buffer[0] & 63); fprintf (fp, " D=%u L=%u Z=%u T=%u)", !!(xp->buffer[1] & 0x20), !!(xp->buffer[2] & 0x20), !!(xp->buffer[3] & 0x20), !!(xp->buffer[3] & 0x10)); break; case VBI_XDS_IMPULSE_CAPTURE_ID: fputs ("capture id)", fp); xdump (xp, fp); if (6 != xp->buffer_size) goto invalid; fprintf (fp, " (%d %s", xp->buffer[2] & 31, month_names[xp->buffer[3] & 15]); fprintf (fp, " %02d:%02d", xp->buffer[1] & 31, xp->buffer[0] & 63); fprintf (fp, " length=%02d:%02d", xp->buffer[5] & 63, xp->buffer[4] & 63); fprintf (fp, " D=%u L=%u Z=%u T=%u)", !!(xp->buffer[1] & 0x20), !!(xp->buffer[2] & 0x20), !!(xp->buffer[3] & 0x20), !!(xp->buffer[3] & 0x10)); break; case VBI_XDS_SUPPLEMENTAL_DATA_LOCATION: { unsigned int i; fputs ("supplemental data)", fp); xdump (xp, fp); if (xp->buffer_size < 1) goto invalid; fputc ('(', fp); for (i = 0; i < xp->buffer_size; ++i) { fprintf (fp, "%sfield=%u line=%u", (0 == i) ? "" : ", ", !!(xp->buffer[i] & 0x20), xp->buffer[i] & 31); } fputc (')', fp); break; } case VBI_XDS_LOCAL_TIME_ZONE: fputs ("time zone)", fp); xdump (xp, fp); if (1 != xp->buffer_size) goto invalid; fprintf (fp, " (UTC%+05d ods=%u)", (xp->buffer[0] & 31) * -100, !!(xp->buffer[0] & 0x20)); break; case VBI_XDS_OUT_OF_BAND_CHANNEL: { unsigned int channel; fputs ("out of band channel number)", fp); xdump (xp, fp); if (2 != xp->buffer_size) goto invalid; channel = (xp->buffer[0] & 63) + ((xp->buffer[1] & 63) << 6); fprintf (fp, " (%u)", channel); break; } case VBI_XDS_CHANNEL_MAP_POINTER: { unsigned int channel; fputs ("channel map pointer)", fp); xdump (xp, fp); if (2 != xp->buffer_size) goto invalid; channel = (xp->buffer[0] & 63) + ((xp->buffer[1] & 63) << 6); fprintf (fp, " (%u)", channel); break; } case VBI_XDS_CHANNEL_MAP_HEADER: { unsigned int n_channels; unsigned int version; fputs ("channel map header)", fp); xdump (xp, fp); if (4 != xp->buffer_size) goto invalid; n_channels = (xp->buffer[0] & 63) + ((xp->buffer[1] & 63) << 6); version = xp->buffer[2] & 63; fprintf (fp, " (n_channels: %u, version: %u)", n_channels, version); break; } case VBI_XDS_CHANNEL_MAP: { unsigned int channel; vbi_bool remapped; fputs ("channel map)", fp); xdump (xp, fp); channel = (xp->buffer[0] & 63) + ((xp->buffer[1] & 31) << 6); fprintf (fp, " (channel: %u)", channel); remapped = !!(xp->buffer[1] & 0x20); if (remapped) { unsigned int tune_channel; tune_channel = (xp->buffer[2] & 63) + ((xp->buffer[3] & 63) << 6); fprintf (fp, ", remapped to: %u)", tune_channel); } /* channel id: buffer[2 or 4 ... 31] (xdump'ed) */ fputc (')', fp); break; } default: fputs ("?)", fp); xdump (xp, fp); break; } break; case VBI_XDS_CLASS_PUBLIC_SERVICE: fputs ("(pub. service ", fp); switch (xp->xds_subclass) { case VBI_XDS_WEATHER_BULLETIN: { unsigned int duration; fputs ("weather bulletin)", fp); xdump (xp, fp); fprintf (fp, " (event category: "); for (i = 0; i < 3; ++i) { fputc (_vbi_to_ascii (xp->buffer[i]), fp); } /* 3 digit FIPS number. */ fprintf (fp, ", state: "); for (i = 3; i < 6; ++i) { fputc (_vbi_to_ascii (xp->buffer[i]), fp); } /* 3 digit FIPS number. */ fprintf (fp, ", county: "); for (i = 6; i < 9; ++i) { fputc (_vbi_to_ascii (xp->buffer[i]), fp); } /* 2 digit number of quarter hours */ duration = (xp->buffer[9] & 15) * 150 + (xp->buffer[10] & 15) * 15; fprintf (fp, ", duration: %02d:%02d)", duration / 60, duration % 60); break; } case VBI_XDS_WEATHER_MESSAGE: fputs ("weather message)", fp); xdump (xp, fp); break; } break; case VBI_XDS_CLASS_RESERVED: fputs ("(reserved)", fp); xdump (xp, fp); break; case VBI_XDS_CLASS_UNDEFINED: fputs ("(undefined)", fp); xdump (xp, fp); break; default: fputs ("(?)", fp); xdump (xp, fp); break; } fputc ('\n', fp); } /** * @param xd XDS demultiplexer context allocated with vbi_xds_demux_new(). * * Resets the XDS demux, useful for example after a channel change. * * @since 0.2.16 */ void vbi_xds_demux_reset (vbi_xds_demux * xd) { unsigned int n; unsigned int i, j; assert (NULL != xd); n = N_ELEMENTS (xd->subpacket[0]); for (i = 0; i < N_ELEMENTS (xd->subpacket); ++i) { for (j = 0; j < N_ELEMENTS (xd->subpacket[0]); ++j) { xd->subpacket[i][j].count = 0; } } xd->curr_sp = NULL; } /** * @param xd XDS demultiplexer context allocated with vbi_xds_demux_new(). * @param buffer Closed Caption character pair, as in struct vbi_sliced. * * This function takes two successive bytes of a raw Closed Caption * stream, filters out XDS data and calls the output function given to * vbi_xds_demux_new() when a new packet is complete. * * You should feed only data from NTSC line 284. * * @returns * @c FALSE if the buffer contained parity errors. * * @since 0.2.16 */ vbi_bool vbi_xds_demux_feed (vbi_xds_demux * xd, const uint8_t buffer[2]) { _vbi_xds_subpacket *sp; vbi_bool r; int c1, c2; assert (NULL != xd); assert (NULL != buffer); r = TRUE; sp = xd->curr_sp; log ("XDS demux %02x %02x\n", buffer[0], buffer[1]); c1 = vbi_unpar8 (buffer[0]); c2 = vbi_unpar8 (buffer[1]); if ((c1 | c2) < 0) { log ("XDS tx error, discard current packet\n"); if (sp) { sp->count = 0; sp->checksum = 0; } xd->curr_sp = NULL; return FALSE; } switch (c1) { case 0x00: /* Stuffing. */ break; case 0x01 ... 0x0E: { vbi_xds_class xds_class; vbi_xds_subclass xds_subclass; unsigned int i; /* Packet header. */ xds_class = (c1 - 1) >> 1; xds_subclass = c2; i = xds_subclass; /* MISC subclass 0x4n */ if (i >= 0x40) i += 0x10 - 0x40; if (xds_class > VBI_XDS_CLASS_MISC || i > N_ELEMENTS (xd->subpacket[0])) { log ("XDS ignore packet 0x%x/0x%02x, " "unknown class or subclass\n", xds_class, xds_subclass); goto discard; } sp = &xd->subpacket[xds_class][i]; xd->curr_sp = sp; xd->curr.xds_class = xds_class; xd->curr.xds_subclass = xds_subclass; if (c1 & 1) { /* Start packet. */ sp->checksum = c1 + c2; sp->count = 2; } else { /* Continue packet. */ if (0 == sp->count) { log ("XDS can't continue packet " "0x%x/0x%02x, missed start\n", xd->curr.xds_class, xd->curr.xds_subclass); goto discard; } } break; } case 0x0F: /* Packet terminator. */ if (!sp) { log ("XDS can't finish packet, missed start\n"); break; } sp->checksum += c1 + c2; if (0 != (sp->checksum & 0x7F)) { log ("XDS ignore packet 0x%x/0x%02x, " "checksum error\n", xd->curr.xds_class, xd->curr.xds_subclass); } else if (sp->count <= 2) { log ("XDS ignore empty packet 0x%x/0x%02x\n", xd->curr.xds_class, xd->curr.xds_subclass); } else { memcpy (xd->curr.buffer, sp->buffer, 32); xd->curr.buffer_size = sp->count - 2; xd->curr.buffer[sp->count - 2] = 0; if (XDS_DEMUX_LOG) _vbi_xds_packet_dump (&xd->curr, stderr); r = xd->callback (xd, &xd->curr, xd->user_data); } /* fall through */ discard: if (sp) { sp->count = 0; sp->checksum = 0; } /* fall through */ case 0x10 ... 0x1F: /* Closed Caption. */ xd->curr_sp = NULL; break; case 0x20 ... 0x7F: /* Packet contents. */ if (!sp) { log ("XDS can't store packet, missed start\n"); break; } if (sp->count >= sizeof (sp->buffer) + 2) { log ("XDS discard packet 0x%x/0x%02x, " "buffer overflow\n", xd->curr.xds_class, xd->curr.xds_subclass); goto discard; } sp->buffer[sp->count - 2] = c1; sp->buffer[sp->count - 1] = c2; sp->checksum += c1 + c2; sp->count += 1 + (0 != c2); break; } return r; } /** * @param xd XDS demultiplexer context allocated with vbi_xds_demux_new(). * @param sliced Sliced VBI data. * @param n_lines Number of lines in the @a sliced array. * * This function works like vbi_xds_demux_feed() but operates on sliced * VBI data and filters out @c VBI_SLICED_CAPTION_525 on NTSC line 284. * * @returns * @c FALSE if any of the Caption lines contained parity errors. * * @since 0.2.26 */ vbi_bool vbi_xds_demux_feed_frame (vbi_xds_demux * xd, const vbi_sliced * sliced, unsigned int n_lines) { const vbi_sliced *end; assert (NULL != xd); assert (NULL != sliced); for (end = sliced + n_lines; sliced < end; ++sliced) { switch (sliced->id) { case VBI_SLICED_CAPTION_525: case VBI_SLICED_CAPTION_525_F2: if (284 != sliced->line && 0 != sliced->line) continue; if (!vbi_xds_demux_feed (xd, sliced->data)) return FALSE; break; default: break; } } return TRUE; } #if 0 /* ideas */ const vbi_xds_packet * vbi_xds_demux_get_packet (vbi_xds_demux * xd, vbi_xds_class xds_class, vbi_xds_subclass xds_subclass) { /* most recently received packet of this class. */ } const vbi_xds_packet * vbi_xds_demux_cor (vbi_xds_demux * xd, const uint8_t buffer[2]) { ... } void vbi_xds_demux_set_log (vbi_xds_demux * xd, vbi_log_fn * callback, unsigned int pri_mask) { ... } #endif /** @internal */ void _vbi_xds_demux_destroy (vbi_xds_demux * xd) { assert (NULL != xd); CLEAR (*xd); } /** @internal */ vbi_bool _vbi_xds_demux_init (vbi_xds_demux * xd, vbi_xds_demux_cb * callback, void * user_data) { assert (NULL != xd); assert (NULL != callback); vbi_xds_demux_reset (xd); xd->callback = callback; xd->user_data = user_data; return TRUE; } /** * @param xd XDS demultiplexer context allocated with * vbi_xds_demux_new(), can be @c NULL. * * Frees all resources associated with @a xd. * * @since 0.2.16 */ void vbi_xds_demux_delete (vbi_xds_demux * xd) { if (NULL == xd) return; _vbi_xds_demux_destroy (xd); free (xd); } /** * @param callback Function to be called by vbi_xds_demux_feed() when * a new packet is available. * @param user_data User pointer passed through to @a callback function. * * Allocates a new Extended Data Service (EIA 608) demultiplexer. * * @returns * Pointer to newly allocated XDS demux context which must be * freed with vbi_xds_demux_delete() when done. @c NULL on failure * (out of memory). * * @since 0.2.16 */ vbi_xds_demux * vbi_xds_demux_new (vbi_xds_demux_cb * callback, void * user_data) { vbi_xds_demux *xd; assert (NULL != callback); if (!(xd = malloc (sizeof (*xd)))) { return NULL; } _vbi_xds_demux_init (xd, callback, user_data); return xd; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/src/xds_demux.h000066400000000000000000000135611476363111200155250ustar00rootroot00000000000000/* * libzvbi -- Extended Data Service demultiplexer * * Copyright (C) 2000-2005 Michael H. Schimek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ /* $Id: xds_demux.h,v 1.11 2008-02-24 14:16:16 mschimek Exp $ */ #ifndef __ZVBI_XDS_DEMUX_H__ #define __ZVBI_XDS_DEMUX_H__ #include /* uint8_t */ #include /* FILE */ #include "macros.h" #include "sliced.h" VBI_BEGIN_DECLS /* Public */ /** * @addtogroup XDSDemux * @{ */ /** * @brief XDS packet class. * XDS data is transmitted in packets. Each packet belongs * to one of seven classes. */ typedef enum { VBI_XDS_CLASS_CURRENT = 0x00, VBI_XDS_CLASS_FUTURE, VBI_XDS_CLASS_CHANNEL, VBI_XDS_CLASS_MISC, VBI_XDS_CLASS_PUBLIC_SERVICE, VBI_XDS_CLASS_RESERVED, VBI_XDS_CLASS_UNDEFINED } vbi_xds_class; #define VBI_XDS_MAX_CLASSES (VBI_XDS_CLASS_UNDEFINED + 1) /** * @brief @c VBI_XDS_CLASS_CURRENT and @c VBI_XDS_CLASS_FUTURE subclass. */ typedef enum { VBI_XDS_PROGRAM_ID = 0x01, VBI_XDS_PROGRAM_LENGTH, VBI_XDS_PROGRAM_NAME, VBI_XDS_PROGRAM_TYPE, VBI_XDS_PROGRAM_RATING, VBI_XDS_PROGRAM_AUDIO_SERVICES, VBI_XDS_PROGRAM_CAPTION_SERVICES, VBI_XDS_PROGRAM_CGMS, VBI_XDS_PROGRAM_ASPECT_RATIO, /** @since 0.2.17 */ VBI_XDS_PROGRAM_DATA = 0x0C, /** @since 0.2.17 */ VBI_XDS_PROGRAM_MISC_DATA, VBI_XDS_PROGRAM_DESCRIPTION_BEGIN = 0x10, VBI_XDS_PROGRAM_DESCRIPTION_END = 0x18 } vbi_xds_subclass_program; /** @brief @c VBI_XDS_CLASS_CHANNEL subclass. */ typedef enum { VBI_XDS_CHANNEL_NAME = 0x01, VBI_XDS_CHANNEL_CALL_LETTERS, VBI_XDS_CHANNEL_TAPE_DELAY, /** @since 0.2.17 */ VBI_XDS_CHANNEL_TSID } vbi_xds_subclass_channel; /** @brief @c VBI_XDS_CLASS_MISC subclass. */ typedef enum { VBI_XDS_TIME_OF_DAY = 0x01, VBI_XDS_IMPULSE_CAPTURE_ID, VBI_XDS_SUPPLEMENTAL_DATA_LOCATION, VBI_XDS_LOCAL_TIME_ZONE, /** @since 0.2.17 */ VBI_XDS_OUT_OF_BAND_CHANNEL = 0x40, /** @since 0.2.17 */ VBI_XDS_CHANNEL_MAP_POINTER, /** @since 0.2.17 */ VBI_XDS_CHANNEL_MAP_HEADER, /** @since 0.2.17 */ VBI_XDS_CHANNEL_MAP } vbi_xds_subclass_misc; #ifndef DOXYGEN_SHOULD_SKIP_THIS /* Compatibility. */ #define VBI_XDS_MISC_TIME_OF_DAY VBI_XDS_TIME_OF_DAY #define VBI_XDS_MISC_IMPULSE_CAPTURE_ID VBI_XDS_IMPULSE_CAPTURE_ID #define VBI_XDS_MISC_SUPPLEMENTAL_DATA_LOCATION \ VBI_XDS_SUPPLEMENTAL_DATA_LOCATION #define VBI_XDS_MISC_LOCAL_TIME_ZONE VBI_XDS_LOCAL_TIME_ZONE #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief @c VBI_XDS_CLASS_PUBLIC_SERVICE subclass. * @since 0.2.17 */ typedef enum { VBI_XDS_WEATHER_BULLETIN = 0x01, VBI_XDS_WEATHER_MESSAGE } vbi_xds_subclass_public_service; #define VBI_XDS_MAX_SUBCLASSES (0x18) /** * @brief Generic XDS subclass. * You must cast to the appropriate * subclass type depending on the XDS class. */ typedef unsigned int vbi_xds_subclass; /** * @brief XDS Packet. * A pointer to this structure is passed to the XDS demux callback. * * @note The structure may grow in the future. */ typedef struct { vbi_xds_class xds_class; vbi_xds_subclass xds_subclass; /** XDS packets have variable length 1 ... 32 bytes. */ unsigned int buffer_size; /** * Packet data. Bit 7 (odd parity) is cleared, * buffer[buffer_size] is 0. */ uint8_t buffer[36]; } vbi_xds_packet; /** @} */ extern void _vbi_xds_packet_dump (const vbi_xds_packet * xp, FILE * fp); /** * @addtogroup XDSDemux * @{ */ /** * @brief XDS demultiplexer. * * The contents of this structure are private. * Call vbi_xds_demux_new() to allocate a XDS demultiplexer. */ typedef struct _vbi_xds_demux vbi_xds_demux; /** * @param xd XDS demultiplexer context allocated with vbi_xds_demux_new(). * @param user_data User data pointer given to vbi_xds_demux_new(). * @param xp Pointer to the received XDS data packet. * * The XDS demux calls a function of this type when an XDS packet * has been completely received, all bytes have correct parity and the * packet checksum is correct. Other packets are discarded. * * @returns * FALSE on error, will be returned by vbi_xds_demux_feed(). */ typedef vbi_bool vbi_xds_demux_cb (vbi_xds_demux * xd, const vbi_xds_packet * xp, void * user_data); extern void vbi_xds_demux_reset (vbi_xds_demux * xd); extern vbi_bool vbi_xds_demux_feed (vbi_xds_demux * xd, const uint8_t buffer[2]); extern vbi_bool vbi_xds_demux_feed_frame (vbi_xds_demux * xd, const vbi_sliced * sliced, unsigned int n_lines); extern void vbi_xds_demux_delete (vbi_xds_demux * xd); extern vbi_xds_demux * vbi_xds_demux_new (vbi_xds_demux_cb * callback, void * user_data) _vbi_alloc; /** @} */ /* Private */ /** * @internal */ typedef struct { uint8_t buffer[32]; unsigned int count; unsigned int checksum; } _vbi_xds_subpacket; /** * @internal */ struct _vbi_xds_demux { _vbi_xds_subpacket subpacket[VBI_XDS_MAX_CLASSES] [VBI_XDS_MAX_SUBCLASSES]; vbi_xds_packet curr; _vbi_xds_subpacket * curr_sp; vbi_xds_demux_cb * callback; void * user_data; }; extern void _vbi_xds_demux_destroy (vbi_xds_demux * xd); extern vbi_bool _vbi_xds_demux_init (vbi_xds_demux * xd, vbi_xds_demux_cb * callback, void * user_data); VBI_END_DECLS #endif /* __ZVBI_XDS_DEMUX_H__ */ /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/000077500000000000000000000000001476363111200135365ustar00rootroot00000000000000zvbi-0.2.44/test/Makefile.am000066400000000000000000000077721476363111200156070ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in compile_tests = \ ctest$(EXEEXT) \ cpptest$(EXEEXT) cpptest_SOURCES = cpptest.cc if HAVE_GCC_C89_SUPPORT compile_tests += ctest-c89$(EXEEXT) ctest-gnu89$(EXEEXT) ctest_c89_SOURCES = ctest.c ctest_c89_CFLAGS = -pedantic-errors -std=c89 ctest_gnu89_SOURCES = ctest.c ctest_gnu89_CFLAGS = -pedantic-errors -std=gnu89 endif if HAVE_GCC_C94_SUPPORT compile_tests += ctest-c94$(EXEEXT) ctest_c94_SOURCES = ctest.c ctest_c94_CFLAGS = -pedantic-errors -std=iso9899:199409 endif if HAVE_GCC_C99_SUPPORT compile_tests += ctest-c99$(EXEEXT) ctest-gnu99$(EXEEXT) ctest_c99_SOURCES = ctest.c ctest_c99_CFLAGS = -pedantic-errors -std=c99 ctest_gnu99_SOURCES = ctest.c ctest_gnu99_CFLAGS = -pedantic-errors -std=gnu99 endif if HAVE_GXX_CXX98_SUPPORT compile_tests += cpptest-cxx98$(EXEEXT) cpptest-gnuxx98$(EXEEXT) cpptest_cxx98_SOURCES = cpptest.cc cpptest_cxx98_CXXFLAGS = -pedantic-errors -std=c++98 cpptest_gnuxx98_SOURCES = cpptest.cc cpptest_gnuxx98_CXXFLAGS = -pedantic-errors -std=gnu++98 endif scripts = # Rewrite scripts to execute in linux for windows if LINUX_FOR_WINDOWS scripts += \ exoptest_windows \ test-unicode_windows endif if !WINDOWS scripts += \ exoptest \ test-unicode endif # Currently failing in windows, need to debug if WINDOWS only_linux = else only_linux = \ test-pdc$(EXEEXT) # TODO: Fix timezone conversions endif TESTS = \ $(compile_tests) \ $(only_linux) \ $(scripts) \ test-dvb_demux$(EXEEXT) \ test-dvb_mux$(EXEEXT) \ test-hamm$(EXEEXT) \ test-packet-830$(EXEEXT) \ test-raw_decoder$(EXEEXT) \ test-vps$(EXEEXT) check_PROGRAMS = \ $(compile_tests) \ $(only_linux) \ test-dvb_demux$(EXEEXT) \ test-dvb_mux$(EXEEXT) \ test-hamm$(EXEEXT) \ test-packet-830$(EXEEXT) \ test-raw_decoder$(EXEEXT) \ test-vps$(EXEEXT) check_SCRIPTS = \ $(scripts) test_dvb_demux_SOURCES = \ test-dvb_demux.cc \ test-common.cc test-common.h test_dvb_mux_SOURCES = \ test-dvb_mux.cc \ test-common.cc test-common.h test_hamm_SOURCES = test-hamm.cc test_packet_830_SOURCES = \ test-packet-830.cc \ test-pdc.h \ test-common.cc test-common.h test_pdc_SOURCES = \ test-pdc.cc test-pdc.h \ test-common.cc test-common.h test_raw_decoder_SOURCES = \ test-raw_decoder.cc \ test-common.cc test-common.h test_vps_SOURCES = \ test-vps.cc \ test-pdc.h \ test-common.cc test-common.h # exoptest: explist # test-unicode: unicode if ENABLE_PROXY proxy_programs = proxy-test$(EXEEXT) else proxy_programs = endif if HAVE_X x_programs = caption$(EXEEXT) osc$(EXEEXT) else x_programs = endif noinst_PROGRAMS = \ capture$(EXEEXT) \ date$(EXEEXT) \ decode$(EXEEXT) \ explist$(EXEEXT) \ export$(EXEEXT) \ glyph$(EXEEXT) \ sliced2pes$(EXEEXT) \ test-vps$(EXEEXT) \ ttxfilter$(EXEEXT) \ unicode$(EXEEXT) \ $(proxy_programs) \ $(x_programs) capture_SOURCES = \ capture.c \ sliced.c sliced.h caption_SOURCES = \ caption.c \ sliced.c sliced.h date_SOURCES = \ date.c \ sliced.c sliced.h decode_SOURCES = \ decode.c \ sliced.c sliced.h export_SOURCES = \ export.c \ sliced.c sliced.h sliced2pes_SOURCES = \ sliced2pes.c \ sliced.c sliced.h ttxfilter_SOURCES = \ ttxfilter.c \ sliced.c sliced.h noinst_SCRIPTS = \ uclist EXTRA_DIST = \ README \ $(noinst_SCRIPTS) \ $(check_SCRIPTS) \ unicode-out-ref.txt CLEANFILES = \ unicode-out.txt AM_CFLAGS = \ -I$(top_srcdir) \ $(X_CFLAGS) \ -D_REENTRANT \ -D_GNU_SOURCE AM_CPPFLAGS = $(AM_CFLAGS) # Libtool creates a wrapper script around the application until the # library is installed, which is a major annoyance for debugging with # gdb and valgrind. So we link these apps statically if possible. if BUILD_STATIC_LIB LDADD = $(top_builddir)/src/.libs/libzvbi.a else LDADD = $(top_builddir)/src/libzvbi.la endif LDADD += \ $(LIBS) \ $(X_LIBS) unrename: for file in *.cc *.c *.h ; do \ case "$$file" in \ test-raw_decoder.cc|osc.c) continue ; ;; \ esac ; \ if grep -q -i vbi3_ $$file ; then \ sed 's/vbi3_/vbi_/g;s/VBI3_/VBI_/g' <$$file >tmp ; \ mv tmp $$file ; \ fi ; \ done zvbi-0.2.44/test/README000066400000000000000000000136201476363111200144200ustar00rootroot00000000000000Test programs unicode Prints all Teletext and Closed Caption character sets used by libzvbi in UTF-8 format. Purpose: compare against specifications to verify libzvbi character code to Unicode conversions. uclist Lists all Unicode characters used in a UTF-8 coded text on stdin. NB this script needs access to . Purpose: verify the libzvbi built-in fonts contain all required glyphs. glyph Prints all Teletext and Closed Caption character sets used by libzvbi as PPM images. Purpose: compare against specifications to verify libzvbi exp-gfx.c Unicode to glyph conversions. explist Lists all export modules and their respective options. If option -c/--check given, tests the export [module] api. Use this to test new modules. export Exports a Teletext page from a TTX sample stream on stdin, eg. ./export "text;charset=ISO-8859-1,control=1" 100 \ foo.txt ./capture --sim --pal --sliced | ./export "png" 100 >foo.png ./capture --pes | ./export "html" 300 >foo.html Purpose: test teletext decoder and export modules. (Sample streams are in CVS zapping/libvbi/samples, versions before 2003-11.) caption Closed Caption test displaying a hello world message or the CC sample stream on stdin, to debug the CC decoder. Note this works only on a X11 5:6:5 LE display. The keys F1-F8 switch between the Closed Caption channels 1-4 and Text channels 1-4. osc Raw vbi data visualization. Purpose: test vbi device interface, raw vbi decoder. Works only on X11 5:6:5 LE display. Options: -d | --device name Default is /dev/vbi. -s | --sim Create artificial raw vbi data instead of opening a vbi device, to test decoding in absence of a real source. -v | --verbose Trace device access on stderr, may reveal why opening a device failed. -p | --pal, -n | --ntsc Video standard hint for simulation and v4l. When not given libzvbi may have to guess which can fail. Default is PAL. -1 | --v4l On Linux, use the bttv / V4L interface if supported by the driver. -2 | --v4l2-read Use V4L2 interface in read mode. -3 | --v4l2-mmap Use V4L2 interface in mmap mode (default). -e | --ignore-error Ignore read errors. (--long options are only available on GNU systems.) capture Capture sliced vbi data. Purpose: test vbi device interface, raw vbi decoder. Options: -d | --device name Default is /dev/vbi. -s | --sim Create artificial raw vbi data instead of opening a vbi device, to test decoding in absence of a real source. -v | --verbose Trace device access on stderr, may reveal why opening a device failed. -vv enables ioctl logging. -p | --pal, -n | --ntsc Video standard hint for simulation and v4l. When not given libzvbi may have to guess which can fail. Default is PAL. -1 | --v4l On Linux, use the bttv / V4L interface if supported by the driver. -2 | --v4l2-read Use V4L2 interface in read mode. -3 | --v4l2-mmap Use V4L2 interface in mmap mode (default). -i | --pid n Use Linux DVB interface instead and filter out VBI packets with this PID. -e | --ignore-error Ignore read errors. -r | --strict n How strictly shall the raw VBI decoder match VBI services against driver capabilities? 0 - loose, let's try anyway (default) 1 - medium, usually no problems with the accepted services (see --verbose) 2 - pedantic, when the signal is good all accepted services will work --read Use libzvbi capture read interface (default). --pull Use libzvbi capture pull interface, to test if it works. Options to write sliced vbi data _as_ASCII_ to standard output (these are moving to test/decode, see there for more options): --dump-wss Wide Screen Signalling (PAL & NTSC-Japan). --dump-vps Video Programming System (PAL in DE/AT/CH). --dump-sliced Everything. Options to write sliced vbi data _in_binary_format_ to standard output. This can be piped "./capture --pes | ./export " or "./capture --sliced | ./decode " or "./capture --sliced | ./caption". -l | --sliced Write sliced vbi data (an old ad-hoc format). -P | --pes Write DVB PES packets. Note when the source is a DVB device this will not pass through but de- and remultiplex PES packets. PES output is experimental, use at your own risk. -T | --ts Write TS packets with PID 999 (experimental) The caption, decode and export tools all support --sliced and --pes format (may require a --pes option to recognize the format). --long options are only available on GNU & compatible systems. Note you will need another application to tune in a channel, for example v4lctl from the xawtv package or czap/szap/tzap from the dvbutils package: v4lctl setstation BBC ./capture --device /dev/vbi --sliced | ./export text 100 tzap BBC # keep it running in another terminal window ./capture --device /dev/dvb/adapter0/demux0 --pid 1234 --sliced | \ ./export text 100 The capture tool cannot automatically determine the PID of the DVB VBI stream. To find it you will need another tool evaluating the service information tables transmitted by the network, or you can look for the PID of the video stream in the tzap config file and try a slightly higher number. decode Decodes sliced VBI data on stdin, e. g. ./capture --sliced | ./decode --ttx Also a test of the libzvbi low-level decoders. Type ./decode -h for options. sliced2pes Converts ./capture --sliced format to DVB PES. For example "./capture --sliced | ./sliced2pes >file" is equivalent to "./capture --pes >file". Type ./sliced2pes -h for options. ttxfilter Filters Teletext pages out of a VBI stream. Useful mostly to extract subtitles, for example "./capture --sliced | ./ttxfilter 150 777 300-400 >file". Type ./ttxfilter -h for options. test-*.cc Unit tests (make check). The ultimate test is http://zapping.sourceforge.net, the TV viewer this library was written for. zvbi-0.2.44/test/caption.c000066400000000000000000000520671476363111200153510ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2000, 2001 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: caption.c,v 1.21 2013-08-28 14:44:44 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #ifdef HAVE_GETOPT_LONG # include #endif #ifndef X_DISPLAY_MISSING #include #include #include #include "src/vbi.h" #include "src/exp-gfx.h" #include "src/hamm.h" #include "src/dvb_demux.h" #include "src/cc608_decoder.h" #include "sliced.h" #define PROGRAM_NAME "caption" static const char * option_in_file_name; static enum file_format option_in_file_format; static unsigned int option_in_ts_pid; static vbi_bool option_use_cc608_decoder; static vbi_bool option_use_cc608_event; static double option_frame_rate; static struct stream * rst; /* For real time playback of recorded streams. */ static double wait_until; static double frame_period; /* Teletext/CC/VPS/WSS decoder. */ static vbi_decoder * vbi; /* Closed Caption decoder. */ static _vbi_cc608_decoder * cd; #define WINDOW_WIDTH 640 #define WINDOW_HEIGHT 480 /* Character cell size. */ #define CELL_WIDTH 16 #define CELL_HEIGHT 26 /* Maximum text size in characters. TEXT_COLUMNS includes two spaces added for legibility. */ #define TEXT_COLUMNS 34 #define TEXT_ROWS 15 /* Maximum text size in pixels, not counting the vertical offset for smooth rolling. */ #define TEXT_WIDTH (TEXT_COLUMNS * CELL_WIDTH) #define TEXT_HEIGHT (TEXT_ROWS * CELL_HEIGHT) static Display * display; static int screen; static Colormap cmap; static Window window; static GC gc; static uint8_t * ximgdata; /* Color of the "video pixels" we overlay. */ static const vbi_rgba video_color = 0x80FF80; /* 0xBBGGRR */ static XColor video_xcolor; /* Color of the border around the text, if any. */ static const vbi_rgba border_color = 0xFF8080; static XColor border_xcolor; /* Display and ximage color depth: 15, 16, 24, 32 bits */ static unsigned int color_depth; static XImage * ximage; /* Current vertical offset for smooth rolling, 0 ... CELL_HEIGHT - 1. */ static unsigned int vert_offset; /* Draw the ximage into the window. */ static vbi_bool update_display; /* The currently displayed page. */ static vbi_page curr_page; /* Draw characters with flash attribute in on or off state. */ static vbi_bool flash_on; static unsigned int flash_count; /* Get a new copy of the current page from the Caption decoder and redraw the ximage. */ static vbi_bool redraw_page; /* Switches. */ /* Currently selected Caption channel. */ static vbi_pgno channel; /* Add spaces for legibility. */ static vbi_bool padding; /* Show a border around the text area. */ static vbi_bool show_border; static vbi_bool smooth_rolling; static void put_image (void) { unsigned int x; unsigned int y; unsigned int width; /* 32 or with padding 34 columns. */ width = curr_page.columns * CELL_WIDTH; x = (WINDOW_WIDTH - width) / 2; /* vert_offset is between 0 ... CELL_HEIGHT - 1. */ y = vert_offset + (WINDOW_HEIGHT - (TEXT_HEIGHT + CELL_HEIGHT)) / 2; if (show_border) XSetForeground (display, gc, border_xcolor.pixel); else XSetForeground (display, gc, video_xcolor.pixel); XFillRectangle (display, (Drawable) window, gc, /* x, y */ 0, 0, WINDOW_WIDTH, /* height */ y); XFillRectangle (display, (Drawable) window, gc, /* x, y */ 0, y, x, TEXT_HEIGHT); XPutImage (display, window, gc, ximage, /* src_x, src_y */ 0, 0, /* dest_x, dest_y */ x, y, width, TEXT_HEIGHT); XFillRectangle (display, (Drawable) window, gc, /* x, y */ x + width, y, x, TEXT_HEIGHT); XFillRectangle (display, (Drawable) window, gc, /* x, y */ 0, y + TEXT_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT - (y + TEXT_HEIGHT)); } static void draw_transparent_spaces (unsigned int column, unsigned int row, unsigned int n_columns) { uint8_t *d; const uint8_t *s; unsigned int i; unsigned int j; switch (color_depth) { case 32: /* assumed to be B G R A in memory */ d = ximgdata + column * CELL_WIDTH * 4 + row * CELL_HEIGHT * TEXT_WIDTH * 4; for (j = 0; j < CELL_HEIGHT; ++j) { uint32_t *d32 = (uint32_t *) d; for (i = 0; i < n_columns * CELL_WIDTH; ++i) d32[i] = video_xcolor.pixel; d += TEXT_WIDTH * 4; } break; case 24: /* assumed to be B G R in memory */ d = ximgdata + column * CELL_WIDTH * 3 + row * CELL_HEIGHT * TEXT_WIDTH * 3; s = (const uint8_t *) &video_xcolor.pixel; if (Z_BYTE_ORDER == Z_BIG_ENDIAN) s += sizeof (video_xcolor.pixel) - 3; for (j = 0; j < CELL_HEIGHT; ++j) { for (i = 0; i < n_columns * CELL_WIDTH; ++i) memcpy (d + i * 3, s, 3); d += TEXT_WIDTH * 3; } break; case 16: /* assumed to be gggbbbbb rrrrrggg in memory */ case 15: /* assumed to be gggbbbbb arrrrrgg in memory */ d = ximgdata + column * CELL_WIDTH * 2 + row * CELL_HEIGHT * TEXT_WIDTH * 2; for (j = 0; j < CELL_HEIGHT; ++j) { uint16_t *d16 = (uint16_t *) d; for (i = 0; i < n_columns * CELL_WIDTH; ++i) d16[i] = video_xcolor.pixel; d += TEXT_WIDTH * 2; } break; default: assert (0); } } static void draw_character (vbi_page * pg, unsigned int column, unsigned int row) { vbi_rgba buffer[CELL_WIDTH * CELL_HEIGHT]; const vbi_rgba *s; uint8_t *p; unsigned int i; unsigned int j; /* Regrettably at the moment this function supports only one pixel format. */ vbi_draw_cc_page_region (pg, VBI_PIXFMT_RGBA32_LE, buffer, /* bytes_per_line */ sizeof (buffer) / CELL_HEIGHT, column, row, /* n_columns, n_rows */ 1, 1); /* Alpha blending. */ if (option_use_cc608_decoder) { for (i = 0; i < CELL_WIDTH * CELL_HEIGHT; ++i) { uint8_t alpha; /* (text * alpha + video * (0xFF - alpha)) / 0xFF. */ alpha = buffer[i] >> 24; if (alpha >= 0xFF) { /* Most likely case, nothing to do. */ } else if (0 == alpha) { buffer[i] = video_color; } else { unsigned int text; /* There are really just three alpha levels: 0x00, 0x80, and 0xFF so we take a (not quite correct) shortcut. */ text = (((buffer[i] & 0xFF00FF) + (video_color & 0xFF00FF)) >> 1) & 0xFF00FF; text |= (((buffer[i] & 0xFF00) + (video_color & 0xFF00)) >> 1) & 0xFF00; buffer[i] = text; } } } s = buffer; switch (color_depth) { case 32: /* assumed to be B G R A in memory */ p = ximgdata + column * CELL_WIDTH * 4 + row * CELL_HEIGHT * TEXT_WIDTH * 4; for (j = 0; j < CELL_HEIGHT; ++j) { for (i = 0; i < CELL_WIDTH; ++i) { p[i * 4 + 0] = *s >> 16; p[i * 4 + 1] = *s >> 8; p[i * 4 + 2] = *s; p[i * 4 + 3] = 0xFF; ++s; } p += TEXT_WIDTH * 4; } break; case 24: /* assumed to be B G R in memory */ p = ximgdata + column * CELL_WIDTH * 3 + row * CELL_HEIGHT * TEXT_WIDTH * 3; for (j = 0; j < CELL_HEIGHT; ++j) { for (i = 0; i < CELL_WIDTH; ++i) { p[i * 3 + 0] = *s >> 16; p[i * 3 + 1] = *s >> 8; p[i * 3 + 2] = *s; ++s; } p += TEXT_WIDTH * 3; } break; case 16: /* assumed to be gggbbbbb rrrrrggg in memory */ p = ximgdata + column * CELL_WIDTH * 2 + row * CELL_HEIGHT * TEXT_WIDTH * 2; for (j = 0; j < CELL_HEIGHT; ++j) { for (i = 0; i < CELL_WIDTH; ++i) { unsigned int n; n = (*s >> 19) & 0x001F; n |= (*s >> 5) & 0x07E0; n |= (*s << 8) & 0xF800; ++s; p[i * 2 + 0] = n; p[i * 2 + 1] = n >> 8; } p += TEXT_WIDTH * 2; } break; case 15: /* assumed to be gggbbbbb arrrrrgg in memory */ p = ximgdata + column * CELL_WIDTH * 2 + row * CELL_HEIGHT * TEXT_WIDTH * 2; for (j = 0; j < CELL_HEIGHT; ++j) { for (i = 0; i < CELL_WIDTH; ++i) { unsigned int n; n = (*s >> 19) & 0x001F; n |= (*s >> 6) & 0x03E0; n |= (*s << 7) & 0x7C00; n |= 0x8000; ++s; p[i * 2 + 0] = n; p[i * 2 + 1] = n >> 8; } p += TEXT_WIDTH * 2; } break; } } static void draw_row (vbi_page * pg, unsigned int row) { const vbi_char *cp; unsigned int n_tspaces; int column; cp = pg->text + row * pg->columns; n_tspaces = 0; for (column = 0; column < pg->columns; ++column) { if (VBI_TRANSPARENT_SPACE == cp[column].opacity) { ++n_tspaces; continue; } if (n_tspaces > 0) { draw_transparent_spaces (column - n_tspaces, row, n_tspaces); n_tspaces = 0; } draw_character (pg, column, row); } if (n_tspaces > 0) { draw_transparent_spaces (column - n_tspaces, row, n_tspaces); } } static vbi_bool same_text (vbi_page * pg1, unsigned int row1, vbi_page * pg2, unsigned int row2) { if (pg1->columns != pg2->columns) return FALSE; return (0 == memcmp (pg1->text + row1 * pg1->columns, pg2->text + row2 * pg2->columns, pg1->columns * sizeof (pg1->text[0]))); } static void new_draw_page (vbi_page * pg) { int row; assert (0 == pg->dirty.y0); assert ((pg->rows - 1) == pg->dirty.y1); for (row = 0; row < pg->rows; ++row) { if (same_text (&curr_page, row, pg, row)) { continue; } else if (same_text (&curr_page, row + 1, pg, row)) { unsigned int row_size; /* A shortcut for roll-up caption. */ row_size = TEXT_WIDTH * CELL_HEIGHT * color_depth / 8; memmove (ximgdata + row * row_size, ximgdata + (row + 1) * row_size, row_size); } else { draw_row (pg, row); } } curr_page = *pg; } static void old_draw_page (vbi_page * pg) { int row; for (row = pg->dirty.y0; row <= pg->dirty.y1; ++row) draw_row (pg, row); /* For put_image(). */ curr_page.columns = pg->columns; } static void old_roll_up (unsigned int first_row, unsigned int last_row) { unsigned int row_size; /* In the window first_row ... last_row shift all rows up by one row (may be faster than redrawing all characters). */ assert (first_row < last_row); assert (last_row < TEXT_ROWS); row_size = TEXT_WIDTH * CELL_HEIGHT * color_depth / 8; memmove (ximgdata + first_row * row_size, ximgdata + (first_row + 1) * row_size, (last_row - first_row) * row_size); } static void old_clear_display (void) { unsigned int row; for (row = 0; row < TEXT_ROWS; ++row) { draw_transparent_spaces (/* column */ 0, row, TEXT_COLUMNS); } } static void get_and_draw_page (void) { vbi_page page; vbi_bool success; if (option_use_cc608_decoder) { success = _vbi_cc608_decoder_get_page (cd, &page, channel, padding); } else { success = vbi_fetch_cc_page (vbi, &page, channel, /* reset dirty flags */ TRUE); } assert (success); if (!flash_on) { int i; for (i = 0; i < page.rows * page.columns; ++i) { if (page.text[i].flash) { page.text[i].foreground = page.text[i].background; } } } if (option_use_cc608_decoder) { new_draw_page (&page); } else { old_draw_page (&page); } } static void event_handler (vbi_event * ev, void * user_data) { vbi_page page; vbi_bool success; user_data = user_data; switch (ev->type) { case VBI_EVENT_CAPTION: if (channel != ev->ev.caption.pgno) return; success = vbi_fetch_cc_page (vbi, &page, channel, /* reset dirty flags */ TRUE); assert (success); if (abs (page.dirty.roll) > page.rows) { old_clear_display (); update_display = TRUE; } else if (page.dirty.roll == -1) { old_roll_up (page.dirty.y0, page.dirty.y1); if (smooth_rolling) { vert_offset = CELL_HEIGHT - 2; /* field lines */ } update_display = TRUE; } else { old_draw_page (&page); update_display = TRUE; } break; case _VBI_EVENT_CC608: if (channel != ev->ev._cc608->channel) return; success = _vbi_cc608_decoder_get_page (cd, &page, channel, padding); assert (success); /* XXX Perhaps the decoder should pass a roll_offset, first_row, last_row, cc_mode? */ if (smooth_rolling && 0 != (ev->ev._cc608->flags & _VBI_CC608_START_ROLLING)) { vert_offset = CELL_HEIGHT - 2; /* field lines */ } new_draw_page (&page); update_display = TRUE; break; default: assert (0); } } static void x_event (void) { while (XPending (display)) { XEvent event; XNextEvent (display, &event); switch (event.type) { case KeyPress: { int c = XLookupKeysym (&event.xkey, 0); switch (c) { case 'b': show_border ^= TRUE; update_display = TRUE; break; case 'c': case 'q': exit (EXIT_SUCCESS); case 'p': padding ^= TRUE; redraw_page = TRUE; break; case 's': smooth_rolling ^= TRUE; if (vert_offset > 0) { vert_offset = 0; update_display = TRUE; } break; case '1' ... '8': channel = c - '1' + VBI_CAPTION_CC1; vert_offset = 0; redraw_page = TRUE; break; case XK_F1 ... XK_F8: channel = c - XK_F1 + VBI_CAPTION_CC1; vert_offset = 0; redraw_page = TRUE; break; } break; } case Expose: update_display = TRUE; break; case ClientMessage: /* WM_DELETE_WINDOW. */ exit (EXIT_SUCCESS); } } if (redraw_page) { get_and_draw_page (); redraw_page = FALSE; update_display = TRUE; } if (update_display) { put_image (); update_display = FALSE; } if (0 == flash_count) { flash_on ^= 1; flash_count = flash_on ? 20 : 10; redraw_page = TRUE; } else { --flash_count; } if (vert_offset > 0) { vert_offset -= 2 /* field lines */; update_display = TRUE; } } static void alloc_color (XColor * xc, vbi_rgba rgba) { xc->red = VBI_R (rgba) * 0x0101; xc->green = VBI_G (rgba) * 0x0101; xc->blue = VBI_B (rgba) * 0x0101; XAllocColor (display, cmap, xc); } static void init_window (int ac, char ** av) { Atom delete_window_atom; XWindowAttributes wa; unsigned int row; unsigned int image_size; ac = ac; /* unused */ av = av; display = XOpenDisplay (NULL); if (NULL == display) { error_exit ("Cannot open X display."); } screen = DefaultScreen (display); cmap = DefaultColormap (display, screen); alloc_color (&video_xcolor, video_color); alloc_color (&border_xcolor, border_color); assert (TEXT_WIDTH <= WINDOW_WIDTH); assert (TEXT_HEIGHT <= WINDOW_HEIGHT); window = XCreateSimpleWindow (display, RootWindow (display, screen), /* x, y */ 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, /* borderwidth */ 2, /* border color */ video_xcolor.pixel, /* bg color */ video_xcolor.pixel); if (0 == window) { error_exit ("Cannot open X window."); } XGetWindowAttributes (display, window, &wa); /* FIXME determine the R/B order and endianess. Currently we assume lsb == blue, little endian. */ color_depth = wa.depth; switch (wa.depth) { case 32: case 24: case 16: case 15: break; default: error_exit ("Sorry, this program cannot run " "on a screen with color depth %u.", wa.depth); } image_size = TEXT_WIDTH * TEXT_HEIGHT * wa.depth / 8; ximgdata = malloc (image_size); if (NULL == ximgdata) { no_mem_exit (); } for (row = 0; row < TEXT_ROWS; ++row) { draw_transparent_spaces (/* column */ 0, row, TEXT_COLUMNS); } ximage = XCreateImage (display, DefaultVisual (display, screen), DefaultDepth (display, screen), /* format */ ZPixmap, /* x offset */ 0, (char *) ximgdata, TEXT_WIDTH, TEXT_HEIGHT, /* bitmap_pad */ 8, /* bytes_per_line: contiguous */ 0); if (NULL == ximage) { no_mem_exit (); } delete_window_atom = XInternAtom (display, "WM_DELETE_WINDOW", /* only_if_exists */ False); XSelectInput (display, window, (KeyPressMask | ExposureMask | StructureNotifyMask)); XSetWMProtocols (display, window, &delete_window_atom, /* n_atoms */ 1); XStoreName (display, window, "Caption Test - [B|P|Q|S|F1..F8]"); gc = XCreateGC (display, window, /* valuemask */ 0, /* values */ NULL); XMapWindow (display, window); XSync (display, /* discard events */ False); } static vbi_bool decode_frame (const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { raw = raw; sp = sp; if (option_frame_rate < 1e9) { for (;;) { struct timeval tv; double now; gettimeofday (&tv, /* tz */ NULL); now = tv.tv_sec + tv.tv_usec * (1 / 1e6); if (now >= wait_until) { if (wait_until <= 0.0) wait_until = now; wait_until += 1 / option_frame_rate; break; } usleep ((wait_until - now) * 1e6); } } if (option_use_cc608_decoder) { _vbi_cc608_decoder_feed_frame (cd, sliced, n_lines, sample_time, stream_time); } else { vbi_decode (vbi, (vbi_sliced *) sliced, n_lines, sample_time); } x_event (); return TRUE; } static void usage (FILE * fp) { fprintf (fp, _("\ %s %s\n\n\ Copyright (C) 2000, 2001, 2007, 2008, 2009 Michael H. Schimek\n\ This program is licensed under GPLv2 or later. NO WARRANTIES.\n\n\ Usage: %s [options] < sliced VBI data\n\ -h | --help | --usage Print this message and exit\n\ -i | --input name Read the VBI data from this file instead\n\ of standard input\n\ -c | --cc608-decoder Use new cc608_decoder\n\ -P | --pes Source is a DVB PES stream\n\ -T | --ts pid Source is a DVB TS stream\n\ -V | --version Print the program version and exit\n\ "), PROGRAM_NAME, VERSION, program_invocation_name); } static const char short_options [] = "cehi:r:PT:V"; #ifdef HAVE_GETOPT_LONG static const struct option long_options [] = { { "cc608-decoder", no_argument, NULL, 'c' }, { "help", no_argument, NULL, 'h' }, { "usage", no_argument, NULL, 'h' }, { "cc608-event", no_argument, NULL, 'e' }, { "input", required_argument, NULL, 'i' }, { "frame-rate", required_argument, NULL, 'r' }, { "pes", no_argument, NULL, 'P' }, { "ts", required_argument, NULL, 'T' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, 0, 0 } }; #else # define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; int main (int argc, char ** argv) { vbi_bool success; init_helpers (argc, argv); option_in_file_format = FILE_FORMAT_SLICED; option_frame_rate = 1e9; for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ break; case 'c': option_use_cc608_decoder = TRUE; break; case 'h': usage (stdout); exit (EXIT_SUCCESS); case 'i': assert (NULL != optarg); option_in_file_name = optarg; break; case 'e': option_use_cc608_event = TRUE; break; case 'r': assert (NULL != optarg); option_frame_rate = strtod (optarg, NULL); break; case 'P': option_in_file_format = FILE_FORMAT_DVB_PES; break; case 'T': option_in_ts_pid = parse_option_ts (); option_in_file_format = FILE_FORMAT_DVB_TS; break; case 'V': printf (PROGRAM_NAME " " VERSION "\n"); exit (EXIT_SUCCESS); default: usage (stderr); exit (EXIT_FAILURE); } } init_window (argc, argv); if (option_use_cc608_decoder) { /* Note this is an experimental module, the interface and output may change. */ cd = _vbi_cc608_decoder_new (); if (NULL == cd) no_mem_exit (); success = _vbi_cc608_decoder_add_event_handler (cd, _VBI_EVENT_CC608, event_handler, /* user_data */ NULL); if (!success) no_mem_exit (); } else { unsigned int event_mask; /* Teletext/CC/VPS/WSS decoder. */ vbi = vbi_decoder_new (); if (NULL == vbi) no_mem_exit (); if (option_use_cc608_event) { error_exit ("Not implemented yet.\n"); event_mask = _VBI_EVENT_CC608; } else { event_mask = VBI_EVENT_CAPTION; } success = vbi_event_handler_add (vbi, event_mask, event_handler, /* used_data */ NULL); if (!success) no_mem_exit (); } /* Switches. */ channel = VBI_CAPTION_CC1; padding = TRUE; show_border = FALSE; smooth_rolling = TRUE; rst = read_stream_new (option_in_file_name, option_in_file_format, option_in_ts_pid, decode_frame); wait_until = 0; frame_period = 1 / option_frame_rate; stream_loop (rst); stream_delete (rst); error_msg (_("End of stream.")); for (;;) { x_event (); usleep (33333); } _vbi_cc608_decoder_delete (cd); vbi_decoder_delete (vbi); exit (EXIT_SUCCESS); } #else /* X_DISPLAY_MISSING */ int main (int argc, char ** argv) { printf ("Not compiled with X11 support.\n"); exit(EXIT_FAILURE); } #endif zvbi-0.2.44/test/capture.c000066400000000000000000000460351476363111200153550ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2000, 2001, 2002 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: capture.c,v 1.39 2008-08-19 10:06:52 mschimek Exp $ */ /* For libzvbi version 0.2.x / 0.3.x. */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_LONG #include #endif #include "src/version.h" #if 2 == VBI_VERSION_MINOR # include "src/hamm.h" # define SCANNING(sp) ((sp)->scanning) #elif 3 == VBI_VERSION_MINOR # include "src/vbi.h" # include "src/zvbi.h" # include "src/misc.h" # define SCANNING(sp) \ (0 != ((sp)->videostd_set & VBI_VIDEOSTD_SET_525_60) ? 525 : 625) #else # error VBI_VERSION_MINOR == ? #endif #include "sliced.h" #undef _ #define _(x) x /* TODO */ #define PROGRAM_NAME "zvbi-capture" static const char * option_out_file_name; static enum file_format option_out_file_format; static unsigned int option_out_ts_pid; static int option_read_not_pull; static int option_strict; static int option_dump_wss; static int option_dump_sliced; static int option_cc_test; static int option_cc_test_test; static vbi_bool option_raw_output; static vbi_bool option_sliced_output; static unsigned int option_sim_flags; static struct stream * cst; static struct stream * wst; static const char ** sim_cc_streams; static unsigned int n_sim_cc_streams; struct frame { vbi_sliced sliced[50]; unsigned int n_lines; uint8_t * raw; double sample_time; int64_t stream_time; }; static struct frame frame_buffers[5]; static unsigned int next_frame; static unsigned int n_frames_buffered; static unsigned int sliced_output_count; static unsigned int raw_output_count; /* * Dump */ extern int vbi_printable (int); static void decode_wss_625 (const uint8_t * buf) { static const char *formats[] = { "Full format 4:3, 576 lines", "Letterbox 14:9 centre, 504 lines", "Letterbox 14:9 top, 504 lines", "Letterbox 16:9 centre, 430 lines", "Letterbox 16:9 top, 430 lines", "Letterbox > 16:9 centre", "Full format 14:9 centre, 576 lines", "Anamorphic 16:9, 576 lines" }; static const char *subtitles[] = { "none", "in active image area", "out of active image area", "?" }; int g1 = buf[0] & 15; int parity; if (option_dump_wss) { parity = g1; parity ^= parity >> 2; parity ^= parity >> 1; g1 &= 7; printf ("WSS PAL: "); if (!(parity & 1)) printf (" "); printf ("%s; %s mode; %s colour coding;\n" " %s helper; reserved b7=%d; %s\n" " open subtitles: %s; %scopyright %s; copying %s\n", formats[g1], (buf[0] & 0x10) ? "film" : "camera", (buf[0] & 0x20) ? "MA/CP" : "standard", (buf[0] & 0x40) ? "modulated" : "no", !!(buf[0] & 0x80), (buf[1] & 0x01) ? "have TTX subtitles; " : "", subtitles[(buf[1] >> 1) & 3], (buf[1] & 0x08) ? "surround sound; " : "", (buf[1] & 0x10) ? "asserted" : "unknown", (buf[1] & 0x20) ? "restricted" : "not restricted"); } } static void decode_wss_cpr1204 (const uint8_t * buf) { if (option_dump_wss) { const int poly = (1 << 6) + (1 << 1) + 1; int g = (buf[0] << 12) + (buf[1] << 4) + buf[2]; int j, crc; crc = g | (((1 << 6) - 1) << (14 + 6)); for (j = 14 + 6 - 1; j >= 0; j--) { if (crc & ((1 << 6) << j)) crc ^= poly << j; } fprintf (stderr, "WSS CPR >> g=%08x crc=%08x\n", g, crc); } } static void decode_sliced (const vbi_sliced * s, unsigned int n_lines, double sample_time, int64_t stream_time) { if (option_dump_sliced) { const vbi_sliced *q = s; unsigned int i; printf ("Frame %f %010" PRId64 "\n", sample_time, stream_time); for (i = 0; i < n_lines; q++, i++) { unsigned int j; printf ("%08x %3d ", q->id, q->line); for (j = 0; j < sizeof(q->data); j++) { printf ("%02x ", 0xFF & q->data[j]); } putchar (' '); for (j = 0; j < sizeof(q->data); j++) { char c = _vbi_to_ascii (q->data[j]); putchar (c); } putchar ('\n'); } } for (; n_lines > 0; s++, n_lines--) { if (s->id == 0) { continue; } else if (s->id & (VBI_SLICED_VPS | VBI_SLICED_TELETEXT_B | VBI_SLICED_CAPTION_525 | VBI_SLICED_CAPTION_625)) { /* Nothing to do. Use the 'decode' tool instead. */ } else if (s->id & VBI_SLICED_WSS_625) { decode_wss_625 (s->data); } else if (s->id & VBI_SLICED_WSS_CPR1204) { decode_wss_cpr1204 (s->data); } else { fprintf (stderr, "Oops. Unhandled VBI service %08x\n", s->id); } } } /* Purpose of this function is to record raw and sliced VBI data around three kinds of events for remote examination: - Frames without data, - Lines which contain 0x00 0x00 instead of 0x80 0x80 bytes, - Lines where the transmitted bytes have wrong parity. The raw VBI data may be required to understand what happened, and due to its volume we cannot record it unconditionally. */ static vbi_bool cc_test (const vbi_sliced * sliced, unsigned int n_lines) { static const unsigned int max_error_count[3] = { 5, 5, 5 }; static unsigned int error_count[3]; static unsigned int frame_count; unsigned int error_set; unsigned int i; error_set = 0; if (option_cc_test_test && 0 == rand() % 300) n_lines = 0; if (0 == n_lines) { error_msg ("No data on this frame..."); if (error_count[0] < max_error_count[0]) { ++error_count[0]; error_set |= 1 << 0; } } else { for (i = 0; i < n_lines; ++i) { if (sliced[i].id & (VBI_SLICED_CAPTION_525 | VBI_SLICED_CAPTION_625)) { int b1, b2; int c1, c2; b1 = sliced[i].data[0]; b2 = sliced[i].data[1]; if (option_cc_test_test && 0 == rand() % 300) { b1 = 0; b2 = 0; } if (0x00 == b1 && 0x00 == b2) { error_msg ("Null bytes..."); if (error_count[1] < max_error_count[1]) { ++error_count[1]; error_set |= 1 << 1; } } c1 = vbi_unpar8 (b1); c2 = vbi_unpar8 (b2); if (option_cc_test_test && 0 == rand() % 300) { c2 = -1; } if ((c1 | c2) < 0) { error_msg ("Parity error..."); if (error_count[2] < max_error_count[2]) { ++error_count[2]; error_set |= 1 << 2; } break; } } } } if (0 != error_set) { unsigned int n_errors = 0; unsigned int n_kinds = 0; for (i = 0; i < N_ELEMENTS (error_count); ++i) { n_errors += 5 - error_count[i]; n_kinds += (error_count[i] < 5); } if (0 == n_kinds) { error_msg ("Done."); return FALSE; /* terminate loop */ } if (0 == frame_count % (5 * 30)) { error_msg ("Waiting for %u errors of %u kinds...", n_errors, n_kinds); } } ++frame_count; if (option_raw_output != (0 != error_set)) { option_sliced_output = (0 != error_set); raw_output_count = N_ELEMENTS (frame_buffers) * 2; if (sliced_output_count < raw_output_count) sliced_output_count = raw_output_count; } return TRUE; /* success, continue loop */ } static vbi_bool decode_frame (const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { if (option_dump_sliced || option_dump_wss) decode_sliced (sliced, n_lines, sample_time, stream_time); if (option_cc_test) { struct frame *f; unsigned int size; f = frame_buffers + next_frame; if (n_frames_buffered >= N_ELEMENTS (frame_buffers)) { write_stream_sliced (wst, (sliced_output_count > 0) ? f->sliced : NULL, f->n_lines, (raw_output_count > 0) ? f->raw : NULL, sp, f->sample_time, f->stream_time); if (sliced_output_count > 0) --sliced_output_count; if (raw_output_count > 0) --raw_output_count; n_frames_buffered = N_ELEMENTS (frame_buffers) - 1; } assert (n_lines <= N_ELEMENTS (f->sliced)); memcpy (f->sliced, sliced, n_lines * sizeof (*sliced)); f->n_lines = n_lines; size = (sp->count[0] + sp->count[1]) * sp->bytes_per_line; memcpy (f->raw, raw, size); f->sample_time = sample_time; f->stream_time = stream_time; if (++next_frame >= N_ELEMENTS (frame_buffers)) next_frame = 0; ++n_frames_buffered; if (!cc_test (sliced, n_lines)) return FALSE; } else if (option_raw_output || option_sliced_output) { write_stream_sliced (wst, option_sliced_output ? sliced : NULL, n_lines, option_raw_output ? raw : NULL, sp, sample_time, stream_time); } return TRUE; } static void init_frame_buffers (const vbi_sampling_par *sp) { unsigned int size; unsigned int i; size = (sp->count[0] + sp->count[1]) * sp->bytes_per_line; for (i = 0; i < N_ELEMENTS (frame_buffers); ++i) { frame_buffers[i].raw = malloc (size); if (NULL == frame_buffers[i].raw) no_mem_exit (); } } static void usage (FILE * fp) { fprintf (fp, _("\ %s %s -- VBI capture tool\n\n\ Copyright (C) 2000-2007 Michael H. Schimek\n\ This program is licensed under GPLv2 or later. NO WARRANTIES.\n\n\ Usage: %s [options] > sliced VBI data\n\ -h | --help | --usage Print this message and exit\n\ -q | --quiet Suppress progress and error messages\n\ -v | --verbose Increase verbosity\n\ -V | --version Print the program version and exit\n\ Device options:\n\ -c | --sim-cc file Simulate a VBI device and load this Closed Caption\n\ test stream into the simulation\n\ -d | --device file Capture from this device (default %s)\n\ V4L/V4L2: /dev/vbi, /dev/vbi0, /dev/vbi1, ...\n\ Linux DVB: /dev/dvb/adapter0/demux0, ...\n\ *BSD bktr driver: /dev/vbi, /dev/vbi0, ...\n\ -i | --pid pid Capture the stream with this PID from a Linux\n\ DVB device\n\ -m | --sim-laced Simulate a VBI device capturing interlaced raw\n\ VBI data\n\ -n | --ntsc Video standard hint for V4L interface and\n\ simulated VBI device (default PAL/SECAM)\n\ -p | --pal | --secam Video standard hint for V4L interface\n\ -s | --sim Simulate a VBI device\n\ -u | --sim-unsync Simulate a VBI device with wrong/unknown field\n\ parity\n\ -w | --sim-noise Simulate a VBI device with noisy signal\n\ -x | --proxy Capture through the VBI proxy daemon\n\ Output options:\n\ -j | --dump Sliced VBI data (text)\n\ -l | --sliced Sliced VBI data (binary)\n\ -o | --output name Write the VBI data to this file instead of\n\ standard output\n" /* later */ /* "-r | --raw Raw VBI data (binary)\n" */ "-P | --pes DVB PES stream\n\ -T | --ts pid DVB TS stream\n\ "), PROGRAM_NAME, VERSION, program_invocation_name, option_dev_name); } static const char short_options[] = "c:d:hi:jlmno:pqr:suvwxPT:V"; #ifdef HAVE_GETOPT_LONG static const struct option long_options[] = { { "sim-cc", required_argument, NULL, 'c' }, { "device", required_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "usage", no_argument, NULL, 'h' }, { "pid", required_argument, NULL, 'i' }, { "dump", no_argument, NULL, 'j' }, { "sliced", no_argument, NULL, 'l' }, { "sim-laced", no_argument, NULL, 'm' }, { "ntsc", no_argument, NULL, 'n' }, { "output", required_argument, NULL, 'o' }, { "pal", no_argument, NULL, 'p' }, { "secam", no_argument, NULL, 'p' }, { "quiet", no_argument, NULL, 'q' }, { "raw", optional_argument, NULL, 'r' }, { "sim", no_argument, NULL, 's' }, { "sim-unsync", no_argument, NULL, 'u' }, { "verbose", no_argument, NULL, 'v' }, { "sim-noise", optional_argument, NULL, 'w' }, { "proxy", no_argument, NULL, 'x' }, { "pes", no_argument, NULL, 'P' }, { "ts", required_argument, NULL, 'T' }, { "version", no_argument, NULL, 'V' }, { "loose", no_argument, &option_strict, 0 }, { "strict", no_argument, &option_strict, 2 }, { "cc-test", no_argument, &option_cc_test, TRUE }, { "cc-test-test", no_argument, &option_cc_test_test, TRUE }, { "dump-wss", no_argument, &option_dump_wss, TRUE }, { "read", no_argument, &option_read_not_pull, TRUE }, { "pull", no_argument, &option_read_not_pull, FALSE }, { NULL, 0, 0, 0 } }; #else #define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; static char * load_string (const char * name) { FILE *fp; char *buffer; size_t buffer_size; size_t done; buffer = NULL; buffer_size = 0; done = 0; fp = fopen (name, "r"); if (NULL == fp) { exit (EXIT_FAILURE); } for (;;) { char *new_buffer; size_t new_size; size_t space; size_t actual; new_size = 16384; if (buffer_size > 0) new_size = buffer_size * 2; new_buffer = realloc (buffer, new_size); if (NULL == new_buffer) { free (buffer); exit (EXIT_FAILURE); } buffer = new_buffer; buffer_size = new_size; space = buffer_size - done - 1; actual = fread (buffer + done, 1, space, fp); if ((size_t) -1 == actual) { exit (EXIT_FAILURE); } done += actual; if (actual < space) break; } buffer[done] = 0; fclose (fp); return buffer; } static void sim_load_caption (void) { unsigned int i; for (i = 0; i < n_sim_cc_streams; ++i) { char *buffer; vbi_bool success; fprintf (stderr, "Loading '%s'.\n", sim_cc_streams[i]); buffer = load_string (sim_cc_streams[i]); success = capture_stream_sim_load_caption (cst, buffer, /* append */ (i > 0)); assert (success); } } int main (int argc, char ** argv) { unsigned int interfaces; unsigned int scanning; unsigned int services; vbi_bool sim_interlaced; vbi_bool sim_synchronous; init_helpers (argc, argv); scanning = 625; sim_interlaced = FALSE; sim_synchronous = TRUE; option_strict = 1; option_dump_wss = FALSE; option_read_not_pull = FALSE; interfaces = (INTERFACE_V4L2 | INTERFACE_V4L | INTERFACE_BKTR); for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ if (option_dump_wss) { option_raw_output = FALSE; option_sliced_output = FALSE; } break; case 'c': { const char **pp; pp = realloc (sim_cc_streams, (n_sim_cc_streams + 1) * sizeof (*pp)); assert (NULL != pp); sim_cc_streams = pp; sim_cc_streams[n_sim_cc_streams++] = optarg; interfaces = INTERFACE_SIM; break; } case 'd': parse_option_dev_name (); break; case 'h': usage (stdout); exit (EXIT_SUCCESS); case 'i': parse_option_dvb_pid (); interfaces = INTERFACE_DVB; break; case 'j': option_dump_sliced = TRUE; option_raw_output = FALSE; option_sliced_output = FALSE; break; case 'l': option_sliced_output = TRUE; option_dump_sliced = FALSE; option_dump_wss = FALSE; if (FILE_FORMAT_XML != option_out_file_format) option_out_file_format = FILE_FORMAT_SLICED; break; case 'm': sim_interlaced = TRUE; interfaces = INTERFACE_SIM; break; case 'n': scanning = 525; break; case 'o': assert (NULL != optarg); option_out_file_name = optarg; break; case 'p': scanning = 625; break; case 'q': parse_option_quiet (); break; #if 0 case 'r': /* Optional optarg: line numbers (not implemented yet). */ option_raw_output = TRUE; option_dump_sliced = FALSE; option_dump_wss = FALSE; option_out_file_format = FILE_FORMAT_XML; break; #endif case 's': interfaces = INTERFACE_SIM; break; case 'u': sim_synchronous = FALSE; interfaces = INTERFACE_SIM; break; case 'v': parse_option_verbose (); break; case 'w': /* Optional optarg: noise parameters (not implemented yet). */ interfaces = INTERFACE_SIM; option_sim_flags |= _VBI_RAW_NOISE_2; break; case 'x': interfaces &= ~(INTERFACE_SIM | INTERFACE_DVB); interfaces |= INTERFACE_PROXY; break; case 'P': option_sliced_output = TRUE; option_dump_sliced = FALSE; option_dump_wss = FALSE; option_out_file_format = FILE_FORMAT_DVB_PES; break; case 'T': option_sliced_output = TRUE; option_dump_sliced = FALSE; option_dump_wss = FALSE; option_out_ts_pid = parse_option_ts (); option_out_file_format = FILE_FORMAT_DVB_TS; break; case 'V': printf (PROGRAM_NAME " " VERSION "\n"); exit (EXIT_SUCCESS); default: usage (stderr); exit (EXIT_FAILURE); } } if (!(option_sliced_output || option_raw_output || option_dump_sliced || option_dump_wss)) { error_msg (_("Give one of the -j, -l, -P or -T options\n" "to enable output, or -h for help.")); exit (EXIT_FAILURE); } services = (VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625 | VBI_SLICED_TELETEXT_B | VBI_SLICED_CAPTION_525 | VBI_SLICED_CAPTION_625 | VBI_SLICED_VPS | VBI_SLICED_VPS_F2 | VBI_SLICED_WSS_625 | VBI_SLICED_WSS_CPR1204); switch (option_out_file_format) { case FILE_FORMAT_DVB_PES: case FILE_FORMAT_DVB_TS: /* Other formats cannot be encoded. */ services &= (VBI_SLICED_TELETEXT_B | VBI_SLICED_CAPTION_625 | VBI_SLICED_VPS | VBI_SLICED_WSS_625); break; default: break; } cst = capture_stream_new (interfaces, option_dev_name, scanning, services, /* n_buffers (V4L2 mmap) */ 5, option_dvb_pid, sim_interlaced, sim_synchronous, option_raw_output, option_read_not_pull, option_strict, decode_frame); if (interfaces & INTERFACE_SIM) { sim_load_caption (); capture_stream_sim_set_flags (cst, option_sim_flags); if (0 != option_sim_flags) capture_stream_sim_decode_raw (cst, TRUE); } if (option_cc_test_test) option_cc_test = TRUE; if (option_cc_test) { option_raw_output = FALSE; option_sliced_output = TRUE; option_dump_sliced = FALSE; option_dump_wss = FALSE; option_out_file_format = FILE_FORMAT_SLICED; sliced_output_count = 2 * 60 * 30; } if (option_raw_output || option_sliced_output) { vbi_sampling_par sp; capture_stream_get_sampling_par (cst, &sp); if (option_cc_test) init_frame_buffers (&sp); wst = write_stream_new (option_out_file_name, option_out_file_format, option_out_ts_pid, SCANNING (&sp)); } stream_loop (cst); stream_delete (wst); wst = NULL; stream_delete (cst); cst = NULL; exit(EXIT_SUCCESS); } zvbi-0.2.44/test/cc608-attributes.xml000066400000000000000000000652271476363111200173030ustar00rootroot00000000000000 Libzvbi EIA 608-B Closed Caption test stream: Attribute codes. The caption decoder should not add spaces for legibility during this test. Full support of all optional attributes is assumed. These four rows show white text, not italic, underlined or flashing, on an opaque black background. Testing Preamble Address Codes. The following rows do not start with a space. White text on black background. White underlined on black. Green on black. Green underlined on black. Blue on black. Blue underlined on black. Cyan on black. Cyan underlined on black. Red on black. Red underlined on black. Yellow on black. Yellow underlined on black. Magenta on black. Magenta underlined on black. White italics on black. White it. un. on black. White text on black background. White underlined on black. The following two rows are indented by four columns. White on black. White un. on black. Blue on black text. Red on black text. The following two rows are indented by 8 columns. White on black. White un. on black. Y ellow on .... bl ack. Yellow on .... broken lack. Ye llow on .... broken lack. No characters in the following row are underlined. Yel low on .... broken lack. Yellow on .. .. black. The following three rows contain one transparent space. Yellow... > < white text. Yellow... > < cyan text. Yellow... > &ts; < cyan text. The following row starts with two opaque black spaces and contains one transp. space. Red un.it.fl. >&ts; < magenta fl. The following row starts with two opaque black spaces and contains no transp. spaces. Red un.it.fl. &ts; Xblack. on The following row starts with two opaque black spaces and contains one transp. space. Red un.it.fl. >&ts; X &ts;< magenta fl. The following row starts with two opaque black spaces and contains one transp. space. Red un.it.fl. > < white fl. Testing Mid-Row Codes. The following 17 rows start with an opaque black space. < White text on black backgr. < White underlined on black. < Green on black. < Green underlined on black. < Blue on black. < Blue underlined on black. < Cyan on black. < Cyan underlined on black. < Red on black. < Red underlined on black. < Yellow on black. < Yellow underlined on black. < Magenta on black. < Magenta underlined on black. < White italics on black. < White ital. und. on black. < White flashing on black. The following four rows contain one opaque black space. White >< blue. White >< white ital. White >< white undl. Solid >< flashing. The following row starts with one opaque black space. Blue italic on black text. The following two rows start with two opaque black spaces. Cyan italic on black. Red on black. The following 8 rows contain one opaque black space. White ital. > < white straight. White ital. > < green straight. White ital. > < blue straight. White ital. > < cyan straight. White ital. > < red straight. White ital. > < yellow straight. White ital. > < magenta str. White ital. > < white ital. The following two rows contain two opaque black spaces. < White ital. > < green str. < White ital. > < yellow str. The first space below may be underlined. < Green un. it. > < green. < Red un. it. > < red un. The first two spaces below may be underlined. < Blue un. it. > < blue. < Blue un. it. > < blue un. Green flashing on black. Blue underl. fl. on black. White ital. fl. on black. The entire row below is cyan underl. on red and contains two opaque red spaces. < Solid >< flashing. The entire row below is white italic on semi-transp. blue and contains two semi blue spaces. < Solid >< flashing. The entire row below is blue with transp. background and contains two transp. spaces. < Solid >< flashing. The following 16 rows contain two opaque black spaces. < Red fl. > < white solid. < Red fl. > < white un. solid. < Red fl. > < green solid. < Red fl. > < green un. solid. < Red fl. > < blue solid. < Red fl. > < blue un. solid. < Red fl. > < cyan solid. < Red fl. > < cyan un. solid. < Red fl. > < red solid. < Red fl. > < red un. solid. < Red fl. > < yellow solid. < Red fl. > < yellow un. solid. < Red fl. > < magenta solid. < Red fl. > < magenta un. sol. < Red fl. > < red ital. solid. < Red fl. > < red it. un. sol. The row below starts with two opaque black spaces. < Red it. un. fl. on black. Both rows below start with three opaque black spaces. < Red it. un. fl. on black. < Red it. un. fl. on black. The following 16 rows start with one opaque space. All columns have the same background color and opacity. x Blue on white opaque backgr. x Blue on white semi-transp. xWhite on green opaque. xWhite on green semi-transp. xWhite on blue opaque. xWhite on blue semi-transp. x Blue on cyan opaque. x Blue on cyan semi-transp. xWhite on red opaque. xWhite on red semi-transp. x Blue on yellow opaque. x Blue on yellow semi-transp. xWhite on magenta opaque. xWhite on magenta semi-transp. xWhite on black opaque. xWhite on black semi-transp. The following row contains one opaque blue space. White on black > < on blue. The following row starts with one transparent space. < Blue on transp. backgr. The following two rows contain one transparent space. Blue on black > < on transp. Blue on semi black > < transp. The following row starts with one transp. space and contains one opaque white space. Blue on transp. > < on white. The following row shows blue text, starts with one transp. space and contains one white semi-transparent space. Transp. bg. > < semi white. In the following 15 rows all columns have the same background. Blue background. Blue semi background. Transparent background. Blue background. Blue background. Blue background. Green background. Green semi background. Transparent background. Green background. Bluebackground. Blue semibackground. Transparentbackground. Bluebackground. Bluebackground. Bluebackground. All columns in the row below have an opaque blue background. Solid > < flashing. All columns in the row below have a blue semi- transparent background. Solid > < flashing. All columns in the row below have a transparent background. Solid > < flashing. The following row starts with two white spaces. xBlack text on white backgr. The following row starts with two white spaces, the second space may be underlined. xBlack underlined on white. The following three rows start with two opaque yellow spaces. x Black on yellow. x Black on yellow. x Black underlined on yellow. The following four rows start with three opaque yellow spaces. x Black not underl. on yellow. x Black underlined on yellow. x Black italic on yellow. x Black ital. undl. on yellow. The following two rows show black text on a green opaque background. xFlashing > < solid. xFlashing > < solid underl. The following two rows contain one transparent space. Green on black > &ts;< ditto. Green on black > < ditto. The following two rows contain one transparent space. White underlined > &ts;< ditto. White underlined > < ditto. The following two rows contain one transparent space. White italic > &ts;< ditto. White italic > < ditto. The following two rows contain one transparent space and start with an opaque space. Flashing >&ts; < flashing. Flashing > < flashing. The following two rows contain one transparent space and start with an opaque space. White on blue >&ts; < ditto. White on blue > < ditto. The following two rows contain one transparent space and start with a semi-transp. space. White on semi blue >&ts; < ditto. White on semi blue > < ditto. The following two rows start with a transparent space and contain another one. White on transparent >&ts; < ditto. White on transparent > < ditto. End of test stream. zvbi-0.2.44/test/cc608-charsets.xml000066400000000000000000001117401476363111200167210ustar00rootroot00000000000000 Libzvbi EIA 608-B Closed Caption test stream: Character sets. Standard Characters. Decoders manufactured before 1996 may display the alternate character in parentheses: 20 Standard space: 21 Exclamation mark: ! 22 Quotation mark: " 23 Number sign: # 24 Dollar sign: $ 25 Percent sign: % 26 Ampersand: & 27 Apostrophe: ' 28 Opening parentheses: ( 29 Closing parentheses: ) 2A a acute (A): * 2B Plus sign: + 2C Comma: , 2D Minus sign, hyphen: - 2E Period: . 2F Slash: / 30-39 Digit 0 to 9: 0123456789 3A Colon: : 3B Semicolon: ; 3C Less-than sign: < 3D Equal sign: = 3E Greater-than sign: > 3F Question mark: ? 40 At sign: @ 41-5A Upper case letters: ABCDEFGHIJ KLMNOPQRST UVWXYZ 5B Opening square bracket: [ 5C e acute (E): \ 5D Closing square bracket: ] 5E i acute (I): ^ 5F o acute (O): _ 60 u acute (U): ` 61-7A Lower (upper) case letters: abcdefghij klmnopqrst uvwxyz 7B c cedilla (C): { 7C Division sign: | 7D N tilde: } 7E n tilde (N): ~ 7F Solid block:  Special Characters. Decoders manufactured before 1996 may display the alternate character in parentheses: 1130 Registered mark: 1131 Degree sign: 1132 Fraction one half: 1133 Inverse question mark: 1134 Trademark: 1135 Cents sign: 1136 Pound sign: 1137 Music note: 1138 a grave (A): 1139 Transparent space: 113A e grave (E): 113B a circumflex (A): 113C e circumflex (E): 113D i circumflex (I): 113E o circumflex (O): 113F u circumflex (U): Optional Extended Characters. Decoders conforming only to 47 CFR 15.119 may display the character x instead: 1220 A acute: x 1221 E acute: x 1222 O acute: x 1223 U acute: x 1224 U diaresis: x 1225 u diaresis: x 1226 Opening single quote: x 1227 Inverted exclam. mark: x 1228 Asterisk: x 1229 Plain single quote: x 122A Em dash: x 122B Copyright: x 122C Servicemark: x 122D Round bullet: x 122E Opening double quotes: x 122F Closing double quotes: x 1230 A grave: x 1231 A circumflex: x 1232 C cedilla: x 1233 E grave: x 1234 E circumflex: x 1235 E diaresis: x 1236 e diaresis: x 1237 I circumflex: x 1238 I diaresis: x 1239 i diaresis: x 123A O circumflex: x 123B U grave: x 123C u grave: x 123D U circumflex: x 123E Opening guillemets: x 123F Closing guillemets: x 1320 A tilde: x 1321 a tilde: x 1322 I acute: x 1323 I grave: x 1324 i grave: x 1325 O grave: x 1326 o grave: x 1327 O tilde: x 1328 o tilde: x 1329 Opening curly bracket: x 132A Closing curly bracket: x 132B Backslash: x 132C Caret: x 132D Underbar: x 132E Pipe: x 132F Tilde: x 1330 A diaresis: x 1331 a diaresis: x 1332 O diaresis: x 1333 o diaresis: x 1334 Sharp s: x 1335 Yen symbol: x 1336 Currency sign: x 1337 Vertical bar: x 1338 A ring: x 1339 a ring: x 133A O slash: x 133B o slash: x 133C Upper left corner: x 133D Upper right corner: x 133E Lower left corner: x 133F Lower right corner: x The following text checks the character design of the utilized font. Bracket pairs: () <> [] xx xx The plain single quote x should look like a short vertical stroke. The plain single and double quotes should be similar: x " xText in plain single quotes. x "Text in plain double quotes." The apostrophe ' should look like a comma and mirror the opening single quote x: xText in curled single quotes.' The curled single and double quotes should be similar: x' xx xText in curled single quotes.' xText in curled double quotes. x Table of Latin characters: Axx xx xx - a* x xx - Exx x- x- - e\ - x- - Ixx x- x- - ix^ - x- - Oxx xx x- x ox_ x x- x Uxx x- x- - ux` - x- - Cx c{ N} n~ The following text checks if the strokes of box drawing characters meet, regardless if a fixed width or proportional spacing font utilized. Box drawing characters: xx x iiiii xx x xx x xMx mmmmm xIx x&ts; x xx x xx xx x xx x xx x x This vertical line x has no gaps. x x 12345678901234567890123456789012 xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xM Column 32 above shows an M. The hor. line has no gaps. 12345678901234567890123456789012 @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@M Column 32 above shows an M. Italics: !"#$%&' ()*+,-./ 01234567 89:;<=>? @ABCDEFG HIJKLMNO PQRSTUVW XYZ[\]^_ `abcdefg hijklmno pqrstuvw xyz{|}~ xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx Box drawing characters should not be slanted: xx x iiiii xx x xx x xMx mmmmm xIx x&ts; x xx x xx xx x xx x xx x x This vertical line x has no gaps. x x 12345678901234567890123456789012 xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xM Column 32 above shows an M. The line above has no gaps. 12345678901234567890123456789012 @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@M Column 32 above shows an M. Underlined: !"#$%&' ()*+,-./ 01234567 89:;<=>? @ABCDEFG HIJKLMNO PQRSTUVW XYZ[\]^_ `abcdefg hijklmno pqrstuvw xyz{|}~ xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx Underlined underbar: x Box drawing characters should not be underlined: xx x iiiii xx x xx x xMx mmmmm xIx x&ts; x xx x xx xx x xx x xx x x This vertical line x has no gaps. x x 12345678901234567890123456789012 xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xM Column 32 above shows an M. The line has no gaps. 12345678901234567890123456789012 @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@M Column 32 above shows an M. Italics underlined: !"#$%&' ()*+,-./ 01234567 89:;<=>? @ABCDEFG HIJKLMNO PQRSTUVW XYZ[\]^_ `abcdefg hijklmno pqrstuvw xyz{|}~ xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx Ital. underl. underbar: x Box drawing characters should not be slanted and underlined: xx x iiiii xx x xx x xMx mmmmm xIx x&ts; x xx x xx xx x xx x xx x x This vertical line x has no gaps. x x 12345678901234567890123456789012 xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xM Column 32 above shows an M. The line has no gaps. 12345678901234567890123456789012 @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@M Column 32 above shows an M. All caption modes and Text mode use the same character sets. Pop-Up mode. !"#$%&' ()*+,-./ 01234567 89:;<=>? @ABCDEFG HIJKLMNO PQRSTUVW XYZ[\]^_ `abcdefg hijklmno pqrstuvw xyz{|}~ xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx Paint-On mode. !"#$%&' ()*+,-./ 01234567 89:;<=>? @ABCDEFG HIJKLMNO PQRSTUVW XYZ[\]^_ `abcdefg hijklmno pqrstuvw xyz{|}~ xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx Text mode. Please change to caption channel T1. Text mode. Text mode. !"#$%&' ()*+,-./ 01234567 89:;<=>? @ABCDEFG HIJKLMNO PQRSTUVW XYZ[\]^_ `abcdefg hijklmno pqrstuvw xyz{|}~ xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx End of test stream. End of test stream. zvbi-0.2.44/test/cc608-roll-up.xml000066400000000000000000000751161476363111200165050ustar00rootroot00000000000000 Libzvbi EIA 608-B Closed Caption test stream: Roll-Up caption. This is Roll-Up caption on channel CC1. You can see four rows of text at the bottom of the screen. When we add a new row, the top row disappears and the remaining rows roll up. These rows do not start or end with a space but the decoder may add one to enhance legibility. This row starts with a space. This row ends with a space. The decoder should not add another space. The video picture is visible where no character or standard space > < is displayed. The row below is empty. Throughout the row above the video picture is visible. The row below is empty too. &ts; Actually one transparent space character was transmitted. This row contains one space: The decoder should display one space. This row starts w. 2 spaces. This row ends with 2 spaces. The decoder should display two spaces in both rows. This >&ts;< is a transparent space, a space character with transparent background, where the video picture is visible. &ts;This row starts with a transparent space. The decoder may replace it by one opaque space for legibility. A transparent space follows the comma in this row,&ts; the decoder may replace it by one opaque space. Control codes (like Special Character Transparent Space) may repeat once to ensure correct reception. &ts;&ts;&ts;This row starts with two transparent spaces, but three were transmitted. &ts;&ts;&ts;&ts;This row also starts with two transparent spaces, but four were transmitted. &ts;&ts;&ts;&ts;This row starts with two transp. spaces. The decoder may replace the second one by an opaque space. This row is indented by one column using a TO1 code. This row is indented by two columns using a TO2 code. This row is indented by three columns using a TO3 code. This row is indented by four columns, using two TO2 codes, four were transmitted. This row is indented by four columns using a PAC code. &ts;&ts;&ts;&ts;&ts;&ts;&ts;&ts; < four transp. spaces. < four transp. spaces. < four transp. spaces. 3 rows above are identical. This row ends with two&ts;&ts;&ts;&ts; transp. spaces. The decoder may replace the first one by an opaque space. Transparent spaces > &ts;&ts;&ts;&ts;&ts;&ts;&ts;&ts; Transparent spaces > Transparent spaces > 3 rows above are identical. This row >&ts;&ts;&ts;&ts;&ts;&ts;< contains three transp. spaces. The decoder may replace the first and third one by opaque spaces. Three >&ts;&ts;&ts;&ts;&ts;&ts;< transp. spaces. Three >< transp. spaces. Three >< transp. spaces. 3 rows above are identical. There are 32 columns: 12345678901234567890123456789012 The row above begins with "1234" and ends with "9012". We move the cursor to each column using one PAC and one TOx code: 12345678901234567890123456789012 1 2 3 12345678901234567890123456789012 4 5 6 12345678901234567890123456789012 7 8 9 12345678901234567890123456789012 10 11 12 12345678901234567890123456789012 13 14 15 12345678901234567890123456789012 16 17 18 12345678901234567890123456789012 19 20 21 12345678901234567890123456789012 22 23 24 12345678901234567890123456789012 25 26 27 12345678901234567890123456789012 28 29 30 12345678901234567890123456789012 31 12345678901234567890123456789012 X The row above contains only one "X" in column 32. PAC TOx are andcodes not destr uctive. XXXXXXXXXXX We can overwrite text. We display four contiguous rows of text. The cursor stays on the base row, at the bottom of the screen. We now change to a three row window. The top row disappears and the cursor stays in this row > < finishing it. We display three contiguous rows of text. The cursor is still on the base row. We now change to a two row window, the top row disappears. We display two rows of text. The cursor is on the base row. We change back to four rows, the top two rows remain empty. These are three rows of text and now four. The screen is divided into 15 rows. Currently the 15th row is our base row. We now move this text to the top of the screen. All four rows move immediately and without erasing. Row 12. You see four rows starting with "All four ...". The rows roll up as usual. We now try each row of the screen as our base row: 15. 14 13 12 11 10 9. 8 7 6 5 4 When we request row 1 ... 3 the decoder should keep all four rows of the text visible. This is row 4, requested 4. 3 2 1 Three rows of text. All rows should remain visible. This is row 4, requested 4. 3, requested 3 2 1 Two rows of text. This is row 4, requested 4. 3, requested 3 2, requested 2 1 We change back to four rows, the top two rows remain empty. This is screen row 4. Pop-Up mode. This sentence begins in row 13, column 1. You see three rows of text. Paint-On mode. This sentence begins in row 14, column 1. You see two rows. Characters are always displayed immediately when received by the receiver. There are 32 columns: 12345678901234567890123456789012 The row above begins with "1234" and ends with "9012". This sentence does not fit in 32 columns. The row above ends with "in 3." Decoders are not supposed to word-wrap. This sentence does not fit in 32 columns The row above ends with "in 3s", the "s" is not underlined. Another long sentence with Mid-Row codes. The row above ends with a yellow "Mid- ", the space may be underlined. This very long sentence does not fit in 32 columns, or 80 columns, or even 256 columns and must not overflow buffers because the decoder cannot assume that a Carriage Return code, Backspace code or Preamble Address Code will be received before the limit is reached. The row above ends with "no." Special Character Transparent Space code cannot move the cursor beyond column 32: 12345678901234567890123456789012 &ts;&ts;&ts;&ts;&ts;X The row above shows one "X" in column 32. A TOx code cannot move the cursor beyond column 32: 12345678901234567890123456789012 X The row above shows one "X" in column 32. 12345678901234567890123456789012 X The row above shows one "X" in column 32. 12345678901234567890123456789012 X The row above shows one "X" in column 32. 12345678901234567890123456789012 X The row above shows one "X" in column 32. The row below is empty. X Throughout the row above the video picture is visible. This row >XXX< contains three transparent spaces. This row contXains one X. This row contXXX ains none. A BS code in the first column: It has no effect. It has no effect. Green text. Yellow underl. > &ts;< white ital. Yellow underl. > &ts;X < white ital. Yellow underl. > &ts;X < ditto. White underl. > < white ital. White underl. > X < ditto. Solid >< flashing. Solid >X < solid. Blue opaque background. Blue semi-transparent backgr. Blue semi > < red opaque. Blue semi > X < blue semi. 1234567890123456789012345678901 The row above ends with "7890". 12345678901234567890123456789012 The row above ends with "90&ts;2". The row below is empty. 12345678901234567890123456789012 Throughout the row above the video picture is visible. 12345678901234567890123456789012 The row above shows one character "1" in the first column. 12345678901234567890123456789012 The row above contains 31 characters, ends with "8901". Flashing >X < solid. Flashing >X < flashing. Blue opaque background. Blue semi-transparent backgr. Blue semi > X < red opaque. Blue semi > X < blue semi. Reception of data > Caption channel CC2. Caption channel CC3. < for another > This is caption channel CC4. < caption channel or for > Text channel T1. Text channel T2. Text channel T3. Text channel T4. < Text mode does not change the cursor position. Roll-Up mode. Above yo u see "Roll-Up mode." RUx did not delete the displayed memory. Next the caption will briefly disappear. RUx did not delete the non-displayed memory. Roll-Up mode. Above you see "Roll-Up mode." The caption will briefly disappear again. RUx did not delete the non-displayed memory. RUx did not delete the displayed memory. End of test stream. zvbi-0.2.44/test/cc608-test-stream.dtd000066400000000000000000000214471476363111200173340ustar00rootroot00000000000000 '> zvbi-0.2.44/test/cpptest.cc000066400000000000000000000020051476363111200155240ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2002 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: cpptest.cc,v 1.4 2008-03-01 07:37:06 mschimek Exp $ */ #include "src/libzvbi.h" /* For now just to see if it compiles. */ int main (int argc, char **argv) { argc = argc; /* unused */ argv = argv; return 0; } zvbi-0.2.44/test/ctest.c000066400000000000000000000020301476363111200150170ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2002 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: ctest.c,v 1.2 2008-03-01 07:37:02 mschimek Exp $ */ #include "src/libzvbi.h" /* Just a test to see if the header compiles without errors. */ int main (int argc, char **argv) { argc = argc; /* unused */ argv = argv; return 0; } zvbi-0.2.44/test/date.c000066400000000000000000000167051476363111200146300ustar00rootroot00000000000000/* * zvbi-date -- Get station date and time from Teletext packet 8/30/2 * * Copyright (C) 2006, 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: date.c,v 1.3 2013-08-28 14:44:28 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #ifdef HAVE_GETOPT_LONG # include #endif #ifdef _WIN32 #include #include #endif #include "sliced.h" #include "src/vbi.h" #include "src/event.h" #define PROGRAM_NAME "zvbi-date" static vbi_bool option_set_time; static struct stream * cst; static vbi_decoder * dec; static void print_time (const vbi_local_time * lt) { char *buffer; size_t buffer_size; struct tm tm; size_t len; CLEAR (tm); #ifdef _WIN32 if (0 != localtime_s (&tm, <->time)) error_exit (_("Invalid date received.")); #else if (NULL == localtime_r (<->time, &tm)) error_exit (_("Invalid date received.")); #endif buffer_size = 1 << 16; buffer = malloc (buffer_size); if (NULL == buffer) no_mem_exit (); tm.tm_isdst = -1; /* unknown */ #ifdef HAVE_TM_GMTOFF if (lt->seconds_east_valid) { tm.tm_gmtoff = lt->seconds_east_valid; len = strftime (buffer, buffer_size, "%F %T %Z", &tm); } else #endif { len = strftime (buffer, buffer_size, "%F %T UTC", &tm); } if (0 == len) no_mem_exit (); puts (buffer); free (buffer); buffer = NULL; } #ifdef _WIN32 static void unix_time_to_file_time(time_t t, LPFILETIME pft) { LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000; pft->dwLowDateTime = (DWORD)ll; pft->dwHighDateTime = ll >> 32; } #endif static void set_time (const vbi_local_time * lt) { #if defined HAVE_CLOCK_SETTIME && defined CLOCK_REALTIME { struct timespec ts; ts.tv_sec = lt->time; /* UTC */ ts.tv_nsec = 0; if (0 == clock_settime (CLOCK_REALTIME, &ts)) return; } #endif #ifdef _WIN32 { SYSTEMTIME st; FILETIME ft; unix_time_to_file_time(lt->time, &ft); FileTimeToSystemTime(&ft, &st); if (0 == SetLocalTime (&st)) return; } #else { struct timeval tv; tv.tv_sec = lt->time; /* UTC */ tv.tv_usec = 0; if (0 == settimeofday (&tv, /* tz */ NULL)) return; } #endif error_exit (_("Cannot set system time: %s."), strerror (errno)); } static void event_handler (vbi_event * ev, void * user_data) { user_data = user_data; /* unused */ switch (ev->type) { case VBI_EVENT_LOCAL_TIME: { const vbi_local_time *lt; lt = ev->ev.local_time; if (option_log_mask & VBI_LOG_NOTICE) { print_time (lt); } if (option_set_time) { set_time (lt); } exit (EXIT_SUCCESS); } default: assert (0); } } static vbi_bool decode_function (const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { static double first_sample_time = 0.0; raw = raw; /* unused */ sp = sp; stream_time = stream_time; /* Packet 8/30/2 should repeat once every second. */ if (first_sample_time <= 0.0) { first_sample_time = sample_time; } else if (sample_time - first_sample_time > 2.5) { error_exit (_("No station tuned in, poor reception, " "or date and time not transmitted.")); } /* Should really be const vbi_sliced *. */ vbi_decode (dec, (vbi_sliced *) sliced, n_lines, sample_time); return TRUE; } static void usage (FILE * fp) { fprintf (fp, _("\ %s %s -- Get date and time from Teletext\n\n\ Copyright (C) 2006, 2007 Michael H. Schimek\n\ This program is licensed under GPLv2+. NO WARRANTIES.\n\n\ Usage: %s [options]\n\ -h | --help | --usage Print this message and exit\n\ -q | --quiet Suppress progress and error messages\n\ -v | --verbose Increase verbosity\n\ -V | --version Print the program version and exit\n\ Device options:\n\ -d | --device file Capture from this device (default %s)\n\ V4L/V4L2: /dev/vbi, /dev/vbi0, /dev/vbi1, ...\n\ Linux DVB: /dev/dvb/adapter0/demux0, ...\n\ *BSD bktr driver: /dev/vbi, /dev/vbi0, ...\n\ -i | --pid pid Capture the stream with this PID from a Linux\n\ DVB device\n\ -n | --ntsc Video standard hint for V4L interface (default\n\ PAL/SECAM)\n\ -p | --pal | --secam Video standard hint\n\ Other options:\n\ -s | --set Set system time from received date and time\n\ "), PROGRAM_NAME, VERSION, program_invocation_name, option_dev_name); } static const char short_options [] = "d:hi:npqsvV"; #ifdef HAVE_GETOPT_LONG static const struct option long_options [] = { { "device", required_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "usage", no_argument, NULL, 'h' }, { "pid", required_argument, NULL, 'i' }, { "ntsc", no_argument, NULL, 'n' }, { "pal", no_argument, NULL, 'p' }, { "secam", no_argument, NULL, 'p' }, { "quiet", no_argument, NULL, 'q' }, { "set", no_argument, NULL, 's' }, { "verbose", no_argument, NULL, 'v' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, 0, 0 } }; #else # define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; int main (int argc, char ** argv) { unsigned int interfaces; unsigned int scanning; vbi_bool success; init_helpers (argc, argv); scanning = 625; interfaces = (INTERFACE_V4L2 | INTERFACE_V4L | INTERFACE_BKTR); for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ break; case 'd': parse_option_dev_name (); break; case 'h': usage (stdout); exit (EXIT_SUCCESS); case 'i': parse_option_dvb_pid (); interfaces = INTERFACE_DVB; break; case 'n': scanning = 525; break; case 'p': scanning = 625; break; case 'q': parse_option_quiet (); break; case 's': option_set_time = TRUE; break; case 'v': parse_option_verbose (); break; case 'V': printf (PROGRAM_NAME " " VERSION "\n"); exit (EXIT_SUCCESS); default: usage (stderr); exit (EXIT_FAILURE); } } cst = capture_stream_new (interfaces, option_dev_name, scanning, VBI_SLICED_TELETEXT_B, /* n_buffers (V4L2 mmap) */ 5, option_dvb_pid, /* interlaced (SIM) */ FALSE, /* synchronous (SIM) */ TRUE, /* capture_raw_data */ FALSE, /* read_not_pull */ FALSE, /* strict */ 1, decode_function); dec = vbi_decoder_new (); if (NULL == dec) no_mem_exit (); success = vbi_event_handler_register (dec, VBI_EVENT_LOCAL_TIME, event_handler, /* user_data */ NULL); if (!success) no_mem_exit (); stream_loop (cst); stream_delete (cst); cst = NULL; exit (EXIT_SUCCESS); } zvbi-0.2.44/test/decode.c000066400000000000000000000514131476363111200151310ustar00rootroot00000000000000/* * zvbi-decode -- Decode sliced VBI data using low-level * libzvbi functions * * Copyright (C) 2004, 2006, 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: decode.c,v 1.36 2009-12-14 23:43:52 mschimek Exp $ */ /* For libzvbi version 0.2.x / 0.3.x. */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_LONG # include #endif #include "src/version.h" #if 2 == VBI_VERSION_MINOR # include "src/bcd.h" # include "src/cc608_decoder.h" # include "src/conv.h" # include "src/dvb_demux.h" # include "src/hamm.h" # include "src/idl_demux.h" # include "src/lang.h" # include "src/packet-830.h" # include "src/pfc_demux.h" # include "src/vps.h" # include "src/xds_demux.h" #elif 3 == VBI_VERSION_MINOR # include "src/zvbi.h" # include "src/misc.h" /* _vbi_to_ascii() */ #else # error VBI_VERSION_MINOR == ? #endif #include "sliced.h" #undef _ #define _(x) x /* i18n TODO */ /* Will be installed one day. */ #define PROGRAM_NAME "zvbi-decode" static const char * option_in_file_name; static enum file_format option_in_file_format; static unsigned int option_in_ts_pid; static vbi_pgno option_pfc_pgno; static unsigned int option_pfc_stream; static vbi_bool option_decode_ttx; static vbi_bool option_decode_8301; static vbi_bool option_decode_8302; static vbi_bool option_decode_caption; static vbi_bool option_decode_xds; static vbi_bool option_decode_idl; static vbi_bool option_decode_vps; static vbi_bool option_decode_vps_other; static vbi_bool option_decode_wss; static vbi_bool option_dump_network; static vbi_bool option_dump_hex; static vbi_bool option_dump_bin; static vbi_bool option_dump_time; static double option_metronome_tick; static vbi_pgno option_pfc_pgno = 0; static unsigned int option_pfc_stream = 0; static unsigned int option_idl_channel = 0; static unsigned int option_idl_address = 0; static struct stream * rst; static vbi_pfc_demux * pfc; static vbi_idl_demux * idl; static vbi_xds_demux * xds; extern void _vbi_pfc_block_dump (const vbi_pfc_block * pb, FILE * fp, vbi_bool binary); static vbi_bool xds_cb (vbi_xds_demux * xd, const vbi_xds_packet * xp, void * user_data) { xd = xd; /* unused */ user_data = user_data; _vbi_xds_packet_dump (xp, stdout); return TRUE; /* no errors */ } static void caption (const uint8_t buffer[2], unsigned int line) { if (option_decode_xds && 284 == line) { if (!vbi_xds_demux_feed (xds, buffer)) { printf ("Parity error in XDS data.\n"); } } if (option_decode_caption && (21 == line || 284 == line /* NTSC */ || 22 == line /* PAL? */)) { printf ("CC line=%3u ", line); _vbi_cc608_dump (stdout, buffer[0], buffer[1]); } } #if 3 == VBI_VERSION_MINOR /* XXX port me back */ static void dump_cni (vbi_cni_type type, unsigned int cni) { vbi_network nk; vbi_bool success; if (!option_dump_network) return; success = vbi_network_init (&nk); if (!success) no_mem_exit (); success = vbi_network_set_cni (&nk, type, cni); if (!success) no_mem_exit (); _vbi_network_dump (&nk, stdout); putchar ('\n'); vbi_network_destroy (&nk); } #endif /* 3 == VBI_VERSION_MINOR */ static void dump_bytes (const uint8_t * buffer, unsigned int n_bytes) { unsigned int j; if (option_dump_bin) { fwrite (buffer, 1, n_bytes, stdout); return; } if (option_dump_hex) { for (j = 0; j < n_bytes; ++j) printf ("%02x ", buffer[j]); } putchar ('>'); for (j = 0; j < n_bytes; ++j) { /* Not all Teletext characters are representable in ASCII or even UTF-8, but at this stage we don't know the Teletext code page for a proper conversion. */ char c = _vbi_to_ascii (buffer[j]); putchar (c); } puts ("<"); } static void packet_8301 (const uint8_t buffer[42], unsigned int designation) { unsigned int cni; time_t time; int gmtoff; struct tm tm; if (!option_decode_8301) return; if (!vbi_decode_teletext_8301_cni (&cni, buffer)) { printf ("Error in Teletext " "packet 8/30 format 1 CNI.\n"); return; } if (!vbi_decode_teletext_8301_local_time (&time, &gmtoff, buffer)) { printf ("Error in Teletext " "packet 8/30 format 1 local time.\n"); return; } printf ("Teletext packet 8/30/%u cni=%x time=%u gmtoff=%d ", designation, cni, (unsigned int) time, gmtoff); #ifdef _WIN32 gmtime_s (&tm, &time); #else gmtime_r (&time, &tm); #endif printf ("(%4u-%02u-%02u %02u:%02u:%02u UTC)\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); #if 3 == VBI_VERSION_MINOR if (0 != cni) dump_cni (VBI_CNI_TYPE_8301, cni); #endif } static void packet_8302 (const uint8_t buffer[42], unsigned int designation) { unsigned int cni; vbi_program_id pi; if (!option_decode_8302) return; if (!vbi_decode_teletext_8302_cni (&cni, buffer)) { printf ("Error in Teletext " "packet 8/30 format 2 CNI.\n"); return; } if (!vbi_decode_teletext_8302_pdc (&pi, buffer)) { printf ("Error in Teletext " "packet 8/30 format 2 PDC data.\n"); return; } printf ("Teletext packet 8/30/%u cni=%x ", designation, cni); _vbi_program_id_dump (&pi, stdout); putchar ('\n'); #if 3 == VBI_VERSION_MINOR if (0 != pi.cni) dump_cni (pi.cni_type, pi.cni); #endif } static vbi_bool page_function_clear_cb (vbi_pfc_demux * dx, void * user_data, const vbi_pfc_block * block) { dx = dx; /* unused */ user_data = user_data; _vbi_pfc_block_dump (block, stdout, option_dump_bin); return TRUE; } static vbi_bool idl_format_a_cb (vbi_idl_demux * idl, const uint8_t * buffer, unsigned int n_bytes, unsigned int flags, void * user_data) { idl = idl; user_data = user_data; if (!option_dump_bin) { printf ("IDL-A%s%s ", (flags & VBI_IDL_DATA_LOST) ? " " : "", (flags & VBI_IDL_DEPENDENT) ? " " : ""); } dump_bytes (buffer, n_bytes); return TRUE; } static void packet_idl (const uint8_t buffer[42], unsigned int channel) { int pa; /* packet address */ int ft; /* format type */ printf ("IDL ch=%u ", channel); switch (channel) { case 0: assert (0); case 4: case 12: printf ("(Low bit rate audio) "); dump_bytes (buffer, 42); break; case 5: case 6: case 13: case 14: pa = vbi_unham8 (buffer[3]); pa |= vbi_unham8 (buffer[4]) << 4; pa |= vbi_unham8 (buffer[5]) << 8; if (pa < 0) { printf ("Hamming error in Datavideo " "packet-address byte.\n"); return; } printf ("(Datavideo) pa=0x%x ", pa); dump_bytes (buffer, 42); break; case 8: case 9: case 10: case 11: case 15: ft = vbi_unham8 (buffer[2]); if (ft < 0) { printf ("Hamming error in IDL format " "A or B format-type byte.\n"); return; } if (0 == (ft & 1)) { int ial; /* interpretation and address length */ unsigned int spa_length; int spa; /* service packet address */ unsigned int i; ial = vbi_unham8 (buffer[3]); if (ial < 0) { printf ("Hamming error in IDL format " "A interpretation-and-address-" "length byte.\n"); return; } spa_length = (unsigned int) ial & 7; if (7 == spa_length) { printf ("(Format A?) "); dump_bytes (buffer, 42); return; } spa = 0; for (i = 0; i < spa_length; ++i) spa |= vbi_unham8 (buffer[4 + i]) << (4 * i); if (spa < 0) { printf ("Hamming error in IDL format " "A service-packet-address byte.\n"); return; } printf ("(Format A) spa=0x%x ", spa); } else if (1 == (ft & 3)) { int an; /* application number */ int ai; /* application identifier */ an = (ft >> 2); ai = vbi_unham8 (buffer[3]); if (ai < 0) { printf ("Hamming error in IDL format " "B application-number byte.\n"); return; } printf ("(Format B) an=%d ai=%d ", an, ai); } dump_bytes (buffer, 42); break; default: dump_bytes (buffer, 42); break; } } static void teletext (const uint8_t buffer[42], unsigned int line) { int pmag; unsigned int magazine; unsigned int packet; if (NULL != pfc) { if (!vbi_pfc_demux_feed (pfc, buffer)) { printf ("Error in Teletext " "PFC packet.\n"); return; } } if (NULL != idl) { if (!vbi_idl_demux_feed (idl, buffer)) { printf ("Error in Teletext " "IDL packet.\n"); return; } } if (!(option_decode_ttx | option_decode_8301 | option_decode_8302 | option_decode_idl)) return; pmag = vbi_unham16p (buffer); if (pmag < 0) { printf ("Hamming error in Teletext " "packet number.\n"); return; } magazine = pmag & 7; if (0 == magazine) magazine = 8; packet = pmag >> 3; if (8 == magazine && 30 == packet) { int designation; designation = vbi_unham8 (buffer[2]); if (designation < 0 ) { printf ("Hamming error in Teletext " "packet 8/30 designation byte.\n"); return; } if (designation >= 0 && designation <= 1) { packet_8301 (buffer, designation); return; } if (designation >= 2 && designation <= 3) { packet_8302 (buffer, designation); return; } } if (30 == packet || 31 == packet) { if (option_decode_idl) { #if 1 packet_idl (buffer, pmag & 15); #else printf ("Teletext IDL packet %u/%2u ", magazine, packet); dump_bytes (buffer, /* n_bytes */ 42); #endif return; } } if (option_decode_ttx) { printf ("Teletext line=%3u %x/%2u ", line, magazine, packet); dump_bytes (buffer, /* n_bytes */ 42); return; } } static void vps (const uint8_t buffer[13], unsigned int line) { if (option_decode_vps) { unsigned int cni; #if 1 vbi_program_id pi; #endif if (option_dump_bin) { printf ("VPS line=%3u ", line); fwrite (buffer, 1, 13, stdout); fflush (stdout); return; } if (!vbi_decode_vps_cni (&cni, buffer)) { printf ("Error in VPS packet CNI.\n"); return; } if (!vbi_decode_vps_pdc (&pi, buffer)) { printf ("Error in VPS packet PDC data.\n"); return; } printf ("VPS line=%3u ", line); _vbi_program_id_dump (&pi, stdout); putchar ('\n'); #if 3 == VBI_VERSION_MINOR if (0 != pi.cni) dump_cni (pi.cni_type, pi.cni); #endif } if (option_decode_vps_other) { static char pr_label[2][20]; static char label[2][20]; static int l[2] = { 0, 0 }; unsigned int i; int c; i = (line != 16); c = vbi_rev8 (buffer[1]); if (c & 0x80) { label[i][l[i]] = 0; strcpy (pr_label[i], label[i]); l[i] = 0; } label[i][l[i]] = _vbi_to_ascii (c); l[i] = (l[i] + 1) % 16; printf ("VPS line=%3u bytes 3-10: " "%02x %02x (%02x='%c') %02x %02x " "%02x %02x %02x %02x (\"%s\")\n", line, buffer[0], buffer[1], c, _vbi_to_ascii (c), buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], pr_label[i]); } } #if 3 == VBI_VERSION_MINOR /* XXX port me back */ static void wss_625 (const uint8_t buffer[2]) { if (option_decode_wss) { vbi_aspect_ratio ar; if (!vbi_decode_wss_625 (&ar, buffer)) { printf ("Error in WSS packet.\n"); return; } fputs ("WSS ", stdout); _vbi_aspect_ratio_dump (&ar, stdout); putchar ('\n'); } } #endif /* 3 == VBI_VERSION_MINOR */ static vbi_bool decode_frame (const vbi_sliced * s, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { static double metronome = 0.0; static double last_sample_time = 0.0; static int64_t last_stream_time = 0; raw = raw; /* unused */ sp = sp; if (option_dump_time || option_metronome_tick > 0.0) { /* Sample time: When we captured the data, in seconds since 1970-01-01 (gettimeofday()). Stream time: For ATSC/DVB the Presentation Time Stamp. For analog the frame number multiplied by the nominal frame period (1/25 or 1001/30000 s). Both given in 90 kHz units. Note this isn't fully implemented yet. */ if (option_metronome_tick > 0.0) { printf ("ST %f (adv %+f, err %+f) PTS %" PRId64 " (adv %+" PRId64 ", err %+f)\n", sample_time, sample_time - last_sample_time, sample_time - metronome, stream_time, stream_time - last_stream_time, (double) stream_time - metronome); metronome += option_metronome_tick; } else { printf ("ST %f (%+f) PTS %" PRId64 " (%+" PRId64 ")\n", sample_time, sample_time - last_sample_time, stream_time, stream_time - last_stream_time); } last_sample_time = sample_time; last_stream_time = stream_time; } while (n_lines > 0) { switch (s->id) { case VBI_SLICED_TELETEXT_B_L10_625: case VBI_SLICED_TELETEXT_B_L25_625: case VBI_SLICED_TELETEXT_B_625: teletext (s->data, s->line); break; case VBI_SLICED_VPS: case VBI_SLICED_VPS_F2: vps (s->data, s->line); break; case VBI_SLICED_CAPTION_625_F1: case VBI_SLICED_CAPTION_625_F2: case VBI_SLICED_CAPTION_625: case VBI_SLICED_CAPTION_525_F1: case VBI_SLICED_CAPTION_525_F2: case VBI_SLICED_CAPTION_525: caption (s->data, s->line); break; case VBI_SLICED_WSS_625: #if 3 == VBI_VERSION_MINOR /* XXX port me back */ wss_625 (s->data); #endif break; case VBI_SLICED_WSS_CPR1204: break; } ++s; --n_lines; } return TRUE; } static void usage (FILE * fp) { /* FIXME Supposed to be localized but we can't use #ifs within the _() macro. */ fprintf (fp, "\ %s %s -- Low-level VBI decoder\n\n\ Copyright (C) 2004, 2006, 2007 Michael H. Schimek\n\ This program is licensed under GPLv2 or later. NO WARRANTIES.\n\n\ Usage: %s [options] < sliced VBI data\n\ -h | --help | --usage Print this message and exit\n\ -q | --quiet Suppress progress and error messages\n\ -V | --version Print the program version and exit\n\ Input options:\n\ -i | --input name Read the VBI data from this file instead of\n\ standard input\n\ -P | --pes Source is a DVB PES stream\n\ -T | --ts pid Source is a DVB TS stream\n\ Decoding options:\n\ -1 | --8301 Teletext packet 8/30 format 1 (local time)\n\ -2 | --8302 Teletext packet 8/30 format 2 (PDC)\n\ -c | --cc Closed Caption\n\ -j | --idl Any Teletext IDL packets (M/30, M/31)\n\ -t | --ttx Decode any Teletext packet\n\ -v | --vps Video Programming System (PDC)\n" #if 3 == VBI_VERSION_MINOR /* XXX port me back */ "-w | --wss Wide Screen Signalling\n" #endif "-x | --xds Decode eXtended Data Service (NTSC line 284)\n\ -a | --all Everything above, e.g.\n\ -i decode IDL packets\n\ -a decode everything\n\ -a -i everything except IDL\n\ -l | --idl-ch N\n\ -d | --idl-addr NNN Decode Teletext IDL format A data from channel N,\n\ service packet address NNN (default 0)\n\ -r | --vps-other Decode VPS data unrelated to PDC\n\ -p | --pfc-pgno NNN\n\ -s | --pfc-stream NN Decode Teletext Page Function Clear data\n\ from page NNN (for example 1DF), stream NN\n\ (default 0)\n\ Modifying options:\n\ -e | --hex With -t dump packets in hex and ASCII,\n\ otherwise only ASCII\n\ -n | --network With -1, -2, -v decode CNI and print\n\ available information about the network\n\ -b | --bin With -t, -p, -v dump data in binary format\n\ instead of ASCII\n\ -m | --time Dump capture timestamps\n\ -M | --metronome tick Compare timestamps against a metronome advancing\n\ by tick seconds per frame\n\ ", PROGRAM_NAME, VERSION, program_invocation_name); } static const char short_options [] = "12abcd:ehi:jl:mnp:qrs:tvwxM:PT:V"; #ifdef HAVE_GETOPT_LONG static const struct option long_options [] = { { "8301", no_argument, NULL, '1' }, { "8302", no_argument, NULL, '2' }, { "all", no_argument, NULL, 'a' }, { "bin", no_argument, NULL, 'b' }, { "cc", no_argument, NULL, 'c' }, { "idl-addr", required_argument, NULL, 'd' }, { "hex", no_argument, NULL, 'e' }, { "help", no_argument, NULL, 'h' }, { "usage", no_argument, NULL, 'h' }, { "input", required_argument, NULL, 'i' }, { "idl", no_argument, NULL, 'j' }, { "idl-ch", required_argument, NULL, 'l' }, { "time", no_argument, NULL, 'm' }, { "network", no_argument, NULL, 'n' }, { "pfc-pgno", required_argument, NULL, 'p' }, { "quiet", no_argument, NULL, 'q' }, { "vps-other", no_argument, NULL, 'r' }, { "pfc-stream", required_argument, NULL, 's' }, { "ttx", no_argument, NULL, 't' }, { "vps", no_argument, NULL, 'v' }, { "wss", no_argument, NULL, 'w' }, { "xds", no_argument, NULL, 'x' }, { "metronome", required_argument, NULL, 'M' }, { "pes", no_argument, NULL, 'P' }, { "ts", required_argument, NULL, 'T' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, 0, 0 } }; #else # define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; int main (int argc, char ** argv) { init_helpers (argc, argv); option_in_file_format = FILE_FORMAT_SLICED; for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ break; case '1': option_decode_8301 ^= TRUE; break; case '2': option_decode_8302 ^= TRUE; break; case 'a': option_decode_ttx = TRUE; option_decode_8301 = TRUE; option_decode_8302 = TRUE; option_decode_caption = TRUE; option_decode_idl = TRUE; option_decode_vps = TRUE; option_decode_wss = TRUE; option_decode_xds = TRUE; option_pfc_pgno = 0x1DF; break; case 'b': option_dump_bin ^= TRUE; break; case 'c': option_decode_caption ^= TRUE; break; case 'd': assert (NULL != optarg); option_idl_address = strtol (optarg, NULL, 0); break; case 'e': option_dump_hex ^= TRUE; break; case 'h': usage (stdout); exit (EXIT_SUCCESS); case 'i': assert (NULL != optarg); option_in_file_name = optarg; break; case 'j': option_decode_idl ^= TRUE; break; case 'l': assert (NULL != optarg); option_idl_channel = strtol (optarg, NULL, 0); break; case 'm': option_dump_time ^= TRUE; break; case 'n': option_dump_network ^= TRUE; break; case 'p': assert (NULL != optarg); option_pfc_pgno = strtol (optarg, NULL, 16); break; case 'q': parse_option_quiet (); break; case 'r': option_decode_vps_other ^= TRUE; break; case 's': assert (NULL != optarg); option_pfc_stream = strtol (optarg, NULL, 0); break; case 't': option_decode_ttx ^= TRUE; break; case 'v': option_decode_vps ^= TRUE; break; case 'w': option_decode_wss ^= TRUE; break; case 'x': option_decode_xds ^= TRUE; break; case 'M': assert (NULL != optarg); option_metronome_tick = strtod (optarg, NULL); break; case 'P': option_in_file_format = FILE_FORMAT_DVB_PES; break; case 'T': option_in_ts_pid = parse_option_ts (); option_in_file_format = FILE_FORMAT_DVB_TS; break; case 'V': printf (PROGRAM_NAME " " VERSION "\n"); exit (EXIT_SUCCESS); default: usage (stderr); exit (EXIT_FAILURE); } } if (0 != option_pfc_pgno) { pfc = vbi_pfc_demux_new (option_pfc_pgno, option_pfc_stream, page_function_clear_cb, /* user_data */ NULL); if (NULL == pfc) no_mem_exit (); } if (0 != option_idl_channel) { idl = vbi_idl_a_demux_new (option_idl_channel, option_idl_address, idl_format_a_cb, /* user_data */ NULL); if (NULL == idl) no_mem_exit (); } if (option_decode_xds) { xds = vbi_xds_demux_new (xds_cb, /* used_data */ NULL); if (NULL == xds) no_mem_exit (); } rst = read_stream_new (option_in_file_name, option_in_file_format, option_in_ts_pid, decode_frame); stream_loop (rst); stream_delete (rst); rst = NULL; error_msg (_("End of stream.")); vbi_xds_demux_delete (xds); xds = NULL; vbi_idl_demux_delete (idl); idl = NULL; vbi_pfc_demux_delete (pfc); pfc = NULL; exit (EXIT_SUCCESS); } zvbi-0.2.44/test/exoptest000077500000000000000000000000771476363111200153430ustar00rootroot00000000000000#!/bin/sh # Check export options ./explist --check >/dev/null zvbi-0.2.44/test/exoptest_windows000077500000000000000000000001061476363111200171060ustar00rootroot00000000000000#!/bin/sh # Check export options wine explist.exe --check >/dev/null zvbi-0.2.44/test/exp-test.sh000077500000000000000000000024001476363111200156420ustar00rootroot00000000000000#!/bin/bash sliced_bz2="$1" pgno=${2-100} # 100 ... 899 or empty for all pages, default 100 if [ "$3" = "--val" ] ; then val="valgrind --leak-check=full --error-exitcode=1" fi die() { echo $1 exit 1 } target_loop() { local option="$1" for target in 1 2 3 5 ; do # ttxfilter to clean up malformed streams. bunzip2 < "$sliced_bz2" | ./ttxfilter 100-899 \ | $val ./export -a $target $module$option $pgno \ -o exp-test-out-$target$option.$module \ || die for file in exp-test-out-1$option*.$module ; do file2=`echo "$file" | sed "s/^exp-test-out-1/exp-test-out-$target/"` cmp $file $file2 \ || die "Mismatch btw $module$option target 1 and $target ($file2)" echo "$module$option target $target ok" done done if [ -z "$pgno" ] ; then for target in 1 2 3 5 ; do rm exp-test-out-$target*.$module done fi } if [ -z "$sliced_bz2" ] ; then die "Please name a bzip2'ed sliced VBI file" fi rm -f exp-test-out-* # Module vtx has been disabled in 0.2.28. for module in html text ; do target_loop done for module in png ppm xpm ; do # xpm not ported to 0.3 yet. if ./export -m | grep -q $module ; then target_loop ,aspect=0 target_loop ,aspect=1 fi done echo "Test complete" zvbi-0.2.44/test/explist.c000066400000000000000000000335271476363111200154040ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2000, 2001 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: explist.c,v 1.13 2008-03-01 07:36:54 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #ifdef HAVE_GETOPT_LONG #include #endif #include #include "src/libzvbi.h" #ifndef _ #ifdef ENABLE_NLS # include # define _(String) dgettext (PACKAGE, String) # ifdef gettext_noop # define N_(String) gettext_noop (String) # else # define N_(String) (String) # endif #else /* Stubs that do something close enough. */ # define textdomain(String) (String) # define gettext(String) (String) # define dgettext(Domain,Message) (Message) # define dcgettext(Domain,Message,Type) (Message) # define bindtextdomain(Domain,Directory) (Domain) # define _(String) (String) # define N_(String) (String) #endif #endif vbi_bool check = FALSE; #define TYPE_STR(type) case type : type_str = #type ; break #define INT_TYPE(oi) ((oi)->type == VBI_OPTION_BOOL \ || (oi)->type == VBI_OPTION_INT \ || (oi)->type == VBI_OPTION_MENU) #define REAL_TYPE(oi) ((oi)->type == VBI_OPTION_REAL) #define MENU_TYPE(oi) ((oi)->menu.str != NULL) #define ASSERT_ERRSTR(expr) \ do { \ if (!(expr)) { \ printf("Assertion '" #expr "' failed; errstr=\"%s\"\n", \ vbi_export_errstr(ex)); \ exit(EXIT_FAILURE); \ } \ } while (0) #define BOUNDS_CHECK(type) \ do { \ if (oi->menu.type) { \ assert(oi->def.num >= 0); \ assert(oi->def.num <= oi->max.num); \ assert(oi->min.num == 0); \ assert(oi->max.num > 0); \ assert(oi->step.num == 1); \ } else { \ assert(oi->max.type >= oi->min.type); \ assert(oi->step.type > 0); \ assert(oi->def.type >= oi->min.type \ && oi->def.type <= oi->max.type); \ } \ } while (0) #define STRING_CHECK(type) \ do { \ if (oi->menu.type) { \ assert(oi->def.num >= 0); \ assert(oi->def.num <= oi->max.num); \ assert(oi->min.num == 0); \ assert(oi->max.num > 0); \ assert(oi->step.num == 1); \ } else { \ assert(oi->def.str != NULL); \ } \ } while (0) static void keyword_check(char *keyword) { int i, l; assert(keyword != NULL); l = strlen(keyword); assert(strlen(keyword) > 0); for (i = 0; i < l; i++) { if (isalnum(keyword[i])) continue; if (strchr("_", keyword[i])) continue; fprintf(stderr, "Bad keyword: '%s'\n", keyword); exit(EXIT_FAILURE); } } static void print_current(vbi_option_info *oi, vbi_option_value current) { if (REAL_TYPE(oi)) { printf(" current value=%f\n", current.dbl); if (!oi->menu.dbl) assert(current.dbl >= oi->min.dbl && current.dbl <= oi->max.dbl); } else { printf(" current value=%d\n", current.num); if (!oi->menu.num) assert(current.num >= oi->min.num && current.num <= oi->max.num); } } static void test_modified(vbi_option_info *oi, vbi_option_value old, vbi_option_value new) { if (REAL_TYPE(oi)) { /* XXX unsafe */ if (old.dbl != new.dbl) { printf("but modified current value to %f\n", new.dbl); exit(EXIT_FAILURE); } } else { if (old.num != new.num) { printf("but modified current value to %d\n", new.num); exit(EXIT_FAILURE); } } } static void test_set_int(vbi_export *ex, vbi_option_info *oi, vbi_option_value *current, int value) { vbi_option_value new_current; vbi_bool r; printf(" try to set %d: ", value); r = vbi_export_option_set(ex, oi->keyword, value); if (r) printf("success."); else printf("failed, errstr=\"%s\".", vbi_export_errstr(ex)); new_current.num = 0x54321; if (!vbi_export_option_get(ex, oi->keyword, &new_current)) { printf("vbi_export_option_get failed, errstr==\"%s\"\n", vbi_export_errstr(ex)); if (new_current.num != 0x54321) printf("but modified destination to %d\n", new_current.num); exit(EXIT_FAILURE); } if (!r) test_modified(oi, *current, new_current); print_current(oi, *current = new_current); } static void test_set_real(vbi_export *ex, vbi_option_info *oi, vbi_option_value *current, double value) { vbi_option_value new_current; vbi_bool r; printf(" try to set %f: ", value); r = vbi_export_option_set(ex, oi->keyword, value); if (r) printf("success."); else printf("failed, errstr=\"%s\".", vbi_export_errstr(ex)); new_current.dbl = 8192.0; if (!vbi_export_option_get(ex, oi->keyword, &new_current)) { printf("vbi_export_option_get failed, errstr==\"%s\"\n", vbi_export_errstr(ex)); /* XXX unsafe */ if (new_current.dbl != 8192.0) printf("but modified destination to %f\n", new_current.dbl); exit(EXIT_FAILURE); } if (!r) test_modified(oi, *current, new_current); print_current(oi, *current = new_current); } static void test_set_entry(vbi_export *ex, vbi_option_info *oi, vbi_option_value *current, int entry) { vbi_option_value new_current; int new_entry; vbi_bool r0, r1; vbi_bool valid; valid = (MENU_TYPE(oi) && entry >= oi->min.num && entry <= oi->max.num); printf(" try to set menu entry %d: ", entry); r0 = vbi_export_option_menu_set(ex, oi->keyword, entry); switch (r0 = r0 * 2 + valid) { case 0: printf("failed as expected, errstr=\"%s\".", vbi_export_errstr(ex)); break; case 1: printf("failed, errstr=\"%s\".", vbi_export_errstr(ex)); break; case 2: printf("unexpected success."); break; default: printf("success."); } ASSERT_ERRSTR(vbi_export_option_get(ex, oi->keyword, &new_current)); if (r0 == 0 || r0 == 1) test_modified(oi, *current, new_current); valid = MENU_TYPE(oi); new_entry = 0x33333; r1 = vbi_export_option_menu_get(ex, oi->keyword, &new_entry); switch (r1 = r1 * 2 + valid) { case 1: printf("\nvbi_export_option_menu_get failed, errstr==\"%s\"\n", vbi_export_errstr(ex)); break; case 2: printf("\nvbi_export_option_menu_get: unexpected success.\n"); break; default: break; } if ((r1 == 0 || r1 == 1) && new_entry != 0x33333) { printf("vbi_export_option_menu_get failed, " "but modified destination to %d\n", new_current.num); exit(EXIT_FAILURE); } if (r0 == 1 || r0 == 2 || r1 == 1 || r1 == 2) exit(EXIT_FAILURE); switch (oi->type) { case VBI_OPTION_BOOL: case VBI_OPTION_INT: if (oi->menu.num) assert(new_current.num == oi->menu.num[new_entry]); else test_modified(oi, *current, new_current); print_current(oi, *current = new_current); break; case VBI_OPTION_REAL: if (oi->menu.dbl) { /* XXX unsafe */ assert(new_current.dbl == oi->menu.dbl[new_entry]); } else { test_modified(oi, *current, new_current); } print_current(oi, *current = new_current); break; case VBI_OPTION_MENU: print_current(oi, *current = new_current); break; default: assert(!"reached"); break; } } static void dump_option_info(vbi_export *ex, vbi_option_info *oi) { vbi_option_value val; const char *type_str; int i; switch (oi->type) { TYPE_STR(VBI_OPTION_BOOL); TYPE_STR(VBI_OPTION_INT); TYPE_STR(VBI_OPTION_REAL); TYPE_STR(VBI_OPTION_STRING); TYPE_STR(VBI_OPTION_MENU); default: printf(" * Option %s has invalid type %d\n", oi->keyword, oi->type); exit(EXIT_FAILURE); } printf(" * type=%s keyword=%s label=\"%s\" tooltip=\"%s\"\n", type_str, oi->keyword, _(oi->label), _(oi->tooltip)); keyword_check(oi->keyword); switch (oi->type) { case VBI_OPTION_BOOL: case VBI_OPTION_INT: BOUNDS_CHECK(num); if (oi->menu.num) { printf(" %d menu entries, default=%d: ", oi->max.num - oi->min.num + 1, oi->def.num); for (i = oi->min.num; i <= oi->max.num; i++) printf("%d%s", oi->menu.num[i], (i < oi->max.num) ? ", " : ""); printf("\n"); } else printf(" default=%d, min=%d, max=%d, step=%d\n", oi->def.num, oi->min.num, oi->max.num, oi->step.num); ASSERT_ERRSTR(vbi_export_option_get(ex, oi->keyword, &val)); print_current(oi, val); if (check) { if (oi->menu.num) { test_set_entry(ex, oi, &val, oi->min.num); test_set_entry(ex, oi, &val, oi->max.num); test_set_entry(ex, oi, &val, oi->min.num - 1); test_set_entry(ex, oi, &val, oi->max.num + 1); test_set_int(ex, oi, &val, oi->menu.num[oi->min.num]); test_set_int(ex, oi, &val, oi->menu.num[oi->max.num]); test_set_int(ex, oi, &val, oi->menu.num[oi->min.num] - 1); test_set_int(ex, oi, &val, oi->menu.num[oi->max.num] + 1); } else { test_set_entry(ex, oi, &val, 0); test_set_int(ex, oi, &val, oi->min.num); test_set_int(ex, oi, &val, oi->max.num); test_set_int(ex, oi, &val, oi->min.num - 1); test_set_int(ex, oi, &val, oi->max.num + 1); } } break; case VBI_OPTION_REAL: BOUNDS_CHECK(dbl); if (oi->menu.dbl) { printf(" %d menu entries, default=%d: ", oi->max.num - oi->min.num + 1, oi->def.num); for (i = oi->min.num; i <= oi->max.num; i++) printf("%f%s", oi->menu.dbl[i], (i < oi->max.num) ? ", " : ""); } else printf(" default=%f, min=%f, max=%f, step=%f\n", oi->def.dbl, oi->min.dbl, oi->max.dbl, oi->step.dbl); ASSERT_ERRSTR(vbi_export_option_get(ex, oi->keyword, &val)); print_current(oi, val); if (check) { if (oi->menu.num) { test_set_entry(ex, oi, &val, oi->min.num); test_set_entry(ex, oi, &val, oi->max.num); test_set_entry(ex, oi, &val, oi->min.num - 1); test_set_entry(ex, oi, &val, oi->max.num + 1); test_set_real(ex, oi, &val, oi->menu.dbl[oi->min.num]); test_set_real(ex, oi, &val, oi->menu.dbl[oi->max.num]); test_set_real(ex, oi, &val, oi->menu.dbl[oi->min.num] - 1); test_set_real(ex, oi, &val, oi->menu.dbl[oi->max.num] + 1); } else { test_set_entry(ex, oi, &val, 0); test_set_real(ex, oi, &val, oi->min.dbl); test_set_real(ex, oi, &val, oi->max.dbl); test_set_real(ex, oi, &val, oi->min.dbl - 1); test_set_real(ex, oi, &val, oi->max.dbl + 1); } } break; case VBI_OPTION_STRING: if (oi->menu.str) { STRING_CHECK(str); printf(" %d menu entries, default=%d: ", oi->max.num - oi->min.num + 1, oi->def.num); for (i = oi->min.num; i <= oi->max.num; i++) printf("%s%s", oi->menu.str[i], (i < oi->max.num) ? ", " : ""); } else printf(" default=\"%s\"\n", oi->def.str); ASSERT_ERRSTR(vbi_export_option_get(ex, oi->keyword, &val)); printf(" current value=\"%s\"\n", val.str); assert(val.str); free(val.str); if (check) { printf(" try to set \"foobar\": "); if (vbi_export_option_set(ex, oi->keyword, "foobar")) printf("success."); else printf("failed, errstr=\"%s\".", vbi_export_errstr(ex)); ASSERT_ERRSTR(vbi_export_option_get(ex, oi->keyword, &val)); printf(" current value=\"%s\"\n", val.str); assert(val.str); free(val.str); } break; case VBI_OPTION_MENU: printf(" %d menu entries, default=%d: ", oi->max.num - oi->min.num + 1, oi->def.num); for (i = oi->min.num; i <= oi->max.num; i++) { assert(oi->menu.str[i] != NULL); printf("%s%s", _(oi->menu.str[i]), (i < oi->max.num) ? ", " : ""); } printf("\n"); ASSERT_ERRSTR(vbi_export_option_get(ex, oi->keyword, &val)); print_current(oi, val); if (check) { test_set_entry(ex, oi, &val, oi->min.num); test_set_entry(ex, oi, &val, oi->max.num); test_set_entry(ex, oi, &val, oi->min.num - 1); test_set_entry(ex, oi, &val, oi->max.num + 1); } break; default: assert(!"reached"); break; } } static void list_options(vbi_export *ex) { vbi_option_info *oi; int i; puts(" List of options:"); for (i = 0; (oi = vbi_export_option_info_enum(ex, i)); i++) { assert(oi->keyword != NULL); ASSERT_ERRSTR(oi == vbi_export_option_info_keyword(ex, oi->keyword)); dump_option_info(ex, oi); } } static void list_modules(void) { vbi_export_info *xi; vbi_export *ex; char *errstr; int i; puts("List of export modules:"); for (i = 0; (xi = vbi_export_info_enum(i)); i++) { assert(xi->keyword != NULL); assert(xi == vbi_export_info_keyword(xi->keyword)); printf("* keyword=%s label=\"%s\"\n" " tooltip=\"%s\" mime_type=%s extension=%s\n", xi->keyword, _(xi->label), _(xi->tooltip), xi->mime_type, xi->extension); keyword_check(xi->keyword); if (!(ex = vbi_export_new(xi->keyword, &errstr))) { printf("Could not open '%s': %s\n", xi->keyword, errstr); exit(EXIT_FAILURE); } ASSERT_ERRSTR(xi == vbi_export_info_export(ex)); list_options(ex); vbi_export_delete(ex); } puts("-- end of list --"); } static const char short_options[] = "c"; #ifdef HAVE_GETOPT_LONG static const struct option long_options[] = { { "check", no_argument, NULL, 'c' }, { 0, 0, 0, 0 } }; #else #define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif int main(int argc, char **argv) { int index, c; setlocale (LC_ALL, ""); textdomain ("foobar"); /* we are not the library */ while ((c = getopt_long(argc, argv, short_options, long_options, &index)) != -1) switch (c) { case 'c': check = TRUE; break; default: fprintf(stderr, "Unknown option\n"); exit(EXIT_FAILURE); } list_modules(); exit(EXIT_SUCCESS); } zvbi-0.2.44/test/export.c000066400000000000000000000723041476363111200152310ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2004, 2005, 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: export.c,v 1.25 2013-08-28 14:44:39 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #ifdef HAVE_GETOPT_LONG # include #endif #include "src/version.h" #if 2 == VBI_VERSION_MINOR # include "src/cache.h" # include "src/decoder.h" # include "src/export.h" # include "src/page_table.h" # include "src/vbi.h" # include "src/vt.h" # define vbi_decoder_feed(vbi, sliced, n_lines, ts) \ vbi_decode (vbi, (vbi_sliced *) sliced, n_lines, ts) # define vbi_export_info_from_export(ex) \ vbi_export_info_export (ex) /* Not available. */ # define vbi_export_set_timestamp(ex, ts) ((void) 0) # define vbi_export_set_link_cb(ex, cb, ud) ((void) 0) # define vbi_export_set_pdc_cb(ex, cb, ud) ((void) 0) #elif 3 == VBI_VERSION_MINOR # include "src/misc.h" # include "src/zvbi.h" #else # error VBI_VERSION_MINOR == ? #endif #include "sliced.h" #undef _ #define _(x) x /* later */ #define PROGRAM_NAME "zvbi-export" static const char * option_in_file_name; static enum file_format option_in_file_format; static unsigned int option_in_ts_pid; static vbi_bool option_dcc; static unsigned int option_delay; static vbi_bool have_option_default_cs; static vbi_ttx_charset_code option_default_cs; static vbi_ttx_charset_code option_override_cs; static vbi_bool option_dump_pg; static vbi_bool option_fast; static vbi_bool option_header_only; static vbi_bool option_hyperlinks; static vbi_bool option_navigation; static vbi_bool option_padding; static vbi_bool option_panels; static vbi_bool option_pdc_enum; static vbi_bool option_pdc_links; static vbi_bool option_row_update; static vbi_bool option_subtitles; static vbi_rgba option_default_bg; static vbi_rgba option_default_fg; static unsigned int option_target = 3; static struct stream * rst; static vbi_decoder * vbi; static vbi_export * ex; static vbi_page_table * pt; static vbi_pgno cc_chan; static char * out_file_name_prefix; static char * out_file_name_suffix; static int cr; static vbi_bool quit; static void close_output_file (FILE * fp) { if (fp != stdout) { if (0 != fclose (fp)) write_error_exit (/* msg: errno */ NULL); } else { fflush (fp); } } static char * output_file_name (vbi_pgno pgno, vbi_subno subno) { char *name; int r; if (NULL == out_file_name_prefix) { error_exit ("This target requires " "an output file name.\n"); } r = asprintf (&name, "%s-%03x-%02x.%s", out_file_name_prefix, pgno, subno, out_file_name_suffix); if (r < 0 || NULL == name) no_mem_exit (); return name; } static FILE * open_output_file (vbi_pgno pgno, vbi_subno subno) { char *name; FILE *fp; if (NULL == out_file_name_prefix) return stdout; name = output_file_name (pgno, subno); fp = fopen (name, "w"); if (NULL == fp) { error_exit (_("Could not open " "output file '%s': %s."), name, strerror (errno)); } free (name); return fp; } static void page_dump (vbi_page * pg) { unsigned int row; for (row = 0; row < (unsigned int) pg->rows; ++row) { const vbi_char *cp; unsigned int column; fprintf (stderr, "%2d: >", row); cp = pg->text + row * pg->columns; for (column = 0; column < (unsigned int) pg->columns; ++column) { int c; c = cp[column].unicode; if (c < 0x20 || c > 0x7E) c = '.'; fputc (c, stderr); } fputs ("<\n", stderr); } } #if 2 == VBI_VERSION_MINOR static void do_export (vbi_pgno pgno, vbi_subno subno) { vbi_page page; vbi_bool success; if (option_delay > 1) { --option_delay; return; } success = vbi_fetch_vt_page (vbi, &page, pgno, subno, VBI_WST_LEVEL_3p5, /* n_rows */ 25, /* navigation */ TRUE); if (!success) { /* Shouldn't happen. */ error_exit (_("Unknown error.")); } if (option_dump_pg) { page_dump (&page); } switch (option_target) { char *file_name; void *buffer; void *buffer2; FILE *fp; size_t size; ssize_t ssize; case 1: buffer = malloc (1 << 20); if (NULL == buffer) no_mem_exit (); ssize = vbi_export_mem (ex, buffer, 1 << 20, &page); success = (ssize >= 0); if (success) { ssize_t ssize2; fp = open_output_file (pgno, subno); if (1 != fwrite (buffer, ssize, 1, fp)) write_error_exit (/* msg: errno */ NULL); close_output_file (fp); /* Test. */ ssize2 = vbi_export_mem (ex, buffer, 0, &page); assert (ssize == ssize2); assert (ssize > 0); ssize2 = vbi_export_mem (ex, buffer, ssize - 1, &page); assert (ssize == ssize2); } free (buffer); break; case 2: buffer = NULL; buffer2 = vbi_export_alloc (ex, &buffer, &size, &page); /* Test. */ assert (buffer == buffer2); success = (NULL != buffer); if (success) { fp = open_output_file (pgno, subno); if (1 != fwrite (buffer, size, 1, fp)) write_error_exit (/* msg: errno */ NULL); close_output_file (fp); free (buffer); } break; case 3: /* This is the default target. The other cases are only implemented for tests and will be removed when I wrote proper unit tests. */ fp = open_output_file (pgno, subno); success = vbi_export_stdio (ex, fp, &page); close_output_file (fp); break; case 5: file_name = output_file_name (pgno, subno); success = vbi_export_file (ex, file_name, &page); free (file_name); break; default: error_exit ("Invalid target %u.", option_target); break; } if (!success) { error_exit (_("Export of page %x failed: %s"), pgno, vbi_export_errstr (ex)); } vbi_unref_page (&page); } static void event_handler (vbi_event * ev, void * user_data) { vbi_pgno pgno; vbi_subno subno; user_data = user_data; /* unused */ if (quit) return; switch (ev->type) { case VBI_EVENT_TTX_PAGE: pgno = ev->ev.ttx_page.pgno; subno = ev->ev.ttx_page.subno; if (option_log_mask & VBI_LOG_INFO) { fprintf (stderr, "Teletext page %03x.%02x %c", pgno, subno, cr); } if (0 == vbi_page_table_num_pages (pt)) { do_export (pgno, subno); } else if (vbi_page_table_contains_page (pt, pgno)) { do_export (pgno, subno); if (!option_subtitles) { vbi_page_table_remove_page (pt, pgno); quit = (0 == vbi_page_table_num_pages (pt)); } } break; default: assert (0); } } static void finalize (void) { /* Nothing to do. */ } static void init_vbi_decoder (void) { vbi_bool success; vbi = vbi_decoder_new (); if (NULL == vbi) no_mem_exit (); if (have_option_default_cs) { vbi_teletext_set_default_region (vbi, option_default_cs); } success = vbi_event_handler_add (vbi, VBI_EVENT_TTX_PAGE, event_handler, /* user_data */ NULL); if (!success) no_mem_exit (); } #elif 3 == VBI_VERSION_MINOR #define ev_timestamp ev->timestamp extern void vbi_preselection_dump (const vbi_preselection *pl, FILE * fp); static void pdc_dump (vbi_page * pg) { const vbi_preselection *pl; unsigned int size; unsigned int i; pl = vbi_page_get_preselections (pg, &size); assert (NULL != pl); for (i = 0; i < size; ++i) { fprintf (stderr, "%02u: ", i); _vbi_preselection_dump (pl + i, stderr); fputc ('\n', stderr); } if (0 == i) fputs ("No PDC data\n", stderr); } static vbi_bool export_link (vbi_export * e, void * user_data, const vbi_link * link) { vbi_bool success; user_data = user_data; /* unused */ if (0) fprintf (stderr, "link text: \"%s\"\n", link->name); switch (link->type) { case VBI_LINK_HTTP: case VBI_LINK_FTP: case VBI_LINK_EMAIL: success = vbi_export_printf (e, "%s", link->url, link->name); break; case VBI_LINK_PAGE: case VBI_LINK_SUBPAGE: success = vbi_export_printf (e, "%s", out_file_name_prefix ? out_file_name_prefix : "ttx", link->pgno, (VBI_ANY_SUBNO == link->subno) ? 0 : link->subno, out_file_name_suffix ? out_file_name_suffix : "html", link->name); break; default: success = vbi_export_puts (e, link->name); break; } return success; } static vbi_bool export_pdc (vbi_export * e, void * user_data, const vbi_preselection *pl, const char * text) { unsigned int end; vbi_bool success; user_data = user_data; /* unused */ end = pl->at1_hour * 60 + pl->at1_minute + pl->length; /* XXX pl->title uses locale encoding but the html page may not. (export charset parameter) */ success = vbi_export_printf (e, "%s", pl->year, pl->month, pl->day, pl->at1_hour, pl->at1_minute, (end / 60 % 24), end % 60, pl->at2_hour, pl->at2_minute, pl->_pgno, pl->title, text); return success; } static void do_export (vbi_pgno pgno, vbi_subno subno, double timestamp) { vbi_page *pg; vbi_bool success; if (option_delay > 1) { --option_delay; return; } if (pgno >= 0x100) { if (0 != option_override_cs) { pg = vbi_decoder_get_page (vbi, NULL /* current network */, pgno, subno, VBI_HEADER_ONLY, option_header_only, VBI_PADDING, option_padding, VBI_PANELS, option_panels, VBI_NAVIGATION, option_navigation, VBI_HYPERLINKS, option_hyperlinks, VBI_PDC_LINKS, option_pdc_links, VBI_WST_LEVEL, VBI_WST_LEVEL_3p5, VBI_OVERRIDE_CHARSET_0, option_override_cs, VBI_END); } else { pg = vbi_decoder_get_page (vbi, NULL /* current network */, pgno, subno, VBI_HEADER_ONLY, option_header_only, VBI_PADDING, option_padding, VBI_PANELS, option_panels, VBI_NAVIGATION, option_navigation, VBI_HYPERLINKS, option_hyperlinks, VBI_PDC_LINKS, option_pdc_links, VBI_WST_LEVEL, VBI_WST_LEVEL_3p5, VBI_DEFAULT_CHARSET_0, option_default_cs, VBI_END); } } else { pg = vbi_decoder_get_page (vbi, NULL /* current network */, pgno, subno, VBI_PADDING, option_padding, VBI_DEFAULT_FOREGROUND, option_default_fg, VBI_DEFAULT_BACKGROUND, option_default_bg, VBI_ROW_CHANGE, option_row_update, VBI_END); } assert (NULL != pg); if (option_dump_pg) { page_dump (pg); } switch (option_target) { char *file_name; void *buffer; void *buffer2; FILE *fp; size_t size; ssize_t ssize; case 1: buffer = malloc (1 << 20); if (NULL == buffer) no_mem_exit (); ssize = vbi_export_mem (ex, buffer, 1 << 20, pg); success = (ssize >= 0); if (success) { ssize_t ssize2; fp = open_output_file (pgno, subno); if (1 != fwrite (buffer, ssize, 1, fp)) write_error_exit (/* msg: errno */ NULL); close_output_file (fp); /* Test. */ ssize2 = vbi_export_mem (ex, buffer, 0, pg); assert (ssize == ssize2); assert (ssize > 0); ssize2 = vbi_export_mem (ex, buffer, ssize - 1, pg); assert (ssize == ssize2); } free (buffer); break; case 2: buffer = NULL; buffer2 = vbi_export_alloc (ex, &buffer, &size, pg); /* Test. */ assert (buffer == buffer2); success = (NULL != buffer); if (success) { fp = open_output_file (pgno, subno); if (1 != fwrite (buffer, size, 1, fp)) write_error_exit (/* msg: errno */ NULL); close_output_file (fp); free (buffer); } break; case 3: /* This is the default target. The other cases are only implemented for tests and will be removed when I wrote proper unit tests. */ fp = open_output_file (pgno, subno); /* For proper timing of subtitles. */ vbi_export_set_timestamp (ex, timestamp); success = vbi_export_stdio (ex, fp, pg); close_output_file (fp); break; case 5: file_name = output_file_name (pgno, subno); success = vbi_export_file (ex, file_name, pg); free (file_name); break; default: error_exit ("Invalid target %u.", option_target); break; } if (!success) { error_exit (_("Export of page %x failed: %s"), pgno, vbi_export_errstr (ex)); } if (option_pdc_enum) { pdc_dump (pg); } vbi_page_delete (pg); pg = NULL; } static void update_page_table (void) { vbi_pgno pgno; if (0 == vbi_page_table_num_pages (pt)) return; pgno = 0; while (vbi_page_table_next_page (pt, &pgno)) { vbi_ttx_page_stat ps; if (!vbi_teletext_decoder_get_ttx_page_stat (vbi_decoder_cast_to_teletext_decoder (vbi), &ps, /* nk: current */ NULL, pgno)) { continue; } /* XXX what are the defaults in ps until we receive the page inventory? */ if (VBI_NO_PAGE == ps.page_type) { vbi_page_table_remove_page (pt, pgno); } else if (0 && ps.subpages > 0) { if (!vbi_page_table_contains_all_subpages (pt, pgno)) vbi_page_table_remove_subpages (pt, pgno, ps.subpages, 0x3F7E); } } quit = (0 == vbi_page_table_num_pages (pt)); } static vbi_bool event_handler (const vbi_event * ev, void * user_data) { vbi_pgno pgno; vbi_subno subno; user_data = user_data; /* unused */ if (quit) return TRUE; switch (ev->type) { case VBI_EVENT_TTX_PAGE: pgno = ev->ev.ttx_page.pgno; subno = ev->ev.ttx_page.subno; if (option_log_mask & VBI_LOG_INFO) { fprintf (stderr, "Teletext page %03x.%02x %c", pgno, subno, cr); } if (0 == vbi_page_table_num_pages (pt)) { do_export (pgno, subno, ev->timestamp); } else if (vbi_page_table_contains_page (pt, pgno)) { do_export (pgno, subno, ev->timestamp); if (!option_subtitles) { vbi_page_table_remove_page (pt, pgno); quit = (0 == vbi_page_table_num_pages (pt)); } } break; case VBI_EVENT_CC_PAGE: if (option_row_update && !(ev->ev.caption.flags & VBI_ROW_UPDATE)) break; pgno = ev->ev.caption.channel; if (option_log_mask & VBI_LOG_INFO) { fprintf (stderr, "Caption channel %u %c", pgno, cr); } if (pgno != cc_chan) break; do_export (pgno, VBI_ANY_SUBNO, ev->timestamp); break; case VBI_EVENT_PAGE_TYPE: update_page_table (); break; default: assert (0); } return TRUE; /* handled */ } static void finalize (void) { const vbi_export_info *xi; xi = vbi_export_info_from_export (ex); if (xi->open_format && NULL == out_file_name_prefix) { if (!vbi_export_stdio (ex, stdout, NULL)) write_error_exit (vbi_export_errstr (ex)); } } static void init_vbi_decoder (void) { vbi_event_mask event_mask; vbi_bool success; /* XXX videostd? */ vbi = vbi_decoder_new (/* cache: allocate one */ NULL, /* network: current */ NULL, VBI_VIDEOSTD_SET_625_50); if (NULL == vbi) no_mem_exit (); vbi_decoder_detect_channel_change (vbi, option_dcc); event_mask = (VBI_EVENT_TTX_PAGE | VBI_EVENT_CC_PAGE); if (option_fast) event_mask |= VBI_EVENT_PAGE_TYPE; success = vbi_decoder_add_event_handler (vbi, event_mask, event_handler, /* user_data */ NULL); if (!success) no_mem_exit (); } #else # error VBI_VERSION_MINOR == ? #endif static vbi_bool decode_frame (const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { static vbi_bool have_start_timestamp = FALSE; raw = raw; /* unused */ sp = sp; stream_time = stream_time; /* To calculate the delay of the first subtitle page. */ if (!have_start_timestamp) { vbi_export_set_timestamp (ex, sample_time); have_start_timestamp = TRUE; } vbi_decoder_feed (vbi, sliced, n_lines, sample_time); return !quit; } static void init_export_module (const char * module_name) { const vbi_export_info *xi; char *errstr; errstr = NULL; /* just in case */ ex = vbi_export_new (module_name, &errstr); if (NULL == ex) { error_exit (_("Cannot open export module '%s': %s"), module_name, errstr); /* NB. free (errstr); here if you don't exit(). */ } if (0 == strncmp (module_name, "html", 4)) { if (option_hyperlinks) vbi_export_set_link_cb (ex, export_link, /* user_data */ NULL); if (option_pdc_links) vbi_export_set_pdc_cb (ex, export_pdc, /* user_data */ NULL); } xi = vbi_export_info_from_export (ex); if (NULL == xi) no_mem_exit (); if (NULL == out_file_name_suffix) { char *end = NULL; out_file_name_suffix = strdup (xi->extension); if (NULL == out_file_name_suffix) no_mem_exit (); out_file_name_suffix = strtok_r (out_file_name_suffix, ",", &end); } #if 3 == VBI_VERSION_MINOR if (xi->open_format) option_subtitles ^= TRUE; #endif } static void list_options (vbi_export * ex) { const vbi_option_info *oi; unsigned int i; for (i = 0; (oi = vbi_export_option_info_enum (ex, i)); ++i) { char buf[32]; switch (oi->type) { case VBI_OPTION_BOOL: case VBI_OPTION_INT: case VBI_OPTION_MENU: snprintf (buf, sizeof (buf), "%d", oi->def.num); break; case VBI_OPTION_REAL: snprintf (buf, sizeof (buf), "%f", oi->def.dbl); break; case VBI_OPTION_STRING: break; } if (NULL == oi->tooltip) continue; if (NULL == oi->tooltip) { printf (" Option '%s' (%s)\n", oi->keyword, VBI_OPTION_STRING == oi->type ? oi->def.str : buf); } else { printf (" Option '%s' - %s (%s)\n", oi->keyword, _(oi->tooltip), VBI_OPTION_STRING == oi->type ? oi->def.str : buf); } if (VBI_OPTION_MENU == oi->type) { int i; for (i = oi->min.num; i <= oi->max.num; ++i) { printf (" %d - %s\n", i, _(oi->menu.str[i])); } } } } static void list_modules (void) { const vbi_export_info *xi; unsigned int i; for (i = 0; (xi = vbi_export_info_enum (i)); ++i) { vbi_export *ex; printf ("'%s' - %s\n", _(xi->keyword), _(xi->tooltip)); ex = vbi_export_new (xi->keyword, /* errstr */ NULL); if (NULL == ex) no_mem_exit (); list_options (ex); vbi_export_delete (ex); ex = NULL; } } static void usage (FILE * fp) { /* FIXME Supposed to be _(localized) but we can't use #ifs within the _() macro. */ fprintf (fp, "\ %s %s -- Teletext and Closed Caption export utility\n\n\ Copyright (C) 2004, 2005, 2007 Michael H. Schimek\n\ This program is licensed under GPLv2. NO WARRANTIES.\n\n\ Usage: %s [options] format [page number(s)] < sliced vbi data > file\n\ -h | --help | --usage Print this message and exit\n\ -q | --quiet Suppress progress and error messages\n\ -v | --verbose Increase verbosity\n\ -V | --version Print the program version and exit\n\ Input options:\n\ -i | --input name Read the VBI data from this file instead of\n\ standard input\n\ -P | --pes Source is a DVB PES stream\n\ -T | --ts pid Source is a DVB TS stream\n\ Scan options:\n" #if 3 == VBI_VERSION_MINOR "-f | --fast Do not wait for Teletext pages which are\n\ currently not in transmission\n" #endif "-w | --wait n Export the second (third, fourth, ...)\n\ transmission of the requested page\n" #if 3 == VBI_VERSION_MINOR "Formatting options:\n\ -n | --nav Add TOP or FLOF navigation elements to Teletext\n\ pages\n\ -d | --pad Add an extra column to Teletext pages for a more\n\ balanced view, spaces to Closed Caption pages for\n\ readability\n" #endif "Export options:\n" #if 3 == VBI_VERSION_MINOR "-e | --pdc-enum Print additional Teletext PDC information\n" #endif "-g | --dump-pg For debugging dump the vbi_page being exported\n" #if 3 == VBI_VERSION_MINOR "-l | --links Turn HTTP, SMTP, etc. links on a Teletext page\n\ into hyperlinks in HTML output\n" #endif "-o | --output name Write the page to this file instead of standard\n\ output. The page number and a suitable .extension\n\ will be appended as necessary.\n" #if 3 == VBI_VERSION_MINOR "-p | --pdc Turn PDC markup on a Teletext page into hyperlinks\n\ in HTML output\n\ -r | --row-update Export a Closed Caption page only when a row is\n\ complete, not on every new character. Has only an\n\ effect on roll-up and paint-on style caption.\n\ -s | --stream Export all (rather than just one) transmissions\n\ of this page to a single file. This is the default\n\ for caption/subtitle formats.\n" #endif "Formats:\n\ -m | --list List available output formats and their options.\n\ Append options to the format name separated by\n\ commas: text,charset=UTF-8\n\ Valid page numbers are:\n" #if 3 == VBI_VERSION_MINOR "1 ... 8 Closed Caption channel 1 ... 4, text channel\n\ 1 ... 4\n" #endif "100 ... 899 Teletext page. The program can export multiple\n\ Teletext pages: 100 110 200-299. If no page\n\ numbers are given it exports all received Teletext\n\ pages until it is terminated.\n\ ", PROGRAM_NAME, VERSION, program_invocation_name); } static const char short_options [] = "1a:cdefghi:lmno:pqrsvwAB:C:F:H:O:PT:V"; #ifdef HAVE_GETOPT_LONG static const struct option long_options [] = { { "all-pages", no_argument, NULL, '1' }, { "target", required_argument, NULL, 'a' }, { "dcc", no_argument, NULL, 'c' }, { "pad", no_argument, NULL, 'd' }, { "pdc-enum", no_argument, NULL, 'e' }, { "fast", no_argument, NULL, 'f' }, { "dump-pg", no_argument, NULL, 'g' }, { "help", no_argument, NULL, 'h' }, { "usage", no_argument, NULL, 'h' }, { "input", required_argument, NULL, 'i' }, { "links", no_argument, NULL, 'l' }, { "list", no_argument, NULL, 'm' }, { "nav", no_argument, NULL, 'n' }, { "output", required_argument, NULL, 'o' }, { "pdc", no_argument, NULL, 'p' }, { "quiet", no_argument, NULL, 'q' }, { "row-update", no_argument, NULL, 'r' }, { "stream", no_argument, NULL, 's' }, { "verbose", no_argument, NULL, 'v' }, { "wait", no_argument, NULL, 'w' }, { "side-panels", required_argument, NULL, 'A' }, { "default-bg", required_argument, NULL, 'B' }, { "default-cs", required_argument, NULL, 'C' }, { "default-fg", required_argument, NULL, 'F' }, { "header-only", required_argument, NULL, 'H' }, { "override-cs", required_argument, NULL, 'O' }, { "pes", no_argument, NULL, 'P' }, { "ts", required_argument, NULL, 'T' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, 0, 0 } }; #else # define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; static void parse_output_option (void) { assert (NULL != optarg); free (out_file_name_prefix); out_file_name_prefix = NULL; free (out_file_name_suffix); out_file_name_suffix = NULL; if (0 == strcmp (optarg, "-")) { /* Write to stdout. */ } else { char *s; s = strrchr (optarg, '.'); if (NULL == s) { out_file_name_prefix = strdup (optarg); if (NULL == out_file_name_prefix) no_mem_exit (); } else { out_file_name_prefix = strndup (optarg, s - optarg); if (NULL == out_file_name_prefix) no_mem_exit (); if (0 != s[1]) { out_file_name_suffix = strdup (s + 1); if (NULL == out_file_name_suffix) no_mem_exit (); } } } } static vbi_bool valid_pgno (vbi_pgno pgno) { return (vbi_is_bcd (pgno) && pgno >= 0x100 && pgno <= 0x899); } static void invalid_pgno_exit (const char * arg) { error_exit (_("Invalid page number '%s'."), arg); } static void parse_page_numbers (unsigned int argc, char ** argv) { unsigned int i; for (i = 0; i < argc; ++i) { vbi_pgno first_pgno; vbi_pgno last_pgno; vbi_bool success; const char *s; char *end; s = argv[i]; first_pgno = strtoul (s, &end, 16); s = end; if (first_pgno >= 1 && first_pgno <= 8) { if (0 != cc_chan) { error_exit (_("Can export only one " "Closed Caption channel.")); } cc_chan = first_pgno; while (*s && isspace (*s)) ++s; if (0 != *s) invalid_pgno_exit (argv[i]); continue; } if (!valid_pgno (first_pgno)) invalid_pgno_exit (argv[i]); last_pgno = first_pgno; while (*s && isspace (*s)) ++s; if ('-' == *s) { ++s; while (*s && isspace (*s)) ++s; last_pgno = strtoul (s, &end, 16); s = end; if (!valid_pgno (last_pgno)) invalid_pgno_exit (argv[i]); } else if (0 != *s) { invalid_pgno_exit (argv[i]); } success = vbi_page_table_add_pages (pt, first_pgno, last_pgno); if (!success) no_mem_exit (); } } int main (int argc, char ** argv) { const char *module_name; unsigned int n_pages; vbi_bool all_pages; init_helpers (argc, argv); option_in_file_format = FILE_FORMAT_SLICED; option_default_fg = (vbi_rgba) 0xFFFFFF; option_default_bg = (vbi_rgba) 0x000000; all_pages = FALSE; for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ break; case '1': /* Compatibility (used to be pgno -1). */ all_pages = TRUE; break; case 'a': /* For debugging. */ assert (NULL != optarg); option_target = strtoul (optarg, NULL, 0); break; case 'c': option_dcc = TRUE; break; case 'd': option_padding = TRUE; break; case 'e': option_pdc_enum = TRUE; break; case 'f': option_fast = TRUE; break; case 'g': option_dump_pg = TRUE; break; case 'h': usage (stdout); exit (EXIT_SUCCESS); case 'i': assert (NULL != optarg); option_in_file_name = optarg; break; case 'l': option_hyperlinks = TRUE; break; case 'm': list_modules (); exit (EXIT_SUCCESS); case 'n': option_navigation = TRUE; break; case 'o': parse_output_option (); break; case 'p': option_pdc_links = TRUE; break; case 'q': parse_option_quiet (); break; case 'r': option_row_update = TRUE; break; case 's': option_subtitles = TRUE; break; case 'v': parse_option_verbose (); break; case 'w': option_delay += 1; break; case 'A': option_panels = TRUE; break; case 'B': assert (NULL != optarg); option_default_bg = strtoul (optarg, NULL, 0); break; case 'C': assert (NULL != optarg); option_default_cs = strtoul (optarg, NULL, 0); have_option_default_cs = TRUE; break; case 'F': assert (NULL != optarg); option_default_fg = strtoul (optarg, NULL, 0); break; case 'H': option_header_only = TRUE; break; case 'O': assert (NULL != optarg); option_override_cs = strtoul (optarg, NULL, 0); break; case 'P': option_in_file_format = FILE_FORMAT_DVB_PES; break; case 'T': option_in_ts_pid = parse_option_ts (); option_in_file_format = FILE_FORMAT_DVB_TS; break; case 'V': printf (PROGRAM_NAME " " VERSION "\n"); exit (EXIT_SUCCESS); default: usage (stderr); exit (EXIT_FAILURE); } } option_pdc_links |= option_pdc_enum; if (argc - optind < 1) { usage (stderr); exit (EXIT_FAILURE); } module_name = argv[optind++]; pt = vbi_page_table_new (); if (NULL == pt) no_mem_exit (); if (all_pages) { /* Compatibility. */ out_file_name_prefix = strdup ("test"); if (NULL == out_file_name_prefix) no_mem_exit (); } else { parse_page_numbers (argc - optind, &argv[optind]); } n_pages = vbi_page_table_num_pages (pt); if (1 != n_pages && option_delay > 0) { error_exit (_("The --wait option requires " "a single page number.")); } if (NULL == out_file_name_prefix) { switch (n_pages) { case 0: /* all pages? */ error_exit (_("No page number or " "output file name specified.")); break; case 1: /* one page to stdout */ break; default: /* multiple pages */ error_exit (_("No output file name specified.")); break; } } init_export_module (module_name); init_vbi_decoder (); cr = isatty (STDERR_FILENO) ? '\r' : '\n'; rst = read_stream_new (option_in_file_name, option_in_file_format, option_in_ts_pid, decode_frame); stream_loop (rst); stream_delete (rst); rst = NULL; vbi_decoder_delete (vbi); vbi = NULL; finalize (); free (out_file_name_prefix); out_file_name_prefix = NULL; free (out_file_name_suffix); out_file_name_suffix = NULL; if (!option_subtitles) { n_pages = vbi_page_table_num_pages (pt); if (1 == n_pages) { vbi_pgno pgno = 0; vbi_page_table_next_page (pt, &pgno); error_exit (_("End of stream. Page %03x not found."), pgno); } else if (n_pages > 0) { error_exit (_("End of stream. %u pages not found.")); } } vbi_page_table_delete (pt); pt = NULL; vbi_export_delete (ex); ex = NULL; exit (EXIT_SUCCESS); } zvbi-0.2.44/test/glyph.c000066400000000000000000000125711476363111200150330ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2000, 2001 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: glyph.c,v 1.11 2008-03-01 07:36:46 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "src/lang.h" #include "src/export.h" static vbi_page *pg; static vbi_char ac; static int cx, cy; static void new_page(void) { int i; cx = 0; cy = 0; memset(&ac, 0, sizeof(ac)); ac.unicode = 0x0020; ac.foreground = 1; ac.background = 0; assert((pg = calloc(1, sizeof(*pg)))); pg->rows = 25; pg->columns = 40; for (i = 0; i < pg->rows * pg->columns; i++) pg->text[i] = ac; pg->color_map[0] = 0x00000000; pg->color_map[1] = 0x00FFFFFF; } static void putwchar_local(int c) { if (c == '\n') { cx = 0; if (cy < pg->rows - 1) cy++; } else { ac.unicode = c; pg->text[cy * pg->columns + cx] = ac; if (cx < pg->columns - 1) cx++; } } #undef putwchar #define putwchar putwchar_local static void putwstr(const char *s) { for (; *s; s++) putwchar(*s); } static const char national[] = { 0x23, 0x24, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x7B, 0x7C, 0x7D, 0x7E }; static void store(int s) { vbi_export *e; char buf[80]; assert((e = vbi_export_new("ppm", NULL))); snprintf(buf, sizeof(buf) - 1, "char_set_%u.ppm", s); vbi_export_file(e, buf, pg); vbi_export_delete(e); free(pg); } static void print_set(const char *name, int s) { int i, j; new_page(); putwstr(name); putwchar('\n'); for (i = 0; i < 16; i++) { for (j = 2; j < 8; j++) { putwchar(vbi_teletext_unicode(s, 0, j * 16 + i)); putwchar(' '); } putwchar('\n'); } store(s); } int main(int argc, char **argv) { unsigned int i; argc = argc; argv = argv; new_page(); putwstr("ETS 300 706 Table 36: Latin National Option Sub-sets\n\n"); for (i = 1; i < 14; i++) { unsigned int j; for (j = 0; j < sizeof(national) / sizeof(national[0]); j++) { putwchar(vbi_teletext_unicode(1, i, national[j])); putwchar(' '); } putwchar('\n'); } store(0); print_set("ETS 300 706 Table 35: Latin G0 Primary Set\n", 1); print_set("ETS 300 706 Table 37: Latin G2 Supplementary Set\n", 2); print_set("ETS 300 706 Table 38: Cyrillic G0 Primary Set - Option 1 - Serbian/Croatian\n", 3); print_set("ETS 300 706 Table 39: Cyrillic G0 Primary Set - Option 2 - Russian/Bulgarian\n", 4); print_set("ETS 300 706 Table 40: Cyrillic G0 Primary Set - Option 3 - Ukrainian\n", 5); print_set("ETS 300 706 Table 41: Cyrillic G2 Supplementary Set\n", 6); print_set("ETS 300 706 Table 42: Greek G0 Primary Set\n", 7); print_set("ETS 300 706 Table 43: Greek G2 Supplementary Set\n", 8); print_set("ETS 300 706 Table 44: Arabic G0 Primary Set\n", 9); print_set("ETS 300 706 Table 45: Arabic G2 Supplementary Set\n", 10); print_set("ETS 300 706 Table 46: Hebrew G0 Primary Set\n", 11); new_page(); putwstr("ETS 300 706 Table 47: G1 Block Mosaics Set\n\n"); for (i = 0; i < 16; i++) { unsigned int j; for (j = 2; j < 8; j++) { if (j == 4 || j == 5) putwchar(' '); else putwchar(vbi_teletext_unicode(12, 0, j * 16 + i)); putwchar(' '); } putwchar('\n'); } store(12); print_set("ETS 300 706 Table 48: G3 Smooth Mosaics and Line Drawing Set\n", 13); new_page(); putwstr("Teletext composed glyphs\n\n "); for (i = 0x40; i < 0x60; i++) putwchar(vbi_teletext_unicode(1, 0, i)); putwstr("\n\n"); for (i = 0; i < 16; i++) { unsigned int j; putwchar(vbi_teletext_unicode(2, 0, 0x40 + i)); putwstr(" "); for (j = 0x40; j < 0x60; j++) { unsigned int c = vbi_teletext_composed_unicode(i, j); putwchar((c == 0) ? '-' : c); } putwchar('\n'); } store(14); new_page(); putwstr("Teletext composed glyphs\n\n "); for (i = 0x60; i < 0x80; i++) putwchar(vbi_teletext_unicode(1, 0, i)); putwstr("\n\n"); for (i = 0; i < 16; i++) { unsigned int j; putwchar(vbi_teletext_unicode(2, 0, 0x40 + i)); putwstr(" "); for (j = 0x60; j < 0x80; j++) { unsigned int c = vbi_teletext_composed_unicode(i, j); putwchar((c == 0) ? '-' : c); } putwchar('\n'); } store(15); new_page(); pg->columns = 32; pg->rows = 16; putwstr("EIA 608 Closed Captioning Basic Character Set\n\n"); for (i = 0; i < 8; i++) { unsigned int j; for (j = 0x20; j < 0x80; j += 8) { putwchar(vbi_caption_unicode(j + i, /* to_upper */ FALSE)); putwchar(' '); } putwchar('\n'); } store(16); new_page(); pg->columns = 32; pg->rows = 16; putwstr("EIA 608 Closed Captioning Special Characters\n\n"); for (i = 0; i < 16; i++) { putwchar(vbi_caption_unicode(0x1130 | i, /* to_upper */ FALSE)); } store(17); exit(EXIT_SUCCESS); } zvbi-0.2.44/test/osc.c000066400000000000000000000446311476363111200144760ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2000-2002, 2004 Michael H. Schimek * Copyright (C) 2003 James Mastros * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: osc.c,v 1.35 2008-03-01 07:36:41 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #ifdef HAVE_GETOPT_LONG #include #endif #include "src/inout.h" #include "src/decoder.h" #include "src/misc.h" #include "src/hamm.h" #include "src/io-sim.h" #include "src/raw_decoder.h" /* _vbi_service_table[] */ #include "src/proxy-msg.h" #include "src/proxy-client.h" #ifndef X_DISPLAY_MISSING #include #include #include vbi_capture * cap; vbi_raw_decoder * par; vbi_proxy_client * pxc = NULL; int src_w, src_h; vbi_sliced * sliced; int slines; vbi_bool quit; int do_sim; int ignore_error; int desync; Display * display; int screen; Colormap cmap; Window window; int dst_w, dst_h; GC gc; XEvent event; XImage * ximage; void * ximgdata; unsigned char *raw1, *raw2; int palette[256]; int depth; int draw_row, draw_offset; int draw_count = -1; int cur_x, cur_y; extern void vbi_capture_set_log_fp (vbi_capture * capture, FILE * fp); extern vbi_bool vbi_capture_force_read_mode; #define PIL(day, mon, hour, min) \ (((day) << 15) + ((mon) << 11) + ((hour) << 6) + ((min) << 0)) /* Return value must be free()d by caller! */ static char * decode_ttx(uint8_t *buf, int line) { char *text, *text_start; int packet_address; int magazine, packet; int j; text_start = text = malloc(255); memset(text, 0, 255); packet_address = vbi_unham16p (buf + 0); if (packet_address < 0) return text; /* hamming error */ magazine = packet_address & 7; packet = packet_address >> 3; text += snprintf(text, 255, "pg %x%02d ln %03d >", magazine, packet, line); for (j = 0; j < 42; j++) { char c = _vbi_to_ascii (buf[j]); *text = c; text++; } *text='<'; *text=0; return text_start; } static char * dump_pil(int pil) { int day, mon, hour, min; static char text[255]; memset(text, 0, 255); day = pil >> 15; mon = (pil >> 11) & 0xF; hour = (pil >> 6) & 0x1F; min = pil & 0x3F; if (pil == PIL(0, 15, 31, 63)) snprintf(text, 255, " PDC: Timer-control (no PDC)\n"); else if (pil == PIL(0, 15, 30, 63)) snprintf(text, 255, " PDC: Recording inhibit/terminate\n"); else if (pil == PIL(0, 15, 29, 63)) snprintf(text, 255, " PDC: Interruption\n"); else if (pil == PIL(0, 15, 28, 63)) snprintf(text, 255, " PDC: Continue\n"); else if (pil == PIL(31, 15, 31, 63)) snprintf(text, 255, " PDC: No time\n"); else snprintf(text, 255, " PDC: %05x, 200X-%02d-%02d %02d:%02d\n", pil, mon, day, hour, min); return text; } static char * decode_vps(uint8_t *buf) { char *text, *text_start; static char pr_label[20]; static char label[20]; static int l = 0; int cni, pcs, pty, pil; int c; text_start=text=malloc(255); memset(text, 0, 255); text += snprintf(text, 255, "VPS: "); c = vbi_rev8 (buf[1]); if ((int8_t) c < 0) { label[l] = 0; memcpy(pr_label, label, sizeof(pr_label)); l = 0; } c &= 0x7F; label[l] = _vbi_to_ascii (c); l = (l + 1) % 16; text += snprintf(text, 250, " 3-10: %02x %02x %02x %02x %02x %02x %02x %02x (\"%s\")\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], pr_label); pcs = buf[2] >> 6; cni = + ((buf[10] & 3) << 10) + ((buf[11] & 0xC0) << 2) + ((buf[8] & 0xC0) << 0) + (buf[11] & 0x3F); pil = ((buf[8] & 0x3F) << 14) + (buf[9] << 6) + (buf[10] >> 2); pty = buf[12]; /* FIXME use real buffer size. */ text += snprintf(text, 100, " CNI: %04x PCS: %d PTY: %d ", cni, pcs, pty); text += snprintf(text, 50, " %s", dump_pil(pil)); return(text_start); } /* End from capture.c */ static void draw(unsigned char *raw) { int rem = src_w - draw_offset; char buf[256]; unsigned char *data = raw; int i, v, h0, field, end, line; XTextItem xti; int x; if (draw_count == 0) return; if (draw_count > 0) draw_count--; memcpy(raw2, raw, src_w * src_h); if (depth == 24) { unsigned int *p = ximgdata; for (i = src_w * src_h; i >= 0; i--) *p++ = palette[(int) *data++]; } else { unsigned short *p = ximgdata; // 64 bit safe? for (i = src_w * src_h; i >= 0; i--) *p++ = palette[(int) *data++]; } XPutImage(display, window, gc, ximage, draw_offset, 0, 0, 0, rem, src_h); //XSync(display, False); XSetForeground(display, gc, 0); //XSync(display, False); if (rem < dst_w) { // fprintf(stderr, "%u: %p %u %u %u %u %u %u\n",__LINE__, // display, window, gc, // rem, 0, dst_w, src_h); XFillRectangle(display, window, gc, rem, 0, dst_w, src_h); //XSync(display, False); } if ((v = dst_h - src_h) <= 0) return; XSetForeground(display, gc, 0); //XSync(display, False); // fprintf(stderr, "%u: %p %u %u %u %u %u %u\n",__LINE__, //display, window, gc,0, src_h, dst_w, dst_h); XFillRectangle(display, window, gc, 0, src_h, dst_w, dst_h); //XSync(display, False); XSetForeground(display, gc, ~0); //XSync(display, False); field = (draw_row >= par->count[0]); if (par->start[field] < 0) { xti.nchars = snprintf(buf, 255, "Row %d Line ?", draw_row); line = -1; } else if (field == 0) { line = draw_row + par->start[0]; xti.nchars = snprintf(buf, 255, "Row %d Line %d", draw_row, line); } else { line = draw_row - par->count[0] + par->start[1]; xti.nchars = snprintf(buf, 255, "Row %d Line %d", draw_row, line); } for (i = 0; i < slines; i++) if (sliced[i].line == (unsigned int) line) break; if (i < slines) { int svc_idx=0; while (_vbi_service_table[svc_idx].id !=0 && _vbi_service_table[svc_idx].id != sliced[i].id) svc_idx++; if (_vbi_service_table[svc_idx].id == sliced[i].id) { struct _vbi_service_par service; service = _vbi_service_table[svc_idx]; xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars, " %s (%x) +%dns", service.label, service.id, service.offset ); if (service.id & VBI_SLICED_TELETEXT_B) { char *text = decode_ttx(sliced[i].data, sliced[i].line); xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars, ": %s", text); free(text); } else if (service.id & VBI_SLICED_VPS) { char *text = decode_vps(sliced[i].data); xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars, ": %s", text); free(text); } } else { xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars, " %s (%d)", vbi_sliced_name(sliced[i].id) ?: "???", sliced[i].id); } } else { int s = 0, sd = 0; data = raw + draw_row * src_w; for (i = 0; i < src_w; i++) s += data[i]; s /= src_w; for (i = 0; i < src_w; i++) sd += abs(data[i] - s); sd /= src_w; xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars, (sd < 5) ? " Blank" : " Unknown signal"); xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars, " (%d)", sd); } /* XSetForeground(display, gc, 0x00FFFF00); XFillRectangle(display, window, gc, 0xc0-draw_offset, src_h, 1, dst_h); XFillRectangle(display, window, gc, 0x19b-draw_offset, src_h, 1, dst_h); */ /* 50% grey */ XSetForeground(display, gc, 0xAAAAAAAA); //XSync(display, False); x=draw_offset; while (x 0) draw_row--; goto redraw; case XK_Down: if (draw_row < (src_h - 1)) draw_row++; goto redraw; case XK_Left: if (draw_offset > 0) draw_offset -= 10; goto redraw; case XK_Right: if (draw_offset < (src_w - 10)) draw_offset += 10; goto redraw; } break; } case ConfigureNotify: dst_w = event.xconfigurerequest.width; dst_h = event.xconfigurerequest.height; redraw: if (draw_count == 0) { draw_count = 1; draw(raw2); } break; case MotionNotify: cur_x = event.xmotion.x; cur_y = event.xmotion.y; // printf("Got MotionNotify: (%d, %d)\n", event.xmotion.x, event.xmotion.y); break; case ClientMessage: exit(EXIT_SUCCESS); } } } static void init_window(int ac, char **av, const char *dev_name) { char buf[256]; Atom delete_window_atom; XWindowAttributes wa; int i; ac = ac; av = av; if (!(display = XOpenDisplay(NULL))) { fprintf(stderr, "No display\n"); exit(EXIT_FAILURE); } screen = DefaultScreen(display); cmap = DefaultColormap(display, screen); window = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, // x, y dst_w = 768, dst_h = src_h + 110, // w, h 2, // borderwidth 0xffffffff, // fgd 0x00000000); // bgd if (!window) { fprintf(stderr, "No window\n"); exit(EXIT_FAILURE); } XGetWindowAttributes(display, window, &wa); depth = wa.depth; if (depth != 15 && depth != 16 && depth != 24) { fprintf(stderr, "Sorry, cannot run at colour depth %d\n", depth); exit(EXIT_FAILURE); } for (i = 0; i < 256; i++) { switch (depth) { case 15: palette[i] = ((i & 0xF8) << 7) + ((i & 0xF8) << 2) + ((i & 0xF8) >> 3); break; case 16: palette[i] = ((i & 0xF8) << 8) + ((i & 0xFC) << 3) + ((i & 0xF8) >> 3); break; case 24: palette[i] = (i << 16) + (i << 8) + i; break; } } if (depth == 24) { if (!(ximgdata = malloc(src_w * src_h * 4))) { fprintf(stderr, "Virtual memory exhausted\n"); exit(EXIT_FAILURE); } } else { if (!(ximgdata = malloc(src_w * src_h * 2))) { fprintf(stderr, "Virtual memory exhausted\n"); exit(EXIT_FAILURE); } } if (!(raw1 = malloc(src_w * src_h))) { fprintf(stderr, "Virtual memory exhausted\n"); exit(EXIT_FAILURE); } if (!(raw2 = malloc(src_w * src_h))) { fprintf(stderr, "Virtual memory exhausted\n"); exit(EXIT_FAILURE); } ximage = XCreateImage(display, DefaultVisual(display, screen), DefaultDepth(display, screen), ZPixmap, 0, (char *) ximgdata, src_w, src_h, 8, 0); if (!ximage) { fprintf(stderr, "No ximage\n"); exit(EXIT_FAILURE); } delete_window_atom = XInternAtom(display, "WM_DELETE_WINDOW", False); XSelectInput(display, window, PointerMotionMask | KeyPressMask | ExposureMask | StructureNotifyMask); XSetWMProtocols(display, window, &delete_window_atom, 1); snprintf(buf, sizeof(buf) - 1, "%s - [cursor] [g]rab [l]ive", dev_name); XStoreName(display, window, buf); gc = XCreateGC(display, window, 0, NULL); XMapWindow(display, window); XSync(display, False); } static void mainloop(void) { double timestamp; struct timeval tv; tv.tv_sec = 2; tv.tv_usec = 0; assert((sliced = malloc(sizeof(vbi_sliced) * src_h))); for (quit = FALSE; !quit;) { int r; r = vbi_capture_read(cap, raw1, sliced, &slines, ×tamp, &tv); switch (r) { case -1: fprintf(stderr, "VBI read error: %d, %s%s\n", errno, strerror(errno), ignore_error ? " (ignored)" : ""); if (ignore_error) continue; else exit(EXIT_FAILURE); case 0: fprintf(stderr, "VBI read timeout%s\n", ignore_error ? " (ignored)" : ""); if (ignore_error || (pxc != NULL)) continue; else exit(EXIT_FAILURE); case 1: break; default: assert(!"reached"); } draw(raw1); /* printf("raw: %f; sliced: %d\n", timestamp, slines); */ xevent(); } } static const char short_options[] = "1234cd:enpsv"; #ifdef HAVE_GETOPT_LONG static const struct option long_options[] = { { "desync", no_argument, NULL, 'c' }, { "device", required_argument, NULL, 'd' }, { "ignore-error", no_argument, NULL, 'e' }, { "ntsc", no_argument, NULL, 'n' }, { "pal", no_argument, NULL, 'p' }, { "sim", no_argument, NULL, 's' }, { "v4l", no_argument, NULL, '1' }, { "v4l2-read", no_argument, NULL, '2' }, { "v4l2-mmap", no_argument, NULL, '3' }, { "proxy", no_argument, NULL, '4' }, { "verbose", no_argument, NULL, 'v' }, { 0, 0, 0, 0 } }; #else #define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif int main(int argc, char **argv) { const char *dev_name = "/dev/vbi"; char *errstr; unsigned int services; int scanning = 625; int strict; int verbose = 0; int interface = 0; int c, index; while ((c = getopt_long(argc, argv, short_options, long_options, &index)) != -1) switch (c) { case 0: /* set flag */ break; case '2': /* Preliminary hack for tests. */ vbi_capture_force_read_mode = TRUE; /* fall through */ case '1': case '3': case '4': interface = c - '0'; break; case 'c': desync ^= TRUE; break; case 'd': dev_name = strdup (optarg); break; case 'e': ignore_error ^= TRUE; break; case 'n': scanning = 525; break; case 'p': scanning = 625; break; case 's': do_sim ^= TRUE; break; case 'v': ++verbose; break; default: fprintf(stderr, "Unknown option\n"); exit(EXIT_FAILURE); } services = VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625 | VBI_SLICED_TELETEXT_B | VBI_SLICED_CAPTION_525 | VBI_SLICED_CAPTION_625 | VBI_SLICED_VPS | VBI_SLICED_WSS_625 | VBI_SLICED_WSS_CPR1204; strict = 0; if (do_sim) { cap = vbi_capture_sim_new (scanning, &services, /* interlaced */ FALSE, !desync); assert ((par = vbi_capture_parameters(cap))); } else { do { if ((2 == interface) || (3 == interface)) { cap = vbi_capture_v4l2k_new (dev_name, /* fd */ -1, /* buffers */ 5, &services, strict, &errstr, /* trace */ !!verbose); if (cap) break; fprintf (stderr, "Cannot capture vbi data " "with v4l2k interface:\n%s\n", errstr); free (errstr); cap = vbi_capture_v4l2_new (dev_name, /* buffers */ 5, &services, strict, &errstr, /* trace */ !!verbose); if (cap) break; fprintf (stderr, "Cannot capture vbi data " "with v4l2 interface:\n%s\n", errstr); free (errstr); } if (interface < 2) { cap = vbi_capture_v4l_new (dev_name, scanning, &services, strict, &errstr, /* trace */ !!verbose); if (cap) break; fprintf (stderr, "Cannot capture vbi data " "with v4l interface:\n%s\n", errstr); free (errstr); } if (interface == 4) { pxc = vbi_proxy_client_create(dev_name, "capture", 0, &errstr, !!verbose); if (pxc != NULL) { /* strip non-raw services, else request for raw is masked out */ unsigned int sv = services & (VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625); cap = vbi_capture_proxy_new(pxc, 5, 0, &sv, strict, &errstr ); if (cap) break; fprintf (stderr, "Cannot capture vbi data " "through proxy:\n%s\n", errstr); } fprintf (stderr, "Cannot initialize proxy\n%s\n", errstr); } /* BSD interface */ if (1) { cap = vbi_capture_bktr_new (dev_name, scanning, &services, strict, &errstr, /* trace */ !!verbose); if (cap) break; fprintf (stderr, "Cannot capture vbi data " "with bktr interface:\n%s\n", errstr); free (errstr); } exit(EXIT_FAILURE); } while (0); assert ((par = vbi_capture_parameters(cap))); } if (verbose > 1) { vbi_capture_set_log_fp (cap, stderr); } assert (par->sampling_format == VBI_PIXFMT_YUV420); src_w = par->bytes_per_line / 1; src_h = par->count[0] + par->count[1]; init_window(argc, argv, dev_name); mainloop(); if (!do_sim) vbi_capture_delete(cap); exit(EXIT_SUCCESS); } #else /* X_DISPLAY_MISSING */ int main(int argc, char **argv) { printf("Could not find X11 or has been disabled at configuration time\n"); exit(EXIT_FAILURE); } #endif zvbi-0.2.44/test/proxy-test.c000066400000000000000000001024141476363111200160420ustar00rootroot00000000000000/* * VBI proxy test client * * Copyright (C) 2003, 2004 Tom Zoerner * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * * Description: * * This is a small demo application for the VBI proxy and libzvbi. * It will read VBI data from the device given on the command line * and dump requested services' data to standard output. See below * for a list of possible options. * * $Log: not supported by cvs2svn $ * Revision 1.20 2008/03/01 07:36:32 mschimek * Line feed cosmetics. * * Revision 1.19 2007/11/27 18:26:48 mschimek * Updated the FSF address in the copyright notice. * * Revision 1.18 2007/11/03 21:15:38 tomzo * Bugfix setup of raw capture handling * * Revision 1.17 2006/02/10 06:25:38 mschimek * *** empty log message *** * * Revision 1.16 2004/12/31 06:05:20 mschimek * *** empty log message *** * * Revision 1.15 2004/11/07 10:52:47 mschimek * *** empty log message *** * * Revision 1.4 2004/11/07 08:18:53 michael * *** empty log message *** * * Revision 1.14 2004/11/03 17:07:40 mschimek * *** empty log message *** * * Revision 1.3 2004/10/29 01:49:04 michael * *** empty log message *** * * Revision 1.13 2004/10/25 16:56:30 mschimek * *** empty log message *** * * Revision 1.12 2004/10/24 18:20:00 tomzo * Added test support for norm change handling * * Revision 1.7 2003/06/07 09:43:23 tomzo * Added test for proxy with select() and zero timeout (#if 0'ed) * * Revision 1.6 2003/06/01 19:36:42 tomzo * Added tests for TV channel switching * - added new command line options -channel, -freq, -chnprio * - use new func vbi_capture_channel_change() * * Revision 1.5 2003/05/24 12:19:57 tomzo * - added dynamic service switch to test add_service() interface: new function * read_service_string() reads service requests from stdin * - added new service closed caption * * Revision 1.4 2003/05/10 13:31:23 tomzo * - bugfix main loop: check for 0 result from vbi_capture_pull_sliced() * and for NULL pointer for sliced buffer * - added new "-debug 0..2" option: old "-trace" could only set level 1 * - split off argv parsing from main function * * Revision 1.3 2003/05/03 12:07:48 tomzo * - use vbi_capture_pull_sliced() instead of vbi_capture_read_sliced() * - fixed copyright headers, added description to file headers * */ static const char rcsid [] = "$Id: proxy-test.c,v 1.21 2008-07-26 06:22:19 mschimek Exp $"; #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #ifdef _WIN32 #define __STDC_WANT_LIB_EXT1__ 1 #endif #include #include #include #include #include #include #include #define USE_LIBZVBI #include "src/libzvbi.h" #ifdef ENABLE_V4L #include "src/videodev.h" #endif #define DEVICE_PATH "/dev/vbi0" #define BUFFER_COUNT 5 typedef enum { TEST_API_V4L, TEST_API_V4L2, TEST_API_PROXY, } PROXY_TEST_API; typedef enum { TEST_SCANNING_BOTH = 0, TEST_SCANNING_625 = 625, TEST_SCANNING_525 = 525, } PROXY_TEST_SCANNING; static char * p_dev_name = DEVICE_PATH; static PROXY_TEST_API opt_api = TEST_API_PROXY; static PROXY_TEST_SCANNING opt_scanning = TEST_SCANNING_BOTH; static unsigned int opt_services; static int opt_strict; static int opt_debug_level; static int opt_channel; static int opt_frequency; static int opt_chnprio; static int opt_subprio; static int update_services; #define ALL_SERVICES_625 ( VBI_SLICED_TELETEXT_B | \ VBI_SLICED_VPS | \ VBI_SLICED_CAPTION_625 | \ VBI_SLICED_WSS_625 | \ VBI_SLICED_VBI_625 ) #define ALL_SERVICES_525 ( VBI_SLICED_CAPTION_525 | \ VBI_SLICED_2xCAPTION_525 | \ VBI_SLICED_TELETEXT_BD_525 | \ VBI_SLICED_VBI_525 ) /* --------------------------------------------------------------------------- ** Switch channel and frequency (Video 4 Linux #1 API) */ static vbi_bool SwitchTvChannel( vbi_proxy_client * vpc, int channel, int freq ) { #ifdef ENABLE_V4L struct video_channel vchan; long lfreq; vbi_bool result; result = TRUE; // get current config of the selected chanel if (channel != -1) { result = FALSE; memset(&vchan, 0, sizeof(vchan)); vchan.channel = channel; if (opt_scanning == TEST_SCANNING_625) vchan.norm = VIDEO_MODE_PAL; else if (opt_scanning == TEST_SCANNING_525) vchan.norm = VIDEO_MODE_NTSC; if (vbi_proxy_client_device_ioctl(vpc, VIDIOCGCHAN, &vchan) == 0) { vchan.channel = channel; if (vbi_proxy_client_device_ioctl(vpc, VIDIOCSCHAN, &vchan) == 0) { result = TRUE; } else perror("ioctl VIDIOCSCHAN"); } else perror("ioctl VIDIOCGCHAN"); } if (freq != -1) { result = FALSE; if ( (channel == -1) || ((vchan.type & VIDEO_TYPE_TV) && (vchan.flags & VIDEO_VC_TUNER)) ) { lfreq = freq; if (vbi_proxy_client_device_ioctl(vpc, VIDIOCSFREQ, &lfreq) == 0) { result = TRUE; } else perror("ioctl VIDIOCSFREQ"); } else fprintf(stderr, "cannot tune frequency: channel has no tuner\n"); } return result; #else return FALSE; #endif } /* ---------------------------------------------------------------------------- ** Callback for proxy events */ static void ProxyEventCallback( void * p_client_data, VBI_PROXY_EV_TYPE ev_mask ) { vbi_proxy_client * pProxyClient; VBI_PROXY_CHN_FLAGS flags; if (p_client_data != NULL) { pProxyClient = *(vbi_proxy_client **) p_client_data; if (pProxyClient != NULL) { if (ev_mask & VBI_PROXY_EV_CHN_RECLAIMED) { fprintf(stderr, "ProxyEventCallback: token was reclaimed\n"); vbi_proxy_client_channel_notify(pProxyClient, VBI_PROXY_CHN_TOKEN, 0); } else if (ev_mask & VBI_PROXY_EV_CHN_GRANTED) { fprintf(stderr, "ProxyEventCallback: token granted\n"); if ((opt_channel != -1) || (opt_frequency != -1)) { if (SwitchTvChannel(pProxyClient, opt_channel, opt_frequency)) flags = VBI_PROXY_CHN_TOKEN | VBI_PROXY_CHN_FLUSH; else flags = VBI_PROXY_CHN_RELEASE | VBI_PROXY_CHN_FAIL | VBI_PROXY_CHN_FLUSH; if (opt_scanning != TEST_SCANNING_BOTH) flags |= VBI_PROXY_CHN_NORM; } else flags = VBI_PROXY_CHN_RELEASE; vbi_proxy_client_channel_notify(pProxyClient, flags, opt_scanning); } if (ev_mask & VBI_PROXY_EV_NORM_CHANGED) { fprintf(stderr, "ProxyEventCallback: TV norm changed\n"); update_services = TRUE; } } } } /* ---------------------------------------------------------------------------- ** Resolve parity on an array in-place ** - errors are ignored, the character just replaced by a blank ** - non-printable characters are replaced by blanks */ static const signed char parityTab[256] = { //0x80, 0x01, 0x02, 0x83, 0x04, 0x85, 0x86, 0x07, // non-printable //0x08, 0x89, 0x8a, 0x0b, 0x8c, 0x0d, 0x0e, 0x8f, // non-printable //0x10, 0x91, 0x92, 0x13, 0x94, 0x15, 0x16, 0x97, // non-printable //0x98, 0x19, 0x1a, 0x9b, 0x1c, 0x9d, 0x9e, 0x1f, // non-printable 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xa1, 0xa2, 0x23, 0xa4, 0x25, 0x26, 0xa7, 0xa8, 0x29, 0x2a, 0xab, 0x2c, 0xad, 0xae, 0x2f, 0xb0, 0x31, 0x32, 0xb3, 0x34, 0xb5, 0xb6, 0x37, 0x38, 0xb9, 0xba, 0x3b, 0xbc, 0x3d, 0x3e, 0xbf, 0x40, 0xc1, 0xc2, 0x43, 0xc4, 0x45, 0x46, 0xc7, 0xc8, 0x49, 0x4a, 0xcb, 0x4c, 0xcd, 0xce, 0x4f, 0xd0, 0x51, 0x52, 0xd3, 0x54, 0xd5, 0xd6, 0x57, 0x58, 0xd9, 0xda, 0x5b, 0xdc, 0x5d, 0x5e, 0xdf, 0xe0, 0x61, 0x62, 0xe3, 0x64, 0xe5, 0xe6, 0x67, 0x68, 0xe9, 0xea, 0x6b, 0xec, 0x6d, 0x6e, 0xef, 0x70, 0xf1, 0xf2, 0x73, 0xf4, 0x75, 0x76, 0xf7, 0xf8, 0x79, 0x7a, 0xfb, 0x7c, 0xfd, 0xfe, 0x20, // 0x7f, //0x00, 0x81, 0x82, 0x03, 0x84, 0x05, 0x06, 0x87, // non-printable //0x88, 0x09, 0x0a, 0x8b, 0x0c, 0x8d, 0x8e, 0x0f, // non-printable //0x90, 0x11, 0x12, 0x93, 0x14, 0x95, 0x96, 0x17, // non-printable //0x18, 0x99, 0x9a, 0x1b, 0x9c, 0x1d, 0x1e, 0x9f, // non-printable 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xa0, 0x21, 0x22, 0xa3, 0x24, 0xa5, 0xa6, 0x27, 0x28, 0xa9, 0xaa, 0x2b, 0xac, 0x2d, 0x2e, 0xaf, 0x30, 0xb1, 0xb2, 0x33, 0xb4, 0x35, 0x36, 0xb7, 0xb8, 0x39, 0x3a, 0xbb, 0x3c, 0xbd, 0xbe, 0x3f, 0xc0, 0x41, 0x42, 0xc3, 0x44, 0xc5, 0xc6, 0x47, 0x48, 0xc9, 0xca, 0x4b, 0xcc, 0x4d, 0x4e, 0xcf, 0x50, 0xd1, 0xd2, 0x53, 0xd4, 0x55, 0x56, 0xd7, 0xd8, 0x59, 0x5a, 0xdb, 0x5c, 0xdd, 0xde, 0x5f, 0x60, 0xe1, 0xe2, 0x63, 0xe4, 0x65, 0x66, 0xe7, 0xe8, 0x69, 0x6a, 0xeb, 0x6c, 0xed, 0xee, 0x6f, 0xf0, 0x71, 0x72, 0xf3, 0x74, 0xf5, 0xf6, 0x77, 0x78, 0xf9, 0xfa, 0x7b, 0xfc, 0x7d, 0x7e, 0xff, }; static int UnHamParityArray( const unsigned char *pin, char *pout, int byteCount ) { int errCount; signed char c1; errCount = 0; for (; byteCount > 0; byteCount--) { c1 = (char)parityTab[*(pin++)]; if (c1 > 0) { *(pout++) = c1; } else { *(pout++) = 0xA0; /* Latin-1 space character */ errCount += 1; } } return errCount; } static const unsigned char unhamTab[256] = { 0x01, 0xff, 0x01, 0x01, 0xff, 0x00, 0x01, 0xff, 0xff, 0x02, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x07, 0xff, 0x00, 0x01, 0xff, 0x00, 0x00, 0xff, 0x00, 0x06, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x03, 0xff, 0xff, 0x0c, 0x01, 0xff, 0x04, 0xff, 0xff, 0x07, 0x06, 0xff, 0xff, 0x07, 0xff, 0x07, 0x07, 0x07, 0x06, 0xff, 0xff, 0x05, 0xff, 0x00, 0x0d, 0xff, 0x06, 0x06, 0x06, 0xff, 0x06, 0xff, 0xff, 0x07, 0xff, 0x02, 0x01, 0xff, 0x04, 0xff, 0xff, 0x09, 0x02, 0x02, 0xff, 0x02, 0xff, 0x02, 0x03, 0xff, 0x08, 0xff, 0xff, 0x05, 0xff, 0x00, 0x03, 0xff, 0xff, 0x02, 0x03, 0xff, 0x03, 0xff, 0x03, 0x03, 0x04, 0xff, 0xff, 0x05, 0x04, 0x04, 0x04, 0xff, 0xff, 0x02, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x07, 0xff, 0x05, 0x05, 0x05, 0x04, 0xff, 0xff, 0x05, 0x06, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x03, 0xff, 0xff, 0x0c, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x09, 0x0a, 0xff, 0xff, 0x0b, 0x0a, 0x0a, 0x0a, 0xff, 0x08, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x0d, 0xff, 0xff, 0x0b, 0x0b, 0x0b, 0x0a, 0xff, 0xff, 0x0b, 0x0c, 0x0c, 0xff, 0x0c, 0xff, 0x0c, 0x0d, 0xff, 0xff, 0x0c, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x07, 0xff, 0x0c, 0x0d, 0xff, 0x0d, 0xff, 0x0d, 0x0d, 0x06, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x0d, 0xff, 0x08, 0xff, 0xff, 0x09, 0xff, 0x09, 0x09, 0x09, 0xff, 0x02, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x09, 0x08, 0x08, 0x08, 0xff, 0x08, 0xff, 0xff, 0x09, 0x08, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x03, 0xff, 0xff, 0x0c, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x09, 0x0f, 0xff, 0x0f, 0x0f, 0xff, 0x0e, 0x0f, 0xff, 0x08, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x0d, 0xff, 0xff, 0x0e, 0x0f, 0xff, 0x0e, 0x0e, 0xff, 0x0e }; #define UnHam84Byte(P,V) (( *(V) = ((int)unhamTab[*((const unsigned char *)(P))] | ((int)unhamTab[*(((const unsigned char *)(P))+1)] << 4)) ) >= 0 ) /* --------------------------------------------------------------------------- ** Decode a teletext data line */ static void PrintTeletextData( const unsigned char * data, int line, int id ) { char tmparr[46]; int tmp1, tmp2, tmp3; uint mag, pkgno, pageNo, sub; mag = 0xF; pkgno = 0xFF; pageNo = 0xFFF; sub = 0xFFFF; if (UnHam84Byte(data, &tmp1)) { pkgno = (tmp1 >> 3) & 0x1f; mag = tmp1 & 7; if (mag == 0) mag = 8; if (UnHam84Byte(data + 2, &tmp1) && UnHam84Byte(data + 4, &tmp2) && UnHam84Byte(data + 6, &tmp3)) { /* it's a page header: decode page number and sub-page code */ pageNo = tmp1 | ((uint)mag << 8); sub = (tmp2 | (tmp3 << 8)) & 0x3f7f; } } if (pkgno != 0) { UnHamParityArray(data+2, tmparr, 40); tmparr[40] = 0; printf("line %3d id=%d pkg %X.%03X: '%s'\n", line, id, mag, pkgno, tmparr); } else { UnHamParityArray(data+2+8, tmparr, 40-8); tmparr[40-8] = 0; printf("line %3d id=%d page %03X.%04X: '%s'\n", line, id, pageNo, sub, tmparr); } } /* --------------------------------------------------------------------------- ** Decode a VPS data line ** - bit fields are defined in "VPS Richtlinie 8R2" from August 1995 ** - called by the VBI decoder for every received VPS line */ static void PrintVpsData( const unsigned char * data ) { uint mday, month, hour, minute; uint cni; #define VPSOFF -3 cni = ((data[VPSOFF+13] & 0x3) << 10) | ((data[VPSOFF+14] & 0xc0) << 2) | ((data[VPSOFF+11] & 0xc0)) | (data[VPSOFF+14] & 0x3f); if ((cni != 0) && (cni != 0xfff)) { if (cni == 0xDC3) { /* special case: "ARD/ZDF Gemeinsames Vormittagsprogramm" */ cni = (data[VPSOFF+5] & 0x20) ? 0xDC1 : 0xDC2; } /* decode VPS PIL */ mday = (data[VPSOFF+11] & 0x3e) >> 1; month = ((data[VPSOFF+12] & 0xe0) >> 5) | ((data[VPSOFF+11] & 1) << 3); hour = (data[VPSOFF+12] & 0x1f); minute = (data[VPSOFF+13] >> 2); printf("VPS %d.%d. %02d:%02d CNI 0x%04X\n", mday, month, hour, minute, cni); } } /* --------------------------------------------------------------------------- ** Check stdin for services change requests ** - syntax: ["+"|"-"|"="]keyword, e.g. "+vps-ttx" or "=wss" */ static unsigned int read_service_string( void ) { unsigned int services; unsigned int tmp_services; struct timeval timeout; vbi_bool substract; fd_set rd; char buf[100]; char *p_inp; int ret; services = opt_services; timeout.tv_sec = 0; timeout.tv_usec = 0; FD_ZERO(&rd); FD_SET(0, &rd); ret = select(1, &rd, NULL, NULL, &timeout); if (ret == 1) { ret = read(0, buf, sizeof(buf)); if (ret > 0) { p_inp = buf; while (*p_inp != 0) { while (*p_inp == ' ') p_inp += 1; substract = FALSE; if (*p_inp == '=') { services = 0; p_inp += 1; } else if (*p_inp == '-') { substract = TRUE; p_inp += 1; } else if (*p_inp == '+') p_inp += 1; if ( (strncasecmp(p_inp, "ttx", 3) == 0) || (strncasecmp(p_inp, "teletext", 8) == 0) ) { tmp_services = VBI_SLICED_TELETEXT_B | VBI_SLICED_TELETEXT_BD_525; } else if (strncasecmp(p_inp, "vps", 3) == 0) { tmp_services = VBI_SLICED_VPS; } else if (strncasecmp(p_inp, "wss", 3) == 0) { tmp_services = VBI_SLICED_WSS_625 | VBI_SLICED_WSS_CPR1204; } else if ( (strncasecmp(p_inp, "cc", 2) == 0) || (strncasecmp(p_inp, "caption", 7) == 0) ) { tmp_services = VBI_SLICED_CAPTION_625 | VBI_SLICED_CAPTION_525; } else if (strncasecmp(p_inp, "raw", 3) == 0) { tmp_services = VBI_SLICED_VBI_625 | VBI_SLICED_VBI_525; } else tmp_services = 0; if (substract == FALSE) services |= tmp_services; else services &= ~ tmp_services; while ((*p_inp != 0) && (*p_inp != '+') && (*p_inp != '-')) p_inp += 1; } } else if ((ret < 0) && (errno != EINTR) && (errno != EAGAIN)) { fprintf(stderr, "read_service_string: read: %d (%s)\n", errno, strerror(errno)); } } else if ((ret < 0) && (errno != EINTR) && (errno != EAGAIN)) { fprintf(stderr, "read_service_string: select: %d (%s)\n", errno, strerror(errno)); } return services; } /* --------------------------------------------------------------------------- ** Print usage and exit */ static void usage_exit( const char *argv0, const char *argvn, const char * reason ) { fprintf(stderr, "%s: %s: %s\n" "Usage: %s [ Options ] service ...\n" "Supported services : ttx | vps | wss | cc | raw\n" "Supported options:\n" " -dev : device path\n" " -api : v4l API: proxy|v4l2|v4l\n" " -strict : service strictness level: 0..2\n" " -channel : switch video input channel\n" " -freq : switch TV tuner frequency\n" " -chnprio <1..3> : channel switch priority\n" " -subprio <0..4> : background scheduling priority\n" " -debug : enable debug output: 1=warnings, 2=all\n" " -help : this message\n" "You can also type service requests to stdin at runtime:\n" "Format: [\"+\"|\"-\"|\"=\"], e.g. \"+vps -ttx\" or \"=wss\"\n", argv0, reason, argvn, argv0); exit(1); } /* --------------------------------------------------------------------------- ** Parse numeric value in command line options */ static vbi_bool parse_argv_numeric( char * p_number, int * p_value ) { char * p_num_end; if (*p_number != 0) { *p_value = strtol(p_number, &p_num_end, 0); return (*p_num_end == 0); } else return FALSE; } /* --------------------------------------------------------------------------- ** Parse command line options */ static void parse_argv( int argc, char * argv[] ) { int arg_val; int arg_idx = 1; int have_service = 0; opt_debug_level = 0; opt_services = 0; opt_strict = 0; opt_channel = -1; opt_frequency = -1; opt_chnprio = VBI_CHN_PRIO_INTERACTIVE; opt_subprio = 0; while (arg_idx < argc) { if ( (strcasecmp(argv[arg_idx], "ttx") == 0) || (strcasecmp(argv[arg_idx], "teletext") == 0) ) { opt_services |= VBI_SLICED_TELETEXT_B | VBI_SLICED_TELETEXT_BD_525; have_service = 1; arg_idx += 1; } else if (strcasecmp(argv[arg_idx], "vps") == 0) { opt_services |= VBI_SLICED_VPS; have_service = 1; arg_idx += 1; } else if (strcasecmp(argv[arg_idx], "wss") == 0) { opt_services |= VBI_SLICED_WSS_625 | VBI_SLICED_WSS_CPR1204; have_service = 1; arg_idx += 1; } else if ( (strcasecmp(argv[arg_idx], "cc") == 0) || (strcasecmp(argv[arg_idx], "caption") == 0) ) { opt_services |= VBI_SLICED_CAPTION_625 | VBI_SLICED_CAPTION_525; have_service = 1; arg_idx += 1; } else if (strcasecmp(argv[arg_idx], "raw") == 0) { opt_services |= VBI_SLICED_VBI_625 | VBI_SLICED_VBI_525; have_service = 1; arg_idx += 1; } else if (strcasecmp(argv[arg_idx], "null") == 0) { have_service = 1; arg_idx += 1; } else if (strcasecmp(argv[arg_idx], "-dev") == 0) { if (arg_idx + 1 < argc) { p_dev_name = argv[arg_idx + 1]; if (access(p_dev_name, R_OK | W_OK) == -1) usage_exit(argv[0], argv[arg_idx +1], "failed to access device"); arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing mode keyword after"); } else if (strcasecmp(argv[arg_idx], "-api") == 0) { if (arg_idx + 1 < argc) { if (strcasecmp(argv[arg_idx + 1], "proxy") == 0) opt_api = TEST_API_PROXY; #ifdef USE_LIBZVBI else if ( (strcasecmp(argv[arg_idx + 1], "v4l") == 0) || (strcasecmp(argv[arg_idx + 1], "v4l1") == 0) ) opt_api = TEST_API_V4L; else if (strcasecmp(argv[arg_idx + 1], "v4l2") == 0) opt_api = TEST_API_V4L2; #endif else usage_exit(argv[0], argv[arg_idx +1], "unknown API keyword"); arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing mode keyword after"); } else if (strcasecmp(argv[arg_idx], "-norm") == 0) { if (arg_idx + 1 < argc) { if ( (strcasecmp("PAL", argv[arg_idx + 1]) == 0) || (strcasecmp("SECAM", argv[arg_idx + 1]) == 0) ) opt_scanning = TEST_SCANNING_625; else if (strcasecmp("NTSC", argv[arg_idx + 1]) == 0) opt_scanning = TEST_SCANNING_525; else usage_exit(argv[0], argv[arg_idx], "missing mode keyword after"); arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing mode keyword after"); } else if (strcasecmp(argv[arg_idx], "-trace") == 0) { opt_debug_level = 1; arg_idx += 1; } else if (strcasecmp(argv[arg_idx], "-debug") == 0) { if ((arg_idx + 1 < argc) && parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_debug_level = arg_val; arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing debug level after"); } else if (strcasecmp(argv[arg_idx], "-strict") == 0) { if ((arg_idx + 1 < argc) && parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_strict = arg_val; arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing strict level after"); } else if (strcasecmp(argv[arg_idx], "-channel") == 0) { if ((arg_idx + 1 < argc) && parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_channel = arg_val; arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing channel index after"); } else if (strcasecmp(argv[arg_idx], "-freq") == 0) { if ((arg_idx + 1 < argc) && parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_frequency = arg_val; arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing frequency value after"); } else if (strcasecmp(argv[arg_idx], "-chnprio") == 0) { if ((arg_idx + 1 < argc) && parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_chnprio = arg_val; arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing priority level after"); } else if (strcasecmp(argv[arg_idx], "-subprio") == 0) { if ((arg_idx + 1 < argc) && parse_argv_numeric(argv[arg_idx + 1], &arg_val)) { opt_subprio = arg_val; arg_idx += 2; } else usage_exit(argv[0], argv[arg_idx], "missing priority level after"); } else if (strcasecmp(argv[arg_idx], "-help") == 0) { usage_exit(argv[0], "", "the following options are available"); } else usage_exit(argv[0], argv[arg_idx], "unknown option or argument"); } if (have_service == 0) { usage_exit(argv[0], "no service given", "Must specify at least one service"); } if (opt_scanning == TEST_SCANNING_625) opt_services &= ALL_SERVICES_625; else if (opt_scanning == TEST_SCANNING_525) opt_services &= ALL_SERVICES_525; } /* ---------------------------------------------------------------------------- ** Main entry point */ int main ( int argc, char ** argv ) { vbi_proxy_client * pProxyClient; vbi_capture * pVbiCapt; vbi_sliced * pVbiData; vbi_capture_buffer * pVbiBuf; vbi_raw_decoder raw_dec; char * pErr; struct timeval timeout; unsigned int new_services; unsigned int cur_services; unsigned int * p_services; uint lineCount; uint lastLineCount; uint line; int res; parse_argv(argc, argv); fcntl(0, F_SETFL, O_NONBLOCK); if ((opt_services != 0) && (opt_scanning == 0)) { cur_services = opt_services; p_services = &cur_services; } else { cur_services = 0; p_services = NULL; } pProxyClient = NULL; pVbiCapt = NULL; #ifdef USE_LIBZVBI if (opt_api == TEST_API_V4L2) pVbiCapt = vbi_capture_v4l2_new(p_dev_name, BUFFER_COUNT, p_services, opt_strict, &pErr, opt_debug_level); if (opt_api == TEST_API_V4L) pVbiCapt = vbi_capture_v4l_new(p_dev_name, 0, p_services, opt_strict, &pErr, opt_debug_level); #endif if (opt_api == TEST_API_PROXY) { pProxyClient = vbi_proxy_client_create(p_dev_name, "proxy-test", 0, &pErr, opt_debug_level); if (pProxyClient != NULL) { pVbiCapt = vbi_capture_proxy_new(pProxyClient, BUFFER_COUNT, 0, p_services, opt_strict, &pErr ); } vbi_proxy_client_set_callback(pProxyClient, ProxyEventCallback, &pProxyClient); } if (pVbiCapt != NULL) { lastLineCount = -1; /* switch to the requested channel */ if ( (opt_channel != -1) || (opt_frequency != -1) || (opt_chnprio != VBI_CHN_PRIO_INTERACTIVE) ) { vbi_channel_profile chn_profile; memset(&chn_profile, 0, sizeof(chn_profile)); if ( (opt_chnprio == VBI_CHN_PRIO_BACKGROUND) && ((opt_channel != -1) || (opt_frequency != -1)) ) { chn_profile.is_valid = TRUE; chn_profile.sub_prio = opt_subprio; chn_profile.min_duration = 10; } vbi_proxy_client_channel_request(pProxyClient, opt_chnprio, &chn_profile); if (opt_chnprio != VBI_CHN_PRIO_BACKGROUND) { SwitchTvChannel(pProxyClient, opt_channel, opt_frequency); } } if ((opt_services & (VBI_SLICED_VBI_625 | VBI_SLICED_VBI_525)) != 0) { vbi_raw_decoder * p_dec; /* initialize services for raw capture */ p_dec = vbi_capture_parameters(pVbiCapt); if (p_dec != NULL) { vbi_raw_decoder_init(&raw_dec); raw_dec.scanning = p_dec->scanning; raw_dec.sampling_format = p_dec->sampling_format; raw_dec.sampling_rate = p_dec->sampling_rate; raw_dec.bytes_per_line = p_dec->bytes_per_line; raw_dec.offset = p_dec->offset; raw_dec.start[0] = p_dec->start[0]; raw_dec.start[1] = p_dec->start[1]; raw_dec.count[0] = p_dec->count[0]; raw_dec.count[1] = p_dec->count[1]; raw_dec.interlaced = p_dec->interlaced; raw_dec.synchronous = p_dec->synchronous; vbi_raw_decoder_add_services(&raw_dec, ALL_SERVICES_525|ALL_SERVICES_625, 0); } } update_services = (opt_scanning != 0); while(1) { fd_set rd; int vbi_fd = vbi_capture_fd(pVbiCapt); if (vbi_fd == -1) break; FD_ZERO(&rd); FD_SET(vbi_fd, &rd); FD_SET(0, &rd); select(vbi_fd + 1, &rd, NULL, NULL, NULL); if (FD_ISSET(0, &rd)) { new_services = read_service_string(); if (opt_scanning == TEST_SCANNING_625) new_services &= ALL_SERVICES_625; else if (opt_scanning == TEST_SCANNING_525) new_services &= ALL_SERVICES_525; if (new_services != opt_services) { fprintf(stderr, "switching service from 0x%X to 0x%X...\n", opt_services, new_services); opt_services = new_services; update_services = TRUE; } } if (update_services) { cur_services = vbi_capture_update_services(pVbiCapt, TRUE, TRUE, opt_services, opt_strict, &pErr); if ((cur_services != 0) || (opt_services == 0)) fprintf(stderr, "...got granted services 0x%X.\n", cur_services); else fprintf(stderr, "...failed: %s\n", ((pErr != NULL) ? pErr : "")); lastLineCount = 0; update_services = FALSE; } if (FD_ISSET(vbi_fd, &rd)) { timeout.tv_sec = 0; timeout.tv_usec = 1000; if ((opt_services & (VBI_SLICED_VBI_625 | VBI_SLICED_VBI_525)) == 0) { res = vbi_capture_pull_sliced(pVbiCapt, &pVbiBuf, &timeout); if (res < 0) { fprintf(stderr, "VBI read error: %d (%s)\n", errno, strerror(errno)); break; } else if ((res > 0) && (pVbiBuf != NULL)) { lineCount = ((unsigned int) pVbiBuf->size) / sizeof(vbi_sliced); pVbiData = pVbiBuf->data; if (lastLineCount != lineCount) { fprintf(stderr, "%d lines\n", lineCount); lastLineCount = lineCount; } for (line=0; line < lineCount; line++) { if ((pVbiData[line].id & (VBI_SLICED_TELETEXT_B | VBI_SLICED_TELETEXT_BD_525)) != 0) { PrintTeletextData(pVbiData[line].data, pVbiData[line].line, pVbiData[line].id); } else if (pVbiData[line].id == VBI_SLICED_VPS) { PrintVpsData(pVbiData[line].data); } else if (pVbiData[line].id == VBI_SLICED_WSS_625) { printf("WSS 0x%02X%02X%02X\n", pVbiData[line].data[0], pVbiData[line].data[1], pVbiData[line].data[2]); } } } else fprintf(stderr, "proxy-test: timeout in VBI read\n"); } else { res = vbi_capture_pull_raw(pVbiCapt, &pVbiBuf, &timeout); if ((res < 0) && (errno != EAGAIN)) { fprintf(stderr, "VBI read error: %d (%s)\n", errno, strerror(errno)); break; } else if ((res > 0) && (pVbiBuf != NULL)) { #ifdef USE_LIBZVBI pVbiData = malloc(32 * sizeof(vbi_sliced)); lineCount = vbi_raw_decode(&raw_dec, pVbiBuf->data, pVbiData); if (lastLineCount != lineCount) { fprintf(stderr, "%d lines\n", lineCount); lastLineCount = lineCount; } for (line=0; line < lineCount; line++) { if ((pVbiData[line].id & (VBI_SLICED_TELETEXT_B|VBI_SLICED_TELETEXT_BD_525)) != 0) { PrintTeletextData(pVbiData[line].data, pVbiData[line].line, pVbiData[line].id); } else if (pVbiData[line].id == VBI_SLICED_VPS) { PrintVpsData(pVbiData[line].data); } else if (pVbiData[line].id == VBI_SLICED_WSS_625) { printf("WSS 0x%02X%02X%02X\n", pVbiData[line].data[0], pVbiData[line].data[1], pVbiData[line].data[2]); } } free(pVbiData); #endif /* USE_LIBZVBI */ } else if (opt_debug_level > 0) { fprintf(stderr, "VBI read timeout\n"); } } } } vbi_capture_delete(pVbiCapt); } else { if (pErr != NULL) { fprintf(stderr, "libzvbi error: %s\n", pErr); free(pErr); } else fprintf(stderr, "error starting acquisition\n"); } if (pProxyClient != NULL) { vbi_proxy_client_destroy(pProxyClient); pProxyClient = NULL; } exit(0); return(0); } zvbi-0.2.44/test/sliced.c000066400000000000000000001041621476363111200151510ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2005 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: sliced.c,v 1.20 2013-08-28 14:45:28 mschimek Exp $ */ /* For libzvbi version 0.2.x / 0.3.x. */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif /* Misc. helper functions. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "src/dvb_mux.h" #include "src/dvb_demux.h" #include "src/inout.h" #include "src/io-sim.h" #include "src/raw_decoder.h" #include "src/vbi.h" #include "sliced.h" #if 2 == VBI_VERSION_MINOR # include "src/proxy-msg.h" # include "src/proxy-client.h" # define sp_sample_format sampling_format # define sp_samples_per_line bytes_per_line # define VBI_PIXFMT_Y8 VBI_PIXFMT_YUV420 # define vbi_pixfmt_name(x) "Y8" # define vbi_pixfmt_bytes_per_pixel(x) 1 #elif 3 == VBI_VERSION_MINOR # define sp_sample_format sample_format # define sp_samples_per_line samples_per_line #else # error VBI_VERSION_MINOR == ? #endif #undef _ #define _(x) x /* later */ typedef vbi_bool read_loop_fn (struct stream * st); typedef vbi_bool write_fn (struct stream * st, const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time); struct stream { uint8_t buffer[4096]; uint8_t b64_buffer[4096]; vbi_sliced sliced[64]; vbi_sliced sliced2[64]; uint8_t * raw; const uint8_t * bp; const uint8_t * end; stream_callback_fn * callback; read_loop_fn * loop; write_fn * write_func; vbi_dvb_mux * mx; vbi_dvb_demux * dx; #if 2 == VBI_VERSION_MINOR vbi_proxy_client * proxy; #endif vbi_capture * cap; vbi_raw_decoder * rd; vbi_sampling_par sp; vbi_bool raw_valid; vbi_bool decode_raw; vbi_bool debug; unsigned int sliced2_lines; double sample_time; int64_t stream_time; unsigned int interfaces; unsigned int system; vbi_bool read_not_pull; int fd; vbi_bool close_fd; }; #ifndef HAVE_PROGRAM_INVOCATION_NAME char * program_invocation_name; char * program_invocation_short_name; #endif const char * option_dev_name; unsigned int option_dvb_pid; vbi_bool option_quiet; unsigned int option_log_mask; static vbi_bool have_dev_name; void vprint_error (const char * template, va_list ap) { if (option_quiet) return; fprintf (stderr, "%s: ", program_invocation_short_name); vfprintf (stderr, template, ap); fputc ('\n', stderr); } void error_msg (const char * template, ...) { va_list ap; va_start (ap, template); vprint_error (template, ap); va_end (ap); } void error_exit (const char * template, ...) { va_list ap; va_start (ap, template); vprint_error (template, ap); va_end (ap); exit (EXIT_FAILURE); } void write_error_exit (const char * msg) { if (NULL == msg) msg = strerror (errno); error_exit (_("Write error: %s."), msg); } void read_error_exit (const char * msg) { if (NULL == msg) msg = strerror (errno); error_exit (_("Read error: %s."), msg); } void no_mem_exit (void) { error_exit (_("Out of memory.")); } static void premature_exit (void) { error_exit (_("Premature end of input file.")); } static void bad_format_exit (void) { error_exit (_("Invalid data in input file.")); } void stream_delete (struct stream * st) { if (NULL == st) return; if (st->close_fd) { if (-1 == close (st->fd)) { if (NULL != st->write_func) write_error_exit (/* msg: errno */ NULL); } } vbi_capture_delete (st->cap); #if 3 == VBI_VERSION_MINOR vbi_raw_decoder_delete (st->rd); #endif free (st->raw); CLEAR (*st); free (st); } vbi_bool stream_loop (struct stream * st) { return st->loop (st); } static vbi_bool pes_ts_cb (vbi_dvb_mux * mx, void * user_data, const uint8_t * packet, unsigned int packet_size) { struct stream *st = (struct stream *) user_data; ssize_t actual; mx = mx; /* unused */ assert (packet_size < 66000); actual = write (st->fd, packet, packet_size); if (actual != (ssize_t) packet_size) write_error_exit (/* msg: errno */ NULL); return TRUE; } static vbi_bool write_func_pes_ts (struct stream * st, const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { vbi_bool success; assert (NULL != sliced); assert (n_lines <= 32); assert (stream_time >= 0); raw = raw; /* unused */ sp = sp; sample_time = sample_time; success = vbi_dvb_mux_feed (st->mx, sliced, n_lines, (VBI_SLICED_CAPTION_625 | VBI_SLICED_TELETEXT_B_625 | VBI_SLICED_VPS | VBI_SLICED_WSS_625), /* raw */ NULL, /* sp */ NULL, /* pts */ stream_time); if (!success) { /* Probably. */ error_exit (_("Maximum PES packet size %u bytes " "is too small for this input stream."), vbi_dvb_mux_get_max_pes_packet_size (st->mx)); } return TRUE; } struct service { const char * name; vbi_service_set id; unsigned int n_bytes; }; static const struct service service_map [] = { { "TELETEXT_B", VBI_SLICED_TELETEXT_B, 42 }, { "CAPTION_625", VBI_SLICED_CAPTION_625, 2 }, { "VPS", VBI_SLICED_VPS | VBI_SLICED_VPS_F2, 13 }, { "WSS_625", VBI_SLICED_WSS_625, 2 }, { "WSS_CPR1204", VBI_SLICED_WSS_CPR1204, 3 }, { NULL, 0, 0 }, { NULL, 0, 0 }, { "CAPTION_525", VBI_SLICED_CAPTION_525, 2 }, }; static void st_printf (struct stream * st, const char * templ, ...) { va_list ap; int n; va_start (ap, templ); n = vsnprintf ((char *) st->buffer, sizeof (st->buffer), templ, ap); va_end (ap); if (n < 1 || n >= (int) sizeof (st->buffer)) error_exit (_("Buffer overflow.")); if (n != write (st->fd, st->buffer, n)) write_error_exit (/* msg: errno */ NULL); } static const uint8_t base64 [] = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"); static void encode_base64 (uint8_t * out, const uint8_t * in, unsigned int n_bytes) { unsigned int block; for (; n_bytes >= 3; n_bytes -= 3) { block = in[0] * 65536 + in[1] * 256 + in[2]; in += 3; out[0] = base64[block >> 18]; out[1] = base64[(block >> 12) & 0x3F]; out[2] = base64[(block >> 6) & 0x3F]; out[3] = base64[block & 0x3F]; out += 4; } switch (n_bytes) { case 2: block = in[0] * 256 + in[1]; out[0] = base64[block >> 10]; out[1] = base64[(block >> 4) & 0x3F]; out[2] = base64[(block << 2) & 0x3F]; out[3] = '='; out += 4; break; case 1: block = in[0]; out[0] = base64[block >> 2]; out[1] = base64[(block << 4) & 0x3F]; out[2] = '='; out[3] = '='; out += 4; break; } *out = 0; } static void write_xml_sliced (struct stream * st, const vbi_sliced * sliced, unsigned int n_lines) { const vbi_sliced *sliced_end; sliced_end = sliced + n_lines; assert (N_ELEMENTS (st->b64_buffer) >= (sizeof (sliced->data) + 2) * 4 / 3 + 1); for (; sliced < sliced_end; ++sliced) { unsigned int i; if (VBI_SLICED_VBI_525 == sliced->id || VBI_SLICED_VBI_625 == sliced->id) continue; for (i = 0; i < N_ELEMENTS (service_map); ++i) { if (sliced->id & service_map[i].id) break; } if (i >= N_ELEMENTS (service_map)) error_exit (_("Unknown data service.")); assert (service_map[i].n_bytes <= sizeof (sliced->data)); encode_base64 (st->b64_buffer, sliced->data, service_map[i].n_bytes); if (0 == sliced->line) { st_printf (st, "%s\n", service_map[i].name, st->b64_buffer); } else { st_printf (st, "%s\n", service_map[i].name, sliced->line, st->b64_buffer); } } } static void write_xml_raw (struct stream * st, const uint8_t * raw, const vbi_sampling_par *sp) { const char *format; unsigned int n_samples; unsigned int n_rows; unsigned int row; assert ((N_ELEMENTS (st->b64_buffer) - 1) * 3 / 4 - 2 >= (unsigned int) sp->sp_samples_per_line); format = vbi_pixfmt_name (sp->sp_sample_format); if (NULL == format) error_exit (_("Unknown raw VBI format.")); n_samples = sp->sp_samples_per_line * vbi_pixfmt_bytes_per_pixel (sp->sp_sample_format); n_rows = sp->count[0] + sp->count[1]; if (sp->interlaced) assert (sp->count[0] == sp->count[1]); for (row = 0; row < n_rows; ++row) { unsigned int line; if (sp->interlaced) { line = sp->start[row & 1]; if (line > 0) line += row >> 1; } else if (row < (unsigned int) sp->count[0]) { line = sp->start[0]; if (line > 0) line += row; } else { line = sp->start[1]; if (line > 0) line += row - sp->count[0]; } encode_base64 (st->b64_buffer, raw, n_samples); if (0 == line) { st_printf (st, "%s\n", format, sp->sampling_rate, sp->offset, st->b64_buffer); } else { st_printf (st, "%s\n", format, sp->sampling_rate, sp->offset, line, st->b64_buffer); } raw += sp->bytes_per_line; } } static vbi_bool write_func_xml (struct stream * st, const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { struct timeval tv; double intpart; if (NULL == sliced && NULL == raw) return TRUE; if (NULL != sliced) assert (n_lines <= st->system); if (NULL != raw) assert (NULL != sp); assert (sample_time >= 0); assert (stream_time >= 0); tv.tv_usec = (int)(1e6 * modf (sample_time, &intpart)); tv.tv_sec = (int) intpart; st_printf (st, "\n", (525 == st->system) ? "525_60" : "625_50", (int64_t) tv.tv_sec, (unsigned int) tv.tv_usec, stream_time); if (NULL != sliced) write_xml_sliced (st, sliced, n_lines); if (NULL != raw) write_xml_raw (st, raw, sp); st_printf (st, "\n"); return TRUE; } static vbi_bool write_func_old_sliced (struct stream * st, const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { if (NULL == sliced && NULL == raw) return TRUE; if (NULL != sliced) assert (n_lines <= 254); else n_lines = 0; if (NULL != raw) assert (NULL != sp); stream_time = stream_time; st_printf (st, "%f\n%c", sample_time - st->sample_time, n_lines + (NULL != raw)); while (n_lines > 0) { unsigned int i; for (i = 0; i < N_ELEMENTS (service_map); ++i) { if (sliced->id & service_map[i].id) { int n; st_printf (st, "%c%c%c", /* service number */ i, /* line number low/high */ sliced->line & 0xFF, sliced->line >> 8); n = service_map[i].n_bytes; assert (n > 0 && n <= (int) sizeof (sliced->data)); if (n != write (st->fd, sliced->data, n)) write_error_exit (NULL); } } ++sliced; --n_lines; } if (NULL != raw) { uint8_t *p = st->b64_buffer; int n; #define w8(n) *p++ = n #define w16(n) w8 ((n) & 0xFF); w8 (((n) >> 8) & 0xFF) #define w32(n) w16 ((n) & 0xFFFF); w16 (((n) >> 16) & 0xFFFF) w8 (255); /* service number */ w16 (0); /* line number */ w16 (st->system); w32 (sp->sampling_rate); w16 (sp->sp_samples_per_line); w16 (sp->bytes_per_line); w16 (sp->offset); w16 (sp->start[0]); w16 (sp->start[1]); w16 (sp->count[0]); w16 (sp->count[1]); w8 (sp->interlaced); w8 (sp->synchronous); #undef w8 #undef w16 #undef w32 n = p - st->b64_buffer; assert (n > 0 && n <= (int) sizeof (st->b64_buffer)); if (n != write (st->fd, st->b64_buffer, n)) write_error_exit (NULL); n = (sp->count[0] + sp->count[1]) * sp->bytes_per_line; assert (n > 0 && n <= 625 * 4096); if (n != write (st->fd, raw, n)) write_error_exit (NULL); } st->sample_time = sample_time; return TRUE; } vbi_bool write_stream_sliced (struct stream * st, const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { return st->write_func (st, sliced, n_lines, raw, sp, sample_time, stream_time); } void write_stream_set_data_identifier (struct stream * st, unsigned int data_identifier) { assert (NULL != st->mx); if (!vbi_dvb_mux_set_data_identifier (st->mx, data_identifier)) { error_exit (_("Invalid data identifier 0x%x."), data_identifier); } } void write_stream_set_pes_packet_size (struct stream * st, unsigned int min, unsigned int max) { assert (NULL != st->mx); if (!vbi_dvb_mux_set_pes_packet_size (st->mx, min, max)) no_mem_exit (); } struct stream * write_stream_new (const char * file_name, enum file_format file_format, unsigned int ts_pid, unsigned int system) { struct stream *st; assert (525 == system || 625 == system); st = calloc (1, sizeof (*st)); if (NULL == st) no_mem_exit (); if (NULL == file_name || 0 == strcmp (file_name, "-")) { st->fd = STDOUT_FILENO; if (isatty (STDOUT_FILENO)) { error_exit (_("Output of this program is binary " "data. You should pipe it to another " "tool or redirect to a file.\n")); } } else { st->fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); if (-1 == st->fd) { error_exit (_("Cannot open '%s' for writing: %s."), file_name, strerror (errno)); } st->close_fd = TRUE; } switch (file_format) { case FILE_FORMAT_SLICED: st->write_func = write_func_old_sliced; break; case FILE_FORMAT_XML: st->write_func = write_func_xml; break; case FILE_FORMAT_DVB_PES: st->write_func = write_func_pes_ts; st->mx = vbi_dvb_pes_mux_new (pes_ts_cb, /* user_data */ st); if (NULL == st->mx) no_mem_exit (); break; case FILE_FORMAT_DVB_TS: st->write_func = write_func_pes_ts; st->mx = vbi_dvb_ts_mux_new (ts_pid, pes_ts_cb, /* user_data */ st); if (NULL == st->mx) no_mem_exit (); break; default: error_exit (_("Unknown output file format.")); break; } st->sample_time = 0.0; st->stream_time = 0; st->system = system; return st; } static vbi_bool read_more (struct stream * st) { unsigned int retry; uint8_t *s; uint8_t *e; s = (uint8_t *) st->end; e = st->buffer + sizeof (st->buffer); if (s >= e) s = st->buffer; retry = 100; do { ssize_t actual; int saved_errno; actual = read (st->fd, s, e - s); if (0 == actual) return FALSE; /* EOF */ if (actual > 0) { st->bp = s; st->end = s + actual; return TRUE; } saved_errno = errno; if (EINTR != saved_errno) { read_error_exit (/* msg: errno */ NULL); } } while (--retry > 0); read_error_exit (/* msg: errno */ NULL); return FALSE; } static vbi_bool read_loop_pes_ts (struct stream * st) { for (;;) { double sample_time; int64_t pts; unsigned int left; unsigned int n_lines; if (st->bp >= st->end) { if (!read_more (st)) break; /* EOF */ } left = st->end - st->bp; n_lines = vbi_dvb_demux_cor (st->dx, st->sliced, N_ELEMENTS (st->sliced), &pts, &st->bp, &left); if (0 == n_lines) continue; if (pts < 0) { /* XXX WTF? */ continue; } sample_time = pts * (1 / 90000.0); if (!st->callback (st->sliced, n_lines, /* raw */ NULL, /* sp */ NULL, sample_time, pts)) return FALSE; } return TRUE; } static vbi_bool next_byte (struct stream * st, int * c) { do { if (st->bp < st->end) { *c = *st->bp++; return TRUE; } } while (read_more (st)); return FALSE; /* EOF */ } static void next_block (struct stream * st, uint8_t * buffer, unsigned int buffer_size) { do { unsigned int available; available = st->end - st->bp; if (buffer_size <= available) { memcpy (buffer, st->bp, buffer_size); st->bp += buffer_size; return; } memcpy (buffer, st->bp, available); st->bp += available; buffer += available; buffer_size -= available; } while (read_more (st)); premature_exit (); } static uint8_t * next_raw_data (struct stream * st, vbi_sampling_par * sp) { uint8_t sp_buffer[32]; unsigned int system; unsigned int size; uint8_t *p; next_block (st, sp_buffer, 22); p = sp_buffer; CLEAR (*sp); #define r8(v) v = *p++ #define r16(v) v = p[0] | (p[1] << 8); p += 2 #define r32(v) v = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); p += 4 r16 (system); sp->sp_sample_format = VBI_PIXFMT_Y8; r32 (sp->sampling_rate); r16 (sp->sp_samples_per_line); r16 (sp->bytes_per_line); r16 (sp->offset); r16 (sp->start[0]); r16 (sp->start[1]); r16 (sp->count[0]); r16 (sp->count[1]); r8 (sp->interlaced); r8 (sp->synchronous); #undef r8 #undef r16 #undef r32 assert (22 == p - sp_buffer); #if 2 == VBI_VERSION_MINOR switch (system) { case 525: case 625: sp->scanning = system; default: bad_format_exit (); break; } #elif 3 == VBI_VERSION_MINOR switch (system) { case 525: sp->videostd_set = VBI_VIDEOSTD_SET_525_60; break; case 625: sp->videostd_set = VBI_VIDEOSTD_SET_625_50; break; default: bad_format_exit (); break; } #else # error VBI_VERSION_MINOR == ? #endif size = (sp->count[0] + sp->count[1]) * sp->bytes_per_line; p = malloc (size); if (NULL == p) no_mem_exit (); next_block (st, p, size); #if 3 == VBI_VERSION_MINOR if (st->debug) { if (NULL == st->rd) { vbi_bool success; st->rd = vbi_raw_decoder_new (sp); if (NULL == st->rd) return p; vbi_raw_decoder_add_services (st->rd, -1, 1); success = vbi_raw_decoder_debug (st->rd, TRUE); assert (success); } st->sliced2_lines = vbi_raw_decoder_decode (st->rd, st->sliced2, N_ELEMENTS (st->sliced2), p); } #endif /* 3 == VBI_VERSION_MINOR */ return p; } static vbi_bool next_time_delta (struct stream * st, double * dt) { char buffer[32]; unsigned int i; for (i = 0; i < N_ELEMENTS (buffer); ++i) { int c; if (!next_byte (st, &c)) { if (i > 0) premature_exit (); else return FALSE; } if ('\n' == c) { if (0 == i) { bad_format_exit (); } else { buffer[i] = 0; *dt = strtod (buffer, NULL); return TRUE; } } if ('-' != c && '.' != c && !isdigit (c)) bad_format_exit (); buffer[i] = c; } return FALSE; } static vbi_bool read_loop_old_sliced (struct stream * st) { for (;;) { vbi_sliced *s; uint8_t *raw; vbi_sampling_par sp; double dt; vbi_bool success; int n_lines; int count; if (!next_time_delta (st, &dt)) break; /* EOF */ /* Time in seconds since last frame. */ if (dt < 0.0) dt = -dt; st->sample_time += dt; if (!next_byte (st, &n_lines)) bad_format_exit (); if ((unsigned int) n_lines > N_ELEMENTS (st->sliced)) bad_format_exit (); s = st->sliced; raw = NULL; st->raw_valid = FALSE; for (count = n_lines; count > 0; --count) { int index; int line; if (!next_byte (st, &index)) premature_exit (); if (!next_byte (st, &line)) premature_exit (); s->line = line; if (!next_byte (st, &line)) premature_exit (); s->line += (line & 15) * 256; switch (index) { case 0: s->id = VBI_SLICED_TELETEXT_B; next_block (st, s->data, 42); break; case 1: s->id = VBI_SLICED_CAPTION_625; next_block (st, s->data, 2); break; case 2: s->id = VBI_SLICED_VPS; next_block (st, s->data, 13); break; case 3: s->id = VBI_SLICED_WSS_625; next_block (st, s->data, 2); break; case 4: s->id = VBI_SLICED_WSS_CPR1204; next_block (st, s->data, 3); break; case 7: s->id = VBI_SLICED_CAPTION_525; next_block (st, s->data, 2); break; case 255: raw = next_raw_data (st, &sp); st->raw_valid = TRUE; break; default: bad_format_exit (); break; } ++s; } st->stream_time = st->sample_time * 90000; if (st->raw_valid && st->decode_raw) { success = st->callback (st->sliced2, st->sliced2_lines, raw, &sp, st->sample_time, st->stream_time); } else { success = st->callback (st->sliced, n_lines, raw, &sp, st->sample_time, st->stream_time); } free (raw); raw = NULL; if (!success) return FALSE; } return TRUE; } static vbi_bool look_ahead (struct stream * st, unsigned int n_bytes) { assert (n_bytes <= sizeof (st->buffer)); do { unsigned int available; const uint8_t *end; available = st->end - st->bp; if (available >= n_bytes) return TRUE; end = st->buffer + sizeof (st->buffer); if (n_bytes > (unsigned int)(end - st->bp)) { memmove (st->buffer, st->bp, available); st->bp = st->buffer; st->end = st->buffer + available; } } while (read_more (st)); return FALSE; /* EOF */ } static vbi_bool is_old_sliced_format (const uint8_t s[8]) { unsigned int i; if ('0' != s[0] || '.' != s[1]) return FALSE; for (i = 2; i < 8; ++i) { if (!isdigit (s[i])) return FALSE; } return TRUE; } static vbi_bool is_xml_format (const uint8_t s[6]) { unsigned int i; if ('<' != s[0]) return FALSE; for (i = 1; i < 6; ++i) { if (!isalpha (s[i])) return FALSE; } return TRUE; } static vbi_bool is_pes_format (const uint8_t s[4]) { return (0x00 == s[0] && 0x00 == s[1] && 0x01 == s[2] && 0xBD == s[3]); } static vbi_bool is_ts_format (const uint8_t s[1]) { return (0x47 == s[0]); } static enum file_format detect_file_format (struct stream * st) { if (!look_ahead (st, 8)) return 0; /* unknown format */ if (is_old_sliced_format (st->bp)) return FILE_FORMAT_SLICED; if (is_xml_format (st->buffer)) return FILE_FORMAT_XML; /* Can/shall we guess a PID? */ if (0) { /* Somewhat unreliable and works only if the packets are aligned. */ if (is_ts_format (st->buffer)) return FILE_FORMAT_DVB_TS; } /* Works only if the packets are aligned. */ if (is_pes_format (st->buffer)) return FILE_FORMAT_DVB_PES; return 0; /* unknown format */ } struct stream * read_stream_new (const char * file_name, enum file_format file_format, unsigned int ts_pid, stream_callback_fn * callback) { struct stream *st; st = calloc (1, sizeof (*st)); if (NULL == st) no_mem_exit (); if (NULL == file_name || 0 == strcmp (file_name, "-")) { st->fd = STDIN_FILENO; if (isatty (STDIN_FILENO)) error_exit (_("No VBI data on standard input.")); } else { st->fd = open (file_name, O_RDONLY, 0); if (-1 == st->fd) { error_exit (_("Cannot open '%s' for reading: %s."), file_name, strerror (errno)); } st->close_fd = TRUE; } if (0 == file_format) file_format = detect_file_format (st); switch (file_format) { case FILE_FORMAT_SLICED: st->loop = read_loop_old_sliced; break; case FILE_FORMAT_XML: st->loop = NULL; error_exit ("XML read function " "not implemented yet."); break; case FILE_FORMAT_DVB_PES: st->loop = read_loop_pes_ts; st->dx = vbi_dvb_pes_demux_new (/* callback */ NULL, /* user_data */ NULL); if (NULL == st->dx) no_mem_exit (); break; case FILE_FORMAT_DVB_TS: st->loop = read_loop_pes_ts; st->dx = _vbi_dvb_ts_demux_new (/* callback */ NULL, /* user_data */ NULL, ts_pid); if (NULL == st->dx) no_mem_exit (); break; default: error_exit (_("Unknown input file format.")); break; } st->callback = callback; st->sample_time = 0.0; st->stream_time = 0; st->bp = st->buffer; st->end = st->buffer; return st; } static vbi_bool capture_loop (struct stream * st) { struct timeval timeout; timeout.tv_sec = 2; timeout.tv_usec = 0; for (;;) { vbi_capture_buffer *raw_buffer; vbi_capture_buffer *sliced_buffer; uint8_t *raw; vbi_sliced *sliced; int n_lines; double sample_time; int64_t stream_time; int r; if (st->read_not_pull) { r = vbi_capture_read (st->cap, st->raw, st->sliced, &n_lines, &sample_time, &timeout); } else { r = vbi_capture_pull (st->cap, &raw_buffer, &sliced_buffer, &timeout); } switch (r) { case -1: read_error_exit (/* msg: errno */ NULL); case 0: error_exit (_("Read timeout.")); case 1: break; default: assert (0); } if (st->read_not_pull) { raw = st->raw; sliced = st->sliced; } else { if (NULL != raw_buffer) raw = raw_buffer->data; else raw = NULL; sliced = sliced_buffer->data; n_lines = sliced_buffer->size / sizeof (vbi_sliced); sample_time = sliced_buffer->timestamp; } if (st->interfaces & INTERFACE_DVB) { stream_time = vbi_capture_dvb_last_pts (st->cap); } else { stream_time = sample_time * 90000; } if (!st->callback (sliced, n_lines, raw, &st->sp, sample_time, stream_time)) return FALSE; } return TRUE; } void capture_stream_sim_set_flags (struct stream * st, unsigned int flags) { if (NULL == st->cap) return; if (st->interfaces & INTERFACE_SIM) { _vbi_capture_sim_set_flags (st->cap, flags); if (flags & _VBI_RAW_NOISE_2) { vbi_capture_sim_add_noise (st->cap, /* min_freq */ 0, /* max_freq */ 5000000, /* amplitude */ 25); } else { vbi_capture_sim_add_noise (st->cap, 0, 0, 0); } } } void capture_stream_sim_decode_raw (struct stream * st, vbi_bool enable) { st->decode_raw = !!enable; if (NULL == st->cap) return; if (st->interfaces & INTERFACE_SIM) vbi_capture_sim_decode_raw (st->cap, enable); } vbi_bool capture_stream_sim_load_caption (struct stream * st, const char * stream, vbi_bool append) { if (NULL == st->cap) return FALSE; if (0 == (st->interfaces & INTERFACE_SIM)) return FALSE; return vbi_capture_sim_load_caption (st->cap, stream, append); } vbi_bool capture_stream_get_point (struct stream * st, vbi_bit_slicer_point *point, unsigned int row, unsigned int nth_bit) { #if 2 == VBI_VERSION_MINOR st = st; /* unused */ point = point; row = row; nth_bit = nth_bit; return FALSE; #elif 3 == VBI_VERSION_MINOR if (NULL != st->cap) { return vbi_capture_sampling_point (st->cap, point, row, nth_bit); } else if (NULL != st->rd) { if (!st->raw_valid) return FALSE; return vbi_raw_decoder_sampling_point (st->rd, point, row, nth_bit); } else { return FALSE; } #else # error VBI_VERSION_MINOR == ? #endif } vbi_bool capture_stream_debug (struct stream * st, vbi_bool enable) { #if 2 == VBI_VERSION_MINOR st = st; /* unused */ enable = enable; return FALSE; #elif 3 == VBI_VERSION_MINOR st->debug = TRUE; if (NULL != st->cap) { return vbi_capture_debug (st->cap, enable); } else if (NULL != st->rd) { return vbi_raw_decoder_debug (st->rd, enable); } else { return FALSE; } #else # error VBI_VERSION_MINOR == ? #endif } void capture_stream_get_sampling_par (struct stream * st, vbi_sampling_par * sp) { assert (NULL != sp); *sp = st->sp; } static void capture_error_msg (const char * interface_name, const char * errstr) { error_msg (_("Cannot capture VBI data " "with %s interface: %s."), interface_name, errstr); } struct stream * capture_stream_new (unsigned int interfaces, const char * dev_name, unsigned int system, vbi_service_set services, unsigned int n_buffers, unsigned int ts_pid, vbi_bool sim_interlaced, vbi_bool sim_synchronous, vbi_bool capture_raw_data, vbi_bool read_not_pull, unsigned int strict, stream_callback_fn * callback) { struct stream *st; vbi_bool trace; assert (0 != interfaces); assert (525 == system || 625 == system); assert (0 != services); assert (NULL != callback); st = calloc (1, sizeof (*st)); if (NULL == st) no_mem_exit (); trace = (0 != (option_log_mask & VBI_LOG_INFO)); if (interfaces & INTERFACE_SIM) { st->cap = vbi_capture_sim_new (system, &services, sim_interlaced, sim_synchronous); if (NULL == st->cap) no_mem_exit (); interfaces = INTERFACE_SIM; } if (NULL == dev_name && (interfaces & (INTERFACE_DVB | INTERFACE_V4L2 | INTERFACE_V4L | INTERFACE_BKTR))) { error_exit (_("No device name specified.")); } if (interfaces & INTERFACE_DVB) { char *errstr; assert (0 == (interfaces & (INTERFACE_V4L2 | INTERFACE_V4L | INTERFACE_BKTR | INTERFACE_PROXY))); #if 2 == VBI_VERSION_MINOR /* not ported to 0.3 yet */ assert (NULL != dev_name); if (capture_raw_data) { error_exit (_("Cannot capture raw VBI data " "from a DVB device.")); } st->cap = vbi_capture_dvb_new2 (dev_name, ts_pid, &errstr, trace); if (NULL == st->cap) { interfaces &= ~INTERFACE_DVB; capture_error_msg ("DVB", errstr); } else { interfaces = INTERFACE_DVB; } #elif 3 == VBI_VERSION_MINOR ts_pid = ts_pid; capture_raw_data = capture_raw_data; errstr = NULL; error_exit ("Sorry, no DVB support yet."); #else # error VBI_VERSION_MINOR == ? #endif } if (interfaces & INTERFACE_PROXY) { #if 2 == VBI_VERSION_MINOR char *errstr = NULL; st->proxy = vbi_proxy_client_create(dev_name, "test/capture", 0, /* no flags */ &errstr, trace); if (NULL != st->proxy) { st->cap = vbi_capture_proxy_new(st->proxy, n_buffers, system, &services, strict, &errstr ); if (NULL == st->cap) { interfaces &= ~INTERFACE_PROXY; capture_error_msg ("PROXY", errstr); free (errstr); } else { interfaces = INTERFACE_PROXY; } } else { capture_error_msg ("PROXY", errstr); free (errstr); } #else error_exit ("Sorry, the proxy interface is not " "available yet.\n"); #endif } if (interfaces & INTERFACE_V4L2) { char *errstr; st->cap = vbi_capture_v4l2_new (dev_name, n_buffers, &services, strict, &errstr, trace); if (NULL == st->cap) { interfaces &= ~INTERFACE_V4L2; capture_error_msg ("V4L2", errstr); free (errstr); } else { interfaces = INTERFACE_V4L2; } } if (interfaces & INTERFACE_V4L) { char *errstr; st->cap = vbi_capture_v4l_new (dev_name, system, &services, strict, &errstr, trace); if (NULL == st->cap) { interfaces &= ~INTERFACE_V4L; capture_error_msg ("V4L", errstr); free (errstr); } else { interfaces = INTERFACE_V4L; } } if (interfaces & INTERFACE_BKTR) { char *errstr; st->cap = vbi_capture_bktr_new (dev_name, system, &services, strict, &errstr, trace); if (NULL == st->cap) { interfaces &= ~INTERFACE_BKTR; capture_error_msg ("BKTR", errstr); free (errstr); } else { interfaces = INTERFACE_BKTR; } } if (0 == interfaces) exit (EXIT_FAILURE); if (interfaces & (INTERFACE_SIM | INTERFACE_V4L2 | INTERFACE_V4L | INTERFACE_BKTR | INTERFACE_PROXY)) { unsigned int max_lines; unsigned int raw_size; st->sp = *vbi_capture_parameters (st->cap); max_lines = st->sp.count[0] + st->sp.count[1]; assert (N_ELEMENTS (st->sliced) >= max_lines); raw_size = st->sp.bytes_per_line * max_lines; assert (raw_size > 0); st->raw = malloc (raw_size); if (NULL == st->raw) no_mem_exit (); } else if (interfaces & INTERFACE_DVB) { assert (N_ELEMENTS (st->sliced) >= 2 * 32); /* XXX We should have sampling parameters because DVB VBI can transmit raw VBI samples. For now let's just make write_stream_new() happy. */ CLEAR (st->sp); #if 2 == VBI_VERSION_MINOR st->sp.scanning = 625; #else st->sp.videostd_set = VBI_VIDEOSTD_SET_625_50; #endif } st->loop = capture_loop; st->interfaces = interfaces; st->read_not_pull = read_not_pull; st->callback = callback; return st; } void parse_option_verbose (void) { option_log_mask = option_log_mask * 2 + 1; vbi_set_log_fn (option_log_mask, vbi_log_on_stderr, /* user_data */ NULL); } void parse_option_quiet (void) { option_quiet = TRUE; option_log_mask = 0; vbi_set_log_fn (option_log_mask, /* log_function */ NULL, /* user_data */ NULL); } unsigned int parse_option_ts (void) { unsigned long int value; const char *s = optarg; char *end; assert (NULL != optarg); value = strtoul (s, &end, 0); if (value <= 0x000F || value >= 0x1FFF) { error_exit (_("Invalid PID %u."), value); } return value; } void parse_option_dvb_pid (void) { if (!have_dev_name) { /* Change default. */ option_dev_name = "/dev/dvb/adapter0/demux0"; } option_dvb_pid = parse_option_ts (); } void parse_option_dev_name (void) { assert (NULL != optarg); option_dev_name = optarg; have_dev_name = TRUE; } void init_helpers (int argc, char ** argv) { argc = argc; argv = argv; #ifndef HAVE_PROGRAM_INVOCATION_NAME { unsigned int i; for (i = strlen (argv[0]); i > 0; --i) { if ('/' == argv[0][i - 1]) break; } program_invocation_name = argv[0]; program_invocation_short_name = &argv[0][i]; } #endif setlocale (LC_ALL, ""); option_dev_name = "/dev/vbi"; option_log_mask = VBI_LOG_NOTICE * 2 - 1; vbi_set_log_fn (option_log_mask, vbi_log_on_stderr, /* user_data */ NULL); } zvbi-0.2.44/test/sliced.h000066400000000000000000000114701476363111200151550ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2005 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: sliced.h,v 1.14 2008-03-01 07:37:24 mschimek Exp $ */ /* For libzvbi version 0.2.x / 0.3.x. */ #include #include #include #include "src/macros.h" #include "src/sliced.h" #include "src/sampling_par.h" #include "src/bit_slicer.h" #include "src/version.h" #include "src/io-sim.h" /* Helper functions. */ #ifndef CLEAR # define CLEAR(var) memset (&(var), 0, sizeof (var)) #endif #ifndef N_ELEMENTS # define N_ELEMENTS(array) (sizeof (array) / sizeof (*(array))) #endif enum file_format { FILE_FORMAT_SLICED = 1, FILE_FORMAT_RAW, FILE_FORMAT_XML, FILE_FORMAT_DVB_PES, FILE_FORMAT_DVB_TS, FILE_FORMAT_NEW_SLICED, }; enum interface { INTERFACE_SIM = (1 << 0), INTERFACE_DVB = (1 << 1), INTERFACE_V4L2 = (1 << 2), INTERFACE_V4L = (1 << 3), INTERFACE_BKTR = (1 << 4), INTERFACE_PROXY = (1 << 5), }; typedef vbi_bool stream_callback_fn (const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time); struct stream; #ifndef HAVE_PROGRAM_INVOCATION_NAME extern char * program_invocation_name; extern char * program_invocation_short_name; #endif extern const char * option_dev_name; extern unsigned int option_dvb_pid; extern vbi_bool option_quiet; extern unsigned int option_log_mask; extern void vprint_error (const char * template, va_list ap); extern void error_msg (const char * template, ...); extern void error_exit (const char * template, ...); extern void write_error_exit (const char * msg); extern void read_error_exit (const char * msg); extern void no_mem_exit (void); extern void stream_delete (struct stream * st); extern vbi_bool stream_loop (struct stream * st); extern vbi_bool write_stream_raw (struct stream * st, uint8_t * raw, vbi_sampling_par * sp, double sample_time, int64_t stream_time); extern vbi_bool write_stream_sliced (struct stream * st, const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time); extern void write_stream_set_data_identifier (struct stream * st, unsigned int data_identifier); extern void write_stream_set_pes_packet_size (struct stream * st, unsigned int min, unsigned int max); extern struct stream * write_stream_new (const char * file_name, enum file_format file_format, unsigned int ts_pid, unsigned int system); extern struct stream * read_stream_new (const char * file_name, enum file_format file_format, unsigned int ts_pid, stream_callback_fn * callback); #if 2 == VBI_VERSION_MINOR typedef struct { } vbi_bit_slicer_point; #endif extern void capture_stream_sim_set_flags (struct stream * st, unsigned int flags); extern void capture_stream_sim_decode_raw (struct stream * st, vbi_bool enable); extern vbi_bool capture_stream_sim_load_caption (struct stream * st, const char * stream, vbi_bool append); extern vbi_bool capture_stream_get_point (struct stream * st, vbi_bit_slicer_point *point, unsigned int row, unsigned int nth_bit); extern vbi_bool capture_stream_debug (struct stream * st, vbi_bool enable); extern void capture_stream_get_sampling_par (struct stream * st, vbi_sampling_par * sp); extern struct stream * capture_stream_new (unsigned int interfaces, const char * dev_name, unsigned int system, vbi_service_set services, unsigned int n_buffers, unsigned int ts_pid, vbi_bool sim_interlaced, vbi_bool sim_synchronous, vbi_bool capture_raw_data, vbi_bool read_not_pull, unsigned int strict, stream_callback_fn * callback); extern void parse_option_verbose (void); extern void parse_option_quiet (void); extern unsigned int parse_option_ts (void); extern void parse_option_dvb_pid (void); extern void parse_option_dev_name (void); extern void init_helpers (int argc, char ** argv); zvbi-0.2.44/test/sliced2pes.c000066400000000000000000000174201476363111200157430ustar00rootroot00000000000000/* * zvbi-sliced2pes -- Sliced VBI file converter * * Copyright (C) 2004, 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: sliced2pes.c,v 1.15 2008-03-01 07:36:24 mschimek Exp $ */ /* For libzvbi version 0.2.x / 0.3.x. */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include /* optarg */ #include #ifdef HAVE_GETOPT_LONG # include #endif #include "src/version.h" #if 2 == VBI_VERSION_MINOR # include "src/dvb_mux.h" #elif 3 == VBI_VERSION_MINOR # include "src/zvbi.h" #else # error VBI_VERSION_MINOR == ? #endif #include "sliced.h" #undef _ #define _(x) x /* i18n TODO */ /* Will be installed one day. */ #define PROGRAM_NAME "sliced2pes" static const char * option_in_file_name; static enum file_format option_in_file_format; static unsigned int option_in_ts_pid; static const char * option_out_file_name; static enum file_format option_out_file_format; static unsigned int option_out_ts_pid; static unsigned long option_data_identifier; static unsigned long option_min_pes_packet_size; static unsigned long option_max_pes_packet_size; static struct stream * rst; static struct stream * wst; static vbi_bool output_frame (const vbi_sliced * sliced, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { raw = raw; /* unused */ sp = sp; write_stream_sliced (wst, sliced, n_lines, /* raw */ NULL, /* sp */ NULL, sample_time, stream_time); return TRUE; } static void get_mux_defaults (void) { vbi_dvb_mux *mx; mx = vbi_dvb_pes_mux_new (/* callback */ NULL, /* user_data */ NULL); if (NULL == mx) no_mem_exit (); option_data_identifier = vbi_dvb_mux_get_data_identifier (mx); option_min_pes_packet_size = vbi_dvb_mux_get_min_pes_packet_size (mx); option_max_pes_packet_size = vbi_dvb_mux_get_max_pes_packet_size (mx); vbi_dvb_mux_delete (mx); } static void usage (FILE * fp) { get_mux_defaults (); fprintf (fp, _("\ %s %s -- VBI stream converter\n\n\ Copyright (C) 2004, 2007 Michael H. Schimek\n\ This program is licensed under GPLv2 or later. NO WARRANTIES.\n\n\ Usage: %s [options] < sliced VBI data > PES or TS stream\n\ -h | --help | --usage Print this message and exit\n\ -q | --quiet Suppress progress and error messages\n\ -v | --verbose Increase verbosity\n\ -V | --version Print the program version and exit\n\ Input options:\n\ -i | --input name Read the VBI data from this file instead\n\ of standard input\n\ -P | --pes | --pes-input Source is a DVB PES stream\n\ -T | --ts | --ts-input pid Source is a DVB TS stream\n\ Output options:\n\ -d | --data-identifier n 0x10 ... 0x1F for compatibility with\n\ ETS 300 472 compliant decoders, or\n\ 0x99 ... 0x9B as defined in EN 301 775\n\ (default 0x%02lx)\n\ -m | --max | --max-packet-size n Maximum PES packet size (%lu bytes)\n\ -n | --min | --min-packet-size n Minimum PES packet size (%lu bytes)\n\ -o | --output name Write the VBI data to this file instead\n\ of standard output\n\ -p | --pes-output Generate a DVB PES stream\n\ -t | --ts-output pid Generate a DVB TS stream with this PID\n\ "), PROGRAM_NAME, VERSION, program_invocation_name, option_data_identifier, option_max_pes_packet_size, option_min_pes_packet_size); } static const char short_options [] = "d:hi:m:n:o:pqt:vPT:V"; #ifdef HAVE_GETOPT_LONG static const struct option long_options [] = { { "data-identifier", required_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "usage", no_argument, NULL, 'h' }, { "input", required_argument, NULL, 'i' }, { "max-packet-size", required_argument, NULL, 'm' }, { "min-packet-size", required_argument, NULL, 'n' }, { "output", required_argument, NULL, 'o' }, { "pes-output", no_argument, NULL, 'p' }, { "quiet", no_argument, NULL, 'q' }, { "ts-output", required_argument, NULL, 't' }, { "verbose", no_argument, NULL, 'v' }, { "pes-input", no_argument, NULL, 'P' }, { "ts-input", required_argument, NULL, 'T' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, 0, 0 } }; #else # define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; static void parse_option_data_identifier (void) { const char *s = optarg; char *end; assert (NULL != optarg); option_data_identifier = strtoul (s, &end, 0); if (option_data_identifier > 0xFF) { error_exit (_("Invalid data identifier 0x%02lx."), option_data_identifier); } } int main (int argc, char ** argv) { init_helpers (argc, argv); get_mux_defaults (); option_in_file_format = FILE_FORMAT_SLICED; option_out_file_format = FILE_FORMAT_DVB_PES; for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ break; case 'd': parse_option_data_identifier (); break; case 'h': usage (stdout); exit (EXIT_SUCCESS); case 'i': assert (NULL != optarg); option_in_file_name = optarg; break; case 'm': assert (NULL != optarg); option_max_pes_packet_size = strtoul (optarg, NULL, 0); if (option_max_pes_packet_size > UINT_MAX) option_max_pes_packet_size = UINT_MAX; break; case 'n': assert (NULL != optarg); option_min_pes_packet_size = strtoul (optarg, NULL, 0); if (option_min_pes_packet_size > UINT_MAX) option_min_pes_packet_size = UINT_MAX; break; case 'o': assert (NULL != optarg); option_out_file_name = optarg; break; case 'p': option_out_file_format = FILE_FORMAT_DVB_PES; break; case 'q': parse_option_quiet (); break; case 't': option_out_ts_pid = parse_option_ts (); option_out_file_format = FILE_FORMAT_DVB_TS; break; case 'v': parse_option_verbose (); break; case 'P': option_in_file_format = FILE_FORMAT_DVB_PES; break; case 'T': option_in_ts_pid = parse_option_ts (); option_in_file_format = FILE_FORMAT_DVB_TS; break; case 'V': printf (PROGRAM_NAME " " VERSION "\n"); exit (EXIT_SUCCESS); default: usage (stderr); exit (EXIT_FAILURE); } } wst = write_stream_new (option_out_file_name, option_out_file_format, option_out_ts_pid, /* system */ 625); write_stream_set_data_identifier (wst, option_data_identifier); write_stream_set_pes_packet_size (wst, option_min_pes_packet_size, option_max_pes_packet_size); rst = read_stream_new (option_in_file_name, option_in_file_format, option_in_ts_pid, output_frame); stream_loop (rst); stream_delete (rst); rst = NULL; stream_delete (wst); wst = NULL; error_msg (_("End of stream.")); exit (EXIT_SUCCESS); } zvbi-0.2.44/test/test-common.cc000066400000000000000000000060431476363111200163150ustar00rootroot00000000000000/* * libzvbi - Unit test helper functions * * Copyright (C) 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-common.cc,v 1.7 2009-03-04 21:48:14 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "src/misc.h" #include "test-common.h" #include "src/version.h" #ifdef _WIN32 long int mrand48(void) { return rand() > RAND_MAX / 2 ? rand() : -rand(); } #endif void * memset_rand (void * dst, size_t n_bytes) { uint8_t *p; uint8_t *p_end; unsigned int x; size_t todo; assert (NULL != dst); assert (n_bytes > 0); assert (n_bytes < (10 << 20)); p = (uint8_t *) dst; todo = (size_t) dst & 3; if (0 != todo) { todo = MIN (4 - todo, n_bytes); p_end = p + todo; x = mrand48 (); while (p < p_end) { *p++ = x; x >>= 8; } n_bytes -= todo; } p_end = p + (n_bytes & ~3); for (; p < p_end; p += 4) * ((uint32_t *) p) = mrand48 (); todo = n_bytes & 3; if (0 != todo) { p_end = p + todo; x = mrand48 (); while (p < p_end) { *p++ = x; x >>= 8; } } return dst; } int memcmp_zero (const void * src, size_t n_bytes) { const uint8_t *s; for (s = (const uint8_t *) src; n_bytes > 0; ++s, --n_bytes) { if (0 != *s) return 1; } return 0; } void * xmalloc (size_t n_bytes) { void *p; assert (n_bytes > 0); assert (n_bytes < (10 << 20)); p = malloc (n_bytes); assert (NULL != p); return p; } void * xralloc (size_t n_bytes) { return memset_rand (xmalloc (n_bytes), n_bytes); } void * xmemdup (const void * src, size_t n_bytes) { void *dst; assert (NULL != src); dst = xmalloc (n_bytes); memcpy (dst, src, n_bytes); return dst; } #if 3 == VBI_VERSION_MINOR static unsigned int malloc_count; static unsigned int malloc_fail_cycle; static void * my_malloc (size_t n_bytes) { if (malloc_count++ == malloc_fail_cycle) return NULL; else return malloc (n_bytes); } #endif void test_malloc (void (* function)(void), unsigned int n_cycles) { #if 3 == VBI_VERSION_MINOR vbi_malloc = my_malloc; for (malloc_fail_cycle = 0; malloc_fail_cycle < n_cycles; ++malloc_fail_cycle) { malloc_count = 0; function (); } vbi_malloc = malloc; #else function = function; /* unused */ n_cycles = n_cycles; #endif } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/test-common.h000066400000000000000000000030231476363111200161520ustar00rootroot00000000000000/* * libzvbi - Unit test helper functions * * Copyright (C) 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-common.h,v 1.6 2009-03-04 21:48:11 mschimek Exp $ */ #include #include #include "src/macros.h" #define RAND(var) memset_rand (&(var), sizeof (var)) extern void * memset_rand (void * dst, size_t n) _vbi_nonnull ((1)); extern int memcmp_zero (const void * src, size_t n) _vbi_nonnull ((1)); extern void * xmalloc (size_t n_bytes) _vbi_alloc; extern void * xralloc (size_t n_bytes) _vbi_alloc; extern void * xmemdup (const void * src, size_t n_bytes) _vbi_alloc _vbi_nonnull ((1)); extern void test_malloc (void (* function)(void), unsigned int n_cycles = 1); /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/test-dvb_demux.cc000066400000000000000000000041321476363111200167770ustar00rootroot00000000000000/* * libzvbi - vbi_dvb_demux unit test * * Copyright (C) 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-dvb_demux.cc,v 1.3 2008-03-01 07:36:09 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "src/dvb_demux.h" #include "test-common.h" /* TO DO */ static void test_silly_start_codes (void) { vbi_dvb_demux *dx; uint8_t *packet; const uint8_t *p; unsigned int p_left; vbi_sliced *sliced_out; unsigned int max_lines_out; int64_t pts_out; unsigned int i; dx = vbi_dvb_pes_demux_new (/* callback */ NULL, /* user_data */ NULL); assert (NULL != dx); max_lines_out = 50; sliced_out = (vbi_sliced *) xmalloc (max_lines_out * sizeof (*sliced_out)); packet = (uint8_t *) xmalloc (256); memset (packet, -1, 256); packet[7] = 0x00; packet[8] = 0x00; packet[9] = 0x01; for (i = 0x00; i < 0xBC; ++i) { unsigned int n_lines_out; packet[10] = 0x04; p = packet; p_left = 256; n_lines_out = vbi_dvb_demux_cor (dx, sliced_out, max_lines_out, &pts_out, &p, &p_left); assert (0 == n_lines_out); assert (0 == p_left); } free (packet); free (sliced_out); vbi_dvb_demux_delete (dx); } int main (void) { /* Regression for a bug fixed in 0.2.27. */ test_silly_start_codes (); return 0; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/test-dvb_mux.cc000066400000000000000000002514471476363111200165030ustar00rootroot00000000000000/* * libzvbi - vbi_dvb_mux unit test * * Copyright (C) 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-dvb_mux.cc,v 1.7 2017-03-18 15:08:55 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include /* XXX -> dvb_mux.h */ #include "src/misc.h" #include "src/dvb.h" #include "src/dvb_mux.h" #include "src/dvb_demux.h" #include "src/version.h" #include "test-common.h" #if 3 == VBI_VERSION_MINOR # define sp_sample_format sample_format # define sp_samples_per_line samples_per_line #else # define sp_sample_format sampling_format /* Has no samples_per_line field yet. */ # define sp_samples_per_line bytes_per_line #endif // XXX Later. enum { VBI_ERR_BUFFER_OVERFLOW = 0, VBI_ERR_AMBIGUOUS_VIDEOSTD = 0, VBI_ERR_LINE_NUMBER = 0, VBI_ERR_LINE_ORDER = 0, VBI_ERR_INVALID_SERVICE = 0, VBI_ERR_SAMPLE_NUMBER = 0, VBI_ERR_NO_RAW_DATA = 0, VBI_ERR_SAMPLING_PAR = 0, }; enum { EXPECT_FAILURE = FALSE, EXPECT_SUCCESS = TRUE }; // Data unit size. enum { VARIABLE = FALSE, FIXED = TRUE }; // Add stuffing data units. enum { NO_STUFFING = FALSE, STUFFING = TRUE, ANY_STUFFING = 0x12345, }; static const unsigned int ANY_DATA_IDENTIFIER = 0x12345; static const vbi_videostd_set ANY_VIDEOSTD = 0x12345; static const vbi_service_set ALL_SERVICES = -1; // EN 301 775 table 2. static const unsigned int data_identifiers[] = { 0, // "reserved for future use" 0x0F, 0x10, /* "EBU Teletext only or EBU Teletext combined with VPS and/or WSS and/or Closed Captioning and/or VBI sample data" */ 0x1F, 0x20, // "reserved for future use" 0x7F, 0x80, // "user defined" 0x98, 0x99, /* "EBU Teletext and/or VPS and/or WSS and/or Closed Captioning and/or VBI sample data" */ 0x9B, 0x9C, // "user defined" 0xFF, UINT_MAX }; // EN 301 775 table 3. static const vbi_service_set good_services [] = { 0, VBI_SLICED_CAPTION_625, // EN 301 775 section 4.8.2: Only first field. VBI_SLICED_CAPTION_625_F1, VBI_SLICED_TELETEXT_B_625, VBI_SLICED_TELETEXT_B_L10_625, VBI_SLICED_TELETEXT_B_L25_625, // EN 301 775 section 4.6.2: Only first field. VBI_SLICED_VPS, VBI_SLICED_WSS_625, }; static vbi_bool is_good_service (vbi_service_set service) { unsigned int i; for (i = 0; i < N_ELEMENTS (good_services); ++i) { if (service == good_services[i]) return TRUE; } return FALSE; } static const vbi_service_set all_services [] = { 0, (vbi_service_set) -1, VBI_SLICED_2xCAPTION_525, VBI_SLICED_CAPTION_525, VBI_SLICED_CAPTION_525_F1, VBI_SLICED_CAPTION_525_F2, // Just a little challenge. VBI_SLICED_CAPTION_625 | VBI_SLICED_WSS_625, VBI_SLICED_CAPTION_625, VBI_SLICED_CAPTION_625_F1, VBI_SLICED_CAPTION_625_F2, VBI_SLICED_TELETEXT_A, VBI_SLICED_TELETEXT_BD_525, VBI_SLICED_TELETEXT_B_525, VBI_SLICED_TELETEXT_B_625 | VBI_SLICED_VPS, VBI_SLICED_TELETEXT_B_625, VBI_SLICED_TELETEXT_B_L10_625, VBI_SLICED_TELETEXT_B_L25_625, VBI_SLICED_TELETEXT_C_525, VBI_SLICED_TELETEXT_D_525, VBI_SLICED_VBI_525, VBI_SLICED_VBI_625, VBI_SLICED_VPS | VBI_SLICED_CAPTION_625, VBI_SLICED_VPS | VBI_SLICED_VPS_F2, VBI_SLICED_VPS, VBI_SLICED_VPS_F2, VBI_SLICED_WSS_625, VBI_SLICED_WSS_CPR1204, }; /* These line numbers are bad because they cannot be encoded in a ETS 300 472 / EN 301 775 compliant stream. */ static const unsigned int bad_line_numbers [] = { 32, 262, 263 + 32, 312, 313 + 32, 524, 525, 526, 624, 625, 626, INT_MAX, ((unsigned int) INT_MAX) + 1, UINT_MAX }; static const unsigned int raw_offsets [] = { 0, 1, 39, 40, 41, 250, 251, 252, 719, 720, 721, INT_MAX, ((unsigned int) INT_MAX) + 1, UINT_MAX }; static const unsigned int border_uints [] = { INT_MAX, ((unsigned int) INT_MAX) + 1, UINT_MAX }; static vbi_sliced * alloc_sliced (unsigned int n_lines) { vbi_sliced *sliced; sliced = (vbi_sliced *) xmalloc (n_lines * sizeof (vbi_sliced)); /* Must initialize the .data[] arrays for valgrind. We fill with 0xFF because zeros have special meaning. */ memset (sliced, -1, n_lines * sizeof (vbi_sliced)); return sliced; } static uint8_t * alloc_raw_frame (const vbi_sampling_par *sp) { unsigned int n_lines; unsigned int size; n_lines = sp->count[0] + sp->count[1]; assert (n_lines > 0); size = (n_lines - 1) * sp->bytes_per_line; size += sp->sp_samples_per_line; assert (size < (10 << 20)); return (uint8_t *) xralloc (size); } static void assert_stuffing_ok (unsigned int * n_sliced_dus, unsigned int * n_raw_dus, unsigned int * n_stuffing_dus, const uint8_t * p, unsigned int n_bytes, vbi_bool fixed_length) { /* Verify the value of reserved bits and stuffing bytes which are ignored by the vbi_dvb_demux. */ *n_sliced_dus = 0; *n_raw_dus = 0; *n_stuffing_dus = 0; while (n_bytes >= 2) { unsigned int data_unit_id; unsigned int data_unit_length; unsigned int n_pixels; unsigned int min_bits; unsigned int i; data_unit_id = p[0]; data_unit_length = p[1]; // EN 301 775 section 4.4.2. if (fixed_length) assert (0x2C == data_unit_length); // EN 301 775 table 3. switch (data_unit_id) { case 0x02: // "EBU Teletext non-subtitle data" // EN 301 775 table 4. min_bits = 2 + 1 + 5 + 8 + 336; assert (0xC0 == (p[2] & 0xC0)); ++*n_sliced_dus; break; case 0x03: // "EBU Teletext subtitle data" // Not supported by libzvbi. assert (0); case 0xB4: /* DATA_UNIT_ZVBI_WSS_CPR1204 */ // Should not appear here. assert (0); case 0xB5: /* DATA_UNIT_ZVBI_CLOSED_CAPTION_525 */ // Should not appear here. assert (0); case 0xB6: /* DATA_UNIT_ZVBI_MONOCHROME_SAMPLES_525 */ // Should not appear here. assert (0); case 0xC0: // "Inverted Teletext" case 0xC1: // "Reserved"? Teletext? (EN 301 775 table 1) // Not supported by libzvbi. assert (0); case 0xC3: // "VPS" // EN 301 775 table 6. min_bits = 2 + 1 + 5 + 104; assert (0xC0 == (p[2] & 0xC0)); ++*n_sliced_dus; break; case 0xC4: // "WSS" // EN 301 775 table 8. min_bits = 2 + 1 + 5 + 14 + 2; assert (0xC0 == (p[2] & 0xC0)); assert (0x03 == (p[4] & 0x03)); ++*n_sliced_dus; break; case 0xC5: // "Closed Captioning" // EN 301 775 table 10. min_bits = 2 + 1 + 5 + 16; assert (0xC0 == (p[2] & 0xC0)); ++*n_sliced_dus; break; case 0xC6: // "monochrome 4:2:2 samples" n_pixels = p[5]; min_bits = 1 + 1 + 1 + 5 + 16 + 8 + 8 * n_pixels; ++*n_raw_dus; break; case 0xFF: // "stuffing" // EN 301 775 table 1. min_bits = 0; ++*n_stuffing_dus; break; default: assert (0); } /* Our mux should not generate stuffing data units between other data units. */ if (0xFF != data_unit_id) assert (0 == *n_stuffing_dus); assert (data_unit_length >= min_bits / 8); assert (n_bytes >= 2 + data_unit_length); // EN 301 775 table 1: N * stuffing_byte [8]. for (i = min_bits / 8; i < data_unit_length; ++i) assert (0xFF == p[2 + i]); p += 2 + data_unit_length; n_bytes -= 2 + data_unit_length; } assert (0 == n_bytes); } static void assert_raw_data_units_ok (unsigned int * n_raw_dus, unsigned int * n_stuffing_dus, const uint8_t * p, unsigned int n_bytes, vbi_bool fixed_length, const uint8_t * raw, unsigned int raw_offset, vbi_videostd_set videostd_set, unsigned int frame_line, unsigned int first_pixel_position_0, unsigned int n_pixels_total) { unsigned int f2_start; unsigned int next_first_pixel_position; *n_raw_dus = 0; *n_stuffing_dus = 0; if (0 != (videostd_set & VBI_VIDEOSTD_SET_525_60)) { assert (0 == (videostd_set & VBI_VIDEOSTD_SET_625_50)); f2_start = 263; } else if (0 != (videostd_set & VBI_VIDEOSTD_SET_625_50)) { f2_start = 313; } else { assert (0); } next_first_pixel_position = first_pixel_position_0 + raw_offset; while (n_bytes >= 2) { unsigned int data_unit_id; unsigned int data_unit_length; unsigned int min_bits; unsigned int first_segment_flag; unsigned int last_segment_flag; unsigned int field_parity; unsigned int line_offset; unsigned int first_pixel_position; unsigned int n_pixels; unsigned int i; data_unit_id = p[0]; data_unit_length = p[1]; // EN 301 775 section 4.4.2. if (fixed_length) assert (0x2C == data_unit_length); // EN 301 775 table 3. switch (data_unit_id) { case 0x02: // "EBU Teletext non-subtitle data" case 0x03: // "EBU Teletext subtitle data" case 0xC0: // "Inverted Teletext" case 0xC1: // "Reserved"? Teletext? (EN 301 775 table 1) case 0xC3: // "VPS" case 0xC4: // "WSS" case 0xC5: // "Closed Captioning" // Should not appear here. assert (0); case 0xC6: // "monochrome 4:2:2 samples" // EN 301 775 table 12. first_segment_flag = !!(p[2] & 0x80); last_segment_flag = !!(p[2] & 0x40); field_parity = !!(p[2] & 0x20); line_offset = p[2] & 0x1F; first_pixel_position = p[3] * 256 + p[4]; n_pixels = p[5]; // EN 301 775 section 4.9.2 assert ((first_pixel_position == first_pixel_position_0) == first_segment_flag); // EN 301 775 section 4.9.2 assert ((first_pixel_position + n_pixels == (first_pixel_position_0 + n_pixels_total)) == last_segment_flag); assert (field_parity == (frame_line < f2_start)); if (0 == field_parity) assert (line_offset == frame_line - f2_start); else assert (line_offset == frame_line); // EN 301 775 table 12, section 4.9.2. assert (first_pixel_position <= 719); /* EN 301 775 section 4.9.2: "If this segment is followed by another (i.e. last_segment_flag equals '0'), the value of first_pixel_position of the next segment shall equal the sum of the current values of first_pixel_position and n_pixels." */ assert (first_pixel_position == next_first_pixel_position); next_first_pixel_position = first_pixel_position + n_pixels; // EN 301 775 table 12, section 4.9.2. assert (n_pixels >= 1); assert (n_pixels <= 251); assert (0 == memcmp (raw, p + 6, n_pixels)); raw += n_pixels; // EN 301 775 table 12. min_bits = 1 + 1 + 1 + 5 + 16 + 8 + n_pixels * 8; ++*n_raw_dus; break; case 0xFF: // "stuffing" // EN 301 775 table 1. min_bits = 0; ++*n_stuffing_dus; break; default: assert (0); } /* Our mux should not generate stuffing data units between other data units. */ if (0xFF != data_unit_id) assert (0 == *n_stuffing_dus); assert (data_unit_length >= min_bits / 8); assert (n_bytes >= 2 + data_unit_length); // EN 301 775 table 1: N * stuffing_byte [8]. for (i = min_bits / 8; i < data_unit_length; ++i) assert (0xFF == p[2 + i]); p += 2 + data_unit_length; n_bytes -= 2 + data_unit_length; } assert (0 == n_bytes); } static void assert_pes_packet_ok (unsigned int * n_sliced_dus, unsigned int * n_raw_dus, unsigned int * n_stuffing_dus, const uint8_t * p, unsigned int n_bytes, unsigned int data_identifier, unsigned int min_size, unsigned int max_size) { unsigned int PES_packet_length; unsigned int PES_header_data_length; vbi_bool fixed_length; unsigned int i; // EN 301 775 section 4.4.2. fixed_length = (data_identifier >= 0x10 && data_identifier <= 0x1F); assert (n_bytes >= 46); /* packet_start_code_prefix [24], stream_id [8] */ assert (0x00 == p[0]); assert (0x00 == p[1]); assert (0x01 == p[2]); assert (0xBD == p[3]); PES_packet_length = p[4] * 256 + p[5]; // EN 301 775 section 4.3. assert (0 == (PES_packet_length + 6) % 184); assert (PES_packet_length + 6 >= min_size); assert (PES_packet_length + 6 <= max_size); /* '10', PES_scrambling_control [2], PES_priority, data_alignment_indicator, copyright, original_or_copy */ assert (0x84 == p[6]); /* PTS_DTS_flags [2], ESCR_flag, ES_rate_flag DSM_trick_mode_flag, additional_copy_info_flag, PES_CRC_flag, PES_extension_flag */ assert (0x80 == p[7]); PES_header_data_length = p[8]; // EN 301 775 section 4.3. assert (0x24 == PES_header_data_length); /* '0010', PTS 32...30 [3] marker_bit, PTS 29 ... 15 [15], marker_bit, PTS 14 ... 0 [15] marker_bit */ assert (0x21 == (p[9] & 0xF1)); assert (0x01 == (p[11] & 0x01)); assert (0x01 == (p[13] & 0x01)); // EN 301 775 section 4.3. (9 + 0x24 == 45) for (i = 14; i <= 44; ++i) { /* stuffing_byte [8] */ assert (0xFF == p[i]); } assert (data_identifier == p[45]); p += 46; n_bytes -= 46; assert (PES_packet_length - 40 == n_bytes); assert_stuffing_ok (n_sliced_dus, n_raw_dus, n_stuffing_dus, p, n_bytes, fixed_length); } static void assert_same_sliced (const vbi_sliced * sliced_in, unsigned int n_lines_in, const vbi_sliced * sliced_out, unsigned int n_lines_out, vbi_service_set service_mask) { unsigned int i_in; unsigned int i_out; i_out = 0; for (i_in = 0; i_in < n_lines_in; ++i_in) { vbi_service_set id_in; vbi_service_set id_out; unsigned int payload_bits; id_in = sliced_in[i_in].id; id_out = sliced_out[i_out].id; switch (id_in & service_mask) { case VBI_SLICED_CAPTION_625_F1: case VBI_SLICED_CAPTION_625: assert (VBI_SLICED_CAPTION_625_F1 == id_out); break; case VBI_SLICED_TELETEXT_B_L10_625: case VBI_SLICED_TELETEXT_B_L25_625: case VBI_SLICED_TELETEXT_B_625: assert (VBI_SLICED_TELETEXT_B_625 == id_out); break; case VBI_SLICED_VPS: case VBI_SLICED_WSS_625: assert (id_in == id_out); break; default: // Was not encoded. continue; } assert (i_out < n_lines_out); assert (sliced_in[i_in].line == sliced_out[i_out].line); payload_bits = vbi_sliced_payload_bits (id_in); assert (payload_bits > 0); assert ( ((payload_bits + 7) >> 3) <= sizeof (sliced_in[0].data)); assert (0 == memcmp (sliced_in[i_in].data, sliced_out[i_out].data, payload_bits >> 3)); if ((payload_bits & 7) > 0) { unsigned int last_in; unsigned int last_out; unsigned int mask; last_in = sliced_in[i_in].data[payload_bits >> 3]; last_out = sliced_out[i_out].data[payload_bits >> 3]; mask = (1 << (payload_bits & 7)) - 1; assert (0 == ((last_in ^ last_out) & mask)); } ++i_out; } assert (i_out == n_lines_out); } static void assert_du_conversion_ok (const uint8_t * packet, unsigned int packet_size, const vbi_sliced * sliced_in, unsigned int n_lines_in, vbi_service_set service_mask) { vbi_sliced *sliced_out; const uint8_t *p; unsigned int p_left; unsigned int n_lines_out; unsigned int max_lines_out; vbi_bool success; max_lines_out = n_lines_in * 2 + 1; sliced_out = alloc_sliced (max_lines_out); memset_rand (sliced_out, sizeof (sliced_out)); p = packet; p_left = packet_size; success = _vbi_dvb_demultiplex_sliced (sliced_out, &n_lines_out, max_lines_out, &p, &p_left); assert (TRUE == success); assert (n_lines_out < max_lines_out); assert (n_lines_out <= n_lines_in); assert (0 == p_left); assert_same_sliced (sliced_in, n_lines_in, sliced_out, n_lines_out, service_mask); free (sliced_out); sliced_out = (vbi_sliced *) -1; } static void assert_pes_conversion_ok (const uint8_t * packet, unsigned int packet_size, const vbi_sliced * sliced_in, unsigned int n_lines_in, vbi_service_set service_mask, int64_t pts_in) { vbi_dvb_demux *dx; const uint8_t *p; vbi_sliced *sliced_out; unsigned int p_left; unsigned int n_lines_out; unsigned int max_lines_out; int64_t pts_valid_bits; int64_t pts_out; max_lines_out = n_lines_in * 2 + 1; sliced_out = alloc_sliced (max_lines_out); memset_rand (sliced_out, sizeof (sliced_out)); pts_out = rand(); dx = vbi_dvb_pes_demux_new (/* callback */ NULL, /* user_data */ NULL); assert (NULL != dx); p = packet; p_left = packet_size; n_lines_out = vbi_dvb_demux_cor (dx, sliced_out, max_lines_out, &pts_out, &p, &p_left); assert (0 == n_lines_out); assert (0 == p_left); p = packet; p_left = packet_size; n_lines_out = vbi_dvb_demux_cor (dx, sliced_out, max_lines_out, &pts_out, &p, &p_left); // Frame complete? /* FIXME: Frame end may be unclear, e.g. only data units with line_offset = 0 in the packet. */ if (0 == p_left) { vbi_dvb_demux_delete (dx); free (sliced_out); return; } assert (n_lines_out < max_lines_out); assert (n_lines_out <= n_lines_in); vbi_dvb_demux_delete (dx); dx = (vbi_dvb_demux *) -1; assert_same_sliced (sliced_in, n_lines_in, sliced_out, n_lines_out, service_mask); // FIXME: Compare raw data frame. // ISO 13818-1 section 2.4.3.7. pts_valid_bits = ((int64_t) 1 << 33) - 1; assert (0 == ((pts_in ^ pts_out) & pts_valid_bits)); assert (0 == (pts_out & ~pts_valid_bits)); free (sliced_out); } /* Test vbi_dvb_multiplex_sliced(). */ static void assert_multiplex_sliced (uint8_t * const p1, const unsigned int p1_size, const vbi_sliced * const s1, const unsigned int s1_lines, const vbi_service_set service_mask, unsigned int data_identifier, vbi_bool stuffing, vbi_bool exp_success, int exp_errno, unsigned int exp_out_lines, unsigned int exp_out_data_size, unsigned int exp_consumed_lines) { uint8_t *p; uint8_t *rand_buffer; const vbi_sliced *s; unsigned int p_left; unsigned int s_left; unsigned int fixed_length; unsigned int n_sliced_dus; unsigned int n_raw_dus; unsigned int n_stuffing_dus; vbi_bool success; if (ANY_STUFFING == stuffing) { assert_multiplex_sliced (p1, p1_size, s1, s1_lines, service_mask, data_identifier, /* stuffing */ FALSE, exp_success, exp_errno, exp_out_lines, exp_out_data_size, exp_consumed_lines); stuffing = TRUE; if (exp_success) exp_out_data_size = p1_size; } if (ANY_DATA_IDENTIFIER == data_identifier) { assert_multiplex_sliced (p1, p1_size, s1, s1_lines, service_mask, /* data_identifier */ 0x99, stuffing, exp_success, exp_errno, exp_out_lines, exp_out_data_size, exp_consumed_lines); data_identifier = 0x10; if (exp_success) { if (0 == p1_size % 46) { if (stuffing) exp_out_data_size = p1_size; else exp_out_data_size = exp_out_lines * 46; } else { exp_success = FALSE; exp_errno = VBI_ERR_BUFFER_OVERFLOW; exp_out_lines = 0; exp_out_data_size = 0; exp_consumed_lines = 0; } } } if (NULL != p1 && p1_size > 0) { rand_buffer = (uint8_t *) xralloc (p1_size); memcpy (p1, rand_buffer, p1_size); } else { rand_buffer = NULL; } p = p1; p_left = p1_size; s = s1; s_left = s1_lines; success = vbi_dvb_multiplex_sliced (&p, &p_left, &s, &s_left, service_mask, data_identifier, stuffing); assert (exp_success == success); if (!success) { exp_errno = exp_errno; // XXX later: assert (exp_errno == errno); } assert (p1 + exp_out_data_size == p); assert (p1_size - exp_out_data_size == p_left); assert (s1 + exp_consumed_lines == s); assert (s1_lines - exp_consumed_lines == s_left); if (NULL == p1) goto finish; assert (0 == memcmp (p, rand_buffer + exp_out_data_size, p1_size - exp_out_data_size)); // EN 301 775 section 4.4.2. fixed_length = (data_identifier >= 0x10 && data_identifier <= 0x1F); assert_stuffing_ok (&n_sliced_dus, &n_raw_dus, &n_stuffing_dus, p1, exp_out_data_size, fixed_length); if (success && stuffing) { assert (exp_out_lines == n_sliced_dus); assert (0 == n_raw_dus); assert_du_conversion_ok (p1, p1_size, s1, s1_lines - s_left, service_mask); } else { assert (exp_out_lines == n_sliced_dus); assert (0 == n_raw_dus); assert (0 == n_stuffing_dus); if (exp_out_data_size > 0) { assert_du_conversion_ok (p1, exp_out_data_size, s1, s1_lines - s_left, service_mask); } } finish: free (rand_buffer); } static void test_ms_stuffing (unsigned int buffer_size, vbi_sliced * sliced, unsigned int n_lines, unsigned int data_identifier, unsigned int exp_out_lines) { uint8_t *buffer; buffer = (uint8_t *) xmalloc (buffer_size); assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, data_identifier, STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, exp_out_lines, /* exp_out_data_size */ buffer_size, /* exp_consumed_lines */ exp_out_lines); free (buffer); } static void test_multiplex_sliced_stuffing (void) { vbi_sliced *sliced; unsigned int n_lines; unsigned int buffer_size; sliced = alloc_sliced (n_lines = 1); sliced[0].id = VBI_SLICED_TELETEXT_B_625; sliced[0].line = 7; for (buffer_size = 2; buffer_size < 46; ++buffer_size) { test_ms_stuffing (buffer_size, sliced, n_lines, /* data_identifier */ 0x99, /* exp_out_lines */ 0); } for (buffer_size = 46; buffer_size < 300; ++buffer_size) { test_ms_stuffing (buffer_size, sliced, n_lines, /* data_identifier */ 0x99, /* exp_out_lines */ 1); } for (buffer_size = 1 * 46; buffer_size <= 10 * 46; buffer_size += 46) { test_ms_stuffing (buffer_size, sliced, n_lines, /* data_identifier */ 0x10, /* exp_out_lines */ 1); } free (sliced); } static void test_multiplex_sliced_null_sliced (void) { unsigned int buffer_size; for (buffer_size = 2; buffer_size < 300; ++buffer_size) { test_ms_stuffing (buffer_size, /* sliced */ NULL, /* sliced_lines */ 1, /* data_identifier */ 0x99, /* exp_out_lines */ 0); test_ms_stuffing (buffer_size, /* sliced */ (vbi_sliced *) -1, /* sliced_lines */ 0, /* data_identifier */ 0x99, /* exp_out_lines */ 0); } for (buffer_size = 1 * 46; buffer_size <= 10 * 46; buffer_size += 46) { test_ms_stuffing (buffer_size, /* sliced */ NULL, /* sliced_lines */ 1, /* data_identifier */ 0x10, /* exp_out_lines */ 0); test_ms_stuffing (buffer_size, /* sliced */ (vbi_sliced *) -1, /* sliced_lines */ 0, /* data_identifier */ 0x10, /* exp_out_lines */ 0); } } static void test_ms_line (vbi_service_set service, unsigned int line, vbi_bool correct) { vbi_sliced *sliced; uint8_t *buffer; unsigned int buffer_size; unsigned int n_lines; buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); sliced = alloc_sliced (n_lines = 1); sliced[0].id = service; sliced[0].line = line; if (0 == service) { assert (correct); /* Will be discarded without further checks. */ assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, /* exp_out_lines */ 0, /* exp_out_data_size */ 0, /* exp_consumed_lines */ 1); } else if (correct) { assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, /* data_identifier */ 0x10, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, /* exp_out_lines */ 1, /* exp_out_data_size */ 1 * 46, /* exp_consumed_lines */ 1); } else { assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_LINE_NUMBER, /* exp_out_lines */ 0, /* exp_out_data_size */ 0, /* exp_consumed_lines */ 0); } free (sliced); free (buffer); } static vbi_bool is_correct_line (vbi_service_set service, unsigned int field, unsigned int line_offset) { switch (service) { case 0: return TRUE; case VBI_SLICED_TELETEXT_B_625: case VBI_SLICED_TELETEXT_B_L10_625: case VBI_SLICED_TELETEXT_B_L25_625: // EN 301 775 section 4.5.2. /* Note an undefined line (0) in the second field is permitted, but libzvbi cannot express such line numbers. */ if (0 == field && 0 == line_offset) return TRUE; return (line_offset >= 7 && line_offset <= 22); case VBI_SLICED_VPS: // EN 301 775 section 4.6.2. if (0 == field) return (16 == line_offset); return FALSE; case VBI_SLICED_WSS_625: // EN 301 775 section 4.7.2. if (0 == field) return (23 == line_offset); return FALSE; case VBI_SLICED_CAPTION_625: case VBI_SLICED_CAPTION_625_F1: // EN 301 775 section 4.8.2. if (0 == field) return (21 == line_offset); return FALSE; case VBI_SLICED_VBI_625: // EN 301 775 section 4.9.2. return (line_offset >= 7 && line_offset <= 23); default: break; } assert (0); return FALSE; } static void test_multiplex_sliced_line_number_checks (void) { vbi_sliced *sliced; uint8_t *buffer; vbi_service_set service; unsigned int buffer_size; unsigned int n_lines; unsigned int i; buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); sliced = alloc_sliced (n_lines = 1); sliced[0].id = 0; sliced[0].line = 100; assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, /* exp_out_lines */ 0, /* exp_out_data_size */ 0, /* exp_consumed_lines */ 1); free (sliced); sliced = (vbi_sliced *) -1; free (buffer); buffer = (uint8_t *) -1; for (i = 0; i <= 31; ++i) { unsigned int j; for (j = 0; j < N_ELEMENTS (good_services); ++j) { service = good_services[j]; test_ms_line (service, /* line */ i, is_correct_line (service, 0, i)); test_ms_line (service, /* line */ i + 313, is_correct_line (service, 1, i)); } } for (i = 0; i < N_ELEMENTS (bad_line_numbers); ++i) { unsigned int j; for (j = 0; j < N_ELEMENTS (good_services); ++j) { service = good_services[j]; test_ms_line (service, /* line */ bad_line_numbers[i], /* correct */ 0 == service); } } } static void test_multiplex_sliced_service_checks (vbi_service_set service) { vbi_sliced *sliced; uint8_t *buffer; unsigned int buffer_size; unsigned int n_lines; unsigned int line; unsigned int i; buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); sliced = alloc_sliced (n_lines = 8); /* Verify the data service checks. */ for (i = 0; i < 6; ++i) { sliced[i].id = VBI_SLICED_TELETEXT_B_625; sliced[i].line = i + 7; } if (service & VBI_SLICED_VPS) line = 16; else if (service & VBI_SLICED_CAPTION_625) line = 21; else if (service & VBI_SLICED_WSS_625) line = 23; else line = 13; sliced[6].id = service; sliced[6].line = line; sliced[7].id = VBI_SLICED_TELETEXT_B_625; sliced[7].line = 320; if (is_good_service (service)) { unsigned int exp_out_lines; if (VBI_SLICED_NONE == service) exp_out_lines = n_lines - 1; else exp_out_lines = n_lines; assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, /* data identifier */ 0x10, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, exp_out_lines, exp_out_lines * 46, /* exp_consumed_lines */ n_lines); } else { assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_INVALID_SERVICE, /* exp_out_lines */ 6, /* exp_out_data_size */ 6 * 46, /* exp_consumed_lines */ 6); } /* Verify the service filter. */ if (-1u == service || (VBI_SLICED_TELETEXT_B_625 == (VBI_SLICED_TELETEXT_B_625 & service))) { assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, /* service_mask */ ~service, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, /* exp_out_lines */ 0, /* exp_out_data_size */ 0, /* exp_consumed_lines */ n_lines); } else { assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, /* service_mask */ ~service, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, /* exp_out_lines */ n_lines - 1, /* exp_out_data_size */ (n_lines - 1) * 46, /* exp_consumed_lines */ n_lines); } free (sliced); free (buffer); } static void test_ms_good_line_order (unsigned int nth, unsigned int line) { vbi_sliced *sliced; uint8_t *buffer; unsigned int buffer_size; unsigned int n_lines; unsigned int i; buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); sliced = alloc_sliced (n_lines = 8); for (i = 0; i < 4; ++i) { sliced[i].id = VBI_SLICED_TELETEXT_B_625; sliced[i].line = i + 7; } for (i = 4; i < 8; ++i) { sliced[i].id = VBI_SLICED_TELETEXT_B_625; sliced[i].line = i + 7 + 313; } assert (nth < n_lines); sliced[nth].line = line; assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, /* exp_out_lines */ n_lines, /* exp_out_data_size */ n_lines * 46, /* exp_consumed_lines */ n_lines); free (sliced); free (buffer); } static void test_ms_bad_line_order (unsigned int nth, unsigned int line, unsigned int bad) { vbi_sliced *sliced; uint8_t *buffer; unsigned int buffer_size; unsigned int n_lines; unsigned int i; buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); sliced = alloc_sliced (n_lines = 8); for (i = 0; i < 4; ++i) { sliced[i].id = VBI_SLICED_TELETEXT_B_625; sliced[i].line = 7 + i; } for (i = 4; i < 8; ++i) { sliced[i].id = VBI_SLICED_TELETEXT_B_625; sliced[i].line = 313 + 7 + i - 4; } assert (nth < n_lines); sliced[nth].line = line; assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_LINE_ORDER, /* exp_out_lines */ bad, /* exp_out_data_size */ bad * 46, /* exp_consumed_lines */ bad); free (sliced); free (buffer); } static void test_multiplex_sliced_line_order_checks (void) { unsigned int i; for (i = 0; i < 8; ++i) test_ms_good_line_order (i, 0); test_ms_bad_line_order (0, 19, 1); test_ms_bad_line_order (0, 320, 1); for (i = 1; i < 3; ++i) { // 7, 8, 9, 10, 320, 321, 322, 323. test_ms_bad_line_order (i, 19, i + 1); test_ms_bad_line_order (i, 320 + i, i + 1); test_ms_bad_line_order (i + 4, 7 + i, i + 4); } test_ms_good_line_order (3, 19); test_ms_good_line_order (4, 19); // No line twice. test_ms_bad_line_order (2, 7, 2); test_ms_bad_line_order (2, 8, 2); test_ms_bad_line_order (2, 10, 3); test_ms_bad_line_order (6, 320, 6); test_ms_bad_line_order (6, 321, 6); test_ms_bad_line_order (6, 323, 7); } static void test_ms_packet_offset_size (unsigned int offset, unsigned int buffer_size, vbi_bool stuffing) { vbi_sliced *sliced; uint8_t *buffer; unsigned int n_lines; unsigned int max_lines; unsigned int exp_out_lines; unsigned int exp_out_data_size; vbi_bool full; unsigned int i; buffer = (uint8_t *) xmalloc (buffer_size + offset); sliced = alloc_sliced (max_lines = 24 - 7); n_lines = 0; exp_out_lines = 0; exp_out_data_size = 0; full = FALSE; for (i = 7; i < 16; ++i) { sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = i; if (exp_out_data_size + 46 > buffer_size) { full = TRUE; } else { exp_out_data_size += 46; ++exp_out_lines; } } sliced[n_lines].id = VBI_SLICED_VPS; sliced[n_lines++].line = 16; if (exp_out_data_size + 16 > buffer_size) { full = TRUE; } else if (!full) { exp_out_data_size += 16; ++exp_out_lines; } for (i = 17; i < 21; ++i) { sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = i; if (exp_out_data_size + 46 > buffer_size) { full = TRUE; } else if (!full) { exp_out_data_size += 46; ++exp_out_lines; } } sliced[n_lines].id = VBI_SLICED_CAPTION_625; sliced[n_lines++].line = 21; if (exp_out_data_size + 5 > buffer_size) { full = TRUE; } else if (!full) { exp_out_data_size += 5; ++exp_out_lines; } sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = 22; if (exp_out_data_size + 46 > buffer_size) { full = TRUE; } else if (!full) { exp_out_data_size += 46; ++exp_out_lines; } sliced[n_lines].id = VBI_SLICED_WSS_625; sliced[n_lines++].line = 23; if (exp_out_data_size + 5 > buffer_size) { full = TRUE; } else if (!full) { exp_out_data_size += 5; ++exp_out_lines; } assert (n_lines == max_lines); if (stuffing) exp_out_data_size = buffer_size; assert_multiplex_sliced (buffer + offset, buffer_size, sliced, n_lines, ALL_SERVICES, ANY_DATA_IDENTIFIER, stuffing, EXPECT_SUCCESS, /* exp_errno */ 0, exp_out_lines, exp_out_data_size, /* exp_consumed_lines */ exp_out_lines); free (sliced); free (buffer); } static void test_multiplex_sliced_packet_size (void) { unsigned int i; for (i = 2; i < 2048; (i < 300) ? ++i : i += 7) { test_ms_packet_offset_size (0, i, STUFFING); test_ms_packet_offset_size (0, i, NO_STUFFING); } } static void test_multiplex_sliced_data_identifier_checks (unsigned int data_identifier) { vbi_sliced *sliced; unsigned int buffer_size; unsigned int n_lines; vbi_bool fixed_length; sliced = alloc_sliced (n_lines = 1); sliced[0].id = VBI_SLICED_TELETEXT_B_625; sliced[0].line = 7; // EN 301 775 section 4.4.2. fixed_length = (data_identifier >= 0x10 && data_identifier <= 0x1F); for (buffer_size = 20 * 46 - 1; buffer_size <= 20 * 46 + 1; ++buffer_size) { uint8_t *buffer; buffer = (uint8_t *) xmalloc (buffer_size); if (!fixed_length || 0 == buffer_size % 46) { assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, data_identifier, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, /* exp_out_lines */ n_lines, /* exp_out_data_size */ n_lines * 46, /* exp_consumed_lines */ n_lines); } else { assert_multiplex_sliced (buffer, buffer_size, sliced, n_lines, ALL_SERVICES, data_identifier, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_BUFFER_OVERFLOW, /* exp_out_lines */ 0, /* exp_out_data_size */ 0, /* exp_consumed_lines */ 0); } free (buffer); buffer = (uint8_t *) -1; } free (sliced); } static void test_multiplex_sliced_packet_size_checks (void) { uint8_t *buffer; unsigned int buffer_size; assert_multiplex_sliced (/* buffer */ (uint8_t *) -1, /* buffer_size */ 0, /* sliced */ (vbi_sliced *) -1, /* sliced_lines */ 1, ALL_SERVICES, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_BUFFER_OVERFLOW, /* exp_out_lines */ 0, /* exp_out_data_size */ 0, /* exp_consumed_lines */ 0); buffer = (uint8_t *) xmalloc (buffer_size = 1); assert_multiplex_sliced (buffer, buffer_size, /* sliced */ (vbi_sliced *) -1, /* sliced_lines */ 1, ALL_SERVICES, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_BUFFER_OVERFLOW, /* exp_out_lines */ 0, /* exp_out_data_size */ 0, /* exp_consumed_lines */ 0); free (buffer); buffer = (uint8_t *) xmalloc (buffer_size = 2); assert_multiplex_sliced (buffer, buffer_size, /* sliced */ (vbi_sliced *) -1, /* sliced_lines */ 0, ALL_SERVICES, /* data_identifier */ 0x99, STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0, /* exp_out_lines */ 0, /* exp_out_data_size */ buffer_size, /* exp_consumed_lines */ 0); free (buffer); } static void test_multiplex_sliced_unaligned_packet (void) { unsigned int i; for (i = 1; i < 16; ++i) { test_ms_packet_offset_size (i, 20 * 46, STUFFING); } } static void test_multiplex_sliced_null_packet_checks (void) { vbi_sliced *sliced; sliced = alloc_sliced (1); sliced[0].id = VBI_SLICED_TELETEXT_B_625; sliced[0].line = 7; assert_multiplex_sliced (/* buffer */ NULL, /* buffer_size */ 20 * 46, sliced, /* sliced_lines */ 1, ALL_SERVICES, ANY_DATA_IDENTIFIER, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_BUFFER_OVERFLOW, /* exp_out_lines */ 0, /* exp_out_data_size */ 0, /* exp_consumed_lines */ 0); free (sliced); } void test_multiplex_sliced (void) { unsigned int i; test_multiplex_sliced_null_packet_checks (); test_multiplex_sliced_packet_size_checks (); for (i = 0; i < N_ELEMENTS (data_identifiers); ++i) { unsigned int di = data_identifiers[i]; test_multiplex_sliced_data_identifier_checks (di); } test_multiplex_sliced_line_order_checks (); for (i = 0; i < N_ELEMENTS (all_services); ++i) test_multiplex_sliced_service_checks (all_services[i]); test_multiplex_sliced_line_number_checks (); test_multiplex_sliced_packet_size (); test_multiplex_sliced_unaligned_packet (); test_multiplex_sliced_null_sliced (); test_multiplex_sliced_stuffing (); } /* Test vbi_dvb_multiplex_raw(). */ static void assert_multiplex_raw (uint8_t * const p1, const unsigned int p1_size, const uint8_t * const r1, const unsigned int r1_size, unsigned int data_identifier, vbi_videostd_set videostd_set, const unsigned int line, const unsigned int first_pixel_position, const unsigned int n_pixels_total, vbi_bool stuffing, const vbi_bool exp_success, const int exp_errno) { uint8_t *p; uint8_t *rand_buffer; const uint8_t *r; unsigned int p_left; unsigned int r_left; unsigned int exp_out_dus; unsigned int exp_out_data_size; unsigned int exp_consumed_samples; unsigned int n_raw_dus; unsigned int n_stuffing_dus; vbi_bool fixed_length; vbi_bool success; if (ANY_DATA_IDENTIFIER == data_identifier) { assert_multiplex_raw (p1, p1_size, r1, r1_size, /* data_identifier */ 0x10, videostd_set, line, first_pixel_position, n_pixels_total, stuffing, exp_success, exp_errno); data_identifier = 0x99; } if (ANY_STUFFING == stuffing) { assert_multiplex_raw (p1, p1_size, r1, r1_size, data_identifier, videostd_set, line, first_pixel_position, n_pixels_total, /* stuffing */ FALSE, exp_success, exp_errno); stuffing = TRUE; } if (ANY_VIDEOSTD == videostd_set) { assert_multiplex_raw (p1, p1_size, r1, r1_size, data_identifier, VBI_VIDEOSTD_SET_525_60, line, first_pixel_position, n_pixels_total, stuffing, exp_success, exp_errno); videostd_set = VBI_VIDEOSTD_SET_625_50; } if (NULL != p1 && p1_size > 0) { rand_buffer = (uint8_t *) xralloc (p1_size); memcpy (p1, rand_buffer, p1_size); } else { rand_buffer = NULL; } p = p1; p_left = p1_size; r = r1; r_left = r1_size; success = vbi_dvb_multiplex_raw (&p, &p_left, &r, &r_left, data_identifier, videostd_set, line, first_pixel_position, n_pixels_total, stuffing); assert (exp_success == success); if (!success) { (void) exp_errno; // XXX later: assert (exp_errno == errno); assert (p1 == p); assert (p1_size == p_left); assert (r1 == r); assert (r1_size == r_left); if (NULL != p1) { assert (0 == memcmp (p1, rand_buffer, p1_size)); } goto finish; } // EN 301 775 section 4.4.2. fixed_length = (data_identifier >= 0x10 && data_identifier <= 0x1F); if (fixed_length) { exp_out_dus = MIN (p1_size / 46, (r1_size + 39) / 40); exp_out_data_size = exp_out_dus * 46; exp_consumed_samples = MIN (r1_size, exp_out_dus * 40); } else { exp_out_dus = MIN (p1_size / 257, r1_size / 251); exp_out_data_size = exp_out_dus * 257; exp_consumed_samples = exp_out_dus * 251; if (stuffing && exp_out_data_size + 1 == p1_size) { /* One byte less to make room for a stuffing data unit. */ --exp_consumed_samples; } else if (exp_consumed_samples < r1_size && exp_out_data_size + 7 <= p1_size) { unsigned int n_samples; n_samples = MIN (r1_size - exp_consumed_samples, p1_size - 6 - exp_out_data_size); ++exp_out_dus; exp_out_data_size += 6 + n_samples; exp_consumed_samples += n_samples; } } if (stuffing) exp_out_data_size = p1_size; assert (p1 + exp_out_data_size == p); assert (p1_size - exp_out_data_size == p_left); assert (r1 + r1_size - r_left == r); assert (r1_size - exp_consumed_samples == r_left); assert (0 == memcmp (p, rand_buffer + exp_out_data_size, p1_size - exp_out_data_size)); assert_raw_data_units_ok (&n_raw_dus, &n_stuffing_dus, p1, exp_out_data_size, fixed_length, r1, /* offset */ n_pixels_total - r1_size, videostd_set, line, first_pixel_position, n_pixels_total); assert (exp_out_dus == n_raw_dus); if (!stuffing) assert (0 == n_stuffing_dus); finish: free (rand_buffer); } static void test_mr_size_offset (unsigned int raw_left, unsigned int first_pixel_position, unsigned int n_pixels_total) { uint8_t *buffer; uint8_t *raw; unsigned int buffer_size; vbi_bool exp_success; raw = (uint8_t *) xralloc (720); buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); if (0 == raw_left) { assert_multiplex_raw (buffer, buffer_size, raw, /* raw_size */ 0, ANY_DATA_IDENTIFIER, VBI_VIDEOSTD_SET_625_50, /* line */ 10, first_pixel_position, n_pixels_total, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_NO_RAW_DATA); goto finish; } if (0 == n_pixels_total) exp_success = FALSE; else if ((uint64_t) first_pixel_position + n_pixels_total > (uint64_t) 720) exp_success = FALSE; else if (raw_left > n_pixels_total) exp_success = FALSE; else exp_success = TRUE; assert_multiplex_raw (buffer, buffer_size, raw, raw_left, ANY_DATA_IDENTIFIER, VBI_VIDEOSTD_SET_625_50, /* line */ 10, first_pixel_position, n_pixels_total, ANY_STUFFING, exp_success, VBI_ERR_SAMPLE_NUMBER); free (buffer); buffer = (uint8_t *) xmalloc (buffer_size = 2 * 46); assert_multiplex_raw (buffer, buffer_size, raw, raw_left, ANY_DATA_IDENTIFIER, VBI_VIDEOSTD_SET_625_50, /* line */ 10, first_pixel_position, n_pixels_total, ANY_STUFFING, exp_success, VBI_ERR_SAMPLE_NUMBER); finish: free (buffer); free (raw); } static void test_multiplex_raw_size_offsets (void) { unsigned int i, j, k; for (i = 0; i < N_ELEMENTS (raw_offsets); ++i) { for (j = 0; j < N_ELEMENTS (raw_offsets); ++j) { for (k = 0; k < N_ELEMENTS (raw_offsets); ++k) { test_mr_size_offset (raw_offsets[i], raw_offsets[j], raw_offsets[k]); } } } } static void test_mr_line (unsigned int line, vbi_bool exp_success_525, vbi_bool exp_success_625) { uint8_t *buffer; uint8_t *raw; unsigned int buffer_size; unsigned int raw_size; buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); raw = (uint8_t *) xralloc (raw_size = 720); assert_multiplex_raw (buffer, buffer_size, raw, raw_size, ANY_DATA_IDENTIFIER, VBI_VIDEOSTD_SET_525_60, line, /* first_pixel_position */ 0, /* n_pixels_total */ raw_size, ANY_STUFFING, exp_success_525, VBI_ERR_LINE_NUMBER); assert_multiplex_raw (buffer, buffer_size, raw, raw_size, ANY_DATA_IDENTIFIER, VBI_VIDEOSTD_SET_625_50, line, /* first_pixel_position */ 0, /* n_pixels_total */ raw_size, ANY_STUFFING, exp_success_625, VBI_ERR_LINE_NUMBER); free (raw); free (buffer); } static void test_multiplex_raw_line_number_checks (void) { unsigned int line; unsigned int i; for (line = 0; line < 650; ++line) { vbi_bool exp_success_525; vbi_bool exp_success_625; exp_success_525 = FALSE; exp_success_625 = FALSE; // EN 301 775 table 13. if (line >= 7 && line <= 23) { exp_success_525 = TRUE; exp_success_625 = TRUE; } else if (line >= 263 + 7 && line <= 263 + 23) { exp_success_525 = TRUE; } else if (line >= 313 + 7 && line <= 313 + 23) { exp_success_625 = TRUE; } test_mr_line (line, exp_success_525, exp_success_625); } for (i = 0; i < N_ELEMENTS (border_uints); ++i) test_mr_line (border_uints[i], FALSE, FALSE); } static void test_multiplex_raw_videostd_checks (void) { uint8_t *buffer; unsigned int buffer_size; buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); assert_multiplex_raw (buffer, buffer_size, /* raw */ (uint8_t *) -1, /* raw_size */ 720, ANY_DATA_IDENTIFIER, /* videostd_set */ 0, /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ 720, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_AMBIGUOUS_VIDEOSTD); #if 3 == VBI_VERSION_MINOR assert_multiplex_raw (buffer, buffer_size, /* raw */ (uint8_t *) -1, /* raw_size */ 720, ANY_DATA_IDENTIFIER, (VBI_VIDEOSTD_SET (VBI_VIDEOSTD_PAL_B) | VBI_VIDEOSTD_SET (VBI_VIDEOSTD_NTSC_M)), /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ 720, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_AMBIGUOUS_VIDEOSTD); #else assert_multiplex_raw (buffer, buffer_size, /* raw */ (uint8_t *) -1, /* raw_size */ 720, ANY_DATA_IDENTIFIER, (VBI_VIDEOSTD_SET_625_50 | VBI_VIDEOSTD_SET_525_60), /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ 720, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_AMBIGUOUS_VIDEOSTD); #endif free (buffer); } static void test_multiplex_raw_data_identifier (unsigned int data_identifier) { uint8_t *raw; unsigned int buffer_size; unsigned int raw_size; vbi_bool fixed_length; raw = (uint8_t *) xralloc (raw_size = 720); // EN 301 775 section 4.4.2. fixed_length = (data_identifier >= 0x10 && data_identifier <= 0x1F); for (buffer_size = 20 * 46 - 1; buffer_size <= 20 * 46 + 1; ++buffer_size) { uint8_t *buffer; vbi_bool exp_success; buffer = (uint8_t *) xmalloc (buffer_size); exp_success = (!fixed_length || 0 == buffer_size % 46); assert_multiplex_raw (buffer, buffer_size, raw, raw_size, data_identifier, ANY_VIDEOSTD, /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ raw_size, ANY_STUFFING, exp_success, VBI_ERR_BUFFER_OVERFLOW); free (buffer); buffer = (uint8_t *) -1; } free (raw); } static void test_multiplex_raw_unaligned_raw (void) { uint8_t *buffer; uint8_t *raw; unsigned int buffer_size; unsigned int raw_size; unsigned int i; buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); raw = (uint8_t *) xralloc (15 + (raw_size = 720)); for (i = 1; i < 16; ++i) { assert_multiplex_raw (buffer, buffer_size, raw + i, raw_size, ANY_DATA_IDENTIFIER, ANY_VIDEOSTD, /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ raw_size, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0); } free (raw); free (buffer); } static void test_multiplex_raw_null_raw_checks (void) { uint8_t *buffer; unsigned int buffer_size; buffer = (uint8_t *) xmalloc (buffer_size = 20 * 46); assert_multiplex_raw (buffer, buffer_size, /* raw */ NULL, /* raw_size */ 720, ANY_DATA_IDENTIFIER, ANY_VIDEOSTD, /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ 720, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_NO_RAW_DATA); free (buffer); } static void test_mr_packet_size (unsigned int buffer_size, unsigned int data_identifier, vbi_bool exp_success) { uint8_t *buffer; uint8_t *raw; unsigned int raw_size; if (0 == buffer_size) { buffer = (uint8_t *) -1; } else { buffer = (uint8_t *) xmalloc (buffer_size); } raw = (uint8_t *) xralloc (raw_size = 720); assert_multiplex_raw (buffer, buffer_size, raw, raw_size, data_identifier, ANY_VIDEOSTD, /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ raw_size, ANY_STUFFING, exp_success, VBI_ERR_BUFFER_OVERFLOW); free (raw); if (buffer_size > 0) free (buffer); } static void test_multiplex_raw_packet_size_checks (void) { unsigned int buffer_size; for (buffer_size = 0; buffer_size <= 1; ++buffer_size) { test_mr_packet_size (buffer_size, ANY_DATA_IDENTIFIER, EXPECT_FAILURE); } for (buffer_size = 2; buffer_size <= 45; ++buffer_size) { test_mr_packet_size (buffer_size, /* data_identifier */ 0x10, EXPECT_FAILURE); test_mr_packet_size (buffer_size, /* data_identifier */ 0x99, EXPECT_SUCCESS); } for (buffer_size = 46; buffer_size < 900; ++buffer_size) { test_mr_packet_size (buffer_size, /* data_identifier */ 0x99, EXPECT_SUCCESS); } for (buffer_size = 1 * 46; buffer_size < 20 * 46; buffer_size += 46) { test_mr_packet_size (buffer_size, /* data_identifier */ 0x10, EXPECT_SUCCESS); } } static void test_multiplex_raw_unaligned_packet (void) { uint8_t *raw; unsigned int raw_size; unsigned int i; raw = (uint8_t *) xralloc (raw_size = 720); for (i = 1; i < 16; ++i) { uint8_t *buffer; unsigned int buffer_size; buffer = (uint8_t *) xmalloc (i + (buffer_size = 20 * 46)); assert_multiplex_raw (buffer + i, buffer_size, raw, raw_size, ANY_DATA_IDENTIFIER, ANY_VIDEOSTD, /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ raw_size, ANY_STUFFING, EXPECT_SUCCESS, /* exp_errno */ 0); free (buffer); buffer = (uint8_t *) -1; } free (raw); } static void test_multiplex_raw_null_packet_checks (void) { assert_multiplex_raw (/* packet */ NULL, /* packet_size */ 20 * 46, /* raw */ (uint8_t *) -1, /* raw_size */ 720, ANY_DATA_IDENTIFIER, VBI_VIDEOSTD_SET_625_50, /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ 720, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_BUFFER_OVERFLOW); assert_multiplex_raw (/* packet */ (uint8_t *) -1, /* packet_size */ 0, /* raw */ (uint8_t *) -1, /* raw_size */ 720, ANY_DATA_IDENTIFIER, VBI_VIDEOSTD_SET_625_50, /* line */ 10, /* first_pixel_position */ 0, /* n_pixels_total */ 720, ANY_STUFFING, EXPECT_FAILURE, VBI_ERR_BUFFER_OVERFLOW); } static void test_multiplex_raw (void) { unsigned int i; test_multiplex_raw_null_packet_checks (); test_multiplex_raw_null_raw_checks (); test_multiplex_raw_packet_size_checks (); for (i = 0; i < N_ELEMENTS (data_identifiers); ++i) test_multiplex_raw_data_identifier (data_identifiers[i]); test_multiplex_raw_videostd_checks (); test_multiplex_raw_line_number_checks (); test_multiplex_raw_size_offsets (); test_multiplex_raw_unaligned_packet (); test_multiplex_raw_unaligned_raw (); } /* Test PES/TS multiplexer. */ static const vbi_sampling_par good_par_625 = { #if 3 == VBI_VERSION_MINOR /* videostd_set */ VBI_VIDEOSTD_SET_625_50, /* sample_format */ VBI_PIXFMT_Y8, /* sampling_rate */ 13500000, /* samples_per_line */ 720, /* bytes_per_line */ 720, /* offset */ 132, /* start */ { 7, 320 }, /* count */ { 17, 17 }, /* interlaced */ FALSE, /* synchronous */ TRUE #else /* scanning */ 625, /* sampling_format */ VBI_PIXFMT_YUV420, /* sampling_rate */ 13500000, /* bytes_per_line */ 720, /* offset */ 132, /* start */ { 7, 320 }, /* count */ { 17, 17 }, /* interlaced */ FALSE, /* synchronous */ TRUE #endif }; static const unsigned int packet_sizes [] = { 0, 12, 183, 184, 185, 1234, 65503, 65504, 65505, INT_MAX, ((unsigned int) INT_MAX) + 1, UINT_MAX, }; class DVBMux { protected: vbi_dvb_mux * _mx; public: DVBMux () { _mx = NULL; }; ~DVBMux () { vbi_dvb_mux_delete (_mx); }; operator vbi_dvb_mux* () const { return _mx; }; unsigned int get_min_pes_packet_size () { assert (NULL != _mx); return vbi_dvb_mux_get_min_pes_packet_size (_mx); }; unsigned int get_max_pes_packet_size () { assert (NULL != _mx); return vbi_dvb_mux_get_max_pes_packet_size (_mx); }; bool set_pes_packet_size (unsigned int min_size, unsigned int max_size) { assert (NULL != _mx); return vbi_dvb_mux_set_pes_packet_size (_mx, min_size, max_size); }; unsigned int get_data_identifier () { assert (NULL != _mx); return vbi_dvb_mux_get_data_identifier (_mx); }; bool set_data_identifier (unsigned int di) { assert (NULL != _mx); return vbi_dvb_mux_set_data_identifier (_mx, di); }; }; class DVBPESMux : public DVBMux { public: DVBPESMux (vbi_dvb_mux_cb * callback = NULL, void * user_data = NULL) { _mx = vbi_dvb_pes_mux_new (callback, user_data); assert (NULL != _mx); }; }; static void alloc_init_sliced (vbi_sliced ** sliced_p, unsigned int * n_lines_p) { const unsigned int max_lines = 2 * (23 - 7) + 1; vbi_sliced *sliced; unsigned int n_lines; unsigned int field; sliced = alloc_sliced (max_lines); n_lines = 0; for (field = 0; field < 2; ++field) { unsigned int j; for (j = 7; j < 15; ++j) { sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = field * 313 + j; } if (0 == field) sliced[n_lines].id = VBI_SLICED_VBI_625; else sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = field * 313 + 15; if (0 == field) sliced[n_lines].id = VBI_SLICED_VPS; else sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = field * 313 + 16; for (j = 17; j < 20; ++j) { sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = field * 313 + j; } if (1 == field) sliced[n_lines].id = VBI_SLICED_VBI_625; else sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = field * 313 + 20; if (0 == field) sliced[n_lines].id = VBI_SLICED_CAPTION_625; else sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = field * 313 + 21; sliced[n_lines].id = VBI_SLICED_TELETEXT_B_625; sliced[n_lines++].line = field * 313 + 22; if (0 == field) { sliced[n_lines].id = VBI_SLICED_WSS_625; sliced[n_lines++].line = 23; } } assert (n_lines == max_lines); *sliced_p = sliced; *n_lines_p = n_lines; } class DVBPESMuxTest : public DVBPESMux { /* vbi_dvb_mux_cor() parameters. */ uint8_t * _buffer; unsigned int _buffer_size; bool _have_buffer; bool _free_buffer; vbi_sliced * _sliced; unsigned int _sliced_lines; bool _have_sliced; bool _free_sliced; uint8_t * _raw; const vbi_sampling_par *_sp; bool _have_raw; bool _free_raw; vbi_service_set _service_mask; int64_t _pts; /* Test status. */ bool _cb_cmp; uint8_t * _cb_bp; uint8_t * _cb_ts_bp; int _cb_next_continuity_counter; void cmp_ts_feed (const vbi_bool exp_success, const int exp_errno, const unsigned int exp_consumed_lines); void cmp_ts_cor (const vbi_bool exp_success, const int exp_errno, const unsigned int exp_consumed_lines); void cmp_pes_feed (const vbi_bool exp_success, const int exp_errno, const unsigned int exp_consumed_lines); void copy_props (vbi_dvb_mux *mx) { vbi_bool success; assert (NULL != mx); success = vbi_dvb_mux_set_data_identifier (mx, get_data_identifier ()); assert (TRUE == success); success = vbi_dvb_mux_set_pes_packet_size (mx, get_min_pes_packet_size (), get_max_pes_packet_size ()); assert (TRUE == success); }; public: DVBPESMuxTest () { _have_buffer = false; _free_buffer = false; _have_sliced = false; _free_sliced = false; _have_raw = false; _free_raw = false; _service_mask = ALL_SERVICES; _pts = 0x1234567; }; ~DVBPESMuxTest () { if (_free_raw) free (_raw); if (_free_sliced) free (_sliced); if (_free_buffer) free (_buffer); }; bool ts_cb (const uint8_t *packet, unsigned int packet_size); bool pes_cb (const uint8_t *packet, unsigned int packet_size); void set_buffer (uint8_t *buffer, unsigned int n_bytes) { if (_free_buffer) free (_buffer); _buffer = buffer; _buffer_size = n_bytes; _have_buffer = true; _free_buffer = false; }; void set_buffer_size (unsigned int n_bytes) { if (_free_buffer) free (_buffer); if (n_bytes > 0) _buffer = (uint8_t *) xmalloc (n_bytes); else _buffer = NULL; _buffer_size = n_bytes; _have_buffer = true; _free_buffer = true; }; void set_sliced (vbi_sliced* sliced, unsigned int n_lines) { if (_free_sliced) free (_sliced); _sliced = sliced; _sliced_lines = n_lines; _have_sliced = true; _free_sliced = false; }; void set_raw (uint8_t *raw, const vbi_sampling_par *sp) { if (_free_raw) free (_raw); _raw = raw; _sp = sp; _have_raw = true; _free_raw = false; }; void set_sampling_par (const vbi_sampling_par *sp) { if (_free_raw) free (_raw); _raw = alloc_raw_frame (sp); _sp = sp; _have_raw = true; _free_raw = true; }; void set_service_mask (vbi_service_set mask) { _service_mask = mask; }; void set_pts (int64_t pts) { _pts = pts; }; void test (const vbi_bool exp_success, const int exp_errno, const unsigned int exp_consumed_lines); void test_line (const vbi_sampling_par *sp, const vbi_service_set service, const unsigned int line, const vbi_bool exp_success); void test_fail (const int exp_errno, const unsigned int exp_consumed_lines) { test (/* exp_success */ FALSE, exp_errno, exp_consumed_lines); }; void test_pass () { if (!_have_sliced) { alloc_init_sliced (&_sliced, &_sliced_lines); _have_sliced = true; _free_sliced = true; } test (/* exp_success */ TRUE, /* exp_errno */ 0, /* exp_consumed_lines */ _sliced_lines); }; }; bool DVBPESMuxTest::pes_cb (const uint8_t * packet, unsigned int packet_size) { assert (0 == packet_size % 184); assert (packet_size >= get_min_pes_packet_size ()); assert (packet_size <= get_max_pes_packet_size ()); if (_cb_cmp) { // Compare against the output of the PES mux coroutine. assert (0 == memcmp (_cb_bp, packet, packet_size)); } else { unsigned int n_sliced_dus; unsigned int n_raw_dus; unsigned int n_stuffing_dus; // For the TS feed test. memcpy (_cb_bp, packet, packet_size); assert_pes_packet_ok (&n_sliced_dus, &n_raw_dus, &n_stuffing_dus, packet, packet_size, get_data_identifier (), get_min_pes_packet_size (), get_max_pes_packet_size ()); assert (0 == n_sliced_dus); assert (0 == n_raw_dus); assert (n_stuffing_dus > 0); } _cb_bp += packet_size; return true; } static vbi_bool dvb_mux_pes_cb (vbi_dvb_mux * mx, void * user_data, const uint8_t * packet, unsigned int packet_size) { DVBPESMuxTest *tmx = (DVBPESMuxTest *) user_data; mx = mx; /* unused */ return tmx->pes_cb (packet, packet_size); } bool DVBPESMuxTest::ts_cb (const uint8_t * packet, unsigned int packet_size) { unsigned int payload_unit_start_indicator; unsigned int continuity_counter; assert (188 == packet_size); /* sync_byte [8], transport_error_indicator, payload_unit_start_indicator, transport_priority, PID [13] == 0x1234, transport_scrambling_control [2] == '00' (not scrambled), adaptation_field_control [2] == '01' (payload only, no adaption field), continuity_counter [4] */ assert (0x47 == packet[0]); assert (0x12 == (packet[1] & ~0x40)); assert (0x34 == packet[2]); assert (0x10 == (packet[3] & ~0x0F)); payload_unit_start_indicator = !!(packet[1] & 0x40); assert ((0x00 == packet[4] && 0x00 == packet[5] && 0x01 == packet[6] && 0xBD == packet[7]) == payload_unit_start_indicator); continuity_counter = packet[3] & 0x0F; if (-1 != _cb_next_continuity_counter) { assert ((unsigned int) _cb_next_continuity_counter == continuity_counter); } _cb_next_continuity_counter = (continuity_counter + 1) & 0xF; if (_cb_cmp) { // Compare against the output of the PES mux coroutine. assert (0 == memcmp (_cb_bp, packet + 4, 184)); _cb_bp += 184; // Compare against the output of the TS mux coroutine. assert (0 == memcmp (_cb_ts_bp, packet, 188)); _cb_ts_bp += 188; } return true; } static vbi_bool dvb_mux_ts_cb (vbi_dvb_mux * mx, void * user_data, const uint8_t * packet, unsigned int packet_size) { DVBPESMuxTest *tmx = (DVBPESMuxTest *) user_data; mx = mx; /* unused */ return tmx->ts_cb (packet, packet_size); } void DVBPESMuxTest::test (const vbi_bool exp_success, const int exp_errno, const unsigned int exp_consumed_lines) { uint8_t *rand_buffer; uint8_t *ts_buffer; uint8_t *ts_rand_buffer; unsigned int ts_buffer_size; if (!_have_buffer) set_buffer_size (4 << 10); if (!_have_sliced) { alloc_init_sliced (&_sliced, &_sliced_lines); _have_sliced = true; _free_sliced = true; } if (!_have_raw) set_sampling_par (&good_par_625); if (NULL != _buffer) { rand_buffer = (uint8_t *) xralloc (_buffer_size); memcpy (_buffer, rand_buffer, _buffer_size); ts_buffer_size = _buffer_size * 188 / 184; ts_buffer = (uint8_t *) xmalloc (ts_buffer_size); ts_rand_buffer = (uint8_t *) xralloc (ts_buffer_size); memcpy (ts_buffer, ts_rand_buffer, ts_buffer_size); } else { rand_buffer = NULL; ts_buffer = NULL; ts_rand_buffer = NULL; ts_buffer_size = 0; } uint8_t *p = _buffer; unsigned int p_left = _buffer_size; const vbi_sliced *s = _sliced; unsigned int s_left = _sliced_lines; vbi_bool success = vbi_dvb_mux_cor (*this, &p, &p_left, &s, &s_left, _service_mask, _raw, _sp, _pts); if (NULL == _buffer || 0 == _buffer_size || NULL == _sliced || 0 == _sliced_lines) assert (FALSE == success); else assert (exp_success == success); unsigned int pes_bytes_out = _buffer_size - p_left; assert (_buffer + pes_bytes_out == p); assert (_sliced + exp_consumed_lines == s); assert (_sliced_lines - exp_consumed_lines == s_left); if (success) { unsigned int n_sliced_dus; unsigned int n_raw_dus; unsigned int n_stuffing_dus; assert_pes_packet_ok (&n_sliced_dus, &n_raw_dus, &n_stuffing_dus, _buffer, pes_bytes_out, get_data_identifier (), get_min_pes_packet_size (), get_max_pes_packet_size ()); assert (_sliced_lines >= n_sliced_dus); if (0 == _service_mask) { assert (0 == n_sliced_dus); assert (0 == n_raw_dus); } if (NULL == _raw || NULL == _sp) assert (0 == n_raw_dus); assert_pes_conversion_ok (_buffer, pes_bytes_out, _sliced, _sliced_lines, _service_mask, _pts); } else { (void) exp_errno; // assert (exp_errno == errno); assert (0 == pes_bytes_out); } if (NULL != _buffer) { assert (0 == memcmp (p, rand_buffer + pes_bytes_out, _buffer_size - pes_bytes_out)); } if (NULL != _buffer && _buffer_size > 0) { vbi_dvb_mux *mx; unsigned int exp_bytes_out; // Verify that the PES callback gives the same result. mx = vbi_dvb_pes_mux_new (/* callback */ dvb_mux_pes_cb, /* user_data */ this); assert (NULL != mx); copy_props (mx); _cb_bp = _buffer; _cb_cmp = (NULL != _sliced && _sliced_lines > 0); success = vbi_dvb_mux_feed (mx, _sliced, _sliced_lines, _service_mask, _raw, _sp, _pts); assert (exp_success == success); if (_cb_cmp) { exp_bytes_out = pes_bytes_out; } else if (success) { // Stuffing. exp_bytes_out = get_min_pes_packet_size (); } else { exp_bytes_out = 0; } assert (_cb_bp == _buffer + exp_bytes_out); if (!success) { (void) exp_errno; // assert (exp_errno == errno); } vbi_dvb_mux_delete (mx); } { vbi_dvb_mux *mx; // Test the TS coroutine. mx = vbi_dvb_ts_mux_new (/* pid */ 0x1234, /* callback */ NULL, /* user_data */ NULL); assert (NULL != mx); copy_props (mx); p = ts_buffer; p_left = ts_buffer_size; s = _sliced; s_left = _sliced_lines; success = vbi_dvb_mux_cor (mx, &p, &p_left, &s, &s_left, _service_mask, _raw, _sp, _pts); if (NULL == ts_buffer || 0 == ts_buffer_size || NULL == _sliced || 0 == _sliced_lines) assert (FALSE == success); else assert (exp_success == success); unsigned int ts_bytes_out = ts_buffer_size - p_left; assert (pes_bytes_out * 188 / 184 == ts_bytes_out); assert (ts_buffer + ts_bytes_out == p); assert (_sliced + exp_consumed_lines == s); assert (_sliced_lines - exp_consumed_lines == s_left); if (!success) { (void) exp_errno; // assert (exp_errno == errno); assert (0 == ts_bytes_out); } if (NULL != ts_buffer) { assert (0 == memcmp (p, ts_rand_buffer + ts_bytes_out, ts_buffer_size - ts_bytes_out)); } vbi_dvb_mux_delete (mx); } if (NULL != _buffer && _buffer_size > 0) { vbi_dvb_mux *mx; unsigned int exp_bytes_out; /* Verify that the TS callback and the TS coroutine give the same result as the PES coroutine. */ mx = vbi_dvb_ts_mux_new (/* pid */ 0x1234, /* callback */ dvb_mux_ts_cb, /* user_data */ this); assert (NULL != mx); copy_props (mx); _cb_bp = _buffer; _cb_cmp = (NULL != _sliced && _sliced_lines > 0); _cb_ts_bp = ts_buffer; _cb_next_continuity_counter = -1; success = vbi_dvb_mux_feed (mx, _sliced, _sliced_lines, _service_mask, _raw, _sp, _pts); assert (exp_success == success); if (_cb_cmp) { exp_bytes_out = pes_bytes_out; } else if (success) { // Stuffing. exp_bytes_out = get_min_pes_packet_size (); } else { exp_bytes_out = 0; } assert (_cb_bp == _buffer + pes_bytes_out); assert (_cb_ts_bp == ts_buffer + pes_bytes_out * 188 / 184); if (!success) { (void) exp_errno; // assert (exp_errno == errno); } vbi_dvb_mux_delete (mx); } free (ts_rand_buffer); free (ts_buffer); free (rand_buffer); } void DVBPESMuxTest::test_line (const vbi_sampling_par *sp, const vbi_service_set service, const unsigned int line, const vbi_bool exp_success) { if (!_free_sliced) { _sliced = alloc_sliced (1); _sliced_lines = 1; _have_sliced = true; _free_sliced = true; } _sliced[0].id = service; _sliced[0].line = line; set_sampling_par (sp); test (exp_success, VBI_ERR_LINE_NUMBER, /* exp_consumed_lines */ exp_success ? 1 : 0); }; static void test_dvb_mux_cor_partial_reads_and_reset (unsigned int pid) { static const unsigned int steps [] = { 1, 46, 184, 188, 999999, INT_MAX, (unsigned int) INT_MAX + 1, UINT_MAX }; vbi_dvb_mux *mx; vbi_sliced *sliced; const vbi_sliced *s; uint8_t *buffer1; uint8_t *buffer2; uint8_t *p; uint8_t *raw; unsigned int buffer_size; unsigned int n_lines; unsigned int p_left; unsigned int s_left; unsigned int i; vbi_bool success; if (0 == pid) { mx = vbi_dvb_pes_mux_new (/* callback */ NULL, /* user_data */ NULL); buffer_size = 68 * 46; } else { mx = vbi_dvb_ts_mux_new (pid, /* callback */ NULL, /* user_data */ NULL); buffer_size = 68 * 46 * 188 / 184; } assert (NULL != mx); alloc_init_sliced (&sliced, &n_lines); raw = alloc_raw_frame (&good_par_625); buffer1 = (uint8_t *) xralloc (buffer_size); buffer2 = (uint8_t *) xmalloc (buffer_size); p = buffer1; p_left = buffer_size; s = sliced; s_left = n_lines; success = vbi_dvb_mux_cor (mx, &p, &p_left, &s, &s_left, ALL_SERVICES, raw, &good_par_625, /* pts */ 0x1234567); assert (TRUE == success); assert (0 == p_left); assert (0 == s_left); for (i = 0; i < N_ELEMENTS (steps); ++i) { p = buffer2; p_left = buffer_size / 2; s = sliced; s_left = n_lines; success = vbi_dvb_mux_cor (mx, &p, &p_left, &s, &s_left, ALL_SERVICES, raw, &good_par_625, /* pts */ 0x1234567); assert (TRUE == success); // Discard the second half. vbi_dvb_mux_reset (mx); memset_rand (buffer2, buffer_size); p = buffer2; s = sliced; s_left = n_lines; do { p_left = steps[i]; success = vbi_dvb_mux_cor (mx, &p, &p_left, &s, &s_left, ALL_SERVICES, raw, &good_par_625, /* pts */ 0x1234567); assert (TRUE == success); } while (s_left > 0); assert (buffer2 + buffer_size == p); if (0 == pid) { assert (0 == memcmp (buffer1, buffer2, buffer_size)); } else { unsigned int j; for (j = 0; j < buffer_size; j += 188) { assert (buffer1[j + 0] == buffer2[j + 0]); assert (buffer1[j + 1] == buffer2[j + 1]); assert (buffer1[j + 2] == buffer2[j + 2]); /* Ignore continuity_counter change due to the reset. (The function intentionally resets not to zero.) */ assert (0 == ((buffer1[j + 3] ^ buffer2[j + 3]) & 0xF0)); assert (0 == memcmp (buffer1 + j + 4, buffer2 + j + 4, 184)); } assert (j == buffer_size); } } free (buffer2); free (buffer1); free (raw); free (sliced); vbi_dvb_mux_delete (mx); } static void test_dvb_mux_cor_service_mask (void) { DVBPESMuxTest mx; mx.set_service_mask (VBI_SLICED_VPS | VBI_SLICED_WSS_625); mx.test_pass (); mx.set_service_mask (0); mx.test_pass (); } static void test_dvb_mux_cor_pts (void) { static const int64_t ptss [] = { (int64_t) 0x8000000000000000ll, -1, 0, 0x7FFFFFFFFFFFFFFFll, }; DVBPESMuxTest mx; unsigned int i; for (i = 0; i < N_ELEMENTS (ptss); ++i) { mx.set_pts (ptss[i]); mx.test_pass (); } } static void test_mx_raw_offset (unsigned int bytes_per_line, unsigned int samples_per_line, unsigned int offset) { DVBPESMuxTest mx; vbi_sampling_par sp; sp = good_par_625; sp.bytes_per_line = bytes_per_line; sp.sp_samples_per_line = samples_per_line; sp.offset = offset; if (offset < 132 || (uint64_t) offset + samples_per_line > 132 + 720 || 0 == samples_per_line || samples_per_line > bytes_per_line) { mx.set_sliced (/* sliced */ (vbi_sliced *) -1, /* sliced_lines */ 17); mx.set_raw ((uint8_t *) -1, &sp); mx.test_fail (VBI_ERR_SAMPLING_PAR, /* exp_consumed_lines */ 0); } else if (bytes_per_line < INT_MAX) { mx.set_sampling_par (&sp); mx.test_pass (); } } static void test_dvb_mux_cor_sampling_parameter_checks (void) { DVBPESMuxTest mx; vbi_sampling_par sp; unsigned int i; // FIXME: Test vbi_valid_sampling_par_log(). #if 3 == VBI_VERSION_MINOR sp = good_par_625; sp.videostd_set = 0; mx.set_sampling_par (&sp); mx.test_fail (VBI_ERR_SAMPLING_PAR, /* exp_consumed_lines */ 0); sp = good_par_625; sp.videostd_set = VBI_VIDEOSTD_SET_525_60; mx.set_sampling_par (&sp); mx.test_fail (VBI_ERR_SAMPLING_PAR, /* exp_consumed_lines */ 0); sp = good_par_625; sp.videostd_set = (VBI_VIDEOSTD_SET (VBI_VIDEOSTD_PAL_B) | VBI_VIDEOSTD_SET (VBI_VIDEOSTD_NTSC_M)); mx.set_sampling_par (&sp); mx.test_fail (VBI_ERR_SAMPLING_PAR, /* exp_consumed_lines */ 0); #else sp = good_par_625; sp.scanning = 0; mx.set_sampling_par (&sp); mx.test_fail (VBI_ERR_SAMPLING_PAR, /* exp_consumed_lines */ 0); sp = good_par_625; sp.scanning = 525; mx.set_sampling_par (&sp); mx.test_fail (VBI_ERR_SAMPLING_PAR, /* exp_consumed_lines */ 0); #endif sp = good_par_625; sp.sp_sample_format = VBI_PIXFMT_YUYV; mx.set_sampling_par (&sp); mx.test_fail (VBI_ERR_SAMPLING_PAR, /* exp_consumed_lines */ 0); sp = good_par_625; sp.sampling_rate = 27000000; mx.set_sampling_par (&sp); mx.test_fail (VBI_ERR_SAMPLING_PAR, /* exp_consumed_lines */ 0); sp = good_par_625; sp.synchronous = FALSE; mx.set_sampling_par (&sp); mx.test_fail (VBI_ERR_SAMPLING_PAR, /* exp_consumed_lines */ 0); for (i = 0; i < N_ELEMENTS (raw_offsets); ++i) { unsigned int j; for (j = 0; j < N_ELEMENTS (raw_offsets); ++j) { unsigned int k; for (k = 0; k < N_ELEMENTS (raw_offsets); ++k) { test_mx_raw_offset (raw_offsets[i], raw_offsets[j], raw_offsets[k]); } } } sp = good_par_625; sp.interlaced = TRUE; mx.set_sampling_par (&sp); mx.test_pass (); } static void test_dvb_mux_cor_unaligned_raw (void) { DVBPESMuxTest mx; uint8_t *raw; unsigned int n_lines; unsigned int size; unsigned int i; n_lines = good_par_625.count[0] + good_par_625.count[1]; size = 15 + n_lines * good_par_625.bytes_per_line; raw = (uint8_t *) xralloc (size); for (i = 1; i < 16; ++i) { mx.set_raw (raw + i, &good_par_625); mx.test_pass (); } free (raw); } static void test_dvb_mux_cor_null_raw_or_sp_checks (void) { DVBPESMuxTest mx; uint8_t *raw; mx.set_raw (/* raw */ NULL, &good_par_625); mx.test_fail (VBI_ERR_NO_RAW_DATA, /* exp_consumed_lines */ 15 - 7); raw = alloc_raw_frame (&good_par_625); mx.set_raw (raw, /* sp */ NULL); mx.test_fail (VBI_ERR_NO_RAW_DATA, /* exp_consumed_lines */ 15 - 7); free (raw); } static void test_dvb_mux_cor_sp_line_number_checks (void) { DVBPESMuxTest mx; vbi_sampling_par sp; sp = good_par_625; sp.count[0] = 0; mx.test_line (&sp, VBI_SLICED_VBI_625, 7, /* exp_success */ FALSE); sp = good_par_625; sp.count[1] = 0; mx.test_line (&sp, VBI_SLICED_VBI_625, 320, /* exp_success */ FALSE); sp = good_par_625; sp.start[0] = 8; sp.count[0] = 22 - 8 + 1; sp.start[1] = 313 + 8; sp.count[1] = 22 - 8 + 1; mx.test_line (&sp, VBI_SLICED_VBI_625, 7, /* exp_success */ FALSE); mx.test_line (&sp, VBI_SLICED_VBI_625, 8, /* exp_success */ TRUE); mx.test_line (&sp, VBI_SLICED_VBI_625, 22, /* exp_success */ TRUE); mx.test_line (&sp, VBI_SLICED_VBI_625, 23, /* exp_success */ FALSE); mx.test_line (&sp, VBI_SLICED_VBI_625, 313 + 7, /* exp_success */ FALSE); mx.test_line (&sp, VBI_SLICED_VBI_625, 313 + 8, /* exp_success */ TRUE); mx.test_line (&sp, VBI_SLICED_VBI_625, 313 + 22, /* exp_success */ TRUE); mx.test_line (&sp, VBI_SLICED_VBI_625, 313 + 23, /* exp_success */ FALSE); } static void test_dvb_mux_cor_line_number_checks (void) { DVBPESMuxTest mx; unsigned int i; mx.test_line (&good_par_625, /* service */ 0, /* line (bad) */ 100, /* exp_success */ TRUE); for (i = 0; i <= 31; ++i) { unsigned int j; for (j = 0; j < N_ELEMENTS (good_services); ++j) { vbi_service_set service = good_services[j]; mx.test_line (&good_par_625, service, /* line */ i, is_correct_line (service, 0, i)); mx.test_line (&good_par_625, service, /* line */ i + 313, is_correct_line (service, 1, i)); } vbi_service_set service = VBI_SLICED_VBI_625; mx.test_line (&good_par_625, service, /* line */ i, is_correct_line (service, 0, i)); mx.test_line (&good_par_625, service, /* line */ i + 313, is_correct_line (service, 1, i)); } for (i = 0; i < N_ELEMENTS (bad_line_numbers); ++i) { unsigned int j; for (j = 0; j < N_ELEMENTS (good_services); ++j) { vbi_service_set service = good_services[j]; mx.test_line (&good_par_625, service, bad_line_numbers[i], /* correct */ 0 == service); } mx.test_line (&good_par_625, VBI_SLICED_VBI_625, bad_line_numbers[i], /* correct */ false); } } static void test_dvb_mux_cor_service_checks (void) { DVBPESMuxTest mx; vbi_sliced *sliced; unsigned int n_lines; unsigned int i; sliced = alloc_sliced (n_lines = 8); for (i = 0; i < 6; ++i) { sliced[i].id = VBI_SLICED_TELETEXT_B_625; sliced[i].line = i + 7; } sliced[7].id = VBI_SLICED_TELETEXT_B_625; sliced[7].line = 320; for (i = 0; i < N_ELEMENTS (all_services); ++i) { vbi_service_set service; unsigned int line; service = all_services[i]; if (service & VBI_SLICED_VPS) line = 16; else if (service & VBI_SLICED_CAPTION_625) line = 21; else if (service & VBI_SLICED_WSS_625) line = 23; else line = 13; sliced[6].id = service; sliced[6].line = line; mx.set_sliced (sliced, n_lines); if (VBI_SLICED_VBI_625 == service || is_good_service (service)) { mx.test_pass (); } else { mx.test_fail (VBI_ERR_INVALID_SERVICE, /* exp_consumed_lines */ 6); } } free (sliced); } static void test_dvb_mux_cor_line_order_checks (void) { DVBPESMuxTest mx; vbi_sliced *sliced; unsigned int n_lines; alloc_init_sliced (&sliced, &n_lines); assert (VBI_SLICED_TELETEXT_B_625 == sliced[1].id); assert (8 == sliced[1].line); assert (VBI_SLICED_TELETEXT_B_625 == sliced[2].id); assert (9 == sliced[2].line); assert (VBI_SLICED_TELETEXT_B_625 == sliced[3].id); assert (10 == sliced[3].line); sliced[1].line = 0; sliced[2].line = 0; mx.set_sliced (sliced, n_lines); mx.test_pass (); sliced[1].line = 10; sliced[2].line = 0; mx.set_sliced (sliced, n_lines); mx.test_fail (VBI_ERR_LINE_ORDER, 3); sliced[1].line = 8; sliced[2].line = 8; mx.set_sliced (sliced, n_lines); mx.test_fail (VBI_ERR_LINE_ORDER, 2); sliced[1].line = 55; sliced[2].line = 9; mx.set_sliced (sliced, n_lines); mx.test_fail (VBI_ERR_LINE_ORDER, 2); sliced[1].id = VBI_SLICED_TELETEXT_B_625; sliced[1].line = 11; sliced[2].id = VBI_SLICED_VBI_625; sliced[2].line = 9; mx.set_sliced (sliced, n_lines); mx.test_fail (VBI_ERR_LINE_ORDER, 2); sliced[1].id = VBI_SLICED_VBI_625; sliced[1].line = 11; sliced[2].id = VBI_SLICED_TELETEXT_B_625; sliced[2].line = 9; mx.set_sliced (sliced, n_lines); mx.test_fail (VBI_ERR_LINE_ORDER, 2); sliced[1].id = VBI_SLICED_TELETEXT_B_625; sliced[1].line = 8; sliced[2].id = VBI_SLICED_TELETEXT_B_625; sliced[2].line = 9; assert (VBI_SLICED_TELETEXT_B_625 == sliced[17 + 1].id); assert (313 + 8 == sliced[17 + 1].line); assert (VBI_SLICED_TELETEXT_B_625 == sliced[17 + 2].id); assert (313 + 9 == sliced[17 + 2].line); sliced[17 + 1].line = 313 + 10; mx.set_sliced (sliced, n_lines); mx.test_fail (VBI_ERR_LINE_ORDER, 17 + 2); free (sliced); } static void test_dvb_mux_cor_packet_overflow_checks (void) { DVBPESMuxTest mx; vbi_sliced *sliced; unsigned int max_size; unsigned int n_lines; unsigned int i; mx.set_pes_packet_size (0, UINT_MAX); max_size = mx.get_max_pes_packet_size (); // Cannot fit because the header takes another 46 bytes. n_lines = max_size / 46; sliced = alloc_sliced (n_lines); for (i = 0; i < n_lines; ++i) { sliced[i].id = VBI_SLICED_TELETEXT_B_625; sliced[i].line = 0; } mx.set_buffer_size ((n_lines + 1) * 46); mx.set_sliced (sliced, n_lines); mx.test_fail (VBI_ERR_BUFFER_OVERFLOW, /* exp_consumed_lines */ n_lines - 1); free (sliced); } static void test_dvb_mux_cor_null_sliced_checks (void) { DVBPESMuxTest mx; vbi_sliced *sliced; unsigned int n_lines; alloc_init_sliced (&sliced, &n_lines); mx.set_sliced (NULL, n_lines); mx.test (/* exp_success */ TRUE, /* exp_errno */ 0, /* exp_consumed_lines */ 0); mx.set_sliced (sliced, /* n_lines */ 0); mx.test_pass (); free (sliced); } static void test_dvb_mux_cor_unaligned_packet (void) { DVBPESMuxTest mx; uint8_t *buffer; unsigned int buffer_size; unsigned int i; buffer_size = 4 << 10; buffer = (uint8_t *) xmalloc (15 + buffer_size); for (i = 1; i < 16; ++i) { mx.set_buffer (buffer + i, buffer_size); mx.test_pass (); } free (buffer); } static void test_dvb_mux_cor_null_packet_checks (void) { DVBPESMuxTest mx; mx.set_buffer (NULL, /* buffer_size */ 4 << 10); mx.test_fail (VBI_ERR_BUFFER_OVERFLOW, /* exp_consumed_lines */ 0); } static void test_dvb_mux_feed_no_callback_checks (void) { vbi_dvb_mux *mx; vbi_sliced *sliced; uint8_t *raw; unsigned int n_lines; vbi_bool success; mx = vbi_dvb_pes_mux_new (/* callback */ NULL, /* user_data */ NULL); alloc_init_sliced (&sliced, &n_lines); raw = alloc_raw_frame (&good_par_625); success = vbi_dvb_mux_feed (mx, sliced, n_lines, ALL_SERVICES, raw, &good_par_625, /* pts */ 0x1234567); assert (FALSE == success); // XXX Later // assert (VBI_ERR_NO_CALLBACK == errno); free (raw); free (sliced); vbi_dvb_mux_delete (mx); } static void test_dvb_mux_data_identifier_accessors (void) { DVBPESMuxTest mx; unsigned int di_tested; unsigned int i; // Default. assert (0x10 == mx.get_data_identifier ()); for (i = 0; i < 300; ++i) { unsigned int old_di; unsigned int new_di; vbi_bool success; old_di = 0x1F ^ (i & 0xF); mx.set_data_identifier (old_di); success = mx.set_data_identifier (i); // EN 300 775 table 2. assert (success == ((i >= 0x10 && i <= 0x1F) || (i >= 0x99 && i <= 0x9B))); new_di = mx.get_data_identifier (); if (success) { assert (i == new_di); } else { // No change. assert (old_di == new_di); } } di_tested = 0; for (i = 0; i < N_ELEMENTS (data_identifiers); ++i) { unsigned int di; di = data_identifiers[i]; if (!mx.set_data_identifier (di)) continue; di_tested |= 1 << (di >= 0x99); mx.test_pass (); } assert (3 == di_tested); } static void test_mx_packet_size (unsigned int min_size, unsigned int max_size) { DVBPESMuxTest mx; uint8_t *buffer; vbi_sliced *sliced; unsigned int n_lines; vbi_bool success; success = mx.set_pes_packet_size (min_size, max_size); assert (TRUE == success); buffer = (uint8_t *) xmalloc (max_size); alloc_init_sliced (&sliced, &n_lines); if (max_size <= 184) n_lines = 1; mx.set_buffer (buffer, max_size); mx.set_sliced (sliced, n_lines); mx.test_pass (); free (sliced); free (buffer); } static void test_dvb_mux_packet_size_accessors (void) { DVBPESMuxTest mx; unsigned int min; unsigned int max; unsigned int i; min = mx.get_min_pes_packet_size (); max = mx.get_max_pes_packet_size (); // Defaults. assert (184 == min); assert (65504 == max); for (i = 0; i < N_ELEMENTS (packet_sizes); ++i) { unsigned int j; for (j = 0; j < N_ELEMENTS (packet_sizes); ++j) { vbi_bool success; success = mx.set_pes_packet_size (packet_sizes[i], packet_sizes[j]); assert (TRUE == success); min = mx.get_min_pes_packet_size (); max = mx.get_max_pes_packet_size (); assert (0 == min % 184); assert (0 == max % 184); assert (min >= 184); assert (max <= 65504); assert (min <= max); if (packet_sizes[i] <= 65504) assert (min >= packet_sizes[i]); if (packet_sizes[j] >= min) // sic assert (max <= packet_sizes[j]); } } test_mx_packet_size (184, 184); test_mx_packet_size (184, 65504); test_mx_packet_size (65504, 65504); } static void test_dvb_mux_new_pid_checks (void) { unsigned int i; for (i = 0x0000; i <= 0x2000; ++i) { vbi_dvb_mux *mx; mx = vbi_dvb_ts_mux_new (/* pid */ i, /* callback */ NULL, /* user_data */ NULL); assert ((NULL != mx) == (i >= 0x0010 && i <= 0x1FFE)); vbi_dvb_mux_delete (mx); mx = (vbi_dvb_mux *) -1; } assert (NULL == vbi_dvb_ts_mux_new (0x123456, NULL, NULL)); assert (NULL == vbi_dvb_ts_mux_new (UINT_MAX, NULL, NULL)); } static void test_dvb_ts_mux_malloc (void) { vbi_dvb_mux *mx; mx = vbi_dvb_ts_mux_new (/* pid */ 0x1234, /* callback */ NULL, /* user_data */ NULL); assert (ENOMEM == errno); assert (NULL == mx); } static void test_dvb_pes_mux_malloc (void) { vbi_dvb_mux *mx; mx = vbi_dvb_pes_mux_new (/* callback */ NULL, /* user_data */ NULL); assert (ENOMEM == errno); assert (NULL == mx); } static void test_dvb_mux (void) { test_malloc (test_dvb_pes_mux_malloc, /* n_cycles */ 2); test_malloc (test_dvb_ts_mux_malloc, /* n_cycles */ 2); test_dvb_mux_new_pid_checks (); test_dvb_mux_packet_size_accessors (); test_dvb_mux_data_identifier_accessors (); test_dvb_mux_feed_no_callback_checks (); test_dvb_mux_cor_null_packet_checks (); test_dvb_mux_cor_null_sliced_checks (); test_dvb_mux_cor_packet_overflow_checks (); test_dvb_mux_cor_line_order_checks (); test_dvb_mux_cor_service_checks (); test_dvb_mux_cor_line_number_checks (); test_dvb_mux_cor_null_raw_or_sp_checks (); test_dvb_mux_cor_sampling_parameter_checks (); test_dvb_mux_cor_sp_line_number_checks (); test_dvb_mux_cor_unaligned_packet (); test_dvb_mux_cor_unaligned_raw (); test_dvb_mux_cor_service_mask (); test_dvb_mux_cor_partial_reads_and_reset (/* pid */ 0); test_dvb_mux_cor_partial_reads_and_reset (/* pid */ 0x1234); test_dvb_mux_cor_pts (); } int main (void) { test_multiplex_sliced (); test_multiplex_raw (); test_dvb_mux (); return 0; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/test-hamm.cc000066400000000000000000000170771476363111200157600ustar00rootroot00000000000000/* * libzvbi -- Error correction functions unit test * * Copyright (C) 2003, 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-hamm.cc,v 1.5 2008-03-01 07:35:55 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include /* mrand48() */ #include /* memset() */ #include "src/hamm.h" #ifdef _WIN32 long int mrand48(void) { return rand() > RAND_MAX / 2 ? rand() : -rand(); } #endif namespace vbi { static inline unsigned int rev8 (uint8_t c) { return vbi_rev8 (c); }; static inline unsigned int rev8 (const uint8_t* p) { return vbi_rev8 (*p); }; static inline unsigned int rev16 (uint16_t c) { return vbi_rev16 (c); } static inline unsigned int rev16 (const uint8_t* p) { return vbi_rev16p (p); }; static inline unsigned int par8 (uint8_t c) { return vbi_par8 (c); }; static inline void par (uint8_t* p, unsigned int n) { vbi_par (p, n); }; static inline void par (uint8_t* begin, uint8_t* end) { vbi_par (begin, end - begin); }; static inline int unpar8 (uint8_t c) { return vbi_unpar8 (c); }; static inline int unpar (uint8_t* p, unsigned int n) { return vbi_unpar (p, n); }; static inline int unpar (uint8_t* begin, uint8_t* end) { return vbi_unpar (begin, end - begin); }; static inline unsigned int ham8 (unsigned int c) { return vbi_ham8 (c); }; static inline void ham16 (uint8_t* p, uint8_t c) { p[0] = vbi_ham8 (c); p[1] = vbi_ham8 (c >> 4); } static inline int unham8 (uint8_t c) { return vbi_unham8 (c); }; static inline int unham16 (uint16_t c) { return ((int) _vbi_hamm8_inv[c & 255]) | ((int) _vbi_hamm8_inv[c >> 8] << 4); }; static inline int unham16 (uint8_t* p) { return vbi_unham16p (p); }; static inline void ham24 (uint8_t* p, unsigned int c) { vbi_ham24p (p, c); } static inline int unham24 (uint8_t* p) { return vbi_unham24p (p); }; }; static unsigned int parity (unsigned int n) { unsigned int sh; for (sh = sizeof (n) * 8 / 2; sh > 0; sh >>= 1) n ^= n >> sh; return n & 1; } #define BC(n) ((n) * (unsigned int) 0x0101010101010101ULL) static unsigned int population_count (unsigned int n) { n -= (n >> 1) & BC (0x55); n = (n & BC (0x33)) + ((n >> 2) & BC (0x33)); n = (n + (n >> 4)) & BC (0x0F); return (n * BC (0x01)) >> (sizeof (unsigned int) * 8 - 8); } unsigned int hamming_distance (unsigned int a, unsigned int b) { return population_count (a ^ b); } static void test_rev (void) { unsigned int i; for (i = 0; i < 10000; ++i) { unsigned int n = (i < 256) ? i : (unsigned int) mrand48 (); uint8_t buf[4] = { (uint8_t) n, (uint8_t) (n >> 8), (uint8_t) (n >> 16), 0xA5 }; unsigned int r; unsigned int j; for (r = 0, j = 0; j < 8; ++j) if (n & (0x01 << j)) r |= 0x80 >> j; assert (r == vbi::rev8 (n)); assert (vbi::rev8 (n) == vbi::rev8 (buf)); assert (vbi::rev16 (n) == vbi::rev16 (buf)); } } static void test_par_unpar (void) { unsigned int i; for (i = 0; i < 10000; ++i) { unsigned int n = (i < 256) ? i : (unsigned int) mrand48 (); uint8_t buf[4] = { (uint8_t) n, (uint8_t) (n >> 8), (uint8_t) (n >> 16), 0xA5 }; if (parity (n & 0xFF)) assert (vbi::unpar8 (n) == (int)(n & 127)); else assert (-1 == vbi::unpar8 (n)); assert (vbi::unpar8 (vbi::par8 (n)) >= 0); vbi::par (buf, sizeof (buf)); assert (vbi::unpar (buf, sizeof (buf)) >= 0); assert (0 == ((buf[0] | buf[1] | buf[2]) & 0x80)); buf[1] = vbi::par8 (buf[1]); buf[2] = buf[1] ^ 0x80; assert (vbi::unpar (buf, sizeof (buf)) < 0); assert (buf[2] == (buf[1] & 0x7F)); } } static void test_ham8_ham16_unham8_unham16 (void) { unsigned int i; for (i = 0; i < 10000; ++i) { unsigned int n = (i < 256) ? i : (unsigned int) mrand48 (); uint8_t buf[4] = { (uint8_t) n, (uint8_t) (n >> 8), (uint8_t) (n >> 16), 0xA5 }; unsigned int A, B, C, D; int d; A = parity (n & 0xA3); B = parity (n & 0x8E); C = parity (n & 0x3A); D = parity (n & 0xFF); d = (+ ((n & 0x02) >> 1) + ((n & 0x08) >> 2) + ((n & 0x20) >> 3) + ((n & 0x80) >> 4)); if (A && B && C) { unsigned int nn; nn = D ? n : (n ^ 0x40); assert (vbi::ham8 (d) == (nn & 255)); assert (vbi::unham8 (nn) == d); } else if (!D) { unsigned int nn; int dd; dd = vbi::unham8 (n); assert (dd >= 0 && dd <= 15); nn = vbi::ham8 (dd); assert (hamming_distance (n & 255, nn) == 1); } else { assert (-1 == vbi::unham8 (n)); } vbi::ham16 (buf, n); assert (vbi::unham16 (buf) == (int)(n & 255)); } } static void test_ham24 (unsigned int val) { uint8_t buf[4]; unsigned int A, B, C, D, E, F; unsigned int n; memset (buf, 0xA5, sizeof (buf)); vbi::ham24 (buf, val); assert (0xA5 == buf[3]); assert ((int)(val & ((1 << 18) - 1)) == vbi::unham24 (buf)); n = buf[0] | (buf[1] << 8) | (buf[2] << 16); A = parity (n & 0x555555); B = parity (n & 0x666666); C = parity (n & 0x787878); D = parity (n & 0x007F80); E = parity (n & 0x7F8000); F = parity (n & 0xFFFFFF); assert (A && B && C && D && E && F); } static void test_unham24 (void) { unsigned int i; for (i = 0; i < (1 << 24); ++i) { uint8_t buf[4] = { (uint8_t) i, (uint8_t) (i >> 8), (uint8_t) (i >> 16), 0xA5 }; unsigned int A, B, C, D, E, F; int d; A = parity (i & 0x555555); B = parity (i & 0x666666); C = parity (i & 0x787878); D = parity (i & 0x007F80); E = parity (i & 0x7F8000); F = parity (i & 0xFFFFFF); d = (+ ((i & 0x000004) >> (3 - 1)) + ((i & 0x000070) >> (5 - 2)) + ((i & 0x007F00) >> (9 - 5)) + ((i & 0x7F0000) >> (17 - 12))); if (A && B && C && D && E) { /* No error. */ assert (vbi::unham24 (buf) == d); } else if (F) { /* Uncorrectable error. */ assert (vbi::unham24 (buf) < 0); } else { unsigned int err; unsigned int ii; /* Single bit error. */ err = ((E << 4) | (D << 3) | (C << 2) | (B << 1) | A) ^ 0x1F; assert (err > 0); if (err >= 24) { /* Invalid. */ assert (vbi::unham24 (buf) < 0); continue; } /* Correctable single bit error. */ ii = i ^ (1 << (err - 1)); A = parity (ii & 0x555555); B = parity (ii & 0x666666); C = parity (ii & 0x787878); D = parity (ii & 0x007F80); E = parity (ii & 0x7F8000); F = parity (ii & 0xFFFFFF); assert (A && B && C && D && E && F); d = (+ ((ii & 0x000004) >> (3 - 1)) + ((ii & 0x000070) >> (5 - 2)) + ((ii & 0x007F00) >> (9 - 5)) + ((ii & 0x7F0000) >> (17 - 12))); assert (vbi::unham24 (buf) == d); } } } int main (int argc, char ** argv) { unsigned int i; argc = argc; /* unused */ argv = argv; test_rev (); test_par_unpar (); test_ham8_ham16_unham8_unham16 (); for (i = 0; i < (1 << 18); ++i) test_ham24 (i); test_ham24 (1 << 18); test_ham24 (-1); test_unham24 (); return 0; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/test-packet-830.cc000066400000000000000000000311331476363111200166020ustar00rootroot00000000000000/* * libzvbi -- Teletext packet 8/30 low level functions unit test * * Copyright (C) 2008 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-packet-830.cc,v 1.1 2009-03-04 21:48:20 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "src/packet-830.h" #include "src/hamm.h" #include "test-pdc.h" #define N_ELEMENTS(array) (sizeof (array) / sizeof (*(array))) static const unsigned int valid_cnis [] = { 0x0000, 0x0001, 0x0004, 0x0010, 0x0040, 0x0100, 0x0400, 0x1000, 0x4000, 0x5A5A, 0xA5A5, 0xFFFF }; static const uint8_t teletext_8302_sample [42] = { 0x15, 0xEA, 0x49, 0x15, 0x15, 0xEA, 0xEA, 0xEA, 0x5e, 0x15, 0x73, 0xEA, 0x9B, /* 00010101 01110011 11101010 10011011 0 0 0 0 0 1 0 1 1 1 1 1 1 0 1 1 l0l1 a0a1 cccdcecf d3d4c6c7 bit 0 = LSB r u l2l1 --m a2a1 c4c3c2c1 p2p1cAc9 } 3 2 1 0 3 2 1 0 3 2 1 0 3 2 1 0 } EN 300 231 Table 8 13 14 15 16 } r=PRF, u=LUF, l=LCI, a=PCS, c=CNI, p=PIL/PTY, d=day, m=MI/month/minute, h=hour. Compare test-vps.cc. */ 0xEA, 0x49, 0x5E, 0x73, /* 11101010 01001001 01011110 01110011 1 1 1 1 0 0 1 0 0 0 1 1 0 1 0 1 m3d0d1d2 h4m0m1m2 h0h1h2h3 m2m3m4m5 p6p5p4p3 pAp9p8p7 pEpDpCpB pIpHpGpF 3 2 1 0 3 2 1 0 3 2 1 0 3 2 1 0 17 18 19 20 */ 0xA1, 0x49, 0xB6, 0x15, 0x64, /* 10100001 01001001 10110110 00010101 01100100 1 1 0 0 0 0 1 0 1 1 0 1 0 0 0 0 0 1 0 0 cacbm0m1 c4c5c8c9 c0c1c2c3 p4p5p6p7 p0p1p2p3 c6c5pKpJ cCcBc8c7 cGcFcEcD p4p3p2p1 p8p7p6p5 3 2 1 0 3 2 1 0 3 2 1 0 3 2 1 0 3 2 1 0 21 22 23 24 25 */ 0xC2, 0x52, 0xBA, 0x20, 0x52, 0xEF, 0xF4, 0xE5, 0x20, 0x52, 0xEF, 0x73, 0xE5, 0x6E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }; static const int bad_mjd [] = { 0x00000, 0x01111, 0x10111, 0x11011, 0x11101, 0x11110, 0xAAAAB, /* 9999A */ 0xAAABA, 0xAABAA, 0xABAAA, 0xBAAAA, /* A9999 */ 0xFFFFF }; static const int bad_utc [] = { 0x000000, 0x011111, 0x101111, 0x110111, 0x111011, 0x111101, 0x111110, 0x11111B, /* 00000A */ 0x111172, /* 000061 */ 0x11117B, /* 00006A */ 0x111181, /* 000070 */ 0x111B11, /* 000A00 */ 0x117111, /* 006000 */ 0x351111, /* 240000 */ 0x411111, /* 300000 */ 0xFFFFFF }; static void assert_decode_teletext_8301_cni (unsigned int * cni, const uint8_t buffer[42]) { uint8_t buffer2[42]; unsigned int cni2; memcpy (buffer2, buffer, sizeof (buffer2)); memset_rand (cni, sizeof (*cni)); cni2 = *cni; assert (TRUE == vbi_decode_teletext_8301_cni (cni, buffer)); assert ((unsigned int) *cni <= 0xFFFF); assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } static void encode_teletext_8301_cni (uint8_t buffer[42], unsigned int cni) { /* EN 300 706 Section 9.8.1. */ /* -3: CRI, FRC. */ buffer[13 - 3 - 1] = vbi_rev16 (cni); buffer[14 - 3 - 1] = vbi_rev16 (cni) >> 8; } static void assert_decode_teletext_8301_local_time (time_t * time, int * seconds_east, const uint8_t buffer[42], vbi_bool exp_success = TRUE, time_t exp_time = ANY_TIME, int exp_seconds_east = 0) { uint8_t buffer2[42]; time_t time2; int seconds_east2; memcpy (buffer2, buffer, sizeof (buffer2)); memset_rand (time, sizeof (*time)); time2 = *time; memset_rand (seconds_east, sizeof (*seconds_east)); seconds_east2 = *seconds_east; assert (exp_success == vbi_decode_teletext_8301_local_time (time, seconds_east, buffer)); if (exp_success) { if (ANY_TIME != exp_time) { assert (exp_time == *time); assert (exp_seconds_east == *seconds_east); } } else { assert (*time == time2); assert (*seconds_east == seconds_east2); } assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } static void assert_decode_teletext_8301_local_time (const uint8_t buffer[42], vbi_bool exp_success = TRUE, time_t exp_time = ANY_TIME, int exp_seconds_east = 0) { time_t time; int seconds_east; assert_decode_teletext_8301_local_time (&time, &seconds_east, buffer, exp_success, exp_time, exp_seconds_east); } static void encode_teletext_8301_local_time (uint8_t buffer[42], int mjd, int utc, int seconds_east, bool add_one = TRUE) { buffer[15 - 3 - 1] = ((abs (seconds_east / (30 * 60)) & 0x1F) << 1) | ((seconds_east < 0) ? 0x40 : 0x00); if (add_one) mjd += 0x11111; buffer[16 - 3 - 1] = mjd >> 16; buffer[17 - 3 - 1] = mjd >> 8; buffer[18 - 3 - 1] = mjd >> 0; if (add_one) utc += 0x111111; buffer[19 - 3 - 1] = utc >> 16; buffer[20 - 3 - 1] = utc >> 8; buffer[21 - 3 - 1] = utc >> 0; } static void assert_decode_teletext_8302_cni (unsigned int * cni, const uint8_t buffer[42], vbi_bool exp_success = TRUE) { uint8_t buffer2[42]; unsigned int cni2; memcpy (buffer2, buffer, sizeof (buffer2)); memset_rand (cni, sizeof (*cni)); cni2 = *cni; assert (exp_success == vbi_decode_teletext_8302_cni (cni, buffer)); if (exp_success) { assert ((unsigned int) *cni <= 0xFFFF); } else { assert (*cni == cni2); } assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } static void assert_decode_teletext_8302_pdc (test_pid * pid, const uint8_t buffer[42], vbi_bool exp_success = TRUE, const test_pid * exp_pid = NULL) { uint8_t buffer2[42]; test_pid pid2; memcpy (buffer2, buffer, sizeof (buffer2)); pid->randomize (); pid2 = *pid; assert (exp_success == vbi_decode_teletext_8302_pdc (pid, buffer)); if (exp_success) { unsigned int cni; pid->assert_valid_ttx (); assert_decode_teletext_8302_cni (&cni, buffer); assert (cni == pid->cni); if (NULL != exp_pid) { assert (exp_pid->channel == pid->channel); assert (exp_pid->cni == pid->cni); assert (exp_pid->pil == pid->pil); assert (exp_pid->luf == pid->luf); assert (exp_pid->mi == pid->mi); assert (exp_pid->prf == pid->prf); assert (exp_pid->pcs_audio == pid->pcs_audio); assert (exp_pid->pty == pid->pty); } } else { assert (pid2 == *pid); } assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } static void encode_teletext_8302_pdc (uint8_t buffer[42], const test_pid * pid) { unsigned int i; memset_rand (buffer, 42); /* EN 300 706 Section 9.8.2, EN 300 231 Section 8.2.1, TR 300 231 Section 5. */ /* -3: CRI, FRC. */ buffer[13 - 3 - 1] = (+ ((pid->channel << 2) & 0xC) + ((pid->luf << 1) & 0x2) + ((pid->prf << 0) & 0x1)); buffer[14 - 3 - 1] = (+ ((pid->pcs_audio << 2) & 0xC) + ((pid->mi << 1) & 0x2) + ((buffer[14 - 3 - 1] ) & 0x1)); buffer[15 - 3 - 1] = pid->cni >> (16 - 4); buffer[16 - 3 - 1] = (+ (((pid->cni >> (16 - 10)) << 2) & 0xC) + (((pid->pil >> (20 - 2)) << 0) & 0x3)); buffer[17 - 3 - 1] = pid->pil >> (20 - 6); buffer[18 - 3 - 1] = pid->pil >> (20 - 10); buffer[19 - 3 - 1] = pid->pil >> (20 - 14); buffer[20 - 3 - 1] = pid->pil >> (20 - 18); buffer[21 - 3 - 1] = (+ (((pid->pil >> (20 - 20)) << 2) & 0xC) + (((pid->cni >> (16 - 6)) << 0) & 0x3)); buffer[22 - 3 - 1] = (+ (((pid->cni >> (16 - 8)) << 2) & 0xC) + (((pid->cni >> (16 - 12)) << 0) & 0x3)); buffer[23 - 3 - 1] = pid->cni >> (16 - 16); buffer[24 - 3 - 1] = pid->pty >> (8 - 4); buffer[25 - 3 - 1] = pid->pty >> (8 - 8); for (i = 7; i <= 12; ++i) buffer[i - 3 - 1] = vbi_ham8 (buffer[i - 3 - 1]); for (i = 13; i <= 25; ++i) { /* Transmitted MSB first, like VPS. */ int c = vbi_rev8 (buffer[i - 3 - 1]) >> 4; buffer[i - 3 - 1] = vbi_ham8 (c); } } int main (void) { uint8_t buffer1[42]; test_pid pid1; test_pid pid2; time_t t1; unsigned int cni; unsigned int i; for (i = 0; i < N_ELEMENTS (valid_cnis); ++i) { memset_rand (buffer1, sizeof (buffer1)); encode_teletext_8301_cni (buffer1, valid_cnis[i]); assert_decode_teletext_8301_cni (&cni, buffer1); assert (cni == valid_cnis[i]); pid1.randomize (); pid1.cni = valid_cnis[i]; encode_teletext_8302_pdc (buffer1, &pid1); assert_decode_teletext_8302_cni (&cni, buffer1); assert (cni == valid_cnis[i]); /* Single bit error. */ buffer1[15 - 3 - 1] ^= 0x04; buffer1[22 - 3 - 1] ^= 0x02; assert_decode_teletext_8302_cni (&cni, buffer1); assert (cni == valid_cnis[i]); /* Double bit error. */ buffer1[15 - 3 - 1] ^= 0x08; assert_decode_teletext_8302_cni (&cni, buffer1, FALSE); } memset_rand (buffer1, sizeof (buffer1)); t1 = ztime ("19820131T000000"); #ifndef _WIN32 // _mkgmtime doesn't support dates before 1900 encode_teletext_8301_local_time (buffer1, 0x00000, 0x000000, 0); if (TIME_MIN < -2147483648.0) { assert_decode_teletext_8301_local_time (buffer1, TRUE, ztime ("18581117T000000"), 0); } else { /* Not representable as time_t. */ assert_decode_teletext_8301_local_time (buffer1, FALSE); } #endif /* EN 300 706 Table 18: "Reference point". */ encode_teletext_8301_local_time (buffer1, 0x45000, 0x000000, 0); assert_decode_teletext_8301_local_time (buffer1, TRUE, t1, 0); /* 2000 is a leap year. */ encode_teletext_8301_local_time (buffer1, 0x51603, 0x213243, 0); assert_decode_teletext_8301_local_time (buffer1, TRUE, ztime ("20000229T213243"), 0); /* +1 leap second. EN 300 706 Section 9.8.1 does not specify if UDT counts leap seconds. We assume it does, which should be safe because time_t ignores leap seconds. */ encode_teletext_8301_local_time (buffer1, 0x53735, 0x235959, 0); assert_decode_teletext_8301_local_time (buffer1, TRUE, ztime ("20051231T235959"), 0); encode_teletext_8301_local_time (buffer1, 0x53735, 0x235960, 0); assert_decode_teletext_8301_local_time (buffer1, TRUE, ztime ("20060101T000000"), 0); encode_teletext_8301_local_time (buffer1, 0x53736, 0x000000, 0); assert_decode_teletext_8301_local_time (buffer1, TRUE, ztime ("20060101T000000"), 0); /* -1 leap second just skips 0x235959, not testable. */ encode_teletext_8301_local_time (buffer1, 0x99999, 0x235960, 0); if (TIME_MAX > 4294967295.0) { assert_decode_teletext_8301_local_time (buffer1, TRUE, ztime ("21320901T000000"), 0); } else { /* Not representable as time_t. */ assert_decode_teletext_8301_local_time (buffer1, FALSE); } for (i = 0; i < N_ELEMENTS (bad_mjd); ++i) { encode_teletext_8301_local_time (buffer1, bad_mjd[i], 0x111111, /* lto */ 0, /* add_one */ FALSE); assert_decode_teletext_8301_local_time (buffer1, FALSE); } for (i = 0; i < N_ELEMENTS (bad_utc); ++i) { encode_teletext_8301_local_time (buffer1, 0x56111, bad_utc[i], /* lto */ 0, /* add_one */ FALSE); assert_decode_teletext_8301_local_time (buffer1, FALSE); } for (i = 0; i <= 0x1F; ++i) { encode_teletext_8301_local_time (buffer1, 0x45000, 0, i * 30 * 60); assert_decode_teletext_8301_local_time (buffer1, TRUE, t1, i * 30 * 60); buffer1[15 - 3 - 1] ^= 0x40; assert_decode_teletext_8301_local_time (buffer1, TRUE, t1, -i * 30 * 60); } for (i = 0; i < 1000; ++i) { unsigned int j; pid1.populate_ttx (); encode_teletext_8302_pdc (buffer1, &pid1); assert_decode_teletext_8302_cni (&cni, buffer1); assert (cni == pid1.cni); assert_decode_teletext_8302_pdc (&pid2, buffer1, TRUE, &pid1); memset_rand (buffer1, sizeof (buffer1)); for (j = 13; j <= 25; ++j) { buffer1[j - 3 - 1] = vbi_ham8 (buffer1[j - 3 - 1] & 0xF); } assert_decode_teletext_8302_pdc (&pid2, buffer1); pid1 = pid2; /* Single bit error. */ buffer1[14 - 3 - 1] ^= 0x02; buffer1[23 - 3 - 1] ^= 0x80; assert_decode_teletext_8302_pdc (&pid2, buffer1, TRUE, &pid1); /* Double bit error. */ buffer1[23 - 3 - 1] ^= 0x10; assert_decode_teletext_8302_pdc (&pid2, buffer1, FALSE); } assert_decode_teletext_8302_pdc (&pid1, teletext_8302_sample); assert (0 == pid1.channel); assert (0xFDCB == pid1.cni); assert (VBI_PIL (0x0A, 0x0F, 0x0C, 0x28) == pid1.pil); assert (0 == pid1.luf); assert (1 == pid1.mi); assert (0 == pid1.prf); assert (0x02 == pid1.pcs_audio); assert (0x02 == pid1.pty); return 0; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/test-pdc.cc000066400000000000000000001004321476363111200155700ustar00rootroot00000000000000/* * libzvbi -- PDC functions unit test * * Copyright (C) 2008 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-pdc.cc,v 1.2 2009-03-23 01:30:45 mschimek Exp $ */ #undef NDEBUG #include #include "src/pdc.h" #include "src/misc.h" /* mktime(), timegm() */ #include "test-pdc.h" #if 0 /* for debugging */ static void print_time (time_t time) { char buffer[80]; struct tm tm; printf ("%ld ", (long) time); memset (&tm, 0, sizeof (tm)); localtime_r (&time, &tm); strftime (buffer, sizeof (buffer), "%Y-%m-%d %H:%M:%S %Z = ", &tm); fputs (buffer, stdout); memset (&tm, 0, sizeof (tm)); gmtime_r (&time, &tm); strftime (buffer, sizeof (buffer), "%Y-%m-%d %H:%M:%S UTC", &tm); puts (buffer); } #endif /* 0 */ static const vbi_pil valid_dates [] = { VBI_PIL (1, 1, 0, 0), VBI_PIL (1, 1, 1, 0), VBI_PIL (1, 1, 23, 0), VBI_PIL (1, 1, 0, 1), VBI_PIL (1, 1, 0, 59), VBI_PIL (1, 31, 0, 0), VBI_PIL (3, 31, 0, 0), VBI_PIL (4, 30, 0, 0), VBI_PIL (5, 31, 0, 0), VBI_PIL (6, 30, 0, 0), VBI_PIL (7, 31, 0, 0), VBI_PIL (8, 31, 0, 0), VBI_PIL (9, 30, 0, 0), VBI_PIL (10, 31, 0, 0), VBI_PIL (11, 30, 0, 0), VBI_PIL (12, 1, 0, 0), VBI_PIL (12, 31, 0, 0), }; static const vbi_pil invalid_dates [] = { 0, VBI_PIL (0, 1, 0, 0), VBI_PIL (1, 0, 0, 0), VBI_PIL (1, 1, 24, 0), VBI_PIL (1, 1, 31, 0), VBI_PIL (1, 1, 0, 60), VBI_PIL (1, 1, 0, 63), VBI_PIL (2, 30, 0, 0), VBI_PIL (2, 31, 0, 0), VBI_PIL (4, 31, 0, 0), VBI_PIL (6, 31, 0, 0), VBI_PIL (9, 31, 0, 0), VBI_PIL (11, 31, 0, 0), VBI_PIL (13, 1, 0, 0), VBI_PIL (15, 1, 0, 0), VBI_PIL_TIMER_CONTROL, VBI_PIL_INHIBIT_TERMINATE, VBI_PIL_INTERRUPTION, VBI_PIL_CONTINUE, VBI_PIL_NSPV, VBI_PIL_END, }; /* EN 300 231 Annex F. */ static const vbi_pil normal_dates [] = { VBI_PIL (1, 1, 24, 0), VBI_PIL (1, 1, 31, 0), VBI_PIL (1, 1, 0, 60), VBI_PIL (1, 1, 0, 63), /* plus all valid_dates[] */ }; /* EN 300 231 Annex F. */ static const vbi_pil unallocated_dates [] = { 0, VBI_PIL (0, 1, 0, 0), VBI_PIL (15, 0, 0, 0), VBI_PIL (15, 0, 0, 63), VBI_PIL (15, 0, 27, 63), VBI_PIL (15, 0, 31, 0), VBI_PIL (15, 0, 31, 62), VBI_PIL (15, 31, 0, 0), }; /* EN 300 231 Annex F. */ static const vbi_pil indefinite_dates [] = { VBI_PIL (1, 0, 0, 0), VBI_PIL (2, 30, 0, 0), VBI_PIL (2, 31, 0, 0), VBI_PIL (4, 31, 0, 0), VBI_PIL (6, 31, 0, 0), VBI_PIL (9, 31, 0, 0), VBI_PIL (11, 31, 0, 0), VBI_PIL (13, 1, 0, 0), VBI_PIL (14, 1, 0, 0), VBI_PIL (14, 31, 31, 63), VBI_PIL_TIMER_CONTROL, VBI_PIL_INHIBIT_TERMINATE, VBI_PIL_INTERRUPTION, VBI_PIL_CONTINUE, }; static void assert_errno (int exp_errno) { /* XXX later */ exp_errno = exp_errno; } static void assert_pil_from_string (vbi_pil * pil, const char ** s, vbi_bool exp_success = TRUE) { const char *s1; vbi_bool success; s1 = *s; *pil = 12345; success = _vbi_pil_from_string (pil, s); assert (exp_success == success); if (!exp_success) { assert (s1 == *s); assert (12345 == *pil); } } static void test_pil_from_string (void) { static const struct { const char * name; vbi_pil pil; } good_pils [] = { { "cont", VBI_PIL_CONTINUE }, { "continue", VBI_PIL_CONTINUE }, { "cOnTiNuE", VBI_PIL_CONTINUE }, { "end", VBI_PIL_END }, { "END", VBI_PIL_END }, { "inhibit", VBI_PIL_INHIBIT_TERMINATE }, { "int", VBI_PIL_INTERRUPTION }, { "interruption", VBI_PIL_INTERRUPTION }, { "nspv", VBI_PIL_NSPV }, { "rit", VBI_PIL_INHIBIT_TERMINATE }, { "terminate", VBI_PIL_INHIBIT_TERMINATE }, { "tc", VBI_PIL_TIMER_CONTROL }, { "timer", VBI_PIL_TIMER_CONTROL }, { " \t\n timer", VBI_PIL_TIMER_CONTROL }, { "00000000", VBI_PIL (0, 0, 0, 0) }, { "15000000", VBI_PIL (15, 0, 0, 0) }, { "00310000", VBI_PIL (0, 31, 0, 0) }, { "00003100", VBI_PIL (0, 0, 31, 0) }, { "00000063", VBI_PIL (0, 0, 0, 63) }, { "\n \t 11-12T13:14", VBI_PIL (11, 12, 13, 14) }, { "1112", VBI_PIL (0, 0, 11, 12) }, { "11:12", VBI_PIL (0, 0, 11, 12) }, { "11-12T13:14", VBI_PIL (11, 12, 13, 14) }, { "1112T13:14", VBI_PIL (11, 12, 13, 14) }, { "111213:14", VBI_PIL (11, 12, 13, 14) }, { "1112T1314", VBI_PIL (11, 12, 13, 14) }, { "11121314", VBI_PIL (11, 12, 13, 14) }, { "11-1213:14", VBI_PIL (11, 12, 13, 14) }, { "11-121314", VBI_PIL (11, 12, 13, 14) }, { "11-12T1314", VBI_PIL (11, 12, 13, 14) } }; static const struct { const char * name; vbi_pil pil; int c; } trailing_garbage [] = { { "int foo", VBI_PIL_INTERRUPTION, ' ' }, { "int-foo", VBI_PIL_INTERRUPTION, '-' }, { "int\n ", VBI_PIL_INTERRUPTION, '\n' }, { "int\t\n", VBI_PIL_INTERRUPTION, '\t' }, { "00-00T00:00 ", VBI_PIL (0, 0, 0, 0), ' ' }, { "00-00T00:00a", VBI_PIL (0, 0, 0, 0), 'a' }, { "00000000:00", VBI_PIL (0, 0, 0, 0), ':' }, { "01-02T03:04:00", VBI_PIL (1, 2, 3, 4), ':' }, { "1413:2016", VBI_PIL (0, 0, 14, 13), ':' }, { "14:132016", VBI_PIL (0, 0, 14, 13), '2' }, { "1413+2016", VBI_PIL (0, 0, 14, 13), '+' }, { "141320167", VBI_PIL (14, 13, 20, 16), '7' }, { "2004-01-01T01:01", VBI_PIL (0, 0, 20, 4), '-' } }; static const char *bad_pils [] = { "c", "intc", "endfish", "tc2", "T", "8nspv", "0", "1", "11", "-11", "+11", "111", "11-11", "1111T", "11T11", "1-111", "111:1", "1-1T1:1", "11111", "11-111", "11-1111", "111111", "111111", "1111111", "11-1111", "11-11111", "111111", "1111111", "11-11 11:11", "11-11t11:11", "11--111111", "11+111111", "11T+111111", "11T-111111", "111111T11", "111111-11", "200401010101", "16000000", "99000000", "00320000", "00990000", "00003200", "00009900", "00000064", "00000099" }; vbi_pil p; const char *s; unsigned int i; for (i = 0; i < N_ELEMENTS (good_pils); ++i) { s = good_pils[i].name; assert_pil_from_string (&p, &s); assert (p == good_pils[i].pil); assert (0 == *s); } for (i = 0; i < N_ELEMENTS (trailing_garbage); ++i) { s = trailing_garbage[i].name; assert_pil_from_string (&p, &s); assert (p == trailing_garbage[i].pil); assert (*s == trailing_garbage[i].c); } for (i = 0; i < N_ELEMENTS (bad_pils); ++i) { s = bad_pils[i]; assert_pil_from_string (&p, &s, FALSE); } } static void assert_pty_validity_window (time_t * begin, time_t * end, time_t start, const char * tz, vbi_bool exp_success = TRUE, int exp_errno = 0, time_t exp_begin = ANY_TIME, time_t exp_end = ANY_TIME) { vbi_bool success; *begin = 123; *end = 456; success = vbi_pty_validity_window (begin, end, start, tz); assert (exp_success == success); if (exp_success) { if (ANY_TIME != exp_begin) assert (exp_begin == *begin); if (ANY_TIME != exp_end) assert (exp_end == *end); } else { assert_errno (exp_errno); assert (123 == *begin); assert (456 == *end); } } static void assert_pty_validity_window (time_t start, const char * tz, vbi_bool exp_success = TRUE, int exp_errno = 0, time_t exp_begin = ANY_TIME, time_t exp_end = ANY_TIME) { time_t begin1, end1; assert_pty_validity_window (&begin1, &end1, start, tz, exp_success, exp_errno, exp_begin, exp_end); } static void test_pty_validity_window (void) { time_t t; assert_pty_validity_window ((time_t) -1, "UTC"); assert_pty_validity_window ((time_t) -1, "CET"); /* GNU libc setenv() doesn't seem to care. "" may be a shorthand for UTC. */ if (0) { t = ztime ("20010101T000000"); assert_pty_validity_window (t, "", FALSE, EINVAL); assert_pty_validity_window (t, "CET=", FALSE, EINVAL); } if (TIME_MIN >= 0) { /* 'begin' and 'end' cannot be smaller than 'time' (unless there was a negative DST offset). */ assert_pty_validity_window (TIME_MIN, "UTC", TRUE, 0, TIME_MIN); assert_pty_validity_window (TIME_MIN, "CET", TRUE, 0, TIME_MIN); } if (TIME_MAX <= 0x7FFFFFFF) { t = TIME_MAX - 30 * 24 * 60 * 60; assert_pty_validity_window (t, "UTC", TRUE, 0, t); assert_pty_validity_window (t, "CET", TRUE, 0, t); t = TIME_MAX - 26 * 24 * 60 * 60; assert_pty_validity_window (t, "UTC", FALSE, EOVERFLOW); assert_pty_validity_window (t, "CET", FALSE, EOVERFLOW); assert_pty_validity_window (TIME_MAX, "UTC", FALSE, EOVERFLOW); assert_pty_validity_window (TIME_MAX, "CET", FALSE, EOVERFLOW); } t = ztime ("20010101T000000"); assert_pty_validity_window (t, "UTC", TRUE, 0, t, ztime ("20010130T040000")); t = ztime ("20010415T111111"); assert_pty_validity_window (t, "UTC", TRUE, 0, t, ztime ("20010514T040000")); t = ztime ("20010630T222222"); assert_pty_validity_window (t, "UTC", TRUE, 0, t, ztime ("20010729T040000")); t = ztime ("20010701T031415"); assert_pty_validity_window (t, "UTC", TRUE, 0, t, ztime ("20010730T040000")); t = ztime ("20010915T150901"); assert_pty_validity_window (t, "UTC", TRUE, 0, t, ztime ("20011014T040000")); t = ztime ("20011231T235959"); assert_pty_validity_window (t, "UTC", TRUE, 0, t, ztime ("20020129T040000")); /* Regular year. */ t = ztime ("20020131T000000"); assert_pty_validity_window (t, "UTC", TRUE, 0, t, ztime ("20020301T040000")); /* Leap year. */ t = ztime ("20040131T000000"); assert_pty_validity_window (t, "UTC", TRUE, 0, t, ztime ("20040229T040000")); t = ztime ("20040229T000000"); assert_pty_validity_window (t, "UTC", TRUE, 0, t, ztime ("20040329T040000")); /* 2004-03-28 01:00 UTC: London local time changes from 01:00 GMT to 02:00 BST. */ /* Validity window entirely in GMT zone. */ t = ztime ("20040227T235959"); assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20040327T040000")); /* Validity window begins in GMT zone, ends in BST zone. */ t = ztime ("20040228T000000"); assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20040328T030000")); t = ztime ("20040328T010000"); assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20040426T030000")); t = ztime ("20040328T020000"); assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20040426T030000")); /* Validity window entirely in BST zone. */ t = ztime ("20040329T000000"); assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20040427T030000")); /* 2004-10-31 01:00 UTC: London local time changes from 02:00 BST to 01:00 GMT. */ /* Validity window entirely in BST zone. */ t = ztime ("20041001T225959"); /* = 2004-10-01 23:59:59 BST */ assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20041030T030000")); t = ztime ("20041001T235959"); /* = 2004-10-02 00:59:59 BST */ assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20041031T040000")); /* Validity window begins in BST zone, ends in GMT zone. */ t = ztime ("20041002T00000"); assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20041031T040000")); t = ztime ("20041031T01000"); assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20041129T040000")); t = ztime ("20041031T02000"); assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20041129T040000")); /* Validity window entirely in GMT zone. */ t = ztime ("20041101T000000"); assert_pty_validity_window (t, "Europe/London", TRUE, 0, t, ztime ("20041130T040000")); } static void assert_pil_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, const char * tz, vbi_bool exp_success = TRUE, int exp_errno = 0, time_t exp_begin = ANY_TIME, time_t exp_end = ANY_TIME) { time_t begin2, end2; vbi_bool success; int seconds_east; *begin = 123; *end = 456; success = vbi_pil_validity_window (begin, end, pil, start, tz); assert (exp_success == success); if (exp_success) { if (ANY_TIME != exp_begin) assert (exp_begin == *begin); if (ANY_TIME != exp_end) assert (exp_end == *end); } else { assert_errno (exp_errno); assert (123 == *begin); assert (456 == *end); } if (NULL == tz) { return; } else if (0 == strcmp (tz, "UTC")) { seconds_east = 0; } else if (0 == strcmp (tz, "CET")) { if (VBI_PIL_MONTH (pil) >= 3 && VBI_PIL_MONTH (pil) <= 10) { /* GNU libc mktime() changes to CEST if DST is in effect at the given date. Is that expected? */ return; } else { seconds_east = 3600; } } else { return; } begin2 = 123; end2 = 456; success = vbi_pil_lto_validity_window (&begin2, &end2, pil, start, seconds_east); assert (exp_success == success); if (!exp_success) assert_errno (exp_errno); assert (begin2 == *begin); assert (end2 == *end); } static void assert_pil_validity_window (vbi_pil pil, time_t start, const char * tz, vbi_bool exp_success = TRUE, int exp_errno = 0, time_t exp_begin = ANY_TIME, time_t exp_end = ANY_TIME) { time_t begin1, end1; assert_pil_validity_window (&begin1, &end1, pil, start, tz, exp_success, exp_errno, exp_begin, exp_end); } static void test_pil_validity_window (void) { struct tm tm_min, tm_max; vbi_pil p, p1; time_t t, t1; time_t begin, end; time_t begin2, end2; unsigned int i; p1 = VBI_PIL (1, 1, 0, 0); t1 = ztime ("20010101T000000"); for (i = 0; i < N_ELEMENTS (valid_dates); ++i) { vbi_pil p = valid_dates[i]; assert_pil_validity_window (p, t1, "UTC"); assert_pil_validity_window (p, t1, "CET"); assert_pil_validity_window (p, t1, NULL); } for (i = 0; i < N_ELEMENTS (normal_dates); ++i) { vbi_pil p = normal_dates[i]; assert_pil_validity_window (p, t1, "UTC"); assert_pil_validity_window (p, t1, "CET"); assert_pil_validity_window (p, t1, NULL); } for (i = 0; i < N_ELEMENTS (valid_dates); ++i) { vbi_pil p = valid_dates[i]; int j; for (j = -13 * 3600; j <= +13 * 3600; j += 3744) { char tz[16]; time_t begin2, end2; snprintf (tz, sizeof (tz), "UTC%c%02u:%02u:%02u", (j < 0) ? '+' : '-', abs (j) / 3600, abs (j) / 60 % 60, abs (j) % 60); assert (TRUE == vbi_pil_validity_window (&begin, &end, p, t1, tz)); assert (TRUE == vbi_pil_lto_validity_window (&begin2, &end2, p, t1, j)); assert (begin == begin2); assert (end == end2); } } for (i = 0; i < N_ELEMENTS (unallocated_dates); ++i) { vbi_pil p = unallocated_dates[i]; assert_pil_validity_window (p, t1, "UTC", FALSE, EINVAL); assert_pil_validity_window (p, t1, "CET", FALSE, EINVAL); assert_pil_validity_window (p, t1, NULL, FALSE, EINVAL); } for (i = 0; i < N_ELEMENTS (indefinite_dates); ++i) { vbi_pil p = indefinite_dates[i]; assert_pil_validity_window (p, t1, "UTC", TRUE, 0, TIME_MIN, TIME_MAX); assert_pil_validity_window (p, t1, "CET", TRUE, 0, TIME_MIN, TIME_MAX); assert_pil_validity_window (p, t1, NULL, TRUE, 0, TIME_MIN, TIME_MAX); } /* Invalid day in year 2001, therefore indefinite time window. */ assert_pil_validity_window (VBI_PIL (2, 29, 12, 0), t1, "UTC", TRUE, 0, TIME_MIN, TIME_MAX); assert_pil_validity_window (VBI_PIL (2, 29, 12, 0), t1, "CET", TRUE, 0, TIME_MIN, TIME_MAX); /* Valid day in year 2004. */ assert_pil_validity_window (VBI_PIL (2, 29, 12, 0), ztime ("20040101T000000"), "UTC"); assert_pil_validity_window (p1, (time_t) -1, "UTC"); assert_pil_validity_window (p1, (time_t) -1, "CET"); /* GNU libc setenv() doesn't seem to care. "" may be a shorthand for UTC. */ if (0) { assert_pil_validity_window (p1, t1, "", FALSE, EINVAL); assert_pil_validity_window (p1, t1, "CET=", FALSE, EINVAL); } if (TIME_MIN >= 0) { t = TIME_MIN; #ifdef _WIN32 assert (0 == gmtime_s (&tm_min, &t)); #else assert (NULL != gmtime_r (&t, &tm_min)); #endif assert (t == timegm (&tm_min)); p = VBI_PIL (tm_min.tm_mon + 1, tm_min.tm_mday, tm_min.tm_hour, /* minute */ 59), assert_pil_validity_window (p, TIME_MIN, "UTC", FALSE, EOVERFLOW); } if (TIME_MAX <= 0x7FFFFFFF) { t = TIME_MAX; #ifdef _WIN32 assert (0 == gmtime_s (&tm_max, &t)); #else assert (NULL != gmtime_r (&t, &tm_max)); #endif assert (t == timegm (&tm_max)); p = VBI_PIL (tm_max.tm_mon + 1, tm_max.tm_mday, tm_max.tm_hour, 0), assert_pil_validity_window (p, TIME_MAX, "UTC", FALSE, EOVERFLOW); } t = ztime ("20010101T000000"); assert_pil_validity_window (VBI_PIL (6, 30, 23, 59), t, "UTC", TRUE, 0, ztime ("20010630T000000"), ztime ("20010701T040000")); assert_pil_validity_window (VBI_PIL (7, 1, 0, 0), t, "UTC", TRUE, 0, ztime ("20000630T200000"), ztime ("20000702T040000")); t = ztime ("20010415T000000"); assert_pil_validity_window (VBI_PIL (7, 1, 0, 0), t, "UTC", TRUE, 0, ztime ("20010630T200000"), ztime ("20010702T040000")); t = ztime ("20010630T000000"); assert_pil_validity_window (VBI_PIL (7, 1, 23, 59), t, "UTC", TRUE, 0, ztime ("20010701T000000"), ztime ("20010702T040000")); assert_pil_validity_window (VBI_PIL (12, 31, 23, 59), t, "UTC", TRUE, 0, ztime ("20001231T000000"), ztime ("20010101T040000")); assert_pil_validity_window (VBI_PIL (1, 1, 0, 0), t, "UTC", TRUE, 0, ztime ("20001231T200000"), ztime ("20010102T040000")); t = ztime ("20010701T000000"); assert_pil_validity_window (VBI_PIL (1, 1, 0, 0), t, "UTC", TRUE, 0, ztime ("20001231T200000"), ztime ("20010102T040000")); assert_pil_validity_window (VBI_PIL (12, 31, 23, 59), t, "UTC", TRUE, 0, ztime ("20011231T000000"), ztime ("20020101T040000")); t = ztime ("20010915T000000"); assert_pil_validity_window (VBI_PIL (1, 1, 0, 0), t, "UTC", TRUE, 0, ztime ("20011231T200000"), ztime ("20020102T040000")); t = ztime ("20011231T000000"); assert_pil_validity_window (VBI_PIL (1, 1, 0, 0), t, "UTC", TRUE, 0, ztime ("20011231T200000"), ztime ("20020102T040000")); assert_pil_validity_window (VBI_PIL (6, 30, 23, 59), t, "UTC", TRUE, 0, ztime ("20010630T000000"), ztime ("20010701T040000")); assert_pil_validity_window (VBI_PIL (7, 1, 0, 0), t, "UTC", TRUE, 0, ztime ("20010630T200000"), ztime ("20010702T040000")); /* 2004-03-28 01:00 UTC: London local time changes from 01:00 GMT to 02:00 BST. */ t = ztime ("20040301T000000"); /* Validity window entirely in GMT zone. */ assert_pil_validity_window (VBI_PIL (3, 26, 23, 59), t, "Europe/London", TRUE, 0, ztime ("20040326T000000"), ztime ("20040327T040000")); /* Validity window begins in GMT zone, ends in BST zone. */ assert_pil_validity_window (VBI_PIL (3, 27, 0, 0), t, "Europe/London", TRUE, 0, ztime ("20040326T200000"), ztime ("20040328T030000")); assert_pil_validity_window (VBI_PIL (3, 27, 23, 59), t, "Europe/London", TRUE, 0, ztime ("20040327T000000"), ztime ("20040328T030000")); assert_pil_validity_window (VBI_PIL (3, 28, 0, 0), t, "Europe/London", TRUE, 0, ztime ("20040327T200000"), ztime ("20040329T030000")); assert_pil_validity_window (VBI_PIL (3, 28, 1, 0), t, "Europe/London", TRUE, 0, ztime ("20040327T200000"), ztime ("20040329T030000")); assert_pil_validity_window (VBI_PIL (3, 28, 2, 0), t, "Europe/London", TRUE, 0, ztime ("20040327T200000"), ztime ("20040329T030000")); assert_pil_validity_window (VBI_PIL (3, 28, 3, 59), t, "Europe/London", TRUE, 0, ztime ("20040327T200000"), ztime ("20040329T030000")); /* Between 04:00-23:59 local time the validity window begins at 00:00 local time of the same day, which is still 00:00 UTC. */ assert_pil_validity_window (VBI_PIL (3, 28, 4, 0), t, "Europe/London", TRUE, 0, ztime ("20040328T000000"), ztime ("20040329T030000")); /* Validity window entirely in BST zone. */ assert_pil_validity_window (VBI_PIL (3, 29, 0, 0), t, "Europe/London", TRUE, 0, ztime ("20040328T190000"), ztime ("20040330T030000")); /* 2004-10-31 01:00 UTC: London local time changes from 02:00 BST to 01:00 GMT. */ t = ztime ("20041001T000000"); /* Validity window entirely in BST zone. */ assert_pil_validity_window (VBI_PIL (10, 29, 0, 0), t, "Europe/London", TRUE, 0, ztime ("20041028T190000"), ztime ("20041030T030000")); assert_pil_validity_window (VBI_PIL (10, 29, 23, 59), t, "Europe/London", TRUE, 0, ztime ("20041028T230000"), ztime ("20041030T030000")); /* Validity window begins in BST zone, ends in GMT zone. */ assert_pil_validity_window (VBI_PIL (10, 30, 0, 0), t, "Europe/London", TRUE, 0, ztime ("20041029T190000"), ztime ("20041031T040000")); assert_pil_validity_window (VBI_PIL (10, 30, 23, 59), t, "Europe/London", TRUE, 0, ztime ("20041029T230000"), ztime ("20041031T040000")); assert_pil_validity_window (VBI_PIL (10, 31, 0, 0), t, "Europe/London", TRUE, 0, ztime ("20041030T190000"), ztime ("20041101T040000")); assert_pil_validity_window (VBI_PIL (10, 31, 1, 0), t, "Europe/London", TRUE, 0, ztime ("20041030T190000"), ztime ("20041101T040000")); assert_pil_validity_window (VBI_PIL (10, 31, 2, 0), t, "Europe/London", TRUE, 0, ztime ("20041030T190000"), ztime ("20041101T040000")); assert_pil_validity_window (VBI_PIL (10, 31, 3, 59), t, "Europe/London", TRUE, 0, ztime ("20041030T190000"), ztime ("20041101T040000")); /* Between 04:00-23:59 local time the validity window begins at 00:00 local time of the same day, which is still 23:00 UTC. */ assert_pil_validity_window (VBI_PIL (10, 31, 4, 0), t, "Europe/London", TRUE, 0, ztime ("20041030T230000"), ztime ("20041101T040000")); /* Validity window entirely in GMT zone. */ assert_pil_validity_window (VBI_PIL (11, 1, 0, 0), t, "Europe/London", TRUE, 0, ztime ("20041031T200000"), ztime ("20041102T040000")); assert (TRUE == vbi_pty_validity_window (&begin, &end, t1, "UTC")); assert (begin == t1); assert (TRUE == vbi_pil_validity_window (&begin2, &end2, VBI_PIL_NSPV, t1, "UTC")); assert (begin2 == begin); assert (end2 == end); assert (TRUE == vbi_pil_lto_validity_window (&begin2, &end2, VBI_PIL_NSPV, t1, 0)); assert (begin2 == begin); assert (end2 == end); /* 'pil' is assumed to be a time in the UTC + 'seconds_east' zone, but seconds_east does not apply if pil is NSPV. Instead '*begin' is defined only by 'start' here, which is already given in UTC. vbi_pty_validity_window() *may* use 'tz' to correct the validity window for DST, but that is impossible with seconds_east. */ assert (TRUE == vbi_pil_lto_validity_window (&begin2, &end2, VBI_PIL_NSPV, t1, 12345)); assert (begin2 == begin); assert (end2 == end); assert (TRUE == vbi_pty_validity_window (&begin, &end, t1, "UTC+2")); assert (TRUE == vbi_pil_validity_window (&begin2, &end2, VBI_PIL_NSPV, t1, "UTC+2")); assert (begin2 == begin); assert (end2 == end); } static void assert_pil_to_time (vbi_pil pil, time_t start, const char * tz, time_t exp_result = ANY_TIME, int exp_errno = 0) { time_t result; int seconds_east; result = vbi_pil_to_time (pil, start, tz); if (ANY_TIME == exp_result) { assert ((time_t) -1 != result); } else if ((time_t) -1 == exp_result) { assert_errno (exp_errno); } if (NULL == tz) { return; } else if (0 == strcmp (tz, "UTC")) { seconds_east = 0; } else if (0 == strcmp (tz, "CET")) { if (VBI_PIL_MONTH (pil) >= 3 && VBI_PIL_MONTH (pil) <= 10) { /* GNU libc mktime() changes to CEST if DST is in effect at the given date. Is that expected? */ return; } else { seconds_east = 3600; } } else { return; } result = vbi_pil_lto_to_time (pil, start, seconds_east); if (ANY_TIME == exp_result) { assert ((time_t) -1 != result); } else if ((time_t) -1 == exp_result) { assert_errno (exp_errno); } } static void assert_pil_to_time (vbi_pil pil, time_t start, unsigned int exp_year) { char tz[32]; snprintf (tz, sizeof (tz), "%04u%02u%02uT%02u%02u00", exp_year, VBI_PIL_MONTH (pil), VBI_PIL_DAY (pil), VBI_PIL_HOUR (pil), VBI_PIL_MINUTE (pil)); assert_pil_to_time (pil, start, "UTC", ztime (tz)); } static void test_pil_to_time (void) { struct tm tm_min, tm_max; vbi_pil p, p1; time_t t, t1; unsigned int i; p1 = VBI_PIL (1, 1, 0, 0); t1 = ztime ("20010101T000000"); for (i = 0; i < N_ELEMENTS (valid_dates); ++i) { vbi_pil p = valid_dates[i]; assert_pil_to_time (p, t1, "UTC"); assert_pil_to_time (p, t1, "CET"); assert_pil_to_time (p, t1, (const char *) NULL); } for (i = 0; i < N_ELEMENTS (valid_dates); ++i) { vbi_pil p = valid_dates[i]; int j; for (j = -13 * 3600; j <= +13 * 3600; j += 3744) { char tz[16]; snprintf (tz, sizeof (tz), "UTC%c%02u:%02u:%02u", (j < 0) ? '+' : '-', abs (j) / 3600, abs (j) / 60 % 60, abs (j) % 60); t = vbi_pil_to_time (p, t1, tz); assert ((time_t) -1 != t); assert (t == vbi_pil_lto_to_time (p, t1, j)); } } for (i = 0; i < N_ELEMENTS (invalid_dates); ++i) { vbi_pil p = invalid_dates[i]; assert_pil_to_time (p, t1, "UTC", /* exp_result */ -1, EINVAL); assert_pil_to_time (p, t1, "CET", /* exp_result */ -1, EINVAL); assert_pil_to_time (p, t1, NULL, /* exp_result */ -1, EINVAL); } assert_pil_to_time (VBI_PIL (2, 29, 12, 0), t1, "UTC", -1, EINVAL); assert_pil_to_time (VBI_PIL (2, 29, 12, 0), t1, "CET", -1, EINVAL); assert_pil_to_time (VBI_PIL (2, 29, 12, 0), ztime ("20040101T000000"), "UTC", ztime ("20040229T000000")); /* GNU libc setenv() doesn't seem to care. "" may be a shorthand for UTC. */ if (0) { assert_pil_to_time (p1, t1, "", /* exp_result */ -1, EINVAL); assert_pil_to_time (p1, t1, "CET=", /* exp_result */ -1, EINVAL); } assert_pil_to_time (p1, (time_t) -1, "UTC"); assert_pil_to_time (p1, (time_t) -1, "CET"); if (TIME_MIN >= 0) { t = TIME_MIN; #ifdef _WIN32 assert (0 == gmtime_s (&tm_min, &t)); #else assert (NULL != gmtime_r (&t, &tm_min)); #endif assert (t == timegm (&tm_min)); p = VBI_PIL (tm_min.tm_mon + 1, tm_min.tm_mday, tm_min.tm_hour, 59), assert_pil_to_time (p, TIME_MIN, "UTC"); assert_pil_to_time (p, TIME_MIN, "UTC-1", /* exp_result */ -1, EOVERFLOW); assert ((time_t) -1 == vbi_pil_lto_to_time (p, TIME_MIN, /* seconds_east */ -3600)); assert_errno (EOVERFLOW); if (tm_min.tm_hour > 0) { assert_pil_to_time (VBI_PIL (tm_min.tm_mon + 1, tm_min.tm_mday, tm_min.tm_hour - 1, /* minute */ 59), TIME_MIN, "UTC", /* exp_result */ -1, EOVERFLOW); } else if (tm_min.tm_mday > 1) { assert_pil_to_time (VBI_PIL (tm_min.tm_mon + 1, tm_min.tm_mday - 1, tm_min.tm_hour, 59), TIME_MIN, "UTC", /* exp_result */ -1, EOVERFLOW); } else if (tm_min.tm_mon > 0) { assert_pil_to_time (VBI_PIL (tm_min.tm_mon + 1 - 1, tm_min.tm_mday - 1, tm_min.tm_hour, 59), TIME_MIN, "UTC", /* exp_result */ -1, EOVERFLOW); } } if (TIME_MAX <= 0x7FFFFFFF) { /* -1 because GNU libc timegm() appears to clamp against TIME_MAX, which is catched by libzvbi. */ t = TIME_MAX - 1; #ifdef _WIN32 assert (0 == gmtime_s (&tm_max, &t)); #else assert (NULL != gmtime_r (&t, &tm_max)); #endif assert (t == timegm (&tm_max)); p = VBI_PIL (tm_max.tm_mon + 1, tm_max.tm_mday, tm_max.tm_hour, 0), assert_pil_to_time (p, TIME_MAX, "UTC"); assert_pil_to_time (p, TIME_MAX, "UTC+1", /* exp_result */ -1, EOVERFLOW); assert ((time_t) -1 == vbi_pil_lto_to_time (p, TIME_MAX, /* seconds_east */ 3600)); assert_errno (EOVERFLOW); if (tm_max.tm_hour < 23) { assert_pil_to_time (VBI_PIL (tm_max.tm_mon + 1, tm_max.tm_mday, tm_max.tm_hour + 1, /* minute */ 0), TIME_MAX, "UTC", /* exp_result */ -1, EOVERFLOW); } else if (tm_max.tm_mday < 28) { assert_pil_to_time (VBI_PIL (tm_max.tm_mon + 1, tm_max.tm_mday + 1, tm_max.tm_hour, 0), TIME_MAX, "UTC", /* exp_result */ -1, EOVERFLOW); } else if (tm_max.tm_mon < 11) { assert_pil_to_time (VBI_PIL (tm_max.tm_mon + 1 + 1, tm_max.tm_mday + 1, tm_max.tm_hour, 0), TIME_MAX, "UTC", /* exp_result */ -1, EOVERFLOW); } } t = ztime ("20010101T000000"); assert_pil_to_time (VBI_PIL (1, 1, 0, 0), t, 2001); assert_pil_to_time (VBI_PIL (6, 30, 23, 59), t, 2001); assert_pil_to_time (VBI_PIL (7, 1, 0, 0), t, 2000); assert_pil_to_time (VBI_PIL (12, 31, 23, 59), t, 2000); t = ztime ("20010415T000000"); assert_pil_to_time (VBI_PIL (1, 1, 0, 0), t, 2001); assert_pil_to_time (VBI_PIL (6, 30, 23, 59), t, 2001); assert_pil_to_time (VBI_PIL (7, 1, 0, 0), t, 2001); assert_pil_to_time (VBI_PIL (12, 31, 23, 59), t, 2000); t = ztime ("20010630T000000"); assert_pil_to_time (VBI_PIL (1, 1, 0, 0), t, 2001); assert_pil_to_time (VBI_PIL (6, 30, 23, 59), t, 2001); assert_pil_to_time (VBI_PIL (7, 1, 0, 0), t, 2001); assert_pil_to_time (VBI_PIL (12, 31, 23, 59), t, 2000); t = ztime ("20010701T000000"); assert_pil_to_time (VBI_PIL (1, 1, 0, 0), t, 2001); assert_pil_to_time (VBI_PIL (6, 30, 23, 59), t, 2001); assert_pil_to_time (VBI_PIL (7, 1, 0, 0), t, 2001); assert_pil_to_time (VBI_PIL (12, 31, 23, 59), t, 2001); t = ztime ("20010915T000000"); assert_pil_to_time (VBI_PIL (1, 1, 0, 0), t, 2002); assert_pil_to_time (VBI_PIL (6, 30, 23, 59), t, 2001); assert_pil_to_time (VBI_PIL (7, 1, 0, 0), t, 2001); assert_pil_to_time (VBI_PIL (12, 31, 23, 59), t, 2001); t = ztime ("20011231T000000"); assert_pil_to_time (VBI_PIL (1, 1, 0, 0), t, 2002); assert_pil_to_time (VBI_PIL (6, 30, 23, 59), t, 2001); assert_pil_to_time (VBI_PIL (7, 1, 0, 0), t, 2001); assert_pil_to_time (VBI_PIL (12, 31, 23, 59), t, 2001); /* GMT */ assert (ztime ("20010215T200000") == vbi_pil_to_time (VBI_PIL (2, 15, 20, 0), t1, "Europe/London")); assert (ztime ("20010215T200000") == vbi_pil_lto_to_time (VBI_PIL (2, 15, 20, 0), t1, 0)); /* CET (UTC + 1h) */ assert (ztime ("20010215T190000") == vbi_pil_to_time (VBI_PIL (2, 15, 20, 0), t1, "Europe/Paris")); assert (ztime ("20010215T190000") == vbi_pil_lto_to_time (VBI_PIL (2, 15, 20, 0), t1, 3600)); /* CEST (UTC + 2h) */ assert (ztime ("20010715T180000") == vbi_pil_to_time (VBI_PIL (7, 15, 20, 0), ztime ("20010701T000000"), "Europe/Paris")); /* CET because PIL month 2; year 2001 because 8 - 2 <= 6. */ assert (ztime ("20010215T190000") == vbi_pil_to_time (VBI_PIL (2, 15, 20, 0), ztime ("20010831T210000"), "Europe/Paris")); /* CET because PIL month 2; year 2002 because 'start' is already 2001-09-01 01:00 in CEST zone. */ assert (ztime ("20020215T190000") == vbi_pil_to_time (VBI_PIL (2, 15, 20, 0), ztime ("20010831T230000"), "Europe/Paris")); /* XXX Maybe other DST conventions should be tested: http://en.wikipedia.org/wiki/Daylight_saving_time_around_the_world */ } static void test_pil_is_valid_date (void) { unsigned int i; for (i = 0; i < N_ELEMENTS (valid_dates); ++i) { assert (vbi_pil_is_valid_date (valid_dates[i])); } assert (vbi_pil_is_valid_date (VBI_PIL (2, 29, 0, 0))); for (i = 0; i < N_ELEMENTS (invalid_dates); ++i) { assert (!vbi_pil_is_valid_date (invalid_dates[i])); } assert (vbi_pil_is_valid_date (VBI_PIL (1, 1, 0, 0) | ~max_pil)); } int main (void) { test_pil_is_valid_date (); test_pil_to_time (); test_pil_validity_window (); test_pty_validity_window (); test_pil_from_string (); return 0; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/test-pdc.h000066400000000000000000000076401476363111200154410ustar00rootroot00000000000000/* * libzvbi -- PDC functions unit test * * Copyright (C) 2008 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-pdc.h,v 1.2 2016-10-17 20:48:27 mschimek Exp $ */ #include #include #include "src/pdc.h" #include "test-common.h" #ifdef _WIN32 #include "src/strptime.h" #define timegm _mkgmtime #endif static const vbi_pil max_pil = VBI_PIL (15, 31, 31, 63); class test_pid : public vbi_program_id { void assert_valid_any (void) { assert ((unsigned int) this->pil <= max_pil); assert (0 == (this->luf & ~1)); assert (0 == (this->mi & ~1)); assert (0 == (this->prf & ~1)); assert ((unsigned int) this->pcs_audio <= 3); assert ((unsigned int) this->pty <= 0xFF); assert (0 == (this->tape_delayed & ~1)); assert (0 == memcmp_zero (this->_reserved2, sizeof (this->_reserved2))); assert (0 == memcmp_zero (this->_reserved3, sizeof (this->_reserved3))); } void assert_valid_simple (void) { assert_valid_any (); assert (0 == this->luf); assert (1 == this->mi); assert (0 == this->prf); } public: void clear (void) { memset (this, 0, sizeof (*this)); } void randomize (void) { memset_rand (this, sizeof (*this)); } void populate_dvb (void) { randomize (); this->pil &= max_pil; } void populate_vps (void) { populate_dvb (); this->cni &= 0xFFF; this->pcs_audio = (vbi_pcs_audio) ((int) this->pcs_audio & 3); this->pty &= 0xFF; } void populate_ttx (void) { populate_vps (); this->channel = (vbi_pid_channel) ((int) this->channel & 3); this->luf &= 1; this->mi &= 1; this->prf &= 1; } void populate_xds (void) { randomize (); this->pil &= max_pil; this->tape_delayed &= 1; } bool operator == (const test_pid& other) const { /* Note: bitwise equal. */ return (0 == memcmp (this, &other, sizeof (*this))); } void assert_valid_ttx (void) { assert_valid_any (); assert (this->channel >= VBI_PID_CHANNEL_LCI_0 && this->channel <= VBI_PID_CHANNEL_LCI_3); assert (VBI_CNI_TYPE_8302 == this->cni_type); assert (0 == this->tape_delayed); } void assert_valid_vps (void) { assert_valid_simple (); assert (VBI_PID_CHANNEL_VPS == this->channel); assert (VBI_CNI_TYPE_VPS == this->cni_type); assert (0 == this->tape_delayed); } void assert_valid_dvb (void) { assert_valid_simple (); assert (VBI_PID_CHANNEL_PDC_DESCRIPTOR == this->channel); assert (VBI_CNI_TYPE_NONE == this->cni_type); assert (0 == this->cni); assert (VBI_PCS_AUDIO_UNKNOWN == this->pcs_audio); assert (0 == this->pty); assert (0 == this->tape_delayed); } void assert_valid_xds (void) { assert_valid_simple (); assert (VBI_PID_CHANNEL_XDS_CURRENT == this->channel || VBI_PID_CHANNEL_XDS_FUTURE == this->channel); assert (VBI_CNI_TYPE_NONE == this->cni_type); assert (0 == this->cni); assert (VBI_PCS_AUDIO_UNKNOWN == this->pcs_audio); assert (0 == this->pty); } }; static time_t ztime (const char * s) _vbi_unused; static time_t ztime (const char * s) { struct tm tm; time_t t; memset (&tm, 0, sizeof (tm)); assert (NULL != strptime (s, "%Y%m%dT%H%M%S", &tm)); t = timegm (&tm); assert ((time_t) -1 != t); return t; } #define ANY_TIME (TIME_MAX - 12345) /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/test-raw_decoder.cc000066400000000000000000000445431476363111200173120ustar00rootroot00000000000000/* * libzvbi - vbi_raw_decoder unit test * * Copyright (C) 2004, 2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-raw_decoder.cc,v 1.4 2008-03-01 07:35:48 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "src/version.h" #if 2 == VBI_VERSION_MINOR # include "src/raw_decoder.h" # include "src/io-sim.h" # define N_ELEMENTS(array) (sizeof (array) / sizeof (*(array))) # define vbi_pixfmt_bytes_per_pixel(pf) VBI_PIXFMT_BPP(pf) # define VBI_PIXFMT_IS_YUV(pf) (0 != (VBI_PIXFMT_SET (pf) \ & VBI_PIXFMT_SET_YUV)) #else # include "src/misc.h" # include "src/zvbi.h" #endif #include "test-common.h" bool verbose; typedef struct { vbi_service_set service; /* Scan lines. */ unsigned int first; unsigned int last; } block; #define BLOCK_END { 0, 0, 0 } static void dump_hex (const uint8_t * p, unsigned int n) { while (n-- > 0) fprintf (stderr, "%02x ", *p++); } static void sliced_rand_lines (const vbi_sliced * s_start, const vbi_sliced * s_end, vbi_sliced * s, vbi_service_set service, unsigned int first_line, unsigned int last_line) { unsigned int line; for (line = first_line; line <= last_line; ++line) { const vbi_sliced *t; assert (s < s_end); for (t = s_start; t < s; ++t) assert (t->line != line); s->id = service; s->line = line; memset_rand (s->data, sizeof (s->data)); ++s; } } static unsigned int sliced_rand (vbi_sliced * s, unsigned int s_lines, const block * b) { const vbi_sliced *s_start; vbi_service_set services; s_start = s; services = 0; while (b->service) { services |= b->service; if (b->first > 0) { sliced_rand_lines (s_start, s_start + s_lines, s, b->service, b->first, b->last); s += b->last - b->first + 1; } ++b; } if (0) fprintf (stderr, "services 0x%08x\n", services); return s - s_start; } static void dump_sliced_pair (const vbi_sliced * s1, const vbi_sliced * s2, unsigned int n_lines) { unsigned int i; for (i = 0; i < n_lines; ++i) fprintf (stderr, "%2u: " "%30s %3u %02x %02x %02x <-> " "%30s %3u %02x %02x %02x\n", i, vbi_sliced_name (s1[i].id), s1[i].line, s1[i].data[0], s1[i].data[1], s1[i].data[2], vbi_sliced_name (s2[i].id), s2[i].line, s2[i].data[0], s2[i].data[1], s2[i].data[2]); } static unsigned int create_raw (uint8_t ** raw, vbi_sliced ** sliced, const vbi_sampling_par *sp, const block * b, unsigned int pixel_mask, unsigned int raw_flags) { unsigned int scan_lines; unsigned int sliced_lines; unsigned int raw_size; unsigned int blank_level; unsigned int black_level; unsigned int white_level; vbi_bool success; scan_lines = sp->count[0] + sp->count[1]; raw_size = sp->bytes_per_line * scan_lines; *raw = (uint8_t *) malloc (raw_size); assert (NULL != *raw); *sliced = (vbi_sliced *) malloc (sizeof (**sliced) * 50); assert (NULL != *sliced); sliced_lines = sliced_rand (*sliced, 50, b); /* Use defaults. */ blank_level = 0; black_level = 0; white_level = 0; if (pixel_mask) { memset_rand (*raw, raw_size); success = _vbi_raw_video_image (*raw, raw_size, sp, blank_level, black_level, white_level, pixel_mask, raw_flags, *sliced, sliced_lines); assert (success); } else { success = _vbi_raw_vbi_image (*raw, raw_size, sp, blank_level, white_level, raw_flags, *sliced, sliced_lines); assert (success); if (raw_flags & _VBI_RAW_NOISE_2) { static uint32_t seed = 12345678; /* Shape as in capture_stream_sim_add_noise(). */ success = vbi_raw_add_noise (*raw, sp, /* min_freq */ 0, /* max_freq */ 5000000, /* amplitude */ 25, seed); assert (success); seed = seed * 1103515245 + 56789; } } return sliced_lines; } static vbi3_raw_decoder * create_decoder (const vbi_sampling_par *sp, const block * b, unsigned int strict) { vbi3_raw_decoder *rd; vbi_service_set in_services; vbi_service_set out_services; in_services = 0; while (b->service) { in_services |= b->service; ++b; } rd = vbi3_raw_decoder_new (sp); assert (NULL != rd); if (sp->synchronous) { #if 2 == VBI_VERSION_MINOR vbi3_raw_decoder_set_log_fn (rd, vbi_log_on_stderr, /* user_data */ NULL, (vbi_log_mask)(VBI_LOG_INFO * 2 - 1)); #else vbi3_raw_decoder_set_log_fn (rd, (vbi_log_mask)(VBI_LOG_INFO * 2 - 1), vbi_log_on_stderr, /* user_data */ NULL); #endif } else { /* Don't complain about expected failures. XXX Check for those in a different function. */ } out_services = vbi3_raw_decoder_add_services (rd, in_services, strict); if (!sp->synchronous) { /* Ambiguous. */ in_services &= ~(VBI_SLICED_VPS | VBI_SLICED_VPS_F2 | VBI_SLICED_WSS_625 | VBI_SLICED_CAPTION_625 | VBI_SLICED_CAPTION_525); } assert (in_services == out_services); return rd; } static void compare_payload (const vbi_sliced * in, const vbi_sliced * out) { unsigned int payload; payload = vbi_sliced_payload_bits (out->id); if (0 != memcmp (in->data, out->data, payload >> 3)) { dump_sliced_pair (in, out, /* n_lines */ 1); assert (0); } if (payload & 7) { unsigned int mask = (1 << (payload & 7)) - 1; payload = (payload >> 3); /* MSBs zero, rest as sent */ assert (0 == ((in->data[payload] & mask) ^ out->data[payload])); } } static void compare_sliced (const vbi_sampling_par *sp, const vbi_sliced * in, const vbi_sliced * out, const vbi_sliced * old, unsigned int in_lines, unsigned int out_lines, unsigned int old_lines) { unsigned int i; unsigned int min; unsigned int id; vbi_sliced *in1; vbi_sliced *s; min = 0; for (i = 0; i < out_lines; ++i) { unsigned int payload; if (sp->synchronous) { /* Ascending line numbers. */ assert (out[i].line > min); min = out[i].line; } else { /* Could be first or second field, we don't know. */ assert (0 == out[i].line); } /* Valid service id. */ assert (0 != out[i].id); payload = (vbi_sliced_payload_bits (out[i].id) + 7) >> 3; assert (payload > 0); /* vbi_sliced big enough. */ assert (payload <= sizeof (out[i].data)); /* Writes more than payload. */ assert (0 == memcmp (out[i].data + payload, old[i].data + payload, sizeof (out[i].data) - payload)); } /* Respects limits. */ assert (0 == memcmp (out + out_lines, old + out_lines, sizeof (*old) * (old_lines - out_lines))); in1 = (vbi_sliced *) xmemdup (in, sizeof (*in) * in_lines); for (i = 0; i < out_lines; ++i) { if (sp->synchronous) { for (s = in1; s < in1 + in_lines; ++s) if (s->line == out[i].line) break; /* Found something we didn't send. */ assert (s < in1 + in_lines); /* Identified as something else. */ /* fprintf (stderr, "%3u id %08x %08x\n", s->line, s->id, out[i].id); */ assert (s->id == out[i].id); } else { /* fprintf (stderr, "%08x ", out[i].id); */ /* No line numbers, but data must be in same order. */ for (s = in1; s < in1 + in_lines; ++s) if (s->id == out[i].id) break; assert (s < in1 + in_lines); /* fprintf (stderr, "from line %3u\n", s->line); */ } compare_payload (s, &out[i]); s->id = 0; } id = 0; for (s = in1; s < in1 + in_lines; ++s) id |= s->id; if (!sp->synchronous) { /* Ok these are ambiguous. */ id &= ~(VBI_SLICED_VPS | VBI_SLICED_VPS_F2 | VBI_SLICED_WSS_625 | VBI_SLICED_CAPTION_625 | VBI_SLICED_CAPTION_525); } /* Anything missed? */ assert (0 == id); free (in1); in1 = NULL; } static void test_cycle (const vbi_sampling_par *sp, const block * b, unsigned int pixel_mask, unsigned int raw_flags, unsigned int strict) { vbi_sliced *in; vbi_sliced out[50]; vbi_sliced old[50]; uint8_t *raw; vbi3_raw_decoder *rd; unsigned int in_lines; unsigned int out_lines; in_lines = create_raw (&raw, &in, sp, b, pixel_mask, raw_flags); if (verbose) dump_hex (raw + 120, 12); rd = create_decoder (sp, b, strict); memset_rand (out, sizeof (out)); memcpy (old, out, sizeof (old)); out_lines = vbi3_raw_decoder_decode (rd, out, 40, raw); if (verbose) { #if 2 == VBI_VERSION_MINOR fprintf (stderr, "%s %08x in=%u out=%u\n", __FUNCTION__, sp->sampling_format, in_lines, out_lines); #else fprintf (stderr, "%s %s in=%u out=%u\n", __FUNCTION__, vbi_pixfmt_name (sp->sample_format), in_lines, out_lines); #endif } if (sp->synchronous) { if (verbose && in_lines != out_lines) dump_sliced_pair (in, out, MIN (in_lines, out_lines)); assert (in_lines == out_lines); } compare_sliced (sp, in, out, old, in_lines, out_lines, 50); vbi3_raw_decoder_delete (rd); free (in); free (raw); } static vbi_bool block_contains_service (const block * b, vbi_service_set services, vbi_bool exclusive) { vbi_service_set all_services = 0; assert (0 != services); while (b->service) { all_services |= b->service; ++b; } if (0 == (all_services & services)) return FALSE; if (exclusive && 0 != (all_services & ~services)) return FALSE; return TRUE; } static void test_vbi (const vbi_sampling_par *sp, const block * b, unsigned int strict) { test_cycle (sp, b, /* pixel_mask */ 0, /* raw_flags */ 0, strict); /* Tests incorrect signal shape reported by Rich Kadel. */ if (block_contains_service (b, VBI_SLICED_CAPTION_525, /* exclusive */ FALSE)) test_cycle (sp, b, /* pixel_mask */ 0, _VBI_RAW_SHIFT_CC_CRI, strict); /* Tests low amplitude CC signals reported by Rich Kadel. */ if (block_contains_service (b, VBI_SLICED_CAPTION_525, /* exclusive */ TRUE) && sp->sampling_rate >= 27000000) { unsigned int i; /* Repeat because the noise varies. */ for (i = 0; i < 1000; ++i) { test_cycle (sp, b, /* pixel_mask */ 0, _VBI_RAW_LOW_AMP_CC | _VBI_RAW_NOISE_2, strict); } } } static void test_video (const vbi_sampling_par *sp, const block * b, unsigned int strict) { vbi_sampling_par sp2; unsigned int pixel_mask; vbi_pixfmt pixfmt; unsigned int samples_per_line; sp2 = *sp; #if 2 == VBI_VERSION_MINOR samples_per_line = sp->bytes_per_line / vbi_pixfmt_bytes_per_pixel (sp->sampling_format); #else samples_per_line = sp->samples_per_line; #endif for (pixfmt = (vbi_pixfmt) 0; pixfmt < VBI_MAX_PIXFMTS; pixfmt = (vbi_pixfmt)(pixfmt + 1)) { if (0 == (VBI_PIXFMT_SET_ALL & VBI_PIXFMT_SET (pixfmt))) continue; #if 2 == VBI_VERSION_MINOR sp2.sampling_format = pixfmt; #else sp2.sample_format = pixfmt; #endif sp2.bytes_per_line = samples_per_line * vbi_pixfmt_bytes_per_pixel (pixfmt); /* Check bit slicer looks at Y/G */ if (VBI_PIXFMT_IS_YUV (pixfmt)) pixel_mask = 0xFF; else pixel_mask = 0xFF00; test_cycle (&sp2, b, pixel_mask, /* raw_flags */ 0, strict); if (block_contains_service (b, VBI_SLICED_CAPTION_525, /* exclusive */ FALSE)) test_cycle (&sp2, b, pixel_mask, _VBI_RAW_SHIFT_CC_CRI, strict); } } static const block ttx_a [] = { { VBI_SLICED_TELETEXT_A, 6, 22 }, { VBI_SLICED_TELETEXT_A, 318, 335 }, BLOCK_END, }; static const block ttx_c_625 [] = { { VBI_SLICED_TELETEXT_C_625, 6, 22 }, { VBI_SLICED_TELETEXT_C_625, 318, 335 }, BLOCK_END, }; static const block ttx_d_625 [] = { { VBI_SLICED_TELETEXT_D_625, 6, 22 }, { VBI_SLICED_TELETEXT_D_625, 318, 335 }, BLOCK_END, }; static const block ttx_wss_cc_625 [] = { { VBI_SLICED_TELETEXT_B_625, 6, 21 }, { VBI_SLICED_CAPTION_625, 22, 22 }, { VBI_SLICED_WSS_625, 23, 23 }, { VBI_SLICED_TELETEXT_B_625, 318, 334 }, { VBI_SLICED_CAPTION_625, 335, 335 }, BLOCK_END, }; static const block hi_f1_625 [] = { { VBI_SLICED_VPS, 16, 16 }, { VBI_SLICED_CAPTION_625_F1, 22, 22 }, { VBI_SLICED_WSS_625, 23, 23 }, BLOCK_END, }; static const block hi_f2_525 [] = { { VBI_SLICED_CAPTION_525_F2, 284, 284 }, BLOCK_END, }; static const block vps_wss_cc_625 [] = { { VBI_SLICED_VPS, 16, 16 }, { VBI_SLICED_CAPTION_625, 22, 22 }, { VBI_SLICED_WSS_625, 23, 23 }, { VBI_SLICED_CAPTION_625, 335, 335 }, BLOCK_END, }; static const block cc_625 [] = { { VBI_SLICED_CAPTION_625, 22, 22 }, { VBI_SLICED_CAPTION_625, 335, 335 }, BLOCK_END, }; static const block ttx_c_525 [] = { { VBI_SLICED_TELETEXT_C_525, 10, 21 }, { VBI_SLICED_TELETEXT_C_525, 272, 284 }, BLOCK_END, }; static const block ttx_d_525 [] = { { VBI_SLICED_TELETEXT_D_525, 10, 21 }, { VBI_SLICED_TELETEXT_D_525, 272, 284 }, BLOCK_END, }; static const block hi_525 [] = { { VBI_SLICED_TELETEXT_B_525, 10, 20 }, { VBI_SLICED_CAPTION_525, 21, 21 }, { VBI_SLICED_TELETEXT_B_525, 272, 283 }, { VBI_SLICED_CAPTION_525, 284, 284 }, BLOCK_END, }; static const block cc_525 [] = { { VBI_SLICED_CAPTION_525, 21, 21 }, { VBI_SLICED_CAPTION_525, 284, 284 }, BLOCK_END, }; static void test2 (const vbi_sampling_par *sp) { #if 2 == VBI_VERSION_MINOR if (625 == sp->scanning) { #else if (sp->videostd_set & VBI_VIDEOSTD_SET_625_50) { #endif if (sp->sampling_rate >= 13500000) { vbi_sampling_par sp1; /* We cannot mix Teletext standards; bit rate and FRC are too similar to reliable distinguish. */ test_vbi (sp, ttx_a, 1); test_vbi (sp, ttx_c_625, 1); /* Needs sampling beyond 0H + 63 us (?) */ #if 2 == VBI_VERSION_MINOR if (sp->bytes_per_line == 2048 * VBI_PIXFMT_BPP (sp->sampling_format)) test_vbi (sp, ttx_d_625, 1); #else if (sp->bytes_per_line == 2048 * vbi_pixfmt_bytes_per_pixel (sp->sample_format)) test_vbi (sp, ttx_d_625, 1); #endif test_vbi (sp, ttx_wss_cc_625, 1); test_video (sp, ttx_wss_cc_625, 1); /* For low_pass_bit_slicer test. */ test_vbi (sp, vps_wss_cc_625, 1); if (!sp->interlaced) { sp1 = *sp; sp1.start[1] = 0; sp1.count[1] = 0; test_vbi (&sp1, hi_f1_625, 2); } } else if (sp->sampling_rate >= 5000000) { test_vbi (sp, vps_wss_cc_625, 1); test_video (sp, vps_wss_cc_625, 1); } else { /* WSS not possible below 5 MHz due to a cri_rate check in bit_slicer_init(), but much less won't work anyway. */ test_vbi (sp, cc_625, 1); test_video (sp, cc_625, 1); } } else { if (sp->sampling_rate >= 13500000) { vbi_sampling_par sp1; test_vbi (sp, ttx_c_525, 1); test_vbi (sp, ttx_d_525, 1); test_vbi (sp, hi_525, 1); test_video (sp, hi_525, 1); /* CC only for the low-amp CC test. */ test_vbi (sp, cc_525, 1); if (!sp->interlaced) { sp1 = *sp; sp1.start[0] = 0; sp1.count[0] = 0; test_vbi (&sp1, hi_f2_525, 2); } } else { test_vbi (sp, cc_525, 1); test_video (sp, cc_525, 1); } } } static void test1 (const vbi_sampling_par *sp) { static const struct { unsigned int sampling_rate; unsigned int samples_per_line; } res [] = { /* bt8x8 PAL ~35.5 MHz / 2048 bt8x8 NTSC ~28.6 MHz / 2048 PAL 1:1 ~14.7 MHz / 768 ITU-R BT.601 13.5 MHz / 720 NTSC 1:1 ~12.3 MHz / 640 */ { 35468950, 2048 }, { 27000000, 1440 }, { 13500000, 720 }, { 3000000, 176 }, }; vbi_sampling_par sp2; unsigned int i; for (i = 0; i < N_ELEMENTS (res); ++i) { if (verbose) fprintf (stderr, "%.2f MHz %u spl\n", res[i].sampling_rate / 1e6, res[i].samples_per_line); sp2 = *sp; sp2.sampling_rate = res[i].sampling_rate; #if 2 == VBI_VERSION_MINOR sp2.bytes_per_line = res[i].samples_per_line * vbi_pixfmt_bytes_per_pixel (sp2.sampling_format); #else sp2.samples_per_line = res[i].samples_per_line; sp2.bytes_per_line = res[i].samples_per_line * vbi_pixfmt_bytes_per_pixel (sp2.sample_format); #endif sp2.offset = (int)(9.7e-6 * sp2.sampling_rate); test2 (&sp2); } } static void test_services (void) { vbi_sampling_par sp; vbi_service_set set; memset (&sp, 0x55, sizeof (sp)); set = vbi_sampling_par_from_services (&sp, /* &max_rate */ NULL, VBI_VIDEOSTD_SET_625_50, ~0 & ~VBI_SLICED_VBI_625); assert (set == (VBI_SLICED_TELETEXT_A | VBI_SLICED_TELETEXT_B_625 | VBI_SLICED_TELETEXT_C_625 | VBI_SLICED_TELETEXT_D_625 | VBI_SLICED_VPS | VBI_SLICED_VPS_F2 | VBI_SLICED_CAPTION_625 | VBI_SLICED_WSS_625)); test2 (&sp); set = vbi_sampling_par_from_services (&sp, /* &max_rate */ NULL, VBI_VIDEOSTD_SET_525_60, ~0 & ~VBI_SLICED_VBI_525); assert (set == (VBI_SLICED_TELETEXT_B_525 | VBI_SLICED_TELETEXT_C_525 | VBI_SLICED_TELETEXT_D_525 | VBI_SLICED_CAPTION_525 | VBI_SLICED_2xCAPTION_525 /* Needs fix */ /* | VBI_SLICED_WSS_CPR1204 */ )); test2 (&sp); } static void test_line_order (vbi_bool synchronous) { vbi_sampling_par sp; memset (&sp, 0x55, sizeof (sp)); #if 2 == VBI_VERSION_MINOR sp.scanning = 625; sp.sampling_format = VBI_PIXFMT_YUV420; #else sp.videostd_set = VBI_VIDEOSTD_SET_PAL_BG; sp.sample_format = VBI_PIXFMT_YUV420; #endif sp.start[0] = 6; sp.count[0] = 23 - 6 + 1; sp.start[1] = 318; sp.count[1] = 335 - 318 + 1; sp.interlaced = FALSE; sp.synchronous = synchronous; test1 (&sp); sp.interlaced = TRUE; test1 (&sp); #if 2 == VBI_VERSION_MINOR sp.scanning = 525; sp.sampling_format = VBI_PIXFMT_YUV420; #else sp.videostd_set = VBI_VIDEOSTD_SET_NTSC; sp.sample_format = VBI_PIXFMT_YUV420; #endif sp.start[0] = 10; sp.count[0] = 21 - 10 + 1; sp.start[1] = 272; sp.count[1] = 284 - 272 + 1; sp.interlaced = FALSE; sp.synchronous = synchronous; test1 (&sp); } int main (int argc, char ** argv) { argv = argv; verbose = (argc > 1); test_services (); test_line_order (/* synchronous */ TRUE); test_line_order (/* synchronous */ FALSE); /* More... */ return 0; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/test-unicode000077500000000000000000000002671476363111200160740ustar00rootroot00000000000000#!/bin/sh # Regression test for Teletext and Closed Caption to # Unicode conversion functions. ./unicode > unicode-out.txt || exit 1 cmp unicode-out.txt "$srcdir/unicode-out-ref.txt" zvbi-0.2.44/test/test-unicode_windows000077500000000000000000000002661476363111200176450ustar00rootroot00000000000000#!/bin/sh # Regression test for Teletext and Closed Caption to # Unicode conversion functions. wine unicode.exe > unicode-out.txt || exit 1 cmp unicode-out.txt "unicode-out-ref.txt" zvbi-0.2.44/test/test-vps.cc000066400000000000000000000204031476363111200156310ustar00rootroot00000000000000/* * libzvbi -- VPS low level functions unit test * * Copyright (C) 2006, 2008 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: test-vps.cc,v 1.1 2009-03-04 21:48:17 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "src/vps.h" #include "test-pdc.h" #define N_ELEMENTS(array) (sizeof (array) / sizeof (*(array))) static const unsigned int valid_cnis [] = { 0x000, 0x001, 0x004, 0x010, 0x040, 0x100, 0x400, 0x5A5, 0xA5A, 0xFFF }; static const uint8_t vps_sample [13] = { 0xB1, 0x04, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1011 0001 0000 0100 1010 0000 aa cccc 10 fedc */ 0xC3, 0x76, 0x3F, 0x41, 0xFF /* 1100 0011 0111 0110 0011 1111 0100 0001 1111 1111 ccdd dddm mmmh hhhh mmmm mmcc cccc cccc pppp pppp 7643 2103 2104 3210 5432 10ba 9854 3210 7654 3210 */ }; static void assert_decode_vps_cni (unsigned int * cni, const uint8_t buffer[13]) { uint8_t buffer2[13]; unsigned int cni2; memcpy (buffer2, buffer, sizeof (buffer2)); memset_rand (cni, sizeof (*cni)); cni2 = *cni; assert (TRUE == vbi_decode_vps_cni (cni, buffer)); assert ((unsigned int) *cni <= 0xFFF); assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } static void assert_encode_vps_cni (uint8_t buffer[13], unsigned int cni, vbi_bool exp_success = TRUE) { static const uint8_t cni_bits[13] = { 0, 0, 0x0F, 0, 0, 0, 0, 0, 0xC0, 0, 0x03, 0xFF, 0 }; uint8_t buffer2[13]; unsigned int i; memset_rand (buffer2, sizeof (buffer2)); memcpy (buffer, buffer2, sizeof (buffer2)); assert (exp_success == vbi_encode_vps_cni (buffer, cni)); if (exp_success) { buffer2[2] |= 0x0F; for (i = 0; i < sizeof (buffer2); ++i) { assert (0 == ((buffer[i] ^ buffer2[i]) & ~cni_bits[i])); } } else { assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } } static void assert_decode_vps_pdc (test_pid * pid, const uint8_t buffer[13], const test_pid * exp_pid = NULL) { uint8_t buffer2[13]; test_pid pid2; unsigned int cni; memcpy (buffer2, buffer, sizeof (buffer2)); pid->randomize (); pid2 = *pid; assert (TRUE == vbi_decode_vps_pdc (pid, buffer)); pid->assert_valid_vps (); assert_decode_vps_cni (&cni, buffer); assert (cni == pid->cni); if (NULL != exp_pid) { assert (exp_pid->cni == pid->cni); assert (exp_pid->pil == pid->pil); assert (exp_pid->pcs_audio == pid->pcs_audio); assert (exp_pid->pty == pid->pty); } assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } static void assert_encode_vps_pdc (uint8_t buffer[13], const test_pid * pid, vbi_bool exp_success = TRUE) { static const uint8_t pdc_bits[13] = { 0, 0, 0xFF, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; uint8_t buffer2[13]; test_pid pid2; unsigned int i; pid2 = *pid; memset_rand (buffer2, sizeof (buffer2)); memcpy (buffer, buffer2, sizeof (buffer2)); assert (exp_success == vbi_encode_vps_pdc (buffer, pid)); if (exp_success) { buffer2[2] |= 0x0F; for (i = 0; i < 13; ++i) { assert (0 == ((buffer[i] ^ buffer2[i]) & ~pdc_bits[i])); } } else { assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } assert (pid2 == *pid); } static void assert_decode_dvb_pdc_descriptor (test_pid * pid, const uint8_t buffer[5], vbi_bool exp_success = TRUE, const test_pid * exp_pid = NULL) { uint8_t buffer2[5]; test_pid pid2; memcpy (buffer2, buffer, sizeof (buffer2)); pid->randomize (); pid2 = *pid; assert (exp_success == vbi_decode_dvb_pdc_descriptor (pid, buffer)); if (exp_success) { pid->assert_valid_dvb (); if (NULL != exp_pid) { assert (exp_pid->pil == pid->pil); } } else { assert (pid2 == *pid); } assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } static void assert_encode_dvb_pdc_descriptor (uint8_t buffer[5], const test_pid * pid, vbi_bool exp_success = TRUE) { uint8_t buffer2[5]; test_pid pid2; pid2 = *pid; memset_rand (buffer2, sizeof (buffer2)); memcpy (buffer, buffer2, sizeof (buffer2)); assert (exp_success == vbi_encode_dvb_pdc_descriptor (buffer, pid)); if (exp_success) { /* EN 300 468 section 6.1, 6.2. */ assert (0x69 == buffer[0]); assert (3 == buffer[1]); /* EN 300 468 section 3.1. */ assert (0xF0 == (buffer[2] & 0xF0)); } else { assert (0 == memcmp (buffer, buffer2, sizeof (buffer2))); } assert (pid2 == *pid); } int main (void) { uint8_t buffer1[13]; test_pid pid1; test_pid pid2; unsigned int cni; unsigned int i; for (i = 0; i < N_ELEMENTS (valid_cnis); ++i) { assert_encode_vps_cni (buffer1, valid_cnis[i]); assert_decode_vps_cni (&cni, buffer1); assert (cni == valid_cnis[i]); } assert_decode_vps_cni (&cni, vps_sample); assert (0x0DC1 == cni); /* TR 101 231. */ assert_encode_vps_cni (buffer1, 0xDC3); buffer1[5 - 3] &= ~(0x80 >> 3); assert_decode_vps_cni (&cni, buffer1); assert (0xDC2 == cni); /* ZDF */ buffer1[5 - 3] |= 0x80 >> 3; assert_decode_vps_cni (&cni, buffer1); assert (0xDC1 == cni); /* ARD */ assert_encode_vps_cni (buffer1, 0x1000, FALSE); assert_encode_vps_cni (buffer1, INT_MIN, FALSE); assert_encode_vps_cni (buffer1, INT_MAX, FALSE); assert_encode_vps_cni (buffer1, UINT_MAX, FALSE); for (i = 0; i < 1000; ++i) { pid1.populate_vps (); assert_encode_vps_pdc (buffer1, &pid1); assert_decode_vps_cni (&cni, buffer1); assert (cni == pid1.cni); assert_decode_vps_pdc (&pid2, buffer1, &pid1); memset_rand (buffer1, sizeof (buffer1)); assert_decode_vps_pdc (&pid2, buffer1); pid1.randomize (); pid1.pil &= max_pil; assert_encode_dvb_pdc_descriptor (buffer1, &pid1); assert_decode_dvb_pdc_descriptor (&pid2, buffer1, TRUE, &pid1); memset_rand (buffer1, sizeof (buffer1)); /* EN 300 468 section 6.1, 6.2. */ buffer1[0] = 0x69; buffer1[1] = 3; assert_decode_dvb_pdc_descriptor (&pid2, buffer1); } assert_decode_vps_pdc (&pid1, vps_sample); assert (0xDC1 == pid1.cni); assert (VBI_PIL (0x0B, 0x01, 0x16, 0x0F) == pid1.pil); assert (0x02 == pid1.pcs_audio); assert (0xFF == pid1.pty); pid1.populate_vps (); pid1.cni = 0x1000; assert_encode_vps_pdc (buffer1, &pid1, FALSE); pid1.cni = UINT_MAX; assert_encode_vps_pdc (buffer1, &pid1, FALSE); /* TR 101 231. */ pid1.populate_vps (); pid1.cni = 0xDC3; assert_encode_vps_pdc (buffer1, &pid1); buffer1[5 - 3] &= ~(0x80 >> 3); assert_decode_vps_pdc (&pid1, buffer1); assert (0xDC2 == pid1.cni); buffer1[5 - 3] |= 0x80 >> 3; assert_decode_vps_pdc (&pid1, buffer1); assert (0xDC1 == pid1.cni); pid1.populate_vps (); pid1.pil = max_pil + 1; assert_encode_vps_pdc (buffer1, &pid1, FALSE); pid1.pil = UINT_MAX; assert_encode_vps_pdc (buffer1, &pid1, FALSE); pid1.populate_vps (); pid1.pcs_audio = (vbi_pcs_audio) 4; assert_encode_vps_pdc (buffer1, &pid1, FALSE); pid1.pcs_audio = (vbi_pcs_audio) UINT_MAX; assert_encode_vps_pdc (buffer1, &pid1, FALSE); pid1.populate_vps (); pid1.pty = 0x100; assert_encode_vps_pdc (buffer1, &pid1, FALSE); pid1.pty = UINT_MAX; assert_encode_vps_pdc (buffer1, &pid1, FALSE); /* EN 300 468 section 6.1, 6.2. */ memset_rand (buffer1, sizeof (buffer1)); buffer1[0] = 0x69; buffer1[1] = 2; assert_decode_dvb_pdc_descriptor (&pid2, buffer1, FALSE); buffer1[1] = 4; assert_decode_dvb_pdc_descriptor (&pid2, buffer1, FALSE); buffer1[0] = 0x6a; buffer1[1] = 3; assert_decode_dvb_pdc_descriptor (&pid2, buffer1, FALSE); pid1.randomize (); pid1.pil = max_pil + 1; assert_encode_dvb_pdc_descriptor (buffer1, &pid1, FALSE); pid1.pil = UINT_MAX; assert_encode_dvb_pdc_descriptor (buffer1, &pid1, FALSE); return 0; } /* Local variables: c-set-style: K&R c-basic-offset: 8 End: */ zvbi-0.2.44/test/ttxfilter.c000066400000000000000000000223361476363111200157350ustar00rootroot00000000000000/* * zvbi-ttxfilter -- Teletext filter * * Copyright (C) 2005-2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: ttxfilter.c,v 1.18 2008-03-01 07:35:40 mschimek Exp $ */ /* For libzvbi version 0.2.x / 0.3.x. */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_LONG # include #endif #include "src/sliced_filter.h" #include "sliced.h" #define PROGRAM_NAME "zvbi-ttxfilter" #undef _ #define _(x) x /* TODO */ static const char * option_in_file_name; static enum file_format option_in_file_format; static unsigned int option_in_ts_pid; static const char * option_out_file_name; static vbi_bool option_experimental_output; static vbi_bool option_abort_on_error; static vbi_bool option_keep_ttx_system_pages; static double option_start_time; static double option_end_time; static struct stream * rst; static struct stream * wst; static vbi_sliced_filter * sf; /* Data is all zero, hopefully ignored due to hamming and parity error. */ static vbi_sliced sliced_blank; static vbi_bool started; static vbi_bool filter_frame (const vbi_sliced * sliced_in, unsigned int n_lines, const uint8_t * raw, const vbi_sampling_par *sp, double sample_time, int64_t stream_time) { vbi_sliced sliced_out[64]; vbi_sliced *s; unsigned int n_lines_prev_in; unsigned int n_lines_prev_out; unsigned int n_lines_in; unsigned int n_lines_out; vbi_bool success; raw = raw; /* unused */ sp = sp; if (!started) { option_start_time += sample_time; option_end_time += sample_time; started = TRUE; } if (sample_time < option_start_time || sample_time >= option_end_time) return TRUE; if (0 == n_lines) return TRUE; n_lines_prev_in = 0; n_lines_prev_out = 0; do { const unsigned int max_lines_out = N_ELEMENTS (sliced_out); n_lines_in = n_lines - n_lines_prev_in; success = vbi_sliced_filter_cor (sf, sliced_out + n_lines_prev_out, &n_lines_out, max_lines_out - n_lines_prev_out, sliced_in + n_lines_prev_in, &n_lines_in); if (success) break; error_msg (vbi_sliced_filter_errstr (sf)); if (option_abort_on_error) { exit (EXIT_FAILURE); } /* Skip the consumed lines and the broken line. */ n_lines_prev_in += n_lines_in + 1; n_lines_prev_out += n_lines_out; } while (n_lines_prev_in < n_lines); n_lines_in += n_lines_prev_in; n_lines_out += n_lines_prev_out; s = sliced_out; if (0 == n_lines_out) { if (0) { /* Decoder may assume data loss without continuous timestamps. */ s = &sliced_blank; n_lines_out = 1; } else { return TRUE; } } write_stream_sliced (wst, s, n_lines_out, /* raw */ NULL, /* sp */ NULL, sample_time, stream_time); return TRUE; } static void usage (FILE * fp) { fprintf (fp, _("\ %s %s -- Teletext filter\n\n\ Copyright (C) 2005-2007 Michael H. Schimek\n\ This program is licensed under GPLv2. NO WARRANTIES.\n\n\ Usage: %s [options] [page numbers] < sliced VBI data > sliced VBI data\n\ -h | --help | --usage Print this message and exit\n\ -q | --quiet Suppress progress and error messages\n\ -v | --verbose Increase verbosity\n\ -V | --version Print the program version and exit\n\ Input options:\n\ -i | --input name Read the VBI data from this file instead\n\ of standard input\n\ -P | --pes Source is a DVB PES stream\n\ -T | --ts pid Source is a DVB TS stream\n\ Filter options:\n\ -s | --system Keep system pages (page inventories, DRCS etc)\n\ -t | --time from-to Keep pages in this time interval, in seconds\n\ since the first frame in the stream\n\ Output options:\n\ -o | --output name Write the VBI data to this file instead of\n\ standard output\n\ Valid page numbers are 100 to 899. You can also specify a range like\n\ 150-299.\n\ "), PROGRAM_NAME, VERSION, program_invocation_name); } static const char short_options [] = "ahi:o:qst:vxPT:V"; #ifdef HAVE_GETOPT_LONG static const struct option long_options [] = { { "abort-on-error", no_argument, NULL, 'a' }, { "help", no_argument, NULL, 'h' }, { "usage", no_argument, NULL, 'h' }, { "input", required_argument, NULL, 'i' }, { "output", required_argument, NULL, 'o' }, { "quiet", no_argument, NULL, 'q' }, { "system", no_argument, NULL, 's' }, { "time", no_argument, NULL, 't' }, { "verbose", no_argument, NULL, 'v' }, { "experimental", no_argument, NULL, 'x' }, { "pes", no_argument, NULL, 'P' }, { "ts", required_argument, NULL, 'T' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, 0, 0 } }; #else # define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; static void parse_option_time (void) { const char *s = optarg; char *end; assert (NULL != optarg); option_start_time = strtod (s, &end); s = end; while (isspace (*s)) ++s; if ('-' != *s++) goto invalid; option_end_time = strtod (s, &end); s = end; if (option_start_time < 0 || option_end_time < 0 || option_end_time <= option_start_time) goto invalid; return; invalid: error_exit (_("Invalid time range '%s'."), optarg); } static vbi_bool valid_pgno (vbi_pgno pgno) { return (vbi_is_bcd (pgno) && pgno >= 0x100 && pgno <= 0x899); } static void invalid_pgno_exit (const char * arg) { error_exit (_("Invalid page number '%s'."), arg); } static void parse_page_numbers (unsigned int argc, char ** argv) { unsigned int i; for (i = 0; i < argc; ++i) { vbi_pgno first_pgno; vbi_pgno last_pgno; vbi_bool success; const char *s; char *end; s = argv[i]; first_pgno = strtoul (s, &end, 16); s = end; if (!valid_pgno (first_pgno)) invalid_pgno_exit (argv[i]); last_pgno = first_pgno; while (*s && isspace (*s)) ++s; if ('-' == *s) { ++s; while (*s && isspace (*s)) ++s; last_pgno = strtoul (s, &end, 16); s = end; if (!valid_pgno (last_pgno)) invalid_pgno_exit (argv[i]); } else if (0 != *s) { invalid_pgno_exit (argv[i]); } success = vbi_sliced_filter_keep_ttx_pages (sf, first_pgno, last_pgno); if (!success) no_mem_exit (); } if (0 == i) error_exit (_("No page numbers specified.")); } int main (int argc, char ** argv) { init_helpers (argc, argv); option_in_file_format = FILE_FORMAT_SLICED; option_start_time = 0.0; option_end_time = 1e30; for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ break; case 'a': option_abort_on_error = TRUE; break; case 'h': usage (stdout); exit (EXIT_SUCCESS); case 'i': assert (NULL != optarg); option_in_file_name = optarg; break; case 'o': assert (NULL != optarg); option_out_file_name = optarg; break; case 'q': parse_option_quiet (); break; case 's': option_keep_ttx_system_pages = TRUE; break; case 't': parse_option_time (); break; case 'v': parse_option_verbose (); break; case 'x': option_experimental_output = TRUE; break; case 'P': option_in_file_format = FILE_FORMAT_DVB_PES; break; case 'T': option_in_ts_pid = parse_option_ts (); option_in_file_format = FILE_FORMAT_DVB_TS; break; case 'V': printf (PROGRAM_NAME " " VERSION "\n"); exit (EXIT_SUCCESS); default: usage (stderr); exit (EXIT_FAILURE); } } sf = vbi_sliced_filter_new (/* callback */ NULL, /* user_data */ NULL); if (NULL == sf) no_mem_exit (); vbi_sliced_filter_keep_ttx_system_pages (sf, option_keep_ttx_system_pages); assert (argc >= optind); parse_page_numbers (argc - optind, &argv[optind]); sliced_blank.id = VBI_SLICED_TELETEXT_B_L10_625; sliced_blank.line = 7; if (option_experimental_output) { wst = write_stream_new (option_out_file_name, FILE_FORMAT_XML, /* ts_pid */ 0, /* system */ 625); } else { wst = write_stream_new (option_out_file_name, FILE_FORMAT_SLICED, /* ts_pid */ 0, /* system */ 625); } rst = read_stream_new (option_in_file_name, option_in_file_format, option_in_ts_pid, filter_frame); stream_loop (rst); stream_delete (rst); rst = NULL; stream_delete (wst); wst = NULL; error_msg (_("End of stream.")); exit (EXIT_SUCCESS); return 0; } zvbi-0.2.44/test/uclist000077500000000000000000000012141476363111200147650ustar00rootroot00000000000000#!/bin/sh # List all Unicode characters used # in UTF-8 file on stdin #awk 'BEGIN { RS = "[^[:alnum:]]" } ; \ # /0x[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]u/ \ # { print $0 }' ../src/lang.c | sort -u >uclist.tmp iconv -f UTF-8 -t UTF-16BE \ | hex -w2 \ | awk '{ print toupper($2 $3) }' \ | sort -u >uclist.tmp wget --proxy=on -q -O - http://www.unicode.org/Public/UNIDATA/NamesList.txt \ | awk 'BEGIN { while ((getline < "uclist.tmp") > 0) l = l $0 " " } \ /^[[:xdigit:]]+/ { if (index(l, $1) > 0) print $0 }' awk '/(E[[:xdigit:]]|F[012345678])[[:xdigit:]][[:xdigit:]]/ \ { print $0 "\tPRIVATE" }' N ^ n ~ / ? O _ o ■ ETS 300 706 Table 37: Latin G2 Supplementary Set   ° — Ω ĸ ¡ ± ˋ ¹ Æ æ ¢ ² ˊ ® Ð đ £ ³ ˆ © ª ð $ × ˜ ™ Ħ ħ ¥ µ ˉ ♪ ı # ¶ ˘ ₠ IJ ij § · ˙ ‰ Ŀ ŀ ¤ ÷ ¨ ɑ Ł ł ‘ ’ . Ø ø “ ” ˚ Œ œ « » ˏ º ß ← ¼ ˍ ⅛ Þ þ ↑ ½ ˝ ⅜ Ŧ ŧ → ¾ ˛ ⅝ Ŋ ŋ ↓ ¿ ˇ ⅞ ʼn ■ ETS 300 706 Table 38: Cyrillic G0 Primary Set - Option 1 - Serbian/Croatian 0 Ч П ч п ! 1 А Ќ а ќ " 2 Б Р б р # 3 Ц С ц с $ 4 Д Т д т % 5 Е У е у & 6 Ф В ф в ' 7 Г Ѓ г ѓ ( 8 Х Љ х љ ) 9 И Њ и њ * : Ј З ј з + ; К Ћ к ћ , < Л Ж л ж - = М Ђ м ђ . > Н Ш н ш / ? О Џ о ■ ETS 300 706 Table 39: Cyrillic G0 Primary Set - Option 2 - Russian/Bulgarian 0 Ю П ю п ! 1 А Я а я " 2 Б Р б р # 3 Ц С ц с $ 4 Д Т д т % 5 Е У е у ы 6 Ф Ж ф ж ' 7 Г В г в ( 8 Х Ь х ь ) 9 И Ъ и ъ * : Ѝ З ѝ з + ; К Ш к ш , < Л Э л э - = М Щ м щ . > Н Ч н ч / ? О Ы о ■ ETS 300 706 Table 40: Cyrillic G0 Primary Set - Option 3 - Ukrainian 0 Ю П ю п ! 1 А Я а я " 2 Б Р б р # 3 Ц С ц с $ 4 Д Т д т % 5 Е У е у ï 6 Ф Ж ф ж ' 7 Г В г в ( 8 Х Ь х ь ) 9 И І и і * : Ѝ З ѝ з + ; К Ш к ш , < Л Є л є - = М Щ м щ . > Н Ч н ч / ? О Ї о ■ ETS 300 706 Table 41: Cyrillic G2 Supplementary Set   ° — D d ¡ ± ˋ ¹ E e ¢ ² ˊ ® F f £ ³ ˆ © G g × ˜ ™ I i ¥ µ ˉ ♪ J j # ¶ ˘ ₠ K k § · ˙ ‰ L l ÷ ¨ ɑ N n ‘ ’ . Ł Q q “ ” ˚ ł R r « » ˏ ß S s ← ¼ ˍ ⅛ U u ↑ ½ ˝ ⅜ V v → ¾ ˛ ⅝ W w ↓ ¿ ˇ ⅞ Z z ETS 300 706 Table 42: Greek G0 Primary Set 0 ΐ Π ΰ π ! 1 Α Ρ α ρ " 2 Β ʹ β ς # 3 Γ Σ γ σ $ 4 Δ Τ δ τ % 5 Ε Υ ε υ & 6 Ζ Φ ζ φ ' 7 Η Χ η χ ( 8 Θ Ψ θ ψ ) 9 Ι Ω ι ω * : Κ Ϊ κ ϊ + ; Λ Ϋ λ ϋ , « Μ ά μ ό - = Ν έ ν ύ . » Ξ ή ξ ώ / ? Ο ί ο ■ ETS 300 706 Table 43: Greek G2 Supplementary Set   ° ? C c a ± ˋ ¹ D d b ² ˊ ® F f £ ³ ˆ © G g e x ˜ ™ J j h m ˉ ♪ L l i n ˘ ₠ Q q § p ˙ ‰ R r : ÷ ¨ ɑ S s ‘ ’ . Ί U u “ ” ˚ Ύ V v k t ˏ Ώ W w ← ¼ ˍ ⅛ Y y ↑ ½ ˝ ⅜ Z z → ¾ ˛ ⅝ Ά Έ ↓ x ˇ ⅞ Ή ■ ETS 300 706 Table 44: Arabic G0 Primary Set 0     ! 1     " 2     £ 3     $ 4     % 5      6      7     ) 8     ( 9     * :     + ؛     ، >     - =     . <     / ؟  #  ■ ETS 300 706 Table 45: Arabic G2 Supplementary Set   à P é p   A Q a q   B R b r   C S c s   D T d t   E U e u   F V f v   G W g w   H X h x   I Y i y   J Z j z   K ë k â   L ê l ô   M ù m û   N î n ç   O  o ETS 300 706 Table 46: Hebrew G0 Primary Set 0 @ P א נ ! 1 A Q ב ס " 2 B R ג ע # 3 C S ד ף $ 4 D T ה פ % 5 E U ו ץ & 6 F V ז צ ' 7 G W ח ק ( 8 H X ט ר ) 9 I Y י ש * : J Z ך ת + ; K ← כ ₪ , < L ½ ל ‖ - = M → ם ¾ . > N ↑ מ ÷ / ? O # ן ■ ETS 300 706 Table 47: G1 Block Mosaics Set                                                                 ETS 300 706 Table 48: G3 Smooth Mosaics and Line Drawing Set                                                                                                 Teletext composed glyphs @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ ˋ -À---È---Ì-----Ò-----Ù---------- ˊ -Á-Ć-É---Í--Ĺ-ŃÓ--ŔŚ-Ú---ÝŹ----- ˆ -Â-Ĉ-Ê-ĜĤÎĴ----Ô---Ŝ-Û-Ŵ-Ŷ------ ˜ -Ã-------Ĩ----ÑÕ-----Ũ---------- ˉ -Ā---Ē---Ī-----Ō-----Ū---------- ˘ -Ă---Ĕ-Ğ-Ĭ-----Ŏ-----Ŭ---------- ˙ ---Ċ-Ė-Ġ-İ----------------Ż----- ¨ -Ä---Ë---Ï-----Ö-----Ü---Ÿ------ . -------------------------------- ˚ -Å-------------------Ů---------- ˏ ---Ç---Ģ---ĶĻ-Ņ---ŖŞŢ----------- ˍ -------------------------------- ˝ ---------------Ő-----Ű---------- ˛ -Ą---Ę---Į-----------Ų---------- ˇ ---ČĎĚ------Ľ-Ň---ŘŠŤ-----Ž----- Teletext composed glyphs `abcdefghijklmnopqrstuvwxyz{¦}~■ `abcdefghijklmnopqrstuvwxyz{¦}~■ ˋ -à---è---ì-----ò-----ù---------- ˊ -á-ć-é---í--ĺ-ńó--ŕś-ú---ýź----- ˆ -â-ĉ-ê-ĝĥîĵ----ô---ŝ-û-ŵ-ŷ------ ˜ -ã-------ĩ----ñõ-----ũ---------- ˉ -ā---ē---ī-----ō-----ū---------- ˘ -ă---ĕ-ğ-ĭ-----ŏ-----ŭ---------- ˙ ---ċ-ė-ġ------------------ż----- ¨ -ä---ë---ï-----ö-----ü---ÿ------ . -------------------------------- ˚ -å-------------------ů---------- ˏ ---ç---ģ---ķļ-ņ---ŗşţ----------- ˍ -------------------------------- ˝ ---------------ő-----ű---------- ˛ -ą---ę---į-----------ų---------- ˇ ---čďě------ľ-ň---řšť-----ž----- Teletext composed glyphs (Unicode U+0080 ... U+00FF) - - - - À - à - - - - - Á Ñ á ñ - - - - Â Ò â ò - - - - Ã Ó ã ó - - ¤ - Ä Ô ä ô - - - - Å Õ å õ - - ¦ - - Ö - ö - - - - Ç - ç - - - - - È - è - - - - - É Ù é ù - - - - Ê Ú ê ú - - - - Ë Û ë û - - - - Ì Ü ì ü - - - - Í Ý í ý - - - - Î - î - - - - - Ï - ï ÿ Teletext composed glyphs (Unicode U+0100 ... U+017F) Ā - Ġ İ - Ő Š Ű ā - ġ - - ő š ű Ă Ē Ģ - - - Ţ Ų ă ē ģ - Ń - ţ ų Ą Ĕ Ĥ Ĵ ń Ŕ Ť Ŵ ą ĕ ĥ ĵ Ņ ŕ ť ŵ Ć Ė - Ķ ņ Ŗ - Ŷ ć ė - ķ Ň ŗ - ŷ Ĉ Ę Ĩ - ň Ř Ũ Ÿ ĉ ę ĩ Ĺ - ř ũ Ź Ċ Ě Ī ĺ - Ś Ū ź ċ ě ī Ļ - ś ū Ż Č Ĝ Ĭ ļ Ō Ŝ Ŭ ż č ĝ ĭ Ľ ō ŝ ŭ Ž Ď Ğ Į ľ Ŏ Ş Ů ž ď ğ į - ŏ ş ů - EIA 608 Closed Caption Basic Character Set ( 0 8 @ H P X ú h p x ( 0 8 @ H P X Ú H P X ! ) 1 9 A I Q Y a i q y ! ) 1 9 A I Q Y A I Q Y " á 2 : B J R Z b j r z " Á 2 : B J R Z B J R Z # + 3 ; C K S [ c k s ç # + 3 ; C K S [ C K S Ç $ , 4 < D L T é d l t ÷ $ , 4 < D L T É D L T ÷ % - 5 = E M U ] e m u Ñ % - 5 = E M U ] E M U Ñ & . 6 > F N V í f n v ñ & . 6 > F N V Í F N V Ñ ' / 7 ? G O W ó g o w ■ ' / 7 ? G O W Ó G O W ■ EIA 608 Closed Caption Special Characters (0x1130+n) ®°½¿™¢£♪à èâêîôû ®°½¿™¢£♪À ÈÂÊÎÔÛ EIA 608 Closed Caption Extended Characters (0x1220+n) ÁÉÓÚÜü‘¡*'─©℠•“”ÀÂÇÈÊËëÎÏïÔÙùÛ«» ÁÉÓÚÜÜ‘¡*'─©℠•“”ÀÂÇÈÊËËÎÏÏÔÙÙÛ«» EIA 608 Closed Caption Extended Characters (0x1320+n) ÃãÍÌìÒòÕõ{}\^_|~ÄäÖöߥ¤│Å娸┌┐└┘ ÃÃÍÌÌÒÒÕÕ{}\^_|~ÄÄÖÖߥ¤│ÅÅØØ┌┐└┘ zvbi-0.2.44/test/unicode.c000066400000000000000000000157611476363111200153420ustar00rootroot00000000000000/* * libzvbi test * * Copyright (C) 2000, 2001, 2008 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* $Id: unicode.c,v 1.13 2008-09-11 02:47:12 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "src/lang.h" #include "src/misc.h" static void putwchar_local (unsigned int c) { if (c < 0x80) { putchar (c); } else if (c < 0x800) { putchar (0xC0 | (c >> 6)); putchar (0x80 | (c & 0x3F)); } else if (c < 0x10000) { putchar (0xE0 | (c >> 12)); putchar (0x80 | ((c >> 6) & 0x3F)); putchar (0x80 | (c & 0x3F)); } else if (c < 0x200000) { putchar (0xF0 | (c >> 18)); putchar (0x80 | ((c >> 12) & 0x3F)); putchar (0x80 | ((c >> 6) & 0x3F)); putchar (0x80 | (c & 0x3F)); } } #undef putwchar #define putwchar putwchar_local static void putwstr (const char * s) { for (; *s; s++) putwchar (*s); } static const unsigned int national [] = { 0x23, 0x24, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x7B, 0x7C, 0x7D, 0x7E }; static void print_set (const char * name, unsigned int s) { unsigned int i, j; putwstr (name); putwchar ('\n'); for (i = 0; i < 16; ++i) { for (j = 2; j < 8; ++j) { putwchar (vbi_teletext_unicode (s, 0, j * 16 + i)); putwchar (' '); } putwchar ('\n'); } putwchar ('\n'); } static void teletext_composed (vbi_bool upper_case) { unsigned int offs; unsigned int i, j; offs = upper_case ? 0x00 : 0x20; putwstr ("Teletext composed glyphs\n\n "); for (i = 0x40; i < 0x60; ++i) putwchar (vbi_teletext_unicode (1, 0, i | offs)); putwstr ("\n\n"); for (i = 0; i < 16; ++i) { putwchar (vbi_teletext_unicode (2, 0, 0x40 + i)); putwstr (" "); for (j = 0x40; j < 0x60; ++j) { unsigned int c; c = vbi_teletext_composed_unicode (i, j | offs); putwchar ((0 == c) ? '-' : c); } putwchar ('\n'); } putwchar ('\n'); } static int is_teletext_composed (unsigned int uc) { unsigned int i, j; for (i = 0; i < 16; ++i) { for (j = 0x20; j < 0x80; ++j) { if (uc == vbi_teletext_composed_unicode (i, j)) return TRUE; } } return FALSE; } static void teletext_composed_inv (void) { unsigned int i, j; putwstr ("Teletext composed glyphs (Unicode U+0080 ... U+00FF)\n\n"); for (i = 0; i < 16; ++i) { for (j = 0x080; j < 0x100; j += 0x10) { putwchar (is_teletext_composed (i + j) ? i + j : '-'); putwchar (' '); } putwchar ('\n'); } putwchar ('\n'); putwstr ("Teletext composed glyphs (Unicode U+0100 ... U+017F)\n\n"); for (i = 0; i < 16; ++i) { for (j = 0x100; j < 0x180; j += 0x10) { putwchar (is_teletext_composed (i + j) ? i + j : '-'); putwchar (' '); } putwchar ('\n'); } putwchar ('\n'); } int main (int argc, char ** argv) { unsigned int i, j; argc = argc; /* unused */ argv = argv; putwstr ("libzvbi unicode test -*- coding: utf-8 -*-\n\n"); putwstr ("ETS 300 706 Table 36: Latin National Option Sub-sets\n\n"); for (i = 1; i < 14; ++i) { for (j = 0; j < N_ELEMENTS (national); ++j) { putwchar (vbi_teletext_unicode (1, i, national[j])); putwchar (' '); } putwchar ('\n'); } putwchar ('\n'); print_set ("ETS 300 706 Table 35: Latin G0 Primary Set\n", 1); print_set ("ETS 300 706 Table 37: Latin G2 Supplementary Set\n", 2); print_set ("ETS 300 706 Table 38: Cyrillic G0 Primary Set " "- Option 1 - Serbian/Croatian\n", 3); print_set ("ETS 300 706 Table 39: Cyrillic G0 Primary Set " "- Option 2 - Russian/Bulgarian\n", 4); print_set ("ETS 300 706 Table 40: Cyrillic G0 Primary Set " "- Option 3 - Ukrainian\n", 5); print_set ("ETS 300 706 Table 41: Cyrillic G2 Supplementary Set\n", 6); print_set ("ETS 300 706 Table 42: Greek G0 Primary Set\n", 7); print_set ("ETS 300 706 Table 43: Greek G2 Supplementary Set\n", 8); print_set ("ETS 300 706 Table 44: Arabic G0 Primary Set\n", 9); print_set ("ETS 300 706 Table 45: Arabic G2 Supplementary Set\n", 10); print_set ("ETS 300 706 Table 46: Hebrew G0 Primary Set\n", 11); putwstr ("ETS 300 706 Table 47: G1 Block Mosaics Set\n\n"); for (i = 0; i < 16; ++i) { for (j = 2; j < 8; ++j) { if (j == 4 || j == 5) putwchar (' '); else putwchar (vbi_teletext_unicode (12, 0, j * 16 + i)); putwchar (' '); } putwchar ('\n'); } putwchar ('\n'); print_set ("ETS 300 706 Table 48: G3 Smooth Mosaics and " "Line Drawing Set\n", 13); teletext_composed (/* upper_case */ TRUE); teletext_composed (/* upper_case */ FALSE); teletext_composed_inv (); putwstr ("\nEIA 608 Closed Caption Basic Character Set\n\n"); for (i = 0; i < 8; ++i) { for (j = 0x20; j < 0x80; j += 8) { putwchar (vbi_caption_unicode (j + i, FALSE)); putwchar (' '); } putwstr (" "); for (j = 0x20; j < 0x80; j += 8) { putwchar (vbi_caption_unicode (j + i, TRUE)); putwchar (' '); } putwchar ('\n'); } putwstr ("\n\nEIA 608 Closed Caption " "Special Characters (0x1130+n)\n\n"); for (i = 0; i < 16; ++i) putwchar (vbi_caption_unicode (0x1130 + i, FALSE)); putwchar ('\n'); for (i = 0; i < 16; ++i) putwchar (vbi_caption_unicode (0x1130 + i, TRUE)); putwstr ("\n\nEIA 608 Closed Caption " "Extended Characters (0x1220+n)\n\n"); for (i = 0; i < 32; ++i) putwchar (vbi_caption_unicode (0x1220 + i, FALSE)); putwchar ('\n'); for (i = 0; i < 32; ++i) putwchar (vbi_caption_unicode (0x1220 + i, TRUE)); putwstr ("\n\nEIA 608 Closed Caption " "Extended Characters (0x1320+n)\n\n"); for (i = 0; i < 32; ++i) putwchar (vbi_caption_unicode (0x1320 + i, FALSE)); putwchar ('\n'); for (i = 0; i < 32; ++i) putwchar (vbi_caption_unicode (0x1320 + i, TRUE)); putwchar ('\n'); assert ('a' == vbi_caption_unicode ('a', FALSE)); assert ('A' == vbi_caption_unicode ('a', TRUE)); assert ('A' == vbi_caption_unicode ('a', -1)); assert ('A' == vbi_caption_unicode ('a', INT_MAX)); for (i = 0; i < 2; ++i) { assert (0 == vbi_caption_unicode (-1, i)); assert (0 == vbi_caption_unicode (0x80, i)); assert (0 == vbi_caption_unicode (0x1130 - 1, i)); assert (0 == vbi_caption_unicode (0x1130 + 16, i)); assert (0 == vbi_caption_unicode (0x1220 - 1, i)); assert (0 == vbi_caption_unicode (0x1220 + 32, i)); assert (0 == vbi_caption_unicode (0x1320 - 1, i)); assert (0 == vbi_caption_unicode (0x1320 + 32, i)); assert (0 == vbi_caption_unicode (INT_MAX, i)); } exit (EXIT_SUCCESS); } zvbi-0.2.44/test_windows.sh000077500000000000000000000025731476363111200156560ustar00rootroot00000000000000#!/usr/bin/sh cd examples # Cannot execute scripts in windows ## Point to 'exe' file if on linux and compiling for windows ./pdc2-test1.sh > /dev/null; echo "pdc2-test1.sh $?" cd ../test # Why differing? (Unrecognized symbols) ## unicode-out.txt unicode-out-ref.txt differ: byte 43, line 1 ./test-unicode_windows > /dev/null; echo "test-unicode_windows $?" # Segmentation fault ## See https://github.com/zapping-vbi/zvbi/pull/42 for details wine test-pdc.exe > /dev/null; echo "test-pdc $?" # Working! ./exoptest_windows > /dev/null; echo "exoptest_windows $?" wine ctest.exe > /dev/null; echo "ctest $?" wine cpptest.exe > /dev/null; echo "cpptest $?" wine ctest-c89.exe > /dev/null; echo "ctest-c89 $?" wine ctest-gnu89.exe > /dev/null; echo "ctest-gnu89 $?" wine ctest-c94.exe > /dev/null; echo "ctest-c94 $?" wine ctest-c99.exe > /dev/null; echo "ctest-c99 $?" wine ctest-gnu99.exe > /dev/null; echo "ctest-gnu99 $?" wine cpptest-cxx98.exe > /dev/null; echo "cpptest-cxx98 $?" wine cpptest-gnuxx98.exe > /dev/null; echo "cpptest-gnuxx98 $?" wine test-dvb_demux.exe > /dev/null; echo "test-dvb_demux $?" wine test-dvb_mux.exe > /dev/null; echo "test-dvb_mux $?" wine test-hamm.exe > /dev/null; echo "test-hamm $?" wine test-packet-830.exe > /dev/null; echo "test-packet-830 $?" wine test-raw_decoder.exe > /dev/null; echo "test-raw_decoder $?" wine test-vps.exe > /dev/null; echo "test-vps $?" zvbi-0.2.44/zvbi-0.2.pc.in000066400000000000000000000003511476363111200147560ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: ZVBI Description: VBI Capturing and Decoding Library Requires: Version: @VERSION@ Libs: -L${libdir} -lzvbi -lm @LIBS@ Cflags: -I${includedir} zvbi-0.2.44/zvbi.spec.in000066400000000000000000000021711476363111200150130ustar00rootroot00000000000000Name: zvbi Summary: Raw VBI, Teletext and Closed Caption decoding library Version: @VERSION@ Release: 1 Copyright: GPL Group: Applications/Multimedia Url: http://zapping.sourceforge.net/ Source: http://prdownloads.sourceforge.net/zapping/%{name}-%{version}.tar.bz2 Packager: Michael H. Schimek Buildroot: %{_tmppath}/%{name}-%{version}-root PreReq: /sbin/install-info Provides: zvbi %description Routines to access raw VBI capture devices (currently the V4L and V4L2 API, and the *BSD bktr driver are supported), a versatile VBI bit slicer, decoders for various data services and basic search, render and export functions for Closed Caption and Teletext pages. %prep %setup -q %build %configure make %install rm -rf %{buildroot} %makeinstall %find_lang %{name} %clean rm -rf %{buildroot} %post -p /sbin/ldconfig %postun -p /sbin/ldconfig %files %defattr (-, root, root) %doc AUTHORS BUGS COPYING ChangeLog NEWS README TODO doc/html %{_libdir}/libzvbi* %{_includedir}/libzvbi.h %changelog * Thu Oct 24 2002 Michael H. Schimek - Added %%find_lang for locale support.