diff --git a/WebRTC-Sample/README.md b/WebRTC-Sample/README.md index 0d7fc91b..26ba80d5 100755 --- a/WebRTC-Sample/README.md +++ b/WebRTC-Sample/README.md @@ -44,38 +44,10 @@ The OWT-SERVER is provided as CentOS 7.6 dockerfile in this sample. |VP8| |VP9| -## Install docker engine: +## Build owt-server components: -(1) Install [docker engine](https://docs.docker.com/install). +View `owt-server/README.md` for more details. -(2) Install [docker compose](https://docs.docker.com/compose/install). - -## Build docker images: - -```bash -cd WebRTC-Sample/owt-server -mkdir build -cd build -cmake .. -make -``` - -## Start/stop services: - -Use the following commands to start or stop services via docker compose: - -```bash -cd WebRTC-Sample/owt-server/build - -# start 4k service -make start_owt_immersive_4k - -# start 8k service -make start_owt_immersive_8k - -# stop service -make stop -``` # OWT-LINUX-PLAYER The OWT-LINIX-PLAYER is an immersive 360 video player on linux, with WebRTC backend. It supports HEVC tile decoding, ERP video rendering, and FoV feedback. diff --git a/WebRTC-Sample/owt-server/CMakeLists.txt b/WebRTC-Sample/owt-server/CMakeLists.txt deleted file mode 100644 index b3981d7f..00000000 --- a/WebRTC-Sample/owt-server/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -cmake_minimum_required (VERSION 2.8) - -Project(OWT NONE) -add_custom_target(update) - -file(GLOB dirs "deployment" "*") -list(REMOVE_DUPLICATES dirs) -foreach (dir ${dirs}) - if(EXISTS ${dir}/CMakeLists.txt) - add_subdirectory(${dir}) - endif() -endforeach() - -# legal message -execute_process(COMMAND printf "\nThis script will build third party components licensed under various open source licenses into your container images. The terms under which those components may be used and distributed can be found with the license document that is provided with those components. Please familiarize yourself with those terms to ensure your distribution of those components complies with the terms of those licenses.\n\n") - diff --git a/WebRTC-Sample/owt-server/LICENSE b/WebRTC-Sample/owt-server/LICENSE deleted file mode 100644 index 57bc88a1..00000000 --- a/WebRTC-Sample/owt-server/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/WebRTC-Sample/owt-server/README.md b/WebRTC-Sample/owt-server/README.md new file mode 100644 index 00000000..6e1ee205 --- /dev/null +++ b/WebRTC-Sample/owt-server/README.md @@ -0,0 +1,79 @@ +# WebRTC360 +It's a webrtc server for 360 video use case.
+Download, build and pack a official owt-server-5.0.x DIST in third-party. Then it will build a immersive video mixer node to replace VideoMixer in DIST, and a WebRTC node for tiles selection and FOV feedback. + +To build and run on a bare-metal machine, following steps below. +As to deploy a docker image, go to `deploy` for details. + +## Install + +1. Install owt-server +``` +./scripts/install_owt_server.sh +``` + +2. Install deps +``` +./scripts/install_deps.sh +``` + +3. Build immersive video mixer node +``` +./scripts/build.js -t all +``` + +4. Pack IM video DIST +``` +./scripts/pack.sh +``` + +## Run Server + +```bash +cd dist + +# init owt-server, only init once after reboot +./bin/init-all.sh +# Update RabbitMQ/MongoDB Account? [No/yes] +# Press "Enter" to use default RabbitMQ/MongoDB account + +# start owt-server +./bin/start-all.sh + +# stop owt-server +./bin/stop-all.sh +killall -9 node + +# check logs for debug +ls logs/ + +# restart owt-server = stop + rm logs + start +./bin/stop-all.sh +killall -9 node + +rm logs/* -v + +./bin/start-all.sh +``` + +## Start 360 video streaming + +```bash +cd ../rest/ +. "$HOME/.nvm/nvm.sh" +npm install mongojs +npm install . + +# start owt-server +# configure owt-server, 4k or 8k +./control.js -s 4k +# restart owt-server to take into effect + +# start input, 4k or 8k +# it is ready for connction from clients +./control.js -c /home/WebRTC360/test1_h265_3840x2048_30fps_30M_200frames.mp4 +# check logs +# dist/logs/streaming-xxx-x.log +# dist/logs/video-xxx-x.log +# dist/logs/webrtc-xxx-x.log +``` diff --git a/WebRTC-Sample/owt-server/ThirdpartyLicenses.txt b/WebRTC-Sample/owt-server/ThirdpartyLicenses.txt deleted file mode 100644 index 6d41c145..00000000 --- a/WebRTC-Sample/owt-server/ThirdpartyLicenses.txt +++ /dev/null @@ -1,2997 +0,0 @@ - Node.js License - ----------------------- - -==== - -Copyright Joyent, Inc. and other Node contributors. All rights reserved. -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. - -==== - -This license applies to all parts of Node that are not externally -maintained libraries. The externally maintained libraries used by Node are: - -- V8, located at deps/v8. V8's license follows: - """ - This license applies to all parts of V8 that are not externally - maintained libraries. The externally maintained libraries used by V8 - are: - - - PCRE test suite, located in - test/mjsunit/third_party/regexp-pcre.js. This is based on the - test suite from PCRE-7.3, which is copyrighted by the University - of Cambridge and Google, Inc. The copyright notice and license - are embedded in regexp-pcre.js. - - - Layout tests, located in test/mjsunit/third_party. These are - based on layout tests from webkit.org which are copyrighted by - Apple Computer, Inc. and released under a 3-clause BSD license. - - - Strongtalk assembler, the basis of the files assembler-arm-inl.h, - assembler-arm.cc, assembler-arm.h, assembler-ia32-inl.h, - assembler-ia32.cc, assembler-ia32.h, assembler-x64-inl.h, - assembler-x64.cc, assembler-x64.h, assembler-mips-inl.h, - assembler-mips.cc, assembler-mips.h, assembler.cc and assembler.h. - This code is copyrighted by Sun Microsystems Inc. and released - under a 3-clause BSD license. - - - Valgrind client API header, located at third_party/valgrind/valgrind.h - This is release under the BSD license. - - These libraries have their own licenses; we recommend you read them, - as their terms may differ from the terms below. - - Copyright 2006-2012, the V8 project authors. All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * Neither the name of Google Inc. 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 - 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. - """ - -- C-Ares, an asynchronous DNS client, located at deps/cares. C-Ares license - follows: - """ - /* Copyright 1998 by the Massachusetts Institute of Technology. - * - * Permission to use, copy, modify, and distribute this - * software and its documentation for any purpose and without - * fee is hereby granted, provided that the above copyright - * notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting - * documentation, and that the name of M.I.T. not be used in - * advertising or publicity pertaining to distribution of the - * software without specific, written prior permission. - * M.I.T. makes no representations about the suitability of - * this software for any purpose. It is provided "as is" - * without express or implied warranty. - """ - -- OpenSSL located at deps/openssl. OpenSSL is cryptographic software written - by Eric Young (eay@cryptsoft.com) to provide SSL/TLS encryption. OpenSSL's - license follows: - """ - /* ==================================================================== - * Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - """ - -- HTTP Parser, located at deps/http_parser. HTTP Parser's license follows: - """ - http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright - Igor Sysoev. - - Additional changes are licensed under the same terms as NGINX and - copyright Joyent, Inc. and other Node contributors. All rights reserved. - - 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. - """ - -- Closure Linter is located at tools/closure_linter. Closure's license - follows: - """ - # Copyright (c) 2007, Google Inc. - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions are - # met: - # - # * Redistributions of source code must retain the above copyright - # notice, this list of conditions and the following disclaimer. - # * 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. - # * Neither the name of Google Inc. 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 - # 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. - """ - -- tools/cpplint.py is a C++ linter. Its license follows: - """ - # Copyright (c) 2009 Google Inc. All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions are - # met: - # - # * Redistributions of source code must retain the above copyright - # notice, this list of conditions and the following disclaimer. - # * 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. - # * Neither the name of Google Inc. 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 - # 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. - """ - -- lib/punycode.js is copyright 2011 Mathias Bynens - and released under the MIT license. - """ - * Punycode.js - * Copyright 2011 Mathias Bynens - * Available under MIT license - """ - -- tools/gyp. GYP is a meta-build system. GYP's license follows: - """ - Copyright (c) 2009 Google Inc. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * Neither the name of Google Inc. 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 - 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. - """ - -- Zlib at deps/zlib. zlib's license follows: - """ - /* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.2.4, March 14th, 2010 - - Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jean-loup Gailly - Mark Adler - - */ - """ - -- npm is a package manager program located at deps/npm. - npm's license follows: - """ - Copyright (c) Isaac Z. Schlueter - All rights reserved. - - npm is released under the Artistic 2.0 License. - The text of the License follows: - - - -------- - - - The Artistic License 2.0 - - Copyright (c) 2000-2006, The Perl Foundation. - - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - This license establishes the terms under which a given free software - Package may be copied, modified, distributed, and/or redistributed. - The intent is that the Copyright Holder maintains some artistic - control over the development of that Package while still keeping the - Package available as open source and free software. - - You are always permitted to make arrangements wholly outside of this - license directly with the Copyright Holder of a given Package. If the - terms of this license do not permit the full use that you propose to - make of the Package, you should contact the Copyright Holder and seek - a different licensing arrangement. - - Definitions - - "Copyright Holder" means the individual(s) or organization(s) - named in the copyright notice for the entire Package. - - "Contributor" means any party that has contributed code or other - material to the Package, in accordance with the Copyright Holder's - procedures. - - "You" and "your" means any person who would like to copy, - distribute, or modify the Package. - - "Package" means the collection of files distributed by the - Copyright Holder, and derivatives of that collection and/or of - those files. A given Package may consist of either the Standard - Version, or a Modified Version. - - "Distribute" means providing a copy of the Package or making it - accessible to anyone else, or in the case of a company or - organization, to others outside of your company or organization. - - "Distributor Fee" means any fee that you charge for Distributing - this Package or providing support for this Package to another - party. It does not mean licensing fees. - - "Standard Version" refers to the Package if it has not been - modified, or has been modified only in ways explicitly requested - by the Copyright Holder. - - "Modified Version" means the Package, if it has been changed, and - such changes were not explicitly requested by the Copyright - Holder. - - "Original License" means this Artistic License as Distributed with - the Standard Version of the Package, in its current version or as - it may be modified by The Perl Foundation in the future. - - "Source" form means the source code, documentation source, and - configuration files for the Package. - - "Compiled" form means the compiled bytecode, object code, binary, - or any other form resulting from mechanical transformation or - translation of the Source form. - - - Permission for Use and Modification Without Distribution - - (1) You are permitted to use the Standard Version and create and use - Modified Versions for any purpose without restriction, provided that - you do not Distribute the Modified Version. - - - Permissions for Redistribution of the Standard Version - - (2) You may Distribute verbatim copies of the Source form of the - Standard Version of this Package in any medium without restriction, - either gratis or for a Distributor Fee, provided that you duplicate - all of the original copyright notices and associated disclaimers. At - your discretion, such verbatim copies may or may not include a - Compiled form of the Package. - - (3) You may apply any bug fixes, portability changes, and other - modifications made available from the Copyright Holder. The resulting - Package will still be considered the Standard Version, and as such - will be subject to the Original License. - - - Distribution of Modified Versions of the Package as Source - - (4) You may Distribute your Modified Version as Source (either gratis - or for a Distributor Fee, and with or without a Compiled form of the - Modified Version) provided that you clearly document how it differs - from the Standard Version, including, but not limited to, documenting - any non-standard features, executables, or modules, and provided that - you do at least ONE of the following: - - (a) make the Modified Version available to the Copyright Holder - of the Standard Version, under the Original License, so that the - Copyright Holder may include your modifications in the Standard - Version. - - (b) ensure that installation of your Modified Version does not - prevent the user installing or running the Standard Version. In - addition, the Modified Version must bear a name that is different - from the name of the Standard Version. - - (c) allow anyone who receives a copy of the Modified Version to - make the Source form of the Modified Version available to others - under - - (i) the Original License or - - (ii) a license that permits the licensee to freely copy, - modify and redistribute the Modified Version using the same - licensing terms that apply to the copy that the licensee - received, and requires that the Source form of the Modified - Version, and of any works derived from it, be made freely - available in that license fees are prohibited but Distributor - Fees are allowed. - - - Distribution of Compiled Forms of the Standard Version - or Modified Versions without the Source - - (5) You may Distribute Compiled forms of the Standard Version without - the Source, provided that you include complete instructions on how to - get the Source of the Standard Version. Such instructions must be - valid at the time of your distribution. If these instructions, at any - time while you are carrying out such distribution, become invalid, you - must provide new instructions on demand or cease further distribution. - If you provide valid instructions or cease distribution within thirty - days after you become aware that the instructions are invalid, then - you do not forfeit any of your rights under this license. - - (6) You may Distribute a Modified Version in Compiled form without - the Source, provided that you comply with Section 4 with respect to - the Source of the Modified Version. - - - Aggregating or Linking the Package - - (7) You may aggregate the Package (either the Standard Version or - Modified Version) with other packages and Distribute the resulting - aggregation provided that you do not charge a licensing fee for the - Package. Distributor Fees are permitted, and licensing fees for other - components in the aggregation are permitted. The terms of this license - apply to the use and Distribution of the Standard or Modified Versions - as included in the aggregation. - - (8) You are permitted to link Modified and Standard Versions with - other works, to embed the Package in a larger work of your own, or to - build stand-alone binary or bytecode versions of applications that - include the Package, and Distribute the result without restriction, - provided the result does not expose a direct interface to the Package. - - - Items That are Not Considered Part of a Modified Version - - (9) Works (including, but not limited to, modules and scripts) that - merely extend or make use of the Package, do not, by themselves, cause - the Package to be a Modified Version. In addition, such works are not - considered parts of the Package itself, and are not subject to the - terms of this license. - - - General Provisions - - (10) Any use, modification, and distribution of the Standard or - Modified Versions is governed by this Artistic License. By using, - modifying or distributing the Package, you accept this license. Do not - use, modify, or distribute the Package, if you do not accept this - license. - - (11) If your Modified Version has been derived from a Modified - Version made by someone other than you, you are nevertheless required - to ensure that your Modified Version complies with the requirements of - this license. - - (12) This license does not grant you the right to use any trademark, - service mark, tradename, or logo of the Copyright Holder. - - (13) This license includes the non-exclusive, worldwide, - free-of-charge patent license to make, have made, use, offer to sell, - sell, import and otherwise transfer the Package with respect to any - patent claims licensable by the Copyright Holder that are necessarily - infringed by the Package. If you institute patent litigation - (including a cross-claim or counterclaim) against any party alleging - that the Package constitutes direct or contributory patent - infringement, then this Artistic License to you shall terminate on the - date that such litigation is filed. - - (14) Disclaimer of Warranty: - THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS - IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED - WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR - NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL - LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL - BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL - DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF - ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - -------- - - - "Node.js" and "node" trademark Joyent, Inc. npm is not officially - part of the Node.js project, and is neither owned by nor - officially affiliated with Joyent, Inc. - - Packages published in the npm registry (other than the Software and - its included dependencies) are not part of npm itself, are the sole - property of their respective maintainers, and are not covered by - this license. - - "npm Logo" created by Mathias Pettersson and Brian Hammond, - used with permission. - - "Gubblebum Blocky" font - Copyright (c) by Tjarda Koster, http://jelloween.deviantart.com - included for use in the npm website and documentation, - used with permission. - - This program uses several Node modules contained in the node_modules/ - subdirectory, according to the terms of their respective licenses. - """ - -- tools/doc/node_modules/marked. Marked is a Markdown parser. Marked's - license follows: - """ - Copyright (c) 2011-2012, Christopher Jeffrey (https://github.com/chjj/) - - 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. - """ - -- test/gc/node_modules/weak. Node-weak is a node.js addon that provides garbage - collector notifications. Node-weak's license follows: - """ - Copyright (c) 2011, Ben Noordhuis - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - """ - -- src/ngx-queue.h. ngx-queue.h is taken from the nginx source tree. nginx's - license follows: - """ - Copyright (C) 2002-2012 Igor Sysoev - Copyright (C) 2011,2012 Nginx, Inc. - - 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 AUTHOR 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 AUTHOR 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. - """ - -- wrk is located at tools/wrk. wrk's license follows: - """ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - """ - - - Libuv License - ----------------------- - -libuv is part of the Node project: http://nodejs.org/ -libuv may be distributed alone under Node's license: - -==== - -Copyright Joyent, Inc. and other Node contributors. All rights reserved. -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. - -==== - -This license applies to all parts of libuv that are not externally -maintained libraries. - -The externally maintained libraries used by libuv are: - - - tree.h (from FreeBSD), copyright Niels Provos. Two clause BSD license. - - - inet_pton and inet_ntop implementations, contained in src/inet.c, are - copyright the Internet Systems Consortium, Inc., and licensed under the ISC - license. - - - stdint-msvc2008.h (from msinttypes), copyright Alexander Chemeris. Three - clause BSD license. - - - pthread-fixes.h, pthread-fixes.c, copyright Google Inc. and Sony Mobile - Communications AB. Three clause BSD license. - - - android-ifaddrs.h, android-ifaddrs.c, copyright Berkeley Software Design - Inc, Kenneth MacKay and Emergya (Cloud4all, FP7/2007-2013, grant agreement - n° 289016). Three clause BSD license. - - - Licode License - ----------------------- - -The MIT License - -Copyright (C) 2012 Universidad Politecnica de Madrid. - -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. - - - WebRTC License - ----------------------- - -============================ -Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * 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. - - * Neither the name of Google 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. - - - libsrtp License - ----------------------- - -The srtp library and the test drivers distributed with it are licensed under the following BSD-based license. - -Copyright (c) 2001-2005 Cisco Systems, Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -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. - -Neither the name of the Cisco Systems, Inc. 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 HOLDERS 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. - - - jQuery License - ----------------------- - -The MIT License (MIT) - -Copyright (c) - -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. - - - Intel (R) VCSA Library License - ----------------------- - -INTEL SOFTWARE LICENSE AGREEMENT - -IMPORTANT - READ BEFORE COPYING, INSTALLING OR USING. -Do not use or load this software and any associated materials (collectively, -the "Software") until you have carefully read the following terms and -conditions. By loading or using the Software, you agree to the terms of this -Agreement. If you do not wish to so agree, do not install or use the Software. - -LICENSES: Please Note: -- If you are a network administrator, the "Site License" below shall -apply to you. -- If you are an end user, the "Single User License" shall apply to you. -- If you are an original equipment manufacturer (OEM), the "OEM License" -shall apply to you. - -SITE LICENSE. You may copy the Software onto your organization's computers -for your organization's use, and you may make a reasonable number of -back-up copies of the Software, subject to these conditions: - -1. This Software is licensed for use only in conjunction with Intel -component products. Use of the Software in conjunction with non-Intel -component products is not licensed hereunder. -2. You may not copy, modify, rent, sell, distribute or transfer any part -of the Software except as provided in this Agreement, and you agree to -prevent unauthorized copying of the Software. -3. You may not reverse engineer, decompile, or disassemble the Software. -4. You may not sublicense or permit simultaneous use of the Software by -more than one user. -5. The Software may include portions offered on terms in addition to those -set out here, as set out in a license accompanying those portions. - -SINGLE USER LICENSE. You may copy the Software onto a single computer for -your personal, noncommercial use, and you may make one back-up copy of the -Software, subject to these conditions: - -1. This Software is licensed for use only in conjunction with Intel -component products. Use of the Software in conjunction with non-Intel -component products is not licensed hereunder. -2. You may not copy, modify, rent, sell, distribute or transfer any part -of the Software except as provided in this Agreement, and you agree to -prevent unauthorized copying of the Software. -3. You may not reverse engineer, decompile, or disassemble the Software. -4. You may not sublicense or permit simultaneous use of the Software by -more than one user. -5. The Software may include portions offered on terms in addition to those -set out here, as set out in a license accompanying those portions. - -OEM LICENSE: You may reproduce and distribute the Software only as an -integral part of or incorporated in Your product or as a standalone -Software maintenance update for existing end users of Your products, -excluding any other standalone products, subject to these conditions: - -1. This Software is licensed for use only in conjunction with Intel -component products. Use of the Software in conjunction with non-Intel -component products is not licensed hereunder. -2. You may not copy, modify, rent, sell, distribute or transfer any part -of the Software except as provided in this Agreement, and you agree to -prevent unauthorized copying of the Software. -3. You may not reverse engineer, decompile, or disassemble the Software. -4. You may only distribute the Software to your customers pursuant to a -written license agreement. Such license agreement may be a "break-the- -seal" license agreement. At a minimum such license shall safeguard -Intel's ownership rights to the Software. -5. The Software may include portions offered on terms in addition to those -set out here, as set out in a license accompanying those portions. - -NO OTHER RIGHTS. No rights or licenses are granted by Intel to You, expressly -or by implication, with respect to any proprietary information or patent, -copyright, mask work, trademark, trade secret, or other intellectual property -right owned or controlled by Intel, except as expressly provided in this -Agreement. - -OWNERSHIP OF SOFTWARE AND COPYRIGHTS. Title to all copies of the Software -remains with Intel or its suppliers. The Software is copyrighted and -protected by the laws of the United States and other countries, and -international treaty provisions. You may not remove any copyright notices -from the Software. Intel may make changes to the Software, or to items -referenced therein, at any time without notice, but is not obligated to -support or update the Software. Except as otherwise expressly provided, Intel -grants no express or implied right under Intel patents, copyrights, -trademarks, or other intellectual property rights. You may transfer the -Software only if the recipient agrees to be fully bound by these terms and if -you retain no copies of the Software. - -LIMITED MEDIA WARRANTY. If the Software has been delivered by Intel on -physical media, Intel warrants the media to be free from material physical -defects for a period of ninety days after delivery by Intel. If such a defect -is found, return the media to Intel for replacement or alternate delivery of -the Software as Intel may select. - -EXCLUSION OF OTHER WARRANTIES. EXCEPT AS PROVIDED ABOVE, THE SOFTWARE IS -PROVIDED "AS IS" WITHOUT ANY EXPRESS OR IMPLIED WARRANTY OF ANY KIND -INCLUDING WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT, OR FITNESS FOR A -PARTICULAR PURPOSE. Intel does not warrant or assume responsibility for the -accuracy or completeness of any information, text, graphics, links or other -items contained within the Software. - -LIMITATION OF LIABILITY. IN NO EVENT SHALL INTEL OR ITS SUPPLIERS BE LIABLE -FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, LOST PROFITS, -BUSINESS INTERRUPTION, OR LOST INFORMATION) ARISING OUT OF THE USE OF OR -INABILITY TO USE THE SOFTWARE, EVEN IF INTEL HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. SOME JURISDICTIONS PROHIBIT EXCLUSION OR -LIMITATION OF LIABILITY FOR IMPLIED WARRANTIES OR CONSEQUENTIAL OR INCIDENTAL -DAMAGES, SO THE ABOVE LIMITATION MAY NOT APPLY TO YOU. YOU MAY ALSO HAVE -OTHER LEGAL RIGHTS THAT VARY FROM JURISDICTION TO JURISDICTION. - -TERMINATION OF THIS AGREEMENT. Intel may terminate this Agreement at any time -if you violate its terms. Upon termination, you will immediately destroy the -Software or return all copies of the Software to Intel. - -APPLICABLE LAWS. Claims arising under this Agreement shall be governed by the -laws of California, excluding its principles of conflict of laws and the -United Nations Convention on Contracts for the Sale of Goods. You may not -export the Software in violation of applicable export laws and regulations. -Intel is not obligated under any other agreements unless they are in writing -and signed by an authorized representative of Intel. - -GOVERNMENT RESTRICTED RIGHTS. The Software is provided with "RESTRICTED -RIGHTS." Use, duplication, or disclosure by the Government is subject to -restrictions as set forth in FAR52.227-14 and DFAR252.227-7013 et seq. or its -successor. Use of the Software by the Government constitutes acknowledgment -of Intel's proprietary rights therein. Contractor or Manufacturer is Intel -2200 Mission College Blvd., Santa Clara, CA 95052. - - - - Intel (R) Media SDK License - ----------------------- - -INTEL SOFTWARE LICENSE AGREEMENT FOR -INTEL (R) MEDIA SDK -IMPORTANT - READ BEFORE COPYING, INSTALLING OR USING. -Do not use or load this software and any associated materials (collectively, the ¡°Software¡±) until you have carefully read the following terms and conditions. By loading or using the Software, you agree to the terms of this Agreement. If you do not wish to so agree, do not install or use the Software. - -LICENSE: Subject to the restrictions below, Intel Corporation ("Intel") grants to you the following non-exclusive, non-assignable, royalty-free copyright licenses in the Software. The Software may contain the software and other property of third party suppliers, some of which may be identified in, and licensed in accordance with, the ¡°license.txt¡± file or other text or file in the Software: - -Developer Tools include developer documentation, installation or development utilities, and other materials. You may use them internally for the purposes of using the Software as licensed hereunder, but you may not redistribute them. -Sample Source may include example interface or application source code. You may copy, modify and compile the Sample Source and distribute it in your own products in binary and source code form. -End-User Documentation includes textual materials intended for end users. You may copy, modify and distribute them. -Licensed Binaries are redistributable code provided in binary form. You may copy and distribute Licensed Binaries with your product. -Open Source includes source which is licensed according the terms and conditions embedded in its files. -RESTRICTIONS. You will make reasonable efforts to discontinue distribution of the portions of the Software that you are licensed hereunder to distribute, upon Intel¡¯s release of an update, upgrade or new version of the Software and to make reasonable efforts to distribute such updates, upgrades or new versions to your customers who have received the Software herein. You may not reverse-assemble, reverse-compile, or otherwise reverse-engineer any software provided solely in binary form. Distribution of the Software is also subject to the following limitations: you (i) are solely responsible to your customers for any update or support obligation or other liability which may arise from the distribution, (ii) do not make any statement that your product is "certified," or that its performance is guaranteed, by Intel, (iii) do not use Intel's name or trademarks to market your product without written permission, (iv) shall prohibit disassembly and reverse engineering, and (v) shall indemnify, hold harmless, and defend Intel and its suppliers from and against any claims or lawsuits, including attorney's fees, that arise or result from your distribution of any product. - -MEDIA FORMAT CODECS AND DIGITAL RIGHTS MANAGEMENT. You acknowledge and agree that your use of the Software or distribution thereof with your products as permitted by this license may require you to procure license(s) from one or more third parties that may hold intellectual property rights applicable to the media format transcoding and/or digital rights management capabilities of the Software, if any. - -OWNERSHIP OF SOFTWARE AND COPYRIGHTS. Title to all copies of the Software remains with Intel or its suppliers. The Software is copyrighted and protected by the laws of the United States and other countries, and international treaty provisions. You may not remove any copyright notices from the Software. Intel may make changes to the Software, or to items referenced therein, at any time without notice, but is not obligated to support or update the Software. Except as otherwise expressly provided, Intel grants no express or implied right under Intel patents, copyrights, trademarks, or other intellectual property rights. You may transfer the Software only if the recipient agrees to be fully bound by these terms and if you retain no copies of the Software. - -LICENSE TO USE COMMENTS OR SUGGESTIONS. This Agreement does NOT obligate you to provide Intel with comments or suggestions regarding the Software. However, should you provide Intel with comments or suggestions for the modification, correction, improvement or enhancement of (a) the Software or (b) Intel products or processes which work with the Software, you grants to Intel a non-exclusive, irrevocable, worldwide, royalty-free license, with the right to sublicense Intel¡¯s licensees and customers, under your intellectual property rights, the rights to use and disclose such comments and suggestions in any manner Intel chooses and to display, perform, copy, make, have made, use, sell, and otherwise dispose of Intel¡¯s and its sublicensee¡¯s products embodying such comments and suggestions in any manner and via any media Intel chooses, without reference to the source. - -LIMITED MEDIA WARRANTY. If the Software has been delivered by Intel on physical media, Intel warrants the media to be free from material physical defects for a period of ninety days after delivery by Intel. If such a defect is found, return the media to Intel for replacement or alternate delivery of the Software as Intel may select. - -EXCLUSION OF OTHER WARRANTIES. EXCEPT AS PROVIDED ABOVE, THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY EXPRESS OR IMPLIED WARRANTY OF ANY KIND INCLUDING WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT, OR FITNESS FOR A PARTICULAR PURPOSE. Intel does not warrant or assume responsibility for the accuracy or completeness of any information, text, graphics, links or other items contained within the Software. - -LIMITATION OF LIABILITY. IN NO EVENT SHALL INTEL OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, LOST PROF-ITS, BUSINESS INTERRUPTION, OR LOST INFORMATION) ARISING OUT OF THE USE OF OR IN-ABILITY TO USE THE SOFTWARE, EVEN IF INTEL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME JURISDICTIONS PROHIBIT EXCLUSION OR LIMITA-TION OF LIABILITY FOR IMPLIED WARRANTIES OR CONSEQUENTIAL OR INCIDENTAL DAMAGES, SO THE ABOVE LIMITA-TION MAY NOT APPLY TO YOU. YOU MAY ALSO HAVE OTHER LEGAL RIGHTS THAT VARY FROM JURISDICTION TO JURISDICTION. - -TERMINATION OF THIS AGREEMENT. Intel may terminate this Agreement at any time if you violate its terms. Upon termination, you will immediately destroy the Software or return all copies of the Software to Intel. - -APPLICABLE LAWS. Claims arising under this Agreement shall be governed by the laws of Delaware, excluding its principles of conflict of laws and the United Nations Convention on Contracts for the Sale of Goods. You may not export the Software in violation of applicable export laws and regulations. Intel is not obligated under any other agreements unless they are in writing and signed by an authorized representative of Intel. - -EXPORT REGULATIONS. You shall not export, either directly or indirectly, any product, service or technical data or system incorporating such items without first obtaining any required license or other approval from the U. S. Department of Commerce or any other agency or department of the United States Government. In the event any product is exported from the United States or re-exported from a foreign destination by you, you shall ensure that the distribution and export/re-export or import of the product is in compliance with all laws, regulations, orders, or other restrictions of the U.S. Export Administration Regulations and the appropriate foreign government. You agree that neither you nor any of your subsidiaries will export/re-export any technical data, process, product, or service, directly or indirectly, to any country for which the United States government or any agency thereof or the foreign government from where it is shipping requires an export license, or other governmental approval, without first obtaining such license or approval. - -GOVERNMENT RESTRICTED RIGHTS. The Software is provided with "RESTRICTED RIGHTS." Use, duplication, or disclosure by the Government is subject to restrictions as set forth in FAR52.227-14 and DFAR252.227-7013 et seq. or its successor. Use of the Software by the Government constitutes acknowledg-ment of Intel's proprietary rights therein. Contractor or Manufacturer is Intel Corporation, 2200 Mission College Blvd., Santa Clara, CA 95052. - - - libvpx License - ----------------------- - -Copyright (c) 2010, Google Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -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. - -Neither the name of Google 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. - - - mustache.js License - ----------------------- - -The MIT License - -Copyright (c) 2009 Chris Wanstrath (Ruby) -Copyright (c) 2010-2014 Jan Lehnardt (JavaScript) - -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. - - - express License - ----------------------- - -The MIT License (MIT) - -Copyright (c) - -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. - - - socket.io License - ----------------------- - -Copyright (c) 2010 - 2014, Alfred E. Heggestad -Copyright (c) 2010 - 2014, Richard Aas -Copyright (c) 2010 - 2014, Creytiv.com -All rights reserved. - - -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 Creytiv.com 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 AUTHOR ``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 AUTHOR 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. - - - - Bootstrap License - ----------------------- - -The MIT License (MIT) - -Copyright (c) 2011-2014 Twitter, Inc - -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. - - - - jade License - ----------------------- - -The MIT License (MIT) - -Copyright (c) - -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. - - - ffmpeg License - ----------------------- - - 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! - - -OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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 licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - speex License - ----------------------- - -(c) 2002-2003, Jean-Marc Valin/Xiph.Org Foundation - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -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. -Neither the name of the Xiph.org Foundation 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 FOUNDATION 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. - - - mongojs License - ----------------------- -Copyright (C) 2011-2014 by Mathias Buus Madsen - -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. - - - FreeType 2 License - ----------------------- - The FreeType Project LICENSE - ---------------------------- - - 2006-Jan-27 - - Copyright 1996-2002, 2006 by - David Turner, Robert Wilhelm, and Werner Lemberg - - - -Introduction -============ - - The FreeType Project is distributed in several archive packages; - some of them may contain, in addition to the FreeType font engine, - various tools and contributions which rely on, or relate to, the - FreeType Project. - - This license applies to all files found in such packages, and - which do not fall under their own explicit license. The license - affects thus the FreeType font engine, the test programs, - documentation and makefiles, at the very least. - - This license was inspired by the BSD, Artistic, and IJG - (Independent JPEG Group) licenses, which all encourage inclusion - and use of free software in commercial and freeware products - alike. As a consequence, its main points are that: - - o We don't promise that this software works. However, we will be - interested in any kind of bug reports. (`as is' distribution) - - o You can use this software for whatever you want, in parts or - full form, without having to pay us. (`royalty-free' usage) - - o You may not pretend that you wrote this software. If you use - it, or only parts of it, in a program, you must acknowledge - somewhere in your documentation that you have used the - FreeType code. (`credits') - - We specifically permit and encourage the inclusion of this - software, with or without modifications, in commercial products. - We disclaim all warranties covering The FreeType Project and - assume no liability related to The FreeType Project. - - - Finally, many people asked us for a preferred form for a - credit/disclaimer to use in compliance with this license. We thus - encourage you to use the following text: - - """ - Portions of this software are copyright ? The FreeType - Project (www.freetype.org). All rights reserved. - """ - - Please replace with the value from the FreeType version you - actually use. - - -Legal Terms -=========== - -0. Definitions --------------- - - Throughout this license, the terms `package', `FreeType Project', - and `FreeType archive' refer to the set of files originally - distributed by the authors (David Turner, Robert Wilhelm, and - Werner Lemberg) as the `FreeType Project', be they named as alpha, - beta or final release. - - `You' refers to the licensee, or person using the project, where - `using' is a generic term including compiling the project's source - code as well as linking it to form a `program' or `executable'. - This program is referred to as `a program using the FreeType - engine'. - - This license applies to all files distributed in the original - FreeType Project, including all source code, binaries and - documentation, unless otherwise stated in the file in its - original, unmodified form as distributed in the original archive. - If you are unsure whether or not a particular file is covered by - this license, you must contact us to verify this. - - The FreeType Project is copyright (C) 1996-2000 by David Turner, - Robert Wilhelm, and Werner Lemberg. All rights reserved except as - specified below. - -1. No Warranty --------------- - - THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY - KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO - USE, OF THE FREETYPE PROJECT. - -2. Redistribution ------------------ - - This license grants a worldwide, royalty-free, perpetual and - irrevocable right and license to use, execute, perform, compile, - display, copy, create derivative works of, distribute and - sublicense the FreeType Project (in both source and object code - forms) and derivative works thereof for any purpose; and to - authorize others to exercise some or all of the rights granted - herein, subject to the following conditions: - - o Redistribution of source code must retain this license file - (`FTL.TXT') unaltered; any additions, deletions or changes to - the original files must be clearly indicated in accompanying - documentation. The copyright notices of the unaltered, - original files must be preserved in all copies of source - files. - - o Redistribution in binary form must provide a disclaimer that - states that the software is based in part of the work of the - FreeType Team, in the distribution documentation. We also - encourage you to put an URL to the FreeType web page in your - documentation, though this isn't mandatory. - - These conditions apply to any software derived from or based on - the FreeType Project, not just the unmodified files. If you use - our work, you must acknowledge us. However, no fee need be paid - to us. - -3. Advertising --------------- - - Neither the FreeType authors and contributors nor you shall use - the name of the other for commercial, advertising, or promotional - purposes without specific prior written permission. - - We suggest, but do not require, that you use one or more of the - following phrases to refer to this software in your documentation - or advertising materials: `FreeType Project', `FreeType Engine', - `FreeType library', or `FreeType Distribution'. - - As you have not signed this license, you are not required to - accept it. However, as the FreeType Project is copyrighted - material, only this license, or another one contracted with the - authors, grants you the right to use, distribute, and modify it. - Therefore, by using, distributing, or modifying the FreeType - Project, you indicate that you understand and accept all the terms - of this license. - -4. Contacts ------------ - - There are two mailing lists related to FreeType: - - o freetype@nongnu.org - - Discusses general use and applications of FreeType, as well as - future and wanted additions to the library and distribution. - If you are looking for support, start in this list if you - haven't found anything to help you in the documentation. - - o freetype-devel@nongnu.org - - Discusses bugs, as well as engine internals, design issues, - specific licenses, porting, etc. - - Our home page can be found at - - http://www.freetype.org - - ---- end of FTL.TXT --- - - - PNotify License - ----------------------- - MOZILLA PUBLIC LICENSE - Version 1.1 - - --------------- - -1. Definitions. - - 1.0.1. "Commercial Use" means distribution or otherwise making the - Covered Code available to a third party. - - 1.1. "Contributor" means each entity that creates or contributes to - the creation of Modifications. - - 1.2. "Contributor Version" means the combination of the Original - Code, prior Modifications used by a Contributor, and the Modifications - made by that particular Contributor. - - 1.3. "Covered Code" means the Original Code or Modifications or the - combination of the Original Code and Modifications, in each case - including portions thereof. - - 1.4. "Electronic Distribution Mechanism" means a mechanism generally - accepted in the software development community for the electronic - transfer of data. - - 1.5. "Executable" means Covered Code in any form other than Source - Code. - - 1.6. "Initial Developer" means the individual or entity identified - as the Initial Developer in the Source Code notice required by Exhibit - A. - - 1.7. "Larger Work" means a work which combines Covered Code or - portions thereof with code not governed by the terms of this License. - - 1.8. "License" means this document. - - 1.8.1. "Licensable" means having the right to grant, to the maximum - extent possible, whether at the time of the initial grant or - subsequently acquired, any and all of the rights conveyed herein. - - 1.9. "Modifications" means any addition to or deletion from the - substance or structure of either the Original Code or any previous - Modifications. When Covered Code is released as a series of files, a - Modification is: - A. Any addition to or deletion from the contents of a file - containing Original Code or previous Modifications. - - B. Any new file that contains any part of the Original Code or - previous Modifications. - - 1.10. "Original Code" means Source Code of computer software code - which is described in the Source Code notice required by Exhibit A as - Original Code, and which, at the time of its release under this - License is not already Covered Code governed by this License. - - 1.10.1. "Patent Claims" means any patent claim(s), now owned or - hereafter acquired, including without limitation, method, process, - and apparatus claims, in any patent Licensable by grantor. - - 1.11. "Source Code" means the preferred form of the Covered Code for - making modifications to it, including all modules it contains, plus - any associated interface definition files, scripts used to control - compilation and installation of an Executable, or source code - differential comparisons against either the Original Code or another - well known, available Covered Code of the Contributor's choice. The - Source Code can be in a compressed or archival form, provided the - appropriate decompression or de-archiving software is widely available - for no charge. - - 1.12. "You" (or "Your") means an individual or a legal entity - exercising rights under, and complying with all of the terms of, this - License or a future version of this License issued under Section 6.1. - For legal entities, "You" includes any entity which controls, is - controlled by, or is under common control with You. For purposes of - this definition, "control" means (a) the power, direct or indirect, - to cause the direction or management of such entity, whether by - contract or otherwise, or (b) ownership of more than fifty percent - (50%) of the outstanding shares or beneficial ownership of such - entity. - -2. Source Code License. - - 2.1. The Initial Developer Grant. - The Initial Developer hereby grants You a world-wide, royalty-free, - non-exclusive license, subject to third party intellectual property - claims: - (a) under intellectual property rights (other than patent or - trademark) Licensable by Initial Developer to use, reproduce, - modify, display, perform, sublicense and distribute the Original - Code (or portions thereof) with or without Modifications, and/or - as part of a Larger Work; and - - (b) under Patents Claims infringed by the making, using or - selling of Original Code, to make, have made, use, practice, - sell, and offer for sale, and/or otherwise dispose of the - Original Code (or portions thereof). - - (c) the licenses granted in this Section 2.1(a) and (b) are - effective on the date Initial Developer first distributes - Original Code under the terms of this License. - - (d) Notwithstanding Section 2.1(b) above, no patent license is - granted: 1) for code that You delete from the Original Code; 2) - separate from the Original Code; or 3) for infringements caused - by: i) the modification of the Original Code or ii) the - combination of the Original Code with other software or devices. - - 2.2. Contributor Grant. - Subject to third party intellectual property claims, each Contributor - hereby grants You a world-wide, royalty-free, non-exclusive license - - (a) under intellectual property rights (other than patent or - trademark) Licensable by Contributor, to use, reproduce, modify, - display, perform, sublicense and distribute the Modifications - created by such Contributor (or portions thereof) either on an - unmodified basis, with other Modifications, as Covered Code - and/or as part of a Larger Work; and - - (b) under Patent Claims infringed by the making, using, or - selling of Modifications made by that Contributor either alone - and/or in combination with its Contributor Version (or portions - of such combination), to make, use, sell, offer for sale, have - made, and/or otherwise dispose of: 1) Modifications made by that - Contributor (or portions thereof); and 2) the combination of - Modifications made by that Contributor with its Contributor - Version (or portions of such combination). - - (c) the licenses granted in Sections 2.2(a) and 2.2(b) are - effective on the date Contributor first makes Commercial Use of - the Covered Code. - - (d) Notwithstanding Section 2.2(b) above, no patent license is - granted: 1) for any code that Contributor has deleted from the - Contributor Version; 2) separate from the Contributor Version; - 3) for infringements caused by: i) third party modifications of - Contributor Version or ii) the combination of Modifications made - by that Contributor with other software (except as part of the - Contributor Version) or other devices; or 4) under Patent Claims - infringed by Covered Code in the absence of Modifications made by - that Contributor. - -3. Distribution Obligations. - - 3.1. Application of License. - The Modifications which You create or to which You contribute are - governed by the terms of this License, including without limitation - Section 2.2. The Source Code version of Covered Code may be - distributed only under the terms of this License or a future version - of this License released under Section 6.1, and You must include a - copy of this License with every copy of the Source Code You - distribute. You may not offer or impose any terms on any Source Code - version that alters or restricts the applicable version of this - License or the recipients' rights hereunder. However, You may include - an additional document offering the additional rights described in - Section 3.5. - - 3.2. Availability of Source Code. - Any Modification which You create or to which You contribute must be - made available in Source Code form under the terms of this License - either on the same media as an Executable version or via an accepted - Electronic Distribution Mechanism to anyone to whom you made an - Executable version available; and if made available via Electronic - Distribution Mechanism, must remain available for at least twelve (12) - months after the date it initially became available, or at least six - (6) months after a subsequent version of that particular Modification - has been made available to such recipients. You are responsible for - ensuring that the Source Code version remains available even if the - Electronic Distribution Mechanism is maintained by a third party. - - 3.3. Description of Modifications. - You must cause all Covered Code to which You contribute to contain a - file documenting the changes You made to create that Covered Code and - the date of any change. You must include a prominent statement that - the Modification is derived, directly or indirectly, from Original - Code provided by the Initial Developer and including the name of the - Initial Developer in (a) the Source Code, and (b) in any notice in an - Executable version or related documentation in which You describe the - origin or ownership of the Covered Code. - - 3.4. Intellectual Property Matters - (a) Third Party Claims. - If Contributor has knowledge that a license under a third party's - intellectual property rights is required to exercise the rights - granted by such Contributor under Sections 2.1 or 2.2, - Contributor must include a text file with the Source Code - distribution titled "LEGAL" which describes the claim and the - party making the claim in sufficient detail that a recipient will - know whom to contact. If Contributor obtains such knowledge after - the Modification is made available as described in Section 3.2, - Contributor shall promptly modify the LEGAL file in all copies - Contributor makes available thereafter and shall take other steps - (such as notifying appropriate mailing lists or newsgroups) - reasonably calculated to inform those who received the Covered - Code that new knowledge has been obtained. - - (b) Contributor APIs. - If Contributor's Modifications include an application programming - interface and Contributor has knowledge of patent licenses which - are reasonably necessary to implement that API, Contributor must - also include this information in the LEGAL file. - - (c) Representations. - Contributor represents that, except as disclosed pursuant to - Section 3.4(a) above, Contributor believes that Contributor's - Modifications are Contributor's original creation(s) and/or - Contributor has sufficient rights to grant the rights conveyed by - this License. - - 3.5. Required Notices. - You must duplicate the notice in Exhibit A in each file of the Source - Code. If it is not possible to put such notice in a particular Source - Code file due to its structure, then You must include such notice in a - location (such as a relevant directory) where a user would be likely - to look for such a notice. If You created one or more Modification(s) - You may add your name as a Contributor to the notice described in - Exhibit A. You must also duplicate this License in any documentation - for the Source Code where You describe recipients' rights or ownership - rights relating to Covered Code. You may choose to offer, and to - charge a fee for, warranty, support, indemnity or liability - obligations to one or more recipients of Covered Code. However, You - may do so only on Your own behalf, and not on behalf of the Initial - Developer or any Contributor. You must make it absolutely clear than - any such warranty, support, indemnity or liability obligation is - offered by You alone, and You hereby agree to indemnify the Initial - Developer and every Contributor for any liability incurred by the - Initial Developer or such Contributor as a result of warranty, - support, indemnity or liability terms You offer. - - 3.6. Distribution of Executable Versions. - You may distribute Covered Code in Executable form only if the - requirements of Section 3.1-3.5 have been met for that Covered Code, - and if You include a notice stating that the Source Code version of - the Covered Code is available under the terms of this License, - including a description of how and where You have fulfilled the - obligations of Section 3.2. The notice must be conspicuously included - in any notice in an Executable version, related documentation or - collateral in which You describe recipients' rights relating to the - Covered Code. You may distribute the Executable version of Covered - Code or ownership rights under a license of Your choice, which may - contain terms different from this License, provided that You are in - compliance with the terms of this License and that the license for the - Executable version does not attempt to limit or alter the recipient's - rights in the Source Code version from the rights set forth in this - License. If You distribute the Executable version under a different - license You must make it absolutely clear that any terms which differ - from this License are offered by You alone, not by the Initial - Developer or any Contributor. You hereby agree to indemnify the - Initial Developer and every Contributor for any liability incurred by - the Initial Developer or such Contributor as a result of any such - terms You offer. - - 3.7. Larger Works. - You may create a Larger Work by combining Covered Code with other code - not governed by the terms of this License and distribute the Larger - Work as a single product. In such a case, You must make sure the - requirements of this License are fulfilled for the Covered Code. - -4. Inability to Comply Due to Statute or Regulation. - - If it is impossible for You to comply with any of the terms of this - License with respect to some or all of the Covered Code due to - statute, judicial order, or regulation then You must: (a) comply with - the terms of this License to the maximum extent possible; and (b) - describe the limitations and the code they affect. Such description - must be included in the LEGAL file described in Section 3.4 and must - be included with all distributions of the Source Code. Except to the - extent prohibited by statute or regulation, such description must be - sufficiently detailed for a recipient of ordinary skill to be able to - understand it. - -5. Application of this License. - - This License applies to code to which the Initial Developer has - attached the notice in Exhibit A and to related Covered Code. - -6. Versions of the License. - - 6.1. New Versions. - Netscape Communications Corporation ("Netscape") may publish revised - and/or new versions of the License from time to time. Each version - will be given a distinguishing version number. - - 6.2. Effect of New Versions. - Once Covered Code has been published under a particular version of the - License, You may always continue to use it under the terms of that - version. You may also choose to use such Covered Code under the terms - of any subsequent version of the License published by Netscape. No one - other than Netscape has the right to modify the terms applicable to - Covered Code created under this License. - - 6.3. Derivative Works. - If You create or use a modified version of this License (which you may - only do in order to apply it to code which is not already Covered Code - governed by this License), You must (a) rename Your license so that - the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", - "MPL", "NPL" or any confusingly similar phrase do not appear in your - license (except to note that your license differs from this License) - and (b) otherwise make it clear that Your version of the license - contains terms which differ from the Mozilla Public License and - Netscape Public License. (Filling in the name of the Initial - Developer, Original Code or Contributor in the notice described in - Exhibit A shall not of themselves be deemed to be modifications of - this License.) - -7. DISCLAIMER OF WARRANTY. - - COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, - WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, - WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF - DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. - THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE - IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, - YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE - COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER - OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF - ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. - -8. TERMINATION. - - 8.1. This License and the rights granted hereunder will terminate - automatically if You fail to comply with terms herein and fail to cure - such breach within 30 days of becoming aware of the breach. All - sublicenses to the Covered Code which are properly granted shall - survive any termination of this License. Provisions which, by their - nature, must remain in effect beyond the termination of this License - shall survive. - - 8.2. If You initiate litigation by asserting a patent infringement - claim (excluding declatory judgment actions) against Initial Developer - or a Contributor (the Initial Developer or Contributor against whom - You file such action is referred to as "Participant") alleging that: - - (a) such Participant's Contributor Version directly or indirectly - infringes any patent, then any and all rights granted by such - Participant to You under Sections 2.1 and/or 2.2 of this License - shall, upon 60 days notice from Participant terminate prospectively, - unless if within 60 days after receipt of notice You either: (i) - agree in writing to pay Participant a mutually agreeable reasonable - royalty for Your past and future use of Modifications made by such - Participant, or (ii) withdraw Your litigation claim with respect to - the Contributor Version against such Participant. If within 60 days - of notice, a reasonable royalty and payment arrangement are not - mutually agreed upon in writing by the parties or the litigation claim - is not withdrawn, the rights granted by Participant to You under - Sections 2.1 and/or 2.2 automatically terminate at the expiration of - the 60 day notice period specified above. - - (b) any software, hardware, or device, other than such Participant's - Contributor Version, directly or indirectly infringes any patent, then - any rights granted to You by such Participant under Sections 2.1(b) - and 2.2(b) are revoked effective as of the date You first made, used, - sold, distributed, or had made, Modifications made by that - Participant. - - 8.3. If You assert a patent infringement claim against Participant - alleging that such Participant's Contributor Version directly or - indirectly infringes any patent where such claim is resolved (such as - by license or settlement) prior to the initiation of patent - infringement litigation, then the reasonable value of the licenses - granted by such Participant under Sections 2.1 or 2.2 shall be taken - into account in determining the amount or value of any payment or - license. - - 8.4. In the event of termination under Sections 8.1 or 8.2 above, - all end user license agreements (excluding distributors and resellers) - which have been validly granted by You or any distributor hereunder - prior to termination shall survive termination. - -9. LIMITATION OF LIABILITY. - - UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT - (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL - DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, - OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR - ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY - CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, - WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER - COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN - INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF - LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY - RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW - PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE - EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO - THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. - -10. U.S. GOVERNMENT END USERS. - - The Covered Code is a "commercial item," as that term is defined in - 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer - software" and "commercial computer software documentation," as such - terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 - C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), - all U.S. Government End Users acquire Covered Code with only those - rights set forth herein. - -11. MISCELLANEOUS. - - This License represents the complete agreement concerning subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. This License shall be governed by - California law provisions (except to the extent applicable law, if - any, provides otherwise), excluding its conflict-of-law provisions. - With respect to disputes in which at least one party is a citizen of, - or an entity chartered or registered to do business in the United - States of America, any litigation relating to this License shall be - subject to the jurisdiction of the Federal Courts of the Northern - District of California, with venue lying in Santa Clara County, - California, with the losing party responsible for costs, including - without limitation, court costs and reasonable attorneys' fees and - expenses. The application of the United Nations Convention on - Contracts for the International Sale of Goods is expressly excluded. - Any law or regulation which provides that the language of a contract - shall be construed against the drafter shall not apply to this - License. - -12. RESPONSIBILITY FOR CLAIMS. - - As between Initial Developer and the Contributors, each party is - responsible for claims and damages arising, directly or indirectly, - out of its utilization of rights under this License and You agree to - work with Initial Developer and Contributors to distribute such - responsibility on an equitable basis. Nothing herein is intended or - shall be deemed to constitute any admission of liability. - -13. MULTIPLE-LICENSED CODE. - - Initial Developer may designate portions of the Covered Code as - "Multiple-Licensed". "Multiple-Licensed" means that the Initial - Developer permits you to utilize portions of the Covered Code under - Your choice of the MPL or the alternative licenses, if any, specified - by the Initial Developer in the file described in Exhibit A. - -EXHIBIT A -Mozilla Public License. - - ``The contents of this file are subject to the Mozilla Public License - Version 1.1 (the "License"); you may not use this file except in - compliance with the License. You may obtain a copy of the License at - http://www.mozilla.org/MPL/ - - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the - License for the specific language governing rights and limitations - under the License. - - The Original Code is ______________________________________. - - The Initial Developer of the Original Code is ________________________. - Portions created by ______________________ are Copyright (C) ______ - _______________________. All Rights Reserved. - - Contributor(s): ______________________________________. - - Alternatively, the contents of this file may be used under the terms - of the _____ license (the "[___] License"), in which case the - provisions of [______] License are applicable instead of those - above. If you wish to allow use of your version of this file only - under the terms of the [____] License and not to allow others to use - your version of this file under the MPL, indicate your decision by - deleting the provisions above and replace them with the notice and - other provisions required by the [___] License. If you do not delete - the provisions above, a recipient may use your version of this file - under either the MPL or the [___] License." - - [NOTE: The text of this Exhibit A may differ slightly from the text of - the notices in the Source Code files of the Original Code. You should - use the text of this Exhibit A rather than the text found in the - Original Code Source Code for Your Modifications.] - - - OpenH264 License - ----------------------- - -==== - -------------------------------------------------------- -About The Cisco-Provided Binary of OpenH264 Video Codec -------------------------------------------------------- - -Cisco provides this program under the terms of the BSD license. - -Additionally, this binary is licensed under Cisco¿s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met. - -As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk. Your rights from Cisco under the BSD license are not affected by this choice. - -For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary - -A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org - ------------ -BSD License ------------ - -Copyright © 2014 Cisco Systems, Inc. - -All rights reserved. - -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. - ------------------------------------------ -AVC/H.264 Patent Portfolio License Notice ------------------------------------------ - -The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software: - -THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (¿AVC VIDEO¿) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO. NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE. ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM - -Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla - ---------------------------------------------- -AVC/H.264 Patent Portfolio License Conditions ---------------------------------------------- - -In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met: - -1. The Cisco-provided binary is separately downloaded to an end user¿s device, and not integrated into or combined with third party software prior to being downloaded to the end user¿s device; - -2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary; - -3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text: - - "OpenH264 Video Codec provided by Cisco Systems, Inc." - -4. Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user. - - - L.Resizer License - ----------------------- - -==== - -Copyright (c) 2013 Marc J. Schmidt - -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. - - - node-amqp License - ----------------------- - -==== - -Copyright 2010 Xavier Shay and Joyent, Inc., Ryan Dahl, Stephane Alnet - -All rights reserved. - -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. - - - node-getopt License - ----------------------- - -==== - -Copyright (c) 2012 Miao Jiang - -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. - - - aws-lib License - ----------------------- - -==== - -The MIT License (MIT) - -Copyright (c) 2010 Mirko Kiefer (the original author) - -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. - - - body-parser License - ----------------------- - -==== - -(The MIT License) - -Copyright (c) 2014 Jonathan Ong -Copyright (c) 2014-2015 Douglas Christopher Wilson - -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. - - - morgan License - ----------------------- - -==== - -(The MIT License) - -Copyright (c) 2014 Jonathan Ong -Copyright (c) 2014 Douglas Christopher Wilson - -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. - - - errorhandler License - ----------------------- - -==== - -(The MIT License) - -Copyright (c) 2014 Jonathan Ong -Copyright (c) 2014-2015 Douglas Christopher Wilson - -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. - - - hmac-sha256.js License - ----------------------- - -==== - -(c) 2009-2013 by Jeff Mott. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, -this list of conditions, and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, -this list of conditions, and the following disclaimer in the documentation -or other materials provided with the distribution. -Neither the name CryptoJS 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. - - DocStrap License - ----------------------- - - ==== - - DocStrap Copyright (c) 2012-2014 Terry Weiss. All rights reserved. - -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. - - - sprint-js - ----------------------- - - ==== - -The MIT License (MIT) - -Copyright (c) 2014-2015 Benjamin De Cock - -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. - - - usrsctp - ----------------------- - - ==== - -Copyright (c) 2015, Randall Stewart and Michael Tuexen -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* 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. - -* Neither the name of usrsctp 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. - - - diff --git a/WebRTC-Sample/owt-server/deploy/README.md b/WebRTC-Sample/owt-server/deploy/README.md new file mode 100644 index 00000000..4ed64a59 --- /dev/null +++ b/WebRTC-Sample/owt-server/deploy/README.md @@ -0,0 +1,52 @@ + +# Docker image deployment steps + +## Build docker image + +```bash +./build_image.sh webrtc360:v5.0 +``` + +## Run docker container + +```bash +docker run --privileged --network=host -it webrtc360:v5.0 /bin/bash +``` + +## Inside docker container: + + - Init and start owt-server + +```bash +cd /root/owt-server +# init owt-server, only init once after reboot +./dist/bin/init-all.sh # just type Enter +# Update RabbitMQ/MongoDB Account? [No/yes] +# Press "Enter" to use default RabbitMQ/MongoDB account +./dist/bin/start-all.sh +``` + + - Setup resolution and configuration + +```bash +cd /home/WebRTC360/rest/ +. "$HOME/.nvm/nvm.sh" +npm install mongojs +npm install . +./rest/control.js -s 4k +# { room_resolution: { width: 3840, height: 2048 } } +# Room : sampleRoom +# Room resolution 3840 x 2048 +# Update room mediaIn, h265 +# Update room mediaOut, h265 +# Room 62f36771189e1302a902b991 updated +``` + + - Start streaming + +```bash +./rest/control.js -c /home/WebRTC360/test1_h265_3840x2048_30fps_30M_200frames.mp4 +# { stream_url: +# '/home/WebRTC360/test1_h265_3840x2048_30fps_30M_200frames.mp4' } +# Stream 614594682930454900 added +``` diff --git a/WebRTC-Sample/owt-server/deploy/build_image.sh b/WebRTC-Sample/owt-server/deploy/build_image.sh new file mode 100755 index 00000000..93153ba2 --- /dev/null +++ b/WebRTC-Sample/owt-server/deploy/build_image.sh @@ -0,0 +1,81 @@ +#!/bin/bash -ex + +SCRIPT=`pwd`/$0 +PATH_NAME=`dirname ${SCRIPT}` +BUILD_DIR=${PATH_NAME}/../ +BASE_NAME="owt-base:5.0" +IMAGE_CASE=$1 +IMAGE_NAME=$2 + +usage() { + echo -e "Usage: $0 [OPTION] [NAME]\n" + echo -e "OPTION: Build from / / " + echo -e " e.g. scratch" + echo -e "NAME: Required image name" + echo -e " e.g. webrtc360:latest" + exit 1 +} + +start_build_process() { + cd ${BUILD_DIR} + docker build -t $1 \ + --build-arg http_proxy=$http_proxy \ + --build-arg https_proxy=$https_proxy \ + -f ${PATH_NAME}/${IMAGE_CASE}/Dockerfile . +} + +build_from_binary() { + IMAGE_CASE="from_binary" + if [ ! -d "${BUILD_DIR}/dist" ]; then + echo "missing dir: WebRTC360/dist" + exit 1 + fi + + if [ ! -d "${BUILD_DIR}/build" ]; then + echo "missing dir: WebRTC360/build" + exit 1 + fi + + start_build_process ${IMAGE_NAME} +} + +build_from_scratch() { + IMAGE_CASE="from_scratch" + start_build_process ${IMAGE_NAME} +} + +build_server_base() { + IMAGE_CASE="owt_server_base" + BASE_INFO="`docker images | grep owt-base || true`" + if [ -z "${BASE_INFO}" ] ; then + echo "Start building owt-server base image" + start_build_process "${BASE_NAME}" + else + BASE_IMAGE_NAME=`echo "${BASE_INFO}" | awk -F ' ' '{print $1}'` + BASE_IMAGE_TAG=`echo "${BASE_INFO}" | awk -F ' ' '{print $2}'` + echo "owt-server exists: ${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG}" + fi +} + +build_from_base() { + IMAGE_CASE="from_base" + start_build_process ${IMAGE_NAME} +} + +if [ $# != 2 ]; then + usage $* +else + cp ${BUILD_DIR}/../../Sample-Videos/test1_h265_3840x2048_30fps_30M_200frames.mp4 . + if [ ${IMAGE_CASE} == "binary" ] ; then + build_from_binary + elif [ ${IMAGE_CASE} == "scratch" ] ; then + build_from_scratch + elif [ ${IMAGE_CASE} == "base" ] ; then + build_server_base + build_from_base + else + usage $* + fi + rm -f ./test1_h265_3840x2048_30fps_30M_200frames.mp4 +fi + diff --git a/WebRTC-Sample/owt-server/deploy/from_base/Dockerfile b/WebRTC-Sample/owt-server/deploy/from_base/Dockerfile new file mode 100644 index 00000000..73c81251 --- /dev/null +++ b/WebRTC-Sample/owt-server/deploy/from_base/Dockerfile @@ -0,0 +1,31 @@ +################################ + +# Base image owt-base:5.0 +FROM owt-base:5.0 +LABEL Description="This is the complete image for WebRTC360 CentOS 7.6" +LABEL Vendor="Intel Corporation" +WORKDIR /home/WebRTC360 +ARG WORKDIR=/home/WebRTC360 + +# Configure proxy if necessary +RUN if [ -n "${http_proxy}" ] ; then \ + echo "proxy=${http_proxy}" >> /etc/yum.conf && \ + echo "http_proxy=${http_proxy}" >> /etc/wgetrc && \ + echo "https_proxy=${https_proxy}" >> /etc/wgetrc && \ + git config --global http.proxy ${http_proxy} && \ + git config --global https.proxy ${https_proxy} ; \ + fi + +# Build immersive video mixer node +RUN source /opt/rh/devtoolset-7/enable && \ + . "$HOME/.nvm/nvm.sh" && \ + ./scripts/build.js -t all + +# Pack IM video DIST +RUN source /opt/rh/devtoolset-7/enable && \ + ./scripts/pack.sh + +COPY deploy/test1_h265_3840x2048_30fps_30M_200frames.mp4 ${WORKDIR} +COPY deploy/systemctl.py /usr/bin/systemctl +RUN chmod a+x /usr/bin/systemctl + diff --git a/WebRTC-Sample/owt-server/deploy/from_binary/Dockerfile b/WebRTC-Sample/owt-server/deploy/from_binary/Dockerfile new file mode 100644 index 00000000..cc464b99 --- /dev/null +++ b/WebRTC-Sample/owt-server/deploy/from_binary/Dockerfile @@ -0,0 +1,42 @@ +################################ + +# Base image centos 7.6.1810 +FROM centos:7.6.1810 +LABEL Description="This is the complete image for WebRTC360 CentOS 7.6" +LABEL Vendor="Intel Corporation" +WORKDIR /home/WebRTC360 +ARG WORKDIR=/home/WebRTC360 + +# Prerequisites +RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 && \ + yum install -y ca-certificates wget xz-utils && \ + yum install -y epel-release boost-system boost-thread && \ + yum install -y log4cxx glib2 freetype-devel psmisc && \ + yum install -y rabbitmq-server mongodb-org docbook2X + +ARG NODE_VER=v10.21.0 +ARG NODE_REPO=https://nodejs.org/dist/${NODE_VER}/node-${NODE_VER}-linux-x64.tar.xz +RUN source ~/.bashrc && \ + wget ${NODE_REPO} && \ + tar xf node-${NODE_VER}-linux-x64.tar.xz && \ + cp node-*/* /usr/local -rf && rm -rf node-* && \ + rm -rf /var/cache/yum/* + +RUN mkdir -pv ${WORKDIR}/dist && \ + mkdir -pv ${WORKDIR}/build/libdeps/build + +COPY rest/ ${WORKDIR}/rest/ +COPY dist/ ${WORKDIR}/dist/ +COPY build/ ${WORKDIR}/build/ +COPY deploy/test1_h265_3840x2048_30fps_30M_200frames.mp4 ${WORKDIR} +COPY scripts/mongodb-org-4.4.repo /etc/yum.repos.d/ + +RUN cd rest/scripts/ && \ + npm install + +COPY deploy/systemctl.py /usr/bin/systemctl +RUN chmod a+x /usr/bin/systemctl + +ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${WORKDIR}/build/libdeps/build/lib +ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${WORKDIR}/build/libdeps/build/lib64:/usr/local/lib + diff --git a/WebRTC-Sample/owt-server/deploy/from_scratch/Dockerfile b/WebRTC-Sample/owt-server/deploy/from_scratch/Dockerfile new file mode 100644 index 00000000..7cb6258e --- /dev/null +++ b/WebRTC-Sample/owt-server/deploy/from_scratch/Dockerfile @@ -0,0 +1,104 @@ +FROM centos:7.6.1810 +LABEL Description="This is the complete image for WebRTC360 CentOS 7.6" +LABEL Vendor="Intel Corporation" +WORKDIR /home/WebRTC360 +ARG WORKDIR=/home/WebRTC360 +ARG DEPSDIR=/third_party/owt-server/build/libdeps/build/ + +# Install prerequisites +RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 +RUN yum install -y ca-certificates wget git sudo make && rm -rf /var/cache/yum/* +RUN wget https://download-ib01.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-7-14.noarch.rpm && \ + rpm -Uvh epel-release*rpm && rm -rf epel-release-7-14.noarch.rpm +RUN yum install -y centos-release-scl-rh && rm -rf /var/cache/yum/* +RUN yum install -y epel-release boost-system boost-thread xz-utils && rm -rf /var/cache/yum/* +RUN yum install -y log4cxx glib2 freetype-devel psmisc devtoolset-7-gcc* && rm -rf /var/cache/yum/* +RUN yum install -y rabbitmq-server libcurl-devel mongodb-org && rm -rf /var/cache/yum/* + +# Configure proxy if necessary +RUN if [ -n "${http_proxy}" ] ; then \ + echo "http_proxy=${http_proxy}" >> /etc/wgetrc && \ + echo "https_proxy=${https_proxy}" >> /etc/wgetrc && \ + git config --global http.proxy ${http_proxy} && \ + git config --global https.proxy ${https_proxy} ; \ + fi + +# Clone owt-server repo and install +RUN mkdir -p ${WORKDIR}/third_party && \ + cd ${WORKDIR}/third_party && \ + git clone --branch 5.0.x https://github.com/open-webrtc-toolkit/owt-server.git && \ + cd ${WORKDIR}/third_party/owt-server && \ + source /opt/rh/devtoolset-7/enable && \ + yum install -y python3 && rm -rf /var/cache/yum/* && \ + export LD_LIBRARY_PATH=${WORKDIR}/${DEPSDIR}/lib/:$LD_LIBRARY_PATH && \ + export LD_LIBRARY_PATH=${WORKDIR}/${DEPSDIR}/lib64/:$LD_LIBRARY_PATH && \ + export PKG_CONFIG_PATH=${WORKDIR}/${DEPSDIR}/lib/pkgconfig/:$PKG_CONFIG_PATH && \ + export PKG_CONFIG_PATH=${WORKDIR}/${DEPSDIR}/lib64/pkgconfig/:$PKG_CONFIG_PATH && \ + ./scripts/installDepsUnattended.sh + +RUN cd ${WORKDIR}/third_party/owt-server && \ + source /opt/rh/devtoolset-7/enable && \ + \. "$HOME/.nvm/nvm.sh" && \ + export LD_LIBRARY_PATH=${WORKDIR}/${DEPSDIR}/lib/:$LD_LIBRARY_PATH && \ + export LD_LIBRARY_PATH=${WORKDIR}/${DEPSDIR}/lib64/:$LD_LIBRARY_PATH && \ + export PKG_CONFIG_PATH=${WORKDIR}/${DEPSDIR}/lib/pkgconfig/:$PKG_CONFIG_PATH && \ + export PKG_CONFIG_PATH=${WORKDIR}/${DEPSDIR}/lib64/pkgconfig/:$PKG_CONFIG_PATH && \ + ./scripts/build.js -t mcu --check + +# Clone owt-client-javascript repo and install +RUN mkdir -p ${WORKDIR}/third_party && \ + cd ${WORKDIR}/third_party && \ + git clone --branch 5.0.x https://github.com/open-webrtc-toolkit/owt-client-javascript.git && \ + cd ${WORKDIR}/third_party/owt-client-javascript/scripts && \ + . "$HOME/.nvm/nvm.sh" && \ + npm install -g grunt-cli && \ + npm install -g mongojs && \ + npm i npm@6.13.1 -g && \ + npm install && \ + grunt + +# Pack owt +RUN cd ${WORKDIR}/third_party/owt-server && \ + . "$HOME/.nvm/nvm.sh" && \ + export LD_LIBRARY_PATH=${WORKDIR}/${DEPSDIR}/lib/:$LD_LIBRARY_PATH && \ + export LD_LIBRARY_PATH=${WORKDIR}/${DEPSDIR}/lib64/:$LD_LIBRARY_PATH && \ + export PKG_CONFIG_PATH=${WORKDIR}/${DEPSDIR}/lib/pkgconfig/:$PKG_CONFIG_PATH && \ + export PKG_CONFIG_PATH=${WORKDIR}/${DEPSDIR}/lib64/pkgconfig/:$PKG_CONFIG_PATH && \ + ./scripts/pack.js \ + -t all -i -with-ffmpeg \ + -p ../owt-client-javascript/dist/samples/conference + +# Get necessary files ready +COPY rest/ ${WORKDIR}/rest/ +COPY source/ ${WORKDIR}/source/ +COPY scripts/ ${WORKDIR}/scripts/ +COPY scripts/mongodb-org-4.4.repo /etc/yum.repos.d/ + +# Install cmake +ARG CMAKE_VER=3.12.4 +ARG CMAKE_REPO=https://cmake.org/files +RUN wget -O - ${CMAKE_REPO}/v${CMAKE_VER%.*}/cmake-${CMAKE_VER}.tar.gz | tar xz && \ + cd cmake-${CMAKE_VER} && \ + source /opt/rh/devtoolset-7/enable && \ + ./bootstrap --prefix="/usr" --system-curl && \ + make -j$(nproc) && \ + make install && \ + cd ${WORKDIR} && rm -rf ./cmake* + +# Install deps +RUN source /opt/rh/devtoolset-7/enable && \ + . "$HOME/.nvm/nvm.sh" && \ + ./scripts/install_deps.sh + +# Build immersive video mixer node +RUN source /opt/rh/devtoolset-7/enable && \ + . "$HOME/.nvm/nvm.sh" && \ + ./scripts/build.js -t all + +# Pack IM video DIST +RUN source /opt/rh/devtoolset-7/enable && \ + ./scripts/pack.sh + +COPY deploy/test1_h265_3840x2048_30fps_30M_200frames.mp4 ${WORKDIR} +COPY deploy/systemctl.py /usr/bin/systemctl +RUN chmod a+x /usr/bin/systemctl diff --git a/WebRTC-Sample/owt-server/deploy/systemctl.py b/WebRTC-Sample/owt-server/deploy/systemctl.py new file mode 100644 index 00000000..120b9aac --- /dev/null +++ b/WebRTC-Sample/owt-server/deploy/systemctl.py @@ -0,0 +1,6477 @@ +#! /usr/bin/python2 +## generated from systemctl3.py - do not change + +from __future__ import print_function + +__copyright__ = "(C) 2016-2020 Guido U. Draheim, licensed under the EUPL" +__version__ = "1.5.4505" + +import logging +logg = logging.getLogger("systemctl") + +from types import GeneratorType +import re +import fnmatch +import shlex +import collections +import errno +import os +import sys +import signal +import time +import socket +import datetime +import string +import fcntl +import select +import hashlib +import pwd +import grp +import threading + +if sys.version[0] == '3': + basestring = str + xrange = range + +DEBUG_AFTER = False +DEBUG_STATUS = False +DEBUG_BOOTTIME = False +DEBUG_INITLOOP = False +DEBUG_KILLALL = False +DEBUG_FLOCK = False +TestListen = False +TestAccept = False + +def logg_debug_flock(format, *args): + if DEBUG_FLOCK: + logg.debug(format, *args) # pragma: no cover +def logg_debug_after(format, *args): + if DEBUG_AFTER: + logg.debug(format, *args) # pragma: no cover + +NOT_A_PROBLEM = 0 # FOUND_OK +NOT_OK = 1 # FOUND_ERROR +NOT_ACTIVE = 2 # FOUND_INACTIVE +NOT_FOUND = 4 # FOUND_UNKNOWN + +# defaults for options +_extra_vars = [] +_force = False +_full = False +_log_lines = 0 +_no_pager = False +_now = False +_no_reload = False +_no_legend = False +_no_ask_password = False +_preset_mode = "all" +_quiet = False +_root = "" +_unit_type = None +_unit_state = None +_unit_property = None +_what_kind = "" +_show_all = False +_user_mode = False + +# common default paths +_system_folder1 = "/etc/systemd/system" +_system_folder2 = "/run/systemd/system" +_system_folder3 = "/var/run/systemd/system" +_system_folder4 = "/usr/local/lib/systemd/system" +_system_folder5 = "/usr/lib/systemd/system" +_system_folder6 = "/lib/systemd/system" +_system_folderX = None +_user_folder1 = "{XDG_CONFIG_HOME}/systemd/user" +_user_folder2 = "/etc/systemd/user" +_user_folder3 = "{XDG_RUNTIME_DIR}/systemd/user" +_user_folder4 = "/run/systemd/user" +_user_folder5 = "/var/run/systemd/user" +_user_folder6 = "{XDG_DATA_HOME}/systemd/user" +_user_folder7 = "/usr/local/lib/systemd/user" +_user_folder8 = "/usr/lib/systemd/user" +_user_folder9 = "/lib/systemd/user" +_user_folderX = None +_init_folder1 = "/etc/init.d" +_init_folder2 = "/run/init.d" +_init_folder3 = "/var/run/init.d" +_init_folderX = None +_preset_folder1 = "/etc/systemd/system-preset" +_preset_folder2 = "/run/systemd/system-preset" +_preset_folder3 = "/var/run/systemd/system-preset" +_preset_folder4 = "/usr/local/lib/systemd/system-preset" +_preset_folder5 = "/usr/lib/systemd/system-preset" +_preset_folder6 = "/lib/systemd/system-preset" +_preset_folderX = None + +# standard paths +_dev_null = "/dev/null" +_dev_zero = "/dev/zero" +_etc_hosts = "/etc/hosts" +_rc3_boot_folder = "/etc/rc3.d" +_rc3_init_folder = "/etc/init.d/rc3.d" +_rc5_boot_folder = "/etc/rc5.d" +_rc5_init_folder = "/etc/init.d/rc5.d" +_proc_pid_stat = "/proc/{pid}/stat" +_proc_pid_status = "/proc/{pid}/status" +_proc_pid_cmdline= "/proc/{pid}/cmdline" +_proc_pid_dir = "/proc" +_proc_sys_uptime = "/proc/uptime" +_proc_sys_stat = "/proc/stat" + +# default values +SystemCompatibilityVersion = 219 +SysInitTarget = "sysinit.target" +SysInitWait = 5 # max for target +MinimumYield = 0.5 +MinimumTimeoutStartSec = 4 +MinimumTimeoutStopSec = 4 +DefaultTimeoutStartSec = 90 # official value +DefaultTimeoutStopSec = 90 # official value +DefaultTimeoutAbortSec = 3600 # officially it none (usually larget than StopSec) +DefaultMaximumTimeout = 200 # overrides all other +DefaultRestartSec = 0.1 # official value of 100ms +DefaultStartLimitIntervalSec = 10 # official value +DefaultStartLimitBurst = 5 # official value +InitLoopSleep = 5 +MaxLockWait = 0 # equals DefaultMaximumTimeout +DefaultPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +ResetLocale = ["LANG", "LANGUAGE", "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", + "LC_MESSAGES", "LC_PAPER", "LC_NAME", "LC_ADDRESS", "LC_TELEPHONE", "LC_MEASUREMENT", + "LC_IDENTIFICATION", "LC_ALL"] +LocaleConf="/etc/locale.conf" +DefaultListenBacklog=2 + +ExitWhenNoMoreServices = False +ExitWhenNoMoreProcs = False +DefaultUnit = os.environ.get("SYSTEMD_DEFAULT_UNIT", "default.target") # systemd.exe --unit=default.target +DefaultTarget = os.environ.get("SYSTEMD_DEFAULT_TARGET", "multi-user.target") # DefaultUnit fallback +# LogLevel = os.environ.get("SYSTEMD_LOG_LEVEL", "info") # systemd.exe --log-level +# LogTarget = os.environ.get("SYSTEMD_LOG_TARGET", "journal-or-kmsg") # systemd.exe --log-target +# LogLocation = os.environ.get("SYSTEMD_LOG_LOCATION", "no") # systemd.exe --log-location +# ShowStatus = os.environ.get("SYSTEMD_SHOW_STATUS", "auto") # systemd.exe --show-status +DefaultStandardInput=os.environ.get("SYSTEMD_STANDARD_INPUT", "null") +DefaultStandardOutput=os.environ.get("SYSTEMD_STANDARD_OUTPUT", "journal") # systemd.exe --default-standard-output +DefaultStandardError=os.environ.get("SYSTEMD_STANDARD_ERROR", "inherit") # systemd.exe --default-standard-error + +EXEC_SPAWN = False +EXEC_DUP2 = True +REMOVE_LOCK_FILE = False +BOOT_PID_MIN = 0 +BOOT_PID_MAX = -9 +PROC_MAX_DEPTH = 100 +EXPAND_VARS_MAXDEPTH = 20 +EXPAND_KEEP_VARS = True +RESTART_FAILED_UNITS = True +ACTIVE_IF_ENABLED=False + +TAIL_CMD = "/usr/bin/tail" +LESS_CMD = "/usr/bin/less" +CAT_CMD = "/usr/bin/cat" + +# The systemd default was NOTIFY_SOCKET="/var/run/systemd/notify" +_notify_socket_folder = "{RUN}/systemd" # alias /run/systemd +_journal_log_folder = "{LOG}/journal" + +SYSTEMCTL_DEBUG_LOG = "{LOG}/systemctl.debug.log" +SYSTEMCTL_EXTRA_LOG = "{LOG}/systemctl.log" + +_default_targets = [ "poweroff.target", "rescue.target", "sysinit.target", "basic.target", "multi-user.target", "graphical.target", "reboot.target" ] +_feature_targets = [ "network.target", "remote-fs.target", "local-fs.target", "timers.target", "nfs-client.target" ] +_all_common_targets = [ "default.target" ] + _default_targets + _feature_targets + +# inside a docker we pretend the following +_all_common_enabled = [ "default.target", "multi-user.target", "remote-fs.target" ] +_all_common_disabled = [ "graphical.target", "resue.target", "nfs-client.target" ] + +target_requires = { "graphical.target": "multi-user.target", "multi-user.target": "basic.target", "basic.target": "sockets.target" } + +_runlevel_mappings = {} # the official list +_runlevel_mappings["0"] = "poweroff.target" +_runlevel_mappings["1"] = "rescue.target" +_runlevel_mappings["2"] = "multi-user.target" +_runlevel_mappings["3"] = "multi-user.target" +_runlevel_mappings["4"] = "multi-user.target" +_runlevel_mappings["5"] = "graphical.target" +_runlevel_mappings["6"] = "reboot.target" + +_sysv_mappings = {} # by rule of thumb +_sysv_mappings["$local_fs"] = "local-fs.target" +_sysv_mappings["$network"] = "network.target" +_sysv_mappings["$remote_fs"] = "remote-fs.target" +_sysv_mappings["$timer"] = "timers.target" + +def strINET(value): + if value == socket.SOCK_DGRAM: + return "UDP" + if value == socket.SOCK_STREAM: + return "TCP" + if value == socket.SOCK_RAW: # pragma: no cover + return "RAW" + if value == socket.SOCK_RDM: # pragma: no cover + return "RDM" + if value == socket.SOCK_SEQPACKET: # pragma: no cover + return "SEQ" + return "" # pragma: no cover + +def strYes(value): + if value is True: + return "yes" + if not value: + return "no" + return str(value) +def strE(part): + if not part: + return "" + return str(part) +def strQ(part): + if part is None: + return "" + if isinstance(part, int): + return str(part) + return "'%s'" % part +def shell_cmd(cmd): + return " ".join([strQ(part) for part in cmd]) +def to_intN(value, default = None): + if not value: + return default + try: + return int(value) + except: + return default +def to_int(value, default = 0): + try: + return int(value) + except: + return default +def to_list(value): + if not value: + return [] + if isinstance(value, list): + return value + if isinstance(value, tuple): + return list(value) + return str(value or "").split(",") +def int_mode(value): + try: return int(value, 8) + except: return None # pragma: no cover +def unit_of(module): + if "." not in module: + return module + ".service" + return module +def o22(part): + if isinstance(part, basestring): + if len(part) <= 22: + return part + return part[:5] + "..." + part[-14:] + return part # pragma: no cover (is always str) +def o44(part): + if isinstance(part, basestring): + if len(part) <= 44: + return part + return part[:10] + "..." + part[-31:] + return part # pragma: no cover (is always str) +def o77(part): + if isinstance(part, basestring): + if len(part) <= 77: + return part + return part[:20] + "..." + part[-54:] + return part # pragma: no cover (is always str) +def unit_name_escape(text): + # https://www.freedesktop.org/software/systemd/man/systemd.unit.html#id-1.6 + esc = re.sub("([^a-z-AZ.-/])", lambda m: "\\x%02x" % ord(m.group(1)[0]), text) + return esc.replace("/", "-") +def unit_name_unescape(text): + esc = text.replace("-", "/") + return re.sub("\\\\x(..)", lambda m: "%c" % chr(int(m.group(1), 16)), esc) + +def is_good_root(root): + if not root: + return True + return root.strip(os.path.sep).count(os.path.sep) > 1 +def os_path(root, path): + if not root: + return path + if not path: + return path + if is_good_root(root) and path.startswith(root): + return path + while path.startswith(os.path.sep): + path = path[1:] + return os.path.join(root, path) +def path_replace_extension(path, old, new): + if path.endswith(old): + path = path[:-len(old)] + return path + new + +def get_PAGER(): + PAGER = os.environ.get("PAGER", "less") + pager = os.environ.get("SYSTEMD_PAGER", "{PAGER}").format(**locals()) + options = os.environ.get("SYSTEMD_LESS", "FRSXMK") # see 'man timedatectl' + if not pager: pager = "cat" + if "less" in pager and options: + return [ pager, "-" + options ] + return [ pager ] + +def os_getlogin(): + """ NOT using os.getlogin() """ + return pwd.getpwuid(os.geteuid()).pw_name + +def get_runtime_dir(): + explicit = os.environ.get("XDG_RUNTIME_DIR", "") + if explicit: return explicit + user = os_getlogin() + return "/tmp/run-"+user +def get_RUN(root = False): + tmp_var = get_TMP(root) + if _root: + tmp_var = _root + if root: + for p in ("/run", "/var/run", "{tmp_var}/run"): + path = p.format(**locals()) + if os.path.isdir(path) and os.access(path, os.W_OK): + return path + os.makedirs(path) # "/tmp/run" + return path + else: + uid = get_USER_ID(root) + for p in ("/run/user/{uid}", "/var/run/user/{uid}", "{tmp_var}/run-{uid}"): + path = p.format(**locals()) + if os.path.isdir(path) and os.access(path, os.W_OK): + return path + os.makedirs(path, 0o700) # "/tmp/run/user/{uid}" + return path +def get_PID_DIR(root = False): + if root: + return get_RUN(root) + else: + return os.path.join(get_RUN(root), "run") # compat with older systemctl.py + +def get_home(): + if False: # pragma: no cover + explicit = os.environ.get("HOME", "") # >> On Unix, an initial ~ (tilde) is replaced by the + if explicit: return explicit # environment variable HOME if it is set; otherwise + uid = os.geteuid() # the current users home directory is looked up in the + # # password directory through the built-in module pwd. + return pwd.getpwuid(uid).pw_name # An initial ~user i looked up directly in the + return os.path.expanduser("~") # password directory. << from docs(os.path.expanduser) +def get_HOME(root = False): + if root: return "/root" + return get_home() +def get_USER_ID(root = False): + ID = 0 + if root: return ID + return os.geteuid() +def get_USER(root = False): + if root: return "root" + uid = os.geteuid() + return pwd.getpwuid(uid).pw_name +def get_GROUP_ID(root = False): + ID = 0 + if root: return ID + return os.getegid() +def get_GROUP(root = False): + if root: return "root" + gid = os.getegid() + return grp.getgrgid(gid).gr_name +def get_TMP(root = False): + TMP = "/tmp" + if root: return TMP + return os.environ.get("TMPDIR", os.environ.get("TEMP", os.environ.get("TMP", TMP))) +def get_VARTMP(root = False): + VARTMP = "/var/tmp" + if root: return VARTMP + return os.environ.get("TMPDIR", os.environ.get("TEMP", os.environ.get("TMP", VARTMP))) +def get_SHELL(root = False): + SHELL = "/bin/sh" + if root: return SHELL + return os.environ.get("SHELL", SHELL) +def get_RUNTIME_DIR(root = False): + RUN = "/run" + if root: return RUN + return os.environ.get("XDG_RUNTIME_DIR", get_runtime_dir()) +def get_CONFIG_HOME(root = False): + CONFIG = "/etc" + if root: return CONFIG + HOME = get_HOME(root) + return os.environ.get("XDG_CONFIG_HOME", HOME + "/.config") +def get_CACHE_HOME(root = False): + CACHE = "/var/cache" + if root: return CACHE + HOME = get_HOME(root) + return os.environ.get("XDG_CACHE_HOME", HOME + "/.cache") +def get_DATA_HOME(root = False): + SHARE = "/usr/share" + if root: return SHARE + HOME = get_HOME(root) + return os.environ.get("XDG_DATA_HOME", HOME + "/.local/share") +def get_LOG_DIR(root = False): + LOGDIR = "/var/log" + if root: return LOGDIR + CONFIG = get_CONFIG_HOME(root) + return os.path.join(CONFIG, "log") +def get_VARLIB_HOME(root = False): + VARLIB = "/var/lib" + if root: return VARLIB + CONFIG = get_CONFIG_HOME(root) + return CONFIG +def expand_path(path, root = False): + HOME = get_HOME(root) + RUN = get_RUN(root) + LOG = get_LOG_DIR(root) + XDG_DATA_HOME=get_DATA_HOME(root) + XDG_CONFIG_HOME=get_CONFIG_HOME(root) + XDG_RUNTIME_DIR=get_RUNTIME_DIR(root) + return os.path.expanduser(path.replace("${","{").format(**locals())) + +def shutil_chown(path, user, group): + if user or group: + uid, gid = -1, -1 + if user: + uid = pwd.getpwnam(user).pw_uid + gid = pwd.getpwnam(user).pw_gid + if group: + gid = grp.getgrnam(group).gr_gid + os.chown(path, uid, gid) +def shutil_fchown(fileno, user, group): + if user or group: + uid, gid = -1, -1 + if user: + uid = pwd.getpwnam(user).pw_uid + gid = pwd.getpwnam(user).pw_gid + if group: + gid = grp.getgrnam(group).gr_gid + os.fchown(fileno, uid, gid) +def shutil_setuid(user = None, group = None, xgroups = None): + """ set fork-child uid/gid (returns pw-info env-settings)""" + if group: + gid = grp.getgrnam(group).gr_gid + os.setgid(gid) + logg.debug("setgid %s for %s", gid, strQ(group)) + groups = [ gid ] + try: + os.setgroups(groups) + logg.debug("setgroups %s < (%s)", groups, group) + except OSError as e: # pragma: no cover (it will occur in non-root mode anyway) + logg.debug("setgroups %s < (%s) : %s", groups, group, e) + if user: + pw = pwd.getpwnam(user) + gid = pw.pw_gid + gname = grp.getgrgid(gid).gr_name + if not group: + os.setgid(gid) + logg.debug("setgid %s for user %s", gid, strQ(user)) + groupnames = [g.gr_name for g in grp.getgrall() if user in g.gr_mem] + groups = [g.gr_gid for g in grp.getgrall() if user in g.gr_mem] + if xgroups: + groups += [g.gr_gid for g in grp.getgrall() if g.gr_name in xgroups and g.gr_gid not in groups] + if not groups: + if group: + gid = grp.getgrnam(group).gr_gid + groups = [ gid ] + try: + os.setgroups(groups) + logg.debug("setgroups %s > %s ", groups, groupnames) + except OSError as e: # pragma: no cover (it will occur in non-root mode anyway) + logg.debug("setgroups %s > %s : %s", groups, groupnames, e) + uid = pw.pw_uid + os.setuid(uid) + logg.debug("setuid %s for user %s", uid, strQ(user)) + home = pw.pw_dir + shell = pw.pw_shell + logname = pw.pw_name + return { "USER": user, "LOGNAME": logname, "HOME": home, "SHELL": shell } + return {} + +def shutil_truncate(filename): + """ truncates the file (or creates a new empty file)""" + filedir = os.path.dirname(filename) + if not os.path.isdir(filedir): + os.makedirs(filedir) + f = open(filename, "w") + f.write("") + f.close() + +# http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid +def pid_exists(pid): + """Check whether pid exists in the current process table.""" + if pid is None: # pragma: no cover (is never null) + return False + return _pid_exists(int(pid)) +def _pid_exists(pid): + """Check whether pid exists in the current process table. + UNIX only. + """ + if pid < 0: + return False + if pid == 0: + # According to "man 2 kill" PID 0 refers to every process + # in the process group of the calling process. + # On certain systems 0 is a valid PID but we have no way + # to know that in a portable fashion. + raise ValueError('invalid PID 0') + try: + os.kill(pid, 0) + except OSError as err: + if err.errno == errno.ESRCH: + # ESRCH == No such process + return False + elif err.errno == errno.EPERM: + # EPERM clearly means there's a process to deny access to + return True + else: + # According to "man 2 kill" possible error values are + # (EINVAL, EPERM, ESRCH) + raise + else: + return True +def pid_zombie(pid): + """ may be a pid exists but it is only a zombie """ + if pid is None: + return False + return _pid_zombie(int(pid)) +def _pid_zombie(pid): + """ may be a pid exists but it is only a zombie """ + if pid < 0: + return False + if pid == 0: + # According to "man 2 kill" PID 0 refers to every process + # in the process group of the calling process. + # On certain systems 0 is a valid PID but we have no way + # to know that in a portable fashion. + raise ValueError('invalid PID 0') + check = _proc_pid_status.format(**locals()) + try: + for line in open(check): + if line.startswith("State:"): + return "Z" in line + except IOError as e: + if e.errno != errno.ENOENT: + logg.error("%s (%s): %s", check, e.errno, e) + return False + return False + +def checkstatus(cmd): + if cmd.startswith("-"): + return False, cmd[1:] + else: + return True, cmd + +# https://github.com/phusion/baseimage-docker/blob/rel-0.9.16/image/bin/my_init +def ignore_signals_and_raise_keyboard_interrupt(signame): + signal.signal(signal.SIGTERM, signal.SIG_IGN) + signal.signal(signal.SIGINT, signal.SIG_IGN) + raise KeyboardInterrupt(signame) + +_default_dict_type = collections.OrderedDict +_default_conf_type = collections.OrderedDict + +class SystemctlConfData: + """ A *.service files has a structure similar to an *.ini file so + that data is structured in sections and values. Actually the + values are lists - the raw data is in .getlist(). Otherwise + .get() will return the first line that was encountered. """ + def __init__(self, defaults=None, dict_type=None, conf_type=None, allow_no_value=False): + self._defaults = defaults or {} + self._conf_type = conf_type or _default_conf_type + self._dict_type = dict_type or _default_dict_type + self._allow_no_value = allow_no_value + self._conf = self._conf_type() + self._files = [] + def defaults(self): + return self._defaults + def sections(self): + return list(self._conf.keys()) + def add_section(self, section): + if section not in self._conf: + self._conf[section] = self._dict_type() + def has_section(self, section): + return section in self._conf + def has_option(self, section, option): + if section not in self._conf: + return False + return option in self._conf[section] + def set(self, section, option, value): + if section not in self._conf: + self._conf[section] = self._dict_type() + if value is None: + self._conf[section][option] = [] + elif option not in self._conf[section]: + self._conf[section][option] = [ value ] + else: + self._conf[section][option].append(value) + def getstr(self, section, option, default = None, allow_no_value = False): + done = self.get(section, option, strE(default), allow_no_value) + if done is None: return strE(default) + return done + def get(self, section, option, default = None, allow_no_value = False): + allow_no_value = allow_no_value or self._allow_no_value + if section not in self._conf: + if default is not None: + return default + if allow_no_value: + return None + logg.warning("section {} does not exist".format(section)) + logg.warning(" have {}".format(self.sections())) + raise AttributeError("section {} does not exist".format(section)) + if option not in self._conf[section]: + if default is not None: + return default + if allow_no_value: + return None + raise AttributeError("option {} in {} does not exist".format(option, section)) + if not self._conf[section][option]: # i.e. an empty list + if default is not None: + return default + if allow_no_value: + return None + raise AttributeError("option {} in {} is None".format(option, section)) + return self._conf[section][option][0] # the first line in the list of configs + def getlist(self, section, option, default = None, allow_no_value = False): + allow_no_value = allow_no_value or self._allow_no_value + if section not in self._conf: + if default is not None: + return default + if allow_no_value: + return [] + logg.warning("section {} does not exist".format(section)) + logg.warning(" have {}".format(self.sections())) + raise AttributeError("section {} does not exist".format(section)) + if option not in self._conf[section]: + if default is not None: + return default + if allow_no_value: + return [] + raise AttributeError("option {} in {} does not exist".format(option, section)) + return self._conf[section][option] # returns a list, possibly empty + def filenames(self): + return self._files + +class SystemctlConfigParser(SystemctlConfData): + """ A *.service files has a structure similar to an *.ini file but it is + actually not like it. Settings may occur multiple times in each section + and they create an implicit list. In reality all the settings are + globally uniqute, so that an 'environment' can be printed without + adding prefixes. Settings are continued with a backslash at the end + of the line. """ + # def __init__(self, defaults=None, dict_type=None, allow_no_value=False): + # SystemctlConfData.__init__(self, defaults, dict_type, allow_no_value) + def read(self, filename): + return self.read_sysd(filename) + def read_sysd(self, filename): + initscript = False + initinfo = False + section = "GLOBAL" + nextline = False + name, text = "", "" + if os.path.isfile(filename): + self._files.append(filename) + for orig_line in open(filename): + if nextline: + text += orig_line + if text.rstrip().endswith("\\") or text.rstrip().endswith("\\\n"): + text = text.rstrip() + "\n" + else: + self.set(section, name, text) + nextline = False + continue + line = orig_line.strip() + if not line: + continue + if line.startswith("#"): + continue + if line.startswith(";"): + continue + if line.startswith(".include"): + logg.error("the '.include' syntax is deprecated. Use x.service.d/ drop-in files!") + includefile = re.sub(r'^\.include[ ]*', '', line).rstrip() + if not os.path.isfile(includefile): + raise Exception("tried to include file that doesn't exist: %s" % includefile) + self.read_sysd(includefile) + continue + if line.startswith("["): + x = line.find("]") + if x > 0: + section = line[1:x] + self.add_section(section) + continue + m = re.match(r"(\w+) *=(.*)", line) + if not m: + logg.warning("bad ini line: %s", line) + raise Exception("bad ini line") + name, text = m.group(1), m.group(2).strip() + if text.endswith("\\") or text.endswith("\\\n"): + nextline = True + text = text + "\n" + else: + # hint: an empty line shall reset the value-list + self.set(section, name, text and text or None) + return self + def read_sysv(self, filename): + """ an LSB header is scanned and converted to (almost) + equivalent settings of a SystemD ini-style input """ + initscript = False + initinfo = False + section = "GLOBAL" + if os.path.isfile(filename): + self._files.append(filename) + for orig_line in open(filename): + line = orig_line.strip() + if line.startswith("#"): + if " BEGIN INIT INFO" in line: + initinfo = True + section = "init.d" + if " END INIT INFO" in line: + initinfo = False + if initinfo: + m = re.match(r"\S+\s*(\w[\w_-]*):(.*)", line) + if m: + key, val = m.group(1), m.group(2).strip() + self.set(section, key, val) + continue + self.systemd_sysv_generator(filename) + return self + def systemd_sysv_generator(self, filename): + """ see systemd-sysv-generator(8) """ + self.set("Unit", "SourcePath", filename) + description = self.get("init.d", "Description", "") + if description: + self.set("Unit", "Description", description) + check = self.get("init.d", "Required-Start","") + if check: + for item in check.split(" "): + if item.strip() in _sysv_mappings: + self.set("Unit", "Requires", _sysv_mappings[item.strip()]) + provides = self.get("init.d", "Provides", "") + if provides: + self.set("Install", "Alias", provides) + # if already in multi-user.target then start it there. + runlevels = self.getstr("init.d", "Default-Start","3 5") + for item in runlevels.split(" "): + if item.strip() in _runlevel_mappings: + self.set("Install", "WantedBy", _runlevel_mappings[item.strip()]) + self.set("Service", "Restart", "no") + self.set("Service", "TimeoutSec", strE(DefaultMaximumTimeout)) + self.set("Service", "KillMode", "process") + self.set("Service", "GuessMainPID", "no") + # self.set("Service", "RemainAfterExit", "yes") + # self.set("Service", "SuccessExitStatus", "5 6") + self.set("Service", "ExecStart", filename + " start") + self.set("Service", "ExecStop", filename + " stop") + if description: # LSB style initscript + self.set("Service", "ExecReload", filename + " reload") + self.set("Service", "Type", "forking") # not "sysv" anymore + +# UnitConfParser = ConfigParser.RawConfigParser +UnitConfParser = SystemctlConfigParser + +class SystemctlSocket: + def __init__(self, conf, sock, skip = False): + self.conf = conf + self.sock = sock + self.skip = skip + def fileno(self): + return self.sock.fileno() + def listen(self, backlog = None): + if backlog is None: + backlog = DefaultListenBacklog + dgram = (self.sock.type == socket.SOCK_DGRAM) + if not dgram and not self.skip: + self.sock.listen(backlog) + def name(self): + return self.conf.name() + def addr(self): + stream = self.conf.get("Socket", "ListenStream", "") + dgram = self.conf.get("Socket", "ListenDatagram", "") + return stream or dgram + def close(self): + self.sock.close() + +class SystemctlConf: + def __init__(self, data, module = None): + self.data = data # UnitConfParser + self.env = {} + self.status = None + self.masked = None + self.module = module + self.nonloaded_path = "" + self.drop_in_files = {} + self._root = _root + self._user_mode = _user_mode + def root_mode(self): + return not self._user_mode + def loaded(self): + files = self.data.filenames() + if self.masked: + return "masked" + if len(files): + return "loaded" + return "" + def filename(self): + """ returns the last filename that was parsed """ + files = self.data.filenames() + if files: + return files[0] + return None + def overrides(self): + """ drop-in files are loaded alphabetically by name, not by full path """ + return [ self.drop_in_files[name] for name in sorted(self.drop_in_files) ] + def name(self): + """ the unit id or defaults to the file name """ + name = self.module or "" + filename = self.filename() + if filename: + name = os.path.basename(filename) + return self.module or name + def set(self, section, name, value): + return self.data.set(section, name, value) + def get(self, section, name, default, allow_no_value = False): + return self.data.getstr(section, name, default, allow_no_value) + def getlist(self, section, name, default = None, allow_no_value = False): + return self.data.getlist(section, name, default or [], allow_no_value) + def getbool(self, section, name, default = None): + value = self.data.get(section, name, default or "no") + if value: + if value[0] in "TtYy123456789": + return True + return False + +class PresetFile: + def __init__(self): + self._files = [] + self._lines = [] + def filename(self): + """ returns the last filename that was parsed """ + if self._files: + return self._files[-1] + return None + def read(self, filename): + self._files.append(filename) + for line in open(filename): + self._lines.append(line.strip()) + return self + def get_preset(self, unit): + for line in self._lines: + m = re.match(r"(enable|disable)\s+(\S+)", line) + if m: + status, pattern = m.group(1), m.group(2) + if fnmatch.fnmatchcase(unit, pattern): + logg.debug("%s %s => %s %s", status, pattern, unit, strQ(self.filename())) + return status + return None + +## with waitlock(conf): self.start() +class waitlock: + def __init__(self, conf): + self.conf = conf # currently unused + self.opened = -1 + self.lockfolder = expand_path(_notify_socket_folder, conf.root_mode()) + try: + folder = self.lockfolder + if not os.path.isdir(folder): + os.makedirs(folder) + except Exception as e: + logg.warning("oops, %s", e) + def lockfile(self): + unit = "" + if self.conf: + unit = self.conf.name() + return os.path.join(self.lockfolder, str(unit or "global") + ".lock") + def __enter__(self): + try: + lockfile = self.lockfile() + lockname = os.path.basename(lockfile) + self.opened = os.open(lockfile, os.O_RDWR | os.O_CREAT, 0o600) + for attempt in xrange(int(MaxLockWait or DefaultMaximumTimeout)): + try: + logg_debug_flock("[%s] %s. trying %s _______ ", os.getpid(), attempt, lockname) + fcntl.flock(self.opened, fcntl.LOCK_EX | fcntl.LOCK_NB) + st = os.fstat(self.opened) + if not st.st_nlink: + logg_debug_flock("[%s] %s. %s got deleted, trying again", os.getpid(), attempt, lockname) + os.close(self.opened) + self.opened = os.open(lockfile, os.O_RDWR | os.O_CREAT, 0o600) + continue + content = "{ 'systemctl': %s, 'lock': '%s' }\n" % (os.getpid(), lockname) + os.write(self.opened, content.encode("utf-8")) + logg_debug_flock("[%s] %s. holding lock on %s", os.getpid(), attempt, lockname) + return True + except IOError as e: + whom = os.read(self.opened, 4096) + os.lseek(self.opened, 0, os.SEEK_SET) + logg.info("[%s] %s. systemctl locked by %s", os.getpid(), attempt, whom.rstrip()) + time.sleep(1) # until MaxLockWait + continue + logg.error("[%s] not able to get the lock to %s", os.getpid(), lockname) + except Exception as e: + logg.warning("[%s] oops %s, %s", os.getpid(), str(type(e)), e) + #TODO# raise Exception("no lock for %s", self.unit or "global") + return False + def __exit__(self, type, value, traceback): + try: + os.lseek(self.opened, 0, os.SEEK_SET) + os.ftruncate(self.opened, 0) + if REMOVE_LOCK_FILE: # an optional implementation + lockfile = self.lockfile() + lockname = os.path.basename(lockfile) + os.unlink(lockfile) # ino is kept allocated because opened by this process + logg.debug("[%s] lockfile removed for %s", os.getpid(), lockname) + fcntl.flock(self.opened, fcntl.LOCK_UN) + os.close(self.opened) # implies an unlock but that has happend like 6 seconds later + self.opened = -1 + except Exception as e: + logg.warning("oops, %s", e) + +waitpid_result = collections.namedtuple("waitpid", ["pid", "returncode", "signal" ]) + +def must_have_failed(waitpid, cmd): + # found to be needed on ubuntu:16.04 to match test result from ubuntu:18.04 and other distros + # .... I have tracked it down that python's os.waitpid() returns an exitcode==0 even when the + # .... underlying process has actually failed with an exitcode<>0. It is unknown where that + # .... bug comes from but it seems a bit serious to trash some very basic unix functionality. + # .... Essentially a parent process does not get the correct exitcode from its own children. + if cmd and cmd[0] == "/bin/kill": + pid = None + for arg in cmd[1:]: + if not arg.startswith("-"): + pid = arg + if pid is None: # unknown $MAINPID + if not waitpid.returncode: + logg.error("waitpid %s did return %s => correcting as 11", cmd, waitpid.returncode) + waitpid = waitpid_result(waitpid.pid, 11, waitpid.signal) + return waitpid + +def subprocess_waitpid(pid): + run_pid, run_stat = os.waitpid(pid, 0) + return waitpid_result(run_pid, os.WEXITSTATUS(run_stat), os.WTERMSIG(run_stat)) +def subprocess_testpid(pid): + run_pid, run_stat = os.waitpid(pid, os.WNOHANG) + if run_pid: + return waitpid_result(run_pid, os.WEXITSTATUS(run_stat), os.WTERMSIG(run_stat)) + else: + return waitpid_result(pid, None, 0) + +parse_result = collections.namedtuple("UnitName", ["fullname", "name", "prefix", "instance", "suffix", "component" ]) + +def parse_unit(fullname): # -> object(prefix, instance, suffix, ...., name, component) + name, suffix = fullname, "" + has_suffix = fullname.rfind(".") + if has_suffix > 0: + name = fullname[:has_suffix] + suffix = fullname[has_suffix+1:] + prefix, instance = name, "" + has_instance = name.find("@") + if has_instance > 0: + prefix = name[:has_instance] + instance = name[has_instance+1:] + component = "" + has_component = prefix.rfind("-") + if has_component > 0: + component = prefix[has_component+1:] + return parse_result(fullname, name, prefix, instance, suffix, component) + +def time_to_seconds(text, maximum): + value = 0. + for part in str(text).split(" "): + item = part.strip() + if item == "infinity": + return maximum + if item.endswith("m"): + try: value += 60 * int(item[:-1]) + except: pass # pragma: no cover + if item.endswith("min"): + try: value += 60 * int(item[:-3]) + except: pass # pragma: no cover + elif item.endswith("ms"): + try: value += int(item[:-2]) / 1000. + except: pass # pragma: no cover + elif item.endswith("s"): + try: value += int(item[:-1]) + except: pass # pragma: no cover + elif item: + try: value += int(item) + except: pass # pragma: no cover + if value > maximum: + return maximum + if not value and text.strip() == "0": + return 0. + if not value: + return 1. + return value +def seconds_to_time(seconds): + seconds = float(seconds) + mins = int(int(seconds) / 60) + secs = int(int(seconds) - (mins * 60)) + msecs = int(int(seconds * 1000) - (secs * 1000 + mins * 60000)) + if mins and secs and msecs: + return "%smin %ss %sms" % (mins, secs, msecs) + elif mins and secs: + return "%smin %ss" % (mins, secs) + elif secs and msecs: + return "%ss %sms" % (secs, msecs) + elif mins and msecs: + return "%smin %sms" % (mins, msecs) + elif mins: + return "%smin" % (mins) + else: + return "%ss" % (secs) + +def getBefore(conf): + result = [] + beforelist = conf.getlist("Unit", "Before", []) + for befores in beforelist: + for before in befores.split(" "): + name = before.strip() + if name and name not in result: + result.append(name) + return result + +def getAfter(conf): + result = [] + afterlist = conf.getlist("Unit", "After", []) + for afters in afterlist: + for after in afters.split(" "): + name = after.strip() + if name and name not in result: + result.append(name) + return result + +def compareAfter(confA, confB): + idA = confA.name() + idB = confB.name() + for after in getAfter(confA): + if after == idB: + logg.debug("%s After %s", idA, idB) + return -1 + for after in getAfter(confB): + if after == idA: + logg.debug("%s After %s", idB, idA) + return 1 + for before in getBefore(confA): + if before == idB: + logg.debug("%s Before %s", idA, idB) + return 1 + for before in getBefore(confB): + if before == idA: + logg.debug("%s Before %s", idB, idA) + return -1 + return 0 + +def conf_sortedAfter(conflist, cmp = compareAfter): + # the normal sorted() does only look at two items + # so if "A after C" and a list [A, B, C] then + # it will see "A = B" and "B = C" assuming that + # "A = C" and the list is already sorted. + # + # To make a totalsorted we have to create a marker + # that informs sorted() that also B has a relation. + # It only works when 'after' has a direction, so + # anything without 'before' is a 'after'. In that + # case we find that "B after C". + class SortTuple: + def __init__(self, rank, conf): + self.rank = rank + self.conf = conf + sortlist = [ SortTuple(0, conf) for conf in conflist] + for check in xrange(len(sortlist)): # maxrank = len(sortlist) + changed = 0 + for A in xrange(len(sortlist)): + for B in xrange(len(sortlist)): + if A != B: + itemA = sortlist[A] + itemB = sortlist[B] + before = compareAfter(itemA.conf, itemB.conf) + if before > 0 and itemA.rank <= itemB.rank: + logg_debug_after(" %-30s before %s", itemA.conf.name(), itemB.conf.name()) + itemA.rank = itemB.rank + 1 + changed += 1 + if before < 0 and itemB.rank <= itemA.rank: + logg_debug_after(" %-30s before %s", itemB.conf.name(), itemA.conf.name()) + itemB.rank = itemA.rank + 1 + changed += 1 + if not changed: + logg_debug_after("done in check %s of %s", check, len(sortlist)) + break + # because Requires is almost always the same as the After clauses + # we are mostly done in round 1 as the list is in required order + for conf in conflist: + logg_debug_after(".. %s", conf.name()) + for item in sortlist: + logg_debug_after("(%s) %s", item.rank, item.conf.name()) + sortedlist = sorted(sortlist, key = lambda item: -item.rank) + for item in sortedlist: + logg_debug_after("[%s] %s", item.rank, item.conf.name()) + return [ item.conf for item in sortedlist ] + +class SystemctlListenThread(threading.Thread): + def __init__(self, systemctl): + threading.Thread.__init__(self, name="listen") + self.systemctl = systemctl + self.stopped = threading.Event() + def stop(self): + self.stopped.set() + def run(self): + READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR + READ_WRITE = READ_ONLY | select.POLLOUT + me = os.getpid() + if DEBUG_INITLOOP: # pragma: no cover + logg.info("[%s] listen: new thread", me) + if not self.systemctl._sockets: + return + if DEBUG_INITLOOP: # pragma: no cover + logg.info("[%s] listen: start thread", me) + listen = select.poll() + for sock in self.systemctl._sockets.values(): + listen.register(sock, READ_ONLY) + sock.listen() + logg.debug("[%s] listen: %s :%s", me, sock.name(), sock.addr()) + timestamp = time.time() + while not self.stopped.is_set(): + try: + sleep_sec = InitLoopSleep - (time.time() - timestamp) + if sleep_sec < MinimumYield: + sleep_sec = MinimumYield + sleeping = sleep_sec + while sleeping > 2: + time.sleep(1) # accept signals atleast every second + sleeping = InitLoopSleep - (time.time() - timestamp) + if sleeping < MinimumYield: + sleeping = MinimumYield + break + time.sleep(sleeping) # remainder waits less that 2 seconds + if DEBUG_INITLOOP: # pragma: no cover + logg.debug("[%s] listen: poll", me) + accepting = listen.poll(100) # milliseconds + if DEBUG_INITLOOP: # pragma: no cover + logg.debug("[%s] listen: poll (%s)", me, len(accepting)) + for sock_fileno, event in accepting: + for sock in self.systemctl._sockets.values(): + if sock.fileno() == sock_fileno: + if not self.stopped.is_set(): + if self.systemctl.loop.acquire(): + logg.debug("[%s] listen: accept %s :%s", me, sock.name(), sock_fileno) + self.systemctl.do_accept_socket_from(sock.conf, sock.sock) + except Exception as e: + logg.info("[%s] listen: interrupted - exception %s", me, e) + raise + for sock in self.systemctl._sockets.values(): + try: + listen.unregister(sock) + sock.close() + except Exception as e: + logg.warning("[%s] listen: close socket: %s", me, e) + return + +class Systemctl: + def __init__(self): + self.error = NOT_A_PROBLEM # program exitcode or process returncode + # from command line options or the defaults + self._extra_vars = _extra_vars + self._force = _force + self._full = _full + self._init = _init + self._no_ask_password = _no_ask_password + self._no_legend = _no_legend + self._now = _now + self._preset_mode = _preset_mode + self._quiet = _quiet + self._root = _root + self._show_all = _show_all + self._unit_property = _unit_property + self._unit_state = _unit_state + self._unit_type = _unit_type + # some common constants that may be changed + self._systemd_version = SystemCompatibilityVersion + self._journal_log_folder = _journal_log_folder + # and the actual internal runtime state + self._loaded_file_sysv = {} # /etc/init.d/name => config data + self._loaded_file_sysd = {} # /etc/systemd/system/name.service => config data + self._file_for_unit_sysv = None # name.service => /etc/init.d/name + self._file_for_unit_sysd = None # name.service => /etc/systemd/system/name.service + self._preset_file_list = None # /etc/systemd/system-preset/* => file content + self._default_target = DefaultTarget + self._sysinit_target = None # stores a UnitConf() + self.doExitWhenNoMoreProcs = ExitWhenNoMoreProcs or False + self.doExitWhenNoMoreServices = ExitWhenNoMoreServices or False + self._user_mode = _user_mode + self._user_getlogin = os_getlogin() + self._log_file = {} # init-loop + self._log_hold = {} # init-loop + self._boottime = None # cache self.get_boottime() + self._SYSTEMD_UNIT_PATH = None + self._SYSTEMD_SYSVINIT_PATH = None + self._SYSTEMD_PRESET_PATH = None + self._restarted_unit = {} + self._restart_failed_units = {} + self._sockets = {} + self.loop = threading.Lock() + def user(self): + return self._user_getlogin + def user_mode(self): + return self._user_mode + def user_folder(self): + for folder in self.user_folders(): + if folder: return folder + raise Exception("did not find any systemd/user folder") + def system_folder(self): + for folder in self.system_folders(): + if folder: return folder + raise Exception("did not find any systemd/system folder") + def preset_folders(self): + SYSTEMD_PRESET_PATH = self.get_SYSTEMD_PRESET_PATH() + for path in SYSTEMD_PRESET_PATH.split(":"): + if path.strip(): yield expand_path(path.strip()) + if SYSTEMD_PRESET_PATH.endswith(":"): + if _preset_folder1: yield _preset_folder1 + if _preset_folder2: yield _preset_folder2 + if _preset_folder3: yield _preset_folder3 + if _preset_folder4: yield _preset_folder4 + if _preset_folder5: yield _preset_folder5 + if _preset_folder6: yield _preset_folder6 + if _preset_folderX: yield _preset_folderX + def init_folders(self): + SYSTEMD_SYSVINIT_PATH = self.get_SYSTEMD_SYSVINIT_PATH() + for path in SYSTEMD_SYSVINIT_PATH.split(":"): + if path.strip(): yield expand_path(path.strip()) + if SYSTEMD_SYSVINIT_PATH.endswith(":"): + if _init_folder1: yield _init_folder1 + if _init_folder2: yield _init_folder2 + if _init_folder3: yield _init_folder3 + if _init_folderX: yield _init_folderX + def user_folders(self): + SYSTEMD_UNIT_PATH = self.get_SYSTEMD_UNIT_PATH() + for path in SYSTEMD_UNIT_PATH.split(":"): + if path.strip(): yield expand_path(path.strip()) + if SYSTEMD_UNIT_PATH.endswith(":"): + if _user_folder1: yield expand_path(_user_folder1) + if _user_folder2: yield expand_path(_user_folder2) + if _user_folder3: yield expand_path(_user_folder3) + if _user_folder4: yield expand_path(_user_folder4) + if _user_folder5: yield expand_path(_user_folder5) + if _user_folder6: yield expand_path(_user_folder6) + if _user_folder7: yield expand_path(_user_folder7) + if _user_folder8: yield expand_path(_user_folder8) + if _user_folder9: yield expand_path(_user_folder9) + if _user_folderX: yield expand_path(_user_folderX) + def system_folders(self): + SYSTEMD_UNIT_PATH = self.get_SYSTEMD_UNIT_PATH() + for path in SYSTEMD_UNIT_PATH.split(":"): + if path.strip(): yield expand_path(path.strip()) + if SYSTEMD_UNIT_PATH.endswith(":"): + if _system_folder1: yield _system_folder1 + if _system_folder2: yield _system_folder2 + if _system_folder3: yield _system_folder3 + if _system_folder4: yield _system_folder4 + if _system_folder5: yield _system_folder5 + if _system_folder6: yield _system_folder6 + if _system_folderX: yield _system_folderX + def get_SYSTEMD_UNIT_PATH(self): + if self._SYSTEMD_UNIT_PATH is None: + self._SYSTEMD_UNIT_PATH = os.environ.get("SYSTEMD_UNIT_PATH", ":") + assert self._SYSTEMD_UNIT_PATH is not None + return self._SYSTEMD_UNIT_PATH + def get_SYSTEMD_SYSVINIT_PATH(self): + if self._SYSTEMD_SYSVINIT_PATH is None: + self._SYSTEMD_SYSVINIT_PATH = os.environ.get("SYSTEMD_SYSVINIT_PATH", ":") + assert self._SYSTEMD_SYSVINIT_PATH is not None + return self._SYSTEMD_SYSVINIT_PATH + def get_SYSTEMD_PRESET_PATH(self): + if self._SYSTEMD_PRESET_PATH is None: + self._SYSTEMD_PRESET_PATH = os.environ.get("SYSTEMD_PRESET_PATH", ":") + assert self._SYSTEMD_PRESET_PATH is not None + return self._SYSTEMD_PRESET_PATH + def sysd_folders(self): + """ if --user then these folders are preferred """ + if self.user_mode(): + for folder in self.user_folders(): + yield folder + if True: + for folder in self.system_folders(): + yield folder + def scan_unit_sysd_files(self, module = None): # -> [ unit-names,... ] + """ reads all unit files, returns the first filename for the unit given """ + if self._file_for_unit_sysd is None: + self._file_for_unit_sysd = {} + for folder in self.sysd_folders(): + if not folder: + continue + folder = os_path(self._root, folder) + if not os.path.isdir(folder): + continue + for name in os.listdir(folder): + path = os.path.join(folder, name) + if os.path.isdir(path): + continue + service_name = name + if service_name not in self._file_for_unit_sysd: + self._file_for_unit_sysd[service_name] = path + logg.debug("found %s sysd files", len(self._file_for_unit_sysd)) + return list(self._file_for_unit_sysd.keys()) + def scan_unit_sysv_files(self, module = None): # -> [ unit-names,... ] + """ reads all init.d files, returns the first filename when unit is a '.service' """ + if self._file_for_unit_sysv is None: + self._file_for_unit_sysv = {} + for folder in self.init_folders(): + if not folder: + continue + folder = os_path(self._root, folder) + if not os.path.isdir(folder): + continue + for name in os.listdir(folder): + path = os.path.join(folder, name) + if os.path.isdir(path): + continue + service_name = name + ".service" # simulate systemd + if service_name not in self._file_for_unit_sysv: + self._file_for_unit_sysv[service_name] = path + logg.debug("found %s sysv files", len(self._file_for_unit_sysv)) + return list(self._file_for_unit_sysv.keys()) + def unit_sysd_file(self, module = None): # -> filename? + """ file path for the given module (systemd) """ + self.scan_unit_sysd_files() + assert self._file_for_unit_sysd is not None + if module and module in self._file_for_unit_sysd: + return self._file_for_unit_sysd[module] + if module and unit_of(module) in self._file_for_unit_sysd: + return self._file_for_unit_sysd[unit_of(module)] + return None + def unit_sysv_file(self, module = None): # -> filename? + """ file path for the given module (sysv) """ + self.scan_unit_sysv_files() + assert self._file_for_unit_sysv is not None + if module and module in self._file_for_unit_sysv: + return self._file_for_unit_sysv[module] + if module and unit_of(module) in self._file_for_unit_sysv: + return self._file_for_unit_sysv[unit_of(module)] + return None + def unit_file(self, module = None): # -> filename? + """ file path for the given module (sysv or systemd) """ + path = self.unit_sysd_file(module) + if path is not None: return path + path = self.unit_sysv_file(module) + if path is not None: return path + return None + def is_sysv_file(self, filename): + """ for routines that have a special treatment for init.d services """ + self.unit_file() # scan all + assert self._file_for_unit_sysd is not None + assert self._file_for_unit_sysv is not None + if not filename: return None + if filename in self._file_for_unit_sysd.values(): return False + if filename in self._file_for_unit_sysv.values(): return True + return None # not True + def is_user_conf(self, conf): + if not conf: # pragma: no cover (is never null) + return False + filename = conf.nonloaded_path or conf.filename() + if filename and "/user/" in filename: + return True + return False + def not_user_conf(self, conf): + """ conf can not be started as user service (when --user)""" + if conf is None: # pragma: no cover (is never null) + return True + if not self.user_mode(): + logg.debug("%s no --user mode >> accept", strQ(conf.filename())) + return False + if self.is_user_conf(conf): + logg.debug("%s is /user/ conf >> accept", strQ(conf.filename())) + return False + # to allow for 'docker run -u user' with system services + user = self.get_User(conf) + if user and user == self.user(): + logg.debug("%s with User=%s >> accept", strQ(conf.filename()), user) + return False + return True + def find_drop_in_files(self, unit): + """ search for some.service.d/extra.conf files """ + result = {} + basename_d = unit + ".d" + for folder in self.sysd_folders(): + if not folder: + continue + folder = os_path(self._root, folder) + override_d = os_path(folder, basename_d) + if not os.path.isdir(override_d): + continue + for name in os.listdir(override_d): + path = os.path.join(override_d, name) + if os.path.isdir(path): + continue + if not path.endswith(".conf"): + continue + if name not in result: + result[name] = path + return result + def load_sysd_template_conf(self, module): # -> conf? + """ read the unit template with a UnitConfParser (systemd) """ + if module and "@" in module: + unit = parse_unit(module) + service = "%s@.service" % unit.prefix + conf = self.load_sysd_unit_conf(service) + if conf: + conf.module = module + return conf + return None + def load_sysd_unit_conf(self, module): # -> conf? + """ read the unit file with a UnitConfParser (systemd) """ + path = self.unit_sysd_file(module) + if not path: return None + assert self._loaded_file_sysd is not None + if path in self._loaded_file_sysd: + return self._loaded_file_sysd[path] + masked = None + if os.path.islink(path) and os.readlink(path).startswith("/dev"): + masked = os.readlink(path) + drop_in_files = {} + data = UnitConfParser() + if not masked: + data.read_sysd(path) + drop_in_files = self.find_drop_in_files(os.path.basename(path)) + # load in alphabetic order, irrespective of location + for name in sorted(drop_in_files): + path = drop_in_files[name] + data.read_sysd(path) + conf = SystemctlConf(data, module) + conf.masked = masked + conf.nonloaded_path = path # if masked + conf.drop_in_files = drop_in_files + conf._root = self._root + self._loaded_file_sysd[path] = conf + return conf + def load_sysv_unit_conf(self, module): # -> conf? + """ read the unit file with a UnitConfParser (sysv) """ + path = self.unit_sysv_file(module) + if not path: return None + assert self._loaded_file_sysv is not None + if path in self._loaded_file_sysv: + return self._loaded_file_sysv[path] + data = UnitConfParser() + data.read_sysv(path) + conf = SystemctlConf(data, module) + conf._root = self._root + self._loaded_file_sysv[path] = conf + return conf + def load_unit_conf(self, module): # -> conf | None(not-found) + """ read the unit file with a UnitConfParser (sysv or systemd) """ + try: + conf = self.load_sysd_unit_conf(module) + if conf is not None: + return conf + conf = self.load_sysd_template_conf(module) + if conf is not None: + return conf + conf = self.load_sysv_unit_conf(module) + if conf is not None: + return conf + except Exception as e: + logg.warning("%s not loaded: %s", module, e) + return None + def default_unit_conf(self, module, description = None): # -> conf + """ a unit conf that can be printed to the user where + attributes are empty and loaded() is False """ + data = UnitConfParser() + data.set("Unit", "Description", description or ("NOT-FOUND " + str(module))) + # assert(not data.loaded()) + conf = SystemctlConf(data, module) + conf._root = self._root + return conf + def get_unit_conf(self, module): # -> conf (conf | default-conf) + """ accept that a unit does not exist + and return a unit conf that says 'not-loaded' """ + conf = self.load_unit_conf(module) + if conf is not None: + return conf + return self.default_unit_conf(module) + def get_unit_type(self, module): + name, ext = os.path.splitext(module) + if ext in [".service", ".socket", ".target"]: + return ext[1:] + return None + def get_unit_section(self, module, default = "Service"): + return string.capwords(self.get_unit_type(module) or default) + def get_unit_section_from(self, conf, default = "Service"): + return self.get_unit_section(conf.name(), default) + def match_sysd_templates(self, modules = None, suffix=".service"): # -> generate[ unit ] + """ make a file glob on all known template units (systemd areas). + It returns no modules (!!) if no modules pattern were given. + The module string should contain an instance name already. """ + modules = to_list(modules) + if not modules: + return + self.scan_unit_sysd_files() + assert self._file_for_unit_sysd is not None + for item in sorted(self._file_for_unit_sysd.keys()): + if "@" not in item: + continue + service_unit = parse_unit(item) + for module in modules: + if "@" not in module: + continue + module_unit = parse_unit(module) + if service_unit.prefix == module_unit.prefix: + yield "%s@%s.%s" % (service_unit.prefix, module_unit.instance, service_unit.suffix) + def match_sysd_units(self, modules = None, suffix=".service"): # -> generate[ unit ] + """ make a file glob on all known units (systemd areas). + It returns all modules if no modules pattern were given. + Also a single string as one module pattern may be given. """ + modules = to_list(modules) + self.scan_unit_sysd_files() + assert self._file_for_unit_sysd is not None + for item in sorted(self._file_for_unit_sysd.keys()): + if not modules: + yield item + elif [ module for module in modules if fnmatch.fnmatchcase(item, module) ]: + yield item + elif [ module for module in modules if module+suffix == item ]: + yield item + def match_sysv_units(self, modules = None, suffix=".service"): # -> generate[ unit ] + """ make a file glob on all known units (sysv areas). + It returns all modules if no modules pattern were given. + Also a single string as one module pattern may be given. """ + modules = to_list(modules) + self.scan_unit_sysv_files() + assert self._file_for_unit_sysv is not None + for item in sorted(self._file_for_unit_sysv.keys()): + if not modules: + yield item + elif [ module for module in modules if fnmatch.fnmatchcase(item, module) ]: + yield item + elif [ module for module in modules if module+suffix == item ]: + yield item + def match_units(self, modules = None, suffix=".service"): # -> [ units,.. ] + """ Helper for about any command with multiple units which can + actually be glob patterns on their respective unit name. + It returns all modules if no modules pattern were given. + Also a single string as one module pattern may be given. """ + found = [] + for unit in self.match_sysd_units(modules, suffix): + if unit not in found: + found.append(unit) + for unit in self.match_sysd_templates(modules, suffix): + if unit not in found: + found.append(unit) + for unit in self.match_sysv_units(modules, suffix): + if unit not in found: + found.append(unit) + return found + def list_service_unit_basics(self): + """ show all the basic loading state of services """ + filename = self.unit_file() # scan all + assert self._file_for_unit_sysd is not None + assert self._file_for_unit_sysv is not None + result = [] + for name, value in self._file_for_unit_sysd.items(): + result += [ (name, "SysD", value) ] + for name, value in self._file_for_unit_sysv.items(): + result += [ (name, "SysV", value) ] + return result + def list_service_units(self, *modules): # -> [ (unit,loaded+active+substate,description) ] + """ show all the service units """ + result = {} + active = {} + substate = {} + description = {} + for unit in self.match_units(to_list(modules)): + result[unit] = "not-found" + active[unit] = "inactive" + substate[unit] = "dead" + description[unit] = "" + try: + conf = self.get_unit_conf(unit) + result[unit] = "loaded" + description[unit] = self.get_description_from(conf) + active[unit] = self.get_active_from(conf) + substate[unit] = self.get_substate_from(conf) or "unknown" + except Exception as e: + logg.warning("list-units: %s", e) + if self._unit_state: + if self._unit_state not in [ result[unit], active[unit], substate[unit] ]: + del result[unit] + return [ (unit, result[unit] + " " + active[unit] + " " + substate[unit], description[unit]) for unit in sorted(result) ] + def show_list_units(self, *modules): # -> [ (unit,loaded,description) ] + """ [PATTERN]... -- List loaded units. + If one or more PATTERNs are specified, only units matching one of + them are shown. NOTE: This is the default command.""" + hint = "To show all installed unit files use 'systemctl list-unit-files'." + result = self.list_service_units(*modules) + if self._no_legend: + return result + found = "%s loaded units listed." % len(result) + return result + [ ("", "", ""), (found, "", ""), (hint, "", "") ] + def list_service_unit_files(self, *modules): # -> [ (unit,enabled) ] + """ show all the service units and the enabled status""" + logg.debug("list service unit files for %s", modules) + result = {} + enabled = {} + for unit in self.match_units(to_list(modules)): + if _unit_type and self.get_unit_type(unit) not in _unit_type.split(","): + continue + result[unit] = None + enabled[unit] = "" + try: + conf = self.get_unit_conf(unit) + if self.not_user_conf(conf): + result[unit] = None + continue + result[unit] = conf + enabled[unit] = self.enabled_from(conf) + except Exception as e: + logg.warning("list-units: %s", e) + return [ (unit, enabled[unit]) for unit in sorted(result) if result[unit] ] + def each_target_file(self): + folders = self.system_folders() + if self.user_mode(): + folders = self.user_folders() + for folder1 in folders: + folder = os_path(self._root, folder1) + if not os.path.isdir(folder): + continue + for filename in os.listdir(folder): + if filename.endswith(".target"): + yield (filename, os.path.join(folder, filename)) + def list_target_unit_files(self, *modules): # -> [ (unit,enabled) ] + """ show all the target units and the enabled status""" + enabled = {} + targets = {} + for target, filepath in self.each_target_file(): + logg.info("target %s", filepath) + targets[target] = filepath + enabled[target] = "static" + for unit in _all_common_targets: + targets[unit] = None + enabled[unit] = "static" + if unit in _all_common_enabled: + enabled[unit] = "enabled" + if unit in _all_common_disabled: + enabled[unit] = "disabled" + return [ (unit, enabled[unit]) for unit in sorted(targets) ] + def show_list_unit_files(self, *modules): # -> [ (unit,enabled) ] + """[PATTERN]... -- List installed unit files + List installed unit files and their enablement state (as reported + by is-enabled). If one or more PATTERNs are specified, only units + whose filename (just the last component of the path) matches one of + them are shown. This command reacts to limitations of --type being + --type=service or --type=target (and --now for some basics).""" + result = [] + if self._now: + basics = self.list_service_unit_basics() + result = [ (name, sysv + " " + filename) for name, sysv, filename in basics ] + elif self._unit_type == "target": + result = self.list_target_unit_files() + elif self._unit_type == "service": + result = self.list_service_unit_files() + elif self._unit_type: + logg.warning("unsupported unit --type=%s", self._unit_type) + else: + result = self.list_target_unit_files() + result += self.list_service_unit_files(*modules) + if self._no_legend: + return result + found = "%s unit files listed." % len(result) + return [ ("UNIT FILE", "STATE") ] + result + [ ("", ""), (found, "") ] + ## + ## + def get_description(self, unit, default = None): + return self.get_description_from(self.load_unit_conf(unit)) + def get_description_from(self, conf, default = None): # -> text + """ Unit.Description could be empty sometimes """ + if not conf: return default or "" + description = conf.get("Unit", "Description", default or "") + return self.expand_special(description, conf) + def read_pid_file(self, pid_file, default = None): + pid = default + if not pid_file: + return default + if not os.path.isfile(pid_file): + return default + if self.truncate_old(pid_file): + return default + try: + # some pid-files from applications contain multiple lines + for line in open(pid_file): + if line.strip(): + pid = to_intN(line.strip()) + break + except Exception as e: + logg.warning("bad read of pid file '%s': %s", pid_file, e) + return pid + def wait_pid_file(self, pid_file, timeout = None): # -> pid? + """ wait some seconds for the pid file to appear and return the pid """ + timeout = int(timeout or (DefaultTimeoutStartSec/2)) + timeout = max(timeout, (MinimumTimeoutStartSec)) + dirpath = os.path.dirname(os.path.abspath(pid_file)) + for x in xrange(timeout): + if not os.path.isdir(dirpath): + time.sleep(1) # until TimeoutStartSec/2 + continue + pid = self.read_pid_file(pid_file) + if not pid: + time.sleep(1) # until TimeoutStartSec/2 + continue + if not pid_exists(pid): + time.sleep(1) # until TimeoutStartSec/2 + continue + return pid + return None + def test_pid_file(self, unit): # -> text + """ support for the testsuite.py """ + conf = self.get_unit_conf(unit) + return self.pid_file_from(conf) or self.get_status_file_from(conf) + def pid_file_from(self, conf, default = ""): + """ get the specified pid file path (not a computed default) """ + pid_file = self.get_pid_file(conf) or default + return os_path(self._root, self.expand_special(pid_file, conf)) + def get_pid_file(self, conf, default = None): + return conf.get("Service", "PIDFile", default) + def read_mainpid_from(self, conf, default = None): + """ MAINPID is either the PIDFile content written from the application + or it is the value in the status file written by this systemctl.py code """ + pid_file = self.pid_file_from(conf) + if pid_file: + return self.read_pid_file(pid_file, default) + status = self.read_status_from(conf) + if "MainPID" in status: + return to_intN(status["MainPID"], default) + return default + def clean_pid_file_from(self, conf): + pid_file = self.pid_file_from(conf) + if pid_file and os.path.isfile(pid_file): + try: + os.remove(pid_file) + except OSError as e: + logg.warning("while rm %s: %s", pid_file, e) + self.write_status_from(conf, MainPID=None) + def get_status_file(self, unit): # for testing + conf = self.get_unit_conf(unit) + return self.get_status_file_from(conf) + def get_status_file_from(self, conf, default = None): + status_file = self.get_StatusFile(conf) + # this not a real setting, but do the expand_special anyway + return os_path(self._root, self.expand_special(status_file, conf)) + def get_StatusFile(self, conf, default = None): # -> text + """ file where to store a status mark """ + status_file = conf.get("Service", "StatusFile", default) + if status_file: + return status_file + root = conf.root_mode() + folder = get_PID_DIR(root) + name = "%s.status" % conf.name() + return os.path.join(folder, name) + def clean_status_from(self, conf): + status_file = self.get_status_file_from(conf) + if os.path.exists(status_file): + os.remove(status_file) + conf.status = {} + def write_status_from(self, conf, **status): # -> bool(written) + """ if a status_file is known then path is created and the + give status is written as the only content. """ + status_file = self.get_status_file_from(conf) + # if not status_file: return False + dirpath = os.path.dirname(os.path.abspath(status_file)) + if not os.path.isdir(dirpath): + os.makedirs(dirpath) + if conf.status is None: + conf.status = self.read_status_from(conf) + if True: + for key in sorted(status.keys()): + value = status[key] + if key.upper() == "AS": key = "ActiveState" + if key.upper() == "EXIT": key = "ExecMainCode" + if value is None: + try: del conf.status[key] + except KeyError: pass + else: + conf.status[key] = strE(value) + try: + with open(status_file, "w") as f: + for key in sorted(conf.status): + value = conf.status[key] + if key == "MainPID" and str(value) == "0": + logg.warning("ignore writing MainPID=0") + continue + content = "{}={}\n".format(key, str(value)) + logg.debug("writing to %s\n\t%s", status_file, content.strip()) + f.write(content) + except IOError as e: + logg.error("writing STATUS %s: %s\n\t to status file %s", status, e, status_file) + return True + def read_status_from(self, conf): + status_file = self.get_status_file_from(conf) + status = {} + # if not status_file: return status + if not os.path.isfile(status_file): + if DEBUG_STATUS: logg.debug("no status file: %s\n returning %s", status_file, status) + return status + if self.truncate_old(status_file): + if DEBUG_STATUS: logg.debug("old status file: %s\n returning %s", status_file, status) + return status + try: + if DEBUG_STATUS: logg.debug("reading %s", status_file) + for line in open(status_file): + if line.strip(): + m = re.match(r"(\w+)[:=](.*)", line) + if m: + key, value = m.group(1), m.group(2) + if key.strip(): + status[key.strip()] = value.strip() + else: #pragma: no cover + logg.warning("ignored %s", line.strip()) + except: + logg.warning("bad read of status file '%s'", status_file) + return status + def get_status_from(self, conf, name, default = None): + if conf.status is None: + conf.status = self.read_status_from(conf) + return conf.status.get(name, default) + def set_status_from(self, conf, name, value): + if conf.status is None: + conf.status = self.read_status_from(conf) + if value is None: + try: del conf.status[name] + except KeyError: pass + else: + conf.status[name] = value + # + def get_boottime(self): + """ detects the boot time of the container - in general the start time of PID 1 """ + if self._boottime is None: + self._boottime = self.get_boottime_from_proc() + assert self._boottime is not None + return self._boottime + def get_boottime_from_proc(self): + """ detects the latest boot time by looking at the start time of available process""" + pid1 = BOOT_PID_MIN or 0 + pid_max = BOOT_PID_MAX + if pid_max < 0: + pid_max = pid1 - pid_max + for pid in xrange(pid1, pid_max): + proc = _proc_pid_stat.format(**locals()) + try: + if os.path.exists(proc): + # return os.path.getmtime(proc) # did sometimes change + return self.path_proc_started(proc) + except Exception as e: # pragma: no cover + logg.warning("boottime - could not access %s: %s", proc, e) + if DEBUG_BOOTTIME: + logg.debug(" boottime from the oldest entry in /proc [nothing in %s..%s]", pid1, pid_max) + return self.get_boottime_from_old_proc() + def get_boottime_from_old_proc(self): + booted = time.time() + for pid in os.listdir(_proc_pid_dir): + proc = _proc_pid_stat.format(**locals()) + try: + if os.path.exists(proc): + # ctime = os.path.getmtime(proc) + ctime = self.path_proc_started(proc) + if ctime < booted: + booted = ctime + except Exception as e: # pragma: no cover + logg.warning("could not access %s: %s", proc, e) + return booted + + # Use uptime, time process running in ticks, and current time to determine process boot time + # You can't use the modified timestamp of the status file because it isn't static. + # ... using clock ticks it is known to be a linear time on Linux + def path_proc_started(self, proc): + #get time process started after boot in clock ticks + with open(proc) as file_stat: + data_stat = file_stat.readline() + file_stat.close() + stat_data = data_stat.split() + started_ticks = stat_data[21] + # man proc(5): "(22) starttime = The time the process started after system boot." + # ".. the value is expressed in clock ticks (divide by sysconf(_SC_CLK_TCK))." + # NOTE: for containers the start time is related to the boot time of host system. + + clkTickInt = os.sysconf_names['SC_CLK_TCK'] + clockTicksPerSec = os.sysconf(clkTickInt) + started_secs = float(started_ticks) / clockTicksPerSec + if DEBUG_BOOTTIME: + logg.debug(" BOOT .. Proc started time: %.3f (%s)", started_secs, proc) + # this value is the start time from the host system + + # Variant 1: + system_uptime = _proc_sys_uptime + with open(system_uptime,"rb") as file_uptime: + data_uptime = file_uptime.readline() + file_uptime.close() + uptime_data = data_uptime.decode().split() + uptime_secs = float(uptime_data[0]) + if DEBUG_BOOTTIME: + logg.debug(" BOOT 1. System uptime secs: %.3f (%s)", uptime_secs, system_uptime) + + #get time now + now = time.time() + started_time = now - (uptime_secs - started_secs) + if DEBUG_BOOTTIME: + logg.debug(" BOOT 1. Proc has been running since: %s" % (datetime.datetime.fromtimestamp(started_time))) + + # Variant 2: + system_stat = _proc_sys_stat + system_btime = 0. + with open(system_stat,"rb") as f: + for line in f: + assert isinstance(line, bytes) + if line.startswith(b"btime"): + system_btime = float(line.decode().split()[1]) + f.closed + if DEBUG_BOOTTIME: + logg.debug(" BOOT 2. System btime secs: %.3f (%s)", system_btime, system_stat) + + started_btime = system_btime + started_secs + if DEBUG_BOOTTIME: + logg.debug(" BOOT 2. Proc has been running since: %s" % (datetime.datetime.fromtimestamp(started_btime))) + + # return started_time + return started_btime + + def get_filetime(self, filename): + return os.path.getmtime(filename) + def truncate_old(self, filename): + filetime = self.get_filetime(filename) + boottime = self.get_boottime() + if filetime >= boottime: + if DEBUG_BOOTTIME: + logg.debug(" file time: %s (%s)", datetime.datetime.fromtimestamp(filetime), o22(filename)) + logg.debug(" boot time: %s (%s)", datetime.datetime.fromtimestamp(boottime), "status modified later") + return False # OK + if DEBUG_BOOTTIME: + logg.info(" file time: %s (%s)", datetime.datetime.fromtimestamp(filetime), o22(filename)) + logg.info(" boot time: %s (%s)", datetime.datetime.fromtimestamp(boottime), "status TRUNCATED NOW") + try: + shutil_truncate(filename) + except Exception as e: + logg.warning("while truncating: %s", e) + return True # truncated + def getsize(self, filename): + if filename is None: # pragma: no cover (is never null) + return 0 + if not os.path.isfile(filename): + return 0 + if self.truncate_old(filename): + return 0 + try: + return os.path.getsize(filename) + except Exception as e: + logg.warning("while reading file size: %s\n of %s", e, filename) + return 0 + # + def read_env_file(self, env_file): # -> generate[ (name,value) ] + """ EnvironmentFile= is being scanned """ + if env_file.startswith("-"): + env_file = env_file[1:] + if not os.path.isfile(os_path(self._root, env_file)): + return + try: + for real_line in open(os_path(self._root, env_file)): + line = real_line.strip() + if not line or line.startswith("#"): + continue + m = re.match(r"(?:export +)?([\w_]+)[=]'([^']*)'", line) + if m: + yield m.group(1), m.group(2) + continue + m = re.match(r'(?:export +)?([\w_]+)[=]"([^"]*)"', line) + if m: + yield m.group(1), m.group(2) + continue + m = re.match(r'(?:export +)?([\w_]+)[=](.*)', line) + if m: + yield m.group(1), m.group(2) + continue + except Exception as e: + logg.info("while reading %s: %s", env_file, e) + def read_env_part(self, env_part): # -> generate[ (name, value) ] + """ Environment== is being scanned """ + ## systemd Environment= spec says it is a space-seperated list of + ## assignments. In order to use a space or an equals sign in a value + ## one should enclose the whole assignment with double quotes: + ## Environment="VAR1=word word" VAR2=word3 "VAR3=$word 5 6" + ## and the $word is not expanded by other environment variables. + try: + for real_line in env_part.split("\n"): + line = real_line.strip() + for found in re.finditer(r'\s*("[\w_]+=[^"]*"|[\w_]+=\S*)', line): + part = found.group(1) + if part.startswith('"'): + part = part[1:-1] + name, value = part.split("=", 1) + yield name, value + except Exception as e: + logg.info("while reading %s: %s", env_part, e) + def show_environment(self, unit): + """ [UNIT]. -- show environment parts """ + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s could not be found.", unit) + return False + if _unit_property: + return conf.getlist("Service", _unit_property) + return self.get_env(conf) + def extra_vars(self): + return self._extra_vars # from command line + def get_env(self, conf): + env = os.environ.copy() + for env_part in conf.getlist("Service", "Environment", []): + for name, value in self.read_env_part(self.expand_special(env_part, conf)): + env[name] = value # a '$word' is not special here (lazy expansion) + for env_file in conf.getlist("Service", "EnvironmentFile", []): + for name, value in self.read_env_file(self.expand_special(env_file, conf)): + env[name] = self.expand_env(value, env) # but nonlazy expansion here + logg.debug("extra-vars %s", self.extra_vars()) + for extra in self.extra_vars(): + if extra.startswith("@"): + for name, value in self.read_env_file(extra[1:]): + logg.info("override %s=%s", name, value) + env[name] = self.expand_env(value, env) + else: + for name, value in self.read_env_part(extra): + logg.info("override %s=%s", name, value) + env[name] = value # a '$word' is not special here + return env + def expand_env(self, cmd, env): + def get_env1(m): + name = m.group(1) + if name in env: + return env[name] + namevar = "$%s" % name + logg.debug("can not expand %s", namevar) + return (EXPAND_KEEP_VARS and namevar or "") + def get_env2(m): + name = m.group(1) + if name in env: + return env[name] + namevar = "${%s}" % name + logg.debug("can not expand %s", namevar) + return (EXPAND_KEEP_VARS and namevar or "") + # + maxdepth = EXPAND_VARS_MAXDEPTH + expanded = re.sub("[$](\w+)", lambda m: get_env1(m), cmd.replace("\\\n","")) + for depth in xrange(maxdepth): + new_text = re.sub("[$][{](\w+)[}]", lambda m: get_env2(m), expanded) + if new_text == expanded: + return expanded + expanded = new_text + logg.error("shell variable expansion exceeded maxdepth %s", maxdepth) + return expanded + def expand_special(self, cmd, conf): + """ expand %i %t and similar special vars. They are being expanded + before any other expand_env takes place which handles shell-style + $HOME references. """ + def xx(arg): return unit_name_unescape(arg) + def yy(arg): return arg + def get_confs(conf): + confs={ "%": "%" } + if conf is None: # pragma: no cover (is never null) + return confs + unit = parse_unit(conf.name()) + # + root = conf.root_mode() + VARTMP = get_VARTMP(root) # $TMPDIR # "/var/tmp" + TMP = get_TMP(root) # $TMPDIR # "/tmp" + RUN = get_RUNTIME_DIR(root) # $XDG_RUNTIME_DIR # "/run" + ETC = get_CONFIG_HOME(root) # $XDG_CONFIG_HOME # "/etc" + DAT = get_VARLIB_HOME(root) # $XDG_CONFIG_HOME # "/var/lib" + LOG = get_LOG_DIR(root) # $XDG_CONFIG_HOME/log # "/var/log" + CACHE = get_CACHE_HOME(root) # $XDG_CACHE_HOME # "/var/cache" + HOME = get_HOME(root) # $HOME or ~ # "/root" + USER = get_USER(root) # geteuid().pw_name # "root" + USER_ID = get_USER_ID(root) # geteuid() # 0 + GROUP = get_GROUP(root) # getegid().gr_name # "root" + GROUP_ID = get_GROUP_ID(root) # getegid() # 0 + SHELL = get_SHELL(root) # $SHELL # "/bin/sh" + # confs["b"] = boot_ID + confs["C"] = os_path(self._root, CACHE) # Cache directory root + confs["E"] = os_path(self._root, ETC) # Configuration directory root + confs["F"] = strE(conf.filename()) # EXTRA + confs["f"] = "/%s" % xx(unit.instance or unit.prefix) + confs["h"] = HOME # User home directory + # confs["H"] = host_NAME + confs["i"] = yy(unit.instance) + confs["I"] = xx(unit.instance) # same as %i but escaping undone + confs["j"] = yy(unit.component) # final component of the prefix + confs["J"] = xx(unit.component) # unescaped final component + confs["L"] = os_path(self._root, LOG) + # confs["m"] = machine_ID + confs["n"] = yy(unit.fullname) # Full unit name + confs["N"] = yy(unit.name) # Same as "%n", but with the type suffix removed. + confs["p"] = yy(unit.prefix) # before the first "@" or same as %n + confs["P"] = xx(unit.prefix) # same as %p but escaping undone + confs["s"] = SHELL + confs["S"] = os_path(self._root, DAT) + confs["t"] = os_path(self._root, RUN) + confs["T"] = os_path(self._root, TMP) + confs["g"] = GROUP + confs["G"] = str(GROUP_ID) + confs["u"] = USER + confs["U"] = str(USER_ID) + confs["V"] = os_path(self._root, VARTMP) + return confs + def get_conf1(m): + confs = get_confs(conf) + if m.group(1) in confs: + return confs[m.group(1)] + logg.warning("can not expand %%%s", m.group(1)) + return "" + result = "" + if cmd: + result = re.sub("[%](.)", lambda m: get_conf1(m), cmd) + #++# logg.info("expanded => %s", result) + return result + ExecMode = collections.namedtuple("ExecMode", ["check"]) + def exec_newcmd(self, cmd, env, conf): + check, cmd = checkstatus(cmd) + mode = Systemctl.ExecMode(check) + newcmd = self.exec_cmd(cmd, env, conf) + return mode, newcmd + def exec_cmd(self, cmd, env, conf): + """ expand ExecCmd statements including %i and $MAINPID """ + cmd2 = cmd.replace("\\\n","") + # according to documentation, when bar="one two" then the expansion + # of '$bar' is ["one","two"] and '${bar}' becomes ["one two"]. We + # tackle that by expand $bar before shlex, and the rest thereafter. + def get_env1(m): + if m.group(1) in env: + return env[m.group(1)] + logg.debug("can not expand $%s", m.group(1)) + return "" # empty string + def get_env2(m): + if m.group(1) in env: + return env[m.group(1)] + logg.debug("can not expand ${%s}", m.group(1)) + return "" # empty string + cmd3 = re.sub("[$](\w+)", lambda m: get_env1(m), cmd2) + newcmd = [] + for part in shlex.split(cmd3): + # newcmd += [ re.sub("[$][{](\w+)[}]", lambda m: get_env2(m), part) ] + newcmd += [ re.sub("[$][{](\w+)[}]", lambda m: get_env2(m), self.expand_special(part, conf)) ] + return newcmd + def remove_service_directories(self, conf, section = "Service"): + ok = True + nameRuntimeDirectory = self.get_RuntimeDirectory(conf, section) + keepRuntimeDirectory = self.get_RuntimeDirectoryPreserve(conf, section) + if not keepRuntimeDirectory: + root = conf.root_mode() + for name in nameRuntimeDirectory.split(" "): + if not name.strip(): continue + RUN = get_RUNTIME_DIR(root) + path = os.path.join(RUN, name) + dirpath = os_path(self._root, path) + ok = self.do_rm_tree(dirpath) and ok + if RUN == "/run": + for var_run in ("/var/run", "/tmp/run"): + if os.path.isdir(var_run): + var_path = os.path.join(var_run, name) + var_dirpath = os_path(self._root, var_path) + self.do_rm_tree(var_dirpath) + if not ok: + logg.debug("could not fully remove service directory %s", path) + return ok + def do_rm_tree(self, path): + ok = True + if os.path.isdir(path): + for dirpath, dirnames, filenames in os.walk(path, topdown=False): + for item in filenames: + filepath = os.path.join(dirpath, item) + try: + os.remove(filepath) + except Exception as e: # pragma: no cover + logg.debug("not removed file: %s (%s)", filepath, e) + ok = False + for item in dirnames: + dir_path = os.path.join(dirpath, item) + try: + os.rmdir(dir_path) + except Exception as e: # pragma: no cover + logg.debug("not removed dir: %s (%s)", dir_path, e) + ok = False + try: + os.rmdir(path) + except Exception as e: + logg.debug("not removed top dir: %s (%s)", path, e) + ok = False # pragma: no cover + logg.debug("%s rm_tree %s", ok and "done" or "fail", path) + return ok + def get_RuntimeDirectoryPreserve(self, conf, section = "Service"): + return conf.getbool(section, "RuntimeDirectoryPreserve", "no") + def get_RuntimeDirectory(self, conf, section = "Service"): + return self.expand_special(conf.get(section, "RuntimeDirectory", ""), conf) + def get_StateDirectory(self, conf, section = "Service"): + return self.expand_special(conf.get(section, "StateDirectory", ""), conf) + def get_CacheDirectory(self, conf, section = "Service"): + return self.expand_special(conf.get(section, "CacheDirectory", ""), conf) + def get_LogsDirectory(self, conf, section = "Service"): + return self.expand_special(conf.get(section, "LogsDirectory", ""), conf) + def get_ConfigurationDirectory(self, conf, section = "Service"): + return self.expand_special(conf.get(section, "ConfigurationDirectory", ""), conf) + def get_RuntimeDirectoryMode(self, conf, section = "Service"): + return conf.get(section, "RuntimeDirectoryMode", "") + def get_StateDirectoryMode(self, conf, section = "Service"): + return conf.get(section, "StateDirectoryMode", "") + def get_CacheDirectoryMode(self, conf, section = "Service"): + return conf.get(section, "CacheDirectoryMode", "") + def get_LogsDirectoryMode(self, conf, section = "Service"): + return conf.get(section, "LogsDirectoryMode", "") + def get_ConfigurationDirectoryMode(self, conf, section = "Service"): + return conf.get(section, "ConfigurationDirectoryMode", "") + def clean_service_directories(self, conf, which = ""): + ok = True + section = self.get_unit_section_from(conf) + nameRuntimeDirectory = self.get_RuntimeDirectory(conf, section) + nameStateDirectory = self.get_StateDirectory(conf, section) + nameCacheDirectory = self.get_CacheDirectory(conf, section) + nameLogsDirectory = self.get_LogsDirectory(conf, section) + nameConfigurationDirectory = self.get_ConfigurationDirectory(conf, section) + root = conf.root_mode() + for name in nameRuntimeDirectory.split(" "): + if not name.strip(): continue + RUN = get_RUNTIME_DIR(root) + path = os.path.join(RUN, name) + if which in ["all", "runtime", ""]: + dirpath = os_path(self._root, path) + ok = self.do_rm_tree(dirpath) and ok + if RUN == "/run": + for var_run in ("/var/run", "/tmp/run"): + var_path = os.path.join(var_run, name) + var_dirpath = os_path(self._root, var_path) + self.do_rm_tree(var_dirpath) + for name in nameStateDirectory.split(" "): + if not name.strip(): continue + DAT = get_VARLIB_HOME(root) + path = os.path.join(DAT, name) + if which in ["all", "state"]: + dirpath = os_path(self._root, path) + ok = self.do_rm_tree(dirpath) and ok + for name in nameCacheDirectory.split(" "): + if not name.strip(): continue + CACHE = get_CACHE_HOME(root) + path = os.path.join(CACHE, name) + if which in ["all", "cache", ""]: + dirpath = os_path(self._root, path) + ok = self.do_rm_tree(dirpath) and ok + for name in nameLogsDirectory.split(" "): + if not name.strip(): continue + LOGS = get_LOG_DIR(root) + path = os.path.join(LOGS, name) + if which in ["all", "logs"]: + dirpath = os_path(self._root, path) + ok = self.do_rm_tree(dirpath) and ok + for name in nameConfigurationDirectory.split(" "): + if not name.strip(): continue + CONFIG = get_CONFIG_HOME(root) + path = os.path.join(CONFIG, name) + if which in ["all", "configuration", ""]: + dirpath = os_path(self._root, path) + ok = self.do_rm_tree(dirpath) and ok + return ok + def env_service_directories(self, conf): + envs = {} + section = self.get_unit_section_from(conf) + nameRuntimeDirectory = self.get_RuntimeDirectory(conf, section) + nameStateDirectory = self.get_StateDirectory(conf, section) + nameCacheDirectory = self.get_CacheDirectory(conf, section) + nameLogsDirectory = self.get_LogsDirectory(conf, section) + nameConfigurationDirectory = self.get_ConfigurationDirectory(conf, section) + root = conf.root_mode() + for name in nameRuntimeDirectory.split(" "): + if not name.strip(): continue + RUN = get_RUNTIME_DIR(root) + path = os.path.join(RUN, name) + envs["RUNTIME_DIRECTORY"] = path + for name in nameStateDirectory.split(" "): + if not name.strip(): continue + DAT = get_VARLIB_HOME(root) + path = os.path.join(DAT, name) + envs["STATE_DIRECTORY"] = path + for name in nameCacheDirectory.split(" "): + if not name.strip(): continue + CACHE = get_CACHE_HOME(root) + path = os.path.join(CACHE, name) + envs["CACHE_DIRECTORY"] = path + for name in nameLogsDirectory.split(" "): + if not name.strip(): continue + LOGS = get_LOG_DIR(root) + path = os.path.join(LOGS, name) + envs["LOGS_DIRECTORY"] = path + for name in nameConfigurationDirectory.split(" "): + if not name.strip(): continue + CONFIG = get_CONFIG_HOME(root) + path = os.path.join(CONFIG, name) + envs["CONFIGURATION_DIRECTORY"] = path + return envs + def create_service_directories(self, conf): + envs = {} + section = self.get_unit_section_from(conf) + nameRuntimeDirectory = self.get_RuntimeDirectory(conf, section) + modeRuntimeDirectory = self.get_RuntimeDirectoryMode(conf, section) + nameStateDirectory = self.get_StateDirectory(conf, section) + modeStateDirectory = self.get_StateDirectoryMode(conf, section) + nameCacheDirectory = self.get_CacheDirectory(conf, section) + modeCacheDirectory = self.get_CacheDirectoryMode(conf, section) + nameLogsDirectory = self.get_LogsDirectory(conf, section) + modeLogsDirectory = self.get_LogsDirectoryMode(conf, section) + nameConfigurationDirectory = self.get_ConfigurationDirectory(conf, section) + modeConfigurationDirectory = self.get_ConfigurationDirectoryMode(conf, section) + root = conf.root_mode() + user = self.get_User(conf) + group = self.get_Group(conf) + for name in nameRuntimeDirectory.split(" "): + if not name.strip(): continue + RUN = get_RUNTIME_DIR(root) + path = os.path.join(RUN, name) + logg.debug("RuntimeDirectory %s", path) + self.make_service_directory(path, modeRuntimeDirectory) + self.chown_service_directory(path, user, group) + envs["RUNTIME_DIRECTORY"] = path + if RUN == "/run": + for var_run in ("/var/run", "/tmp/run"): + if os.path.isdir(var_run): + var_path = os.path.join(var_run, name) + var_dirpath = os_path(self._root, var_path) + if os.path.isdir(var_dirpath): + if not os.path.islink(var_dirpath): + logg.debug("not a symlink: %s", var_dirpath) + continue + dirpath = os_path(self._root, path) + basepath = os.path.dirname(var_dirpath) + if not os.path.isdir(basepath): + os.makedirs(basepath) + try: + os.symlink(dirpath, var_dirpath) + except Exception as e: + logg.debug("var symlink %s\n\t%s", var_dirpath, e) + for name in nameStateDirectory.split(" "): + if not name.strip(): continue + DAT = get_VARLIB_HOME(root) + path = os.path.join(DAT, name) + logg.debug("StateDirectory %s", path) + self.make_service_directory(path, modeStateDirectory) + self.chown_service_directory(path, user, group) + envs["STATE_DIRECTORY"] = path + for name in nameCacheDirectory.split(" "): + if not name.strip(): continue + CACHE = get_CACHE_HOME(root) + path = os.path.join(CACHE, name) + logg.debug("CacheDirectory %s", path) + self.make_service_directory(path, modeCacheDirectory) + self.chown_service_directory(path, user, group) + envs["CACHE_DIRECTORY"] = path + for name in nameLogsDirectory.split(" "): + if not name.strip(): continue + LOGS = get_LOG_DIR(root) + path = os.path.join(LOGS, name) + logg.debug("LogsDirectory %s", path) + self.make_service_directory(path, modeLogsDirectory) + self.chown_service_directory(path, user, group) + envs["LOGS_DIRECTORY"] = path + for name in nameConfigurationDirectory.split(" "): + if not name.strip(): continue + CONFIG = get_CONFIG_HOME(root) + path = os.path.join(CONFIG, name) + logg.debug("ConfigurationDirectory %s", path) + self.make_service_directory(path, modeConfigurationDirectory) + # not done according the standard + # self.chown_service_directory(path, user, group) + envs["CONFIGURATION_DIRECTORY"] = path + return envs + def make_service_directory(self, path, mode): + ok = True + dirpath = os_path(self._root, path) + if not os.path.isdir(dirpath): + try: + os.makedirs(dirpath) + logg.info("created directory path: %s", dirpath) + except Exception as e: # pragma: no cover + logg.debug("errors directory path: %s\n\t%s", dirpath, e) + ok = False + filemode = int_mode(mode) + if filemode: + try: + os.chmod(dirpath, filemode) + except Exception as e: # pragma: no cover + logg.debug("errors directory path: %s\n\t%s", dirpath, e) + ok = False + else: + logg.debug("path did already exist: %s", dirpath) + if not ok: + logg.debug("could not fully create service directory %s", path) + return ok + def chown_service_directory(self, path, user, group): + # the standard defines an optimization so that if the parent + # directory does have the correct user and group then there + # is no other chown on files and subdirectories to be done. + dirpath = os_path(self._root, path) + if not os.path.isdir(dirpath): + logg.debug("chown did not find %s", dirpath) + return True + if user or group: + st = os.stat(dirpath) + st_user = pwd.getpwuid(st.st_uid).pw_name + st_group = grp.getgrgid(st.st_gid).gr_name + change = False + if user and (user.strip() != st_user and user.strip() != str(st.st_uid)): + change = True + if group and (group.strip() != st_group and group.strip() != str(st.st_gid)): + change = True + if change: + logg.debug("do chown %s", dirpath) + try: + ok = self.do_chown_tree(dirpath, user, group) + logg.info("changed %s:%s %s", user, group, ok) + return ok + except Exception as e: + logg.info("oops %s\n\t%s", dirpath, e) + else: + logg.debug("untouched %s", dirpath) + return True + def do_chown_tree(self, path, user, group): + ok = True + uid, gid = -1, -1 + if user: + uid = pwd.getpwnam(user).pw_uid + gid = pwd.getpwnam(user).pw_gid + if group: + gid = grp.getgrnam(group).gr_gid + for dirpath, dirnames, filenames in os.walk(path, topdown=False): + for item in filenames: + filepath = os.path.join(dirpath, item) + try: + os.chown(filepath, uid, gid) + except Exception as e: # pragma: no cover + logg.debug("could not set %s:%s on %s\n\t%s", user, group, filepath, e) + ok = False + for item in dirnames: + dir_path = os.path.join(dirpath, item) + try: + os.chown(dir_path, uid, gid) + except Exception as e: # pragma: no cover + logg.debug("could not set %s:%s on %s\n\t%s", user, group, dir_path, e) + ok = False + try: + os.chown(path, uid, gid) + except Exception as e: # pragma: no cover + logg.debug("could not set %s:%s on %s\n\t%s", user, group, path, e) + ok = False + if not ok: + logg.debug("could not chown %s:%s service directory %s", user, group, path) + return ok + def clean_modules(self, *modules): + """ [UNIT]... -- remove the state directories + /// it recognizes --what=all or any of configuration, state, cache, logs, runtime + while an empty value (the default) removes cache and runtime directories""" + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + lines = _log_lines + follow = _force + ok = self.clean_units(units) + return ok and found_all + def clean_units(self, units, what = ""): + if not what: + what = _what_kind + ok = True + for unit in units: + ok = self.clean_unit(unit, what) and ok + return ok + def clean_unit(self, unit, what = ""): + conf = self.load_unit_conf(unit) + if not conf: return False + return self.clean_unit_from(conf, what) + def clean_unit_from(self, conf, what): + if self.is_active_from(conf): + logg.warning("can not clean active unit: %s", conf.name()) + return False + return self.clean_service_directories(conf, what) + def log_modules(self, *modules): + """ [UNIT]... -- start 'less' on the log files for the services + /// use '-f' to follow and '-n lines' to limit output using 'tail', + using '--no-pager' just does a full 'cat'""" + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + lines = _log_lines + follow = _force + result = self.log_units(units, lines, follow) + if result: + self.error = result + return False + return found_all + def log_units(self, units, lines = None, follow = False): + result = 0 + for unit in self.sortedAfter(units): + exitcode = self.log_unit(unit, lines, follow) + if exitcode < 0: + return exitcode + if exitcode > result: + result = exitcode + return result + def log_unit(self, unit, lines = None, follow = False): + conf = self.load_unit_conf(unit) + if not conf: return -1 + return self.log_unit_from(conf, lines, follow) + def log_unit_from(self, conf, lines = None, follow = False): + log_path = self.get_journal_log_from(conf) + if follow: + cmd = [ TAIL_CMD, "-n", str(lines or 10), "-F", log_path ] + logg.debug("journalctl %s -> %s", conf.name(), cmd) + return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore + elif lines: + cmd = [ TAIL_CMD, "-n", str(lines or 10), log_path ] + logg.debug("journalctl %s -> %s", conf.name(), cmd) + return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore + elif _no_pager: + cmd = [ CAT_CMD, log_path ] + logg.debug("journalctl %s -> %s", conf.name(), cmd) + return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore + else: + cmd = [ LESS_CMD, log_path ] + logg.debug("journalctl %s -> %s", conf.name(), cmd) + return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore + def get_journal_log_from(self, conf): + return os_path(self._root, self.get_journal_log(conf)) + def get_journal_log(self, conf): + """ /var/log/zzz.service.log or /var/log/default.unit.log """ + filename = os.path.basename(strE(conf.filename())) + unitname = (conf.name() or "default")+".unit" + name = filename or unitname + log_folder = expand_path(self._journal_log_folder, conf.root_mode()) + log_file = name.replace(os.path.sep,".") + ".log" + if log_file.startswith("."): + log_file = "dot."+log_file + return os.path.join(log_folder, log_file) + def open_journal_log(self, conf): + log_file = self.get_journal_log_from(conf) + log_folder = os.path.dirname(log_file) + if not os.path.isdir(log_folder): + os.makedirs(log_folder) + return open(os.path.join(log_file), "a") + def get_WorkingDirectory(self, conf): + return conf.get("Service", "WorkingDirectory", "") + def chdir_workingdir(self, conf): + """ if specified then change the working directory """ + # the original systemd will start in '/' even if User= is given + if self._root: + os.chdir(self._root) + workingdir = self.get_WorkingDirectory(conf) + if workingdir: + ignore = False + if workingdir.startswith("-"): + workingdir = workingdir[1:] + ignore = True + into = os_path(self._root, self.expand_special(workingdir, conf)) + try: + logg.debug("chdir workingdir '%s'", into) + os.chdir(into) + return False + except Exception as e: + if not ignore: + logg.error("chdir workingdir '%s': %s", into, e) + return into + else: + logg.debug("chdir workingdir '%s': %s", into, e) + return None + return None + NotifySocket = collections.namedtuple("NotifySocket", ["socket", "socketfile" ]) + def get_notify_socket_from(self, conf, socketfile = None, debug = False): + """ creates a notify-socket for the (non-privileged) user """ + notify_socket_folder = expand_path(_notify_socket_folder, conf.root_mode()) + notify_folder = os_path(self._root, notify_socket_folder) + notify_name = "notify." + str(conf.name() or "systemctl") + notify_socket = os.path.join(notify_folder, notify_name) + socketfile = socketfile or notify_socket + if len(socketfile) > 100: + # occurs during testsuite.py for ~user/test.tmp/root path + if debug: + logg.debug("https://unix.stackexchange.com/questions/367008/%s", + "why-is-socket-path-length-limited-to-a-hundred-chars") + logg.debug("old notify socketfile (%s) = %s", len(socketfile), socketfile) + notify_name44 = o44(notify_name) + notify_name77 = o77(notify_name) + socketfile = os.path.join(notify_folder, notify_name77) + if len(socketfile) > 100: + socketfile = os.path.join(notify_folder, notify_name44) + pref = "zz.%i.%s" % (get_USER_ID(),o22(os.path.basename(notify_socket_folder))) + if len(socketfile) > 100: + socketfile = os.path.join(get_TMP(), pref, notify_name) + if len(socketfile) > 100: + socketfile = os.path.join(get_TMP(), pref, notify_name77) + if len(socketfile) > 100: # pragma: no cover + socketfile = os.path.join(get_TMP(), pref, notify_name44) + if len(socketfile) > 100: # pragma: no cover + socketfile = os.path.join(get_TMP(), notify_name44) + if debug: + logg.info("new notify socketfile (%s) = %s", len(socketfile), socketfile) + return socketfile + def notify_socket_from(self, conf, socketfile = None): + socketfile = self.get_notify_socket_from(conf, socketfile, debug=True) + try: + if not os.path.isdir(os.path.dirname(socketfile)): + os.makedirs(os.path.dirname(socketfile)) + if os.path.exists(socketfile): + os.unlink(socketfile) + except Exception as e: + logg.warning("error %s: %s", socketfile, e) + sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + sock.bind(socketfile) + os.chmod(socketfile, 0o777) # the service my run under some User=setting + return Systemctl.NotifySocket(sock, socketfile) + def read_notify_socket(self, notify, timeout): + notify.socket.settimeout(timeout or DefaultMaximumTimeout) + result = "" + try: + result, client_address = notify.socket.recvfrom(4096) + assert isinstance(result, bytes) + if result: + result = result.decode("utf-8") + result_txt = result.replace("\n","|") + result_len = len(result) + logg.debug("read_notify_socket(%s):%s", result_len, result_txt) + except socket.timeout as e: + if timeout > 2: + logg.debug("socket.timeout %s", e) + return result + def wait_notify_socket(self, notify, timeout, pid = None, pid_file = None): + if not os.path.exists(notify.socketfile): + logg.info("no $NOTIFY_SOCKET exists") + return {} + # + lapseTimeout = max(3, int(timeout / 100)) + mainpidTimeout = lapseTimeout # Apache sends READY before MAINPID + status = "" + logg.info("wait $NOTIFY_SOCKET, timeout %s (lapse %s)", timeout, lapseTimeout) + waiting = " ---" + results = {} + for attempt in xrange(int(timeout)+1): + if pid and not self.is_active_pid(pid): + logg.info("seen dead PID %s", pid) + return results + if not attempt: # first one + time.sleep(1) # until TimeoutStartSec + continue + result = self.read_notify_socket(notify, 1) # sleep max 1 second + for line in result.splitlines(): + # for name, value in self.read_env_part(line) + if "=" not in line: + continue + name, value = line.split("=", 1) + results[name] = value + if name in ["STATUS", "ACTIVESTATE", "MAINPID", "READY"]: + hint="seen notify %s " % (waiting) + logg.debug("%s :%s=%s", hint, name, value) + if status != results.get("STATUS",""): + mainpidTimeout = lapseTimeout + status = results.get("STATUS", "") + if "READY" not in results: + time.sleep(1) # until TimeoutStart + continue + if "MAINPID" not in results and not pid_file: + mainpidTimeout -= 1 + if mainpidTimeout > 0: + waiting = "%4i" % (-mainpidTimeout) + time.sleep(1) # until TimeoutStart + continue + break # READY and MAINPID + if "READY" not in results: + logg.info(".... timeout while waiting for 'READY=1' status on $NOTIFY_SOCKET") + elif "MAINPID" not in results: + logg.info(".... seen 'READY=1' but no MAINPID update status on $NOTIFY_SOCKET") + logg.debug("notify = %s", results) + try: + notify.socket.close() + except Exception as e: + logg.debug("socket.close %s", e) + return results + def start_modules(self, *modules): + """ [UNIT]... -- start these units + /// SPECIAL: with --now or --init it will + run the init-loop and stop the units afterwards """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + init = self._now or self._init + return self.start_units(units, init) and found_all + def start_units(self, units, init = None): + """ fails if any unit does not start + /// SPECIAL: may run the init-loop and + stop the named units afterwards """ + self.wait_system() + done = True + started_units = [] + for unit in self.sortedAfter(units): + started_units.append(unit) + if not self.start_unit(unit): + done = False + if init: + logg.info("init-loop start") + sig = self.init_loop_until_stop(started_units) + logg.info("init-loop %s", sig) + for unit in reversed(started_units): + self.stop_unit(unit) + return done + def start_unit(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.debug("unit could not be loaded (%s)", unit) + logg.error("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.start_unit_from(conf) + def get_TimeoutStartSec(self, conf): + timeout = conf.get("Service", "TimeoutSec", strE(DefaultTimeoutStartSec)) + timeout = conf.get("Service", "TimeoutStartSec", timeout) + return time_to_seconds(timeout, DefaultMaximumTimeout) + def get_SocketTimeoutSec(self, conf): + timeout = conf.get("Socket", "TimeoutSec", strE(DefaultTimeoutStartSec)) + return time_to_seconds(timeout, DefaultMaximumTimeout) + def get_RemainAfterExit(self, conf): + return conf.getbool("Service", "RemainAfterExit", "no") + def start_unit_from(self, conf): + if not conf: return False + if self.syntax_check(conf) > 100: return False + with waitlock(conf): + logg.debug(" start unit %s => %s", conf.name(), strQ(conf.filename())) + return self.do_start_unit_from(conf) + def do_start_unit_from(self, conf): + if conf.name().endswith(".service"): + return self.do_start_service_from(conf) + elif conf.name().endswith(".socket"): + return self.do_start_socket_from(conf) + elif conf.name().endswith(".target"): + return self.do_start_target_from(conf) + else: + logg.error("start not implemented for unit type: %s", conf.name()) + return False + def do_start_service_from(self, conf): + timeout = self.get_TimeoutStartSec(conf) + doRemainAfterExit = self.get_RemainAfterExit(conf) + runs = conf.get("Service", "Type", "simple").lower() + env = self.get_env(conf) + if not self._quiet: + okee = self.exec_check_unit(conf, env, "Service", "Exec") # all... + if not okee and _no_reload: return False + service_directories = self.create_service_directories(conf) + env.update(service_directories) # atleast sshd did check for /run/sshd + # for StopPost on failure: + returncode = 0 + service_result = "success" + if True: + if runs in [ "simple", "forking", "notify", "idle" ]: + env["MAINPID"] = strE(self.read_mainpid_from(conf)) + for cmd in conf.getlist("Service", "ExecStartPre", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info(" pre-start %s", shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + logg.debug(" pre-start done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + if run.returncode and exe.check: + logg.error("the ExecStartPre control process exited with error code") + active = "failed" + self.write_status_from(conf, AS=active ) + if _what_kind not in ["none", "keep"]: + self.remove_service_directories(conf) # cleanup that /run/sshd + return False + if runs in [ "oneshot" ]: + status_file = self.get_status_file_from(conf) + if self.get_status_from(conf, "ActiveState", "unknown") == "active": + logg.warning("the service was already up once") + return True + for cmd in conf.getlist("Service", "ExecStart", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("%s start %s", runs, shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: # pragma: no cover + os.setsid() # detach child process from parent + self.execve_from(conf, newcmd, env) + run = subprocess_waitpid(forkpid) + if run.returncode and exe.check: + returncode = run.returncode + service_result = "failed" + logg.error("%s start %s (%s) <-%s>", runs, service_result, + run.returncode or "OK", run.signal or "") + break + logg.info("%s start done (%s) <-%s>", runs, + run.returncode or "OK", run.signal or "") + if True: + self.set_status_from(conf, "ExecMainCode", strE(returncode)) + active = returncode and "failed" or "active" + self.write_status_from(conf, AS=active) + elif runs in [ "simple", "idle" ]: + status_file = self.get_status_file_from(conf) + pid = self.read_mainpid_from(conf) + if self.is_active_pid(pid): + logg.warning("the service is already running on PID %s", pid) + return True + if doRemainAfterExit: + logg.debug("%s RemainAfterExit -> AS=active", runs) + self.write_status_from(conf, AS="active") + cmdlist = conf.getlist("Service", "ExecStart", []) + for idx, cmd in enumerate(cmdlist): + logg.debug("ExecStart[%s]: %s", idx, cmd) + for cmd in cmdlist: + pid = self.read_mainpid_from(conf) + env["MAINPID"] = strE(pid) + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("%s start %s", runs, shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: # pragma: no cover + os.setsid() # detach child process from parent + self.execve_from(conf, newcmd, env) + self.write_status_from(conf, MainPID=forkpid) + logg.info("%s started PID %s", runs, forkpid) + env["MAINPID"] = strE(forkpid) + time.sleep(MinimumYield) + run = subprocess_testpid(forkpid) + if run.returncode is not None: + logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid, + run.returncode or "OK", run.signal or "") + if doRemainAfterExit: + self.set_status_from(conf, "ExecMainCode", strE(run.returncode)) + active = run.returncode and "failed" or "active" + self.write_status_from(conf, AS=active) + if run.returncode and exe.check: + service_result = "failed" + break + elif runs in [ "notify" ]: + # "notify" is the same as "simple" but we create a $NOTIFY_SOCKET + # and wait for startup completion by checking the socket messages + pid_file = self.pid_file_from(conf) + pid = self.read_mainpid_from(conf) + if self.is_active_pid(pid): + logg.error("the service is already running on PID %s", pid) + return False + notify = self.notify_socket_from(conf) + if notify: + env["NOTIFY_SOCKET"] = notify.socketfile + logg.debug("use NOTIFY_SOCKET=%s", notify.socketfile) + if doRemainAfterExit: + logg.debug("%s RemainAfterExit -> AS=active", runs) + self.write_status_from(conf, AS="active") + cmdlist = conf.getlist("Service", "ExecStart", []) + for idx, cmd in enumerate(cmdlist): + logg.debug("ExecStart[%s]: %s", idx, cmd) + mainpid = None + for cmd in cmdlist: + mainpid = self.read_mainpid_from(conf) + env["MAINPID"] = strE(mainpid) + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("%s start %s", runs, shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: # pragma: no cover + os.setsid() # detach child process from parent + self.execve_from(conf, newcmd, env) + # via NOTIFY # self.write_status_from(conf, MainPID=forkpid) + logg.info("%s started PID %s", runs, forkpid) + mainpid = forkpid + self.write_status_from(conf, MainPID=mainpid) + env["MAINPID"] = strE(mainpid) + time.sleep(MinimumYield) + run = subprocess_testpid(forkpid) + if run.returncode is not None: + logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid, + run.returncode or "OK", run.signal or "") + if doRemainAfterExit: + self.set_status_from(conf, "ExecMainCode", strE(run.returncode)) + active = run.returncode and "failed" or "active" + self.write_status_from(conf, AS=active) + if run.returncode and exe.check: + service_result = "failed" + break + if service_result in [ "success" ] and mainpid: + logg.debug("okay, wating on socket for %ss", timeout) + results = self.wait_notify_socket(notify, timeout, mainpid, pid_file) + if "MAINPID" in results: + new_pid = to_intN(results["MAINPID"]) + if new_pid and new_pid != mainpid: + logg.info("NEW PID %s from sd_notify (was PID %s)", new_pid, mainpid) + self.write_status_from(conf, MainPID=new_pid) + mainpid = new_pid + logg.info("%s start done %s", runs, mainpid) + pid = self.read_mainpid_from(conf) + if pid: + env["MAINPID"] = strE(pid) + else: + service_result = "timeout" # "could not start service" + elif runs in [ "forking" ]: + pid_file = self.pid_file_from(conf) + for cmd in conf.getlist("Service", "ExecStart", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + if not newcmd: continue + logg.info("%s start %s", runs, shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: # pragma: no cover + os.setsid() # detach child process from parent + self.execve_from(conf, newcmd, env) + logg.info("%s started PID %s", runs, forkpid) + run = subprocess_waitpid(forkpid) + if run.returncode and exe.check: + returncode = run.returncode + service_result = "failed" + logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid, + run.returncode or "OK", run.signal or "") + if pid_file and service_result in [ "success" ]: + pid = self.wait_pid_file(pid_file) # application PIDFile + logg.info("%s start done PID %s [%s]", runs, pid, pid_file) + if pid: + env["MAINPID"] = strE(pid) + if not pid_file: + time.sleep(MinimumTimeoutStartSec) + logg.warning("No PIDFile for forking %s", strQ(conf.filename())) + status_file = self.get_status_file_from(conf) + self.set_status_from(conf, "ExecMainCode", strE(returncode)) + active = returncode and "failed" or "active" + self.write_status_from(conf, AS=active) + else: + logg.error("unsupported run type '%s'", runs) + return False + # POST sequence + if not self.is_active_from(conf): + logg.warning("%s start not active", runs) + # according to the systemd documentation, a failed start-sequence + # should execute the ExecStopPost sequence allowing some cleanup. + env["SERVICE_RESULT"] = service_result + for cmd in conf.getlist("Service", "ExecStopPost", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("post-fail %s", shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + logg.debug("post-fail done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + if _what_kind not in ["none", "keep"]: + self.remove_service_directories(conf) + return False + else: + for cmd in conf.getlist("Service", "ExecStartPost", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("post-start %s", shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + logg.debug("post-start done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + return True + def listen_modules(self, *modules): + """ [UNIT]... -- listen socket units""" + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.listen_units(units) and found_all + def listen_units(self, units): + """ fails if any socket does not start """ + self.wait_system() + done = True + started_units = [] + active_units = [] + for unit in self.sortedAfter(units): + started_units.append(unit) + if not self.listen_unit(unit): + done = False + else: + active_units.append(unit) + if active_units: + logg.info("init-loop start") + sig = self.init_loop_until_stop(started_units) + logg.info("init-loop %s", sig) + for unit in reversed(started_units): + pass # self.stop_unit(unit) + return done + def listen_unit(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.debug("unit could not be loaded (%s)", unit) + logg.error("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.listen_unit_from(conf) + def listen_unit_from(self, conf): + if not conf: return False + with waitlock(conf): + logg.debug(" listen unit %s => %s", conf.name(), strQ(conf.filename())) + return self.do_listen_unit_from(conf) + def do_listen_unit_from(self, conf): + if conf.name().endswith(".socket"): + return self.do_start_socket_from(conf) + else: + logg.error("listen not implemented for unit type: %s", conf.name()) + return False + def do_accept_socket_from(self, conf, sock): + logg.debug("%s: accepting %s", conf.name(), sock.fileno()) + service_unit = self.get_socket_service_from(conf) + service_conf = self.load_unit_conf(service_unit) + if service_conf is None or TestAccept: #pragma: no cover + if sock.type == socket.SOCK_STREAM: + conn, addr = sock.accept() + data = conn.recv(1024) + logg.debug("%s: '%s'", conf.name(), data) + conn.send(b"ERROR: "+data.upper()) + conn.close() + return False + if sock.type == socket.SOCK_DGRAM: + data, sender = sock.recvfrom(1024) + logg.debug("%s: '%s'", conf.name(), data) + sock.sendto(b"ERROR: "+data.upper(), sender) + return False + logg.error("can not accept socket type %s", strINET(sock.type)) + return False + return self.do_start_service_from(service_conf) + def get_socket_service_from(self, conf): + socket_unit = conf.name() + accept = conf.getbool("Socket", "Accept", "no") + service_type = accept and "@.service" or ".service" + service_name = path_replace_extension(socket_unit, ".socket", service_type) + service_unit = conf.get("Socket", "Service", service_name) + logg.debug("socket %s -> service %s", socket_unit, service_unit) + return service_unit + def do_start_socket_from(self, conf): + runs = "socket" + timeout = self.get_SocketTimeoutSec(conf) + accept = conf.getbool("Socket", "Accept", "no") + stream = conf.get("Socket", "ListenStream", "") + service_unit = self.get_socket_service_from(conf) + service_conf = self.load_unit_conf(service_unit) + if service_conf is None: + logg.debug("unit could not be loaded (%s)", service_unit) + logg.error("Unit %s not found.", service_unit) + return False + env = self.get_env(conf) + if not self._quiet: + okee = self.exec_check_unit(conf, env, "Socket", "Exec") # all... + if not okee and _no_reload: return False + if True: + for cmd in conf.getlist("Socket", "ExecStartPre", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info(" pre-start %s", shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + logg.debug(" pre-start done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + if run.returncode and exe.check: + logg.error("the ExecStartPre control process exited with error code") + active = "failed" + self.write_status_from(conf, AS=active ) + return False + # service_directories = self.create_service_directories(conf) + # env.update(service_directories) + listening=False + if not accept: + sock = self.create_socket(conf) + if sock and TestListen: + listening=True + self._sockets[conf.name()] = SystemctlSocket(conf, sock) + service_result = "success" + state = sock and "active" or "failed" + self.write_status_from(conf, AS=state) + if not listening: + # we do not listen but have the service started right away + done = self.do_start_service_from(service_conf) + service_result = done and "success" or "failed" + if not self.is_active_from(service_conf): + service_result = "failed" + state = service_result + if service_result in ["success"]: + state = "active" + self.write_status_from(conf, AS=state) + # POST sequence + if service_result in ["failed"]: + # according to the systemd documentation, a failed start-sequence + # should execute the ExecStopPost sequence allowing some cleanup. + env["SERVICE_RESULT"] = service_result + for cmd in conf.getlist("Socket", "ExecStopPost", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("post-fail %s", shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + logg.debug("post-fail done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + return False + else: + for cmd in conf.getlist("Socket", "ExecStartPost", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("post-start %s", shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + logg.debug("post-start done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + return True + def create_socket(self, conf): + unsupported = ["ListenUSBFunction", "ListenMessageQueue", "ListenNetlink"] + unsupported += [ "ListenSpecial", "ListenFIFO", "ListenSequentialPacket"] + for item in unsupported: + if conf.get("Socket", item, ""): + logg.warning("%s: %s sockets are not implemented", conf.name(), item) + self.error |= NOT_OK + return None + vListenDatagram = conf.get("Socket", "ListenDatagram", "") + vListenStream = conf.get("Socket", "ListenStream", "") + address = vListenStream or vListenDatagram + m = re.match(r"(/.*)", address) + if m: + path = m.group(1) + sock = self.create_unix_socket(conf, path, not vListenStream) + self.set_status_from(conf, "path", path) + return sock + m = re.match(r"(\d+[.]\d*[.]\d*[.]\d+):(\d+)", address) + if m: + addr, port = m.group(1), m.group(2) + sock = self.create_port_ipv4_socket(conf, addr, port, not vListenStream) + self.set_status_from(conf, "port", port) + self.set_status_from(conf, "addr", addr) + return sock + m = re.match(r"\[([0-9a-fA-F:]*)\]:(\d+)", address) + if m: + addr, port = m.group(1), m.group(2) + sock = self.create_port_ipv6_socket(conf, addr, port, not vListenStream) + self.set_status_from(conf, "port", port) + self.set_status_from(conf, "addr", addr) + return sock + m = re.match(r"(\d+)$", address) + if m: + port = m.group(1) + sock = self.create_port_socket(conf, port, not vListenStream) + self.set_status_from(conf, "port", port) + return sock + if re.match("@.*", address): + logg.warning("%s: abstract namespace socket not implemented (%s)", conf.name(), address) + return None + if re.match("vsock:.*", address): + logg.warning("%s: virtual machine socket not implemented (%s)", conf.name(), address) + return None + logg.error("%s: unknown socket address type (%s)", conf.name(), address) + return None + def create_unix_socket(self, conf, path, dgram): + sock_stream = dgram and socket.SOCK_DGRAM or socket.SOCK_STREAM + sock = socket.socket(socket.AF_UNIX, sock_stream) + try: + dirmode = conf.get("Socket", "DirectoryMode", "0755") + mode = conf.get("Socket", "SocketMode", "0666") + user = conf.get("Socket", "SocketUser", "") + group = conf.get("Socket", "SocketGroup", "") + symlinks = conf.getlist("Socket", "SymLinks", []) + dirpath = os.path.dirname(path) + if not os.path.isdir(dirpath): + os.makedirs(dirpath, int(dirmode, 8)) + if os.path.exists(path): + os.unlink(path) + sock.bind(path) + os.fchmod(sock.fileno(), int(mode, 8)) + shutil_fchown(sock.fileno(), user, group) + if symlinks: + logg.warning("%s: symlinks for socket not implemented (%s)", conf.name(), path) + except Exception as e: + logg.error("%s: create socket failed [%s]: %s", conf.name(), path, e) + sock.close() + return None + return sock + def create_port_socket(self, conf, port, dgram): + inet = dgram and socket.SOCK_DGRAM or socket.SOCK_STREAM + sock = socket.socket(socket.AF_INET, inet) + try: + sock.bind(('', int(port))) + logg.info("%s: bound socket at %s %s:%s", conf.name(), strINET(inet), "*", port) + except Exception as e: + logg.error("%s: create socket failed (%s:%s): %s", conf.name(), "*", port, e) + sock.close() + return None + return sock + def create_port_ipv4_socket(self, conf, addr, port, dgram): + inet = dgram and socket.SOCK_DGRAM or socket.SOCK_STREAM + sock = socket.socket(socket.AF_INET, inet) + try: + sock.bind((addr, int(port))) + logg.info("%s: bound socket at %s %s:%s", conf.name(), strINET(inet), addr, port) + except Exception as e: + logg.error("%s: create socket failed (%s:%s): %s", conf.name(), addr, port, e) + sock.close() + return None + return sock + def create_port_ipv6_socket(self, conf, addr, port, dgram): + inet = dgram and socket.SOCK_DGRAM or socket.SOCK_STREAM + sock = socket.socket(socket.AF_INET6, inet) + try: + sock.bind((addr, int(port))) + logg.info("%s: bound socket at %s [%s]:%s", conf.name(), strINET(inet), addr, port) + except Exception as e: + logg.error("%s: create socket failed ([%s]:%s): %s", conf.name(), addr, port, e) + sock.close() + return None + return sock + def extend_exec_env(self, env): + env = env.copy() + # implant DefaultPath into $PATH + path = env.get("PATH", DefaultPath) + parts = path.split(os.pathsep) + for part in DefaultPath.split(os.pathsep): + if part and part not in parts: + parts.append(part) + env["PATH"] = str(os.pathsep).join(parts) + # reset locale to system default + for name in ResetLocale: + if name in env: + del env[name] + locale = {} + path = env.get("LOCALE_CONF", LocaleConf) + parts = path.split(os.pathsep) + for part in parts: + if os.path.isfile(part): + for var, val in self.read_env_file("-"+part): + locale[var] = val + env[var] = val + if "LANG" not in locale: + env["LANG"] = locale.get("LANGUAGE", locale.get("LC_CTYPE", "C")) + return env + def expand_list(self, group_lines, conf): + result = [] + for line in group_lines: + for item in line.split(): + if item: + result.append(self.expand_special(item, conf)) + return result + def get_User(self, conf): + return self.expand_special(conf.get("Service", "User", ""), conf) + def get_Group(self, conf): + return self.expand_special(conf.get("Service", "Group", ""), conf) + def get_SupplementaryGroups(self, conf): + return self.expand_list(conf.getlist("Service", "SupplementaryGroups", []), conf) + def skip_journal_log(self, conf): + if self.get_unit_type(conf.name()) not in [ "service" ]: + return True + std_out = conf.get("Service", "StandardOutput", DefaultStandardOutput) + std_err = conf.get("Service", "StandardError", DefaultStandardError) + out, err = False, False + if std_out in ["null"]: out = True + if std_out.startswith("file:"): out = True + if std_err in ["inherit"]: std_err = std_out + if std_err in ["null"]: err = True + if std_err.startswith("file:"): err = True + if std_err.startswith("append:"): err = True + return out and err + def dup2_journal_log(self, conf): + msg = "" + std_inp = conf.get("Service", "StandardInput", DefaultStandardInput) + std_out = conf.get("Service", "StandardOutput", DefaultStandardOutput) + std_err = conf.get("Service", "StandardError", DefaultStandardError) + inp, out, err = None, None, None + if std_inp in ["null"]: + inp = open(_dev_null, "r") + elif std_inp.startswith("file:"): + fname = std_inp[len("file:"):] + if os.path.exists(fname): + inp = open(fname, "r") + else: + inp = open(_dev_zero, "r") + else: + inp = open(_dev_zero, "r") + assert inp is not None + try: + if std_out in ["null"]: + out = open(_dev_null, "w") + elif std_out.startswith("file:"): + fname = std_out[len("file:"):] + fdir = os.path.dirname(fname) + if not os.path.exists(fdir): + os.makedirs(fdir) + out = open(fname, "w") + elif std_out.startswith("append:"): + fname = std_out[len("append:"):] + fdir = os.path.dirname(fname) + if not os.path.exists(fdir): + os.makedirs(fdir) + out = open(fname, "a") + except Exception as e: + msg += "\n%s: %s" % (fname, e) + if out is None: + out = self.open_journal_log(conf) + err = out + assert out is not None + try: + if std_err in ["inherit"]: + err = out + elif std_err in ["null"]: + err = open(_dev_null, "w") + elif std_err.startswith("file:"): + fname = std_err[len("file:"):] + fdir = os.path.dirname(fname) + if not os.path.exists(fdir): + os.makedirs(fdir) + err = open(fname, "w") + elif std_err.startswith("append:"): + fname = std_err[len("append:"):] + fdir = os.path.dirname(fname) + if not os.path.exists(fdir): + os.makedirs(fdir) + err = open(fname, "a") + except Exception as e: + msg += "\n%s: %s" % (fname, e) + if err is None: + err = self.open_journal_log(conf) + assert err is not None + if msg: + err.write("ERROR:") + err.write(msg.strip()) + err.write("\n") + if EXEC_DUP2: + os.dup2(inp.fileno(), sys.stdin.fileno()) + os.dup2(out.fileno(), sys.stdout.fileno()) + os.dup2(err.fileno(), sys.stderr.fileno()) + def execve_from(self, conf, cmd, env): + """ this code is commonly run in a child process // returns exit-code""" + runs = conf.get("Service", "Type", "simple").lower() + # logg.debug("%s process for %s => %s", runs, strE(conf.name()), strQ(conf.filename())) + self.dup2_journal_log(conf) + # + runuser = self.get_User(conf) + rungroup = self.get_Group(conf) + xgroups = self.get_SupplementaryGroups(conf) + envs = shutil_setuid(runuser, rungroup, xgroups) + badpath = self.chdir_workingdir(conf) # some dirs need setuid before + if badpath: + logg.error("(%s): bad workingdir: '%s'", shell_cmd(cmd), badpath) + sys.exit(1) + env = self.extend_exec_env(env) + env.update(envs) # set $HOME to ~$USER + try: + if EXEC_SPAWN: + cmd_args = [ arg for arg in cmd ] # satisfy mypy + exitcode = os.spawnvpe(os.P_WAIT, cmd[0], cmd_args, env) + sys.exit(exitcode) + else: # pragma: no cover + os.execve(cmd[0], cmd, env) + sys.exit(11) # pragma: no cover (can not be reached / bug like mypy#8401) + except Exception as e: + logg.error("(%s): %s", shell_cmd(cmd), e) + sys.exit(1) + def test_start_unit(self, unit): + """ helper function to test the code that is normally forked off """ + conf = self.load_unit_conf(unit) + if not conf: return None + env = self.get_env(conf) + for cmd in conf.getlist("Service", "ExecStart", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + self.execve_from(conf, newcmd, env) + return None + def stop_modules(self, *modules): + """ [UNIT]... -- stop these units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.stop_units(units) and found_all + def stop_units(self, units): + """ fails if any unit fails to stop """ + self.wait_system() + done = True + for unit in self.sortedBefore(units): + if not self.stop_unit(unit): + done = False + return done + def stop_unit(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.stop_unit_from(conf) + + def get_TimeoutStopSec(self, conf): + timeout = conf.get("Service", "TimeoutSec", strE(DefaultTimeoutStartSec)) + timeout = conf.get("Service", "TimeoutStopSec", timeout) + return time_to_seconds(timeout, DefaultMaximumTimeout) + def stop_unit_from(self, conf): + if not conf: return False + if self.syntax_check(conf) > 100: return False + with waitlock(conf): + logg.info(" stop unit %s => %s", conf.name(), strQ(conf.filename())) + return self.do_stop_unit_from(conf) + def do_stop_unit_from(self, conf): + if conf.name().endswith(".service"): + return self.do_stop_service_from(conf) + elif conf.name().endswith(".socket"): + return self.do_stop_socket_from(conf) + elif conf.name().endswith(".target"): + return self.do_stop_target_from(conf) + else: + logg.error("stop not implemented for unit type: %s", conf.name()) + return False + def do_stop_service_from(self, conf): + timeout = self.get_TimeoutStopSec(conf) + runs = conf.get("Service", "Type", "simple").lower() + env = self.get_env(conf) + if not self._quiet: + okee = self.exec_check_unit(conf, env, "Service", "ExecStop") + if not okee and _no_reload: return False + service_directories = self.env_service_directories(conf) + env.update(service_directories) + returncode = 0 + service_result = "success" + if runs in [ "oneshot" ]: + status_file = self.get_status_file_from(conf) + if self.get_status_from(conf, "ActiveState", "unknown") == "inactive": + logg.warning("the service is already down once") + return True + for cmd in conf.getlist("Service", "ExecStop", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("%s stop %s", runs, shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + if run.returncode and exe.check: + returncode = run.returncode + service_result = "failed" + break + if True: + if returncode: + self.set_status_from(conf, "ExecStopCode", strE(returncode)) + self.write_status_from(conf, AS="failed") + else: + self.clean_status_from(conf) # "inactive" + ### fallback Stop => Kill for ["simple","notify","forking"] + elif not conf.getlist("Service", "ExecStop", []): + logg.info("no ExecStop => systemctl kill") + if True: + self.do_kill_unit_from(conf) + self.clean_pid_file_from(conf) + self.clean_status_from(conf) # "inactive" + elif runs in [ "simple", "notify", "idle" ]: + status_file = self.get_status_file_from(conf) + size = os.path.exists(status_file) and os.path.getsize(status_file) + logg.info("STATUS %s %s", status_file, size) + pid = 0 + for cmd in conf.getlist("Service", "ExecStop", []): + env["MAINPID"] = strE(self.read_mainpid_from(conf)) + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("%s stop %s", runs, shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + run = must_have_failed(run, newcmd) # TODO: a workaround + # self.write_status_from(conf, MainPID=run.pid) # no ExecStop + if run.returncode and exe.check: + returncode = run.returncode + service_result = "failed" + break + pid = to_intN(env.get("MAINPID")) + if pid: + if self.wait_vanished_pid(pid, timeout): + self.clean_pid_file_from(conf) + self.clean_status_from(conf) # "inactive" + else: + logg.info("%s sleep as no PID was found on Stop", runs) + time.sleep(MinimumTimeoutStopSec) + pid = self.read_mainpid_from(conf) + if not pid or not pid_exists(pid) or pid_zombie(pid): + self.clean_pid_file_from(conf) + self.clean_status_from(conf) # "inactive" + elif runs in [ "forking" ]: + status_file = self.get_status_file_from(conf) + pid_file = self.pid_file_from(conf) + for cmd in conf.getlist("Service", "ExecStop", []): + # active = self.is_active_from(conf) + if pid_file: + new_pid = self.read_mainpid_from(conf) + if new_pid: + env["MAINPID"] = strE(new_pid) + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("fork stop %s", shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + if run.returncode and exe.check: + returncode = run.returncode + service_result = "failed" + break + pid = to_intN(env.get("MAINPID")) + if pid: + if self.wait_vanished_pid(pid, timeout): + self.clean_pid_file_from(conf) + else: + logg.info("%s sleep as no PID was found on Stop", runs) + time.sleep(MinimumTimeoutStopSec) + pid = self.read_mainpid_from(conf) + if not pid or not pid_exists(pid) or pid_zombie(pid): + self.clean_pid_file_from(conf) + if returncode: + if os.path.isfile(status_file): + self.set_status_from(conf, "ExecStopCode", strE(returncode)) + self.write_status_from(conf, AS="failed") + else: + self.clean_status_from(conf) # "inactive" + else: + logg.error("unsupported run type '%s'", runs) + return False + # POST sequence + if not self.is_active_from(conf): + env["SERVICE_RESULT"] = service_result + for cmd in conf.getlist("Service", "ExecStopPost", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("post-stop %s", shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + logg.debug("post-stop done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + if _what_kind not in ["none", "keep"]: + self.remove_service_directories(conf) + return service_result == "success" + def do_stop_socket_from(self, conf): + runs = "socket" + timeout = self.get_SocketTimeoutSec(conf) + accept = conf.getbool("Socket", "Accept", "no") + service_unit = self.get_socket_service_from(conf) + service_conf = self.load_unit_conf(service_unit) + if service_conf is None: + logg.debug("unit could not be loaded (%s)", service_unit) + logg.error("Unit %s not found.", service_unit) + return False + env = self.get_env(conf) + if not self._quiet: + okee = self.exec_check_unit(conf, env, "Socket", "ExecStop") + if not okee and _no_reload: return False + if not accept: + # we do not listen but have the service started right away + done = self.do_stop_service_from(service_conf) + service_result = done and "success" or "failed" + else: + done = self.do_stop_service_from(service_conf) + service_result = done and "success" or "failed" + # service_directories = self.env_service_directories(conf) + # env.update(service_directories) + # POST sequence + if not self.is_active_from(conf): + env["SERVICE_RESULT"] = service_result + for cmd in conf.getlist("Socket", "ExecStopPost", []): + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("post-stop %s", shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + logg.debug("post-stop done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + return service_result == "success" + def wait_vanished_pid(self, pid, timeout): + if not pid: + return True + logg.info("wait for PID %s to vanish (%ss)", pid, timeout) + for x in xrange(int(timeout)): + if not self.is_active_pid(pid): + logg.info("wait for PID %s is done (%s.)", pid, x) + return True + time.sleep(1) # until TimeoutStopSec + logg.info("wait for PID %s failed (%s.)", pid, x) + return False + def reload_modules(self, *modules): + """ [UNIT]... -- reload these units """ + self.wait_system() + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.reload_units(units) and found_all + def reload_units(self, units): + """ fails if any unit fails to reload """ + self.wait_system() + done = True + for unit in self.sortedAfter(units): + if not self.reload_unit(unit): + done = False + return done + def reload_unit(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.reload_unit_from(conf) + def reload_unit_from(self, conf): + if not conf: return False + if self.syntax_check(conf) > 100: return False + with waitlock(conf): + logg.info(" reload unit %s => %s", conf.name(), strQ(conf.filename())) + return self.do_reload_unit_from(conf) + def do_reload_unit_from(self, conf): + if conf.name().endswith(".service"): + return self.do_reload_service_from(conf) + elif conf.name().endswith(".socket"): + service_unit = self.get_socket_service_from(conf) + service_conf = self.load_unit_conf(service_unit) + if service_conf: + return self.do_reload_service_from(service_conf) + else: + logg.error("no %s found for unit type: %s", service_unit, conf.name()) + return False + elif conf.name().endswith(".target"): + return self.do_reload_target_from(conf) + else: + logg.error("reload not implemented for unit type: %s", conf.name()) + return False + def do_reload_service_from(self, conf): + runs = conf.get("Service", "Type", "simple").lower() + env = self.get_env(conf) + if not self._quiet: + okee = self.exec_check_unit(conf, env, "Service", "ExecReload") + if not okee and _no_reload: return False + initscript = conf.filename() + if self.is_sysv_file(initscript): + status_file = self.get_status_file_from(conf) + if initscript: + newcmd = [initscript, "reload"] + env["SYSTEMCTL_SKIP_REDIRECT"] = "yes" + logg.info("%s reload %s", runs, shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: nocover + run = subprocess_waitpid(forkpid) + self.set_status_from(conf, "ExecReloadCode", run.returncode) + if run.returncode: + self.write_status_from(conf, AS="failed") + return False + else: + self.write_status_from(conf, AS="active") + return True + service_directories = self.env_service_directories(conf) + env.update(service_directories) + if runs in [ "simple", "notify", "forking", "idle" ]: + if not self.is_active_from(conf): + logg.info("no reload on inactive service %s", conf.name()) + return True + for cmd in conf.getlist("Service", "ExecReload", []): + env["MAINPID"] = strE(self.read_mainpid_from(conf)) + exe, newcmd = self.exec_newcmd(cmd, env, conf) + logg.info("%s reload %s", runs, shell_cmd(newcmd)) + forkpid = os.fork() + if not forkpid: + self.execve_from(conf, newcmd, env) # pragma: no cover + run = subprocess_waitpid(forkpid) + if run.returncode and exe.check: + logg.error("Job for %s failed because the control process exited with error code. (%s)", + conf.name(), run.returncode) + return False + time.sleep(MinimumYield) + return True + elif runs in [ "oneshot" ]: + logg.debug("ignored run type '%s' for reload", runs) + return True + else: + logg.error("unsupported run type '%s'", runs) + return False + def restart_modules(self, *modules): + """ [UNIT]... -- restart these units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.restart_units(units) and found_all + def restart_units(self, units): + """ fails if any unit fails to restart """ + self.wait_system() + done = True + for unit in self.sortedAfter(units): + if not self.restart_unit(unit): + done = False + return done + def restart_unit(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.restart_unit_from(conf) + def restart_unit_from(self, conf): + if not conf: return False + if self.syntax_check(conf) > 100: return False + with waitlock(conf): + if conf.name().endswith(".service"): + logg.info(" restart service %s => %s", conf.name(), strQ(conf.filename())) + if not self.is_active_from(conf): + return self.do_start_unit_from(conf) + else: + return self.do_restart_unit_from(conf) + else: + return self.do_restart_unit_from(conf) + def do_restart_unit_from(self, conf): + logg.info("(restart) => stop/start %s", conf.name()) + self.do_stop_unit_from(conf) + return self.do_start_unit_from(conf) + def try_restart_modules(self, *modules): + """ [UNIT]... -- try-restart these units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.try_restart_units(units) and found_all + def try_restart_units(self, units): + """ fails if any module fails to try-restart """ + self.wait_system() + done = True + for unit in self.sortedAfter(units): + if not self.try_restart_unit(unit): + done = False + return done + def try_restart_unit(self, unit): + """ only do 'restart' if 'active' """ + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + with waitlock(conf): + logg.info(" try-restart unit %s => %s", conf.name(), strQ(conf.filename())) + if self.is_active_from(conf): + return self.do_restart_unit_from(conf) + return True + def reload_or_restart_modules(self, *modules): + """ [UNIT]... -- reload-or-restart these units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.reload_or_restart_units(units) and found_all + def reload_or_restart_units(self, units): + """ fails if any unit does not reload-or-restart """ + self.wait_system() + done = True + for unit in self.sortedAfter(units): + if not self.reload_or_restart_unit(unit): + done = False + return done + def reload_or_restart_unit(self, unit): + """ do 'reload' if specified, otherwise do 'restart' """ + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.reload_or_restart_unit_from(conf) + def reload_or_restart_unit_from(self, conf): + """ do 'reload' if specified, otherwise do 'restart' """ + if not conf: return False + with waitlock(conf): + logg.info(" reload-or-restart unit %s => %s", conf.name(), strQ(conf.filename())) + return self.do_reload_or_restart_unit_from(conf) + def do_reload_or_restart_unit_from(self, conf): + if not self.is_active_from(conf): + # try: self.stop_unit_from(conf) + # except Exception as e: pass + return self.do_start_unit_from(conf) + elif conf.getlist("Service", "ExecReload", []): + logg.info("found service to have ExecReload -> 'reload'") + return self.do_reload_unit_from(conf) + else: + logg.info("found service without ExecReload -> 'restart'") + return self.do_restart_unit_from(conf) + def reload_or_try_restart_modules(self, *modules): + """ [UNIT]... -- reload-or-try-restart these units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.reload_or_try_restart_units(units) and found_all + def reload_or_try_restart_units(self, units): + """ fails if any unit fails to reload-or-try-restart """ + self.wait_system() + done = True + for unit in self.sortedAfter(units): + if not self.reload_or_try_restart_unit(unit): + done = False + return done + def reload_or_try_restart_unit(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.reload_or_try_restart_unit_from(conf) + def reload_or_try_restart_unit_from(self, conf): + with waitlock(conf): + logg.info(" reload-or-try-restart unit %s => %s", conf.name(), strQ(conf.filename())) + return self.do_reload_or_try_restart_unit_from(conf) + def do_reload_or_try_restart_unit_from(self, conf): + if conf.getlist("Service", "ExecReload", []): + return self.do_reload_unit_from(conf) + elif not self.is_active_from(conf): + return True + else: + return self.do_restart_unit_from(conf) + def kill_modules(self, *modules): + """ [UNIT]... -- kill these units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + # self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.kill_units(units) and found_all + def kill_units(self, units): + """ fails if any unit could not be killed """ + self.wait_system() + done = True + for unit in self.sortedBefore(units): + if not self.kill_unit(unit): + done = False + return done + def kill_unit(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.kill_unit_from(conf) + def kill_unit_from(self, conf): + if not conf: return False + with waitlock(conf): + logg.info(" kill unit %s => %s", conf.name(), strQ(conf.filename())) + return self.do_kill_unit_from(conf) + def do_kill_unit_from(self, conf): + started = time.time() + doSendSIGKILL = self.get_SendSIGKILL(conf) + doSendSIGHUP = self.get_SendSIGHUP(conf) + useKillMode = self.get_KillMode(conf) + useKillSignal = self.get_KillSignal(conf) + kill_signal = getattr(signal, useKillSignal) + timeout = self.get_TimeoutStopSec(conf) + status_file = self.get_status_file_from(conf) + size = os.path.exists(status_file) and os.path.getsize(status_file) + logg.info("STATUS %s %s", status_file, size) + mainpid = self.read_mainpid_from(conf) + self.clean_status_from(conf) # clear RemainAfterExit and TimeoutStartSec + if not mainpid: + if useKillMode in ["control-group"]: + logg.warning("no main PID %s", strQ(conf.filename())) + logg.warning("and there is no control-group here") + else: + logg.info("no main PID %s", strQ(conf.filename())) + return False + if not pid_exists(mainpid) or pid_zombie(mainpid): + logg.debug("ignoring children when mainpid is already dead") + # because we list child processes, not processes in control-group + return True + pidlist = self.pidlist_of(mainpid) # here + if pid_exists(mainpid): + logg.info("stop kill PID %s", mainpid) + self._kill_pid(mainpid, kill_signal) + if useKillMode in ["control-group"]: + if len(pidlist) > 1: + logg.info("stop control-group PIDs %s", pidlist) + for pid in pidlist: + if pid != mainpid: + self._kill_pid(pid, kill_signal) + if doSendSIGHUP: + logg.info("stop SendSIGHUP to PIDs %s", pidlist) + for pid in pidlist: + self._kill_pid(pid, signal.SIGHUP) + # wait for the processes to have exited + while True: + dead = True + for pid in pidlist: + if pid_exists(pid) and not pid_zombie(pid): + dead = False + break + if dead: + break + if time.time() > started + timeout: + logg.info("service PIDs not stopped after %s", timeout) + break + time.sleep(1) # until TimeoutStopSec + if dead or not doSendSIGKILL: + logg.info("done kill PID %s %s", mainpid, dead and "OK") + return dead + if useKillMode in [ "control-group", "mixed" ]: + logg.info("hard kill PIDs %s", pidlist) + for pid in pidlist: + if pid != mainpid: + self._kill_pid(pid, signal.SIGKILL) + time.sleep(MinimumYield) + # useKillMode in [ "control-group", "mixed", "process" ] + if pid_exists(mainpid): + logg.info("hard kill PID %s", mainpid) + self._kill_pid(mainpid, signal.SIGKILL) + time.sleep(MinimumYield) + dead = not pid_exists(mainpid) or pid_zombie(mainpid) + logg.info("done hard kill PID %s %s", mainpid, dead and "OK") + return dead + def _kill_pid(self, pid, kill_signal = None): + try: + sig = kill_signal or signal.SIGTERM + os.kill(pid, sig) + except OSError as e: + if e.errno == errno.ESRCH or e.errno == errno.ENOENT: + logg.debug("kill PID %s => No such process", pid) + return True + else: + logg.error("kill PID %s => %s", pid, str(e)) + return False + return not pid_exists(pid) or pid_zombie(pid) + def is_active_modules(self, *modules): + """ [UNIT].. -- check if these units are in active state + implements True if all is-active = True """ + # systemctl returns multiple lines, one for each argument + # "active" when is_active + # "inactive" when not is_active + # "unknown" when not enabled + # The return code is set to + # 0 when "active" + # 1 when unit is not found + # 3 when any "inactive" or "unknown" + # However: # TODO! BUG in original systemctl! + # documentation says " exit code 0 if at least one is active" + # and "Unless --quiet is specified, print the unit state" + units = [] + results = [] + for module in modules: + units = self.match_units(to_list(module)) + if not units: + logg.error("Unit %s not found.", unit_of(module)) + # self.error |= NOT_FOUND + self.error |= NOT_ACTIVE + results += [ "inactive" ] + continue + for unit in units: + active = self.get_active_unit(unit) + enabled = self.enabled_unit(unit) + if enabled != "enabled" and ACTIVE_IF_ENABLED: + active = "inactive" # "unknown" + results += [ active ] + break + ## how it should work: + status = "active" in results + ## how 'systemctl' works: + non_active = [ result for result in results if result != "active" ] + if non_active: + self.error |= NOT_ACTIVE + if non_active: + self.error |= NOT_OK # status + if _quiet: + return [] + return results + def is_active_from(self, conf): + """ used in try-restart/other commands to check if needed. """ + if not conf: return False + return self.get_active_from(conf) == "active" + def active_pid_from(self, conf): + if not conf: return False + pid = self.read_mainpid_from(conf) + return self.is_active_pid(pid) + def is_active_pid(self, pid): + """ returns pid if the pid is still an active process """ + if pid and pid_exists(pid) and not pid_zombie(pid): + return pid # usually a string (not null) + return None + def get_active_unit(self, unit): + """ returns 'active' 'inactive' 'failed' 'unknown' """ + conf = self.load_unit_conf(unit) + if not conf: + logg.warning("Unit %s not found.", unit) + return "unknown" + else: + return self.get_active_from(conf) + def get_active_from(self, conf): + if conf.name().endswith(".service"): + return self.get_active_service_from(conf) + elif conf.name().endswith(".socket"): + service_unit = self.get_socket_service_from(conf) + service_conf = self.load_unit_conf(service_unit) + return self.get_active_service_from(service_conf) + elif conf.name().endswith(".target"): + return self.get_active_target_from(conf) + else: + logg.debug("is-active not implemented for unit type: %s", conf.name()) + return "unknown" # TODO: "inactive" ? + def get_active_service_from(self, conf): + """ returns 'active' 'inactive' 'failed' 'unknown' """ + # used in try-restart/other commands to check if needed. + if not conf: return "unknown" + pid_file = self.pid_file_from(conf) + if pid_file: # application PIDFile + if not os.path.exists(pid_file): + return "inactive" + status_file = self.get_status_file_from(conf) + if self.getsize(status_file): + state = self.get_status_from(conf, "ActiveState", "") + if state: + if DEBUG_STATUS: + logg.info("get_status_from %s => %s", conf.name(), state) + return state + pid = self.read_mainpid_from(conf) + if DEBUG_STATUS: + logg.debug("pid_file '%s' => PID %s", pid_file or status_file, strE(pid)) + if pid: + if not pid_exists(pid) or pid_zombie(pid): + return "failed" + return "active" + else: + return "inactive" + def get_active_target_from(self, conf): + """ returns 'active' 'inactive' 'failed' 'unknown' """ + return self.get_active_target(conf.name()) + def get_active_target(self, target): + """ returns 'active' 'inactive' 'failed' 'unknown' """ + if target in self.get_active_target_list(): + status = self.is_system_running() + if status in [ "running" ]: + return "active" + return "inactive" + else: + services = self.target_default_services(target) + result = "active" + for service in services: + conf = self.load_unit_conf(service) + if conf: + state = self.get_active_from(conf) + if state in ["failed"]: + result = state + elif state not in ["active"]: + result = state + return result + def get_active_target_list(self): + current_target = self.get_default_target() + target_list = self.get_target_list(current_target) + target_list += [ DefaultUnit ] # upper end + target_list += [ SysInitTarget ] # lower end + return target_list + def get_substate_from(self, conf): + """ returns 'running' 'exited' 'dead' 'failed' 'plugged' 'mounted' """ + if not conf: return None + pid_file = self.pid_file_from(conf) + if pid_file: + if not os.path.exists(pid_file): + return "dead" + status_file = self.get_status_file_from(conf) + if self.getsize(status_file): + state = self.get_status_from(conf, "ActiveState", "") + if state: + if state in [ "active" ]: + return self.get_status_from(conf, "SubState", "running") + else: + return self.get_status_from(conf, "SubState", "dead") + pid = self.read_mainpid_from(conf) + if DEBUG_STATUS: + logg.debug("pid_file '%s' => PID %s", pid_file or status_file, strE(pid)) + if pid: + if not pid_exists(pid) or pid_zombie(pid): + return "failed" + return "running" + else: + return "dead" + def is_failed_modules(self, *modules): + """ [UNIT]... -- check if these units are in failes state + implements True if any is-active = True """ + units = [] + results = [] + for module in modules: + units = self.match_units(to_list(module)) + if not units: + logg.error("Unit %s not found.", unit_of(module)) + # self.error |= NOT_FOUND + results += [ "inactive" ] + continue + for unit in units: + active = self.get_active_unit(unit) + enabled = self.enabled_unit(unit) + if enabled != "enabled" and ACTIVE_IF_ENABLED: + active = "inactive" + results += [ active ] + break + if "failed" in results: + self.error = 0 + else: + self.error |= NOT_OK + if _quiet: + return [] + return results + def is_failed_from(self, conf): + if conf is None: return True + return self.get_active_from(conf) == "failed" + def reset_failed_modules(self, *modules): + """ [UNIT]... -- Reset failed state for all, one, or more units """ + units = [] + status = True + for module in modules: + units = self.match_units(to_list(module)) + if not units: + logg.error("Unit %s not found.", unit_of(module)) + # self.error |= NOT_FOUND + return False + for unit in units: + if not self.reset_failed_unit(unit): + logg.error("Unit %s could not be reset.", unit_of(module)) + status = False + break + return status + def reset_failed_unit(self, unit): + conf = self.load_unit_conf(unit) + if not conf: + logg.warning("Unit %s not found.", unit) + return False + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.reset_failed_from(conf) + def reset_failed_from(self, conf): + if conf is None: return True + if not self.is_failed_from(conf): return False + done = False + status_file = self.get_status_file_from(conf) + if status_file and os.path.exists(status_file): + try: + os.remove(status_file) + done = True + logg.debug("done rm %s", status_file) + except Exception as e: + logg.error("while rm %s: %s", status_file, e) + pid_file = self.pid_file_from(conf) + if pid_file and os.path.exists(pid_file): + try: + os.remove(pid_file) + done = True + logg.debug("done rm %s", pid_file) + except Exception as e: + logg.error("while rm %s: %s", pid_file, e) + return done + def status_modules(self, *modules): + """ [UNIT]... check the status of these units. + """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s could not be found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + result = self.status_units(units) + # if not found_all: + # self.error |= NOT_OK | NOT_ACTIVE # 3 + # # same as (dead) # original behaviour + return result + def status_units(self, units): + """ concatenates the status output of all units + and the last non-successful statuscode """ + status = 0 + result = "" + for unit in units: + status1, result1 = self.status_unit(unit) + if status1: status = status1 + if result: result += "\n\n" + result += result1 + if status: + self.error |= NOT_OK | NOT_ACTIVE # 3 + return result + def status_unit(self, unit): + conf = self.get_unit_conf(unit) + result = "%s - %s" % (unit, self.get_description_from(conf)) + loaded = conf.loaded() + if loaded: + filename = str(conf.filename()) + enabled = self.enabled_from(conf) + result += "\n Loaded: {loaded} ({filename}, {enabled})".format(**locals()) + for path in conf.overrides(): + result += "\n Drop-In: {path}".format(**locals()) + else: + result += "\n Loaded: failed" + return 3, result + active = self.get_active_from(conf) + substate = self.get_substate_from(conf) + result += "\n Active: {} ({})".format(active, substate) + if active == "active": + return 0, result + else: + return 3, result + def cat_modules(self, *modules): + """ [UNIT]... show the *.system file for these" + """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s could not be found.", unit_of(module)) + # self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + result = self.cat_units(units) + if not found_all: + self.error |= NOT_OK + return result + def cat_units(self, units): + done = True + result = "" + for unit in units: + text = self.cat_unit(unit) + if not text: + done = False + else: + if result: + result += "\n\n" + result += text + if not done: + self.error = NOT_OK + return result + def cat_unit(self, unit): + try: + unit_file = self.unit_file(unit) + if unit_file: + return open(unit_file).read() + logg.error("No files found for %s", unit) + except Exception as e: + print("Unit {} is not-loaded: {}".format(unit, e)) + self.error |= NOT_OK + return None + ## + ## + def load_preset_files(self, module = None): # -> [ preset-file-names,... ] + """ reads all preset files, returns the scanned files """ + if self._preset_file_list is None: + self._preset_file_list = {} + assert self._preset_file_list is not None + for folder in self.preset_folders(): + if not folder: + continue + if self._root: + folder = os_path(self._root, folder) + if not os.path.isdir(folder): + continue + for name in os.listdir(folder): + if not name.endswith(".preset"): + continue + if name not in self._preset_file_list: + path = os.path.join(folder, name) + if os.path.isdir(path): + continue + preset = PresetFile().read(path) + self._preset_file_list[name] = preset + logg.debug("found %s preset files", len(self._preset_file_list)) + return sorted(self._preset_file_list.keys()) + def get_preset_of_unit(self, unit): + """ [UNIT] check the *.preset of this unit + """ + self.load_preset_files() + assert self._preset_file_list is not None + for filename in sorted(self._preset_file_list.keys()): + preset = self._preset_file_list[filename] + status = preset.get_preset(unit) + if status: + return status + return None + def preset_modules(self, *modules): + """ [UNIT]... -- set 'enabled' when in *.preset + """ + if self.user_mode(): + logg.warning("preset makes no sense in --user mode") + return True + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s could not be found.", unit_of(module)) + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.preset_units(units) and found_all + def preset_units(self, units): + """ fails if any unit could not be changed """ + self.wait_system() + fails = 0 + found = 0 + for unit in units: + status = self.get_preset_of_unit(unit) + if not status: continue + found += 1 + if status.startswith("enable"): + if self._preset_mode == "disable": continue + logg.info("preset enable %s", unit) + if not self.enable_unit(unit): + logg.warning("failed to enable %s", unit) + fails += 1 + if status.startswith("disable"): + if self._preset_mode == "enable": continue + logg.info("preset disable %s", unit) + if not self.disable_unit(unit): + logg.warning("failed to disable %s", unit) + fails += 1 + return not fails and not not found + def system_preset_all(self, *modules): + """ 'preset' all services + enable or disable services according to *.preset files + """ + if self.user_mode(): + logg.warning("preset-all makes no sense in --user mode") + return True + found_all = True + units = self.match_units() # TODO: how to handle module arguments + return self.preset_units(units) and found_all + def wanted_from(self, conf, default = None): + if not conf: return default + return conf.get("Install", "WantedBy", default, True) + def enablefolders(self, wanted): + if self.user_mode(): + for folder in self.user_folders(): + yield self.default_enablefolder(wanted, folder) + if True: + for folder in self.system_folders(): + yield self.default_enablefolder(wanted, folder) + def enablefolder(self, wanted): + if self.user_mode(): + user_folder = self.user_folder() + return self.default_enablefolder(wanted, user_folder) + else: + return self.default_enablefolder(wanted) + def default_enablefolder(self, wanted, basefolder = None): + basefolder = basefolder or self.system_folder() + if not wanted: + return wanted + if not wanted.endswith(".wants"): + wanted = wanted + ".wants" + return os.path.join(basefolder, wanted) + def enable_modules(self, *modules): + """ [UNIT]... -- enable these units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + # self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + logg.info("matched %s", unit) #++ + if unit not in units: + units += [ unit ] + return self.enable_units(units) and found_all + def enable_units(self, units): + self.wait_system() + done = True + for unit in units: + if not self.enable_unit(unit): + done = False + elif self._now: + self.start_unit(unit) + return done + def enable_unit(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + unit_file = conf.filename() + if unit_file is None: + logg.error("Unit file %s not found.", unit) + return False + if self.is_sysv_file(unit_file): + if self.user_mode(): + logg.error("Initscript %s not for --user mode", unit) + return False + return self.enable_unit_sysv(unit_file) + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.enable_unit_from(conf) + def enable_unit_from(self, conf): + wanted = self.wanted_from(conf) + if not wanted and not self._force: + logg.debug("%s has no target", conf.name()) + return False # "static" is-enabled + target = wanted or self.get_default_target() + folder = self.enablefolder(target) + if self._root: + folder = os_path(self._root, folder) + if not os.path.isdir(folder): + os.makedirs(folder) + source = conf.filename() + if not source: # pragma: no cover (was checked before) + logg.debug("%s has no real file", conf.name()) + return False + symlink = os.path.join(folder, conf.name()) + if True: + _f = self._force and "-f" or "" + logg.info("ln -s {_f} '{source}' '{symlink}'".format(**locals())) + if self._force and os.path.islink(symlink): + os.remove(target) + if not os.path.islink(symlink): + os.symlink(source, symlink) + return True + def rc3_root_folder(self): + old_folder = os_path(self._root, _rc3_boot_folder) + new_folder = os_path(self._root, _rc3_init_folder) + if os.path.isdir(old_folder): # pragma: no cover + return old_folder + return new_folder + def rc5_root_folder(self): + old_folder = os_path(self._root, _rc5_boot_folder) + new_folder = os_path(self._root, _rc5_init_folder) + if os.path.isdir(old_folder): # pragma: no cover + return old_folder + return new_folder + def enable_unit_sysv(self, unit_file): + # a "multi-user.target"/rc3 is also started in /rc5 + rc3 = self._enable_unit_sysv(unit_file, self.rc3_root_folder()) + rc5 = self._enable_unit_sysv(unit_file, self.rc5_root_folder()) + return rc3 and rc5 + def _enable_unit_sysv(self, unit_file, rc_folder): + name = os.path.basename(unit_file) + nameS = "S50"+name + nameK = "K50"+name + if not os.path.isdir(rc_folder): + os.makedirs(rc_folder) + # do not double existing entries + for found in os.listdir(rc_folder): + m = re.match(r"S\d\d(.*)", found) + if m and m.group(1) == name: + nameS = found + m = re.match(r"K\d\d(.*)", found) + if m and m.group(1) == name: + nameK = found + target = os.path.join(rc_folder, nameS) + if not os.path.exists(target): + os.symlink(unit_file, target) + target = os.path.join(rc_folder, nameK) + if not os.path.exists(target): + os.symlink(unit_file, target) + return True + def disable_modules(self, *modules): + """ [UNIT]... -- disable these units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + # self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.disable_units(units) and found_all + def disable_units(self, units): + self.wait_system() + done = True + for unit in units: + if not self.disable_unit(unit): + done = False + return done + def disable_unit(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + unit_file = conf.filename() + if unit_file is None: + logg.error("Unit file %s not found.", unit) + return False + if self.is_sysv_file(unit_file): + if self.user_mode(): + logg.error("Initscript %s not for --user mode", unit) + return False + return self.disable_unit_sysv(unit_file) + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + return self.disable_unit_from(conf) + def disable_unit_from(self, conf): + wanted = self.wanted_from(conf) + if not wanted and not self._force: + logg.debug("%s has no target", conf.name()) + return False # "static" is-enabled + target = wanted or self.get_default_target() + for folder in self.enablefolders(target): + if self._root: + folder = os_path(self._root, folder) + symlink = os.path.join(folder, conf.name()) + if os.path.exists(symlink): + try: + _f = self._force and "-f" or "" + logg.info("rm {_f} '{symlink}'".format(**locals())) + if os.path.islink(symlink) or self._force: + os.remove(symlink) + except IOError as e: + logg.error("disable %s: %s", symlink, e) + except OSError as e: + logg.error("disable %s: %s", symlink, e) + return True + def disable_unit_sysv(self, unit_file): + rc3 = self._disable_unit_sysv(unit_file, self.rc3_root_folder()) + rc5 = self._disable_unit_sysv(unit_file, self.rc5_root_folder()) + return rc3 and rc5 + def _disable_unit_sysv(self, unit_file, rc_folder): + # a "multi-user.target"/rc3 is also started in /rc5 + name = os.path.basename(unit_file) + nameS = "S50"+name + nameK = "K50"+name + # do not forget the existing entries + for found in os.listdir(rc_folder): + m = re.match(r"S\d\d(.*)", found) + if m and m.group(1) == name: + nameS = found + m = re.match(r"K\d\d(.*)", found) + if m and m.group(1) == name: + nameK = found + target = os.path.join(rc_folder, nameS) + if os.path.exists(target): + os.unlink(target) + target = os.path.join(rc_folder, nameK) + if os.path.exists(target): + os.unlink(target) + return True + def is_enabled_sysv(self, unit_file): + name = os.path.basename(unit_file) + target = os.path.join(self.rc3_root_folder(), "S50%s" % name) + if os.path.exists(target): + return True + return False + def is_enabled_modules(self, *modules): + """ [UNIT]... -- check if these units are enabled + returns True if any of them is enabled.""" + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + # self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.is_enabled_units(units) # and found_all + def is_enabled_units(self, units): + """ true if any is enabled, and a list of infos """ + result = False + infos = [] + for unit in units: + infos += [ self.enabled_unit(unit) ] + if self.is_enabled(unit): + result = True + if not result: + self.error |= NOT_OK + return infos + def is_enabled(self, unit): + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s not found.", unit) + return False + unit_file = conf.filename() + if not unit_file: + logg.error("Unit %s not found.", unit) + return False + if self.is_sysv_file(unit_file): + return self.is_enabled_sysv(unit_file) + state = self.get_enabled_from(conf) + if state in ["enabled", "static"]: + return True + return False # ["disabled", "masked"] + def enabled_unit(self, unit): + conf = self.get_unit_conf(unit) + return self.enabled_from(conf) + def enabled_from(self, conf): + unit_file = strE(conf.filename()) + if self.is_sysv_file(unit_file): + state = self.is_enabled_sysv(unit_file) + if state: + return "enabled" + return "disabled" + return self.get_enabled_from(conf) + def get_enabled_from(self, conf): + if conf.masked: + return "masked" + wanted = self.wanted_from(conf) + target = wanted or self.get_default_target() + for folder in self.enablefolders(target): + if self._root: + folder = os_path(self._root, folder) + target = os.path.join(folder, conf.name()) + if os.path.isfile(target): + return "enabled" + if not wanted: + return "static" + return "disabled" + def mask_modules(self, *modules): + """ [UNIT]... -- mask non-startable units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.mask_units(units) and found_all + def mask_units(self, units): + self.wait_system() + done = True + for unit in units: + if not self.mask_unit(unit): + done = False + return done + def mask_unit(self, unit): + unit_file = self.unit_file(unit) + if not unit_file: + logg.error("Unit %s not found.", unit) + return False + if self.is_sysv_file(unit_file): + logg.error("Initscript %s can not be masked", unit) + return False + conf = self.get_unit_conf(unit) + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + folder = self.mask_folder() + if self._root: + folder = os_path(self._root, folder) + if not os.path.isdir(folder): + os.makedirs(folder) + target = os.path.join(folder, os.path.basename(unit_file)) + dev_null = _dev_null + if True: + _f = self._force and "-f" or "" + logg.debug("ln -s {_f} {dev_null} '{target}'".format(**locals())) + if self._force and os.path.islink(target): + os.remove(target) + if not os.path.exists(target): + os.symlink(dev_null, target) + logg.info("Created symlink {target} -> {dev_null}".format(**locals())) + return True + elif os.path.islink(target): + logg.debug("mask symlink does already exist: %s", target) + return True + else: + logg.error("mask target does already exist: %s", target) + return False + def mask_folder(self): + for folder in self.mask_folders(): + if folder: return folder + raise Exception("did not find any systemd/system folder") + def mask_folders(self): + if self.user_mode(): + for folder in self.user_folders(): + yield folder + if True: + for folder in self.system_folders(): + yield folder + def unmask_modules(self, *modules): + """ [UNIT]... -- unmask non-startable units """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s not found.", unit_of(module)) + self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.unmask_units(units) and found_all + def unmask_units(self, units): + self.wait_system() + done = True + for unit in units: + if not self.unmask_unit(unit): + done = False + return done + def unmask_unit(self, unit): + unit_file = self.unit_file(unit) + if not unit_file: + logg.error("Unit %s not found.", unit) + return False + if self.is_sysv_file(unit_file): + logg.error("Initscript %s can not be un/masked", unit) + return False + conf = self.get_unit_conf(unit) + if self.not_user_conf(conf): + logg.error("Unit %s not for --user mode", unit) + return False + folder = self.mask_folder() + if self._root: + folder = os_path(self._root, folder) + target = os.path.join(folder, os.path.basename(unit_file)) + if True: + _f = self._force and "-f" or "" + logg.info("rm {_f} '{target}'".format(**locals())) + if os.path.islink(target): + os.remove(target) + return True + elif not os.path.exists(target): + logg.debug("Symlink did not exist anymore: %s", target) + return True + else: + logg.warning("target is not a symlink: %s", target) + return True + def list_dependencies_modules(self, *modules): + """ [UNIT]... show the dependency tree" + """ + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s could not be found.", unit_of(module)) + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.list_dependencies_units(units) # and found_all + def list_dependencies_units(self, units): + if self._now: + return self.list_start_dependencies_units(units) + result = [] + for unit in units: + if result: + result += [ "", "" ] + result += self.list_dependencies_unit(unit) + return result + def list_dependencies_unit(self, unit): + result = [] + for line in self.list_dependencies(unit, ""): + result += [ line ] + return result + def list_dependencies(self, unit, indent = None, mark = None, loop = []): + mapping = {} + mapping["Requires"] = "required to start" + mapping["Wants"] = "wanted to start" + mapping["Requisite"] = "required started" + mapping["Bindsto"] = "binds to start" + mapping["PartOf"] = "part of started" + mapping[".requires"] = ".required to start" + mapping[".wants"] = ".wanted to start" + mapping["PropagateReloadTo"] = "(to be reloaded as well)" + mapping["Conflicts"] = "(to be stopped on conflict)" + restrict = ["Requires", "Requisite", "ConsistsOf", "Wants", + "BindsTo", ".requires", ".wants"] + indent = indent or "" + mark = mark or "" + deps = self.get_dependencies_unit(unit) + conf = self.get_unit_conf(unit) + if not conf.loaded(): + if not self._show_all: + return + yield "%s(%s): %s" % (indent, unit, mark) + else: + yield "%s%s: %s" % (indent, unit, mark) + for stop_recursion in [ "Conflict", "conflict", "reloaded", "Propagate" ]: + if stop_recursion in mark: + return + for dep in deps: + if dep in loop: + logg.debug("detected loop at %s", dep) + continue + new_loop = loop + list(deps.keys()) + new_indent = indent + "| " + new_mark = deps[dep] + if not self._show_all: + if new_mark not in restrict: + continue + if new_mark in mapping: + new_mark = mapping[new_mark] + restrict = ["Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf", + ".requires", ".wants"] + for line in self.list_dependencies(dep, new_indent, new_mark, new_loop): + yield line + def get_dependencies_unit(self, unit, styles = None): + styles = styles or [ "Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf", + ".requires", ".wants", "PropagateReloadTo", "Conflicts", ] + conf = self.get_unit_conf(unit) + deps = {} + for style in styles: + if style.startswith("."): + for folder in self.sysd_folders(): + if not folder: + continue + require_path = os.path.join(folder, unit + style) + if self._root: + require_path = os_path(self._root, require_path) + if os.path.isdir(require_path): + for required in os.listdir(require_path): + if required not in deps: + deps[required] = style + else: + for requirelist in conf.getlist("Unit", style, []): + for required in requirelist.strip().split(" "): + deps[required.strip()] = style + return deps + def get_required_dependencies(self, unit, styles = None): + styles = styles or [ "Requires", "Wants", "Requisite", "BindsTo", + ".requires", ".wants" ] + return self.get_dependencies_unit(unit, styles) + def get_start_dependencies(self, unit, styles = None): # pragma: no cover + """ the list of services to be started as well / TODO: unused """ + styles = styles or ["Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf", + ".requires", ".wants"] + deps = {} + unit_deps = self.get_dependencies_unit(unit) + for dep_unit, dep_style in unit_deps.items(): + if dep_style in styles: + if dep_unit in deps: + if dep_style not in deps[dep_unit]: + deps[dep_unit].append( dep_style) + else: + deps[dep_unit] = [ dep_style ] + next_deps = self.get_start_dependencies(dep_unit) + for dep, styles in next_deps.items(): + for style in styles: + if dep in deps: + if style not in deps[dep]: + deps[dep].append(style) + else: + deps[dep] = [ style ] + return deps + def list_start_dependencies_units(self, units): + unit_order = [] + deps = {} + for unit in units: + unit_order.append(unit) + # unit_deps = self.get_start_dependencies(unit) # TODO + unit_deps = self.get_dependencies_unit(unit) + for dep_unit, styles in unit_deps.items(): + dep_styles = to_list(styles) + for dep_style in dep_styles: + if dep_unit in deps: + if dep_style not in deps[dep_unit]: + deps[dep_unit].append( dep_style) + else: + deps[dep_unit] = [ dep_style ] + deps_conf = [] + for dep in deps: + if dep in unit_order: + continue + conf = self.get_unit_conf(dep) + if conf.loaded(): + deps_conf.append(conf) + for unit in unit_order: + deps[unit] = [ "Requested" ] + conf = self.get_unit_conf(unit) + if conf.loaded(): + deps_conf.append(conf) + result = [] + sortlist = conf_sortedAfter(deps_conf, cmp=compareAfter) + for item in sortlist: + line = (item.name(), "(%s)" % (" ".join(deps[item.name()]))) + result.append(line) + return result + def sortedAfter(self, unitlist): + """ get correct start order for the unit list (ignoring masked units) """ + conflist = [ self.get_unit_conf(unit) for unit in unitlist ] + if True: + conflist = [] + for unit in unitlist: + conf = self.get_unit_conf(unit) + if conf.masked: + logg.debug("ignoring masked unit %s", unit) + continue + conflist.append(conf) + sortlist = conf_sortedAfter(conflist) + return [ item.name() for item in sortlist ] + def sortedBefore(self, unitlist): + """ get correct start order for the unit list (ignoring masked units) """ + conflist = [ self.get_unit_conf(unit) for unit in unitlist ] + if True: + conflist = [] + for unit in unitlist: + conf = self.get_unit_conf(unit) + if conf.masked: + logg.debug("ignoring masked unit %s", unit) + continue + conflist.append(conf) + sortlist = conf_sortedAfter(reversed(conflist)) + return [ item.name() for item in reversed(sortlist) ] + def system_daemon_reload(self): + """ reload does will only check the service files here. + The returncode will tell the number of warnings, + and it is over 100 if it can not continue even + for the relaxed systemctl.py style of execution. """ + errors = 0 + for unit in self.match_units(): + try: + conf = self.get_unit_conf(unit) + except Exception as e: + logg.error("%s: can not read unit file %s\n\t%s", + unit, strQ(conf.filename()), e) + continue + errors += self.syntax_check(conf) + if errors: + logg.warning(" (%s) found %s problems", errors, errors % 100) + return True # errors + def syntax_check(self, conf): + filename = conf.filename() + if filename and filename.endswith(".service"): + return self.syntax_check_service(conf) + return 0 + def syntax_check_service(self, conf): + unit = conf.name() + if not conf.data.has_section("Service"): + logg.error(" %s: a .service file without [Service] section", unit) + return 101 + errors = 0 + haveType = conf.get("Service", "Type", "simple") + haveExecStart = conf.getlist("Service", "ExecStart", []) + haveExecStop = conf.getlist("Service", "ExecStop", []) + haveExecReload = conf.getlist("Service", "ExecReload", []) + usedExecStart = [] + usedExecStop = [] + usedExecReload = [] + if haveType not in [ "simple", "forking", "notify", "oneshot", "dbus", "idle"]: + logg.error(" %s: Failed to parse service type, ignoring: %s", unit, haveType) + errors += 100 + for line in haveExecStart: + if not line.startswith("/") and not line.startswith("-/"): + logg.error(" %s: Executable path is not absolute, ignoring: %s", unit, line.strip()) + errors += 1 + usedExecStart.append(line) + for line in haveExecStop: + if not line.startswith("/") and not line.startswith("-/"): + logg.error(" %s: Executable path is not absolute, ignoring: %s", unit, line.strip()) + errors += 1 + usedExecStop.append(line) + for line in haveExecReload: + if not line.startswith("/") and not line.startswith("-/"): + logg.error(" %s: Executable path is not absolute, ignoring: %s", unit, line.strip()) + errors += 1 + usedExecReload.append(line) + if haveType in ["simple", "notify", "forking", "idle"]: + if not usedExecStart and not usedExecStop: + logg.error(" %s: Service lacks both ExecStart and ExecStop= setting. Refusing.", unit) + errors += 101 + elif not usedExecStart and haveType != "oneshot": + logg.error(" %s: Service has no ExecStart= setting, which is only allowed for Type=oneshot services. Refusing.", unit) + errors += 101 + if len(usedExecStart) > 1 and haveType != "oneshot": + logg.error(" %s: there may be only one ExecStart statement (unless for 'oneshot' services)." + + "\n\t\t\tYou can use ExecStartPre / ExecStartPost to add additional commands.", unit) + errors += 1 + if len(usedExecStop) > 1 and haveType != "oneshot": + logg.info(" %s: there should be only one ExecStop statement (unless for 'oneshot' services)." + + "\n\t\t\tYou can use ExecStopPost to add additional commands (also executed on failed Start)", unit) + if len(usedExecReload) > 1: + logg.info(" %s: there should be only one ExecReload statement." + + "\n\t\t\tUse ' ; ' for multiple commands (ExecReloadPost or ExedReloadPre do not exist)", unit) + if len(usedExecReload) > 0 and "/bin/kill " in usedExecReload[0]: + logg.warning(" %s: the use of /bin/kill is not recommended for ExecReload as it is asychronous." + + "\n\t\t\tThat means all the dependencies will perform the reload simultanously / out of order.", unit) + if conf.getlist("Service", "ExecRestart", []): #pragma: no cover + logg.error(" %s: there no such thing as an ExecRestart (ignored)", unit) + if conf.getlist("Service", "ExecRestartPre", []): #pragma: no cover + logg.error(" %s: there no such thing as an ExecRestartPre (ignored)", unit) + if conf.getlist("Service", "ExecRestartPost", []): #pragma: no cover + logg.error(" %s: there no such thing as an ExecRestartPost (ignored)", unit) + if conf.getlist("Service", "ExecReloadPre", []): #pragma: no cover + logg.error(" %s: there no such thing as an ExecReloadPre (ignored)", unit) + if conf.getlist("Service", "ExecReloadPost", []): #pragma: no cover + logg.error(" %s: there no such thing as an ExecReloadPost (ignored)", unit) + if conf.getlist("Service", "ExecStopPre", []): #pragma: no cover + logg.error(" %s: there no such thing as an ExecStopPre (ignored)", unit) + for env_file in conf.getlist("Service", "EnvironmentFile", []): + if env_file.startswith("-"): continue + if not os.path.isfile(os_path(self._root, self.expand_special(env_file, conf))): + logg.error(" %s: Failed to load environment files: %s", unit, env_file) + errors += 101 + return errors + def exec_check_unit(self, conf, env, section = "Service", exectype = ""): + if conf is None: # pragma: no cover (is never null) + return True + if not conf.data.has_section(section): + return True #pragma: no cover + haveType = conf.get(section, "Type", "simple") + if self.is_sysv_file(conf.filename()): + return True # we don't care about that + unit = conf.name() + abspath = 0 + notexists = 0 + badusers = 0 + badgroups = 0 + for execs in [ "ExecStartPre", "ExecStart", "ExecStartPost", "ExecStop", "ExecStopPost", "ExecReload" ]: + if not execs.startswith(exectype): + continue + for cmd in conf.getlist(section, execs, []): + mode, newcmd = self.exec_newcmd(cmd, env, conf) + if not newcmd: + continue + exe = newcmd[0] + if not exe: + continue + if exe[0] != "/": + logg.error(" %s: Exec is not an absolute path: %s=%s", unit, execs, cmd) + abspath += 1 + if not os.path.isfile(exe): + logg.error(" %s: Exec command does not exist: (%s) %s", unit, execs, exe) + if mode.check: + notexists += 1 + newexe1 = os.path.join("/usr/bin", exe) + newexe2 = os.path.join("/bin", exe) + if os.path.exists(newexe1): + logg.error(" %s: but this does exist: %s %s", unit, " " * len(execs), newexe1) + elif os.path.exists(newexe2): + logg.error(" %s: but this does exist: %s %s", unit, " " * len(execs), newexe2) + users = [ conf.get(section, "User", ""), conf.get(section, "SocketUser", "") ] + groups = [ conf.get(section, "Group", ""), conf.get(section, "SocketGroup", "") ] + conf.getlist(section, "SupplementaryGroups") + for user in users: + if user: + try: pwd.getpwnam(self.expand_special(user, conf)) + except Exception as e: + logg.error(" %s: User does not exist: %s (%s)", unit, user, getattr(e, "__doc__", "")) + badusers += 1 + for group in groups: + if group: + try: grp.getgrnam(self.expand_special(group, conf)) + except Exception as e: + logg.error(" %s: Group does not exist: %s (%s)", unit, group, getattr(e, "__doc__", "")) + badgroups += 1 + tmpproblems = 0 + for setting in ("RootDirectory", "RootImage", "BindPaths", "BindReadOnlyPaths", + "ReadWritePaths", "ReadOnlyPaths", "TemporaryFileSystem"): + setting_value = conf.get(section, setting, "") + if setting_value: + logg.info("%s: %s private directory remounts ignored: %s=%s", unit, section, setting, setting_value) + tmpproblems += 1 + for setting in ("PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "DynamicUser", + "ProtectSystem", "ProjectHome", "ProtectHostname", "PrivateMounts", "MountAPIVFS"): + setting_yes = conf.getbool(section, setting, "no") + if setting_yes: + logg.info("%s: %s private directory option is ignored: %s=yes", unit, section, setting) + tmpproblems += 1 + if not abspath and not notexists and not badusers and not badgroups: + return True + if True: + filename = strE(conf.filename()) + if len(filename) > 44: filename = o44(filename) + logg.error(" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + if abspath: + logg.error(" The SystemD ExecXY commands must always be absolute paths by definition.") + time.sleep(1) + if notexists: + logg.error(" Oops, %s executable paths were not found in the current environment. Refusing.", notexists) + time.sleep(1) + if badusers or badgroups: + logg.error(" Oops, %s user names and %s group names were not found. Refusing.", badusers, badgroups) + time.sleep(1) + if tmpproblems: + logg.info(" Note, %s private directory settings are ignored. The application should not depend on it.", tmpproblems) + time.sleep(1) + logg.error(" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + return False + def show_modules(self, *modules): + """ [PATTERN]... -- Show properties of one or more units + Show properties of one or more units (or the manager itself). + If no argument is specified, properties of the manager will be + shown. If a unit name is specified, properties of the unit is + shown. By default, empty properties are suppressed. Use --all to + show those too. To select specific properties to show, use + --property=. This command is intended to be used whenever + computer-parsable output is required. Use status if you are looking + for formatted human-readable output. + / + NOTE: only a subset of properties is implemented """ + notfound = [] + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s could not be found.", unit_of(module)) + units += [ module ] + # self.error |= NOT_FOUND + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + return self.show_units(units) + notfound # and found_all + def show_units(self, units): + logg.debug("show --property=%s", self._unit_property) + result = [] + for unit in units: + if result: result += [ "" ] + for var, value in self.show_unit_items(unit): + if self._unit_property: + if self._unit_property != var: + continue + else: + if not value and not self._show_all: + continue + result += [ "%s=%s" % (var, value) ] + return result + def show_unit_items(self, unit): + """ [UNIT]... -- show properties of a unit. + """ + logg.info("try read unit %s", unit) + conf = self.get_unit_conf(unit) + for entry in self.each_unit_items(unit, conf): + yield entry + def each_unit_items(self, unit, conf): + loaded = conf.loaded() + if not loaded: + loaded = "not-loaded" + if "NOT-FOUND" in self.get_description_from(conf): + loaded = "not-found" + names = { unit: 1, conf.name(): 1 } + yield "Id", conf.name() + yield "Names", " ".join(sorted(names.keys())) + yield "Description", self.get_description_from(conf) # conf.get("Unit", "Description") + yield "PIDFile", self.get_pid_file(conf) # not self.pid_file_from w/o default location + yield "PIDFilePath", self.pid_file_from(conf) + yield "MainPID", strE(self.active_pid_from(conf)) # status["MainPID"] or PIDFile-read + yield "SubState", self.get_substate_from(conf) or "unknown" # status["SubState"] or notify-result + yield "ActiveState", self.get_active_from(conf) or "unknown" # status["ActiveState"] + yield "LoadState", loaded + yield "UnitFileState", self.enabled_from(conf) + yield "StatusFile", self.get_StatusFile(conf) + yield "StatusFilePath", self.get_status_file_from(conf) + yield "JournalFile", self.get_journal_log(conf) + yield "JournalFilePath", self.get_journal_log_from(conf) + yield "NotifySocket", self.get_notify_socket_from(conf) + yield "User", self.get_User(conf) or "" + yield "Group", self.get_Group(conf) or "" + yield "SupplementaryGroups", " ".join(self.get_SupplementaryGroups(conf)) + yield "TimeoutStartUSec", seconds_to_time(self.get_TimeoutStartSec(conf)) + yield "TimeoutStopUSec", seconds_to_time(self.get_TimeoutStopSec(conf)) + yield "NeedDaemonReload", "no" + yield "SendSIGKILL", strYes(self.get_SendSIGKILL(conf)) + yield "SendSIGHUP", strYes(self.get_SendSIGHUP(conf)) + yield "KillMode", strE(self.get_KillMode(conf)) + yield "KillSignal", strE(self.get_KillSignal(conf)) + yield "StartLimitBurst", strE(self.get_StartLimitBurst(conf)) + yield "StartLimitIntervalSec", seconds_to_time(self.get_StartLimitIntervalSec(conf)) + yield "RestartSec", seconds_to_time(self.get_RestartSec(conf)) + yield "RemainAfterExit", strYes(self.get_RemainAfterExit(conf)) + yield "WorkingDirectory", strE(self.get_WorkingDirectory(conf)) + env_parts = [] + for env_part in conf.getlist("Service", "Environment", []): + env_parts.append(self.expand_special(env_part, conf)) + if env_parts: + yield "Environment", " ".join(env_parts) + env_files = [] + for env_file in conf.getlist("Service", "EnvironmentFile", []): + env_files.append(self.expand_special(env_file, conf)) + if env_files: + yield "EnvironmentFile", " ".join(env_files) + def get_SendSIGKILL(self, conf): + return conf.getbool("Service", "SendSIGKILL", "yes") + def get_SendSIGHUP(self, conf): + return conf.getbool("Service", "SendSIGHUP", "no") + def get_KillMode(self, conf): + return conf.get("Service", "KillMode", "control-group") + def get_KillSignal(self, conf): + return conf.get("Service", "KillSignal", "SIGTERM") + # + igno_centos = [ "netconsole", "network" ] + igno_opensuse = [ "raw", "pppoe", "*.local", "boot.*", "rpmconf*", "postfix*" ] + igno_ubuntu = [ "mount*", "umount*", "ondemand", "*.local" ] + igno_always = [ "network*", "dbus*", "systemd-*", "kdump*" ] + igno_always += [ "purge-kernels.service", "after-local.service", "dm-event.*" ] # as on opensuse + igno_targets = [ "remote-fs.target" ] + def _ignored_unit(self, unit, ignore_list): + for ignore in ignore_list: + if fnmatch.fnmatchcase(unit, ignore): + return True # ignore + if fnmatch.fnmatchcase(unit, ignore+".service"): + return True # ignore + return False + def default_services_modules(self, *modules): + """ show the default services + This is used internally to know the list of service to be started in the 'get-default' + target runlevel when the container is started through default initialisation. It will + ignore a number of services - use '--all' to show a longer list of services and + use '--all --force' if not even a minimal filter shall be used. + """ + results = [] + targets = modules or [ self.get_default_target() ] + for target in targets: + units = self.target_default_services(target) + logg.debug(" %s # %s", " ".join(units), target) + for unit in units: + if unit not in results: + results.append(unit) + return results + def target_default_services(self, target = None, sysv = "S"): + """ get the default services for a target - this will ignore a number of services, + use '--all' and --force' to get more services. + """ + igno = self.igno_centos + self.igno_opensuse + self.igno_ubuntu + self.igno_always + if self._show_all: + igno = self.igno_always + if self._force: + igno = [] + logg.debug("ignored services filter for default.target:\n\t%s", igno) + default_target = target or self.get_default_target() + return self.enabled_target_services(default_target, sysv, igno) + def enabled_target_services(self, target, sysv = "S", igno = []): + units = [] + if self.user_mode(): + targetlist = self.get_target_list(target) + logg.debug("check for %s user services : %s", target, targetlist) + for targets in targetlist: + for unit in self.enabled_target_user_local_units(targets, ".target", igno): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.required_target_units(targets, ".socket", igno): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.enabled_target_user_local_units(targets, ".socket", igno): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.required_target_units(targets, ".service", igno): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.enabled_target_user_local_units(targets, ".service", igno): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.enabled_target_user_system_units(targets, ".service", igno): + if unit not in units: + units.append(unit) + else: + targetlist = self.get_target_list(target) + logg.debug("check for %s system services: %s", target, targetlist) + for targets in targetlist: + for unit in self.enabled_target_configured_system_units(targets, ".target", igno + self.igno_targets): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.required_target_units(targets, ".socket", igno): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.enabled_target_installed_system_units(targets, ".socket", igno): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.required_target_units(targets, ".service", igno): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.enabled_target_installed_system_units(targets, ".service", igno): + if unit not in units: + units.append(unit) + for targets in targetlist: + for unit in self.enabled_target_sysv_units(targets, sysv, igno): + if unit not in units: + units.append(unit) + return units + def enabled_target_user_local_units(self, target, unit_kind = ".service", igno = []): + units = [] + for basefolder in self.user_folders(): + if not basefolder: + continue + folder = self.default_enablefolder(target, basefolder) + if self._root: + folder = os_path(self._root, folder) + if os.path.isdir(folder): + for unit in sorted(os.listdir(folder)): + path = os.path.join(folder, unit) + if os.path.isdir(path): continue + if self._ignored_unit(unit, igno): + continue # ignore + if unit.endswith(unit_kind): + units.append(unit) + return units + def enabled_target_user_system_units(self, target, unit_kind = ".service", igno = []): + units = [] + for basefolder in self.system_folders(): + if not basefolder: + continue + folder = self.default_enablefolder(target, basefolder) + if self._root: + folder = os_path(self._root, folder) + if os.path.isdir(folder): + for unit in sorted(os.listdir(folder)): + path = os.path.join(folder, unit) + if os.path.isdir(path): continue + if self._ignored_unit(unit, igno): + continue # ignore + if unit.endswith(unit_kind): + conf = self.load_unit_conf(unit) + if conf is None: + pass + elif self.not_user_conf(conf): + pass + else: + units.append(unit) + return units + def enabled_target_installed_system_units(self, target, unit_type = ".service", igno = []): + units = [] + for basefolder in self.system_folders(): + if not basefolder: + continue + folder = self.default_enablefolder(target, basefolder) + if self._root: + folder = os_path(self._root, folder) + if os.path.isdir(folder): + for unit in sorted(os.listdir(folder)): + path = os.path.join(folder, unit) + if os.path.isdir(path): continue + if self._ignored_unit(unit, igno): + continue # ignore + if unit.endswith(unit_type): + units.append(unit) + return units + def enabled_target_configured_system_units(self, target, unit_type = ".service", igno = []): + units = [] + if True: + folder = self.default_enablefolder(target) + if self._root: + folder = os_path(self._root, folder) + if os.path.isdir(folder): + for unit in sorted(os.listdir(folder)): + path = os.path.join(folder, unit) + if os.path.isdir(path): continue + if self._ignored_unit(unit, igno): + continue # ignore + if unit.endswith(unit_type): + units.append(unit) + return units + def enabled_target_sysv_units(self, target, sysv = "S", igno = []): + units = [] + folders = [] + if target in [ "multi-user.target", DefaultUnit ]: + folders += [ self.rc3_root_folder() ] + if target in [ "graphical.target" ]: + folders += [ self.rc5_root_folder() ] + for folder in folders: + if not os.path.isdir(folder): + logg.warning("non-existant %s", folder) + continue + for unit in sorted(os.listdir(folder)): + path = os.path.join(folder, unit) + if os.path.isdir(path): continue + m = re.match(sysv+r"\d\d(.*)", unit) + if m: + service = m.group(1) + unit = service + ".service" + if self._ignored_unit(unit, igno): + continue # ignore + units.append(unit) + return units + def required_target_units(self, target, unit_type, igno): + units = [] + deps = self.get_required_dependencies(target) + for unit in sorted(deps): + if self._ignored_unit(unit, igno): + continue # ignore + if unit.endswith(unit_type): + if unit not in units: + units.append(unit) + return units + def get_target_conf(self, module): # -> conf (conf | default-conf) + """ accept that a unit does not exist + and return a unit conf that says 'not-loaded' """ + conf = self.load_unit_conf(module) + if conf is not None: + return conf + target_conf = self.default_unit_conf(module) + if module in target_requires: + target_conf.set("Unit", "Requires", target_requires[module]) + return target_conf + def get_target_list(self, module): + """ the Requires= in target units are only accepted if known """ + target = module + if "." not in target: target += ".target" + targets = [ target ] + conf = self.get_target_conf(module) + requires = conf.get("Unit", "Requires", "") + while requires in target_requires: + targets = [ requires ] + targets + requires = target_requires[requires] + logg.debug("the %s requires %s", module, targets) + return targets + def system_default(self, arg = True): + """ start units for default system level + This will go through the enabled services in the default 'multi-user.target'. + However some services are ignored as being known to be installation garbage + from unintended services. Use '--all' so start all of the installed services + and with '--all --force' even those services that are otherwise wrong. + /// SPECIAL: with --now or --init the init-loop is run and afterwards + a system_halt is performed with the enabled services to be stopped.""" + self.sysinit_status(SubState = "initializing") + logg.info("system default requested - %s", arg) + init = self._now or self._init + return self.start_system_default(init = init) + def start_system_default(self, init = False): + """ detect the default.target services and start them. + When --init is given then the init-loop is run and + the services are stopped again by 'systemctl halt'.""" + target = self.get_default_target() + services = self.start_target_system(target, init) + logg.info("%s system is up", target) + if init: + logg.info("init-loop start") + sig = self.init_loop_until_stop(services) + logg.info("init-loop %s", sig) + self.stop_system_default() + return not not services + def start_target_system(self, target, init = False): + services = self.target_default_services(target, "S") + self.sysinit_status(SubState = "starting") + self.start_units(services) + return services + def do_start_target_from(self, conf): + target = conf.name() + # services = self.start_target_system(target) + services = self.target_default_services(target, "S") + units = [service for service in services if not self.is_running_unit(service)] + logg.debug("start %s is starting %s from %s", target, units, services) + return self.start_units(units) + def stop_system_default(self): + """ detect the default.target services and stop them. + This is commonly run through 'systemctl halt' or + at the end of a 'systemctl --init default' loop.""" + target = self.get_default_target() + services = self.stop_target_system(target) + logg.info("%s system is down", target) + return not not services + def stop_target_system(self, target): + services = self.target_default_services(target, "K") + self.sysinit_status(SubState = "stopping") + self.stop_units(services) + return services + def do_stop_target_from(self, conf): + target = conf.name() + # services = self.stop_target_system(target) + services = self.target_default_services(target, "K") + units = [service for service in services if self.is_running_unit(service)] + logg.debug("stop %s is stopping %s from %s", target, units, services) + return self.stop_units(units) + def do_reload_target_from(self, conf): + target = conf.name() + return self.reload_target_system(target) + def reload_target_system(self, target): + services = self.target_default_services(target, "S") + units = [service for service in services if self.is_running_unit(service)] + return self.reload_units(units) + def system_halt(self, arg = True): + """ stop units from default system level """ + logg.info("system halt requested - %s", arg) + done = self.stop_system_default() + try: + os.kill(1, signal.SIGQUIT) # exit init-loop on no_more_procs + except Exception as e: + logg.warning("SIGQUIT to init-loop on PID-1: %s", e) + return done + def system_get_default(self): + """ get current default run-level""" + return self.get_default_target() + def get_targets_folder(self): + return os_path(self._root, self.mask_folder()) + def get_default_target_file(self): + targets_folder = self.get_targets_folder() + return os.path.join(targets_folder, DefaultUnit) + def get_default_target(self, default_target = None): + """ get current default run-level""" + current = default_target or self._default_target + default_target_file = self.get_default_target_file() + if os.path.islink(default_target_file): + current = os.path.basename(os.readlink(default_target_file)) + return current + def set_default_modules(self, *modules): + """ set current default run-level""" + if not modules: + logg.debug(".. no runlevel given") + self.error |= NOT_OK + return "Too few arguments" + current = self.get_default_target() + default_target_file = self.get_default_target_file() + msg = "" + for module in modules: + if module == current: + continue + targetfile = None + for targetname, targetpath in self.each_target_file(): + if targetname == module: + targetfile = targetpath + if not targetfile: + self.error |= NOT_OK | NOT_ACTIVE # 3 + msg = "No such runlevel %s" % (module) + continue + # + if os.path.islink(default_target_file): + os.unlink(default_target_file) + if not os.path.isdir(os.path.dirname(default_target_file)): + os.makedirs(os.path.dirname(default_target_file)) + os.symlink(targetfile, default_target_file) + msg = "Created symlink from %s -> %s" % (default_target_file, targetfile) + logg.debug("%s", msg) + return msg + def init_modules(self, *modules): + """ [UNIT*] -- init loop: '--init default' or '--init start UNIT*' + The systemctl init service will start the enabled 'default' services, + and then wait for any zombies to be reaped. When a SIGINT is received + then a clean shutdown of the enabled services is ensured. A Control-C in + in interactive mode will also run 'stop' on all the enabled services. // + When a UNIT name is given then only that one is started instead of the + services in the 'default.target'. Using 'init UNIT' is better than + '--init start UNIT' because the UNIT is also stopped cleanly even when + it was never enabled in the system. + /// SPECIAL: when using --now then only the init-loop is started, + with the reap-zombies function and waiting for an interrupt. + (and no unit is started/stoppped wether given or not). + """ + if self._now: + result = self.init_loop_until_stop([]) + return not not result + if not modules: + # like 'systemctl --init default' + if self._now or self._show_all: + logg.debug("init default --now --all => no_more_procs") + self.doExitWhenNoMoreProcs = True + return self.start_system_default(init = True) + # + # otherwise quit when all the init-services have died + self.doExitWhenNoMoreServices = True + if self._now or self._show_all: + logg.debug("init services --now --all => no_more_procs") + self.doExitWhenNoMoreProcs = True + found_all = True + units = [] + for module in modules: + matched = self.match_units(to_list(module)) + if not matched: + logg.error("Unit %s could not be found.", unit_of(module)) + found_all = False + continue + for unit in matched: + if unit not in units: + units += [ unit ] + logg.info("init %s -> start %s", ",".join(modules), ",".join(units)) + done = self.start_units(units, init = True) + logg.info("-- init is done") + return done # and found_all + def start_log_files(self, units): + self._log_file = {} + self._log_hold = {} + for unit in units: + conf = self.load_unit_conf(unit) + if not conf: continue + if self.skip_journal_log(conf): continue + log_path = self.get_journal_log_from(conf) + try: + opened = os.open(log_path, os.O_RDONLY | os.O_NONBLOCK) + self._log_file[unit] = opened + self._log_hold[unit] = b"" + except Exception as e: + logg.error("can not open %s log: %s\n\t%s", unit, log_path, e) + def read_log_files(self, units): + BUFSIZE=8192 + for unit in units: + if unit in self._log_file: + new_text = b"" + while True: + buf = os.read(self._log_file[unit], BUFSIZE) + if not buf: break + new_text += buf + continue + text = self._log_hold[unit] + new_text + if not text: continue + lines = text.split(b"\n") + if not text.endswith(b"\n"): + self._log_hold[unit] = lines[-1] + lines = lines[:-1] + for line in lines: + prefix = unit.encode("utf-8") + content = prefix+b": "+line+b"\n" + os.write(1, content) + try: os.fsync(1) + except: pass + def stop_log_files(self, units): + for unit in units: + try: + if unit in self._log_file: + if self._log_file[unit]: + os.close(self._log_file[unit]) + except Exception as e: + logg.error("can not close log: %s\n\t%s", unit, e) + self._log_file = {} + self._log_hold = {} + + def get_StartLimitBurst(self, conf): + defaults = DefaultStartLimitBurst + return to_int(conf.get("Service", "StartLimitBurst", strE(defaults)), defaults) # 5 + def get_StartLimitIntervalSec(self, conf, maximum = None): + maximum = maximum or 999 + defaults = DefaultStartLimitIntervalSec + interval = conf.get("Service", "StartLimitIntervalSec", strE(defaults)) # 10s + return time_to_seconds(interval, maximum) + def get_RestartSec(self, conf, maximum = None): + maximum = maximum or DefaultStartLimitIntervalSec + delay = conf.get("Service", "RestartSec", strE(DefaultRestartSec)) + return time_to_seconds(delay, maximum) + def restart_failed_units(self, units, maximum = None): + """ This function will retart failed units. + / + NOTE that with standard settings the LimitBurst implementation has no effect. If + the InitLoopSleep is ticking at the Default of 5sec and the LimitBurst Default + is 5x within a Default 10secs time frame then within those 10sec only 2 loop + rounds have come here checking for possible restarts. You can directly shorten + the interval ('-c InitLoopSleep=1') or have it indirectly shorter from the + service descriptor's RestartSec ("RestartSec=2s"). + """ + global InitLoopSleep + me = os.getpid() + maximum = maximum or DefaultStartLimitIntervalSec + restartDelay = MinimumYield + for unit in units: + now = time.time() + try: + conf = self.load_unit_conf(unit) + if not conf: continue + restartPolicy = conf.get("Service", "Restart", "no") + if restartPolicy in ["no", "on-success"]: + logg.debug("[%s] [%s] Current NoCheck (Restart=%s)", me, unit, restartPolicy) + continue + restartSec = self.get_RestartSec(conf) + if restartSec == 0: + if InitLoopSleep > 1: + logg.warning("[%s] set InitLoopSleep from %ss to 1 (caused by RestartSec=0!)", + unit, InitLoopSleep) + InitLoopSleep = 1 + elif restartSec > 0.9 and restartSec < InitLoopSleep: + restartSleep = int(restartSec + 0.2) + if restartSleep < InitLoopSleep: + logg.warning("[%s] set InitLoopSleep from %ss to %s (caused by RestartSec=%.3fs)", + unit, InitLoopSleep, restartSleep, restartSec) + InitLoopSleep = restartSleep + isUnitState = self.get_active_from(conf) + isUnitFailed = isUnitState in ["failed"] + logg.debug("[%s] [%s] Current Status: %s (%s)", me, unit, isUnitState, isUnitFailed) + if not isUnitFailed: + if unit in self._restart_failed_units: + del self._restart_failed_units[unit] + continue + limitBurst = self.get_StartLimitBurst(conf) + limitSecs = self.get_StartLimitIntervalSec(conf) + if limitBurst > 1 and limitSecs >= 1: + try: + if unit not in self._restarted_unit: + self._restarted_unit[unit] = [] + # we want to register restarts from now on + restarted = self._restarted_unit[unit] + logg.debug("[%s] [%s] Current limitSecs=%ss limitBurst=%sx (restarted %sx)", + me, unit, limitSecs, limitBurst, len(restarted)) + oldest = 0. + interval = 0. + if len(restarted) >= limitBurst: + logg.debug("[%s] [%s] restarted %s", + me, unit, [ "%.3fs" % (t - now) for t in restarted ]) + while len(restarted): + oldest = restarted[0] + interval = time.time() - oldest + if interval > limitSecs: + restarted = restarted[1:] + continue + break + self._restarted_unit[unit] = restarted + logg.debug("[%s] [%s] ratelimit %s", + me, unit, [ "%.3fs" % (t - now) for t in restarted ]) + # all values in restarted have a time below limitSecs + if len(restarted) >= limitBurst: + logg.info("[%s] [%s] Blocking Restart - oldest %s is %s ago (allowed %s)", + me, unit, oldest, interval, limitSecs) + self.write_status_from(conf, AS="error") + unit = "" # dropped out + continue + except Exception as e: + logg.error("[%s] burst exception %s", unit, e) + if unit: # not dropped out + if unit not in self._restart_failed_units: + self._restart_failed_units[unit] = now + restartSec + logg.debug("[%s] [%s] restart scheduled in %+.3fs", + me, unit, (self._restart_failed_units[unit] - now)) + except Exception as e: + logg.error("[%s] [%s] An error ocurred while restart checking: %s", me, unit, e) + if not self._restart_failed_units: + self.error |= NOT_OK + return [] + # NOTE: this function is only called from InitLoop when "running" + # let's check if any of the restart_units has its restartSec expired + now = time.time() + restart_done = [] + logg.debug("[%s] Restart checking %s", + me, [ "%+.3fs" % (t - now) for t in self._restart_failed_units.values() ]) + for unit in sorted(self._restart_failed_units): + restartAt = self._restart_failed_units[unit] + if restartAt > now: + continue + restart_done.append(unit) + try: + conf = self.load_unit_conf(unit) + if not conf: continue + isUnitState = self.get_active_from(conf) + isUnitFailed = isUnitState in ["failed"] + logg.debug("[%s] [%s] Restart Status: %s (%s)", me, unit, isUnitState, isUnitFailed) + if isUnitFailed: + logg.debug("[%s] [%s] --- restarting failed unit...", me, unit) + self.restart_unit(unit) + logg.debug("[%s] [%s] --- has been restarted.", me, unit) + if unit in self._restarted_unit: + self._restarted_unit[unit].append(time.time()) + except Exception as e: + logg.error("[%s] [%s] An error ocurred while restarting: %s", me, unit, e) + for unit in restart_done: + if unit in self._restart_failed_units: + del self._restart_failed_units[unit] + logg.debug("[%s] Restart remaining %s", + me, [ "%+.3fs" % (t - now) for t in self._restart_failed_units.values() ]) + return restart_done + + def init_loop_until_stop(self, units): + """ this is the init-loop - it checks for any zombies to be reaped and + waits for an interrupt. When a SIGTERM /SIGINT /Control-C signal + is received then the signal name is returned. Any other signal will + just raise an Exception like one would normally expect. As a special + the 'systemctl halt' emits SIGQUIT which puts it into no_more_procs mode.""" + signal.signal(signal.SIGQUIT, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt("SIGQUIT")) + signal.signal(signal.SIGINT, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt("SIGINT")) + signal.signal(signal.SIGTERM, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt("SIGTERM")) + # + self.start_log_files(units) + logg.debug("start listen") + listen = SystemctlListenThread(self) + logg.debug("starts listen") + listen.start() + logg.debug("started listen") + self.sysinit_status(ActiveState = "active", SubState = "running") + timestamp = time.time() + result = None + while True: + try: + if DEBUG_INITLOOP: # pragma: no cover + logg.debug("DONE InitLoop (sleep %ss)", InitLoopSleep) + sleep_sec = InitLoopSleep - (time.time() - timestamp) + if sleep_sec < MinimumYield: + sleep_sec = MinimumYield + sleeping = sleep_sec + while sleeping > 2: + time.sleep(1) # accept signals atleast every second + sleeping = InitLoopSleep - (time.time() - timestamp) + if sleeping < MinimumYield: + sleeping = MinimumYield + break + time.sleep(sleeping) # remainder waits less that 2 seconds + timestamp = time.time() + self.loop.acquire() + if DEBUG_INITLOOP: # pragma: no cover + logg.debug("NEXT InitLoop (after %ss)", sleep_sec) + self.read_log_files(units) + if DEBUG_INITLOOP: # pragma: no cover + logg.debug("reap zombies - check current processes") + running = self.system_reap_zombies() + if DEBUG_INITLOOP: # pragma: no cover + logg.debug("reap zombies - init-loop found %s running procs", running) + if self.doExitWhenNoMoreServices: + active = False + for unit in units: + conf = self.load_unit_conf(unit) + if not conf: continue + if self.is_active_from(conf): + active = True + if not active: + logg.info("no more services - exit init-loop") + break + if self.doExitWhenNoMoreProcs: + if not running: + logg.info("no more procs - exit init-loop") + break + if RESTART_FAILED_UNITS: + self.restart_failed_units(units) + self.loop.release() + except KeyboardInterrupt as e: + if e.args and e.args[0] == "SIGQUIT": + # the original systemd puts a coredump on that signal. + logg.info("SIGQUIT - switch to no more procs check") + self.doExitWhenNoMoreProcs = True + continue + signal.signal(signal.SIGTERM, signal.SIG_DFL) + signal.signal(signal.SIGINT, signal.SIG_DFL) + logg.info("interrupted - exit init-loop") + result = str(e) or "STOPPED" + break + except Exception as e: + logg.info("interrupted - exception %s", e) + raise + self.sysinit_status(ActiveState = None, SubState = "degraded") + try: self.loop.release() + except: pass + listen.stop() + listen.join(2) + self.read_log_files(units) + self.read_log_files(units) + self.stop_log_files(units) + logg.debug("done - init loop") + return result + def system_reap_zombies(self): + """ check to reap children """ + selfpid = os.getpid() + running = 0 + for pid_entry in os.listdir(_proc_pid_dir): + pid = to_intN(pid_entry) + if pid is None: + continue + if pid == selfpid: + continue + proc_status = _proc_pid_status.format(**locals()) + if os.path.isfile(proc_status): + zombie = False + ppid = -1 + try: + for line in open(proc_status): + m = re.match(r"State:\s*Z.*", line) + if m: zombie = True + m = re.match(r"PPid:\s*(\d+)", line) + if m: ppid = int(m.group(1)) + except IOError as e: + logg.warning("%s : %s", proc_status, e) + continue + if zombie and ppid == os.getpid(): + logg.info("reap zombie %s", pid) + try: os.waitpid(pid, os.WNOHANG) + except OSError as e: + logg.warning("reap zombie %s: %s", e.strerror) + if os.path.isfile(proc_status): + if pid > 1: + running += 1 + return running # except PID 0 and PID 1 + def sysinit_status(self, **status): + conf = self.sysinit_target() + self.write_status_from(conf, **status) + def sysinit_target(self): + if not self._sysinit_target: + self._sysinit_target = self.default_unit_conf(SysInitTarget, "System Initialization") + assert self._sysinit_target is not None + return self._sysinit_target + def is_system_running(self): + conf = self.sysinit_target() + if not self.is_running_unit_from(conf): + time.sleep(MinimumYield) + if not self.is_running_unit_from(conf): + return "offline" + status = self.read_status_from(conf) + return status.get("SubState", "unknown") + def system_is_system_running(self): + state = self.is_system_running() + if state not in [ "running" ]: + self.error |= NOT_OK # 1 + if self._quiet: + return None + return state + def wait_system(self, target = None): + target = target or SysInitTarget + for attempt in xrange(int(SysInitWait)): + state = self.is_system_running() + if "init" in state: + if target in [ SysInitTarget, "basic.target" ]: + logg.info("system not initialized - wait %s", target) + time.sleep(1) + continue + if "start" in state or "stop" in state: + if target in [ "basic.target" ]: + logg.info("system not running - wait %s", target) + time.sleep(1) + continue + if "running" not in state: + logg.info("system is %s", state) + break + def is_running_unit_from(self, conf): + status_file = self.get_status_file_from(conf) + return self.getsize(status_file) > 0 + def is_running_unit(self, unit): + conf = self.get_unit_conf(unit) + return self.is_running_unit_from(conf) + def pidlist_of(self, pid): + if not pid: + return [] + pidlist = [ pid ] + pids = [ pid ] + for depth in xrange(PROC_MAX_DEPTH): + for pid_entry in os.listdir(_proc_pid_dir): + pid = to_intN(pid_entry) + if pid is None: + continue + proc_status = _proc_pid_status.format(**locals()) + if os.path.isfile(proc_status): + try: + for line in open(proc_status): + if line.startswith("PPid:"): + ppid_text = line[len("PPid:"):].strip() + try: ppid = int(ppid_text) + except: continue + if ppid in pidlist and pid not in pids: + pids += [ pid ] + except IOError as e: + logg.warning("%s : %s", proc_status, e) + continue + if len(pids) != len(pidlist): + pidlist = pids[:] + continue + return pids + def echo(self, *targets): + line = " ".join(*targets) + logg.info(" == echo == %s", line) + return line + def killall(self, *targets): + mapping = {} + mapping[":3"] = signal.SIGQUIT + mapping[":QUIT"] = signal.SIGQUIT + mapping[":6"] = signal.SIGABRT + mapping[":ABRT"] = signal.SIGABRT + mapping[":9"] = signal.SIGKILL + mapping[":KILL"] = signal.SIGKILL + sig = signal.SIGTERM + for target in targets: + if target.startswith(":"): + if target in mapping: + sig = mapping[target] + else: # pragma: no cover + logg.error("unsupported %s", target) + continue + for pid_entry in os.listdir(_proc_pid_dir): + pid = to_intN(pid_entry) + if pid: + try: + cmdline = _proc_pid_cmdline.format(**locals()) + cmd = open(cmdline).read().split("\0") + if DEBUG_KILLALL: logg.debug("cmdline %s", cmd) + found = None + cmd_exe = os.path.basename(cmd[0]) + if DEBUG_KILLALL: logg.debug("cmd.exe '%s'", cmd_exe) + if fnmatch.fnmatchcase(cmd_exe, target): found = "exe" + if len(cmd) > 1 and cmd_exe.startswith("python"): + X = 1 + while cmd[X].startswith("-"): X += 1 # atleast '-u' unbuffered + cmd_arg = os.path.basename(cmd[X]) + if DEBUG_KILLALL: logg.debug("cmd.arg '%s'", cmd_arg) + if fnmatch.fnmatchcase(cmd_arg, target): found = "arg" + if cmd_exe.startswith("coverage") or cmd_arg.startswith("coverage"): + x = cmd.index("--") + if x > 0 and x+1 < len(cmd): + cmd_run = os.path.basename(cmd[x+1]) + if DEBUG_KILLALL: logg.debug("cmd.run '%s'", cmd_run) + if fnmatch.fnmatchcase(cmd_run, target): found = "run" + if found: + if DEBUG_KILLALL: logg.debug("%s found %s %s", found, pid, [ c for c in cmd ]) + if pid != os.getpid(): + logg.debug(" kill -%s %s # %s", sig, pid, target) + os.kill(pid, sig) + except Exception as e: + logg.error("kill -%s %s : %s", sig, pid, e) + return True + def force_ipv4(self, *args): + """ only ipv4 localhost in /etc/hosts """ + logg.debug("checking hosts sysconf for '::1 localhost'") + lines = [] + sysconf_hosts = os_path(self._root, _etc_hosts) + for line in open(sysconf_hosts): + if "::1" in line: + newline = re.sub("\\slocalhost\\s", " ", line) + if line != newline: + logg.info("%s: '%s' => '%s'", _etc_hosts, line.rstrip(), newline.rstrip()) + line = newline + lines.append(line) + f = open(sysconf_hosts, "w") + for line in lines: + f.write(line) + f.close() + def force_ipv6(self, *args): + """ only ipv4 localhost in /etc/hosts """ + logg.debug("checking hosts sysconf for '127.0.0.1 localhost'") + lines = [] + sysconf_hosts = os_path(self._root, _etc_hosts) + for line in open(sysconf_hosts): + if "127.0.0.1" in line: + newline = re.sub("\\slocalhost\\s", " ", line) + if line != newline: + logg.info("%s: '%s' => '%s'", _etc_hosts, line.rstrip(), newline.rstrip()) + line = newline + lines.append(line) + f = open(sysconf_hosts, "w") + for line in lines: + f.write(line) + f.close() + def show_help(self, *args): + """[command] -- show this help + """ + lines = [] + okay = True + prog = os.path.basename(sys.argv[0]) + if not args: + argz = {} + for name in dir(self): + arg = None + if name.startswith("system_"): + arg = name[len("system_"):].replace("_","-") + if name.startswith("show_"): + arg = name[len("show_"):].replace("_","-") + if name.endswith("_of_unit"): + arg = name[:-len("_of_unit")].replace("_","-") + if name.endswith("_modules"): + arg = name[:-len("_modules")].replace("_","-") + if arg: + argz[arg] = name + lines.append("%s command [options]..." % prog) + lines.append("") + lines.append("Commands:") + for arg in sorted(argz): + name = argz[arg] + method = getattr(self, name) + doc = "..." + doctext = getattr(method, "__doc__") + if doctext: + doc = doctext + elif not self._show_all: + continue # pragma: no cover + firstline = doc.split("\n")[0] + doc_text = firstline.strip() + if "--" not in firstline: + doc_text = "-- " + doc_text + lines.append(" %s %s" % (arg, firstline.strip())) + return lines + for arg in args: + arg = arg.replace("-","_") + func1 = getattr(self.__class__, arg+"_modules", None) + func2 = getattr(self.__class__, arg+"_of_unit", None) + func3 = getattr(self.__class__, "show_"+arg, None) + func4 = getattr(self.__class__, "system_"+arg, None) + func5 = None + if arg.startswith("__"): + func5 = getattr(self.__class__, arg[2:], None) + func = func1 or func2 or func3 or func4 or func5 + if func is None: + print("error: no such command '%s'" % arg) + okay = False + else: + doc_text = "..." + doc = getattr(func, "__doc__", None) + if doc: + doc_text = doc.replace("\n","\n\n", 1).strip() + if "--" not in doc_text: + doc_text = "-- " + doc_text + else: + func_name = arg # FIXME + logg.debug("__doc__ of %s is none", func_name) + if not self._show_all: continue + lines.append("%s %s %s" % (prog, arg, doc_text)) + if not okay: + self.show_help() + self.error |= NOT_OK + return [] + return lines + def systemd_version(self): + """ the version line for systemd compatibility """ + return "systemd %s\n - via systemctl.py %s" % (self._systemd_version, __version__) + def systemd_features(self): + """ the info line for systemd features """ + features1 = "-PAM -AUDIT -SELINUX -IMA -APPARMOR -SMACK" + features2 = " +SYSVINIT -UTMP -LIBCRYPTSETUP -GCRYPT -GNUTLS" + features3 = " -ACL -XZ -LZ4 -SECCOMP -BLKID -ELFUTILS -KMOD -IDN" + return features1+features2+features3 + def systems_version(self): + return [ self.systemd_version(), self.systemd_features() ] + def test_float(self): + return 0. # "Unknown result type" + +def print_result(result): + # logg_info = logg.info + # logg_debug = logg.debug + def logg_info(*msg): pass + def logg_debug(*msg): pass + exitcode = 0 + if result is None: + logg_info("EXEC END None") + elif result is True: + logg_info("EXEC END True") + exitcode = 0 + elif result is False: + logg_info("EXEC END False") + exitcode = NOT_OK # the only case that exitcode gets set + elif isinstance(result, int): + logg_info("EXEC END %s", result) + # exitcode = result # we do not do that anymore + elif isinstance(result, basestring): + print(result) + result1 = result.split("\n")[0][:-20] + if result == result1: + logg_info("EXEC END '%s'", result) + else: + logg_info("EXEC END '%s...'", result1) + logg_debug(" END '%s'", result) + elif isinstance(result, list) or isinstance(result, GeneratorType): + shown = 0 + for element in result: + if isinstance(element, tuple): + print("\t".join([ str(elem) for elem in element] )) + else: + print(element) + shown += 1 + logg_info("EXEC END %s items", shown) + logg_debug(" END %s", result) + elif isinstance(result, dict): + shown = 0 + for key in sorted(result.keys()): + element = result[key] + if isinstance(element, tuple): + print(key,"=","\t".join([ str(elem) for elem in element])) + else: + print("%s=%s" % (key,element)) + shown += 1 + logg_info("EXEC END %s items", shown) + logg_debug(" END %s", result) + else: + logg.warning("EXEC END Unknown result type %s", str(type(result))) + return exitcode + +if __name__ == "__main__": + import optparse + _o = optparse.OptionParser("%prog [options] command [name...]", + epilog="use 'help' command for more information") + _o.add_option("--version", action="store_true", + help="Show package version") + _o.add_option("--system", action="store_true", default=False, + help="Connect to system manager (default)") # overrides --user + _o.add_option("--user", action="store_true", default=_user_mode, + help="Connect to user service manager") + # _o.add_option("-H", "--host", metavar="[USER@]HOST", + # help="Operate on remote host*") + # _o.add_option("-M", "--machine", metavar="CONTAINER", + # help="Operate on local container*") + _o.add_option("-t","--type", metavar="TYPE", dest="unit_type", default=_unit_type, + help="List units of a particual type") + _o.add_option("--state", metavar="STATE", default=_unit_state, + help="List units with particular LOAD or SUB or ACTIVE state") + _o.add_option("-p", "--property", metavar="NAME", dest="unit_property", default=_unit_property, + help="Show only properties by this name") + _o.add_option("--what", metavar="TYPE", dest="what_kind", default=_what_kind, + help="Defines the service directories to be cleaned (configuration, state, cache, logs, runtime)") + _o.add_option("-a", "--all", action="store_true", dest="show_all", default=_show_all, + help="Show all loaded units/properties, including dead empty ones. To list all units installed on the system, use the 'list-unit-files' command instead") + _o.add_option("-l","--full", action="store_true", default=_full, + help="Don't ellipsize unit names on output (never ellipsized)") + _o.add_option("--reverse", action="store_true", + help="Show reverse dependencies with 'list-dependencies' (ignored)") + _o.add_option("--job-mode", metavar="MODE", + help="Specifiy how to deal with already queued jobs, when queuing a new job (ignored)") + _o.add_option("--show-types", action="store_true", + help="When showing sockets, explicitly show their type (ignored)") + _o.add_option("-i","--ignore-inhibitors", action="store_true", + help="When shutting down or sleeping, ignore inhibitors (ignored)") + _o.add_option("--kill-who", metavar="WHO", + help="Who to send signal to (ignored)") + _o.add_option("-s", "--signal", metavar="SIG", + help="Which signal to send (ignored)") + _o.add_option("--now", action="store_true", default=_now, + help="Start or stop unit in addition to enabling or disabling it") + _o.add_option("-q","--quiet", action="store_true", default=_quiet, + help="Suppress output") + _o.add_option("--no-block", action="store_true", default=False, + help="Do not wait until operation finished (ignored)") + _o.add_option("--no-legend", action="store_true", default=_no_legend, + help="Do not print a legend (column headers and hints)") + _o.add_option("--no-wall", action="store_true", default=False, + help="Don't send wall message before halt/power-off/reboot (ignored)") + _o.add_option("--no-reload", action="store_true", default=_no_reload, + help="Don't reload daemon after en-/dis-abling unit files") + _o.add_option("--no-ask-password", action="store_true", default=_no_ask_password, + help="Do not ask for system passwords") + # _o.add_option("--global", action="store_true", dest="globally", default=_globally, + # help="Enable/disable unit files globally") # for all user logins + # _o.add_option("--runtime", action="store_true", + # help="Enable unit files only temporarily until next reboot") + _o.add_option("-f", "--force", action="store_true", default=_force, + help="When enabling unit files, override existing symblinks / When shutting down, execute action immediately") + _o.add_option("--preset-mode", metavar="TYPE", default=_preset_mode, + help="Apply only enable, only disable, or all presets [%default]") + _o.add_option("--root", metavar="PATH", default=_root, + help="Enable unit files in the specified root directory (used for alternative root prefix)") + _o.add_option("-n","--lines", metavar="NUM", + help="Number of journal entries to show") + _o.add_option("-o","--output", metavar="CAT", + help="change journal output mode [short, ..., cat] (ignored)") + _o.add_option("--plain", action="store_true", + help="Print unit dependencies as a list instead of a tree (ignored)") + _o.add_option("--no-pager", action="store_true", + help="Do not pipe output into pager (mostly ignored)") + # + _o.add_option("-c","--config", metavar="NAME=VAL", action="append", default=[], + help="..override internal variables (InitLoopSleep,SysInitTarget) {%default}") + _o.add_option("-e","--extra-vars", "--environment", metavar="NAME=VAL", action="append", default=[], + help="..override settings in the syntax of 'Environment='") + _o.add_option("-v","--verbose", action="count", default=0, + help="..increase debugging information level") + _o.add_option("-4","--ipv4", action="store_true", default=False, + help="..only keep ipv4 localhost in /etc/hosts") + _o.add_option("-6","--ipv6", action="store_true", default=False, + help="..only keep ipv6 localhost in /etc/hosts") + _o.add_option("-1","--init", action="store_true", default=False, + help="..keep running as init-process (default if PID 1)") + opt, args = _o.parse_args() + logging.basicConfig(level = max(0, logging.FATAL - 10 * opt.verbose)) + logg.setLevel(max(0, logging.ERROR - 10 * opt.verbose)) + # + _extra_vars = opt.extra_vars + _force = opt.force + _full = opt.full + _log_lines = opt.lines + _no_pager = opt.no_pager + _no_reload = opt.no_reload + _no_legend = opt.no_legend + _no_ask_password = opt.no_ask_password + _now = opt.now + _preset_mode = opt.preset_mode + _quiet = opt.quiet + _root = opt.root + _show_all = opt.show_all + _unit_state = opt.state + _unit_type = opt.unit_type + _unit_property = opt.unit_property + _what_kind = opt.what_kind + # being PID 1 (or 0) in a container will imply --init + _pid = os.getpid() + _init = opt.init or _pid in [ 1, 0 ] + _user_mode = opt.user + if os.geteuid() and _pid in [ 1, 0 ]: + _user_mode = True + if opt.system: + _user_mode = False # override --user + # + for setting in opt.config: + nam, val = setting, "1" + if "=" in setting: + nam, val = setting.split("=", 1) + elif nam.startswith("no-") or nam.startswith("NO-"): + nam, val = nam[3:], "0" + elif nam.startswith("No") or nam.startswith("NO"): + nam, val = nam[2:], "0" + if nam in globals(): + old = globals()[nam] + if old is False or old is True: + logg.debug("yes %s=%s", nam, val) + globals()[nam] = (val in ("true", "True", "TRUE", "yes", "y", "Y", "YES", "1")) + logg.debug("... _show_all=%s", _show_all) + elif isinstance(old, float): + logg.debug("num %s=%s", nam, val) + globals()[nam] = float(val) + logg.debug("... MinimumYield=%s", MinimumYield) + elif isinstance(old, int): + logg.debug("int %s=%s", nam, val) + globals()[nam] = int(val) + logg.debug("... InitLoopSleep=%s", InitLoopSleep) + elif isinstance(old, basestring): + logg.debug("str %s=%s", nam, val) + globals()[nam] = val.strip() + logg.debug("... SysInitTarget=%s", SysInitTarget) + else: + logg.warning("(ignored) unknown target type -c '%s' : %s", nam, type(old)) + else: + logg.warning("(ignored) unknown target config -c '%s' : no such variable", nam) + # + systemctl_debug_log = os_path(_root, expand_path(SYSTEMCTL_DEBUG_LOG, not _user_mode)) + systemctl_extra_log = os_path(_root, expand_path(SYSTEMCTL_EXTRA_LOG, not _user_mode)) + if os.access(systemctl_extra_log, os.W_OK): + loggfile = logging.FileHandler(systemctl_extra_log) + loggfile.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) + logg.addHandler(loggfile) + logg.setLevel(max(0, logging.INFO - 10 * opt.verbose)) + if os.access(systemctl_debug_log, os.W_OK): + loggfile = logging.FileHandler(systemctl_debug_log) + loggfile.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) + logg.addHandler(loggfile) + logg.setLevel(logging.DEBUG) + logg.info("EXEC BEGIN %s %s%s%s", os.path.realpath(sys.argv[0]), " ".join(args), + _user_mode and " --user" or " --system", _init and " --init" or "", ) + if _root and not is_good_root(_root): + logg.warning("the --root=path should have alteast three levels /tmp/test_123/root") + # + # + systemctl = Systemctl() + if opt.version: + args = [ "version" ] + if not args: + if _init: + args = [ "default" ] + else: + args = [ "list-units" ] + logg.debug("======= systemctl.py " + " ".join(args)) + command = args[0] + modules = args[1:] + try: + modules.remove("service") + except ValueError: + pass + if opt.ipv4: + systemctl.force_ipv4() + elif opt.ipv6: + systemctl.force_ipv6() + found = False + # command NAME + if command.startswith("__"): + command_name = command[2:] + command_func = getattr(systemctl, command_name, None) + if callable(command_func) and not found: + found = True + result = command_func(*modules) + command_name = command.replace("-","_").replace(".","_")+"_modules" + command_func = getattr(systemctl, command_name, None) + if callable(command_func) and not found: + found = True + result = command_func(*modules) + command_name = "show_"+command.replace("-","_").replace(".","_") + command_func = getattr(systemctl, command_name, None) + if callable(command_func) and not found: + found = True + result = command_func(*modules) + command_name = "system_"+command.replace("-","_").replace(".","_") + command_func = getattr(systemctl, command_name, None) + if callable(command_func) and not found: + found = True + result = command_func() + command_name = "systems_"+command.replace("-","_").replace(".","_") + command_func = getattr(systemctl, command_name, None) + if callable(command_func) and not found: + found = True + result = command_func() + if not found: + logg.error("Unknown operation %s.", command) + sys.exit(1) + # + exitcode = print_result(result) + exitcode |= systemctl.error + sys.exit(exitcode) diff --git a/WebRTC-Sample/owt-server/deployment/CMakeLists.txt b/WebRTC-Sample/owt-server/deployment/CMakeLists.txt deleted file mode 100644 index 39eb5091..00000000 --- a/WebRTC-Sample/owt-server/deployment/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_custom_target(start_owt_immersive_4k "${CMAKE_CURRENT_SOURCE_DIR}/start.sh" "owt-immersive-4k") -add_custom_target(start_owt_immersive_8k "${CMAKE_CURRENT_SOURCE_DIR}/start.sh" "owt-immersive-8k") -add_custom_target(stop "${CMAKE_CURRENT_SOURCE_DIR}/stop.sh") diff --git a/WebRTC-Sample/owt-server/deployment/docker-compose.yml b/WebRTC-Sample/owt-server/deployment/docker-compose.yml deleted file mode 100644 index d63646eb..00000000 --- a/WebRTC-Sample/owt-server/deployment/docker-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: '3.1' - -services: - owt-immersive-4k: - image: xeon-centos76-service-owt-immersive - command: > - sh -c "/home/init.sh - && /home/start.sh - && /home/restApi.sh -s 4k - && /home/restart.sh - && /home/restApi.sh -c /home/Sample-Videos/test1_h265_3840x2048_30fps_30M_200frames.mp4 - && /home/sleep.sh" - network_mode: host - volumes: - - ../../../Sample-Videos:/home/Sample-Videos - - owt-immersive-8k: - image: xeon-centos76-service-owt-immersive - command: > - sh -c "/home/init.sh - && /home/start.sh - && /home/restApi.sh -s 8k - && /home/restart.sh - && /home/restApi.sh -c /home/Sample-Videos/test1_h265_8k_30fps_60M_100frames.mp4 - && /home/sleep.sh" - network_mode: host - volumes: - - ../../../Sample-Videos:/home/Sample-Videos diff --git a/WebRTC-Sample/owt-server/deployment/start.sh b/WebRTC-Sample/owt-server/deployment/start.sh deleted file mode 100755 index 978f42e5..00000000 --- a/WebRTC-Sample/owt-server/deployment/start.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -e - -SUDO="" -if [[ $EUID -ne 0 ]]; then - SUDO="sudo -E" -fi - -DIR=$(dirname $(readlink -f "$0")) -yml="$DIR/docker-compose.yml" - -${SUDO} docker-compose -f "$yml" up ${1} diff --git a/WebRTC-Sample/owt-server/deployment/stop.sh b/WebRTC-Sample/owt-server/deployment/stop.sh deleted file mode 100755 index 5a60d197..00000000 --- a/WebRTC-Sample/owt-server/deployment/stop.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -e - -SUDO="" -if [[ $EUID -ne 0 ]]; then - SUDO="sudo -E" -fi - -DIR=$(dirname $(readlink -f "$0")) -yml="$DIR/docker-compose.yml" - -${SUDO} docker-compose -f "$yml" down diff --git a/WebRTC-Sample/owt-server/image/CMakeLists.txt b/WebRTC-Sample/owt-server/image/CMakeLists.txt deleted file mode 100644 index 162c01fe..00000000 --- a/WebRTC-Sample/owt-server/image/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -include("${CMAKE_SOURCE_DIR}/script/scan-all.cmake") diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/.dockerignore b/WebRTC-Sample/owt-server/image/owt-immersive/.dockerignore deleted file mode 100644 index 1f12d440..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -CMakeLists.txt -*.sh diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/CMakeLists.txt b/WebRTC-Sample/owt-server/image/owt-immersive/CMakeLists.txt deleted file mode 100644 index a1d6cb87..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -set(service "xeon-centos76-service-owt-immersive") -include("${CMAKE_SOURCE_DIR}/script/service.cmake") diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/Dockerfile b/WebRTC-Sample/owt-server/image/owt-immersive/Dockerfile deleted file mode 100755 index 4f992e93..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/Dockerfile +++ /dev/null @@ -1,345 +0,0 @@ - -FROM centos:7.6.1810 AS build -WORKDIR /home -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - -# COMMON BUILD TOOLS -RUN yum install -y -q bzip2 make autoconf libtool git wget ca-certificates pkg-config gcc gcc-c++ bison flex patch epel-release yum-devel libcurl-devel zlib-devel; - -# Install cmake -ARG CMAKE_VER=3.13.1 -ARG CMAKE_REPO=https://cmake.org/files -RUN wget -O - ${CMAKE_REPO}/v${CMAKE_VER%.*}/cmake-${CMAKE_VER}.tar.gz | tar xz && \ - cd cmake-${CMAKE_VER} && \ - ./bootstrap --prefix="/usr/local" --system-curl && \ - make -j8 && \ - make install - -# Install automake, use version 1.14 on CentOS -ARG AUTOMAKE_VER=1.14 -ARG AUTOMAKE_REPO=https://ftp.gnu.org/pub/gnu/automake/automake-${AUTOMAKE_VER}.tar.xz -RUN wget -O - ${AUTOMAKE_REPO} | tar xJ && \ - cd automake-${AUTOMAKE_VER} && \ - ./configure --prefix=/usr --libdir=/usr/local/lib64 --disable-doc && \ - make -j8 && \ - make install - -# Build NASM -ARG NASM_VER=2.13.03 -ARG NASM_REPO=https://www.nasm.us/pub/nasm/releasebuilds/${NASM_VER}/nasm-${NASM_VER}.tar.bz2 -RUN wget ${NASM_REPO} && \ - tar -xaf nasm* && \ - cd nasm-${NASM_VER} && \ - ./autogen.sh && \ - ./configure --prefix="/usr/local" --libdir=/usr/local/lib64 && \ - make -j8 && \ - make install - -# Build YASM -ARG YASM_VER=1.3.0 -ARG YASM_REPO=https://www.tortall.net/projects/yasm/releases/yasm-${YASM_VER}.tar.gz -RUN wget -O - ${YASM_REPO} | tar xz && \ - cd yasm-${YASM_VER} && \ - sed -i "s/) ytasm.*/)/" Makefile.in && \ - ./configure --prefix="/usr/local" --libdir=/usr/local/lib64 && \ - make -j8 && \ - make install - -# Build libnice -ARG NICE_VER="0.1.4" -ARG NICE_REPO=http://nice.freedesktop.org/releases/libnice-${NICE_VER}.tar.gz -ARG LIBNICE_PATCH_VER="4.3.1" -ARG LIBNICE_PATCH_REPO=https://github.com/open-webrtc-toolkit/owt-server/archive/v${LIBNICE_PATCH_VER}.tar.gz - -RUN yum install -y -q glib2-devel - -RUN wget -O - ${NICE_REPO} | tar xz && \ - cd libnice-${NICE_VER} && \ - wget -O - ${LIBNICE_PATCH_REPO} | tar xz && \ - patch -p1 < owt-server-${LIBNICE_PATCH_VER}/scripts/patches/libnice014-agentlock.patch && \ - patch -p1 < owt-server-${LIBNICE_PATCH_VER}/scripts/patches/libnice014-agentlock-plus.patch && \ - patch -p1 < owt-server-${LIBNICE_PATCH_VER}/scripts/patches/libnice014-removecandidate.patch && \ - patch -p1 < owt-server-${LIBNICE_PATCH_VER}/scripts/patches/libnice014-keepalive.patch && \ - patch -p1 < owt-server-${LIBNICE_PATCH_VER}/scripts/patches/libnice014-startcheck.patch && \ - patch -p1 < owt-server-${LIBNICE_PATCH_VER}/scripts/patches/libnice014-closelock.patch && \ - ./configure --prefix="/usr/local" --libdir=/usr/local/lib64 && \ - make -s V= && \ - make install - - -# Build open ssl -ARG OPENSSL_VER="1.1.1h" -ARG OPENSSL_REPO=http://www.openssl.org/source/openssl-${OPENSSL_VER}.tar.gz -ARG BUILD_PREFIX=/usr/local/ssl -ARG BUILD_DESTDIR=/home/build - -RUN wget -O - ${OPENSSL_REPO} | tar xz && \ - cd openssl-${OPENSSL_VER} && \ - ./config no-ssl3 --prefix=${BUILD_PREFIX} --openssldir=${BUILD_PREFIX} -Wl,-rpath=${BUILD_PREFIX}/lib -fPIC && \ - make depend && \ - make -s V=0 && \ - make install - -# Build libre -ARG LIBRE_VER="v0.5.0" -ARG LIBRE_REPO=https://github.com/creytiv/re.git - -RUN git clone ${LIBRE_REPO} && \ - cd re && \ - git checkout ${LIBRE_VER} && \ - make SYSROOT_ALT="/usr" RELEASE=1 && \ - make install SYSROOT_ALT="/usr" RELEASE=1 PREFIX="/usr" - -# Build usrsctp - -ARG USRSCTP_VERSION="30d7f1bd0b58499e1e1f2415e84d76d951665dc8" -ARG USRSCTP_FILE="${USRSCTP_VERSION}.tar.gz" -ARG USRSCTP_EXTRACT="usrsctp-${USRSCTP_VERSION}" -ARG USRSCTP_URL="https://github.com/sctplab/usrsctp/archive/${USRSCTP_FILE}" - -RUN yum install -y -q which - -RUN wget -O - ${USRSCTP_URL} | tar xz && \ - mv ${USRSCTP_EXTRACT} usrsctp && \ - cd usrsctp && \ - ./bootstrap && \ - ./configure --prefix="/usr/local" --libdir=/usr/local/lib64 && \ - make && \ - make install - -# Build libsrtp2 -ARG SRTP2_VER="2.1.0" -ARG SRTP2_REPO=https://codeload.github.com/cisco/libsrtp/tar.gz/v${SRTP2_VER} - -RUN yum install -y -q curl - -RUN curl -o libsrtp-${SRTP2_VER}.tar.gz ${SRTP2_REPO} && \ - tar xzf libsrtp-${SRTP2_VER}.tar.gz && \ - cd libsrtp-${SRTP2_VER} && \ - export PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \ - export CFLAGS="-fPIC" && \ - ./configure --enable-openssl --prefix="/usr/local" --with-openssl-dir="/usr/local/ssl/" && \ - make -s V=0 && \ - make install - -# Build fdk-aac -ARG FDK_AAC_VER=v0.1.6 -ARG FDK_AAC_REPO=https://github.com/mstorsjo/fdk-aac/archive/${FDK_AAC_VER}.tar.gz - -RUN wget -O - ${FDK_AAC_REPO} | tar xz && mv fdk-aac-${FDK_AAC_VER#v} fdk-aac && \ - cd fdk-aac && \ - autoreconf -fiv && \ - ./configure --prefix="/usr/local" --libdir=/usr/local/lib64 --enable-shared && \ - make -j8 && \ - make install DESTDIR=/home/build && \ - make install - - -# Fetch FFmpeg source -ARG FFMPEG_VER=n4.1 -ARG FFMPEG_REPO=https://github.com/FFmpeg/FFmpeg/archive/${FFMPEG_VER}.tar.gz -ARG FFMPEG_1TN_PATCH_REPO=https://patchwork.ffmpeg.org/patch/11625/raw -ARG FFMPEG_THREAD_PATCH_REPO=https://patchwork.ffmpeg.org/patch/11035/raw - -ARG FFMPEG_PATCHES_RELEASE_VER=0.1 -ARG FFMPEG_PATCHES_RELEASE_URL=https://github.com/VCDP/CDN/archive/v${FFMPEG_PATCHES_RELEASE_VER}.tar.gz -ARG FFMPEG_PATCHES_PATH=/home/CDN-${FFMPEG_PATCHES_RELEASE_VER} -RUN wget -O - ${FFMPEG_PATCHES_RELEASE_URL} | tar xz - - -RUN yum install -y -q libass-devel freetype-devel zlib-devel openssl-devel - -RUN wget -O - ${FFMPEG_REPO} | tar xz && mv FFmpeg-${FFMPEG_VER} FFmpeg && \ - cd FFmpeg ; - -# Compile FFmpeg -RUN cd /home/FFmpeg && \ - export PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \ - ./configure --prefix="/usr/local" --libdir=/usr/local/lib64 --enable-shared --disable-static --disable-libvpx --disable-vaapi --enable-libfreetype --enable-libfdk-aac --enable-nonfree && \ - - make -j8 && \ - make install && make install DESTDIR="/home/build" - - -# Install node -ARG NODE_VER=v10.21.0 -ARG NODE_REPO=https://nodejs.org/dist/${NODE_VER}/node-${NODE_VER}-linux-x64.tar.xz - -RUN yum install -y -q ca-certificates wget xz-utils - -RUN wget ${NODE_REPO} && \ - tar xf node-${NODE_VER}-linux-x64.tar.xz && \ - cp node-*/* /usr/local -rf && \ - rm -rf node-* - -# Fetch SVT-HEVC -ARG SVT_HEVC_VER=v1.4.3 -ARG SVT_HEVC_REPO=https://github.com/intel/SVT-HEVC - -RUN yum install -y -q patch centos-release-scl && \ - yum install -y -q devtoolset-7 - -# hadolint ignore=SC1091 -RUN git clone ${SVT_HEVC_REPO} && \ - cd SVT-HEVC/Build/linux && \ - export PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \ - git checkout ${SVT_HEVC_VER} && \ - mkdir -p ../../Bin/Release && \ - ( source /opt/rh/devtoolset-7/enable && \ - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_INSTALL_LIBDIR=lib64 -DCMAKE_ASM_NASM_COMPILER=yasm ../.. && \ - make -j8 && \ - make install DESTDIR=/home/build && \ - make install ) - - -# Build OWT specific modules - -ARG OWTSERVER_COMMIT=fd71357d6fdbd57d3c4be2028976bc2b34fff781 -ARG OWTSERVER_REPO=https://github.com/open-webrtc-toolkit/owt-server.git -ARG OPENH264_MAJOR=1 -ARG OPENH264_MINOR=7 -ARG OPENH264_SOVER=4 -ARG OPENH264_SOURCENAME=v${OPENH264_MAJOR}.${OPENH264_MINOR}.0.tar.gz -ARG OPENH264_SOURCE=https://github.com/cisco/openh264/archive/v${OPENH264_MAJOR}.${OPENH264_MINOR}.0.tar.gz -ARG OPENH264_BINARYNAME=libopenh264-${OPENH264_MAJOR}.${OPENH264_MINOR}.0-linux64.${OPENH264_SOVER}.so -ARG OPENH264_BINARY=https://github.com/cisco/openh264/releases/download/v${OPENH264_MAJOR}.${OPENH264_MINOR}.0/${OPENH264_BINARYNAME}.bz2 -ARG LICODE_COMMIT="8b4692c88f1fc24dedad66b4f40b1f3d804b50ca" -ARG LICODE_REPO=https://github.com/lynckia/licode.git -ARG LICODE_PATCH_REPO=https://github.com/open-webrtc-toolkit/owt-server/tree/master/scripts/patches/licode/ -ARG SAFESTRINGLIB_COMMIT="245c4b8cff1d2e7338b7f3a82828fc8e72b29549" -ARG SAFESTRINGLIB_REPO=https://github.com/intel/safestringlib.git -ARG SCVP_VER="9ce286edf4d5976802bf488b4dd90a16ecc28c36" -ARG SCVP_REPO=https://github.com/OpenVisualCloud/Immersive-Video-Sample -ARG WEBRTC_REPO=https://github.com/open-webrtc-toolkit/owt-deps-webrtc.git -ARG SERVER_PATH=/home/owt-server -ARG OWT_SDK_REPO=https://github.com/open-webrtc-toolkit/owt-client-javascript.git -ARG OWT_BRANCH=360-video -ARG OWT_BRANCH_JS=master -ARG OWT_BRANCH_JS_COMMIT="d727af2927731ff16214d73f57964a992258636d" -ARG WEBRTC_COMMIT="c2aa290cfe4f63d5bfbb6540122a5e6bf2783187" - -ARG FDKAAC_LIB=/home/build/usr/local/lib64 -RUN yum install -y -q python-devel glib2-devel boost-devel log4cxx-devel glog-devel gflags-devel -RUN yum install -y -q patch centos-release-scl devtoolset-7 -ENV PYTHONIOENCODING=UTF-8 -# Install 360scvp -# hadolint ignore=SC1091 -RUN cd /home && \ - source /opt/rh/devtoolset-7/enable && \ - git clone ${SAFESTRINGLIB_REPO} && \ - cd safestringlib && git reset --hard ${SAFESTRINGLIB_COMMIT} && \ - mkdir build && cd build && cmake .. && \ - make -j && \ - mkdir -p /usr/local/lib && \ - cp libsafestring_shared.so /usr/local/lib && \ - mkdir -p /usr/local/lib64 && \ - cp libsafestring_shared.so /usr/local/lib64 && \ - mkdir -p /home/build/usr/local/lib64 && \ - cp libsafestring_shared.so /home/build/usr/local/lib64 && \ - mkdir -p /usr/local/include/safestringlib && \ - cp -rf ../include/* /usr/local/include/safestringlib/ -RUN cd /home && \ - git clone ${SCVP_REPO} && \ - cd Immersive-Video-Sample/src/360SCVP && \ - git reset --hard ${SCVP_VER} && \ - mkdir build && \ - cd build && \ - source /opt/rh/devtoolset-7/enable && \ - cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_INSTALL_LIBDIR=lib64 ../ && \ - make -j && \ - make install DESTDIR=/home/build && \ - make install - -# 1. Clone OWT server source code -# 2. Clone licode source code and patch -# 3. Clone webrtc source code and patch - -# hadolint ignore=SC1091 -RUN git clone -b ${OWT_BRANCH} ${OWTSERVER_REPO} && \ - source /opt/rh/devtoolset-7/enable && \ - - cd ${SERVER_PATH} && git reset --hard ${OWTSERVER_COMMIT} && \ - curl https://patch-diff.githubusercontent.com/raw/open-webrtc-toolkit/owt-server/pull/708.patch | git apply && \ - - # Install node modules for owt - npm config set proxy=${http_proxy} && \ - npm config set https-proxy=${http_proxy} && \ - npm install -g --loglevel error node-gyp@v6.1.0 grunt-cli underscore jsdoc && \ - cd ${SERVER_PATH} && npm install nan && \ - - # Get openh264 for owt - cd ${SERVER_PATH}/third_party && \ - mkdir openh264 && cd openh264 && \ - wget ${OPENH264_SOURCE} && \ - wget ${OPENH264_BINARY} && \ - tar xzf ${OPENH264_SOURCENAME} openh264-${OPENH264_MAJOR}.${OPENH264_MINOR}.0/codec/api && \ - ln -s -v openh264-${OPENH264_MAJOR}.${OPENH264_MINOR}.0/codec codec && \ - bzip2 -d ${OPENH264_BINARYNAME}.bz2 && \ - ln -s -v ${OPENH264_BINARYNAME} libopenh264.so.${OPENH264_SOVER} && \ - ln -s -v libopenh264.so.${OPENH264_SOVER} libopenh264.so && \ - echo 'const char* stub() {return "this is a stub lib";}' > pseudo-openh264.cpp && \ - gcc pseudo-openh264.cpp -fPIC -shared -o pseudo-openh264.so && \ - - # Get licode for owt - cd ${SERVER_PATH}/third_party && git clone ${LICODE_REPO} && \ - cd licode && \ - git reset --hard ${LICODE_COMMIT} && \ - wget -r -nH --cut-dirs=5 --no-parent ${LICODE_PATCH_REPO} && \ - git apply ${SERVER_PATH}/scripts/patches/licode/*.patch && \ - mkdir -p ${SERVER_PATH}/build/libdeps/build/include && \ - cp erizoAPI/lib/json.hpp ${SERVER_PATH}/build/libdeps/build/include && \ - - # Install webrtc for owt - cd ${SERVER_PATH}/third_party && mkdir webrtc && cd webrtc &&\ - export GIT_SSL_NO_VERIFY=1 && \ - git clone -b 59-server ${WEBRTC_REPO} src && cd src && \ - git reset --hard ${WEBRTC_COMMIT} && \ - ./tools-woogeen/install.sh && \ - patch -p1 < ${SERVER_PATH}/scripts/patches/0001-Implement-RtcpFOVObserver.patch && \ - ./tools-woogeen/build.sh && \ - - # Get js client sdk for owt - cd /home && git clone -b ${OWT_BRANCH_JS} ${OWT_SDK_REPO} && cd owt-client-javascript/scripts && git reset --hard ${OWT_BRANCH_JS_COMMIT} && npm install && grunt && \ - export LD_LIBRARY_PATH=/usr/local/lib64 && \ - #Build and pack owt - cd ${SERVER_PATH} && export CPLUS_INCLUDE_PATH=/usr/local/include/svt-hevc && export PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig && ./scripts/build.js -t mcu -r -c && \ - ./scripts/pack.js -t all --install-module --no-pseudo --sample-path /home/owt-client-javascript/dist/samples/conference - -FROM centos:7.6.1810 -LABEL Description="This is the image for owt development on CentOS 7.6" -LABEL Vendor="Intel Corporation" -WORKDIR /home - -# Prerequisites -# Install node -ARG NODE_VER=v10.21.0 -ARG NODE_REPO=https://nodejs.org/dist/${NODE_VER}/node-${NODE_VER}-linux-x64.tar.xz - -RUN yum install -y -q ca-certificates wget xz-utils - -RUN wget ${NODE_REPO} && \ - tar xf node-${NODE_VER}-linux-x64.tar.xz && \ - cp node-*/* /usr/local -rf && \ - rm -rf node-* - -COPY --from=build /home/owt-server/dist /home/owt -COPY --from=build /home/build / -ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib64: -RUN echo "[mongodb-org-3.6]" >> /etc/yum.repos.d/mongodb-org-3.6.repo && \ - echo "name=MongoDB Repository" >> /etc/yum.repos.d/mongodb-org-3.6.repo && \ - echo "baseurl=https://repo.mongodb.org/yum/redhat/7/mongodb-org/3.6/x86_64/" >> /etc/yum.repos.d/mongodb-org-3.6.repo && \ - echo "gpgcheck=1" >> /etc/yum.repos.d/mongodb-org-3.6.repo && \ - echo "enabled=1" >> /etc/yum.repos.d/mongodb-org-3.6.repo && \ - echo "gpgkey=https://www.mongodb.org/static/pgp/server-3.6.asc" >> /etc/yum.repos.d/mongodb-org-3.6.repo && \ - yum install epel-release boost-system boost-thread log4cxx glib2 freetype-devel -y && \ - yum install rabbitmq-server mongodb-org glog-devel gflags-devel -y && \ - yum remove -y -q epel-release && \ - rm -rf /var/cache/yum/*; - -COPY scripts/init.sh scripts/restApi.sh scripts/restart.sh scripts/sleep.sh scripts/start.sh /home/ -COPY scripts/scripts/ /home/scripts/ -RUN cd /home/scripts &&\ - npm config set proxy=${http_proxy} && \ - npm config set https-proxy=${http_proxy} && \ - npm install - diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/build.sh b/WebRTC-Sample/owt-server/image/owt-immersive/build.sh deleted file mode 100755 index 84634f71..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -e - -IMAGE="xeon-centos76-service-owt-immersive" -DIR=$(dirname $(readlink -f "$0")) - -. "$DIR/../../script/build.sh" diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/init.sh b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/init.sh deleted file mode 100755 index 6cdc0bc3..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/init.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -e - -mongod --config /etc/mongod.conf & -rabbitmq-server & -while ! mongo --quiet --eval "db.adminCommand('listDatabases')" -do - sleep 1 -done -echo mongodb connected successfully -cd /home/owt -./management_api/init.sh diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/restApi.sh b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/restApi.sh deleted file mode 100755 index b559c8cb..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/restApi.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/bash -SCRIPT=`pwd`/$0 - -if [ ! -f ${SCRIPT} ]; then - SCRIPT=$0 -fi - -ROOT=`dirname $SCRIPT` -DB_URL='localhost/owtdb' - -UPDATE=false -ADD=false -CLEARUP=false - -WIDTH=0 -HEIGHT=0 -STREAM_URL="" - -usage() { - echo \ -"Usage: $0 [OPTION] -Set server video encoding resolution, publish a video stream, remove video streams. - -Options: - -s [4k|8k] Set server video encoding resolution 3840x2048 or 7680x3840, default is 3840x2048 - -c url Connect a FILE|RTSP|RTMP stream to server - -d Disconnect all streams from server" - - exit 0 -} - -parse_arguments(){ - if [ $# == 0 ]; then - usage $* - fi - - case $1 in - "-s") - if [ $# == 1 ];then - WIDTH=3840 - HEIGHT=2048 - elif [ $2 == '4k' ];then - WIDTH=3840 - HEIGHT=2048 - elif [ $2 == '8k' ];then - WIDTH=7680 - HEIGHT=3840 - else - echo "Invalid parameter $2, must be 4k or 8k." - exit 0 - fi - - UPDATE=true - FILE="updateRoom.js" - ;; - "-c") - if [ $# == 1 ];then - echo "Stream url is required." - exit 0 - fi - - echo "Connecting to $2 ..." - - ADD=true - STREAM_URL=$2 - FILE="add_stream.js" - ;; - "-d") - CLEARUP=true - FILE="clear_streams.js" - ;; - *) - usage $* - ;; - esac -} - -install_config() { - export DB_URL - option=$1 - [[ -s ${ROOT}/scripts/initdb.js ]] && node ${ROOT}/scripts/initdb.js "${ROOT}/scripts/${option}" || return 1 -} - -update_room() { - sed s/width:.*,height:.*/width:${WIDTH},height:${HEIGHT}/g -i ${ROOT}/scripts/config.js - node ${ROOT}/scripts/updateRoom.js -} - -add_stream() { - sed s$\'url\':\'.*\'$\'url\':\'${STREAM_URL}\'$ -i ${ROOT}/scripts/config.js - node ${ROOT}/scripts/add_stream.js -} - -clear_streams() { - node ${ROOT}/scripts/clear_streams.js -} - -echo "$0 Enter" - -parse_arguments $* - -install_config ${FILE} - -${UPDATE} && update_room -${CLEARUP} && clear_streams -${ADD} && add_stream - -echo "$0 Exit" diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/restart.sh b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/restart.sh deleted file mode 100755 index 6fd13388..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/restart.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -e - -cd /home/owt - -./bin/stop-all.sh -rm ./logs/* -./bin/start-all.sh diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/add_stream.js b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/add_stream.js deleted file mode 100755 index 1f4ba3e5..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/add_stream.js +++ /dev/null @@ -1,23 +0,0 @@ -var icsREST = require('./rest.js'); -var config = require('./config.js') -icsREST.API.init('_service_ID_', '_service_KEY_', config.serverUrl, false); -var sampleRoomId; -var streamid; -icsREST.API.getRooms() - .then((rooms) => { - for (var i = 0; i <= rooms.length - 1; i++) { - if (rooms[i].name === "sampleRoom") { - sampleRoomId = rooms[i]._id; - } - } - return icsREST.API.startStreamingIn(sampleRoomId) - }) - .then((stream) => { - stream = JSON.parse(stream) - streamid = stream.id; - return icsREST.API.mixStream(sampleRoomId, streamid) - }) - .then(() => { - console.log(`startStreamingIn sucess => streaminId :${streamid}`) - }) - diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/clear_streams.js b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/clear_streams.js deleted file mode 100755 index c941b2fa..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/clear_streams.js +++ /dev/null @@ -1,21 +0,0 @@ -var icsREST = require('./rest.js'); -var config = require('./config.js') -icsREST.API.init('_service_ID_', '_service_KEY_', config.serverUrl, false); -var sampleRoomId; -icsREST.API.getRooms() - .then((rooms) => { - for (var i = 0; i <= rooms.length - 1; i++) { - if (rooms[i].name === "sampleRoom") { - sampleRoomId = rooms[i]._id; - } - } - return icsREST.API.getStreams(sampleRoomId) - }) - .then((streams) => { - for (var i = 1; i < streams.length; i++) { - if (streams[i] !== 'mixed') { - console.log("clear stream:", streams[i].id) - icsREST.API.stopStreamingIn(sampleRoomId, streams[i].id) - } - } - }) diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/config.js b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/config.js deleted file mode 100755 index 86ecbdc2..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/config.js +++ /dev/null @@ -1,195 +0,0 @@ -module.exports = { - 'serverUrl': 'https://localhost:3000', - 'url':'', - 'streaminId': '', - 'updateRoomOptions': { - "mediaOut": { - "video": { - "parameters": { - "keyFrameInterval": [ - - 30, - 5, - 2, - 1 - ], - "bitrate": [ - "x0.8", - "x0.6", - "x0.4", - "x0.2" - ], - "framerate": [ - 6, - 12, - 15, - 24, - 30, - 48, - 60 - ], - "resolution": [ - "x3/4", - "x2/3", - "x1/2", - "x1/3", - "x1/4", - "hd1080p", - "hd720p", - "svga", - "vga", - "qvga", - "cif" - ] - }, - "format": [ - { - "codec": "vp8" - }, - { - "profile": "CB", - "codec": "h264" - }, - { - "codec": "vp9" - }, - { - "codec": "h265" - } - ] - }, - "audio": [ - { - "channelNum": 2, - "sampleRate": 48000, - "codec": "opus" - }, - { - "sampleRate": 16000, - "codec": "isac" - }, - { - "sampleRate": 32000, - "codec": "isac" - }, - { - "channelNum": 1, - "sampleRate": 16000, - "codec": "g722" - }, - - { - "codec": "pcmu" - }, - { - "channelNum": 2, - "sampleRate": 48000, - "codec": "aac" - }, - { - "codec": "ac3" - }, - { - "codec": "nellymoser" - }, - { - "codec": "ilbc" - } - ] - }, - "mediaIn": { - "video": [ - { - "codec": "h264" - }, - { - "codec": "vp8" - }, - { - "codec": "vp9" - }, - { - "codec": "h265" - } - ], - "audio": [ - { - "channelNum": 2, - "sampleRate": 48000, - "codec": "opus" - }, - { - "sampleRate": 16000, - "codec": "isac" - }, - { - "sampleRate": 32000, - "codec": "isac" - }, - { - "channelNum": 1, - "sampleRate": 16000, - "codec": "g722" - }, - { - "codec": "pcma" - }, - { - "codec": "pcmu" - }, - { - "codec": "aac" - }, - { - "codec": "ac3" - }, - { - "codec": "nellymoser" - }, - { - "codec": "ilbc" - } - ] - }, - "views": [ - { - "video": { - "layout": { - "templates": { - "custom": [], - "base": "fluid" - }, - "fitPolicy": "letterbox" - }, - "keepActiveInputPrimary": false, - "bgColor": { - "b": 0, - "g": 0, - "r": 0 - }, - "motionFactor": 0.8, - "maxInput": 16, - "parameters": { - "keyFrameInterval": 100, - "framerate": 30, - "resolution": { - width:3840,height:2048 - } - }, - "format": { - "codec": "vp9" - } - }, - "audio": { - "vad": true, - "format": { - "codec": "opus", - "sampleRate": 48000, - "channelNum": 2 - } - }, - "label": "common" - } - ], - } -} diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/initdb.js b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/initdb.js deleted file mode 100755 index 43757fe3..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/initdb.js +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/env node - -// Copyright (C) <2019> Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -var dbURL = process.env.DB_URL; -if (!dbURL) { - throw 'DB_URL not found'; -} - -var currentVersion = '1.0'; -var fs = require('fs'); -var path = require('path'); -var db; -var cipher = require('./cipher'); -var script = process.argv[2]; -var dirName = !process.pkg ? __dirname : path.dirname(process.execPath); -var configFile = path.join(dirName, 'management_api.toml'); -var sampleServiceFile = path.resolve(dirName, script); -if(script === '../scripts/updateRoom360.js'){ - sampleServiceFile = path.resolve(dirName, '../scripts/updateRoom360.js'); -} -else if(script === '../scripts/addRtsp.js'){ - sampleServiceFile = path.resolve(dirName, '../scripts/addRtsp.js'); -} -else if(script === '../scripts/clearRtsp.js'){ - sampleServiceFile = path.resolve(dirName, '../scripts/clearRtsp.js'); -} -function prepareDB(next) { - if (fs.existsSync(cipher.astore)) { - cipher.unlock(cipher.k, cipher.astore, function cb (err, authConfig) { - if (!err) { - if (authConfig.mongo && !dbURL.includes('@')) { - dbURL = authConfig.mongo.username - + ':' + authConfig.mongo.password - + '@' + dbURL; - } - } else { - log.error('Failed to get mongodb auth:', err); - } - db = require('mongojs')(dbURL, ['services', 'infos', 'rooms']); - next(); - }); - } else { - db = require('mongojs')(dbURL, ['services', 'infos', 'rooms']); - next(); - } -} - -function upgradeH264(next) { - db.rooms.find({}).toArray(function (err, rooms) { - if (err) { - console.log('Error in getting rooms:', err.message); - db.close(); - return; - } - if (!rooms || rooms.length === 0) { - next(); - return; - } - - var total = rooms.length; - var count = 0; - var i, room; - for (i = 0; i < total; i++) { - room = rooms[i]; - room.mediaOut.video.format.forEach((fmt) => { - if (fmt && fmt.codec === 'h264' && !fmt.profile) { - fmt.profile = 'CB'; - } - }); - room.mediaOut.video.format = room.mediaOut.video.format.filter((fmt) => { - return (fmt && fmt.codec !== 'h265'); - }); - room.mediaIn.video = room.mediaIn.video.filter((fmt) => { - return (fmt && fmt.codec !== 'h265'); - }); - room.views.forEach((viewSettings) => { - var fmt = viewSettings.video.format; - if (fmt && fmt.codec === 'h264' && !fmt.profile) { - fmt.profile = 'CB'; - } else if (fmt && fmt.codec === 'h265') { - fmt.codec = 'h264'; - fmt.profile = 'CB'; - } else if (!fmt) { - viewSettings.video.format = { codec: 'vp8' }; - } - }); - - db.rooms.save(room, function (e, saved) { - if (e) { - console.log('Error in upgrading room:', room._id, e.message); - } - count++; - if (count === total) { - next(); - } - }); - } - }); -} - -function checkVersion (next) { - db.infos.findOne({_id: 1}, function cb (err, info) { - if (err) { - console.log('mongodb: error in query info'); - return db.close(); - } - if (info) { - if (info.version === '1.0') { - next(); - } - } else { - db.services.findOne({}, function cb (err, service) { - if (err) { - console.log('mongodb: error in query service'); - return db.close(); - } - var upgradeNext = function(next) { - upgradeH264(function() { - info = {_id: 1, version: currentVersion}; - db.infos.save(info, function cb (e, s) { - if (e) { - console.log('mongodb: error in updating version'); - return db.close(); - } - next(); - }); - }); - }; - if (service) { - if (typeof service.__v !== 'number') { - console.log(`The existed service "${service.name}" is not in 4.* format.`); - console.log('Preparing to upgrade your database.'); - require('./SchemaUpdate3to4').update(function() { - upgradeNext(next); - }); - } else { - var rl = require('readline').createInterface({ - input: process.stdin, - output: process.stdout - }); - rl.question('This operation will upgrade stored data to version 4.1. Are you ' + - 'sure you want to proceed this operation anyway?[y/n]', (answer) => { - rl.close(); - answer = answer.toLowerCase(); - if (answer === 'y' || answer === 'yes') { - upgradeNext(next); - } else { - process.exit(0); - } - }); - } - } else { - upgradeNext(next); - } - }); - } - }); -} - -function prepareService (serviceName, next) { - db.services.findOne({name: serviceName}, function cb (err, service) { - if (err || !service) { - var crypto = require('crypto'); - var key = crypto.pbkdf2Sync(crypto.randomBytes(64).toString('hex'), crypto.randomBytes(32).toString('hex'), 4000, 128, 'sha256').toString('base64'); - service = {name: serviceName, key: cipher.encrypt(cipher.k, key), encrypted: true, rooms: [], __v: 0}; - db.services.save(service, function cb (err, saved) { - if (err) { - console.log('mongodb: error in adding', serviceName); - return db.close(); - } - saved.key = key; - next(saved); - }); - } else { - if (service.encrypted === true) { - service.key = cipher.decrypt(cipher.k, service.key); - } - next(service); - } - }); -} - -function writeSampleFile(sampleServiceId, sampleServiceKey) { - fs.readFile(sampleServiceFile, 'utf8', function (err, data) { - if (err) { - return console.log(err); - } - data = data.replace(/icsREST\.API\.init\('[^']*', '[^']*'/, 'icsREST.API.init(\''+sampleServiceId+'\', \''+sampleServiceKey+'\''); - fs.writeFile(sampleServiceFile, data, 'utf8', function (err) { - if (err) return console.log(err); - }); - }); -} - -prepareDB(function() { - checkVersion(function() { - prepareService('superService', function (service) { - var superServiceId = service._id+''; - var superServiceKey = service.key; - // console.log('superServiceId:', superServiceId); - // console.log('superServiceKey:', superServiceKey); - - prepareService('sampleService', function (service) { - var sampleServiceId = service._id+''; - var sampleServiceKey = service.key; - // console.log('sampleServiceId:', sampleServiceId); - // console.log('sampleServiceKey:', sampleServiceKey); - db.close(); - - writeSampleFile(sampleServiceId, sampleServiceKey); - }); - }); - }); -}); diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/package.json b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/package.json deleted file mode 100644 index 0b498723..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "WooGeen-MCU-Nuve", - "version": "4.1.0", - "dependencies": { - "ajv": "^5.2.2", - "amqp": "*", - "body-parser": "*", - "express": "*", - "fraction.js": "*", - "log4js": "^1.1.1", - "mongojs": "", - "mongoose": "^5.8.6", - "package.json": "^2.0.1", - "toml": "*" - }, - "engine": { - "node": "8.11.1" - }, - "bin": "owt.js", - "pkg": { - "assets": [ - "node_modules*" - ], - "targets": [ - "node8" - ] - } -} diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/rest.js b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/rest.js deleted file mode 100755 index 7e8d12d1..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/rest.js +++ /dev/null @@ -1,360 +0,0 @@ -const config = require('./config.js'); -'use strict'; -var CryptoJS=CryptoJS||function(h,i){var e={},f=e.lib={},l=f.Base=function(){function a(){}return{extend:function(j){a.prototype=this;var d=new a;j&&d.mixIn(j);d.$super=this;return d},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var d in a)a.hasOwnProperty(d)&&(this[d]=a[d]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),k=f.WordArray=l.extend({init:function(a,j){a= -this.words=a||[];this.sigBytes=j!=i?j:4*a.length},toString:function(a){return(a||m).stringify(this)},concat:function(a){var j=this.words,d=a.words,c=this.sigBytes,a=a.sigBytes;this.clamp();if(c%4)for(var b=0;b>>2]|=(d[b>>>2]>>>24-8*(b%4)&255)<<24-8*((c+b)%4);else if(65535>>2]=d[b>>>2];else j.push.apply(j,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,b=this.sigBytes;a[b>>>2]&=4294967295<<32-8*(b%4);a.length=h.ceil(b/4)},clone:function(){var a= -l.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var b=[],d=0;d>>2]>>>24-8*(c%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>3]|=parseInt(a.substr(c,2),16)<<24-4*(c%8);return k.create(d,b/2)}},q=o.Latin1={stringify:function(a){for(var b= -a.words,a=a.sigBytes,d=[],c=0;c>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return k.create(d,b)}},r=o.Utf8={stringify:function(a){try{return decodeURIComponent(escape(q.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return q.parse(unescape(encodeURIComponent(a)))}},b=f.BufferedBlockAlgorithm=l.extend({reset:function(){this._data=k.create(); -this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=r.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,c=b.sigBytes,e=this.blockSize,g=c/(4*e),g=a?h.ceil(g):h.max((g|0)-this._minBufferSize,0),a=g*e,c=h.min(4*a,c);if(a){for(var f=0;fg;)e(b)&&(8>g&&(k[g]=f(h.pow(b,0.5))),o[g]=f(h.pow(b,1/3)),g++),b++})();var m=[],l=l.SHA256=e.extend({_doReset:function(){this._hash=f.create(k.slice(0))},_doProcessBlock:function(e,f){for(var b=this._hash.words,g=b[0],a=b[1],j=b[2],d=b[3],c=b[4],h=b[5],l=b[6],k=b[7],n=0;64> -n;n++){if(16>n)m[n]=e[f+n]|0;else{var i=m[n-15],p=m[n-2];m[n]=((i<<25|i>>>7)^(i<<14|i>>>18)^i>>>3)+m[n-7]+((p<<15|p>>>17)^(p<<13|p>>>19)^p>>>10)+m[n-16]}i=k+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&h^~c&l)+o[n]+m[n];p=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&a^g&j^a&j);k=l;l=h;h=c;c=d+i|0;d=j;j=a;a=g;g=i+p|0}b[0]=b[0]+g|0;b[1]=b[1]+a|0;b[2]=b[2]+j|0;b[3]=b[3]+d|0;b[4]=b[4]+c|0;b[5]=b[5]+h|0;b[6]=b[6]+l|0;b[7]=b[7]+k|0},_doFinalize:function(){var e=this._data,f=e.words,b=8*this._nDataBytes, -g=8*e.sigBytes;f[g>>>5]|=128<<24-g%32;f[(g+64>>>9<<4)+15]=b;e.sigBytes=4*f.length;this._process()}});i.SHA256=e._createHelper(l);i.HmacSHA256=e._createHmacHelper(l)})(Math); -(function(){var h=CryptoJS,i=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(e,f){e=this._hasher=e.create();"string"==typeof f&&(f=i.parse(f));var h=e.blockSize,k=4*h;f.sigBytes>k&&(f=e.finalize(f));for(var o=this._oKey=f.clone(),m=this._iKey=f.clone(),q=o.words,r=m.words,b=0;b= base64Str.length) { - return END_OF_INPUT; - } - c = base64Str.charCodeAt(base64Count) & 0xff; - base64Count = base64Count + 1; - return c; - }; - - encodeBase64 = function(str) { - var result, inBuffer, done; - setBase64Str(str); - result = ''; - inBuffer = new Array(3); - done = false; - while (!done && (inBuffer[0] = readBase64()) !== END_OF_INPUT) { - inBuffer[1] = readBase64(); - inBuffer[2] = readBase64(); - result = result + (base64Chars[inBuffer[0] >> 2]); - if (inBuffer[1] !== END_OF_INPUT) { - result = result + (base64Chars[((inBuffer[0] << 4) & 0x30) | ( - inBuffer[1] >> 4)]); - if (inBuffer[2] !== END_OF_INPUT) { - result = result + (base64Chars[((inBuffer[1] << 2) & 0x3c) | ( - inBuffer[2] >> 6)]); - result = result + (base64Chars[inBuffer[2] & 0x3F]); - } else { - result = result + (base64Chars[((inBuffer[1] << 2) & 0x3c)]); - result = result + ('='); - done = true; - } - } else { - result = result + (base64Chars[((inBuffer[0] << 4) & 0x30)]); - result = result + ('='); - result = result + ('='); - done = true; - } - } - return result; - }; - - readReverseBase64 = function() { - if (!base64Str) { - return END_OF_INPUT; - } - while (true) { - if (base64Count >= base64Str.length) { - return END_OF_INPUT; - } - var nextCharacter = base64Str.charAt(base64Count); - base64Count = base64Count + 1; - if (reverseBase64Chars[nextCharacter]) { - return reverseBase64Chars[nextCharacter]; - } - if (nextCharacter === 'A') { - return 0; - } - } - }; - - ntos = function(n) { - n = n.toString(16); - if (n.length === 1) { - n = "0" + n; - } - n = "%" + n; - return unescape(n); - }; - - decodeBase64 = function(str) { - var result, inBuffer, done; - setBase64Str(str); - result = ""; - inBuffer = new Array(4); - done = false; - while (!done && (inBuffer[0] = readReverseBase64()) !== END_OF_INPUT && - (inBuffer[1] = readReverseBase64()) !== END_OF_INPUT) { - inBuffer[2] = readReverseBase64(); - inBuffer[3] = readReverseBase64(); - result = result + ntos((((inBuffer[0] << 2) & 0xff) | inBuffer[1] >> - 4)); - if (inBuffer[2] !== END_OF_INPUT) { - result += ntos((((inBuffer[1] << 4) & 0xff) | inBuffer[2] >> 2)); - if (inBuffer[3] !== END_OF_INPUT) { - result = result + ntos((((inBuffer[2] << 6) & 0xff) | inBuffer[ - 3])); - } else { - done = true; - } - } else { - done = true; - } - } - return result; - }; - return { - encodeBase64: encodeBase64, - decodeBase64: decodeBase64 - }; -}(OWT_REST)); - -'use strict'; -var Url = require("url"); -var OWT_REST = OWT_REST || {}; -OWT_REST.API = (function(OWT_REST) { - 'use strict'; - var version = 'v1'; - var params = { - service: undefined, - key: undefined, - url: undefined, - rejectUnauthorizedCert: undefined - }; - - function calculateSignature (toSign, key) { - var hash, hex, signed; - hash = CryptoJS.HmacSHA256(toSign, key); - hex = hash.toString(CryptoJS.enc.Hex); - signed = OWT_REST.Base64.encodeBase64(hex); - return signed; - }; - - function send (method, resource, body) { - return new Promise((resolve,reject)=>{ - var url = Url.parse(params.url + resource); - var ssl = (url.protocol === 'https:' ? true : false); - var timestamp = new Date().getTime(); - var cnounce = require('crypto').randomBytes(8).toString('hex'); - var toSign = timestamp + ',' + cnounce; - var header = 'MAuth realm=http://marte3.dit.upm.es,mauth_signature_method=HMAC_SHA256'; - - header += ',mauth_serviceid='; - header += params.service; - header += ',mauth_cnonce='; - header += cnounce; - header += ',mauth_timestamp='; - header += timestamp; - header += ',mauth_signature='; - header += calculateSignature(toSign, params.key); - - var options = { - hostname: url.hostname, - port: url.port || (ssl ? 443 : 80), - path: url.pathname + (url.search ? url.search : ''), - method: method, - headers: { - 'Host': url.hostname, - 'Authorization': header - } - }; - ssl && (params.rejectUnauthorizedCert !== undefined) && (options.rejectUnauthorized = params.rejectUnauthorizedCert); - - var bodyJSON; - if (body) { - bodyJSON = JSON.stringify(body); - options.headers['Content-Type'] = 'application/json'; - options.headers['Content-Length'] = Buffer.byteLength(bodyJSON); - } else { - options.headers['Content-Type'] = 'text/plain;charset=UTF-8'; - } - - var doRequest = (ssl ? require('https').request : require('http').request); - var req = doRequest(options, (res) => { - res.setEncoding("utf8"); - var resTxt = ''; - var status = res.statusCode; - - res.on('data', (chunk) => { - resTxt += chunk; - }); - - res.on('end', () => { - if (status === 100 || (status >= 200 && status <= 205)) { - resolve(resTxt); - } else { - reject(status, resTxt); - } - }); - - req.on('error', (err) => { - console.error('Send http(s) error:', err); - reject(503, err); - }); - }).on('error', (err) => { - console.error('Build http(s) req error:', err); - reject(503, err); - }); - - bodyJSON && req.write(bodyJSON); - - req.end(); - }) - }; - - var init = function(service, key, url, rejectUnauthorizedCert) { - if (typeof service !== 'string' || service === '') { - throw new TypeError('Invalid service ID'); - } - if (typeof key !== 'string' || key === '') { - throw new TypeError('Invalid service key'); - } - if (typeof url !== 'string' || url === '') { - throw new TypeError('Invalid URL.'); - } - if (typeof rejectUnauthorizedCert !== 'boolean' && rejectUnauthorizedCert !== undefined) { - throw new TypeError('Invalid certificate setting'); - } - params.service = service; - params.key = key; - params.url = (url.endsWith('/') ? (url + version + '/') : (url + '/' + version + '/')); - params.rejectUnauthorizedCert = (rejectUnauthorizedCert === undefined ? true : rejectUnauthorizedCert); - }; - - var getRooms = function(option) { - option = option || {}; - var page = option.page || 1; - var per_page = option.per_page || 50; - var query = '?page=' + page + '&per_page=' + per_page; - return new Promise((resolve,reject)=>{ - send('GET', 'rooms' + query) - .then((rooms)=>{ - rooms = JSON.parse(rooms); - resolve(rooms); - },err=>{ - reject(err); - }) - }) - }; - var updateRoom = function(room) { - return new Promise((resolve,reject)=>{ - var options=config.updateRoomOptions - send('PUT', 'rooms/' + room, options) - .then((room)=>{ - var room = JSON.parse(room); - resolve(room) - },err =>{ - reject(err) - }) - }) - }; - - var getStreams = function(room, callback, callbackError) { - return new Promise((resolve,reject)=>{ - send('GET', 'rooms/' + room + '/streams/') - .then((streamsRtn)=>{ - var streams = JSON.parse(streamsRtn); - resolve(streams) - },err=>{ - reject(err) - }) - }) - }; - - var startStreamingIn = function(room) { - var pub_req = { - connection: { - url: config.url, - transportProtocol: "udp", - bufferSize: 8192, - }, - media: { - audio: "auto", - video: "auto" - } - }; - return new Promise((resolve,reject)=>{ - send('POST', 'rooms/' + room + '/streaming-ins/', pub_req) - .then((stream)=>{ - var streaminId = stream.id; - resolve(stream) - },err=>{ - reject(err) - }) - }) - }; - - var mixStream = function(room, stream) { - var items = [{"op":"add","path":"/info/inViews","value":"common"}] - return new Promise((resolve,reject)=>{ - send('PATCH', 'rooms/' + room + '/streams/' + stream, items) - .then((stream)=>{ - var streaminId = stream.id; - resolve(stream) - },err=>{ - reject(err) - }) - }) - }; - - var stopStreamingIn = function(room, stream) { - return new Promise((resolve,reject)=>{ - send('DELETE', 'rooms/' + room + '/streaming-ins/' + stream) - .then(()=>{ - console.log("delele success") - resolve() - },err=>{ - reject(err) - }) - }) - }; - return { - init: init, - getRooms: getRooms, - updateRoom: updateRoom, - getStreams: getStreams, - startStreamingIn: startStreamingIn, - mixStream: mixStream, - stopStreamingIn: stopStreamingIn, - }; -}(OWT_REST)); -module.exports = OWT_REST; diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/updateRoom.js b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/updateRoom.js deleted file mode 100755 index 65d4a0ae..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/updateRoom.js +++ /dev/null @@ -1,16 +0,0 @@ -var icsREST = require('./rest.js'); -var config = require('./config.js'); -icsREST.API.init('_service_ID_', '_service_KEY_', config.serverUrl, false); -var sampleRoomId; -icsREST.API.getRooms() - .then((rooms) => { - for (var i = 0; i <= rooms.length - 1; i++) { - if (rooms[i].name === "sampleRoom") { - sampleRoomId = rooms[i]._id; - } - } - return icsREST.API.updateRoom(sampleRoomId) - }) - .then((room) => { - console.log(`updateRoom success => resolution : ${room.views[0].video.parameters.resolution.width} x ${room.views[0].video.parameters.resolution.height}`) - }) diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/sleep.sh b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/sleep.sh deleted file mode 100755 index a2420f12..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/sleep.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -e - -sleep infinity diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/start.sh b/WebRTC-Sample/owt-server/image/owt-immersive/scripts/start.sh deleted file mode 100755 index bd831eab..00000000 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/start.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -e - -cd /home/owt - -./bin/start-all.sh diff --git a/WebRTC-Sample/owt-server/rest/control.js b/WebRTC-Sample/owt-server/rest/control.js new file mode 100755 index 00000000..787da2d6 --- /dev/null +++ b/WebRTC-Sample/owt-server/rest/control.js @@ -0,0 +1,288 @@ +#!/usr/bin/env node +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +var initdb = require('./scripts/initdb'); +var rest = require('./scripts/rest'); + +const serverl_url = 'https://localhost:3000'; +const db_user_passwd = '' // 'user:passwd@' +const db_url = `${db_user_passwd}localhost/owtdb`; + +function RoomSetResolution(room, resolution) { + var update = false; + + if (room.views[0].video.parameters.resolution.width != resolution.width || + room.views[0].video.parameters.resolution.height != resolution.height) { + console.log( + 'Update room resolution', + room.views[0].video.parameters.resolution.width, 'x', + room.views[0].video.parameters.resolution.height, '=>', + resolution.width, 'x', resolution.height); + + room.views[0].video.parameters.resolution = resolution; + update = true; + } else { + console.log( + 'Room resolution', room.views[0].video.parameters.resolution.width, 'x', + room.views[0].video.parameters.resolution.height); + } + + return update; +} + +function RoomSetVideoCodec(room, codec) { + var update = false; + + // mediaIn + { + var find = false; + for (var v of room.mediaIn.video) { + if (v.codec === codec) { + find = true; + break; + } + } + + if (!find) { + room.mediaIn.video.push({codec: codec}); + update |= true; + + console.log(`Update room mediaIn, ${codec}`); + } + } + + // mediaOut + { + var find = false; + for (var v of room.mediaOut.video.format) { + if (v.codec === codec) { + find = true; + break; + } + } + + if (!find) { + room.mediaOut.video.format.push({codec: codec}); + update |= true; + + console.log(`Update room mediaOut, ${codec}`); + } + } + + /* + room.mediaIn.video = [ + {'codec': 'h264'}, {'codec': 'vp8'}, {'codec': 'vp9'}, {'codec': 'h265'} + ]; + room.mediaOut.video.format = [ + {'codec': 'vp8'}, {'profile': 'CB', 'codec': 'h264'}, {'codec': 'vp9'}, + {'codec': 'h265'} + ]; + */ + + return update; +} + +async function UpdateRoom(resolution) { + var service = await initdb.GetService(db_url); + rest.API.init( + service.sampleServiceId, service.sampleServiceKey, serverl_url, false); + + rest.API.getRooms( + null, + function(rooms) { + for (var room of rooms) { + console.log('Room', ':', room.name); + const update = RoomSetResolution(room, resolution) || + RoomSetVideoCodec(room, 'h265'); + if (update) { + const room_id = room._id; + rest.API.updateRoom( + room_id, room, + function(res) { + console.log('Room', room_id, 'updated'); + }, + function(err) { + console.log('Error:', err); + }); + } + } + }, + function(status, error) { + // HTTP status and error + console.log(status, error); + }); + + return; +} + +async function AddStream(url) { + var service = await initdb.GetService(db_url); + rest.API.init( + service.sampleServiceId, service.sampleServiceKey, serverl_url, false); + + rest.API.getRooms( + null, + function(rooms) { + for (var room of rooms) { + if (room.name === 'sampleRoom') { + const room_id = room._id; + + var transport = {protocol: 'udp', bufferSize: 2048}; + var media = {audio: 'auto', video: true}; + + rest.API.startStreamingIn( + room_id, url, transport, media, + function(stream) { + // console.log('Streaming-In:', stream); + var items = [ + {'op': 'add', 'path': '/info/inViews', 'value': 'common'} + ] + rest.API.updateStream( + room_id, stream.id, items, + function(stream) { + console.log(`Stream ${stream.id} added`); + }, + function(status, error) { + // HTTP status and error + console.log(status, error); + }); + }, + function(status, error) { + // HTTP status and error + console.log(status, error); + }); + + break; + } + } + }, + function(status, error) { + // HTTP status and error + console.log(status, error); + }); +} + +async function ClearStreams() { + var service = await initdb.GetService(db_url); + rest.API.init( + service.sampleServiceId, service.sampleServiceKey, serverl_url, false); + + rest.API.getRooms( + null, + function(rooms) { + for (var room of rooms) { + if (room.name === 'sampleRoom') { + const room_id = room._id; + + rest.API.getStreams( + room_id, + function(streams) { + // console.log ('This room has ', streams.length, 'streams'); + for (var s of streams) { + // console.log(s); + if (s.type === 'forward' && s.info.type === 'streaming') { + const s_id = s.id; + rest.API.stopStreamingIn( + room_id, s_id, + function(result) { + console.log( + 'External streaming-in:', s_id, + 'in room:', room_id, 'stopped'); + }, + function(status, error) { + // HTTP status and error + console.log(status, error); + }); + } + } + }, + function(status, error) { + // HTTP status and error + console.log(status, error); + }); + + break; + } + } + }, + function(status, error) { + // HTTP status and error + console.log(status, error); + }); +} + +function Usage() { + console.log(`\n\ +Set owt server video encoding resolution, publish a stream, remove all streams. + +Options: + -s [4k|8k] Set server video encoding resolution 3840x2048 or 7680x3840, default is 3840x2048 + -c url Connect a FILE|RTSP|RTMP stream to server + -d Disconnect all streams from server"\ +`) + + process.exit() +} + +function ParseArgs(args) { + var i = 0; + var opts = {}; + + while (i < args.length) { + switch (args[i]) { + case '-s': + if (i + 2 != args.length) + Usage(); + + opts.room_resolution = args[i + 1]; + + if (opts.room_resolution === '4k') { + opts.room_resolution = {width: 3840, height: 2048}; + } else if (opts.room_resolution === '8k') { + opts.room_resolution = {width: 7680, height: 3840}; + } else { + Usage(); + } + + i += 2; + break; + case '-c': + if (i + 2 != args.length) + Usage(); + + opts.stream_url = args[i + 1]; + i += 2; + break; + case '-d': + if (i + 1 != args.length) + Usage(); + + opts.clear_steams = true; + i += 1; + break; + default: + Usage(); + break; + } + + break; + } + + if (Object.keys(opts).length === 0) + Usage(); + + return opts; +} + +var options = ParseArgs(process.argv.slice(2)); +console.log(options); + +if (options.room_resolution) + UpdateRoom(options.room_resolution); + +if (options.stream_url) + AddStream(options.stream_url); + +if (options.clear_steams) + ClearStreams(); diff --git a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/cipher.js b/WebRTC-Sample/owt-server/rest/scripts/cipher.js old mode 100755 new mode 100644 similarity index 99% rename from WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/cipher.js rename to WebRTC-Sample/owt-server/rest/scripts/cipher.js index 653d430b..22814070 --- a/WebRTC-Sample/owt-server/image/owt-immersive/scripts/scripts/cipher.js +++ b/WebRTC-Sample/owt-server/rest/scripts/cipher.js @@ -17,7 +17,7 @@ function encrypt (password, text){ enc += cipher.final('hex'); return enc; } - + function decrypt (password, text){ var decipher = crypto.createDecipheriv(algorithm, password, iv); var dec = decipher.update(text, 'hex', 'utf8'); diff --git a/WebRTC-Sample/owt-server/rest/scripts/initdb.js b/WebRTC-Sample/owt-server/rest/scripts/initdb.js new file mode 100644 index 00000000..97c6d176 --- /dev/null +++ b/WebRTC-Sample/owt-server/rest/scripts/initdb.js @@ -0,0 +1,71 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +'use strict'; + +var cipher = require('./cipher'); +var db; + +function prepareDB(db_url) { + db = require('mongojs')(db_url, ['services', 'infos', 'rooms']); +} + +function checkVersion() { + return new Promise((resolve, reject) => { + db.infos.findOne({_id: 1}, function cb(err, info) { + if (err) { + db.close(); + reject('mongodb: error in query info'); + } + if (info && info.version === '1.0') { + resolve(); + } else { + db.close(); + reject('mongodb: invalid info'); + } + }); + }); +} + +function prepareService(serviceName) { + return new Promise((resolve, reject) => { + db.services.findOne({name: serviceName}, function cb(err, service) { + if (err || !service) { + db.close(); + reject('mongodb: error in query service'); + } else { + if (service.encrypted === true) { + service.key = cipher.decrypt(cipher.k, service.key); + } + resolve(service); + } + }); + }); +} + +async function GetService(db_url) { + var sample_service; + + prepareDB(db_url); + await checkVersion() + .then(() => {return prepareService('sampleService')}) + .then((service) => { + var sampleServiceId = service._id + ''; + var sampleServiceKey = service.key; + // console.log('sampleServiceId:', sampleServiceId); + // console.log('sampleServiceKey:', sampleServiceKey); + db.close(); + + + sample_service = {}; + sample_service.sampleServiceId = sampleServiceId; + sample_service.sampleServiceKey = sampleServiceKey; + }) + + return sample_service; +} + +module.exports = { + GetService: GetService +}; diff --git a/WebRTC-Sample/owt-server/rest/scripts/package.json b/WebRTC-Sample/owt-server/rest/scripts/package.json new file mode 100644 index 00000000..3ed92335 --- /dev/null +++ b/WebRTC-Sample/owt-server/rest/scripts/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "mongojs": "" + } +} diff --git a/WebRTC-Sample/owt-server/rest/scripts/rest.js b/WebRTC-Sample/owt-server/rest/scripts/rest.js new file mode 100644 index 00000000..decfd270 --- /dev/null +++ b/WebRTC-Sample/owt-server/rest/scripts/rest.js @@ -0,0 +1,1693 @@ +/* +CryptoJS v3.0.2 +code.google.com/p/crypto-js +(c) 2009-2012 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +'use strict'; +var CryptoJS=CryptoJS||function(h,i){var e={},f=e.lib={},l=f.Base=function(){function a(){}return{extend:function(j){a.prototype=this;var d=new a;j&&d.mixIn(j);d.$super=this;return d},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var d in a)a.hasOwnProperty(d)&&(this[d]=a[d]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),k=f.WordArray=l.extend({init:function(a,j){a= +this.words=a||[];this.sigBytes=j!=i?j:4*a.length},toString:function(a){return(a||m).stringify(this)},concat:function(a){var j=this.words,d=a.words,c=this.sigBytes,a=a.sigBytes;this.clamp();if(c%4)for(var b=0;b>>2]|=(d[b>>>2]>>>24-8*(b%4)&255)<<24-8*((c+b)%4);else if(65535>>2]=d[b>>>2];else j.push.apply(j,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,b=this.sigBytes;a[b>>>2]&=4294967295<<32-8*(b%4);a.length=h.ceil(b/4)},clone:function(){var a= +l.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var b=[],d=0;d>>2]>>>24-8*(c%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>3]|=parseInt(a.substr(c,2),16)<<24-4*(c%8);return k.create(d,b/2)}},q=o.Latin1={stringify:function(a){for(var b= +a.words,a=a.sigBytes,d=[],c=0;c>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return k.create(d,b)}},r=o.Utf8={stringify:function(a){try{return decodeURIComponent(escape(q.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return q.parse(unescape(encodeURIComponent(a)))}},b=f.BufferedBlockAlgorithm=l.extend({reset:function(){this._data=k.create(); +this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=r.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,c=b.sigBytes,e=this.blockSize,g=c/(4*e),g=a?h.ceil(g):h.max((g|0)-this._minBufferSize,0),a=g*e,c=h.min(4*a,c);if(a){for(var f=0;fg;)e(b)&&(8>g&&(k[g]=f(h.pow(b,0.5))),o[g]=f(h.pow(b,1/3)),g++),b++})();var m=[],l=l.SHA256=e.extend({_doReset:function(){this._hash=f.create(k.slice(0))},_doProcessBlock:function(e,f){for(var b=this._hash.words,g=b[0],a=b[1],j=b[2],d=b[3],c=b[4],h=b[5],l=b[6],k=b[7],n=0;64> +n;n++){if(16>n)m[n]=e[f+n]|0;else{var i=m[n-15],p=m[n-2];m[n]=((i<<25|i>>>7)^(i<<14|i>>>18)^i>>>3)+m[n-7]+((p<<15|p>>>17)^(p<<13|p>>>19)^p>>>10)+m[n-16]}i=k+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&h^~c&l)+o[n]+m[n];p=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&a^g&j^a&j);k=l;l=h;h=c;c=d+i|0;d=j;j=a;a=g;g=i+p|0}b[0]=b[0]+g|0;b[1]=b[1]+a|0;b[2]=b[2]+j|0;b[3]=b[3]+d|0;b[4]=b[4]+c|0;b[5]=b[5]+h|0;b[6]=b[6]+l|0;b[7]=b[7]+k|0},_doFinalize:function(){var e=this._data,f=e.words,b=8*this._nDataBytes, +g=8*e.sigBytes;f[g>>>5]|=128<<24-g%32;f[(g+64>>>9<<4)+15]=b;e.sigBytes=4*f.length;this._process()}});i.SHA256=e._createHelper(l);i.HmacSHA256=e._createHmacHelper(l)})(Math); +(function(){var h=CryptoJS,i=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(e,f){e=this._hasher=e.create();"string"==typeof f&&(f=i.parse(f));var h=e.blockSize,k=4*h;f.sigBytes>k&&(f=e.finalize(f));for(var o=this._oKey=f.clone(),m=this._iKey=f.clone(),q=o.words,r=m.words,b=0;b Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +// This file is borrowed from lynckia/licode with some modifications. + +'use strict'; + +/*global unescape*/ +var OWT_REST = OWT_REST || {}; +OWT_REST.Base64 = (function(OWT_REST) { + var END_OF_INPUT, base64Chars, reverseBase64Chars, base64Str, base64Count, + i, setBase64Str, readBase64, encodeBase64, readReverseBase64, ntos, + decodeBase64; + + END_OF_INPUT = -1; + + base64Chars = [ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + ]; + + reverseBase64Chars = []; + + for (i = 0; i < base64Chars.length; i = i + 1) { + reverseBase64Chars[base64Chars[i]] = i; + } + + setBase64Str = function(str) { + base64Str = str; + base64Count = 0; + }; + + readBase64 = function() { + var c; + if (!base64Str) { + return END_OF_INPUT; + } + if (base64Count >= base64Str.length) { + return END_OF_INPUT; + } + c = base64Str.charCodeAt(base64Count) & 0xff; + base64Count = base64Count + 1; + return c; + }; + + encodeBase64 = function(str) { + var result, inBuffer, done; + setBase64Str(str); + result = ''; + inBuffer = new Array(3); + done = false; + while (!done && (inBuffer[0] = readBase64()) !== END_OF_INPUT) { + inBuffer[1] = readBase64(); + inBuffer[2] = readBase64(); + result = result + (base64Chars[inBuffer[0] >> 2]); + if (inBuffer[1] !== END_OF_INPUT) { + result = result + (base64Chars[((inBuffer[0] << 4) & 0x30) | ( + inBuffer[1] >> 4)]); + if (inBuffer[2] !== END_OF_INPUT) { + result = result + (base64Chars[((inBuffer[1] << 2) & 0x3c) | ( + inBuffer[2] >> 6)]); + result = result + (base64Chars[inBuffer[2] & 0x3F]); + } else { + result = result + (base64Chars[((inBuffer[1] << 2) & 0x3c)]); + result = result + ('='); + done = true; + } + } else { + result = result + (base64Chars[((inBuffer[0] << 4) & 0x30)]); + result = result + ('='); + result = result + ('='); + done = true; + } + } + return result; + }; + + readReverseBase64 = function() { + if (!base64Str) { + return END_OF_INPUT; + } + while (true) { + if (base64Count >= base64Str.length) { + return END_OF_INPUT; + } + var nextCharacter = base64Str.charAt(base64Count); + base64Count = base64Count + 1; + if (reverseBase64Chars[nextCharacter]) { + return reverseBase64Chars[nextCharacter]; + } + if (nextCharacter === 'A') { + return 0; + } + } + }; + + ntos = function(n) { + n = n.toString(16); + if (n.length === 1) { + n = "0" + n; + } + n = "%" + n; + return unescape(n); + }; + + decodeBase64 = function(str) { + var result, inBuffer, done; + setBase64Str(str); + result = ""; + inBuffer = new Array(4); + done = false; + while (!done && (inBuffer[0] = readReverseBase64()) !== END_OF_INPUT && + (inBuffer[1] = readReverseBase64()) !== END_OF_INPUT) { + inBuffer[2] = readReverseBase64(); + inBuffer[3] = readReverseBase64(); + result = result + ntos((((inBuffer[0] << 2) & 0xff) | inBuffer[1] >> + 4)); + if (inBuffer[2] !== END_OF_INPUT) { + result += ntos((((inBuffer[1] << 4) & 0xff) | inBuffer[2] >> 2)); + if (inBuffer[3] !== END_OF_INPUT) { + result = result + ntos((((inBuffer[2] << 6) & 0xff) | inBuffer[ + 3])); + } else { + done = true; + } + } else { + done = true; + } + } + return result; + }; + + return { + encodeBase64: encodeBase64, + decodeBase64: decodeBase64 + }; +}(OWT_REST)); + +// MIT License +// +// Copyright (c) 2012 Universidad Politécnica de Madrid +// +// 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. + +// Copyright (C) <2018> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +// This file is borrowed from lynckia/licode with some modifications. + +'use strict'; + +/*global require, CryptoJS, Buffer, url, http, https*/ +var Url = require("url"); + +var OWT_REST = OWT_REST || {}; + +/**@namespace OWT_REST + * @classDesc Namespace for OWT(Intel Collaboration Suite) REST API definition. + */ +/** + * @class OWT_REST.API + * @classDesc Server-side APIs should be called by RTC service integrators, as demostrated in sampleRTCService.js. Server-side APIs are RESTful, provided as a Node.js module. All APIs, except OWT_REST.API.init(), should not be called too frequently. These API calls carry local timestamps and are grouped by serviceID. Once the server is handling an API call from a certain serviceID, all other API calls from the same serviceID, whose timestamps are behind, would be expired or treated as invalid.
+We recommend that API calls against serviceID should have interval of at least 100ms. Also, it is better to retry the logic if it fails with an unexpected timestamp error. + */ +OWT_REST.API = (function(OWT_REST) { + 'use strict'; + var version = 'v1'; + var params = { + service: undefined, + key: undefined, + url: undefined, + rejectUnauthorizedCert: undefined + }; + + function calculateSignature (toSign, key) { + var hash, hex, signed; + hash = CryptoJS.HmacSHA256(toSign, key); + hex = hash.toString(CryptoJS.enc.Hex); + signed = OWT_REST.Base64.encodeBase64(hex); + return signed; + }; + + function send (method, resource, body, onOK, onError) { + var url = Url.parse(params.url + resource); + var ssl = (url.protocol === 'https:' ? true : false); + var timestamp = new Date().getTime(); + var cnounce = require('crypto').randomBytes(8).toString('hex'); + var toSign = timestamp + ',' + cnounce; + var header = 'MAuth realm=http://marte3.dit.upm.es,mauth_signature_method=HMAC_SHA256'; + + header += ',mauth_serviceid='; + header += params.service; + header += ',mauth_cnonce='; + header += cnounce; + header += ',mauth_timestamp='; + header += timestamp; + header += ',mauth_signature='; + header += calculateSignature(toSign, params.key); + + var options = { + hostname: url.hostname, + port: url.port || (ssl ? 443 : 80), + path: url.pathname + (url.search ? url.search : ''), + method: method, + headers: { + 'Host': url.hostname, + 'Authorization': header + } + }; + ssl && (params.rejectUnauthorizedCert !== undefined) && (options.rejectUnauthorized = params.rejectUnauthorizedCert); + + var bodyJSON; + if (body) { + bodyJSON = JSON.stringify(body); + options.headers['Content-Type'] = 'application/json'; + options.headers['Content-Length'] = Buffer.byteLength(bodyJSON); + } else { + options.headers['Content-Type'] = 'text/plain;charset=UTF-8'; + } + + var doRequest = (ssl ? require('https').request : require('http').request); + var req = doRequest(options, (res) => { + res.setEncoding("utf8"); + var resTxt = ''; + var status = res.statusCode; + + res.on('data', (chunk) => { + resTxt += chunk; + }); + + res.on('end', () => { + if (status === 100 || (status >= 200 && status <= 205)) { + onOK(resTxt); + } else { + onError(status, resTxt); + } + }); + + req.on('error', (err) => { + console.error('Send http(s) error:', err); + onError(503, err); + }); + }).on('error', (err) => { + console.error('Build http(s) req error:', err); + onError(503, err); + }); + + bodyJSON && req.write(bodyJSON); + + req.end(); + }; + + /** + * @function init + * @desc This function completes the essential configuration. +
Remarks:
+ Make sure you use the correct OWT_REST server url, according to the OWT_REST ssl configuration. + * @memberOf OWT_REST.API + * @param {string} service -The ID of your service. + * @param {string} key -The key of your service. + * @param {string} url -The URL of OWT service. + * @param {boolean} rejectUnauthorizedCert -Flag to determine whether reject unauthorized certificates, with value being true or false, true by default. + * @example + OWT_REST.API.init('5188b9af6e53c84ffd600413', '21989', 'http://61.129.90.140:3000/', true) + */ + var init = function(service, key, url, rejectUnauthorizedCert) { + if (typeof service !== 'string' || service === '') { + throw new TypeError('Invalid service ID'); + } + if (typeof key !== 'string' || key === '') { + throw new TypeError('Invalid service key'); + } + if (typeof url !== 'string' || url === '') { + throw new TypeError('Invalid URL.'); + } + if (typeof rejectUnauthorizedCert !== 'boolean' && rejectUnauthorizedCert !== undefined) { + throw new TypeError('Invalid certificate setting'); + } + params.service = service; + params.key = key; + params.url = (url.endsWith('/') ? (url + version + '/') : (url + '/' + version + '/')); + params.rejectUnauthorizedCert = (rejectUnauthorizedCert === undefined ? true : rejectUnauthorizedCert); + }; + + // Convert a viewports object to views which is defined in MCU. + function viewportsToViews(viewports) { + var view = {}; + viewports.forEach(function(viewport) { + view[viewport.name] = { + mediaMixing: viewport.mediaMixing + }; + }); + return view; + } + + /** + * @function createRoom + * @desc This function creates a room. +
Remarks:
+ options: +
+
    +
  • mode:"hybrid" for room with mixing and forward streams.
  • +
  • publishLimit:limiting number of publishers in the room. Value should be equal to or greater than -1. -1 for unlimited.
  • +
  • userLimit:limiting number of users in the room. Value should be equal to or greater than -1. -1 for unlimited.
  • +
  • enableMixing:control whether to enable media mixing in the room, with value choices 0 or 1.
  • +
  • viewports:viewport setting for mixed stream in the room if mixing is enabled. A corresponding mixed stream will be created for each viewport. Values should be an array. Each item has two properties listed as follow
  • +
      +
    • name:the name for this viewport.
    • +
    • mediaMixing:media setting for mixed stream in the room if mixing is enabled. Value should be a JSON object contains two entries: "video" and "audio". Audio entry is currently not used and should be null.
    • +
        +
      • audio: null
      • +
      • video: maxInput, resolution, quality_level, bkColor, layout, avCoordinate, crop
      • +
          +
        • maxInput is for maximum number of slots in the mix stream
        • +
        • resolution denotes the resolution of the video size of mix stream.Valid resolution list:
        • +
            +
          • 'sif'
          • +
          • 'vga'
          • +
          • 'svga'
          • +
          • 'xga'
          • +
          • 'hd720p'
          • +
          • 'hd1080p'
          • +
          • 'uhd_4k'
          • +
          • 'r720x720'
          • +
          • 'r720x1080'
          • +
          • 'r1080x1920'
          • +
          +
        • quality_level indicates the default video quality of the mix stream (choose from "bestSpeed", "betterSpeed", "standard", "betterQuality", "bestQuality").
        • +
        • bkColor sets the background color, supporting RGB color format: {"r":red-value, "g":green-value, "b":blue-value}.
        • +
        • layout describes video layout in mix stream
        • +
            +
          • "base" is the base template (choose from "void", "fluid", "lecture")
          • +
          • If base layout is set to 'void', user must input customized layout for the room, otherwise the video layout would be treated as invalid.
          • +
          • "custom" is user-defined customized video layout. Here we give out an example to show you the details of a valid customized video layout.A valid customized video layout should be a JSON string which represents an array of video layout definition. More details see [customized video layout](@ref layout) .
          • +
          • MCU would try to combine the two entries for mixing video if user sets both.
          • +
          +
        • avCoordinated (0 or 1) is for disabling/enabling VAD(Voice activity detection). When VAD is applied, main pane(layout id=1) will be filled with the user stream which is the most active in voice currently.
        • +
        • crop (0 or 1) is for disabling/enabling video cropping to fit in the region assigned to it in the mixed video.
        • +
        +
      +
    +
+ Omitted entries are set with default values. + All supported resolutions are list in the following table. + @htmlonly + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table : Resolution Mapping for Multistreaming
Base resolutionAvailable resolution list
sif{width: 320, height: 240}
vga{width: 640, height: 480}
svga{width: 800, height: 600}
xga{width: 1024, height: 768}
hd720p{width: 1280, height: 720}, {width: 640, height: 480}, {width: 640, height: 360}
hd1080p{width: 1920, height: 1080}, {width: 1280, height: 720}, {width: 800, height: 600}, {width: 640, height: 480}, {width: 640, height: 360}
uhd_4k{width: 3840, height: 2160}, {width: 1920, height: 1080}, {width: 1280, height: 720}, {width: 800, height: 600}, {width: 640, height: 480}
r720x720{width: 720, height: 720}, {width: 480, height: 480}, {width: 360, height: 360}
r720x1080{width: 720, height: 1280}, {width: 540, height: 960}, {width: 480, height: 853}, {width: 360, height: 640}, {width: 240, height: 426}, {width: 180, height: 320}, {width: 640, height: 480}, {width: 352, height: 288}
r1080x1920{width: 1080, height: 1920}, {width: 810, height: 1440}, {width: 720, height: 1280}, {width: 540, height: 960}, {width: 360, height: 640}, {width: 270, height: 480}, {width: 800, height: 600}, {width: 640, height: 480}, {width: 352, height: 288}
+ @endhtmlonly + * @memberOf OWT_REST.API + * @param {string} name -Room name. + * @param {json} options -Room configuration. + * @param {function} callback -Callback function on success. + * @param {function} callbackError -Callback function on error. + * @example + OWT_REST.API.createRoom('myRoom', { + mode: 'hybrid', + publishLimit: -1, + userLimit: 30, + viewports: [ + { + name: "common", + mediaMixing: { + video: { + maxInput: 15, + resolution: 'hd720p', + quality_level: 'standard', + bkColor: {"r":1, "g":2, "b":255}, + layout: { + base: 'lecture', + }, + avCoordinated: 1, + crop: 1 + }, + audio: null + }, + }, + { + name: "another", + mediaMixing: { + video: { + maxInput: 15, + resolution: 'hd1080p', + quality_level: 'standard', + bkColor: {"r":1, "g":2, "b":255}, + layout: { + base: 'lecture', + }, + avCoordinated: 1, + crop: 1 + }, + audio: null + }, + } + ] + }, function (res) { + console.log ('Room', res.name, 'created with id:', res._id); + }, function (err) { + console.log ('Error:', err); + }); + */ + var createRoom = function(name, options, callback, callbackError) { + if (!options) { + options = {}; + } + + if (options.viewports) { + options.views = viewportsToViews(options.viewports); + delete options.viewports; + } + + send('POST', 'rooms', { + name: name, + options: options + }, function(roomRtn) { + var room = JSON.parse(roomRtn); + callback(room); + }, callbackError); + }; + + /** + * @function getRooms + * @desc This function lists the rooms in your service. + * @memberOf OWT_REST.API + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + OWT_REST.API.getRooms(function(rooms) { + for(var i in rooms) { + console.log('Room', i, ':', rooms[i].name); + } + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getRooms = function(option, callback, callbackError) { + option = option || {}; + var page = option.page || 1; + var per_page = option.per_page || 50; + var query = '?page=' + page + '&per_page=' + per_page; + send('GET', 'rooms' + query, undefined, function(roomsRtn) { + var rooms = JSON.parse(roomsRtn); + callback(rooms); + }, callbackError); + }; + + /** + * @function getRoom + * @desc This function returns information on the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + OWT_REST.API.getRoom(roomId, function(room) { + console.log('Room name:', room.name); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getRoom = function(room, callback, callbackError) { + if (typeof room !== 'string') { + callbackError(401, 'Invalid room ID.'); + return; + } + if (room.trim() === '') { + callbackError(401, 'Empty room ID'); + return; + } + send('GET', 'rooms/' + room, undefined, function(roomRtn) { + var room = JSON.parse(roomRtn); + callback(room); + }, callbackError); + }; + + /** + * @function deleteRoom + * @desc This function deletes the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID to be deleted + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var room = '51c10d86909ad1f939000001'; + OWT_REST.API.deleteRoom(room, function(result) { + console.log ('Result:' result); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var deleteRoom = function(room, callback, callbackError) { + send('DELETE', 'rooms/' + room, undefined, function(room) { + callback(room); + }, callbackError); + }; + + /** + * @function updateRoom + * @desc This function updates a room's configuration entirely. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {json} options -Room configuration. See details about options in {@link OWT_REST.API#createRoom createRoom(name, options, callback, callbackError)}. + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + OWT_REST.API.updateRoom(XXXXXXXXXX, { + publishLimit: -1, + userLimit: -1, + enableMixing: 1, + viewports: [ + { + name: "common", + mediaMixing: { + video: { + maxInput: 15, + resolution: 'hd720p', + quality_level: 'standard', + bkColor: {"r":1, "g":2, "b":255}, + layout: { + base: 'lecture', + }, + avCoordinated: 1, + crop: 1 + }, + audio: null + }, + }, + { + name: "another":, + mediaMixing: { + video: { + maxInput: 15, + resolution: 'hd1080p', + quality_level: 'standard', + bkColor: {"r":1, "g":2, "b":255}, + layout: { + base: 'lecture', + }, + avCoordinated: 1, + crop: 1 + }, + audio: null + }, + } + ] + }, function (res) { + console.log ('Room', res._id, 'updated'); + }, function (err) { + console.log ('Error:', err); + }); + */ + + var updateRoom = function(room, options, callback, callbackError) { + if (options && options.viewports) { + options.views = viewportsToViews(options.viewports); + delete options.viewports; + } + send('PUT', 'rooms/' + room, (options || {}), function(roomRtn) { + var room = JSON.parse(roomRtn); + callback(room); + }, callbackError); + }; + + /** + * @function updateRoomPartially + * @desc This function updates a room's configuration partially. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {Array.<{op: string, path: string, value: json}>} items -Configuration item list to be updated, with format following RFC6902(https://tools.ietf.org/html/rfc6902). + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + OWT_REST.API.updateRoomPartially(XXXXXXXXXX, [ + {op: 'replace', path: '/enableMixing', value: 0}, + {op: 'replace', path: '/viewports/0/mediaMixing/video/avCoordinated', value: 1} + ], function (res) { + console.log ('Room', res._id, 'updated'); + }, function (err) { + console.log ('Error:', err); + }); + */ + var updateRoomPartially = function(room, items, callback, callbackError) { + send('PATCH', 'rooms/' + room, (items || []), function(roomRtn) { + var new_room = JSON.parse(roomRtn); + callback(new_room); + }, callbackError); + }; + + /* + * * @callback onParticipantList + * * @param {Array.<{Object} ParticipantDetail>} participantList -The list of object "ParticipantDetail" same as defined in "onParticipantDetail" callback. + */ + /** + * @function getParticipants + * @desc This function lists participants currently in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {onParticipantList} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + OWT_REST.API.getParticipants(roomId, function(participants) { + var l = JSON.parse(participants); + console.log ('This room has ', l.length, 'participants'); + for (var i in l) { + console.log(i, ':', l[i]); + } + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getParticipants = function(room, callback, callbackError) { + send('GET', 'rooms/' + room + '/participants/', undefined, function(participantsRtn) { + var participants = JSON.parse(participantsRtn); + callback(participants); + }, callbackError); + }; + + /* + * * @callback onParticipantDetail + * * @param {Object} ParticipantDetail -The object containing the detailed info of the specified participant. + * * @param {string} ParticipantDetail.id -The participant ID. + * * @param {string} ParticipantDetail.role -The participant role. + * * @param {string} ParticipantDetail.user -The user ID of the participant. + * * @param {Object} ParticipantDetail.permission -The "Permission" object defined in section "3.3.1 Participant Joins a Room" in "Client-Portal Protocol" doc. + */ + /** + * @function getParticipant + * @desc This function gets a participant's information from the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} participant -Participant ID + * @param {onParticipantDetail} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var participantID = 'JdlUI29yjfVY6O4yAAAB'; + OWT_REST.API.getParticipant(roomId, participantID, function(participant) { + console.log('Participant:', participant); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getParticipant = function(room, participant, callback, callbackError) { + if (typeof participant !== 'string' || participant.trim().length === 0) { + return callbackError('Invalid participant ID'); + } + send('GET', 'rooms/' + room + '/participants/' + participant, undefined, function(participantRtn) { + var p = JSON.parse(participantRtn); + callback(p); + }, callbackError); + }; + + /* + * * @callback onParticipantDetail + * * @param {Object} participantDetail -The object containing the updated detailed info of the specified participant, same as in getParticipant. + */ + /** + * @function updateParticipant + * @desc This function updates the permission of a participant in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} participant -Participant ID + * @param {Array.<{op: string, path: string, value: json}>} items -Permission item list to be updated, with format following RFC6902(https://tools.ietf.org/html/rfc6902). + * @param {onParticipantDetail} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var participantID = 'JdlUI29yjfVY6O4yAAAB'; + OWT_REST.API.getParticipant(roomId, participantID, function(participant) { + console.log('Participant:', participant); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var updateParticipant = function(room, participant, items, callback, callbackError) { + if (typeof participant !== 'string' || participant.trim().length === 0) { + return callbackError('Invalid participant ID'); + } + if (!(items instanceof Array)) { + return callbackError('Invalid update list'); + } + send('PATCH', 'rooms/' + room + '/participants/' + participant, items, function(participantRtn) { + var p = JSON.parse(participantRtn); + callback(p); + }, callbackError); + }; + + /** + * @function dropParticipant + * @desc This function drops a participant from a room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} participant -Participant ID + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var participantID = 'JdlUI29yjfVY6O4yAAAB'; + OWT_REST.API.dropParticipant(roomId, participantID, function(res) { + console.log('Participant', participantID, 'in room', roomId, 'deleted'); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var dropParticipant = function(room, participant, callback, callbackError) { + if (typeof participant !== 'string' || participant.trim().length === 0) { + return callbackError('Invalid participant ID'); + } + send('DELETE', 'rooms/' + room + '/participants/' + participant, undefined, function(participant) { + callback(participant); + }, callbackError); + }; + + /* + * * @callback onStreamList + * * @param {Array.} streamList + * * @param {Object} streamList[x] -Object "StreamInfo" defined in section "3.3.1 Participant Joins a Room" in "Client-Portal Protocol" doc. + */ + /** + * @function getStreams + * @desc This function lists streams currently in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {onStreamList} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + OWT_REST.API.getStreams(roomId, function(streams) { + var l = JSON.parse(streams); + console.log ('This room has ', l.length, 'streams'); + for (var i in l) { + console.log(i, ':', l[i]); + } + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getStreams = function(room, callback, callbackError) { + send('GET', 'rooms/' + room + '/streams/', undefined, function(streamsRtn) { + var streams = JSON.parse(streamsRtn); + callback(streams); + }, callbackError); + }; + + /* + * * @callback onStreamInfo + * * @param {Object} streamInfo -Object "StreamInfo" defined in section "3.3.1 Participant Joins a Room" in "Client-Portal Protocol" doc. + */ + /** + * @function getStream + * @desc This function gets a stream's information from the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} stream -Stream ID + * @param {onStreamInfo} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var streamID = '878889273471677'; + OWT_REST.API.getStream(roomId, streamID, function(stream) { + console.log('Stream:', stream); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getStream = function(room, stream, callback, callbackError) { + if (typeof stream !== 'string' || stream.trim().length === 0) { + return callbackError('Invalid stream ID'); + } + send('GET', 'rooms/' + room + '/streams/' + stream, undefined, function(streamRtn) { + var st = JSON.parse(streamRtn); + callback(st); + }, callbackError); + }; + + /* + * * @callback onStreamInfo + * * @param {Object} streamInfo -Object "StreamInfo" defined in section "3.3.1 Participant Joins a Room" in "Client-Portal Protocol" doc. + */ + /** + * @function updateStream + * @desc This function updates a stream's given attributes in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} stream -Stream ID + * @param {Array.<{op: string, path: string, value: json}>} items -Attributes to be updated, with format following RFC6902(https://tools.ietf.org/html/rfc6902). + * @param {onStreamInfo} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var streamID = '878889273471677'; + OWT_REST.API.updateStream(roomId, streamID, [{op: 'replace', path: '/media/audio/status', value: 'inactive'}], function(stream) { + console.log('Stream:', stream); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var updateStream = function(room, stream, items, callback, callbackError) { + if (typeof stream !== 'string' || stream.trim().length === 0) { + return callbackError('Invalid stream ID'); + } + if (!(items instanceof Array)) { + return callbackError('Invalid update list'); + } + send('PATCH', 'rooms/' + room + '/streams/' + stream, items, function(streamRtn) { + var st = JSON.parse(streamRtn); + callback(st); + }, callbackError); + }; + + /** + * @function deleteStream + * @desc This function deletes the specified stream from the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} stream -Stream ID + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var streamID = '878889273471677'; + OWT_REST.API.deleteStream(roomId, streamID, function(result) { + console.log('Stream:', streamID, 'in room:', roomId, 'deleted'); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var deleteStream = function(room, stream, callback, callbackError) { + if (typeof stream !== 'string' || stream.trim().length === 0) { + return callbackError('Invalid stream ID'); + } + send('DELETE', 'rooms/' + room + '/streams/' + stream, undefined, function(result) { + callback(result); + }, callbackError); + }; + + /* + * * @callback onStartingStreamingInOK + * * @param {Object} streamInfo -The object "StreamInfo" defined in section "3.3.1 Participant Joins a Room" in "Client-Portal Protocol" doc. + */ + /** + *** + * @function startStreamingIn + * @desc This function adds an external RTSP/RTMP stream to the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} url -URL of the streaming source, e.g. the source URL of IPCamera. + * @param {Object} transport -Transport parameters. + * @param {string} transport.protocol -Transport protocol, "tcp" or "udp", "tcp" by default. + * @param {number} transport.bufferSize -The buffer size in bytes in case "udp" is specified, 2048 by default. + * @param {Object} media Media requirements. + * @param {string='auto' | boolean} media.video -If video is required, "auto" or true or false, "auto" by default. + * @param {string='auto' | boolean} media.audio -If audio is required, "auto" or true or false, "auto" by default. + * @param {onStartingStreamingInOK} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var url = 'rtsp://10.239.44.7:554/rtsp_tunnel%3Fh26x=4%26line=1'; + var transport = { + protocol: 'udp', + bufferSize: 2048 + }; + var media = { + audio: 'auto', + video: true + }; + + OWT_REST.API.startStreamingIn(roomId, url, transport, media, function(stream) { + console.log('Streaming-In:', stream); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var startStreamingIn = function(room, url, transport, media, callback, callbackError) { + var pub_req = { + connection: { + url: url, + transportProtocol: transport.protocol, + bufferSize: transport.bufferSize + }, + media: media + }; + send('POST', 'rooms/' + room + '/streaming-ins/', pub_req, function(streamRtn) { + var st = JSON.parse(streamRtn); + callback(st); + }, callbackError); + }; + + /** + * @function stopStreamingIn + * @desc This function stops the specified external streaming-in in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} stream -Stream ID + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var streamID = '878889273471677'; + OWT_REST.API.stopStreamingIn(roomId, streamID, function(result) { + console.log('External streaming-in:', streamID, 'in room:', roomId, 'stopped'); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var stopStreamingIn = function(room, stream, callback, callbackError) { + if (typeof stream !== 'string' || stream.trim().length === 0) { + return callbackError('Invalid stream ID'); + } + send('DELETE', 'rooms/' + room + '/streaming-ins/' + stream, undefined, function(result) { + callback(result); + }, callbackError); + }; + + /* + * * @callback onStreamingOutList + * * @param {Array.} streamingOutList -The list of streaming-outs. + * * @param {Object} streamingOutList[x].media -The media description of the streaming-out, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + */ + /** + * @function getStreamingOuts + * @desc This function gets all the ongoing streaming-outs in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {onStreamingOutList} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + OWT_REST.API.getStreamingOuts(roomId, function(streamingOuts) { + console.log('Streaming-outs:', streamingOuts); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getStreamingOuts = function(room, callback, callbackError) { + send('GET', 'rooms/' + room + '/streaming-outs/', undefined, function(streamingOutList) { + var result = JSON.parse(streamingOutList); + callback(result); + }, callbackError); + }; + + /* + * * @callback onStartingStreamingOutOK + * * @param {Object} streamingOutInfo -The object containing the information of the external streaming-out. + * * @param {string} streamingOutInfo.id -The streaming-out ID. + * * @param {string} streamingOutInfo.protocol -The streaming-out protocol. + * * @param {string} streamingOutInfo.url -The URL of the target streaming-out. + * * @param {string} streamingOutInfo.parameters -The connection parameters of the target streaming-out. + * * @param {Object} streamingOutInfo.media -The media description of the streaming-out, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + */ + /** + * @function startStreamingOut + * @desc This function starts a streaming-out to the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {string} protocol -Streaming-out protocol. + * @param {string} url -The URL of the target streaming-out. + * @param {Object} parameters -The connection parameters of the target streaming-out. + * @param {string} parameters.method -The HTTP(s) method to create file on streaming servers, 'PUT' or 'POST, 'PUT' by default. + * @param {string} parameters.hlsTime -The hls segment length in seconds, 2 by default, required in case protocol is 'hls'. + * @param {string} parameters.hlsListSize -The maximum number of playlist entries, 5 by default, required in case protocol is 'hls'. + * @param {string} parameters.dashSegDuration -The segment length in seconds, 2 by default, required in case protocol is 'dash'. + * @param {string} parameters.dashWindowSize -The maximum number of segments kept in the manifest, 5 by default, required in case protocol is 'dash'. + * @param {Object} media -The media description of the streaming-out, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + * @param {onStartingStreamingOutOK} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var protocol = 'hls' + var url = 'https://USER:PASS@localhost:443/live.m3u8'; + var parameters = { + method: 'PUT', + hlsTime: 2, + hlsListSize: 5 + }, + var media = { + audio: { + from: '7652773772543651' + }, + video: { + from: '7652773772543651', + parameters: { + keyFrameInterval: 2 + } + } + }; + OWT_REST.API.startStreamingOut(roomId, protocol, url, parameters, media, function(streamingOut) { + console.log('Streaming-out:', streamingOut); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var startStreamingOut = function(room, protocol, url, parameters, media, callback, callbackError) { + var options = { + protocol: protocol, + url: url, + parameters: parameters, + media: media + }; + + send('POST', 'rooms/' + room + '/streaming-outs/', options, function(streamingOutRtn) { + var result = JSON.parse(streamingOutRtn); + callback(result); + }, callbackError); + }; + + /* + * * @callback onUpdatingStreamingOutOK + * * @param {Object} streamingOutInfo -The object containing the information of the updated streaming-out, same as defined in onStartingStreamingOutOk. + */ + /** + * @function updateStreamingOut + * @desc This function updates a streaming-out's given attributes in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} id -Streaming-out ID + * @param {Array.<{op: string, path: string, value: json}>} items -Attributes to be updated, with format following RFC6902(https://tools.ietf.org/html/rfc6902). + * @param {onUpdatingStreamingOutOk} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var id = '878889273471677'; + OWT_REST.API.updateStreamingOut(roomId, id, [{op: 'replace', path: '/media/audio/from', value: '9836636255531'}], function(subscription) { + console.log('Subscription:', subscription); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var updateStreamingOut = function(room, id, items, callback, callbackError) { + if (typeof id !== 'string' || id.trim().length === 0) { + return callbackError('Invalid streamingOut ID'); + } + if (!(items instanceof Array)) { + return callbackError('Invalid update list'); + } + send('PATCH', 'rooms/' + room + '/streaming-outs/' + id, items, function(streamingOutRtn) { + var result = JSON.parse(streamingOutRtn); + callback(result); + }, callbackError); + }; + + /** + * @function stopStreamingOut + * @desc This function stops the specified streaming-out in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} id -Streaming-out ID + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var id = '878889273471677'; + OWT_REST.API.stopStreamingOut(roomId, id, function(result) { + console.log('Streaming-out:', id, 'in room:', roomId, 'stopped'); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var stopStreamingOut = function(room, id, callback, callbackError) { + if (typeof id !== 'string' || id.trim().length === 0) { + return callbackError('Invalid streamingOut ID'); + } + send('DELETE', 'rooms/' + room + '/streaming-outs/' + id, undefined, function(result) { + callback(result); + }, callbackError); + }; + + /* + * * @callback onRecordingList + * * @param {Array.<{id: string, storage: Object, media: Object}>} recordingList -The recording list. + * * @param {Object} recordingList[x].storage -The storage information of the recording. + * * @param {string} recordingList[x].storage.host -The host-name or IP address where the recording file is stored. + * * @param {string} recordingList[x].storage.file -The full-path name of the recording file. + * * @param {Object} recordingList[x].media -The media description of the recording, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + */ + /** + * @function getRecordings + * @desc This function gets the all the ongoing recordings in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + OWT_REST.API.getRecordings(roomId, function(recordings) { + console.log('Recordings:', recordings); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getRecordings = function(room, callback, callbackError) { + send('GET', 'rooms/' + room + '/recordings/', undefined, function(recordingList) { + var result = JSON.parse(recordingList); + callback(result); + }, callbackError); + }; + + /* + * * @callback onStartingRecordingOK + * * @param {Object} recordingInfo -The object containing the information of the server-side recording. + * * @param {string} recordingInfo.id -The recording ID. + * * @param {Object} recordingInfo.storage -The storage information of the recording. + * * @param {string} recordingInfo.storage.host -The host-name or IP address where the recording file is stored. + * * @param {string} recordingInfo.storage.file -The full-path name of the recording file. + * * @param {Object} recordingInfo.media -The media description of the recording, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + */ + /** + * @function startRecording + * @desc This function starts a recording in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {string='mp4' | 'mkv' | 'auto'} container -The container type of the recording file, 'auto' by default. + * @param {Object} media -The media description of the recording, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + * @param {onStartingRecordingOK} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var container = 'mkv'; + var media = { + audio: { + from: '7652773772543651' + }, + video: { + from: '7652773772543651', + parameters: { + keyFrameInterval: 2 + } + } + }; + OWT_REST.API.startRecording(roomId, container, media, function(recording) { + console.log('recording:', recording); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var startRecording = function(room, container, media, callback, callbackError) { + var options = { + container: container, + media: media + }; + + send('POST', 'rooms/' + room + '/recordings/', options, function(recordingRtn) { + var result = JSON.parse(recordingRtn); + callback(result); + }, callbackError); + }; + + /* + * * @callback onUpdatingRecordingOK + * * @param {Object} recordingInfo -The object containing the information of the server-side recording, same as defined in onStartingRecordingOk. + */ + /** + * @function updateRecording + * @desc This function updates a recording's given attributes in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} id -Recording ID + * @param {Array.<{op: string, path: string, value: json}>} items -Attributes to be updated, with format following RFC6902(https://tools.ietf.org/html/rfc6902). + * @param {onUpdatingRecordingOk} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var id = '878889273471677'; + OWT_REST.API.updateRecording(roomId, id, [{op: 'replace', path: '/media/audio/from', value: '9836636255531'}], function(subscription) { + console.log('Subscription:', subscription); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var updateRecording = function(room, id, items, callback, callbackError) { + if (typeof id !== 'string' || id.trim().length === 0) { + return callbackError('Invalid recording ID'); + } + if (!(items instanceof Array)) { + return callbackError('Invalid update list'); + } + send('PATCH', 'rooms/' + room + '/recordings/' + id, items, function(recordingRtn) { + var result = JSON.parse(recordingRtn); + callback(result); + }, callbackError); + }; + + /** + * @function stopRecording + * @desc This function stops the specified recording in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} id -Recording ID + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var id = '878889273471677'; + OWT_REST.API.stopRecording(roomId, id, function(result) { + console.log('Recording:', id, 'in room:', roomId, 'stopped'); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var stopRecording = function(room, id, callback, callbackError) { + if (typeof id !== 'string' || id.trim().length === 0) { + return callbackError('Invalid recording ID'); + } + send('DELETE', 'rooms/' + room + '/recordings/' + id, undefined, function(result) { + callback(result); + }, callbackError); + }; + + /* + * * @callback onSipCallList + * * @param {Array.<{Object} SipCallInfo>} sipCallList -The sip call list, the 'SipCallInfo' is the same as defined in onSipCallOK callback parameters. + */ + /** + * @function getSipCalls + * @desc This function gets the all the ongoing sip calls in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {onSipCallList} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + OWT_REST.API.getSipCalls(roomId, function(sipCalls) { + console.log('SipCalls:', sipCalls); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getSipCalls = function(room, callback, callbackError) { + send('GET', 'rooms/' + room + '/sipcalls/', undefined, function(sipCallList) { + var result = JSON.parse(sipCallList); + callback(result); + }, callbackError); + }; + + /* + * * @callback onSipCallOK + * * @param {Object} SipCallInfo -The sip call information. + * * @param {string} SipCallInfo.id -Sip call ID. + * * @param {string 'dial-in' | 'dial-out'} SipCallInfo.type -Sip call type. + * * @param {string} SipCallInfo.peer -Peer URI of the sip call. + * * @param {Object} SipCallInfo.input -Object "StreamInfo" defined in section "3.3.1 Participant Joins a Room" in "Client-Portal Protocol" doc. + * * @param {Object} SipCallInfo.output -The subscription consumed by sip peer. + * * @param {string} SipCallInfo.output.id -ID of the subscription. + * * @param {Object} SipCallInfo.output.media -The media description of the recording, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + */ + /** + * @function makeSipCall + * @desc This function makes a SIP call to the specified peer in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {string} peerUri -The the peer URI to call. + * @param {Object} mediaIn -The media requirements from peer sip endpoint to room. + * @param {boolean} mediaIn.audio -The if audio is required. + * @param {boolean} mediaIn.video -The if video is required. + * @param {Object} mediaOut -The media description from room to peer sip endpoint, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc, with type being 'webrtc'. + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var peerUri = 'alice@company.com'; + var mediaIn = { + audio: true, + video: true + }; + var mediaOut = { + audio: { + from: '51c10d86909ad1f939000001-common' + }, + video: { + from: '51c10d86909ad1f939000001-common' + parameters: { + resolution: { + width: 640, + height: 480 + } + } + } + }; + OWT_REST.API.makeSipCall(roomId, peerUri, mediaIn, mediaOut, function(sipCallInfo) { + console.log('initiate sip call OK', sipCallInfo); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var makeSipCall = function(room, peerUri, mediaIn, mediaOut, callback, callbackError) { + var options = { + peerURI: peerUri, + mediaIn: mediaIn, + mediaOut: mediaOut + }; + + send('POST', 'rooms/' + room + '/sipcalls/', options, function(sipCallInfo) { + var result = JSON.parse(sipCallInfo); + callback(result); + }, callbackError); + }; + + /* + * * @callback onUpdatingSipCallOK + * * @param {Object} SipCallInfo -The updated sip call information, same as defined in parameters of callback onSipCallOk. + */ + /** + * @function updateSipCall + * @desc This function updates a sip call's specified output attributes in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} id -Sip call ID + * @param {Array.<{op: string, path: string, value: json}>} items -Attributes to be updated, with format following RFC6902(https://tools.ietf.org/html/rfc6902). + * @param {onUpdatingSipCallOk} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var id = '878889273471677'; + OWT_REST.API.updateSipCall(roomId, id, [{op: 'replace', path: '/media/audio/from', value: '9836636255531'}], function(sipCallInfo) { + console.log('updated sip call infor:', sipCallInfo); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var updateSipCall = function(room, id, items, callback, callbackError) { + if (typeof id !== 'string' || id.trim().length === 0) { + return callbackError('Invalid sip call ID'); + } + if (!(items instanceof Array)) { + return callbackError('Invalid update list'); + } + send('PATCH', 'rooms/' + room + '/sipcalls/' + id, items, function(sipCallInfoRtn) { + var result = JSON.parse(sipCallInfoRtn); + callback(result); + }, callbackError); + }; + + /** + * @function endSipCall + * @desc This function ends the specified sip call in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} id -Sip call ID + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var id = '878889273471677'; + OWT_REST.API.endSipCall(roomId, id, function(result) { + console.log('Sip call:', id, 'in room:', roomId, 'ended'); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var endSipCall = function(room, id, callback, callbackError) { + if (typeof id !== 'string' || id.trim().length === 0) { + return callbackError('Invalid sip call ID'); + } + send('DELETE', 'rooms/' + room + '/sipcalls/' + id, undefined, function(result) { + callback(result); + }, callbackError); + }; + + /* + * * @callback onAnalyticsList + * * @param {Array.<{id: string, analytics: Object, media: Object}>} analyticsList -The analytics list. + * * @param {Object} analyticsList[x].analytics -The information of the analytics. + * * @param {string} analyticsList[x].analytics.algorithm -The algorithm of the analytics. + * * @param {Object} analyticsList[x].media -The media description of the analytics, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + */ + /** + * @function getAnalytics + * @desc This function gets the all the ongoing analytics in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + OWT_REST.API.getAnalytics(roomId, function(analyticsList) { + console.log('Analytics:', analyticsList); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var getAnalytics = function(room, callback, callbackError) { + send('GET', 'rooms/' + room + '/analytics/', undefined, function(analyticsList) { + var result = JSON.parse(analyticsList); + callback(result); + }, callbackError); + }; + + /* + * * @callback onStartingAnalyticsOK + * * @param {Object} analyticsInfo -The object containing the information of the server-side analytics. + * * @param {string} analyticsInfo.id -The analytics ID. + * * @param {Object} analyticsInfo.analytics -The information of the analytics. + * * @param {string} analyticsInfo.analytics.algorithm -The algorithm of the analytics. + * * @param {Object} analyticsInfo.media -The media description of the analytics, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + */ + /** + * @function startAnalytics + * @desc This function starts a analytics in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID. + * @param {string} algorithm -The algorithm ID. + * @param {Object} media -The media description of the analytics, which must follow the definition of object "MediaSubOptions" in section "3.3.11 Participant Starts a Subscription" in "Client-Portal Protocol.md" doc. + * @param {onStartingAnalyticsOK} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var algorithm = 'b849f44bee074b08bf3e627f3fc927c7'; //guid in plugin.cfg + var media = { + audio: { + from: '7652773772543651' + }, + video: { + from: '7652773772543651', + parameters: { + keyFrameInterval: 2 + } + } + }; + OWT_REST.API.startAnalytics(roomId, algorithm, media, function(analytics) { + console.log('analytics:', analytics); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var startAnalytics = function(room, algorithm, media, callback, callbackError) { + var options = { + algorithm: algorithm, + media: media + }; + + send('POST', 'rooms/' + room + '/analytics/', options, function(analyticsRtn) { + var result = JSON.parse(analyticsRtn); + callback(result); + }, callbackError); + }; + + /** + * @function stopAnalytics + * @desc This function stops the specified analytics in the specified room. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} id -Analytics ID + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var id = '878889273471677'; + OWT_REST.API.stopAnalytics(roomId, id, function(result) { + console.log('Analytics:', id, 'in room:', roomId, 'stopped'); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var stopAnalytics = function(room, id, callback, callbackError) { + if (typeof id !== 'string' || id.trim().length === 0) { + return callbackError('Invalid analytics ID'); + } + send('DELETE', 'rooms/' + room + '/analytics/' + id, undefined, function(result) { + callback(result); + }, callbackError); + }; + + /** + * @function createToken + * @desc This function creates a new token when a new participant to a room needs to be added. + * @memberOf OWT_REST.API + * @param {string} room -Room ID + * @param {string} user -Participant's user ID + * @param {string} role -Participant's role + * @param {object} preference -Preference of this token would be used to connect through + * @param {function} callback -Callback function on success + * @param {function} callbackError -Callback function on error + * @example + var roomId = '51c10d86909ad1f939000001'; + var user = 'user-id@company.com'; + var role = 'guest'; + // Only isp and region are supported in preference currently, please see server's document for details. + var preference = {isp: 'isp', region: 'region'}; + OWT_REST.API.createToken(roomId, user, role, preference, function(token) { + console.log ('Token created:' token); + }, function(status, error) { + // HTTP status and error + console.log(status, error); + }); + */ + var createToken = function(room, user, role, preference, callback, callbackError) { + if (typeof room !== 'string' || typeof user !== 'string' || typeof role !== 'string') { + if (typeof callbackError === 'function') + callbackError(400, 'Invalid argument.'); + return; + } + send('POST', 'rooms/' + room + '/tokens/', {preference: preference, user: user, role: role}, callback, callbackError); + }; + + return { + init: init, + + //Room management. + createRoom: createRoom, + getRooms: getRooms, + getRoom: getRoom, + updateRoom: updateRoom, + updateRoomPartially: updateRoomPartially, + deleteRoom: deleteRoom, + + //Participants management. + getParticipants: getParticipants, + getParticipant: getParticipant, + updateParticipant: updateParticipant, + dropParticipant: dropParticipant, + + //Streams management. + getStreams: getStreams, + getStream: getStream, + updateStream: updateStream, + deleteStream: deleteStream, + + //Streaming-ins management. + startStreamingIn: startStreamingIn, + stopStreamingIn: stopStreamingIn, + + //Streaming-outs management + getStreamingOuts: getStreamingOuts, + startStreamingOut: startStreamingOut, + updateStreamingOut: updateStreamingOut, + stopStreamingOut: stopStreamingOut, + + //Server-side recordings management + getRecordings: getRecordings, + startRecording: startRecording, + updateRecording: updateRecording, + stopRecording: stopRecording, + + //Analytics management + getAnalytics: getAnalytics, + startAnalytics: startAnalytics, + stopAnalytics: stopAnalytics, + + //Sip calls management + getSipCalls: getSipCalls, + makeSipCall: makeSipCall, + updateSipCall: updateSipCall, + endSipCall: endSipCall, + + //Tokens management. + createToken: createToken + }; +}(OWT_REST)); +module.exports = OWT_REST; diff --git a/WebRTC-Sample/owt-server/script/build.sh b/WebRTC-Sample/owt-server/script/build.sh deleted file mode 100644 index cbd0db83..00000000 --- a/WebRTC-Sample/owt-server/script/build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -e - -SUDO="" -if [[ $EUID -ne 0 ]]; then - SUDO="sudo -E" -fi - -if test -z "${DIR}"; then - echo "This script should not be called directly." - exit -1 -fi - -USER="docker" -GROUP="docker" - -# build image(s) in order (to satisfy dependencies) -for dep in '.8.*' '.7.*' '.6.*' '.5.*' '.4.*' '.3.*' '.2.*' '.1.*' ''; do - for dockerfile in `find "${DIR}" -name "Dockerfile${dep}" -print`; do - image=$(head -n 1 "$dockerfile" | grep '# ' | cut -d' ' -f2) - if test -z "$image"; then image="$IMAGE"; fi - - if grep -q 'AS build' "$dockerfile"; then - ${SUDO} docker build --network=host --file="$dockerfile" --target build -t "$image:build" "$DIR" $(env | grep -E '_(proxy|REPO|VER)=' | sed 's/^/--build-arg /') --build-arg USER=${USER} --build-arg GROUP=${GROUP} --build-arg UID=$(id -u) --build-arg GID=$(id -g) - fi - - ${SUDO} docker build --network=host --file="$dockerfile" -t "$image:latest" "$DIR" $(env | grep -E '_(proxy|REPO|VER)=' | sed 's/^/--build-arg /') --build-arg USER=${USER} --build-arg GROUP=${GROUP} --build-arg UID=$(id -u) --build-arg GID=$(id -g) - done -done diff --git a/WebRTC-Sample/owt-server/script/scan-all.cmake b/WebRTC-Sample/owt-server/script/scan-all.cmake deleted file mode 100644 index 64eaa38f..00000000 --- a/WebRTC-Sample/owt-server/script/scan-all.cmake +++ /dev/null @@ -1,6 +0,0 @@ -file(GLOB dirs "*") -foreach (dir ${dirs}) - if(EXISTS ${dir}/CMakeLists.txt) - add_subdirectory(${dir}) - endif() -endforeach() diff --git a/WebRTC-Sample/owt-server/script/service.cmake b/WebRTC-Sample/owt-server/script/service.cmake deleted file mode 100644 index abbb5f72..00000000 --- a/WebRTC-Sample/owt-server/script/service.cmake +++ /dev/null @@ -1,8 +0,0 @@ -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/build.sh") - add_custom_target(build_${service} ALL "${CMAKE_CURRENT_SOURCE_DIR}/build.sh") -endif() - -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/update.sh") - add_custom_target(update_${service} "${CMAKE_CURRENT_SOURCE_DIR}/update.sh") - add_dependencies(update update_${service}) -endif() diff --git a/WebRTC-Sample/owt-server/scripts/build.js b/WebRTC-Sample/owt-server/scripts/build.js new file mode 100755 index 00000000..f9a0e4fb --- /dev/null +++ b/WebRTC-Sample/owt-server/scripts/build.js @@ -0,0 +1,169 @@ +#!/usr/bin/env node +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +// building script +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const rootDir = path.join(path.dirname(module.filename), '..'); +const owtRootDir = path.join(rootDir, 'third_party/owt-server/'); +const depsDir = path.join(owtRootDir, 'build/libdeps/build/'); + +const { chdir, cwd } = process; +const { execSync } = require('child_process'); +const { OptParser, exec } = require(owtRootDir + 'scripts/util'); +const buildTargets = require('./build.json'); + +const optParser = new OptParser(); +optParser.addOption('l', 'list', 'boolean', 'List avaliable build targets'); +optParser.addOption('t', 'target', 'list', 'Specify target to build (Eg. build.js -t video-mixer-sw -t video-transcoder-sw)'); +optParser.addOption('d', 'debug', 'boolean', 'Whether build debug addon (Please add debug-addon option in packing to pack debug addon)'); +optParser.addOption('v', 'verbose', 'boolean', 'Whether use verbose level in building'); +optParser.addOption('r', 'rebuild', 'boolean', 'Whether clean before build'); +//optParser.addOption('c', 'check', 'boolean', 'Whether check after build'); +optParser.addOption('j', 'jobs', 'string', 'Number of concurrent build jobs'); + +const options = optParser.parseArgs(process.argv); + +function getTargets() { + var buildSet = new Set(); + const getIncludings = (includes) => { + var childIncludes; + for (const name of includes) { + if (!buildTargets[name] || buildSet.has(name)) continue; + + if (buildTargets[name].include) { + getIncludings(buildTargets[name].include); + } else { + buildSet.add(name); + } + } + }; + + getIncludings(options.target); + return [...buildSet]; +} + +function listPrint() { + console.log('Avaliable builds:'); + var outputs = []; + for (const name in buildTargets) { + let desc = ''; + if (buildTargets[name].description) { + desc = '- ' + buildTargets[name].description; + } + outputs.push(` ${name} ${desc}`); + } + outputs.sort(); + for (const line of outputs) { + console.log(line); + } +} + +function constructBuildEnv() { + var env = process.env; + env['CFLAGS'] = '-fstack-protector -Wformat -Wformat-security'; + env['CXXFLAGS'] = env['CFLAGS']; + env['LDFLAGS'] = '-z noexecstack -z relro'; + env['PKG_CONFIG_PATH'] = path.join(depsDir, 'lib/pkgconfig') + + ':' + path.join(depsDir, 'lib64/pkgconfig') + + ':' + (env['PKG_CONFIG_PATH'] || ''); + usergcc = path.join(depsDir, 'bin/gcc'); + usergxx = path.join(depsDir, 'bin/g++'); + // Use user compiler if exists + if (fs.existsSync(usergcc) && fs.existsSync(usergxx)) { + env['CC'] = usergcc; + env['CXX'] = usergxx; + } + + if (options.debug) { + env['OPTIMIZATION_LEVEL'] = '0'; + } else { + env['OPTIMIZATION_LEVEL'] = '3'; + env['CFLAGS'] = env['CFLAGS'] + ' -D_FORTIFY_SOURCE=2'; + env['CXXFLAGS'] = env['CFLAGS']; + } + + console.log(env['PKG_CONFIG_PATH']); + + return env; +} + +// Common build commands +var cpuCount = (Number(options.jobs) || os.cpus().length); +logLevel = options.verbose ? 'verbose' : 'error'; +rebuildArgs = ['node-gyp', 'rebuild', `-j ${cpuCount}`, '--loglevel=' + logLevel]; +configureArgs = ['node-gyp', 'configure', '--loglevel=' + logLevel]; +buildArgs = ['node-gyp', 'build', `-j ${cpuCount}`, '--loglevel=' + logLevel]; + +if (options.debug) { + rebuildArgs.push('--debug'); + configureArgs.push('--debug'); + buildArgs.push('--debug'); +} + +// Build single target +function buildTarget(name) { + console.log('\x1b[32mBuilding addon\x1b[0m -', name); + const target = buildTargets[name]; + if (!target.path) { + console.log('\x1b[31mNo binding.gyp:', name, '\x1b[0m'); + return; + } + + const buildPath = path.join(rootDir, target.path); + //var copyGyp = (target.gyp && target.gyp !== 'binding.gyp') ? true : false; + + chdir(buildPath); + //if (copyGyp) execSync(`cp ${target.gyp} binding.gyp`); + + var stdio = [null, process.stdout, process.stderr]; + if (options.rebuild) { + execSync(rebuildArgs.join(' '), { stdio }); + } else { + execSync(configureArgs.join(' '), { stdio }); + execSync(buildArgs.join(' '), { stdio }); + } + + //if (copyGyp) execSync(`rm ${path.join(rootDir, target.path, 'binding.gyp')}`); + console.log('\x1b[32mFinish addon\x1b[0m -', name); +} + +if (options.list) { + listPrint(); + process.exit(0); +} + +if (!options.target || !options.target.length) { + optParser.printHelp(); + process.exit(0); +} + +constructBuildEnv(); +var buildList = getTargets(); +console.log('\x1b[32mFollowing targets will be built:\x1b[0m'); +for (const name of buildList) { + console.log('', name, ''); +} +var works = buildList.map((name) => { + if (name.indexOf('msdk') > 0 && !fs.existsSync(msdkDir)) { + console.log(`\x1b[33mSkip: ${name} - MSDK not installed\x1b[0m`); + return Promise.resolve(); + } + return buildTarget(name); +}); + +Promise.all(works) + .then(() => { + if (options.check) { + moduleTestScript = path.join(owtRootDir, 'scripts/module_test.js'); + runtimeAddonDir = path.join(rootDir, 'source'); + console.log('* Checking modules...'); + let checkOutput = execSync(`node ${moduleTestScript} ${runtimeAddonDir}`).toString(); + console.log(checkOutput); + } + }); + diff --git a/WebRTC-Sample/owt-server/scripts/build.json b/WebRTC-Sample/owt-server/scripts/build.json new file mode 100644 index 00000000..1eb84d1a --- /dev/null +++ b/WebRTC-Sample/owt-server/scripts/build.json @@ -0,0 +1,16 @@ +{ + "all" : { + "include" : [ + "im_video_mixer_sw", + "rtc_frame" + ] + }, + "im_video_mixer_sw" : { + "path" : "source/im_video_mixer_sw/src", + "gyp" : "binding.gyp" + }, + "rtc_frame" : { + "path" : "source/rtc_frame/src", + "gyp" : "binding.gyp" + } +} diff --git a/WebRTC-Sample/owt-server/scripts/ci_process.sh b/WebRTC-Sample/owt-server/scripts/ci_process.sh new file mode 100755 index 00000000..f80654a4 --- /dev/null +++ b/WebRTC-Sample/owt-server/scripts/ci_process.sh @@ -0,0 +1,11 @@ +#!/bin/bash -ex + +ln -s /home/WebRTC360/third_party `pwd`/../ +ln -s /home/WebRTC360/build `pwd`/../ + +source /opt/rh/devtoolset-7/enable +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + +./build.js -t all +./pack.sh diff --git a/WebRTC-Sample/owt-server/scripts/install_deps.sh b/WebRTC-Sample/owt-server/scripts/install_deps.sh new file mode 100755 index 00000000..25ff70b8 --- /dev/null +++ b/WebRTC-Sample/owt-server/scripts/install_deps.sh @@ -0,0 +1,144 @@ +#!/bin/bash -ex +SCRIPT=`pwd`/$0 +PATHNAME=`dirname $SCRIPT` +ROOT=$PATHNAME/.. +BUILD_DIR=$ROOT/build + +LIB_DIR=$BUILD_DIR/libdeps +PREFIX_DIR=$LIB_DIR/build/ + +OWT_ROOT=$ROOT/third_party/owt-server/ + +install_mongodb() { + pushd ${ROOT}/scripts > /dev/null + sudo cp -f mongodb-org-4.4.repo /etc/yum.repos.d/ + sudo yum install mongodb-org -y + popd +} + +install_svt_hevc(){ + pushd $ROOT/third_party >/dev/null + + if [ -d SVT-HEVC ] + then + rm -rf SVT-HEVC + fi + + git clone https://github.com/OpenVisualCloud/SVT-HEVC.git + pushd SVT-HEVC >/dev/null + git checkout v1.5.0 + # PR 580 is under review + git fetch origin pull/580/head && git checkout FETCH_HEAD + + mkdir build + pushd build >/dev/null + cmake -DCMAKE_INSTALL_PREFIX=${PREFIX_DIR} -DCMAKE_INSTALL_LIBDIR=lib .. + make -j4 && make install + popd >/dev/null + + # pseudo lib + echo \ + 'const char* stub() {return "this is a stub lib";}' \ + > pseudo-svtHevcEnc.cpp + gcc pseudo-svtHevcEnc.cpp -fPIC -shared -o pseudo-svtHevcEnc.so + + popd >/dev/null + popd >/dev/null +} + +install_safestringlib(){ + pushd $ROOT/third_party >/dev/null + + if [ -d safestringlib ] + then + rm -fr safestringlib + fi + + git clone https://github.com/intel/safestringlib.git + + cd safestringlib + git checkout 245c4b8cff1d2e7338b7f3a82828fc8e72b29549 + + mkdir build + cd build + cmake .. + make -j$(nproc) + + cp -v libsafestring_shared.so ${PREFIX_DIR}/lib/ + mkdir -p ${PREFIX_DIR}/include/safestringlib + cp -rfv ../include/* ${PREFIX_DIR}/include/safestringlib/ +} + +install_360scvp(){ + sudo yum install -y glog-devel gflags-devel + pushd $ROOT/third_party >/dev/null + + local SCVP_VER="826c61a3cc2804774697a9d7278032c895a4f17d" + local SCVP_REPO=https://github.com/OpenVisualCloud/Immersive-Video-Sample.git + + if [ -d Immersive-Video-Sample ] + then + rm -fr Immersive-Video-Sample + fi + + git clone ${SCVP_REPO} + pushd Immersive-Video-Sample/src/360SCVP + git checkout ${SCVP_VER} + rm build -rf + mkdir build + pushd build + + sed -i "s@INCLUDE_DIRECTORIES\(.*\)@INCLUDE_DIRECTORIES\1\nINCLUDE_DIRECTORIES(${PREFIX_DIR}/include)@" ../CMakeLists.txt + sed -i "s@LINK_DIRECTORIES\(.*\)@LINK_DIRECTORIES\1\nLINK_DIRECTORIES(${PREFIX_DIR}/lib)@" ../CMakeLists.txt + + cmake -DCMAKE_INSTALL_PREFIX=${PREFIX_DIR} -DCMAKE_INSTALL_LIBDIR=lib ../ + make -j$(nproc) + make install + + popd + popd + popd +} + +install_yaml-cpp() { + pushd ${ROOT}/third_party > /dev/null + [ -d yaml-cpp ] && rm -rf yaml-cpp + git clone -b yaml-cpp-0.6.3 https://github.com/jbeder/yaml-cpp.git + pushd yaml-cpp > /dev/null + mkdir build + pushd build > /dev/null + cmake -DYAML_BUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=${PREFIX_DIR} ../ + make -j$(nproc) + make install + popd + popd + popd +} + +rebuild_libwebrtc(){ + pushd $OWT_ROOT/third_party/webrtc-m79/src + + patch -f -p1 < $ROOT/scripts/patches/0001-Impl-Rtcp-FOV-Feedback.patch + cd .. + source ../../scripts/installWebrtc.sh + + popd +} + +if [ ! -d $ROOT/third_party ] +then + mkdir $ROOT/third_party +fi + +if [ ! -d $PREFIX_DIR ] +then + mkdir -p $PREFIX_DIR +fi + +install_mongodb +install_svt_hevc +install_safestringlib +install_360scvp +install_yaml-cpp +rebuild_libwebrtc + diff --git a/WebRTC-Sample/owt-server/scripts/install_owt_server.sh b/WebRTC-Sample/owt-server/scripts/install_owt_server.sh new file mode 100755 index 00000000..5ffb63ac --- /dev/null +++ b/WebRTC-Sample/owt-server/scripts/install_owt_server.sh @@ -0,0 +1,63 @@ +#!/bin/bash -ex + +SCRIPT=`pwd`/$0 +PATHNAME=`dirname ${SCRIPT}` +ROOT=${PATHNAME}/.. +OWT_ROOT=${ROOT}/third_party/owt-server/ + +export_node() { + # node + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion + nvm use v10 +} + +install_devtoolset() { + sudo yum install -y docbook2X + sudo yum install -y centos-release-scl + sudo yum install -y devtoolset-7 + source /opt/rh/devtoolset-7/enable +} + +install_owt_server() { + [ ! -d ${ROOT}/third_party ] && mkdir -p ${ROOT}/third_party + pushd ${ROOT}/third_party + git clone --branch 5.0.x https://github.com/open-webrtc-toolkit/owt-server.git + pushd owt-server + ./scripts/installDepsUnattended.sh + + export_node + + ./scripts/build.js -t mcu --check + popd + popd +} + +install_owt_js_client() { + pushd ${ROOT}/third_party + git clone --branch 5.0.x https://github.com/open-webrtc-toolkit/owt-client-javascript.git + pushd owt-client-javascript + pushd scripts + npm install -g grunt-cli + npm install + grunt + popd + popd + popd +} + +pack_owt() { + pushd ${OWT_ROOT} + ./scripts/pack.js --full --install-module --no-pseudo --with-ffmpeg --app-path ../owt-client-javascript/dist/samples/conference + popd +} + +main() { + install_devtoolset + install_owt_server + install_owt_js_client + pack_owt +} + +main $@ diff --git a/WebRTC-Sample/owt-server/scripts/mongodb-org-4.4.repo b/WebRTC-Sample/owt-server/scripts/mongodb-org-4.4.repo new file mode 100644 index 00000000..2f354c25 --- /dev/null +++ b/WebRTC-Sample/owt-server/scripts/mongodb-org-4.4.repo @@ -0,0 +1,6 @@ +[mongodb-org-4.4] +name=MongoDB Repository +baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.4/x86_64/ +gpgcheck=1 +enabled=1 +gpgkey=https://www.mongodb.org/static/pgp/server-4.4.asc diff --git a/WebRTC-Sample/owt-server/scripts/pack.sh b/WebRTC-Sample/owt-server/scripts/pack.sh new file mode 100755 index 00000000..10cf6911 --- /dev/null +++ b/WebRTC-Sample/owt-server/scripts/pack.sh @@ -0,0 +1,26 @@ +#!/bin/bash -e +SCRIPT=`pwd`/$0 +PATHNAME=`dirname $SCRIPT` +ROOT=$PATHNAME/.. +OWT_DIST=$ROOT/third_party/owt-server/dist +DIST=$ROOT/dist + +if [ ! -d $DIST ] +then + cp -rfv $OWT_DIST $DIST +fi + +# video node +cp -v $ROOT/source/im_video_mixer_sw/src/build/Release/IMVideoMixer-sw.node $DIST/video_agent/videoMixer_sw/build/Release/videoMixer-sw.node +cp -v $ROOT/build/libdeps/build/lib/libSvtHevcEnc.so* $DIST/video_agent/lib +cp -v $ROOT/build/libdeps/build/lib/libyaml-cpp.so* $DIST/video_agent/lib +cp -v $ROOT/source/im_video_mixer_sw/src/mcts_encoder.yaml $DIST/video_agent +cp -v $ROOT/source/im_video_mixer_sw/src/4k_encoder.yaml $DIST/video_agent +cp -v $ROOT/source/im_video_mixer_sw/src/8k_encoder.yaml $DIST/video_agent + +# webrtc node +cp -v $ROOT/source/rtc_frame/src/build/Release/rtcFrame.node $DIST/webrtc_agent/rtcFrame/build/Release/rtcFrame.node +cp -v $ROOT/source/rtc_frame/src/build/Release/rtcadapter.so $DIST/webrtc_agent/rtcFrame/build/Release/rtcadapter.so +cp -v $ROOT/build/libdeps/build/lib/lib360SCVP.so* $DIST/webrtc_agent/lib +cp -v $ROOT/build/libdeps/build/lib/libsafestring_shared.so* $DIST/webrtc_agent/lib + diff --git a/WebRTC-Sample/owt-server/scripts/patches/0001-Impl-Rtcp-FOV-Feedback.patch b/WebRTC-Sample/owt-server/scripts/patches/0001-Impl-Rtcp-FOV-Feedback.patch new file mode 100644 index 00000000..a3d676e2 --- /dev/null +++ b/WebRTC-Sample/owt-server/scripts/patches/0001-Impl-Rtcp-FOV-Feedback.patch @@ -0,0 +1,350 @@ +From 2fff9ab44e584108062e39bc08e24e8959c7c664 Mon Sep 17 00:00:00 2001 +From: "Yu, Dingfeng" +Date: Sat May 8 07:26:31 2021 +0800 +Subject: Impl Rtcp FOV Feedback + +--- + modules/rtp_rtcp/BUILD.gn | 2 + + modules/rtp_rtcp/include/rtp_rtcp.h | 1 + + modules/rtp_rtcp/include/rtp_rtcp_defines.h | 16 +++- + .../rtp_rtcp/source/rtcp_packet/fov_feedback.cc | 93 ++++++++++++++++++++++ + modules/rtp_rtcp/source/rtcp_packet/fov_feedback.h | 56 +++++++++++++ + modules/rtp_rtcp/source/rtcp_receiver.cc | 33 ++++++++ + modules/rtp_rtcp/source/rtcp_receiver.h | 5 ++ + 8 files changed, 206 insertions(+), 1 deletion(-) + create mode 100644 build_overrides/ssl/ssl.gni + create mode 100644 modules/rtp_rtcp/source/rtcp_packet/fov_feedback.cc + create mode 100644 modules/rtp_rtcp/source/rtcp_packet/fov_feedback.h + +diff --git a/modules/rtp_rtcp/BUILD.gn b/modules/rtp_rtcp/BUILD.gn +index b74c177..3cb5cda 100644 +--- a/modules/rtp_rtcp/BUILD.gn ++++ b/modules/rtp_rtcp/BUILD.gn +@@ -27,6 +27,7 @@ rtc_source_set("rtp_rtcp_format") { + "source/rtcp_packet/extended_jitter_report.h", + "source/rtcp_packet/extended_reports.h", + "source/rtcp_packet/fir.h", ++ "source/rtcp_packet/fov_feedback.h", + "source/rtcp_packet/loss_notification.h", + "source/rtcp_packet/nack.h", + "source/rtcp_packet/pli.h", +@@ -65,6 +66,7 @@ rtc_source_set("rtp_rtcp_format") { + "source/rtcp_packet/extended_jitter_report.cc", + "source/rtcp_packet/extended_reports.cc", + "source/rtcp_packet/fir.cc", ++ "source/rtcp_packet/fov_feedback.cc", + "source/rtcp_packet/loss_notification.cc", + "source/rtcp_packet/nack.cc", + "source/rtcp_packet/pli.cc", +diff --git a/modules/rtp_rtcp/include/rtp_rtcp.h b/modules/rtp_rtcp/include/rtp_rtcp.h +index 7682b4a..1a72e44 100644 +--- a/modules/rtp_rtcp/include/rtp_rtcp.h ++++ b/modules/rtp_rtcp/include/rtp_rtcp.h +@@ -103,6 +103,7 @@ class RtpRtcp : public Module, public RtcpFeedbackSenderInterface { + OverheadObserver* overhead_observer = nullptr; + RtcpAckObserver* ack_observer = nullptr; + StreamDataCountersCallback* rtp_stats_callback = nullptr; ++ RtcpFOVObserver* rtcp_fov_observer = nullptr; + + int rtcp_report_interval_ms = 0; + +diff --git a/modules/rtp_rtcp/include/rtp_rtcp_defines.h b/modules/rtp_rtcp/include/rtp_rtcp_defines.h +index db6f53c..1c339fb 100644 +--- a/modules/rtp_rtcp/include/rtp_rtcp_defines.h ++++ b/modules/rtp_rtcp/include/rtp_rtcp_defines.h +@@ -97,7 +97,8 @@ enum RTCPPacketType : uint32_t { + kRtcpXrReceiverReferenceTime = 0x40000, + kRtcpXrDlrrReportBlock = 0x80000, + kRtcpTransportFeedback = 0x100000, +- kRtcpXrTargetBitrate = 0x200000 ++ kRtcpXrTargetBitrate = 0x200000, ++ kRtcpFOVFeedback = 0x400000 + }; + + enum RtxMode { +@@ -329,6 +330,19 @@ class PacketFeedbackObserver { + const std::vector& packet_feedback_vector) = 0; + }; + ++struct RtcpFOVInfo { ++ uint16_t seqnr; ++ uint16_t yaw; ++ uint16_t pitch; ++}; ++ ++class RtcpFOVObserver { ++ public: ++ virtual void OnReceivedFOVFeedback(RtcpFOVInfo &rtcp_fov_info) = 0; ++ ++ virtual ~RtcpFOVObserver() {} ++}; ++ + class RtcpRttStats { + public: + virtual void OnRttUpdate(int64_t rtt) = 0; +diff --git a/modules/rtp_rtcp/source/rtcp_packet/fov_feedback.cc b/modules/rtp_rtcp/source/rtcp_packet/fov_feedback.cc +new file mode 100644 +index 0000000..3e837f0 +--- /dev/null ++++ b/modules/rtp_rtcp/source/rtcp_packet/fov_feedback.cc +@@ -0,0 +1,93 @@ ++/* ++ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. ++ * ++ * Use of this source code is governed by a BSD-style license ++ * that can be found in the LICENSE file in the root of the source ++ * tree. An additional intellectual property rights grant can be found ++ * in the file PATENTS. All contributing project authors may ++ * be found in the AUTHORS file in the root of the source tree. ++ */ ++ ++#include "modules/rtp_rtcp/source/rtcp_packet/fov_feedback.h" ++ ++#include "rtc_base/checks.h" ++#include "rtc_base/logging.h" ++#include "modules/rtp_rtcp/source/byte_io.h" ++#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" ++ ++namespace webrtc { ++namespace rtcp { ++constexpr uint8_t FOVFeedback::kFeedbackMessageType; ++// RFC 4585: Feedback format. ++// Common packet format: ++// ++// 0 1 2 3 ++// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++// |V=2|P| FMT | PT | length | ++// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++// | SSRC of packet sender | ++// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++// | SSRC of media source | ++// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++// : Feedback Control Information (FCI) : ++// : : ++// FOV Feedback (RFC xxxx). ++// The Feedback Control Information (FCI) for the FOV Feedback ++// FCI: ++// 0 1 2 3 ++// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++// | SSRC | ++// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++// | Seq nr. | yaw | ++// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++// | pitch | Reserved = 0 | ++// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++bool FOVFeedback::Parse(const CommonHeader& packet) { ++ RTC_DCHECK_EQ(packet.type(), kPacketType); ++ RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); ++ ++ if ((packet.payload_size_bytes() - kCommonFeedbackLength) != kFciLength) { ++ RTC_LOG(LS_WARNING) << "Invalid size for a valid FOVFeedback packet."; ++ return false; ++ } ++ ++ ParseCommonFeedback(packet.payload()); ++ ++ const uint8_t* next_fci = packet.payload() + kCommonFeedbackLength; ++ ++ seq_nr_ = ByteReader::ReadBigEndian(next_fci); ++ yaw_ = ByteReader::ReadBigEndian(next_fci + 2); ++ pitch_ = ByteReader::ReadBigEndian(next_fci + 4); ++ ++ return true; ++} ++ ++bool FOVFeedback::Create(uint8_t* packet, ++ size_t* index, ++ size_t max_length, ++ PacketReadyCallback callback) const { ++ while (*index + BlockLength() > max_length) { ++ if (!OnBufferFull(packet, index, callback)) ++ return false; ++ } ++ size_t index_end = *index + BlockLength(); ++ CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, ++ index); ++ RTC_DCHECK_NE(Psfb::media_ssrc(), 0); ++ CreateCommonFeedback(packet + *index); ++ *index += kCommonFeedbackLength; ++ ++ constexpr uint32_t kReserved = 0; ++ ++ ByteWriter::WriteBigEndian(packet + *index, seq_nr_); ++ ByteWriter::WriteBigEndian(packet + *index + 2, yaw_); ++ ByteWriter::WriteBigEndian(packet + *index + 4, pitch_); ++ ByteWriter::WriteBigEndian(packet + *index + 6, kReserved); ++ ++ RTC_CHECK_EQ(*index, index_end); ++ return true; ++} ++} // namespace rtcp ++} // namespace webrtc +diff --git a/modules/rtp_rtcp/source/rtcp_packet/fov_feedback.h b/modules/rtp_rtcp/source/rtcp_packet/fov_feedback.h +new file mode 100644 +index 0000000..7e9252b +--- /dev/null ++++ b/modules/rtp_rtcp/source/rtcp_packet/fov_feedback.h +@@ -0,0 +1,56 @@ ++/* ++ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. ++ * ++ * Use of this source code is governed by a BSD-style license ++ * that can be found in the LICENSE file in the root of the source ++ * tree. An additional intellectual property rights grant can be found ++ * in the file PATENTS. All contributing project authors may ++ * be found in the AUTHORS file in the root of the source tree. ++ */ ++ ++#ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_FOV_FEEDBACK_H_ ++#define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_FOV_FEEDBACK_H_ ++ ++#include ++ ++//#include "webrtc/base/basictypes.h" ++#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" ++ ++namespace webrtc { ++namespace rtcp { ++class CommonHeader; ++// FOV Feedback (RFC xxxx). ++class FOVFeedback : public Psfb { ++ public: ++ static constexpr uint8_t kFeedbackMessageType = 8; ++ ++ FOVFeedback() {} ++ ~FOVFeedback() override {} ++ ++ // Parse assumes header is already parsed and validated. ++ bool Parse(const CommonHeader& packet); ++ ++ void SetSeqNr(uint16_t seq_nr) {seq_nr_ = seq_nr;} ++ void SetFOV(uint16_t yaw, uint16_t pitch) {yaw_ = yaw; pitch_ = pitch;} ++ ++ uint16_t seq_nr_; ++ uint16_t yaw_; ++ uint16_t pitch_; ++ ++ bool Create(uint8_t* packet, ++ size_t* index, ++ size_t max_length, ++ PacketReadyCallback callback) const override; ++ ++ size_t BlockLength() const override { ++ return kHeaderLength + kCommonFeedbackLength + kFciLength; ++ } ++ ++ private: ++ static constexpr size_t kFciLength = 8; ++ ++ ++}; ++} // namespace rtcp ++} // namespace webrtc ++#endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_FOV_FEEDBACK_H_ +diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc +index 6b64473..3475461 100644 +--- a/modules/rtp_rtcp/source/rtcp_receiver.cc ++++ b/modules/rtp_rtcp/source/rtcp_receiver.cc +@@ -25,6 +25,7 @@ + #include "modules/rtp_rtcp/source/rtcp_packet/compound_packet.h" + #include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" + #include "modules/rtp_rtcp/source/rtcp_packet/fir.h" ++#include "modules/rtp_rtcp/source/rtcp_packet/fov_feedback.h" + #include "modules/rtp_rtcp/source/rtcp_packet/loss_notification.h" + #include "modules/rtp_rtcp/source/rtcp_packet/nack.h" + #include "modules/rtp_rtcp/source/rtcp_packet/pli.h" +@@ -93,6 +94,7 @@ struct RTCPReceiver::PacketInformation { + absl::optional target_bitrate_allocation; + absl::optional network_state_estimate; + std::unique_ptr loss_notification; ++ std::unique_ptr fov_feedback; + }; + + // Structure for handing TMMBR and TMMBN rtcp messages (RFC5104, section 3.5.4). +@@ -146,6 +148,7 @@ RTCPReceiver::RTCPReceiver(const RtpRtcp::Configuration& config, + network_state_estimate_observer_(config.network_state_estimate_observer), + transport_feedback_observer_(config.transport_feedback_callback), + bitrate_allocation_observer_(config.bitrate_allocation_observer), ++ rtcp_fov_observer_(config.rtcp_fov_observer), + report_interval_ms_(config.rtcp_report_interval_ms > 0 + ? config.rtcp_report_interval_ms + : (config.audio ? kDefaultAudioReportInterval +@@ -394,6 +397,9 @@ bool RTCPReceiver::ParseCompoundPacket(const uint8_t* packet_begin, + case rtcp::Fir::kFeedbackMessageType: + HandleFir(rtcp_block, packet_information); + break; ++ case rtcp::FOVFeedback::kFeedbackMessageType: ++ HandleFOVFeedback(rtcp_block, packet_information); ++ break; + case rtcp::Psfb::kAfbMessageType: + HandlePsfbApp(rtcp_block, packet_information); + break; +@@ -971,6 +977,22 @@ void RTCPReceiver::HandleTransportFeedback( + packet_information->transport_feedback = std::move(transport_feedback); + } + ++void RTCPReceiver::HandleFOVFeedback(const CommonHeader& rtcp_block, ++ PacketInformation* packet_information) { ++ std::unique_ptr fov_feedback( ++ new rtcp::FOVFeedback()); ++ ++ if (!fov_feedback->Parse(rtcp_block)) { ++ ++num_skipped_packets_; ++ return; ++ } ++ ++ if (main_ssrc_ == fov_feedback->media_ssrc()) { ++ packet_information->packet_type_flags |= kRtcpFOVFeedback ; ++ packet_information->fov_feedback = std::move(fov_feedback); ++ } ++} ++ + void RTCPReceiver::NotifyTmmbrUpdated() { + // Find bounding set. + std::vector bounding = +@@ -1111,6 +1133,17 @@ void RTCPReceiver::TriggerCallbacksFromRtcpPacket( + *packet_information.target_bitrate_allocation); + } + ++ if (rtcp_fov_observer_ && ++ packet_information.fov_feedback) { ++ RtcpFOVInfo rtcp_fov_info = { ++ packet_information.fov_feedback->seq_nr_, ++ packet_information.fov_feedback->yaw_, ++ packet_information.fov_feedback->pitch_, ++ }; ++ rtcp_fov_observer_->OnReceivedFOVFeedback( ++ rtcp_fov_info); ++ } ++ + if (!receiver_only_) { + rtc::CritScope cs(&feedbacks_lock_); + if (stats_callback_) { +diff --git a/modules/rtp_rtcp/source/rtcp_receiver.h b/modules/rtp_rtcp/source/rtcp_receiver.h +index 5b92d55..fecfeec 100644 +--- a/modules/rtp_rtcp/source/rtcp_receiver.h ++++ b/modules/rtp_rtcp/source/rtcp_receiver.h +@@ -207,6 +207,10 @@ class RTCPReceiver { + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + ++ void HandleFOVFeedback(const rtcp::CommonHeader& rtcp_block, ++ PacketInformation* packet_information) ++ RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); ++ + void HandleTransportFeedback(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); +@@ -224,6 +228,7 @@ class RTCPReceiver { + NetworkStateEstimateObserver* const network_state_estimate_observer_; + TransportFeedbackObserver* const transport_feedback_observer_; + VideoBitrateAllocationObserver* const bitrate_allocation_observer_; ++ RtcpFOVObserver* const rtcp_fov_observer_; + const int report_interval_ms_; + + rtc::CriticalSection rtcp_receiver_lock_; +-- +1.8.3.1 + diff --git a/WebRTC-Sample/owt-server/scripts/rebuild_360scvp.sh b/WebRTC-Sample/owt-server/scripts/rebuild_360scvp.sh new file mode 100755 index 00000000..751432d9 --- /dev/null +++ b/WebRTC-Sample/owt-server/scripts/rebuild_360scvp.sh @@ -0,0 +1,31 @@ +#!/bin/bash -ex + +#This script is for debug purpose. +#Make sure you've already build the 360SCVP before. + +SCRIPT=`pwd`/$0 +PATHNAME=`dirname $SCRIPT` +ROOT=$PATHNAME/.. +BUILD_DIR=$ROOT/build + +LIB_DIR=$BUILD_DIR/libdeps +PREFIX_DIR=$LIB_DIR/build/ + +OWT_ROOT=$ROOT/third_party/owt-server/ + +main() { + pushd ${ROOT}/third_party/Immersive-Video-Sample/src/360SCVP + [[ -d build ]] && rm build -rf + mkdir build + pushd build + cmake -DCMAKE_INSTALL_PREFIX=${PREFIX_DIR} -DCMAKE_INSTALL_LIBDIR=lib ../ + make -j$(nproc) + make install + popd + popd +} + +main $@ + + + diff --git a/WebRTC-Sample/owt-server/source/common/IMFFmpegFrameDecoder.cpp b/WebRTC-Sample/owt-server/source/common/IMFFmpegFrameDecoder.cpp new file mode 100644 index 00000000..076530d7 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/common/IMFFmpegFrameDecoder.cpp @@ -0,0 +1,258 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#include "webrtc/common_video/include/video_frame_buffer.h" +#include "webrtc/base/keep_ref_until_done.h" + +#include "IMFFmpegFrameDecoder.h" + +#define LTTNG_TRACE_INFO ELOG_TRACE_T + +namespace owt_base { + +DEFINE_LOGGER(FFmpegFrameDecoder, "owt.FFmpegFrameDecoder"); + +int FFmpegFrameDecoder::AVGetBuffer(AVCodecContext *s, AVFrame *frame, int flags) +{ + FFmpegFrameDecoder *FFmpegDecoder = static_cast(s->opaque); + int width = frame->width; + int height = frame->height; + + avcodec_align_dimensions(s, &width, &height); + + rtc::scoped_refptr frame_buffer = FFmpegDecoder->m_bufferManager->getFreeBuffer(width, height); + if (!frame_buffer) { + ELOG_ERROR("No free video buffer"); + return -1; + } + + int y_size = width * height; + int uv_size = ((width + 1) / 2) * ((height + 1) / 2); + int total_size = y_size + 2 * uv_size; + + frame->format = s->pix_fmt; + frame->reordered_opaque = s->reordered_opaque; + + frame->data[0] = frame_buffer->MutableDataY(); + frame->linesize[0] = frame_buffer->StrideY(); + frame->data[1] = frame_buffer->MutableDataU(); + frame->linesize[1] = frame_buffer->StrideU(); + frame->data[2] = frame_buffer->MutableDataV(); + frame->linesize[2] = frame_buffer->StrideV(); + + frame->buf[0] = av_buffer_create( + frame->data[0], + total_size, + AVFreeBuffer, + static_cast(new webrtc::VideoFrame(frame_buffer, + 0 /* timestamp */, + 0 /* render_time_ms */, + webrtc::kVideoRotation_0)), + 0); + + return 0; +} + +void FFmpegFrameDecoder::AVFreeBuffer(void* opaque, uint8_t* data) +{ + webrtc::VideoFrame* video_frame = static_cast(opaque); + delete video_frame; + + return; +} + +FFmpegFrameDecoder::FFmpegFrameDecoder() + : m_decCtx(NULL) + , m_decFrame(NULL) + , m_needKeyFrame(true) +{ +} + +FFmpegFrameDecoder::~FFmpegFrameDecoder() +{ + if (m_decFrame) { + av_frame_free(&m_decFrame); + m_decFrame = NULL; + } + + if (m_decCtx) { + avcodec_free_context(&m_decCtx); + m_decCtx = NULL; + } +} + +bool FFmpegFrameDecoder::init(FrameFormat format) +{ + int ret = 0; + AVCodecID codec_id = AV_CODEC_ID_NONE; + AVCodec* dec = NULL; + + switch (format) { + case FRAME_FORMAT_H264: + codec_id = AV_CODEC_ID_H264; + break; + + case FRAME_FORMAT_H265: + codec_id = AV_CODEC_ID_H265; + break; + + case FRAME_FORMAT_VP8: + codec_id = AV_CODEC_ID_VP8; + break; + + case FRAME_FORMAT_VP9: + codec_id = AV_CODEC_ID_VP9; + break; + + default: + ELOG_ERROR_T("Unspported video frame format %s(%d)", getFormatStr(format), format); + return false; + } + + ELOG_DEBUG_T("Created %s decoder.", getFormatStr(format)); + + dec = avcodec_find_decoder(codec_id); + if (!dec) { + ELOG_ERROR_T("Could not find ffmpeg decoder %s", avcodec_get_name(codec_id)); + return false; + } + + m_decCtx = avcodec_alloc_context3(dec); + if (!m_decCtx ) { + ELOG_ERROR_T("Could not alloc ffmpeg decoder context"); + return false; + } + + m_decCtx->active_thread_type = FF_THREAD_FRAME; + m_decCtx->thread_type = FF_THREAD_FRAME; + m_decCtx->thread_count = 16; + + m_decCtx->get_buffer2 = AVGetBuffer; + m_decCtx->opaque = this; + ret = avcodec_open2(m_decCtx, dec , NULL); + if (ret < 0) { + ELOG_ERROR_T("Could not open ffmpeg decoder context, %s", ff_err2str(ret)); + return false; + } + + m_decFrame = av_frame_alloc(); + if (!m_decFrame) { + ELOG_ERROR_T("Could not allocate dec frame"); + return false; + } + + memset(&m_packet, 0, sizeof(m_packet)); + + m_bufferManager.reset(new I420BufferManager(50)); + + return true; +} + +void FFmpegFrameDecoder::onFrame(const Frame& frame) +{ + int ret; + + if (frame.payload == 0 || frame.length == 0) { + ELOG_DEBUG_T("Null frame, request key frame"); + FeedbackMsg msg {.type = VIDEO_FEEDBACK, .cmd = REQUEST_KEY_FRAME}; + deliverFeedbackMsg(msg); + return; + } + + if (isVideoFrame(frame)) { + LTTNG_TRACE_INFO("FFmpegFrameDecoder: Receive frame, stream(%ld), pts(%d), size(%d), %s" + , (long int)this + , frame.timeStamp /90 + , frame.length + , frame.additionalInfo.video.isKeyFrame ? "key" : "non-key"); + } + + if (m_needKeyFrame) { + if (!frame.additionalInfo.video.isKeyFrame) + return; + + m_needKeyFrame = false; + } + + av_init_packet(&m_packet); + m_packet.data = frame.payload; + m_packet.size = frame.length; + m_packet.dts = frame.timeStamp; + m_packet.pts = frame.timeStamp; + + ret = avcodec_send_packet(m_decCtx, &m_packet); + if (ret < 0) { + ELOG_ERROR_T("Error while send packet, %s", ff_err2str(ret)); + return; + } + + while(true) { + ret = avcodec_receive_frame(m_decCtx, m_decFrame); + if (ret == AVERROR(EAGAIN)) { + //ELOG_TRACE_T("Retry receive frame, %s", ff_err2str(ret)); + return; + }else if (ret < 0) { + ELOG_ERROR_T("Error while receive frame, %s", ff_err2str(ret)); + return; + } + + webrtc::VideoFrame *video_frame = static_cast( + av_buffer_get_opaque(m_decFrame->buf[0])); + video_frame->set_timestamp_us(m_decFrame->pts * 1000 / 90); + video_frame->set_timestamp(m_decFrame->pts); + + rtc::scoped_refptr buf = video_frame->video_frame_buffer(); + + bool isCroppedBuf = false; + if (m_decFrame->width != buf->width() || m_decFrame->height != buf->height()) { + rtc::scoped_refptr cropped_buf( + new rtc::RefCountedObject( + m_decFrame->width, m_decFrame->height, + buf->DataY(), buf->StrideY(), + buf->DataU(), buf->StrideU(), + buf->DataV(), buf->StrideV(), + rtc::KeepRefUntilDone(buf))); + + video_frame = new webrtc::VideoFrame( + cropped_buf, m_decFrame->pts, m_decFrame->pts / 90, + video_frame->rotation()); + + isCroppedBuf = true; + } + + { + Frame frame; + memset(&frame, 0, sizeof(frame)); + frame.format = FRAME_FORMAT_I420; + frame.payload = reinterpret_cast(video_frame); + frame.length = 0; + frame.timeStamp = m_decFrame->pts; + frame.additionalInfo.video.width = video_frame->width(); + frame.additionalInfo.video.height = video_frame->height(); + + LTTNG_TRACE_INFO("FFmpegFrameDecoder: Deliver frame, stream(%ld), pts(%ld), width(%d), height(%d)" + , (long int)this + , m_decFrame->pts / 90 + , video_frame->width() + , video_frame->height()); + + ELOG_TRACE_T("deliverFrame, %dx%d, timeStamp %d", + frame.additionalInfo.video.width, + frame.additionalInfo.video.height, + frame.timeStamp / 90); + deliverFrame(frame); + + if(isCroppedBuf) + delete video_frame; + } + } +} + +char *FFmpegFrameDecoder::ff_err2str(int errRet) +{ + av_strerror(errRet, (char*)(&m_errbuff), 500); + return m_errbuff; +} + +}//namespace owt_base diff --git a/WebRTC-Sample/owt-server/source/common/IMFFmpegFrameDecoder.h b/WebRTC-Sample/owt-server/source/common/IMFFmpegFrameDecoder.h new file mode 100644 index 00000000..995fdd89 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/common/IMFFmpegFrameDecoder.h @@ -0,0 +1,54 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef FFmpegFrameDecoder_h +#define FFmpegFrameDecoder_h + +#include +#include +#include +//#include "lttng_logger.h" + +#include "MediaFramePipeline.h" +#include "I420BufferManager.h" + +extern "C" { +#include +} + +namespace owt_base { + +class FFmpegFrameDecoder : public VideoFrameDecoder { + DECLARE_LOGGER(); + +public: + FFmpegFrameDecoder(); + ~FFmpegFrameDecoder(); + + static bool supportFormat(FrameFormat format) {return true;} + + void onFrame(const Frame&); + bool init(FrameFormat); + +protected: + static int AVGetBuffer(AVCodecContext *s, AVFrame *frame, int flags); + static void AVFreeBuffer(void* opaque, uint8_t* data); + +private: + AVCodecContext *m_decCtx; + AVFrame *m_decFrame; + + AVPacket m_packet; + + boost::scoped_ptr m_bufferManager; + bool m_needKeyFrame; + + char m_errbuff[500]; + char *ff_err2str(int errRet); +}; + +} /* namespace owt_base */ + +#endif /* FFmpegFrameDecoder_h */ + diff --git a/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoder.cpp b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoder.cpp new file mode 100644 index 00000000..5762685d --- /dev/null +++ b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoder.cpp @@ -0,0 +1,204 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#include "IMSVTHEVCEncoder.h" + +#include +#include + +#include +#include +#include + +#include "MediaUtilities.h" + +namespace owt_base { + +DEFINE_LOGGER(SVTHEVCEncoder, "owt.SVTHEVCEncoder"); + +SVTHEVCEncoder::SVTHEVCEncoder(FrameFormat format, VideoCodecProfile profile, bool useSimulcast) + : m_encoderReady(false) + , m_dest(NULL) + , m_width(0) + , m_height(0) + , m_frameRate(0) + , m_bitrateKbps(0) + , m_keyFrameIntervalSeconds(0) + , m_encoded_frame_count(0) +{ + m_hevc_encoder = boost::make_shared(); + + m_srv = boost::make_shared(); + m_srvWork = boost::make_shared(*m_srv); + m_thread = boost::make_shared(boost::bind(&boost::asio::io_service::run, m_srv)); +} + +SVTHEVCEncoder::~SVTHEVCEncoder() +{ + boost::unique_lock ulock(m_mutex); + + m_srvWork.reset(); + m_srv->stop(); + m_thread.reset(); + m_srv.reset(); + + m_dest = NULL; +} + +bool SVTHEVCEncoder::canSimulcast(FrameFormat format, uint32_t width, uint32_t height) +{ + boost::shared_lock lock(m_mutex); + + return false; +} + +bool SVTHEVCEncoder::isIdle() +{ + boost::shared_lock lock(m_mutex); + + return (m_dest == NULL); +} + +bool SVTHEVCEncoder::initEncoder(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds) +{ + m_encoderReady = m_hevc_encoder->init(width, height, frameRate, bitrateKbps, keyFrameIntervalSeconds, 0, 0, 9); + return m_encoderReady; +} + +bool SVTHEVCEncoder::initEncoderAsync(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds) +{ + m_srv->post(boost::bind(&SVTHEVCEncoder::initEncoder, this, width, height, frameRate, bitrateKbps, keyFrameIntervalSeconds)); + return true; +} + +int32_t SVTHEVCEncoder::generateStream(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds, + owt_base::FrameDestination* dest) +{ + boost::unique_lock ulock(m_mutex); + + ELOG_INFO_T("generateStream: {.width=%d, .height=%d, .frameRate=%d, .bitrateKbps=%d, .keyFrameIntervalSeconds=%d}" + , width, height, frameRate, bitrateKbps, keyFrameIntervalSeconds); + + if (m_dest) { + ELOG_ERROR_T("Only support one stream!"); + return -1; + } + + m_width = width; + m_height = height; + m_frameRate = frameRate; + m_bitrateKbps = bitrateKbps; + m_keyFrameIntervalSeconds = keyFrameIntervalSeconds; + + if (m_width != 0 && m_height != 0) { + if (!initEncoderAsync(m_width, m_height, m_frameRate, m_bitrateKbps, m_keyFrameIntervalSeconds)) + return -1; + } + + m_dest = dest; + addVideoDestination(m_dest); + + return 0; +} + +void SVTHEVCEncoder::degenerateStream(int32_t streamId) +{ + boost::unique_lock ulock(m_mutex); + + ELOG_DEBUG_T("degenerateStream"); + + assert(m_dest != NULL); + removeVideoDestination(m_dest); + m_dest = NULL; +} + +void SVTHEVCEncoder::setBitrate(unsigned short kbps, int32_t streamId) +{ + boost::shared_lock lock(m_mutex); + + ELOG_WARN_T("%s", __FUNCTION__); +} + +void SVTHEVCEncoder::requestKeyFrame(int32_t streamId) +{ + boost::shared_lock lock(m_mutex); + + ELOG_DEBUG_T("%s", __FUNCTION__); + + m_hevc_encoder->requestKeyFrame(); +} + +void SVTHEVCEncoder::onFrame(const Frame& frame) +{ + boost::shared_lock lock(m_mutex); + int32_t ret; + + if (m_dest == NULL) { + return; + } + + if (m_width == 0 || m_height == 0) { + m_width = frame.additionalInfo.video.width; + m_height = frame.additionalInfo.video.height; + + if (m_bitrateKbps == 0) + m_bitrateKbps = calcBitrate(m_width, m_height, m_frameRate); + + if (!initEncoderAsync(m_width, m_height, m_frameRate, m_bitrateKbps, m_keyFrameIntervalSeconds)) { + return; + } + } + + if (!m_encoderReady) { + ELOG_WARN_T("Encoder not ready!"); + return; + } + + ret = m_hevc_encoder->sendFrame(frame); + if (!ret) { + ELOG_ERROR_T("SendPicture failed"); + return; + } + + while (true) { + boost::shared_ptr encoded_pkt = m_hevc_encoder->getEncodedPacket(); + if(!encoded_pkt) + break; + + m_packet_queue.push(encoded_pkt); + } + + while (m_packet_queue.size() > 0) { + boost::shared_ptr pkt = m_packet_queue.front(); + m_packet_queue.pop(); + deliverVideoFrame(pkt); + } +} + +void SVTHEVCEncoder::deliverVideoFrame(boost::shared_ptr encoded_pkt) +{ + Frame outFrame; + memset(&outFrame, 0, sizeof(outFrame)); + outFrame.format = FRAME_FORMAT_H265; + outFrame.payload = encoded_pkt->data; + outFrame.length = encoded_pkt->length; + outFrame.timeStamp = (m_encoded_frame_count++) * 1000 / m_frameRate * 90; + outFrame.additionalInfo.video.width = m_width; + outFrame.additionalInfo.video.height = m_height; + outFrame.additionalInfo.video.isKeyFrame = encoded_pkt->isKey; + + ELOG_TRACE_T("deliverFrame, %s, %dx%d(%s), length(%d)", + getFormatStr(outFrame.format), + outFrame.additionalInfo.video.width, + outFrame.additionalInfo.video.height, + outFrame.additionalInfo.video.isKeyFrame ? "key" : "delta", + outFrame.length); + + deliverFrame(outFrame); +} + +} // namespace owt_base diff --git a/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoder.h b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoder.h new file mode 100644 index 00000000..3274bbf0 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoder.h @@ -0,0 +1,72 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef SVTHEVCEncoder_h +#define SVTHEVCEncoder_h + +#include + +#include +#include +#include +#include +#include + +#include "logger.h" +#include "MediaFramePipeline.h" +#include "IMSVTHEVCEncoderBase.h" + +namespace owt_base { + +class SVTHEVCEncoder : public VideoFrameEncoder, public FrameSource { + DECLARE_LOGGER(); + +public: + SVTHEVCEncoder(FrameFormat format, VideoCodecProfile profile, bool useSimulcast = false); + ~SVTHEVCEncoder(); + + FrameFormat getInputFormat() {return FRAME_FORMAT_I420;} + + // Implements VideoFrameEncoder. + void onFrame(const Frame&); + bool canSimulcast(FrameFormat format, uint32_t width, uint32_t height); + bool isIdle(); + int32_t generateStream(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds, + FrameDestination* dest); + void degenerateStream(int32_t streamId); + void setBitrate(unsigned short kbps, int32_t streamId); + void requestKeyFrame(int32_t streamId); + +protected: + bool initEncoder(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds); + bool initEncoderAsync(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds); + void deliverVideoFrame(boost::shared_ptr encoded_pkt); + +private: + bool m_encoderReady; + FrameDestination *m_dest; + + uint32_t m_width; + uint32_t m_height; + uint32_t m_frameRate; + uint32_t m_bitrateKbps; + uint32_t m_keyFrameIntervalSeconds; + + boost::shared_ptr m_hevc_encoder; + uint32_t m_encoded_frame_count; + + boost::shared_mutex m_mutex; + + boost::shared_ptr m_srv; + boost::shared_ptr m_srvWork; + boost::shared_ptr m_thread; + + std::queue> m_packet_queue; +}; + +} /* namespace owt_base */ +#endif /* SVTHEVCEncoder_h */ diff --git a/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoderBase.cpp b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoderBase.cpp new file mode 100644 index 00000000..b52c4c0f --- /dev/null +++ b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoderBase.cpp @@ -0,0 +1,685 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#include "IMSVTHEVCEncoderBase.h" + +#include +#include +#include + +#include "MediaUtilities.h" + +#define LTTNG_TRACE_INFO ELOG_TRACE_T + +namespace owt_base { + +DEFINE_LOGGER(SVTHEVCEncoderBase, "owt.SVTHEVCEncoderBase"); + +SVTHEVCEncodedPacket::SVTHEVCEncodedPacket(EB_BUFFERHEADERTYPE *pBuffer) + : data(NULL) + , length(0) + , isKey(false) +{ + if (pBuffer && pBuffer->pBuffer && pBuffer->nFilledLen) { + m_array.reset(new uint8_t[pBuffer->nFilledLen]); + memcpy(m_array.get(), pBuffer->pBuffer, pBuffer->nFilledLen); + + data = m_array.get(); + length = pBuffer->nFilledLen; + isKey = (pBuffer->sliceType == EB_IDR_PICTURE); + pts = pBuffer->pts; + } +} + +SVTHEVCEncodedPacket::~SVTHEVCEncodedPacket() +{ +} + +SVTHEVCEncoderBase::SVTHEVCEncoderBase() + : m_handle(NULL) + , m_scaling_frame_buffer(NULL) + , m_forceIDR(false) + , m_quit(false) + , m_enableBsDump(false) + , m_bsDumpfp(NULL) +{ + memset(&m_encParameters, 0, sizeof(m_encParameters)); +} + +SVTHEVCEncoderBase::~SVTHEVCEncoderBase() +{ + if (m_thread) { + m_quit = true; + m_queue_cond.notify_all(); + m_thread->join(); + } + + if (m_handle) { + ELOG_INFO_T("EbDeinit* started, if NOT see EbDeinitEncoder ok1 + EbDeinitHandle ok2, crash/leak happens. Check with dmesg/free"); + EB_ERRORTYPE return_error = EbDeinitEncoder(m_handle); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("EbDeinitEncoder failed, ret 0x%x", return_error); + //TODO error handling, shall i continue to EbDeinitHandle? + } else { + ELOG_INFO_T("EbDeinitEncoder ok1"); + } + + return_error = EbDeinitHandle(m_handle); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("EbDeinitHandle failed, ret 0x%x", return_error); + //TODO error handling + } else { + ELOG_INFO_T("EbDeinitHandle ok2"); + } + m_handle = NULL; + + deallocateBuffers(); + + if (m_scaling_frame_buffer) { + free(m_scaling_frame_buffer); + m_scaling_frame_buffer = NULL; + } + + if (m_bsDumpfp) { + fclose(m_bsDumpfp); + m_bsDumpfp = NULL; + } + } +} + +void SVTHEVCEncoderBase::initDefaultParameters() +{ + // Encoding preset + m_encParameters.encMode = 9; + m_encParameters.tune = 1; + //m_encParameters.latencyMode = 1; + + // GOP Structure + m_encParameters.intraPeriodLength = 255; + m_encParameters.intraRefreshType = 0; + m_encParameters.hierarchicalLevels = 0; + + m_encParameters.predStructure = 0; + m_encParameters.baseLayerSwitchMode = 0; + + // Input Info + m_encParameters.sourceWidth = 0; + m_encParameters.sourceHeight = 0; + m_encParameters.frameRate = 0; + m_encParameters.frameRateNumerator = 0; + m_encParameters.frameRateDenominator = 0; + m_encParameters.encoderBitDepth = 8; + m_encParameters.encoderColorFormat = EB_YUV420; + m_encParameters.compressedTenBitFormat = 0; + m_encParameters.framesToBeEncoded = 0; + + // Visual quality optimizations only applicable when tune = 1 + m_encParameters.bitRateReduction = 1; + m_encParameters.improveSharpness = 1; + + // Interlaced Video + m_encParameters.interlacedVideo = 0; + +#if 0 + // Quantization + m_encParameters.qp = 32; + m_encParameters.useQpFile = 0; +#endif + + // Deblock Filter + m_encParameters.disableDlfFlag = 0; + + // SAO + m_encParameters.enableSaoFlag = 1; + + // Motion Estimation Tools + m_encParameters.useDefaultMeHme = 1; + m_encParameters.enableHmeFlag = 1; + +#if 0 + // ME Parameters + m_encParameters.searchAreaWidth = 16; + m_encParameters.searchAreaHeight = 7; +#endif + + // MD Parameters + m_encParameters.constrainedIntra = 0; + + // Rate Control + m_encParameters.rateControlMode = 1; //0 : CQP , 1 : VBR + m_encParameters.sceneChangeDetection = 1; + m_encParameters.lookAheadDistance = 0; + m_encParameters.targetBitRate = 0; + m_encParameters.maxQpAllowed = 48; + m_encParameters.minQpAllowed = 10; + + // bitstream options + m_encParameters.codeVpsSpsPps = 1; + m_encParameters.codeEosNal = 0; + m_encParameters.videoUsabilityInfo = 0; + m_encParameters.highDynamicRangeInput = 0; + m_encParameters.accessUnitDelimiter = 0; + m_encParameters.bufferingPeriodSEI = 0; + m_encParameters.pictureTimingSEI = 0; + m_encParameters.registeredUserDataSeiFlag = 0; + m_encParameters.unregisteredUserDataSeiFlag = 0; + m_encParameters.recoveryPointSeiFlag = 0; + m_encParameters.enableTemporalId = 1; + m_encParameters.profile = 2; + m_encParameters.tier = 0; + m_encParameters.level = 0; + m_encParameters.fpsInVps = 0; + + // Application Specific parameters + m_encParameters.channelId = 0; + m_encParameters.activeChannelCount = 1; + + // Threads management + m_encParameters.logicalProcessors = 0; + m_encParameters.targetSocket = -1; + m_encParameters.switchThreadsToRtPriority = 1; + + // ASM Type + m_encParameters.asmType = 1; + + // Demo features + m_encParameters.speedControlFlag = 0; + m_encParameters.injectorFrameRate = m_encParameters.frameRate << 16; + + // Debug tools + m_encParameters.reconEnabled = false; +} + +void SVTHEVCEncoderBase::updateParameters(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds, uint32_t encMode) +{ + m_encParameters.encMode = encMode; + //resolution + m_encParameters.sourceWidth = width; + m_encParameters.sourceHeight = height; + + //gop + m_encParameters.intraPeriodLength = 5 - 1; //GoP=5 + + //framerate + m_encParameters.frameRate = frameRate << 16; + + //bitrate + m_encParameters.rateControlMode = 1; //0 : CQP , 1 : VBR + m_encParameters.intraRefreshType = 0; //0 for CQP, >=0 for VBR. FOV cannot work with default value -1:CRA Open GOP + m_encParameters.targetBitRate = bitrateKbps * 1000; + m_encParameters.maxQpAllowed = 26; // we heard TV broadcasting is using qp=23 as experience data + m_encParameters.minQpAllowed = 20; // so we narrow qp range down to 20-26 to make latively stable quality + + //lower the latency + m_encParameters.sceneChangeDetection = 0; + m_encParameters.lookAheadDistance = 0; + m_encParameters.predStructure = 0; //0: Low Delay P frame, reduced client decoder latency + + //performance tuning + m_encParameters.targetSocket = (m_encParameters.sourceWidth == 7680 || m_encParameters.sourceWidth == 3840); //bind HR encode to cpu 1 + m_encParameters.switchThreadsToRtPriority = 0; //do not use realtime thread. it seemes unfair to decoding/stitching/networking + //m_encParameters.disableDlfFlag = 1; //disable de-block feature may improve ~0.5 fps (by sacrifice video quality?) +} + +void SVTHEVCEncoderBase::updateMCTSParameters(const EB_H265_ENC_CONFIGURATION conf) +{ + // Encoding Preset + m_encParameters.encMode = conf.encMode; + m_encParameters.tune = conf.tune ? conf.tune : 1; + m_encParameters.asmType = conf.asmType ? conf.asmType : 1; + + // GOP Structure + m_encParameters.intraPeriodLength = conf.intraPeriodLength ? conf.intraPeriodLength : 5 - 1; //GoP=5 + m_encParameters.intraRefreshType = conf.intraRefreshType ? conf.intraRefreshType : 0; + m_encParameters.hierarchicalLevels = conf.hierarchicalLevels ? conf.hierarchicalLevels : 3; + m_encParameters.predStructure = conf.predStructure ? conf.predStructure : 0; + + // Input Info + m_encParameters.sourceWidth = conf.sourceWidth; + m_encParameters.sourceHeight = conf.sourceHeight; + m_encParameters.frameRate = conf.frameRate << 16; + m_encParameters.encoderBitDepth = conf.encoderBitDepth ? conf.encoderBitDepth : 8; + m_encParameters.encoderColorFormat = static_cast(conf.encoderColorFormat ? conf.encoderColorFormat : 1); + + // Rate Control + m_encParameters.rateControlMode = conf.rateControlMode ? conf.rateControlMode : 1; //0 : CQP , 1 : VBR + m_encParameters.sceneChangeDetection = conf.sceneChangeDetection ? conf.sceneChangeDetection : 0; + m_encParameters.lookAheadDistance = conf.lookAheadDistance ? conf.lookAheadDistance : 0; + m_encParameters.targetBitRate = conf.targetBitRate; + m_encParameters.maxQpAllowed = conf.maxQpAllowed ? conf.maxQpAllowed : 26; // we heard TV broadcasting is using qp=23 as experience data + m_encParameters.minQpAllowed = conf.minQpAllowed ? conf.minQpAllowed : 20; // so we narrow qp range down to 20-26 to make latively stable quality + + // Bitstream Options + m_encParameters.profile = conf.profile ? conf.profile : 2; + m_encParameters.level = conf.level ? conf.level : 0; + m_encParameters.tier = conf.tier ? conf.tier : 0; + + // Deblocking + m_encParameters.disableDlfFlag = conf.disableDlfFlag ? conf.disableDlfFlag : 0; + + // SAO + m_encParameters.enableSaoFlag = conf.enableSaoFlag ? conf.enableSaoFlag : 0; + + //performance tuning + m_encParameters.targetSocket = conf.targetSocket? conf.targetSocket : 1; //bind HR encode to cpu 1 + m_encParameters.switchThreadsToRtPriority = conf.switchThreadsToRtPriority? conf.switchThreadsToRtPriority : 0; //do not use realtime thread. it seemes unfair to decoding/stitching/networking + m_encParameters.unrestrictedMotionVector = conf.unrestrictedMotionVector? conf.unrestrictedMotionVector : 0; + m_encParameters.tileSliceMode = conf.tileSliceMode? conf.tileSliceMode : 1; + m_encParameters.tileColumnCount = conf.tileColumnCount; + m_encParameters.tileRowCount = conf.tileRowCount; +} + +void SVTHEVCEncoderBase::setMCTSParameters(uint32_t tiles_col, uint32_t tiles_row) +{ + if (tiles_col * tiles_row > 1) { + m_encParameters.unrestrictedMotionVector = 0; + m_encParameters.tileSliceMode = 1; + m_encParameters.tileColumnCount = tiles_col; + m_encParameters.tileRowCount = tiles_row; +#if 0 + m_encParameters.hierarchicalLevels = 3; + + m_encParameters.disableDlfFlag = 1; + + m_encParameters.bitRateReduction = 0; + m_encParameters.improveSharpness = 0; +#endif + ELOG_DEBUG_T("set MCTS, tiles_col=%d, tiles_row=%d", + tiles_col, tiles_row); + } +} + +bool SVTHEVCEncoderBase::init(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds, + uint32_t tiles_col, uint32_t tiles_row, uint32_t encMode) +{ + EB_ERRORTYPE return_error = EB_ErrorNone; + + ELOG_DEBUG_T("initEncoder: width=%d, height=%d, frameRate=%d, bitrateKbps=%d, .keyFrameIntervalSeconds=%d}" + , width, height, frameRate, bitrateKbps, keyFrameIntervalSeconds); + + return_error = EbInitHandle(&m_handle, this, &m_encParameters); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("InitHandle failed, ret 0x%x", return_error); + + m_handle = NULL; + return false; + } else { + ELOG_INFO_T("EbInitHandle ok"); + } + + //initDefaultParameters(); + updateParameters(width, height, frameRate, bitrateKbps, keyFrameIntervalSeconds, encMode); + setMCTSParameters(tiles_col, tiles_row); + + return_error = EbH265EncSetParameter(m_handle, &m_encParameters); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("SetParameter failed, ret 0x%x", return_error); + + EbDeinitHandle(m_handle); + m_handle = NULL; + return false; + } + + return_error = EbInitEncoder(m_handle); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("InitEncoder failed, ret 0x%x", return_error); + + EbDeinitHandle(m_handle); + m_handle = NULL; + return false; + } + + if (!allocateBuffers()) { + ELOG_ERROR_T("allocateBuffers failed"); + + deallocateBuffers(); + EbDeinitEncoder(m_handle); + EbDeinitHandle(m_handle); + m_handle = NULL; + return false; + } + + if (m_enableBsDump) { + char dumpFileName[128]; + + snprintf(dumpFileName, 128, "/tmp/SVTHEVCEncoderBase-%dx%d-%p.%s", width, height, this, "hevc"); + m_bsDumpfp = fopen(dumpFileName, "wb"); + if (m_bsDumpfp) { + ELOG_INFO_T("Enable bitstream dump, %s", dumpFileName); + } else { + ELOG_INFO_T("Can not open dump file, %s", dumpFileName); + } + } + + m_thread.reset(new boost::thread(&SVTHEVCEncoderBase::encoding_loop, this)); + + return true; +} + +bool SVTHEVCEncoderBase::init(const EB_H265_ENC_CONFIGURATION& conf) +{ + EB_ERRORTYPE return_error = EB_ErrorNone; + + return_error = EbInitHandle(&m_handle, this, &m_encParameters); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("InitHandle failed, ret 0x%x", return_error); + + m_handle = NULL; + return false; + } else { + ELOG_INFO_T("EbInitHandle ok"); + } + + updateMCTSParameters(conf); + + return_error = EbH265EncSetParameter(m_handle, &m_encParameters); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("SetParameter failed, ret 0x%x", return_error); + + EbDeinitHandle(m_handle); + m_handle = NULL; + return false; + } + + return_error = EbInitEncoder(m_handle); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("InitEncoder failed, ret 0x%x", return_error); + + EbDeinitHandle(m_handle); + m_handle = NULL; + return false; + } + + if (!allocateBuffers()) { + ELOG_ERROR_T("allocateBuffers failed"); + + deallocateBuffers(); + EbDeinitEncoder(m_handle); + EbDeinitHandle(m_handle); + m_handle = NULL; + return false; + } + + if (m_enableBsDump) { + char dumpFileName[128]; + + snprintf(dumpFileName, 128, "/tmp/SVTHEVCEncoderBase-%dx%d-%p.%s", conf.sourceWidth, conf.sourceHeight, this, "hevc"); + m_bsDumpfp = fopen(dumpFileName, "wb"); + if (m_bsDumpfp) { + ELOG_INFO_T("Enable bitstream dump, %s", dumpFileName); + } else { + ELOG_INFO_T("Can not open dump file, %s", dumpFileName); + } + } + + m_thread.reset(new boost::thread(&SVTHEVCEncoderBase::encoding_loop, this)); + + return true; +} + +void SVTHEVCEncoderBase::encoding_loop() +{ + ELOG_INFO_T("Enter loop"); + + while(!m_quit) { + boost::shared_ptr video_frame; + { + boost::mutex::scoped_lock lock(m_queue_mutex); + while (m_frame_queue.size() == 0) { + m_queue_cond.wait(lock); + if (m_quit) + goto exit; + } + video_frame = m_frame_queue.front(); + m_frame_queue.pop(); + } + + encodeVideoFrame(video_frame); + } + +exit: + ELOG_INFO_T("sending EOS"); //https://github.com/OpenVisualCloud/SVT-HEVC/issues/535 + m_inputBuffer.nAllocLen = 0; + m_inputBuffer.nFilledLen = 0; + m_inputBuffer.nTickCount = 0; + m_inputBuffer.pBuffer = NULL; + m_inputBuffer.nFlags = EB_BUFFERFLAG_EOS; + int return_error = EbH265EncSendPicture(m_handle, &m_inputBuffer); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("send EOS failed, ret 0x%x", return_error); + } else { + ELOG_INFO_T("send EOS done"); + } + usleep(1000);//1ms + + ELOG_INFO_T("Exit loop"); +} + +bool SVTHEVCEncoderBase::sendFrame(const Frame& frame) +{ + switch (frame.format) { + case FRAME_FORMAT_I420: { + boost::shared_ptr videoFrame = + boost::make_shared(*reinterpret_cast(frame.payload)); + return sendVideoFrame(videoFrame); + } + + default: + ELOG_ERROR_T("Unspported video frame format %s(%d)", + getFormatStr(frame.format), frame.format); + return false; + } +} + +bool SVTHEVCEncoderBase::sendVideoFrame(boost::shared_ptr videoFrame) +{ + boost::mutex::scoped_lock lock(m_queue_mutex); + m_frame_queue.push(videoFrame); + if (m_frame_queue.size() >= 1) + m_queue_cond.notify_all(); + + return true; +} + +int SVTHEVCEncoderBase::frame_queue_size() +{ + boost::mutex::scoped_lock lock(m_queue_mutex); + return m_frame_queue.size(); +} + +bool SVTHEVCEncoderBase::encodeVideoFrame(boost::shared_ptr videoFrame) +{ + rtc::scoped_refptr videoBuffer = videoFrame->video_frame_buffer(); + return encodeVideoBuffer(videoBuffer, videoFrame->timestamp_us() / 1000); +} + +bool SVTHEVCEncoderBase::encodeVideoBuffer(rtc::scoped_refptr videoBuffer, + int64_t pts) +{ + int ret; + + if ((uint32_t)videoBuffer->width() == m_encParameters.sourceWidth + && (uint32_t)videoBuffer->height() == m_encParameters.sourceHeight) { + LTTNG_TRACE_INFO("SVTHEVCEncoderBase: Send HR-pic to HR encoder, pts(%ld)" + , pts); + return encodePicture(videoBuffer->DataY(), videoBuffer->StrideY(), + videoBuffer->DataU(), videoBuffer->StrideU(), + videoBuffer->DataV(), videoBuffer->StrideV(), + pts); + } else { + if (!m_scaling_frame_buffer) { + m_scaling_frame_buffer = + (uint8_t *)malloc(m_encParameters.sourceWidth * m_encParameters.sourceHeight * 3 / 2); + } + + LTTNG_TRACE_INFO("SVTHEVCEncoderBase: Down scale start, pts(%ld)", pts); + ret = libyuv::I420Scale( + videoBuffer->DataY(), videoBuffer->StrideY(), + videoBuffer->DataU(), videoBuffer->StrideU(), + videoBuffer->DataV(), videoBuffer->StrideV(), + videoBuffer->width(), videoBuffer->height(), + m_scaling_frame_buffer, + m_encParameters.sourceWidth, + m_scaling_frame_buffer + m_encParameters.sourceWidth * m_encParameters.sourceHeight, + m_encParameters.sourceWidth / 2, + m_scaling_frame_buffer + m_encParameters.sourceWidth * m_encParameters.sourceHeight * 5 / 4, + m_encParameters.sourceWidth / 2, + m_encParameters.sourceWidth, m_encParameters.sourceHeight, + libyuv::kFilterBox); + if (ret != 0) { + ELOG_ERROR_T("Convert frame failed(%d), %dx%d -> %dx%d", ret + , videoBuffer->width() + , videoBuffer->height() + , m_encParameters.sourceWidth + , m_encParameters.sourceHeight + ); + return false; + } + LTTNG_TRACE_INFO("SVTHEVCEncoderBase: Down scale done, pts(%ld)", pts); + LTTNG_TRACE_INFO("SVTHEVCEncoderBase: Send LR-pic to LR encoder, pts(%ld)", pts); + return encodePicture( + m_scaling_frame_buffer, + m_encParameters.sourceWidth, + m_scaling_frame_buffer + m_encParameters.sourceWidth * m_encParameters.sourceHeight, + m_encParameters.sourceWidth / 2, + m_scaling_frame_buffer + m_encParameters.sourceWidth * m_encParameters.sourceHeight * 5 / 4, + m_encParameters.sourceWidth / 2, + pts); + } +} + +bool SVTHEVCEncoderBase::encodePicture(const uint8_t *y_plane, uint32_t y_stride, + const uint8_t *u_plane, uint32_t u_stride, + const uint8_t *v_plane, uint32_t v_stride, + int64_t pts) +{ + // frame buffer + m_inputFrameBuffer.luma = const_cast(y_plane); + m_inputFrameBuffer.cb = const_cast(u_plane); + m_inputFrameBuffer.cr = const_cast(v_plane); + + m_inputFrameBuffer.yStride = y_stride; + m_inputFrameBuffer.crStride = u_stride; + m_inputFrameBuffer.cbStride = v_stride; + + m_inputBuffer.pts = pts; + m_inputBuffer.dts = pts; + + // frame type + if (m_forceIDR) { + m_inputBuffer.sliceType = EB_IDR_PICTURE; + m_forceIDR = false; + + ELOG_DEBUG_T("encodePicture, IDR"); + } else { + m_inputBuffer.sliceType = EB_INVALID_PICTURE; + } + + int return_error = EbH265EncSendPicture(m_handle, &m_inputBuffer); + if (return_error != EB_ErrorNone) { + ELOG_ERROR_T("encodePicture failed, ret 0x%x", return_error); + return false; + } + + return true; +} + +EB_BUFFERHEADERTYPE *SVTHEVCEncoderBase::getOutBuffer(void) +{ + EB_BUFFERHEADERTYPE *pOutBuffer = &m_outputBuffer; + int return_error; + + return_error = EbH265GetPacket(m_handle, &pOutBuffer , false); + if (return_error == EB_ErrorMax) { + ELOG_ERROR_T("Error while encoding, code 0x%x", pOutBuffer->nFlags); + return NULL; + } else if (return_error != EB_NoErrorEmptyQueue) { + ELOG_TRACE_T("nFilledLen(%d), nTickCount %d(ms), dts(%ld), pts(%ld), nFlags(0x%x), qpValue(%d), sliceType(%d)" + , pOutBuffer->nFilledLen + , pOutBuffer->nTickCount + , pOutBuffer->dts + , pOutBuffer->pts + , pOutBuffer->nFlags + , pOutBuffer->qpValue + , pOutBuffer->sliceType + ); + + dump(pOutBuffer->pBuffer, pOutBuffer->nFilledLen); + + return pOutBuffer; + } else { + return NULL; + } +} + +void SVTHEVCEncoderBase::releaseOutBuffer(EB_BUFFERHEADERTYPE *pOutBuffer) +{ + EbH265ReleaseOutBuffer(&pOutBuffer); +} + +boost::shared_ptr SVTHEVCEncoderBase::getEncodedPacket(void) +{ + EB_BUFFERHEADERTYPE *pOutBuffer = getOutBuffer(); + if (!pOutBuffer) + return NULL; + + boost::shared_ptr encoded_pkt = + boost::make_shared(pOutBuffer); + releaseOutBuffer(pOutBuffer); + return encoded_pkt; +} + +void SVTHEVCEncoderBase::requestKeyFrame() +{ + ELOG_DEBUG_T("%s", __FUNCTION__); + + m_forceIDR = true; +} + +bool SVTHEVCEncoderBase::allocateBuffers() +{ + memset(&m_inputFrameBuffer, 0, sizeof(m_inputFrameBuffer)); + memset(&m_inputBuffer, 0, sizeof(m_inputBuffer)); + memset(&m_outputBuffer, 0, sizeof(m_outputBuffer)); + + // output buffer + m_inputBuffer.nSize = sizeof(EB_BUFFERHEADERTYPE); + m_inputBuffer.nAllocLen = 0; + m_inputBuffer.pAppPrivate = NULL; + m_inputBuffer.sliceType = EB_INVALID_PICTURE; + m_inputBuffer.pBuffer = (unsigned char *)&m_inputFrameBuffer; + + // output buffer + size_t outputStreamBufferSize = m_encParameters.sourceWidth * m_encParameters.sourceHeight * 2; + m_outputBuffer.nSize = sizeof(EB_BUFFERHEADERTYPE); + m_outputBuffer.nAllocLen = outputStreamBufferSize; + m_outputBuffer.pAppPrivate = this; + m_outputBuffer.sliceType = EB_INVALID_PICTURE; + m_outputBuffer.pBuffer = (unsigned char *)malloc(outputStreamBufferSize); + if (!m_outputBuffer.pBuffer) { + ELOG_ERROR_T("Can not alloc mem, size(%ld)", outputStreamBufferSize); + return false; + } + + return true; +} + +void SVTHEVCEncoderBase::deallocateBuffers() +{ + if (!m_outputBuffer.pBuffer) { + free(m_outputBuffer.pBuffer); + m_outputBuffer.pBuffer = NULL; + } +} + +void SVTHEVCEncoderBase::dump(uint8_t *buf, int len) +{ + if (m_bsDumpfp) { + fwrite(buf, 1, len, m_bsDumpfp); + } +} + +} // namespace owt_base diff --git a/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoderBase.h b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoderBase.h new file mode 100644 index 00000000..bc54c826 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCEncoderBase.h @@ -0,0 +1,109 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef SVTHEVCEncoderBase_h +#define SVTHEVCEncoderBase_h + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "logger.h" +//#include "lttng_logger.h" +#include "MediaFramePipeline.h" + +#include "svt-hevc/EbApi.h" + +namespace owt_base { + +class SVTHEVCEncodedPacket { +public: + SVTHEVCEncodedPacket(EB_BUFFERHEADERTYPE* pBuffer); + ~SVTHEVCEncodedPacket(); + + uint8_t *data; + int32_t length; + bool isKey; + int64_t pts; + +private: + boost::shared_array m_array; +}; + +class SVTHEVCEncoderBase { + DECLARE_LOGGER(); + +public: + SVTHEVCEncoderBase(); + ~SVTHEVCEncoderBase(); + + bool init(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds, + uint32_t tiles_col, uint32_t tiles_row, uint32_t encMode); + + bool init(const EB_H265_ENC_CONFIGURATION& conf); + + bool sendFrame(const Frame& frame); + bool sendVideoFrame(boost::shared_ptr video_frame); + + boost::shared_ptr getEncodedPacket(void); + + void requestKeyFrame(); + + void encoding_loop(); + int frame_queue_size(); + +protected: + void initDefaultParameters(); + void updateParameters(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds, uint32_t encMode); + void setMCTSParameters(uint32_t tiles_col, uint32_t tiles_row); + void updateMCTSParameters(const EB_H265_ENC_CONFIGURATION conf); + + bool allocateBuffers(); + void deallocateBuffers(); + + EB_BUFFERHEADERTYPE *getOutBuffer(void); + void releaseOutBuffer(EB_BUFFERHEADERTYPE *pOutBuffer); + + bool encodeVideoFrame(boost::shared_ptr videoFrame); + bool encodeVideoBuffer(rtc::scoped_refptr video_buffer, int64_t pts); + bool encodePicture(const uint8_t *y_plane, uint32_t y_stride, + const uint8_t *u_plane, uint32_t u_stride, + const uint8_t *v_plane, uint32_t v_stride, + int64_t pts); + + void dump(uint8_t *buf, int len); + +private: + EB_COMPONENTTYPE *m_handle; + EB_H265_ENC_CONFIGURATION m_encParameters; + + EB_H265_ENC_INPUT m_inputFrameBuffer; + EB_BUFFERHEADERTYPE m_inputBuffer; + EB_BUFFERHEADERTYPE m_outputBuffer; + + uint8_t *m_scaling_frame_buffer; + bool m_forceIDR; + + boost::scoped_ptr m_thread; + std::queue> m_frame_queue; + boost::mutex m_queue_mutex; + boost::condition_variable m_queue_cond; + bool m_quit; + + bool m_enableBsDump; + FILE *m_bsDumpfp; +}; + +} /* namespace owt_base */ +#endif /* SVTHEVCEncoderBase_h */ diff --git a/WebRTC-Sample/owt-server/source/common/IMSVTHEVCMCTSEncoder.cpp b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCMCTSEncoder.cpp new file mode 100644 index 00000000..e2ec5131 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCMCTSEncoder.cpp @@ -0,0 +1,466 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#include "IMSVTHEVCMCTSEncoder.h" + +#include +#include + +#include +#include +#include +#include + +#include "MediaUtilities.h" +#include "yaml-cpp/yaml.h" + +#define LTTNG_TRACE_INFO ELOG_TRACE_T +#define LTTNG_TRACE_WARNING ELOG_INFO_T +#define NONE_NULL_ASSIGN(assignee, assignerr) \ +if (assignerr) { \ + assignee = assignerr.as(); \ + ELOG_INFO_T("%s : %u", #assignee, assignee); \ +} + +namespace owt_base { + +DEFINE_LOGGER(SVTHEVCMCTSEncoder, "owt.SVTHEVCMCTSEncoder"); + +SVTHEVCMCTSEncoder::SVTHEVCMCTSEncoder(FrameFormat format, VideoCodecProfile profile, bool useSimulcast) + : m_encoderReady(false) + , m_dest(NULL) + , m_width_hi(0) + , m_height_hi(0) + , m_width_low(0) + , m_height_low(0) + , m_frameRate(0) + , m_bitrateKbps(0) + , m_keyFrameIntervalSeconds(0) + , m_payload_buffer(NULL) + , m_payload_buffer_length(0) + , m_stat_timestamp(0) + , m_stat_frame_count(0) + , m_clock(webrtc::Clock::GetRealTimeClock()) + , m_max_output_frames(3) + , m_stat_overflow_frames(0) + , m_stat_overflow_all_frames(0) + , m_conf_file("mcts_encoder.yaml") +{ + m_hi_res_encoder = boost::make_shared(); + m_low_res_encoder = boost::make_shared(); + + m_srv = boost::make_shared(); + m_srvWork = boost::make_shared(*m_srv); + m_thread = boost::make_shared(boost::bind(&boost::asio::io_service::run, m_srv)); +} + +SVTHEVCMCTSEncoder::~SVTHEVCMCTSEncoder() +{ + m_srvWork.reset(); + m_srv->stop(); + m_srv.reset(); + m_thread.reset(); + + if (m_payload_buffer) { + free(m_payload_buffer); + } + + m_dest = NULL; +} + +bool SVTHEVCMCTSEncoder::canSimulcast(FrameFormat format, uint32_t width, uint32_t height) +{ + boost::shared_lock lock(m_mutex); + + return false; +} + +bool SVTHEVCMCTSEncoder::isIdle() +{ + boost::shared_lock lock(m_mutex); + + return (m_dest == NULL); +} + +bool SVTHEVCMCTSEncoder::loadConf(const std::string name, EB_H265_ENC_CONFIGURATION& conf) +{ + + YAML::Node cfg; + try { + cfg = YAML::LoadFile(m_conf_file); + } catch (...) { + ELOG_ERROR_T("load config file %s fail, file error or not exist", m_conf_file.c_str()); + return false; + } + + if (!cfg[name]) { + ELOG_ERROR_T("%s part option not exist", name.c_str()); + return false; + } + + // Encoding Preset + NONE_NULL_ASSIGN(conf.encMode, cfg[name]["encMode"]) + NONE_NULL_ASSIGN(conf.tune, cfg[name]["tune"]) + NONE_NULL_ASSIGN(conf.asmType, cfg[name]["asmType"]) + + // GOP Structure + NONE_NULL_ASSIGN(conf.intraPeriodLength, cfg[name]["intraPeriodLength"]) + NONE_NULL_ASSIGN(conf.intraRefreshType, cfg[name]["intraRefreshType"]) + NONE_NULL_ASSIGN(conf.hierarchicalLevels, cfg[name]["hierarchicalLevels"]) + NONE_NULL_ASSIGN(conf.predStructure, cfg[name]["predStructure"]) + + // Input Info + NONE_NULL_ASSIGN(conf.sourceWidth, cfg[name]["sourceWidth"]) + NONE_NULL_ASSIGN(conf.sourceHeight, cfg[name]["sourceHeight"]) + NONE_NULL_ASSIGN(conf.frameRate, cfg[name]["frameRate"]) + NONE_NULL_ASSIGN(conf.encoderBitDepth, cfg[name]["encoderBitDepth"]) + if (cfg[name]["encoderColorFormat"]) { + conf.encoderColorFormat = static_cast(cfg[name]["encoderColorFormat"].as()); + ELOG_INFO_T("%s : %u", "conf.encoderColorFormat", conf.encoderColorFormat); + } + + // Rate Control + NONE_NULL_ASSIGN(conf.rateControlMode, cfg[name]["rateControlMode"]) + NONE_NULL_ASSIGN(conf.sceneChangeDetection, cfg[name]["sceneChangeDetection"]) + NONE_NULL_ASSIGN(conf.lookAheadDistance, cfg[name]["lookAheadDistance"]) + NONE_NULL_ASSIGN(conf.targetBitRate, cfg[name]["targetBitRate"]) + NONE_NULL_ASSIGN(conf.maxQpAllowed, cfg[name]["maxQpAllowed"]) + NONE_NULL_ASSIGN(conf.minQpAllowed, cfg[name]["minQpAllowed"]) + + // Bitstream Options + NONE_NULL_ASSIGN(conf.profile, cfg[name]["profile"]) + NONE_NULL_ASSIGN(conf.tier, cfg[name]["tier"]) + NONE_NULL_ASSIGN(conf.level, cfg[name]["level"]) + + // Deblock Filter + NONE_NULL_ASSIGN(conf.disableDlfFlag, cfg[name]["disableDlfFlag"]) + + NONE_NULL_ASSIGN(conf.enableSaoFlag, cfg[name]["enableSaoFlag"]) + + NONE_NULL_ASSIGN(conf.targetSocket, cfg[name]["targetSocket"]) + + // Tile Info + NONE_NULL_ASSIGN(conf.tileColumnCount, cfg[name]["tileColumnCount"]) + NONE_NULL_ASSIGN(conf.tileRowCount, cfg[name]["tileRowCount"]) + + return true; +} + +bool SVTHEVCMCTSEncoder::initEncoder(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds) +{ + EB_H265_ENC_CONFIGURATION high_conf; + memset(&high_conf, 0, sizeof(EB_H265_ENC_CONFIGURATION)); + EB_H265_ENC_CONFIGURATION low_conf; + memset(&low_conf, 0, sizeof(EB_H265_ENC_CONFIGURATION)); + + if (!loadConf("high", high_conf)) { + ELOG_ERROR_T("load high resolution config fail!"); + return false; + } + if (!loadConf("low", low_conf)) { + ELOG_ERROR_T("load low resolution config fail!"); + return false; + } + + m_width_hi = high_conf.sourceWidth; + m_height_hi = high_conf.sourceHeight; + + m_width_low = low_conf.sourceWidth; + m_height_low = low_conf.sourceHeight; + + int ready = false; + ready = m_hi_res_encoder->init(high_conf); + if (!ready) + return false; + ready = m_low_res_encoder->init(low_conf); + if (!ready) + return false; + + m_encoderReady = true; + + return true; +} + +bool SVTHEVCMCTSEncoder::initEncoderAsync(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds) +{ + m_srv->post(boost::bind(&SVTHEVCMCTSEncoder::initEncoder, this, width, height, frameRate, bitrateKbps, keyFrameIntervalSeconds)); + return true; +} + +int32_t SVTHEVCMCTSEncoder::generateStream(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds, + owt_base::FrameDestination* dest) +{ + boost::unique_lock ulock(m_mutex); + + ELOG_INFO_T("generateStream: {.width=%d, .height=%d, .frameRate=%d, .bitrateKbps=%d, .keyFrameIntervalSeconds=%d}" + , width, height, frameRate, bitrateKbps, keyFrameIntervalSeconds); + + if (m_dest) { + ELOG_ERROR_T("Only support one stream!"); + return -1; + } + + m_frameRate = frameRate; + m_bitrateKbps = bitrateKbps; + m_keyFrameIntervalSeconds = keyFrameIntervalSeconds; + + if (width != 0 && height != 0) { + if (!initEncoderAsync(width, height, m_frameRate, m_bitrateKbps, m_keyFrameIntervalSeconds)) + return -1; + } + + m_dest = dest; + addVideoDestination(m_dest); + + return 0; +} + +void SVTHEVCMCTSEncoder::degenerateStream(int32_t streamId) +{ + boost::unique_lock ulock(m_mutex); + + ELOG_DEBUG_T("degenerateStream"); + + assert(m_dest != NULL); + removeVideoDestination(m_dest); + m_dest = NULL; +} + +void SVTHEVCMCTSEncoder::setBitrate(unsigned short kbps, int32_t streamId) +{ + boost::shared_lock lock(m_mutex); + + ELOG_WARN_T("%s", __FUNCTION__); +} + +void SVTHEVCMCTSEncoder::requestKeyFrame(int32_t streamId) +{ + boost::shared_lock lock(m_mutex); + + ELOG_DEBUG_T("%s", __FUNCTION__); + //todo +} + +void SVTHEVCMCTSEncoder::onFrame(const Frame& frame) +{ + boost::shared_lock lock(m_mutex); + + if (frame.format != FRAME_FORMAT_I420) { + ELOG_ERROR_T("Frame format not supported"); + return; + } + + if (m_dest == NULL) { + return; + } + + if (!m_encoderReady) { + LTTNG_TRACE_WARNING("SVTHEVCMCTSEncoder: Not ready, Discard frame, pts(%ld)", (long int)frame.timeStamp / 90); + ELOG_WARN_T("Encoder not ready!"); + return; + } + + if (m_hi_res_encoder->frame_queue_size() > 1 || m_low_res_encoder->frame_queue_size() > 1) { + LTTNG_TRACE_WARNING("SVTHEVCMCTSEncoder: Too many pending frames, Discard frame, pts(%ld)", (long int)frame.timeStamp / 90); + ELOG_WARN_T("Too many pending frames"); + } else { + LTTNG_TRACE_INFO("SVTHEVCMCTSEncoder: Receive frame, pts(%ld)", (long int)frame.timeStamp / 90); + + m_hi_res_encoder->sendFrame(frame); + m_low_res_encoder->sendFrame(frame); + } + + fetchOutput(); + deliverOutput(); +} + +void SVTHEVCMCTSEncoder::fetchOutput() +{ + boost::shared_ptr pkt; + + while(true) { + pkt = m_hi_res_encoder->getEncodedPacket(); + if (!pkt) + break; + + m_hi_res_packet_queue.push(pkt); + LTTNG_TRACE_INFO("SVTHEVCMCTSEncoder: HR-Encoding Done, pts(%ld), %s" + , pkt->pts + , pkt->isKey ? "key" : "delta"); + } + + while(true) { + pkt = m_low_res_encoder->getEncodedPacket(); + if (!pkt) + break; + + m_low_res_packet_queue.push(pkt); + LTTNG_TRACE_INFO("SVTHEVCMCTSEncoder: LR-Encoding Done, pts(%ld), %s" + , pkt->pts + , pkt->isKey ? "key" : "delta"); + } +} + +void SVTHEVCMCTSEncoder::deliverOutput() +{ + int deliver_frames = 0; + + if (m_hi_res_packet_queue.size() == 0 || + m_low_res_packet_queue.size() == 0) { + ELOG_TRACE_T("Output frame not ready"); + return; + } + + while (m_hi_res_packet_queue.size() > 0 + && m_low_res_packet_queue.size() > 0) { + boost::shared_ptr hi_res = m_hi_res_packet_queue.front(); + m_hi_res_packet_queue.pop(); + + boost::shared_ptr low_res = m_low_res_packet_queue.front(); + m_low_res_packet_queue.pop(); + + deliverMCTSFrame(hi_res, low_res); + + deliver_frames++; + if (deliver_frames > 1) { + ELOG_TRACE_T("Deliver extra frame"); + m_stat_overflow_frames++; + } + + m_stat_overflow_all_frames++; + if (m_stat_overflow_all_frames >= OverflowThresholdAllFrames) { + // if overflow > 5%, increase buffered frames + // if overflow < 4%, decrease buffered frames + if (m_stat_overflow_frames > OverflowThresholdFrames) { + m_max_output_frames++; + ELOG_TRACE_T("Increase max buffered frame to %d", m_max_output_frames); + } else if (m_stat_overflow_frames < OverflowThresholdFrames - 1) { + m_max_output_frames--; + ELOG_TRACE_T("Decrease max buffered frame to %d", m_max_output_frames); + } + + m_stat_overflow_all_frames = 0; + m_stat_overflow_frames = 0; + } + + if (m_hi_res_packet_queue.size() <= m_max_output_frames + && m_low_res_packet_queue.size() <= m_max_output_frames) { + break; + } + } +} + +void SVTHEVCMCTSEncoder::deliverMCTSFrame(boost::shared_ptr hi_res_pkt, + boost::shared_ptr low_res_pkt) +{ + uint32_t length = 12 + hi_res_pkt->length + 12 + low_res_pkt->length; + + while (m_payload_buffer_length < length) { + if (m_payload_buffer) { + m_payload_buffer_length = m_width_hi * m_height_hi * 2; + m_payload_buffer = (uint8_t *)malloc(m_payload_buffer_length); + continue; + } + + m_payload_buffer_length *= 2; + m_payload_buffer = (uint8_t *)realloc(m_payload_buffer, m_payload_buffer_length); + } + + uint8_t *payload = m_payload_buffer; + + int offset = 0; + + // hi_res + payload[offset + 0] = m_width_hi & 0xff; + payload[offset + 1] = (m_width_hi >> 8) & 0xff; + payload[offset + 2] = (m_width_hi >> 16) & 0xff; + payload[offset + 3] = (m_width_hi >> 24) & 0xff; + offset += 4; + + payload[offset + 0] = m_height_hi & 0xff; + payload[offset + 1] = (m_height_hi >> 8) & 0xff; + payload[offset + 2] = (m_height_hi >> 16) & 0xff; + payload[offset + 3] = (m_height_hi >> 24) & 0xff; + offset += 4; + + payload[offset + 0] = hi_res_pkt->length & 0xff; + payload[offset + 1] = (hi_res_pkt->length >> 8) & 0xff; + payload[offset + 2] = (hi_res_pkt->length >> 16) & 0xff; + payload[offset + 3] = (hi_res_pkt->length >> 24) & 0xff; + offset += 4; + + memcpy(payload + offset, hi_res_pkt->data, hi_res_pkt->length); + offset += hi_res_pkt->length; + + // low_res + payload[offset + 0] = m_width_low & 0xff; + payload[offset + 1] = (m_width_low >> 8) & 0xff; + payload[offset + 2] = (m_width_low >> 16) & 0xff; + payload[offset + 3] = (m_width_low >> 24) & 0xff; + offset += 4; + + payload[offset + 0] = m_height_low & 0xff; + payload[offset + 1] = (m_height_low >> 8) & 0xff; + payload[offset + 2] = (m_height_low >> 16) & 0xff; + payload[offset + 3] = (m_height_low >> 24) & 0xff; + offset += 4; + + payload[offset + 0] = low_res_pkt->length & 0xff; + payload[offset + 1] = (low_res_pkt->length >> 8) & 0xff; + payload[offset + 2] = (low_res_pkt->length >> 16) & 0xff; + payload[offset + 3] = (low_res_pkt->length >> 24) & 0xff; + offset += 4; + + memcpy(payload + offset, low_res_pkt->data, low_res_pkt->length); + offset += low_res_pkt->length; + + m_encodedFrameCount++; + // out + Frame outFrame; + memset(&outFrame, 0, sizeof(outFrame)); + outFrame.format = FRAME_FORMAT_H265; + outFrame.payload = payload; + outFrame.length = length; + outFrame.timeStamp = (m_encodedFrameCount * 1000 / m_frameRate) * 90; + //outFrame.timeStamp = m_clock->TimeInMilliseconds() * 90; + // outFrame.ori_timeStamp = hi_res_pkt->pts * 90; + outFrame.additionalInfo.video.width = m_width_hi; + outFrame.additionalInfo.video.height = m_height_hi; + outFrame.additionalInfo.video.isKeyFrame = hi_res_pkt->isKey; + + LTTNG_TRACE_INFO("SVTHEVCMCTSEncoder: Deliver frame, pts(%ld), %s" + , hi_res_pkt->pts + , hi_res_pkt->isKey ? "key" : "delta"); + + ELOG_TRACE_T("deliverFrame %s, hi-res %dx%d(%s), length(%d), low-res (%s), length(%d) ", + getFormatStr(outFrame.format), + outFrame.additionalInfo.video.width, + outFrame.additionalInfo.video.height, + hi_res_pkt->isKey ? "key" : "delta", + hi_res_pkt->length, + low_res_pkt->isKey ? "key" : "delta", + low_res_pkt->length + ); + + deliverFrame(outFrame); + + //FPS stat + if (m_stat_timestamp == 0) { + m_stat_timestamp = currentTimeMs(); + } + + m_stat_frame_count++; + if (m_stat_frame_count == 100) { + int64_t cur_time = currentTimeMs(); + ELOG_INFO_T("SVTHEVCMCTSEncoder: FPS %.2f", + (double)(1000 * m_stat_frame_count) / (cur_time - m_stat_timestamp)); + m_stat_timestamp = cur_time; + m_stat_frame_count = 0; + } +} + +} // namespace owt_base diff --git a/WebRTC-Sample/owt-server/source/common/IMSVTHEVCMCTSEncoder.h b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCMCTSEncoder.h new file mode 100644 index 00000000..1b633b61 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/common/IMSVTHEVCMCTSEncoder.h @@ -0,0 +1,107 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef SVTHEVCMCTSEncoder_h +#define SVTHEVCMCTSEncoder_h + +#include + +#include +#include +#include +#include +#include +#include + +#include "logger.h" +//#include "lttng_logger.h" +#include "MediaFramePipeline.h" +#include "IMSVTHEVCEncoderBase.h" + +namespace owt_base { + +static inline int64_t currentTimeMs() +{ + timeval time; + gettimeofday(&time, nullptr); + return ((time.tv_sec * 1000) + (time.tv_usec / 1000)); +} + +class SVTHEVCMCTSEncoder : public VideoFrameEncoder, public FrameSource { + DECLARE_LOGGER(); + + const uint32_t OverflowThresholdFrames = 5; + const uint32_t OverflowThresholdAllFrames = 100; + +public: + SVTHEVCMCTSEncoder(FrameFormat format, VideoCodecProfile profile, bool useSimulcast = false); + ~SVTHEVCMCTSEncoder(); + + FrameFormat getInputFormat() {return FRAME_FORMAT_I420;} + + // Implements VideoFrameEncoder. + void onFrame(const Frame&); + bool canSimulcast(FrameFormat format, uint32_t width, uint32_t height); + bool isIdle(); + int32_t generateStream(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds, + FrameDestination* dest); + void degenerateStream(int32_t streamId); + void setBitrate(unsigned short kbps, int32_t streamId); + void requestKeyFrame(int32_t streamId); + +protected: + bool initEncoder(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds); + bool initEncoderAsync(uint32_t width, uint32_t height, uint32_t frameRate, + uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds); + void fetchOutput(); + void deliverOutput(); + void deliverMCTSFrame(boost::shared_ptr hi_res_pkt, + boost::shared_ptr low_res_pkt); + bool loadConf(const std::string name, EB_H265_ENC_CONFIGURATION& conf); + +private: + bool m_encoderReady; + FrameDestination *m_dest; + + uint32_t m_width_hi; + uint32_t m_height_hi; + uint32_t m_width_low; + uint32_t m_height_low; + uint32_t m_frameRate; + uint32_t m_bitrateKbps; + uint32_t m_keyFrameIntervalSeconds; + + uint32_t m_encodedFrameCount; + + boost::shared_ptr m_hi_res_encoder; + boost::shared_ptr m_low_res_encoder; + + boost::shared_mutex m_mutex; + + std::queue> m_hi_res_packet_queue; + std::queue> m_low_res_packet_queue; + + uint8_t *m_payload_buffer; + uint32_t m_payload_buffer_length; + + boost::shared_ptr m_srv; + boost::shared_ptr m_srvWork; + boost::shared_ptr m_thread; + + int64_t m_stat_timestamp; + int32_t m_stat_frame_count; + + const webrtc::Clock *m_clock; + + uint32_t m_max_output_frames; + uint32_t m_stat_overflow_frames; + uint32_t m_stat_overflow_all_frames; + + std::string m_conf_file; +}; + +} /* namespace owt_base */ +#endif /* SVTHEVCMCTSEncoder_h */ diff --git a/WebRTC-Sample/owt-server/source/common/IMVideo.h b/WebRTC-Sample/owt-server/source/common/IMVideo.h new file mode 100644 index 00000000..4a49d4b8 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/common/IMVideo.h @@ -0,0 +1,13 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef IMVIDEO_H +#define IMVIDEO_H + +inline bool isHEVCMCTSVideoResolution(uint32_t width, uint32_t height) { + return (width = 3840 && height == 2048) + || (width = 7680 && height == 3840); +} + +#endif /* IMVideo.h */ diff --git a/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/4k_encoder.yaml b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/4k_encoder.yaml new file mode 100644 index 00000000..6ebcce25 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/4k_encoder.yaml @@ -0,0 +1,85 @@ +# Refer to https://github.com/OpenVisualCloud/SVT-HEVC/blob/master/Docs/svt-hevc_encoder_user_guide.md for parameter explain +high: + # Encoding preset + encMode: 10 # The preset for quality and performance balance, [0-12], 0 is best quality, 12 is best performance + # tune: 1 # Specific encoder tuning, 0 is visually optimized mode, 2 is VMAF optimized mode + # asmType: 1 # 0: C only, 1: Auto select highest assembly instruction set supported) + + # GOP Structure + # intraPeriodLength: 4 # The distance between two adjacent intra frame + # intraRefreshType: 0 # -1: CRA (Open GOP) >=0: IDR + # hierarchicalLevels: 0 # The hierarchical level to construct GOP + # predStructure: 0 # [0-2], 0 is IPPP..., 1 is IBBB...(B is low-delay B), and 2 is IBBB...(B is normal bi-directional B) + + # Input Info + sourceWidth: 3840 + sourceHeight: 2048 + frameRate: 30 + # encoderBitDepth: 8 + # encoderColorFormat: 1 # 1: 420, 2: 422, 3: 444 + + # Rate Control + # rateControlMode: 1 # 0: CQP, 1: VBR + # sceneChangeDetection: 1 + # lookAheadDistance: 0 # The number of frames that used for look ahead + targetBitRate: 20000000 + # maxQpAllowed: 26 # [0 - 51] + # minQpAllowed: 20 # [0 - 50] + + # Bitstream options + # profile: 2 # 1: Main, 2: Main 10 + # tier: 0 # 0: Main, 1: High + # level: 0 # 0 to 6.2 [0 for auto determine Level] + + # Deblock filter + # disableDlfFlag: 0 + + # SAO + # enableSaoFlag: 1 + + # targetSocket: 1 # -1: Both Sockets, 0: Socket 0, 1: Socket 1. + tileColumnCount: 10 # Tile count in the Row + tileRowCount: 8 # Tile count in the column + +low: + # Encoding preset + encMode: 9 # The preset for quality and performance balance, [0-12], 0 is best quality, 12 is best performance + # tune: 1 # Specific encoder tuning, 0 is visually optimized mode, 2 is VMAF optimized mode + # asmType: 1 # 0: C only, 1: Auto select highest assembly instruction set supported) + + # GOP Structure + # intraPeriodLength: 4 # The distance between two adjacent intra frame + # intraRefreshType: 0 # -1: CRA (Open GOP) >=0: IDR + # hierarchicalLevels: 0 # The hierarchical level to construct GOP + # predStructure: 0 # [0-2], 0 is IPPP..., 1 is IBBB...(B is low-delay B), and 2 is IBBB...(B is normal bi-directional B) + + # Input Info + sourceWidth: 1280 + sourceHeight: 768 + frameRate: 30 + # encoderBitDepth: 8 + # encoderColorFormat: 1 # 1: 420, 2: 422, 3: 444 + + # Rate Control + # rateControlMode: 1 # 0: CQP, 1: VBR + # sceneChangeDetection: 1 + # lookAheadDistance: 0 # The number of frames that used for look ahead + targetBitRate: 1736000 + # maxQpAllowed: 26 # [0 - 51] + # minQpAllowed: 20 # [0 - 50] + + # Bitstream options + # profile: 2 # 1: Main, 2: Main 10 + # tier: 0 # 0: Main, 1: High + # level: 0 # 0 to 6.2 [0 for auto determine Level] + + # Deblock filter + # disableDlfFlag: 0 + + # SAO + # enableSaoFlag: 1 + + # targetSocket: 1 # -1: Both Sockets, 0: Socket 0, 1: Socket 1. + tileColumnCount: 5 # Tile count in the Row + tileRowCount: 3 # Tile count in the column + diff --git a/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/8k_encoder.yaml b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/8k_encoder.yaml new file mode 100644 index 00000000..8209797b --- /dev/null +++ b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/8k_encoder.yaml @@ -0,0 +1,85 @@ +# Refer to https://github.com/OpenVisualCloud/SVT-HEVC/blob/master/Docs/svt-hevc_encoder_user_guide.md for parameter explain +high: + # Encoding preset + encMode: 10 # The preset for quality and performance balance, [0-12], 0 is best quality, 12 is best performance + # tune: 1 # Specific encoder tuning, 0 is visually optimized mode, 2 is VMAF optimized mode + # asmType: 1 # 0: C only, 1: Auto select highest assembly instruction set supported) + + # GOP Structure + # intraPeriodLength: 4 # The distance between two adjacent intra frame + # intraRefreshType: 0 # -1: CRA (Open GOP) >=0: IDR + # hierarchicalLevels: 0 # The hierarchical level to construct GOP + # predStructure: 0 # [0-2], 0 is IPPP..., 1 is IBBB...(B is low-delay B), and 2 is IBBB...(B is normal bi-directional B) + + # Input Info + sourceWidth: 7680 + sourceHeight: 3840 + frameRate: 30 + # encoderBitDepth: 8 + # encoderColorFormat: 1 # 1: 420, 2: 422, 3: 444 + + # Rate Control + # rateControlMode: 1 # 0: CQP, 1: VBR + # sceneChangeDetection: 1 + # lookAheadDistance: 0 # The number of frames that used for look ahead + targetBitRate: 100000000 + # maxQpAllowed: 26 # [0 - 51] + # minQpAllowed: 20 # [0 - 50] + + # Bitstream options + # profile: 2 # 1: Main, 2: Main 10 + # tier: 0 # 0: Main, 1: High + # level: 0 # 0 to 6.2 [0 for auto determine Level] + + # Deblock filter + # disableDlfFlag: 0 + + # SAO + # enableSaoFlag: 1 + + # targetSocket: 1 # -1: Both Sockets, 0: Socket 0, 1: Socket 1. + tileColumnCount: 12 # Tile count in the Row + tileRowCount: 6 # Tile count in the column + +low: + # Encoding preset + encMode: 9 # The preset for quality and performance balance, [0-12], 0 is best quality, 12 is best performance + # tune: 1 # Specific encoder tuning, 0 is visually optimized mode, 2 is VMAF optimized mode + # asmType: 1 # 0: C only, 1: Auto select highest assembly instruction set supported) + + # GOP Structure + # intraPeriodLength: 4 # The distance between two adjacent intra frame + # intraRefreshType: 0 # -1: CRA (Open GOP) >=0: IDR + # hierarchicalLevels: 0 # The hierarchical level to construct GOP + # predStructure: 0 # [0-2], 0 is IPPP..., 1 is IBBB...(B is low-delay B), and 2 is IBBB...(B is normal bi-directional B) + + # Input Info + sourceWidth: 512 + sourceHeight: 1280 + frameRate: 30 + # encoderBitDepth: 8 + # encoderColorFormat: 1 # 1: 420, 2: 422, 3: 444 + + # Rate Control + # rateControlMode: 1 # 0: CQP, 1: VBR + # sceneChangeDetection: 1 + # lookAheadDistance: 0 # The number of frames that used for look ahead + targetBitRate: 1736000 + # maxQpAllowed: 26 # [0 - 51] + # minQpAllowed: 20 # [0 - 50] + + # Bitstream options + # profile: 2 # 1: Main, 2: Main 10 + # tier: 0 # 0: Main, 1: High + # level: 0 # 0 to 6.2 [0 for auto determine Level] + + # Deblock filter + # disableDlfFlag: 0 + + # SAO + # enableSaoFlag: 1 + + # targetSocket: 1 # -1: Both Sockets, 0: Socket 0, 1: Socket 1. + tileColumnCount: 2 # Tile count in the Row + tileRowCount: 2 # Tile count in the column + diff --git a/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoCompositor.cpp b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoCompositor.cpp new file mode 100755 index 00000000..0a857918 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoCompositor.cpp @@ -0,0 +1,754 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#include "SoftVideoCompositor.h" + +#include "libyuv/convert.h" +#include "libyuv/scale.h" + +#include +#include + +#include + +#include + +using namespace webrtc; +using namespace owt_base; + +namespace mcu { + +DEFINE_LOGGER(AvatarManager, "mcu.media.SoftVideoCompositor.AvatarManager"); + +AvatarManager::AvatarManager(uint8_t size) + : m_size(size) +{ +} + +AvatarManager::~AvatarManager() +{ +} + +bool AvatarManager::getImageSize(const std::string &url, uint32_t *pWidth, uint32_t *pHeight) +{ + uint32_t width, height; + size_t begin, end; + char *str_end = NULL; + + begin = url.find('.'); + if (begin == std::string::npos) { + ELOG_WARN("Invalid image size in url(%s)", url.c_str()); + return false; + } + + end = url.find('x', begin); + if (end == std::string::npos) { + ELOG_WARN("Invalid image size in url(%s)", url.c_str()); + return false; + } + + width = strtol(url.data() + begin + 1, &str_end, 10); + if (url.data() + end != str_end) { + ELOG_WARN("Invalid image size in url(%s)", url.c_str()); + return false; + } + + begin = end; + end = url.find('.', begin); + if (end == std::string::npos) { + ELOG_WARN("Invalid image size in url(%s)", url.c_str()); + return false; + } + + height = strtol(url.data() + begin + 1, &str_end, 10); + if (url.data() + end != str_end) { + ELOG_WARN("Invalid image size in url(%s)", url.c_str()); + return false; + } + + *pWidth = width; + *pHeight = height; + + ELOG_TRACE("Image size in url(%s), %dx%d", url.c_str(), *pWidth, *pHeight); + return true; +} + +boost::shared_ptr AvatarManager::loadImage(const std::string &url) +{ + uint32_t width, height; + + if (!getImageSize(url, &width, &height)) + return NULL; + + std::ifstream in(url, std::ios::in | std::ios::binary); + + in.seekg (0, in.end); + uint32_t size = in.tellg(); + in.seekg (0, in.beg); + + if (size <= 0 || ((width * height * 3 + 1) / 2) != size) { + ELOG_WARN("Open avatar image(%s) error, invalid size %d, expected size %d" + , url.c_str(), size, (width * height * 3 + 1) / 2); + return NULL; + } + + char *image = new char [size];; + in.read (image, size); + in.close(); + + rtc::scoped_refptr i420Buffer = I420Buffer::Copy( + width, height, + reinterpret_cast(image), width, + reinterpret_cast(image + width * height), width / 2, + reinterpret_cast(image + width * height * 5 / 4), width / 2 + ); + + boost::shared_ptr frame(new webrtc::VideoFrame(i420Buffer, webrtc::kVideoRotation_0, 0)); + + delete [] image; + + return frame; +} + +bool AvatarManager::setAvatar(uint8_t index, const std::string &url) +{ + boost::unique_lock lock(m_mutex); + ELOG_DEBUG("setAvatar(%d) = %s", index, url.c_str()); + + auto it = m_inputs.find(index); + if (it == m_inputs.end()) { + m_inputs[index] = url; + return true; + } + + if (it->second == url) { + return true; + } + std::string old_url = it->second; + it->second = url; + + //delete + for (auto& it2 : m_inputs) { + if (old_url == it2.second) + return true; + } + m_frames.erase(old_url); + return true; +} + +bool AvatarManager::unsetAvatar(uint8_t index) +{ + boost::unique_lock lock(m_mutex); + ELOG_DEBUG("unsetAvatar(%d)", index); + + auto it = m_inputs.find(index); + if (it == m_inputs.end()) { + return true; + } + std::string url = it->second; + m_inputs.erase(it); + + //delete + for (auto& it2 : m_inputs) { + if (url == it2.second) + return true; + } + m_frames.erase(url); + return true; +} + +boost::shared_ptr AvatarManager::getAvatarFrame(uint8_t index) +{ + boost::unique_lock lock(m_mutex); + + auto it = m_inputs.find(index); + if (it == m_inputs.end()) { + ELOG_WARN("Not valid index(%d)", index); + return NULL; + } + auto it2 = m_frames.find(it->second); + if (it2 != m_frames.end()) { + return it2->second; + } + + boost::shared_ptr frame = loadImage(it->second); + m_frames[it->second] = frame; + return frame; +} + +DEFINE_LOGGER(SoftInput, "mcu.media.SoftVideoCompositor.SoftInput"); + +SoftInput::SoftInput() + : m_active(false) +{ + m_bufferManager.reset(new I420BufferManager(3)); + m_converter.reset(new owt_base::FrameConverter()); +} + +SoftInput::~SoftInput() +{ +} + +void SoftInput::setActive(bool active) +{ + boost::unique_lock lock(m_mutex); + m_active = active; + if (!m_active) + m_busyFrame.reset(); +} + +bool SoftInput::isActive(void) +{ + return m_active; +} + +void SoftInput::pushInput(webrtc::VideoFrame *videoFrame) +{ + { + boost::unique_lock lock(m_mutex); + if (!m_active) + return; + } + + if (isHEVCMCTSVideoResolution(videoFrame->width(), videoFrame->height())) { + rtc::scoped_refptr srcI420Buffer = videoFrame->video_frame_buffer(); + { + boost::unique_lock lock(m_mutex); + if (m_active) { + m_busyFrame.reset(new webrtc::VideoFrame(srcI420Buffer, webrtc::kVideoRotation_0, videoFrame->timestamp_us())); + } + } + + return; + } + + rtc::scoped_refptr dstBuffer = m_bufferManager->getFreeBuffer(videoFrame->width(), videoFrame->height()); + if (!dstBuffer) { + ELOG_ERROR("No free buffer"); + return; + } + + rtc::scoped_refptr srcI420Buffer = videoFrame->video_frame_buffer(); + if (!m_converter->convert(srcI420Buffer, dstBuffer.get())) { + ELOG_ERROR("I420Copy failed"); + return; + } + + { + boost::unique_lock lock(m_mutex); + if (m_active) + m_busyFrame.reset(new webrtc::VideoFrame(dstBuffer, webrtc::kVideoRotation_0, 0)); + } +} + +boost::shared_ptr SoftInput::popInput() +{ + boost::unique_lock lock(m_mutex); + + if(!m_active) + return NULL; + + return m_busyFrame; +} + +DEFINE_LOGGER(SoftFrameGenerator, "mcu.media.SoftVideoCompositor.SoftFrameGenerator"); + +SoftFrameGenerator::SoftFrameGenerator( + SoftVideoCompositor *owner, + owt_base::VideoSize &size, + owt_base::YUVColor &bgColor, + const bool crop, + const uint32_t maxFps, + const uint32_t minFps) + : m_clock(Clock::GetRealTimeClock()) + , m_owner(owner) + , m_maxSupportedFps(maxFps) + , m_minSupportedFps(minFps) + , m_counter(0) + , m_counterMax(0) + , m_size(size) + , m_bgColor(bgColor) + , m_crop(crop) + , m_configureChanged(false) + , m_parallelNum(0) +{ + ELOG_DEBUG_T("Support fps max(%d), min(%d)", m_maxSupportedFps, m_minSupportedFps); + + uint32_t fps = m_minSupportedFps; + while (fps <= m_maxSupportedFps) { + if (fps == m_maxSupportedFps) + break; + + fps *= 2; + } + if (fps > m_maxSupportedFps) { + ELOG_WARN_T("Invalid fps min(%d), max(%d) --> mix(%d), max(%d)" + , m_minSupportedFps, m_maxSupportedFps + , m_minSupportedFps, m_minSupportedFps + ); + m_maxSupportedFps = m_minSupportedFps; + } + + m_counter = 0; + m_counterMax = m_maxSupportedFps / m_minSupportedFps; + + m_outputs.resize(m_maxSupportedFps / m_minSupportedFps); + + m_bufferManager.reset(new I420BufferManager(30)); + + // parallet composition + uint32_t nThreads = boost::thread::hardware_concurrency(); + m_parallelNum = nThreads / 2; + if (m_parallelNum > 16) + m_parallelNum = 16; + + ELOG_DEBUG_T("hardware concurrency %d, parallel composition num %d", nThreads, m_parallelNum); + + if (m_parallelNum > 1) { + m_srv = boost::make_shared(); + m_srvWork = boost::make_shared(*m_srv); + m_thrGrp = boost::make_shared(); + + for (uint32_t i = 0; i < m_parallelNum; i++) + m_thrGrp->create_thread(boost::bind(&boost::asio::io_service::run, m_srv)); + } + + m_textDrawer.reset(new owt_base::FFmpegDrawText()); + + m_jobTimer.reset(new JobTimer(m_maxSupportedFps, this)); + m_jobTimer->start(); +} + +SoftFrameGenerator::~SoftFrameGenerator() +{ + ELOG_DEBUG_T("Exit"); + + if (m_srvWork) + m_srvWork.reset(); + + if (m_srv) { + m_srv->stop(); + m_srv.reset(); + } + + if (m_thrGrp) { + m_thrGrp->join_all(); + m_thrGrp.reset(); + } + + m_jobTimer->stop(); + + for (uint32_t i = 0; i < m_outputs.size(); i++) { + if (m_outputs[i].size()) + ELOG_WARN_T("Outputs not empty!!!"); + } +} + +void SoftFrameGenerator::updateLayoutSolution(LayoutSolution& solution) +{ + boost::unique_lock lock(m_configMutex); + + m_newLayout = solution; + m_configureChanged = true; +} + +bool SoftFrameGenerator::isSupported(uint32_t width, uint32_t height, uint32_t fps) +{ + if (fps > m_maxSupportedFps || fps < m_minSupportedFps) + return false; + + uint32_t n = m_minSupportedFps; + while (n <= m_maxSupportedFps) { + if (n == fps) + return true; + + n *= 2; + } + + return false; +} + +bool SoftFrameGenerator::addOutput(const uint32_t width, const uint32_t height, const uint32_t fps, owt_base::FrameDestination *dst) { + assert(isSupported(width, height, fps)); + + boost::unique_lock lock(m_outputMutex); + + int index = m_maxSupportedFps / fps - 1; + + Output_t output{.width = width, .height = height, .fps = fps, .dest = dst}; + m_outputs[index].push_back(output); + return true; +} + +bool SoftFrameGenerator::removeOutput(owt_base::FrameDestination *dst) { + boost::unique_lock lock(m_outputMutex); + + for (uint32_t i = 0; i < m_outputs.size(); i++) { + for (auto it = m_outputs[i].begin(); it != m_outputs[i].end(); ++it) { + if (it->dest == dst) { + m_outputs[i].erase(it); + return true; + } + } + } + + return false; +} + +void SoftFrameGenerator::onTimeout() +{ + bool hasValidOutput = false; + { + boost::unique_lock lock(m_outputMutex); + for (uint32_t i = 0; i < m_outputs.size(); i++) { + if (m_counter % (i + 1)) + continue; + + if (m_outputs[i].size() > 0) { + hasValidOutput = true; + break; + } + } + } + + if (hasValidOutput) { + rtc::scoped_refptr compositeBuffer = generateFrame(); + if (compositeBuffer) { + webrtc::VideoFrame compositeFrame( + compositeBuffer, + webrtc::kVideoRotation_0, + m_clock->TimeInMilliseconds() + ); + compositeFrame.set_timestamp(compositeFrame.timestamp_us() * kMsToRtpTimestamp); + + owt_base::Frame frame; + memset(&frame, 0, sizeof(frame)); + frame.format = owt_base::FRAME_FORMAT_I420; + frame.payload = reinterpret_cast(&compositeFrame); + frame.length = 0; // unused. + frame.timeStamp = compositeFrame.timestamp(); + frame.additionalInfo.video.width = compositeFrame.width(); + frame.additionalInfo.video.height = compositeFrame.height(); + + m_textDrawer->drawFrame(frame); + + { + boost::unique_lock lock(m_outputMutex); + for (uint32_t i = 0; i < m_outputs.size(); i++) { + if (m_counter % (i + 1)) + continue; + + for (auto it = m_outputs[i].begin(); it != m_outputs[i].end(); ++it) { + ELOG_TRACE_T("+++deliverFrame(%d), dst(%p), fps(%d), timestamp(%d)" + , m_counter, it->dest, m_maxSupportedFps / (i + 1), frame.timeStamp / 90); + + it->dest->onFrame(frame); + } + } + } + } + } + + m_counter = (m_counter + 1) % m_counterMax; +} + +rtc::scoped_refptr SoftFrameGenerator::generateFrame() +{ + reconfigureIfNeeded(); + return layout(); +} + +void SoftFrameGenerator::layout_regions(SoftFrameGenerator *t, rtc::scoped_refptr compositeBuffer, const LayoutSolution ®ions) +{ + uint32_t composite_width = compositeBuffer->width(); + uint32_t composite_height = compositeBuffer->height(); + + for (LayoutSolution::const_iterator it = regions.begin(); it != regions.end(); ++it) { + boost::shared_ptr inputFrame = t->m_owner->getInputFrame(it->input); + if (inputFrame == NULL) { + continue; + } + + rtc::scoped_refptr inputBuffer = inputFrame->video_frame_buffer(); + + Region region = it->region; + uint32_t dst_x = (uint64_t)composite_width * region.area.rect.left.numerator / region.area.rect.left.denominator; + uint32_t dst_y = (uint64_t)composite_height * region.area.rect.top.numerator / region.area.rect.top.denominator; + uint32_t dst_width = (uint64_t)composite_width * region.area.rect.width.numerator / region.area.rect.width.denominator; + uint32_t dst_height = (uint64_t)composite_height * region.area.rect.height.numerator / region.area.rect.height.denominator; + + if (dst_x + dst_width > composite_width) + dst_width = composite_width - dst_x; + + if (dst_y + dst_height > composite_height) + dst_height = composite_height - dst_y; + + uint32_t cropped_dst_width; + uint32_t cropped_dst_height; + uint32_t src_x; + uint32_t src_y; + uint32_t src_width; + uint32_t src_height; + if (t->m_crop) { + src_width = std::min((uint32_t)inputBuffer->width(), dst_width * inputBuffer->height() / dst_height); + src_height = std::min((uint32_t)inputBuffer->height(), dst_height * inputBuffer->width() / dst_width); + src_x = (inputBuffer->width() - src_width) / 2; + src_y = (inputBuffer->height() - src_height) / 2; + + cropped_dst_width = dst_width; + cropped_dst_height = dst_height; + } else { + src_width = inputBuffer->width(); + src_height = inputBuffer->height(); + src_x = 0; + src_y = 0; + + cropped_dst_width = std::min(dst_width, inputBuffer->width() * dst_height / inputBuffer->height()); + cropped_dst_height = std::min(dst_height, inputBuffer->height() * dst_width / inputBuffer->width()); + } + + dst_x += (dst_width - cropped_dst_width) / 2; + dst_y += (dst_height - cropped_dst_height) / 2; + + src_x &= ~1; + src_y &= ~1; + src_width &= ~1; + src_height &= ~1; + dst_x &= ~1; + dst_y &= ~1; + cropped_dst_width &= ~1; + cropped_dst_height &= ~1; + + int ret = libyuv::I420Scale( + inputBuffer->DataY() + src_y * inputBuffer->StrideY() + src_x, inputBuffer->StrideY(), + inputBuffer->DataU() + (src_y * inputBuffer->StrideU() + src_x) / 2, inputBuffer->StrideU(), + inputBuffer->DataV() + (src_y * inputBuffer->StrideV() + src_x) / 2, inputBuffer->StrideV(), + src_width, src_height, + compositeBuffer->MutableDataY() + dst_y * compositeBuffer->StrideY() + dst_x, compositeBuffer->StrideY(), + compositeBuffer->MutableDataU() + (dst_y * compositeBuffer->StrideU() + dst_x) / 2, compositeBuffer->StrideU(), + compositeBuffer->MutableDataV() + (dst_y * compositeBuffer->StrideV() + dst_x) / 2, compositeBuffer->StrideV(), + cropped_dst_width, cropped_dst_height, + libyuv::kFilterBox); + if (ret != 0) + ELOG_ERROR("I420Scale failed, ret %d", ret); + } +} + +rtc::scoped_refptr SoftFrameGenerator::layout() +{ + rtc::scoped_refptr compositeBuffer = m_bufferManager->getFreeBuffer(m_size.width, m_size.height); + if (!compositeBuffer) { + ELOG_ERROR("No valid composite buffer"); + return NULL; + } + + if (isHEVCMCTSVideoResolution(m_size.width, m_size.height)) { + if (m_layout.size() == 1) { + boost::shared_ptr inputFrame = m_owner->getInputFrame(m_layout.front().input); + if (inputFrame && inputFrame->width() == (int32_t)m_size.width && inputFrame->height() == (int32_t)m_size.height) { + return inputFrame->video_frame_buffer(); + } + } + } + + // Set the background color + libyuv::I420Rect( + compositeBuffer->MutableDataY(), compositeBuffer->StrideY(), + compositeBuffer->MutableDataU(), compositeBuffer->StrideU(), + compositeBuffer->MutableDataV(), compositeBuffer->StrideV(), + 0, 0, compositeBuffer->width(), compositeBuffer->height(), + m_bgColor.y, m_bgColor.cb, m_bgColor.cr); + + bool isParallelFrameComposition = m_parallelNum > 1 && m_layout.size() > 4; + + if (isParallelFrameComposition) { + int nParallelRegions = (m_layout.size() + m_parallelNum - 1) / m_parallelNum; + int nRegions = m_layout.size(); + + LayoutSolution::iterator regions_begin = m_layout.begin(); + LayoutSolution::iterator regions_end = m_layout.begin(); + + std::vector>> tasks; + while (nRegions > 0) { + if (nRegions < nParallelRegions) + nParallelRegions = nRegions; + + regions_begin = regions_end; + advance(regions_end, nParallelRegions); + + boost::shared_ptr> task = boost::make_shared>( + boost::bind(SoftFrameGenerator::layout_regions, + this, + compositeBuffer, + LayoutSolution(regions_begin, regions_end)) + ); + m_srv->post(boost::bind(&boost::packaged_task::operator(), task)); + tasks.push_back(task); + + nRegions -= nParallelRegions; + } + + for (auto& task : tasks) + task->get_future().wait(); + } else { + layout_regions(this, compositeBuffer, m_layout); + } + + return compositeBuffer; +} + +void SoftFrameGenerator::reconfigureIfNeeded() +{ + { + boost::unique_lock lock(m_configMutex); + if (!m_configureChanged) + return; + + m_layout = m_newLayout; + m_configureChanged = false; + } + + ELOG_DEBUG_T("reconfigure"); +} + +void SoftFrameGenerator::drawText(const std::string& textSpec) +{ + m_textDrawer->setText(textSpec); + m_textDrawer->enable(true); +} + +void SoftFrameGenerator::clearText() +{ + m_textDrawer->enable(false); +} + +DEFINE_LOGGER(SoftVideoCompositor, "mcu.media.SoftVideoCompositor"); + +SoftVideoCompositor::SoftVideoCompositor(uint32_t maxInput, VideoSize rootSize, YUVColor bgColor, bool crop) + : m_maxInput(maxInput) +{ + m_inputs.resize(m_maxInput); + for (auto& input : m_inputs) { + input.reset(new SoftInput()); + } + + m_avatarManager.reset(new AvatarManager(maxInput)); + + m_generators.resize(2); + m_generators[0].reset(new SoftFrameGenerator(this, rootSize, bgColor, crop, 60, 15)); + m_generators[1].reset(new SoftFrameGenerator(this, rootSize, bgColor, crop, 48, 6)); +} + +SoftVideoCompositor::~SoftVideoCompositor() +{ + m_generators.clear(); + m_avatarManager.reset(); + m_inputs.clear(); +} + +void SoftVideoCompositor::updateRootSize(VideoSize& rootSize) +{ + ELOG_WARN("Not support updateRootSize: %dx%d", rootSize.width, rootSize.height); +} + +void SoftVideoCompositor::updateBackgroundColor(YUVColor& bgColor) +{ + ELOG_WARN("Not support updateBackgroundColor: YCbCr(0x%x, 0x%x, 0x%x)", bgColor.y, bgColor.cb, bgColor.cr); +} + +void SoftVideoCompositor::updateLayoutSolution(LayoutSolution& solution) +{ + assert(solution.size() <= m_maxInput); + + for (auto& generator : m_generators) { + generator->updateLayoutSolution(solution); + } +} + +bool SoftVideoCompositor::activateInput(int input) +{ + m_inputs[input]->setActive(true); + return true; +} + +void SoftVideoCompositor::deActivateInput(int input) +{ + m_inputs[input]->setActive(false); +} + +bool SoftVideoCompositor::setAvatar(int input, const std::string& avatar) +{ + return m_avatarManager->setAvatar(input, avatar); +} + +bool SoftVideoCompositor::unsetAvatar(int input) +{ + return m_avatarManager->unsetAvatar(input); +} + +void SoftVideoCompositor::pushInput(int input, const Frame& frame) +{ + assert(frame.format == owt_base::FRAME_FORMAT_I420); + webrtc::VideoFrame* i420Frame = reinterpret_cast(frame.payload); + + m_inputs[input]->pushInput(i420Frame); +} + +bool SoftVideoCompositor::addOutput(const uint32_t width, const uint32_t height, const uint32_t framerateFPS, owt_base::FrameDestination *dst) +{ + ELOG_DEBUG("addOutput, %dx%d, fps(%d), dst(%p)", width, height, framerateFPS, dst); + + for (auto& generator : m_generators) { + if (generator->isSupported(width, height, framerateFPS)) { + return generator->addOutput(width, height, framerateFPS, dst); + } + } + + ELOG_ERROR("Can not addOutput, %dx%d, fps(%d), dst(%p)", width, height, framerateFPS, dst); + return false; +} + +bool SoftVideoCompositor::removeOutput(owt_base::FrameDestination *dst) +{ + ELOG_DEBUG("removeOutput, dst(%p)", dst); + + for (auto& generator : m_generators) { + if (generator->removeOutput(dst)) { + return true; + } + } + + ELOG_ERROR("Can not removeOutput, dst(%p)", dst); + return false; +} + +boost::shared_ptr SoftVideoCompositor::getInputFrame(int index) +{ + boost::shared_ptr src; + + auto& input = m_inputs[index]; + if (input->isActive()) { + src = input->popInput(); + } else { + src = m_avatarManager->getAvatarFrame(index); + } + + return src; +} + +void SoftVideoCompositor::drawText(const std::string& textSpec) +{ + for (auto& generator : m_generators) { + generator->drawText(textSpec); + } +} + +void SoftVideoCompositor::clearText() +{ + for (auto& generator : m_generators) { + generator->clearText(); + } +} + +} diff --git a/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoCompositor.h b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoCompositor.h new file mode 100644 index 00000000..92a54b5b --- /dev/null +++ b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoCompositor.h @@ -0,0 +1,201 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef SoftVideoCompositor_h +#define SoftVideoCompositor_h + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "logger.h" +#include "JobTimer.h" +#include "MediaFramePipeline.h" +#include "FrameConverter.h" +#include "VideoFrameMixer.h" +#include "VideoLayout.h" +#include "I420BufferManager.h" +#include "FFmpegDrawText.h" + +namespace mcu { +class SoftVideoCompositor; + +class AvatarManager { + DECLARE_LOGGER(); + +public: + AvatarManager(uint8_t size); + ~AvatarManager(); + + bool setAvatar(uint8_t index, const std::string &url); + bool unsetAvatar(uint8_t index); + + boost::shared_ptr getAvatarFrame(uint8_t index); + +protected: + bool getImageSize(const std::string &url, uint32_t *pWidth, uint32_t *pHeight); + boost::shared_ptr loadImage(const std::string &url); + +private: + uint8_t m_size; + + std::map m_inputs; + std::map> m_frames; + + boost::shared_mutex m_mutex; +}; + +class SoftInput { + DECLARE_LOGGER(); + +public: + SoftInput(); + ~SoftInput(); + + void setActive(bool active); + bool isActive(void); + + void pushInput(webrtc::VideoFrame *videoFrame); + boost::shared_ptr popInput(); + +private: + bool m_active; + boost::shared_ptr m_busyFrame; + boost::shared_mutex m_mutex; + + boost::scoped_ptr m_bufferManager; + + boost::scoped_ptr m_converter; +}; + +class SoftFrameGenerator : public JobTimerListener +{ + DECLARE_LOGGER(); + + const uint32_t kMsToRtpTimestamp = 90; + + struct Output_t { + uint32_t width; + uint32_t height; + uint32_t fps; + owt_base::FrameDestination *dest; + }; + +public: + SoftFrameGenerator( + SoftVideoCompositor *owner, + owt_base::VideoSize &size, + owt_base::YUVColor &bgColor, + const bool crop, + const uint32_t maxFps, + const uint32_t minFps); + + ~SoftFrameGenerator(); + + void updateLayoutSolution(LayoutSolution& solution); + + bool isSupported(uint32_t width, uint32_t height, uint32_t fps); + + bool addOutput(const uint32_t width, const uint32_t height, const uint32_t fps, owt_base::FrameDestination *dst); + bool removeOutput(owt_base::FrameDestination *dst); + + void drawText(const std::string& textSpec); + void clearText(); + + void onTimeout() override; + +protected: + rtc::scoped_refptr generateFrame(); + rtc::scoped_refptr layout(); + static void layout_regions(SoftFrameGenerator *t, rtc::scoped_refptr compositeBuffer, const LayoutSolution ®ions); + + void reconfigureIfNeeded(); + +private: + const webrtc::Clock *m_clock; + + SoftVideoCompositor *m_owner; + uint32_t m_maxSupportedFps; + uint32_t m_minSupportedFps; + + uint32_t m_counter; + uint32_t m_counterMax; + + std::vector> m_outputs; + boost::shared_mutex m_outputMutex; + + // configure + owt_base::VideoSize m_size; + owt_base::YUVColor m_bgColor; + bool m_crop; + + // reconfifure + LayoutSolution m_layout; + LayoutSolution m_newLayout; + bool m_configureChanged; + boost::shared_mutex m_configMutex; + + boost::scoped_ptr m_bufferManager; + + boost::scoped_ptr m_jobTimer; + + // parallel composition + uint32_t m_parallelNum; + boost::shared_ptr m_srv; + boost::shared_ptr m_srvWork; + boost::shared_ptr m_thrGrp; + + boost::shared_ptr m_textDrawer; +}; + +/** + * composite a sequence of frames into one frame based on current layout config, + * In the future, we may enable the video rotation based on VAD history. + */ +class SoftVideoCompositor : public VideoFrameCompositor { + DECLARE_LOGGER(); + + friend class SoftFrameGenerator; + +public: + SoftVideoCompositor(uint32_t maxInput, owt_base::VideoSize rootSize, owt_base::YUVColor bgColor, bool crop); + ~SoftVideoCompositor(); + + bool activateInput(int input); + void deActivateInput(int input); + bool setAvatar(int input, const std::string& avatar); + bool unsetAvatar(int input); + void pushInput(int input, const owt_base::Frame&); + + void updateRootSize(owt_base::VideoSize& rootSize); + void updateBackgroundColor(owt_base::YUVColor& bgColor); + void updateLayoutSolution(LayoutSolution& solution); + + bool addOutput(const uint32_t width, const uint32_t height, const uint32_t framerateFPS, owt_base::FrameDestination *dst) override; + bool removeOutput(owt_base::FrameDestination *dst) override; + + void drawText(const std::string& textSpec); + void clearText(); + +protected: + boost::shared_ptr getInputFrame(int index); + +private: + uint32_t m_maxInput; + + std::vector> m_generators; + + std::vector> m_inputs; + boost::scoped_ptr m_avatarManager; +}; + +} +#endif /* SoftVideoCompositor_h*/ diff --git a/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoFrameMixerImpl.h b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoFrameMixerImpl.h new file mode 100644 index 00000000..f10c9ad6 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoFrameMixerImpl.h @@ -0,0 +1,322 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef VideoFrameMixerImpl_h +#define VideoFrameMixerImpl_h + +#include +#include +#include +#include +#include +#include + +#include "SoftVideoCompositor.h" + +#include +#include + +#include + +#ifdef ENABLE_MSDK +#include "MsdkVideoCompositor.h" +#include +#include +#endif + +#ifdef ENABLE_SVT_HEVC_ENCODER +#include +#include +#endif + +#include + +namespace mcu { + +class CompositeIn : public owt_base::FrameDestination +{ +public: + CompositeIn(int index, const std::string& avatar, boost::shared_ptr compositor) : m_index(index), m_compositor(compositor) { + m_compositor->activateInput(m_index); + m_compositor->setAvatar(m_index, avatar); + } + + virtual ~CompositeIn() { + m_compositor->unsetAvatar(m_index); + m_compositor->deActivateInput(m_index); + } + + void onFrame(const owt_base::Frame& frame) { + m_compositor->pushInput(m_index, frame); + } + +private: + int m_index; + boost::shared_ptr m_compositor; +}; + +class VideoFrameMixerImpl : public VideoFrameMixer { +public: + VideoFrameMixerImpl(uint32_t maxInput, owt_base::VideoSize rootSize, owt_base::YUVColor bgColor, bool useSimulcast, bool crop); + virtual ~VideoFrameMixerImpl(); + + bool addInput(int input, owt_base::FrameFormat, owt_base::FrameSource*, const std::string& avatar); + void removeInput(int input); + void setInputActive(int input, bool active); + + bool addOutput(int output, + owt_base::FrameFormat, + const owt_base::VideoCodecProfile profile, + const owt_base::VideoSize&, + const unsigned int framerateFPS, + const unsigned int bitrateKbps, + const unsigned int keyFrameIntervalSeconds, + owt_base::FrameDestination*); + void removeOutput(int output); + void setBitrate(unsigned short kbps, int output); + void requestKeyFrame(int output); + + void updateLayoutSolution(LayoutSolution& solution); + + void drawText(const std::string& textSpec); + void clearText(); + +private: + struct Input { + owt_base::FrameSource* source; + boost::shared_ptr decoder; + boost::shared_ptr compositorIn; + }; + + struct Output { + boost::shared_ptr encoder; + int streamId; + }; + + std::map m_inputs; + boost::shared_mutex m_inputMutex; + + boost::shared_ptr m_compositor; + + std::map m_outputs; + boost::shared_mutex m_outputMutex; + + bool m_useSimulcast; + owt_base::VideoSize m_rootSize; +}; + +VideoFrameMixerImpl::VideoFrameMixerImpl(uint32_t maxInput, owt_base::VideoSize rootSize, owt_base::YUVColor bgColor, bool useSimulcast, bool crop) + : m_useSimulcast(useSimulcast) + , m_rootSize(rootSize) +{ +#ifdef ENABLE_MSDK + if (!m_compositor) + m_compositor.reset(new MsdkVideoCompositor(maxInput, rootSize, bgColor, crop)); +#endif + + if (!m_compositor) + m_compositor.reset(new SoftVideoCompositor(maxInput, rootSize, bgColor, crop)); +} + +VideoFrameMixerImpl::~VideoFrameMixerImpl() +{ + { + boost::unique_lock lock(m_outputMutex); + for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { + m_compositor->removeOutput(it->second.encoder.get()); + it->second.encoder->degenerateStream(it->second.streamId); + } + m_outputs.clear(); + } + + { + boost::unique_lock lock(m_inputMutex); + for (auto it = m_inputs.begin(); it != m_inputs.end(); ++it) { + it->second.source->removeVideoDestination(it->second.decoder.get()); + it->second.decoder->removeVideoDestination(it->second.compositorIn.get()); + m_inputs.erase(it); + } + m_inputs.clear(); + } + + m_compositor.reset(); +} + +inline bool VideoFrameMixerImpl::addInput(int input, owt_base::FrameFormat format, owt_base::FrameSource* source, const std::string& avatar) +{ + assert(source); + + boost::upgrade_lock lock(m_inputMutex); + auto it = m_inputs.find(input); + if (it != m_inputs.end()) + return false; + + boost::shared_ptr decoder; + +#ifdef ENABLE_MSDK + if (!decoder && owt_base::MsdkFrameDecoder::supportFormat(format)) + decoder.reset(new owt_base::MsdkFrameDecoder()); +#endif + + if (!decoder && (format == owt_base::FRAME_FORMAT_H265 || format == owt_base::FRAME_FORMAT_H264)) + decoder.reset(new owt_base::FFmpegFrameDecoder()); + + if (!decoder && owt_base::VCMFrameDecoder::supportFormat(format)) + decoder.reset(new owt_base::VCMFrameDecoder(format)); + + if (!decoder && owt_base::FFmpegFrameDecoder::supportFormat(format)) + decoder.reset(new owt_base::FFmpegFrameDecoder()); + + if (!decoder) + return false; + + if (decoder->init(format)) { + boost::shared_ptr compositorIn(new CompositeIn(input, avatar, m_compositor)); + + source->addVideoDestination(decoder.get()); + decoder->addVideoDestination(compositorIn.get()); + + boost::upgrade_to_unique_lock uniqueLock(lock); + Input in{.source = source, .decoder = decoder, .compositorIn = compositorIn}; + m_inputs[input] = in; + return true; + } + + return false; +} + +inline void VideoFrameMixerImpl::removeInput(int input) +{ + boost::upgrade_lock lock(m_inputMutex); + auto it = m_inputs.find(input); + if (it != m_inputs.end()) { + it->second.source->removeVideoDestination(it->second.decoder.get()); + it->second.decoder->removeVideoDestination(it->second.compositorIn.get()); + it->second.compositorIn.reset(); + boost::upgrade_to_unique_lock uniqueLock(lock); + m_inputs.erase(it); + } +} + +inline void VideoFrameMixerImpl::setInputActive(int input, bool active) +{ + auto it = m_inputs.find(input); + // FIXEME: Should show a black frame when input is not active + if (it != m_inputs.end()) { + if (active) { + m_compositor->activateInput(input); + } else { + m_compositor->deActivateInput(input); + } + } +} + +inline void VideoFrameMixerImpl::updateLayoutSolution(LayoutSolution& solution) +{ + m_compositor->updateLayoutSolution(solution); +} + +inline void VideoFrameMixerImpl::setBitrate(unsigned short kbps, int output) +{ + boost::shared_lock lock(m_outputMutex); + auto it = m_outputs.find(output); + if (it != m_outputs.end()) + it->second.encoder->setBitrate(kbps, it->second.streamId); +} + +inline void VideoFrameMixerImpl::requestKeyFrame(int output) +{ + boost::shared_lock lock(m_outputMutex); + auto it = m_outputs.find(output); + if (it != m_outputs.end()) + it->second.encoder->requestKeyFrame(it->second.streamId); +} + +inline bool VideoFrameMixerImpl::addOutput(int output, + owt_base::FrameFormat format, + const owt_base::VideoCodecProfile profile, + const owt_base::VideoSize& outputSize, + const unsigned int framerateFPS, + const unsigned int bitrateKbps, + const unsigned int keyFrameIntervalSeconds, + owt_base::FrameDestination* dest) +{ + boost::shared_ptr encoder; + boost::upgrade_lock lock(m_outputMutex); + + // find a reusable encoder. + auto it = m_outputs.begin(); + for (; it != m_outputs.end(); ++it) { + if (it->second.encoder->canSimulcast(format, outputSize.width, outputSize.height)) + break; + } + + int32_t streamId = -1; + if (it != m_outputs.end()) { // Found a reusable encoder + encoder = it->second.encoder; + streamId = encoder->generateStream(outputSize.width, outputSize.height, framerateFPS, bitrateKbps, keyFrameIntervalSeconds, dest); + if (streamId < 0) + return false; + } else { // Never found a reusable encoder. +#ifdef ENABLE_MSDK + if (!encoder && owt_base::MsdkFrameEncoder::supportFormat(format)) + encoder.reset(new owt_base::MsdkFrameEncoder(format, profile, m_useSimulcast)); +#endif + +#ifdef ENABLE_SVT_HEVC_ENCODER + if (!encoder && format == owt_base::FRAME_FORMAT_H265) { + if (isHEVCMCTSVideoResolution(m_rootSize.width, m_rootSize.height) && + isHEVCMCTSVideoResolution(outputSize.width, outputSize.height)) + encoder.reset(new owt_base::SVTHEVCMCTSEncoder(format, profile, m_useSimulcast)); + else + encoder.reset(new owt_base::SVTHEVCEncoder(format, profile, m_useSimulcast)); + } +#endif + + if (!encoder && owt_base::VCMFrameEncoder::supportFormat(format)) + encoder.reset(new owt_base::VCMFrameEncoder(format, profile, m_useSimulcast)); + + if (!encoder) + return false; + + streamId = encoder->generateStream(outputSize.width, outputSize.height, framerateFPS, bitrateKbps, keyFrameIntervalSeconds, dest); + if (streamId < 0) + return false; + + if (!m_compositor->addOutput(outputSize.width, outputSize.height, framerateFPS, encoder.get())) + return false; + } + + boost::upgrade_to_unique_lock uniqueLock(lock); + Output out{.encoder = encoder, .streamId = streamId}; + m_outputs[output] = out; + return true; +} + +inline void VideoFrameMixerImpl::removeOutput(int32_t output) +{ + boost::upgrade_lock lock(m_outputMutex); + auto it = m_outputs.find(output); + if (it != m_outputs.end()) { + it->second.encoder->degenerateStream(it->second.streamId); + if (it->second.encoder->isIdle()) { + m_compositor->removeOutput(it->second.encoder.get()); + } + boost::upgrade_to_unique_lock ulock(lock); + m_outputs.erase(output); + } +} + +inline void VideoFrameMixerImpl::drawText(const std::string& textSpec) +{ + m_compositor->drawText(textSpec); +} + +inline void VideoFrameMixerImpl::clearText() +{ + m_compositor->clearText(); +} + +} +#endif diff --git a/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoMixer.cpp b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoMixer.cpp new file mode 100644 index 00000000..6ea2a3ac --- /dev/null +++ b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/IMVideoMixer.cpp @@ -0,0 +1,237 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "VideoMixer.h" +#include "IMVideoFrameMixerImpl.h" +#include "VideoFrameMixer.h" + +using namespace webrtc; +using namespace owt_base; + +namespace mcu { + +DEFINE_LOGGER(VideoMixer, "mcu.media.VideoMixer"); + +VideoMixer::VideoMixer(const VideoMixerConfig& config) + : m_nextOutputIndex(0) + , m_maxInputCount(16) +{ + if (ELOG_IS_TRACE_ENABLED()) { + rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE); + rtc::LogMessage::LogTimestamps(true); + + webrtc::Trace::CreateTrace(); + webrtc::Trace::SetTraceFile(NULL, false); + webrtc::Trace::set_level_filter(webrtc::kTraceAll); + } else if (ELOG_IS_DEBUG_ENABLED()) { + rtc::LogMessage::LogToDebug(rtc::LS_INFO); + rtc::LogMessage::LogTimestamps(true); + + const int kTraceFilter = webrtc::kTraceNone | webrtc::kTraceTerseInfo | + webrtc::kTraceWarning | webrtc::kTraceError | + webrtc::kTraceCritical | webrtc::kTraceDebug | + webrtc::kTraceInfo; + + webrtc::Trace::CreateTrace(); + webrtc::Trace::SetTraceFile(NULL, false); + webrtc::Trace::set_level_filter(kTraceFilter); + } + + m_maxInputCount = config.maxInput; + + VideoSize rootSize; + if (!VideoResolutionHelper::getVideoSize(config.resolution, rootSize)) { + ELOG_WARN("configured resolution is invalid!"); + rootSize = DEFAULT_VIDEO_SIZE; + } + + YUVColor bgColor; + if (!VideoColorHelper::getVideoColor(config.bgColor.r, config.bgColor.g, config.bgColor.b, bgColor)) { + ELOG_WARN("configured background color is invalid!"); + bgColor = DEFAULT_VIDEO_BG_COLOR; + } + +#ifdef ENABLE_MSDK + MsdkBase *msdkBase = MsdkBase::get(); + if(msdkBase != NULL) { + msdkBase->setConfigHevcEncoderGaccPlugin(config.useGacc); + msdkBase->setConfigMFETimeout(config.MFE_timeout); + } +#endif + + ELOG_INFO("Init maxInput(%u), rootSize(%u, %u), bgColor(%u, %u, %u)", m_maxInputCount, rootSize.width, rootSize.height, bgColor.y, bgColor.cb, bgColor.cr); + + m_frameMixer.reset(new VideoFrameMixerImpl(m_maxInputCount, rootSize, bgColor, true, config.crop)); +} + +VideoMixer::~VideoMixer() +{ + closeAll(); +} + +bool VideoMixer::addInput(const int inputIndex, const std::string& codec, owt_base::FrameSource* source, const std::string& avatar) +{ + if (m_inputs.find(inputIndex) != m_inputs.end()) { + ELOG_WARN("addInput already exist:%d", inputIndex); + return false; + } + if (m_inputs.size() >= m_maxInputCount) { + ELOG_WARN("addInput reach max input"); + return false; + } + + owt_base::FrameFormat format = getFormat(codec); + + if (m_frameMixer->addInput(inputIndex, format, source, avatar)) { + m_inputs.insert(inputIndex); + return true; + } + ELOG_WARN("addInput fail for inputIndex:%d", inputIndex); + return false; +} + +void VideoMixer::removeInput(const int inputIndex) +{ + if (m_inputs.find(inputIndex) == m_inputs.end()) { + ELOG_WARN("removeInput no such input:%d", inputIndex); + return; + } + + m_inputs.erase(inputIndex); + ELOG_DEBUG("removeInput - recycle input(%d)", inputIndex); + m_frameMixer->removeInput(inputIndex); +} + +void VideoMixer::setInputActive(const int inputIndex, bool active) +{ + if (m_inputs.find(inputIndex) != m_inputs.end()) { + m_frameMixer->setInputActive(inputIndex, active); + } else { + ELOG_WARN("setInputActive no such input:%d", inputIndex); + } +} + +bool VideoMixer::addOutput( + const std::string& outStreamID + , const std::string& codec + , const owt_base::VideoCodecProfile profile + , const std::string& resolution + , const unsigned int framerateFPS + , const unsigned int bitrateKbps + , const unsigned int keyFrameIntervalSeconds + , owt_base::FrameDestination* dest) +{ + owt_base::FrameFormat format = getFormat(codec); + VideoSize vSize; + VideoResolutionHelper::getVideoSize(resolution, vSize); + + if (m_frameMixer->addOutput(m_nextOutputIndex, format, profile, vSize, framerateFPS, bitrateKbps, keyFrameIntervalSeconds, dest)) { + boost::unique_lock lock(m_outputsMutex); + m_outputs[outStreamID] = m_nextOutputIndex++; + return true; + } + return false; +} + +void VideoMixer::removeOutput(const std::string& outStreamID) +{ + int32_t index = -1; + boost::unique_lock lock(m_outputsMutex); + auto it = m_outputs.find(outStreamID); + if (it != m_outputs.end()) { + index = it->second; + m_outputs.erase(it); + } + lock.unlock(); + + if (index != -1) { + m_frameMixer->removeOutput(index); + } +} + +void VideoMixer::forceKeyFrame(const std::string& outStreamID) +{ + int32_t index = -1; + boost::shared_lock lock(m_outputsMutex); + auto it = m_outputs.find(outStreamID); + if (it != m_outputs.end()) { + index = it->second; + } + lock.unlock(); + + if (index != -1) { + m_frameMixer->requestKeyFrame(index); + } +} + +void VideoMixer::updateLayoutSolution(LayoutSolution& solution) { + ELOG_DEBUG("updateLayoutSolution, size(%ld)", solution.size()); + + for (auto& l : solution) { + Region *pRegion = &l.region; + + ELOG_DEBUG("input(%d): shape(%s), left(%d/%d), top(%d/%d), width(%d/%d), height(%d/%d)" + , l.input + , pRegion->shape.c_str() + , pRegion->area.rect.left.numerator, pRegion->area.rect.left.denominator + , pRegion->area.rect.top.numerator, pRegion->area.rect.top.denominator + , pRegion->area.rect.width.numerator, pRegion->area.rect.width.denominator + , pRegion->area.rect.height.numerator, pRegion->area.rect.height.denominator); + + assert(pRegion->shape.compare("rectangle") == 0); + assert(pRegion->area.rect.left.denominator != 0 && pRegion->area.rect.left.denominator >= pRegion->area.rect.left.numerator); + assert(pRegion->area.rect.top.denominator != 0 && pRegion->area.rect.top.denominator >= pRegion->area.rect.top.numerator); + assert(pRegion->area.rect.width.denominator != 0 && pRegion->area.rect.width.denominator >= pRegion->area.rect.width.numerator); + assert(pRegion->area.rect.height.denominator != 0 && pRegion->area.rect.height.denominator >= pRegion->area.rect.height.numerator); + + ELOG_TRACE("input(%d): left(%.2f), top(%.2f), width(%.2f), height(%.2f)" + , l.input + , (float)pRegion->area.rect.left.numerator / pRegion->area.rect.left.denominator + , (float)pRegion->area.rect.top.numerator / pRegion->area.rect.top.denominator + , (float)pRegion->area.rect.width.numerator / pRegion->area.rect.width.denominator + , (float)pRegion->area.rect.height.numerator / pRegion->area.rect.height.denominator); + } + + m_frameMixer->updateLayoutSolution(solution); +} + +void VideoMixer::drawText(const std::string& textSpec) +{ + m_frameMixer->drawText(textSpec); +} + +void VideoMixer::clearText() +{ + m_frameMixer->clearText(); +} + +void VideoMixer::closeAll() +{ + ELOG_DEBUG("closeAll"); + + auto it = m_inputs.begin(); + while (it != m_inputs.end()) { + m_frameMixer->removeInput(*it); + } + m_inputs.clear(); + + { + boost::upgrade_lock outputLock(m_outputsMutex); + auto it = m_outputs.begin(); + while (it != m_outputs.end()) { + int32_t index = it->second; + m_frameMixer->removeOutput(index); + } + boost::upgrade_to_unique_lock outputLock1(outputLock); + m_outputs.clear(); + } + + ELOG_DEBUG("Closed all media in this Mixer"); +} + + +}/* namespace mcu */ diff --git a/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/binding.gyp b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/binding.gyp new file mode 100644 index 00000000..c27414e5 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/im_video_mixer_sw/src/binding.gyp @@ -0,0 +1,63 @@ +{ + 'variables': { + 'owt_root': '../../../third_party/owt-server', + 'owt_root_abs': '=0: IDR + hierarchicalLevels: 3 # The hierarchical level to construct GOP + predStructure: 0 # [0-2], 0 is IPPP..., 1 is IBBB...(B is low-delay B), and 2 is IBBB...(B is normal bi-directional B) + + # Input Info + sourceWidth: 3840 + sourceHeight: 2048 + frameRate: 30 + encoderBitDepth: 8 + encoderColorFormat: 1 # 1: 420, 2: 422, 3: 444 + + # Rate Control + rateControlMode: 1 # 0: CQP, 1: VBR + sceneChangeDetection: 0 + lookAheadDistance: 0 # The number of frames that used for look ahead + targetBitRate: 20000000 + maxQpAllowed: 26 # [0 - 51] + minQpAllowed: 20 # [0 - 50] + + # Bitstream options + profile: 2 # 1: Main, 2: Main 10 + tier: 0 # 0: Main, 1: High + level: 0 # 0 to 6.2 [0 for auto determine Level] + + # Deblock filter + disableDlfFlag: 0 + + # SAO + enableSaoFlag: 1 + + targetSocket: 1 # -1: Both Sockets, 0: Socket 0, 1: Socket 1. + tileColumnCount: 10 # Tile count in the Row + tileRowCount: 8 # Tile count in the column + +low: + # Encoding preset + encMode: 9 # The preset for quality and performance balance, [0-12], 0 is best quality, 12 is best performance + tune: 1 # Specific encoder tuning, 0 is visually optimized mode, 2 is VMAF optimized mode + asmType: 1 # 0: C only, 1: Auto select highest assembly instruction set supported) + + # GOP Structure + intraPeriodLength: 4 # The distance between two adjacent intra frame + intraRefreshType: 0 # -1: CRA (Open GOP) >=0: IDR + hierarchicalLevels: 3 # The hierarchical level to construct GOP + predStructure: 0 # [0-2], 0 is IPPP..., 1 is IBBB...(B is low-delay B), and 2 is IBBB...(B is normal bi-directional B) + + # Input Info + sourceWidth: 1280 + sourceHeight: 768 + frameRate: 30 + encoderBitDepth: 8 + encoderColorFormat: 1 # 1: 420, 2: 422, 3: 444 + + # Rate Control + rateControlMode: 1 # 0: CQP, 1: VBR + sceneChangeDetection: 0 + lookAheadDistance: 0 # The number of frames that used for look ahead + targetBitRate: 1736000 + maxQpAllowed: 26 # [0 - 51] + minQpAllowed: 20 # [0 - 50] + + # Bitstream options + profile: 2 # 1: Main, 2: Main 10 + tier: 0 # 0: Main, 1: High + level: 0 # 0 to 6.2 [0 for auto determine Level] + + # Deblock filter + disableDlfFlag: 0 + + # SAO + enableSaoFlag: 1 + + targetSocket: 1 # -1: Both Sockets, 0: Socket 0, 1: Socket 1. + tileColumnCount: 5 # Tile count in the Row + tileRowCount: 3 # Tile count in the column + diff --git a/WebRTC-Sample/owt-server/source/libwebrtc360/src/CMakeLists.txt b/WebRTC-Sample/owt-server/source/libwebrtc360/src/CMakeLists.txt new file mode 100644 index 00000000..cab6e876 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/libwebrtc360/src/CMakeLists.txt @@ -0,0 +1,17 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.12) +PROJECT(live360-WebRTC-library) + +FIND_PACKAGE(Boost REQUIRED COMPONENTS system thread program_options) + +# FILE(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +AUX_SOURCE_DIRECTORY(. DIR_SRC) + +SET(DIR_SRC + ${DIR_SRC} +) + +ADD_LIBRARY(live360WebRTC SHARED ${DIR_SRC}) + +TARGET_COMPILE_DEFINITIONS(live360WebRTC PUBLIC _ENABLE_HEVC_TILES_MERGER_ ENABLE_WEBRTC360) + +TARGET_LINK_LIBRARIES(live360WebRTC Boost::system Boost::thread Boost::program_options 360SCVP) diff --git a/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360HEVCTilesMerger.cpp b/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360HEVCTilesMerger.cpp new file mode 100644 index 00000000..fb01afbf --- /dev/null +++ b/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360HEVCTilesMerger.cpp @@ -0,0 +1,468 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifdef _ENABLE_HEVC_TILES_MERGER_ + +#include "WebRTC360HEVCTilesMerger.h" + +#define LTTNG_TRACE_INFO ELOG_DEBUG + +#ifdef ENABLE_WEBRTC360 +#define ELOG_DEBUG printf +#define ELOG_ERROR printf +#define ELOG_INFO printf +#define ELOG_TRACE printf +#endif + +#ifndef ENABLE_WEBRTC360 +namespace owt_base { + +DEFINE_LOGGER(HEVCTilesMerger, "owt.HEVCTilesMerger"); +#endif + +static int filterNALs(uint8_t *data, int size, const std::vector &remove_types) +{ + int remove_nals_size = 0; + + int nalu_found_length = 0; + uint8_t* buffer_start = data; + int buffer_length = size; + int nalu_start_offset = 0; + int nalu_end_offset = 0; + int sc_len = 0; + int nalu_type; + + while (buffer_length > 0) { + nalu_found_length = findNALU(buffer_start, buffer_length, &nalu_start_offset, &nalu_end_offset, &sc_len); + if (nalu_found_length < 0) { + /* Error, should never happen */ + break; + } + + nalu_type = (buffer_start[nalu_start_offset] & 0x7e) >> 1; + if (find(remove_types.begin(), remove_types.end(), nalu_type) != remove_types.end()) { + //next + memmove(buffer_start, buffer_start + nalu_start_offset + nalu_found_length, buffer_length - nalu_start_offset - nalu_found_length); + buffer_length -= nalu_start_offset + nalu_found_length; + + remove_nals_size += nalu_start_offset + nalu_found_length; + continue; + } + + buffer_start += (nalu_start_offset + nalu_found_length); + buffer_length -= (nalu_start_offset + nalu_found_length); + } + + return size - remove_nals_size; +} + +HEVCTilesMerger::HEVCTilesMerger() + : m_init(false) + , m_handle(NULL) + , m_inputHiBuffer(NULL) + , m_inputHiBufferLength(0) + , m_inputLowBuffer(NULL) + , m_inputLowBufferLength(0) + , m_outputBuffer(NULL) + , m_outputBufferLength(0) + , m_outputSEIBuffer(NULL) + , m_outputBufferSEILength(0) + , m_frameBuffer(NULL) + , m_frameBufferLength(0) + , m_viewport_w(960) + , m_viewport_h(960) + , m_viewPortFOV(80) + , m_yaw(0) + , m_pitch(0) + , m_fovUpdated(false) + , m_enableBsDump(false) + , m_bsDumpfp_hi_res(NULL) + , m_bsDumpfp_low_res(NULL) + , m_bsDumpfp(NULL) +{ +#if 0 + google::InitGoogleLogging(""); + + if (ELOG_IS_DEBUG_ENABLED() || + ELOG_IS_TRACE_ENABLED()) + google::SetStderrLogging(google::INFO); + else + google::SetStderrLogging(google::ERROR); +#endif +} + +HEVCTilesMerger::~HEVCTilesMerger() +{ + if (m_handle) + I360SCVP_unInit(m_handle); + + if (m_inputHiBuffer) + delete [] m_inputHiBuffer; + + if (m_inputLowBuffer) + delete [] m_inputLowBuffer; + + if (m_outputBuffer) + delete [] m_outputBuffer; + + if (m_outputSEIBuffer) + delete [] m_outputSEIBuffer; + + if (m_frameBuffer) + delete [] m_frameBuffer; +} + +bool HEVCTilesMerger::init(const FrameHevcTiles& tilesFrame) +{ + memset(&m_360scvp_param, 0, sizeof(m_360scvp_param)); + + m_360scvp_param.usedType = E_MERGE_AND_VIEWPORT; + m_360scvp_param.frameWidth = tilesFrame.hi_width; + m_360scvp_param.frameHeight = tilesFrame.hi_height; + m_360scvp_param.frameWidthLow = tilesFrame.low_width; + m_360scvp_param.frameHeightLow = tilesFrame.low_height; + + m_360scvp_param.paramViewPort.geoTypeInput = EGeometryType(E_SVIDEO_EQUIRECT); + m_360scvp_param.paramViewPort.geoTypeOutput = E_SVIDEO_VIEWPORT; + m_360scvp_param.paramViewPort.faceWidth = m_360scvp_param.frameWidth; + m_360scvp_param.paramViewPort.faceHeight = m_360scvp_param.frameHeight; + m_360scvp_param.paramViewPort.viewportWidth = m_viewport_w; + m_360scvp_param.paramViewPort.viewportHeight = m_viewport_h; + m_360scvp_param.paramViewPort.viewPortFOVH = m_viewPortFOV; + m_360scvp_param.paramViewPort.viewPortFOVV = m_viewPortFOV; + + { + boost::unique_lock ulock(m_mutex); + + m_360scvp_param.paramViewPort.viewPortYaw = m_yaw; + m_360scvp_param.paramViewPort.viewPortPitch = m_pitch; + m_fovUpdated = false; + } + + ELOG_INFO("Init hi-res %dx%d, low-res %dx%d, viewport-res %dx%d, viewport-fov %.2f(h-degree)-%.2f(v-degree), FOV(yaw %.2f, pitch %.2f)" + , m_360scvp_param.frameWidth, m_360scvp_param.frameHeight + , m_360scvp_param.frameWidthLow, m_360scvp_param.frameHeightLow + , m_360scvp_param.paramViewPort.viewportWidth + , m_360scvp_param.paramViewPort.viewportHeight + , m_360scvp_param.paramViewPort.viewPortFOVH + , m_360scvp_param.paramViewPort.viewPortFOVV + , m_360scvp_param.paramViewPort.viewPortYaw + , m_360scvp_param.paramViewPort.viewPortPitch + ); + + // hi res input + m_inputHiBufferLength = tilesFrame.hi_width * tilesFrame.hi_height * 5; + m_inputHiBuffer = new uint8_t[m_inputHiBufferLength]; + m_360scvp_param.pInputBitstream = m_inputHiBuffer; + m_360scvp_param.inputBitstreamLen = 0; + + memcpy(m_360scvp_param.pInputBitstream, tilesFrame.hi_payload, tilesFrame.hi_length); + m_360scvp_param.inputBitstreamLen = tilesFrame.hi_length; + + // low res input + m_inputLowBufferLength = tilesFrame.low_width * tilesFrame.low_height * 5; + m_inputLowBuffer = new uint8_t[m_inputLowBufferLength]; + m_360scvp_param.pInputLowBitstream = m_inputLowBuffer; + m_360scvp_param.inputLowBistreamLen = m_inputLowBufferLength; + + memcpy(m_360scvp_param.pInputLowBitstream, tilesFrame.low_payload, tilesFrame.low_length); + m_360scvp_param.inputLowBistreamLen = tilesFrame.low_length; + + // output and sei + m_outputBufferLength = tilesFrame.hi_width * tilesFrame.hi_height * 5; + m_outputBuffer = new uint8_t[m_outputBufferLength]; + m_360scvp_param.pOutputBitstream = m_outputBuffer; + m_360scvp_param.outputBitstreamLen = m_outputBufferLength; + + m_outputBufferSEILength = tilesFrame.hi_width * tilesFrame.hi_height * 5; + m_outputSEIBuffer = new uint8_t[m_outputBufferSEILength]; + m_360scvp_param.pOutputSEI = m_outputSEIBuffer; + m_360scvp_param.outputSEILen = m_outputBufferSEILength; + + m_handle = I360SCVP_Init(&m_360scvp_param); + if (m_handle == NULL) { + ELOG_ERROR("Generate the vide port handle fail!"); + return false; + } + + m_frameBufferLength = m_outputBufferLength + m_outputBufferSEILength; + m_frameBuffer = new uint8_t[m_frameBufferLength]; + + ELOG_INFO("Init OK!"); + return true; +} + +HEVCTilesMerger::FrameHevcTiles::FrameHevcTiles (const Frame& frame) +{ + uint32_t width; + uint32_t height; + uint32_t length; + + int offset = 0; + + // hi-res + width = frame.payload[offset + 0] + | frame.payload[offset + 1] << 8 + | frame.payload[offset + 2] << 16 + | frame.payload[offset + 3] << 24; + offset += 4; + + height = frame.payload[offset + 0] + | frame.payload[offset + 1] << 8 + | frame.payload[offset + 2] << 16 + | frame.payload[offset + 3] << 24; + offset += 4; + + length = frame.payload[offset + 0] + | frame.payload[offset + 1] << 8 + | frame.payload[offset + 2] << 16 + | frame.payload[offset + 3] << 24; + offset += 4; + + this->hi_width = width; + this->hi_height = height; + this->hi_length = length; + this->hi_payload = frame.payload + offset; + + offset += length; + + // low-res + width = frame.payload[offset + 0] + | frame.payload[offset + 1] << 8 + | frame.payload[offset + 2] << 16 + | frame.payload[offset + 3] << 24; + offset += 4; + + height = frame.payload[offset + 0] + | frame.payload[offset + 1] << 8 + | frame.payload[offset + 2] << 16 + | frame.payload[offset + 3] << 24; + offset += 4; + + length = frame.payload[offset + 0] + | frame.payload[offset + 1] << 8 + | frame.payload[offset + 2] << 16 + | frame.payload[offset + 3] << 24; + offset += 4; + + this->low_width = width; + this->low_height = height; + this->low_length = length; + this->low_payload = frame.payload + offset; + + this->isKey = frame.additionalInfo.video.isKeyFrame; + this->timeStamp = frame.timeStamp; + + ELOG_TRACE("hi-res %dx%d, %d, low-res %dx%d, %d, %s", + this->hi_width, this->hi_height, this->hi_length, + this->low_width, this->low_height, this->low_length, + this->isKey ? "key" : "delta"); +} + +void HEVCTilesMerger::onFrame(const Frame& frame, Frame *pOutFrame) +{ +#if 0 + ELOG_INFO("onFrame(%s), %s, %dx%d, length(%d)", + getFormatStr(frame.format), + frame.additionalInfo.video.isKeyFrame ? "key" : "delta", + frame.additionalInfo.video.width, + frame.additionalInfo.video.height, + frame.length + ); +#endif + + switch (frame.format) { + case FRAME_FORMAT_H265: + break; + default: + ELOG_ERROR("Unspported video frame format %d(%s)", frame.format, getFormatStr(frame.format)); + return; + } + + FrameHevcTiles tilesFrame(frame); + + if (!m_init) { + if (!frame.additionalInfo.video.isKeyFrame) { + ELOG_INFO("Need key frames"); + return; + } + + initDump(); + init(tilesFrame); + m_init = true; + } + + dump(tilesFrame.hi_payload, tilesFrame.hi_length, tilesFrame.isKey, m_bsDumpfp_hi_res); + dump(tilesFrame.low_payload, tilesFrame.low_length, tilesFrame.isKey, m_bsDumpfp_low_res); + + if (m_handle) { + memcpy(m_360scvp_param.pInputBitstream, tilesFrame.hi_payload, tilesFrame.hi_length); + m_360scvp_param.inputBitstreamLen = tilesFrame.hi_length; + + memcpy(m_360scvp_param.pInputLowBitstream, tilesFrame.low_payload, tilesFrame.low_length); + m_360scvp_param.inputLowBistreamLen = tilesFrame.low_length; + + m_360scvp_param.outputBitstreamLen = 0; + m_360scvp_param.outputSEILen = 0; + m_360scvp_param.timeStamp = tilesFrame.timeStamp; + + // change viewport + if (tilesFrame.isKey) { + bool needSetFoV = false; + + { + boost::unique_lock ulock(m_mutex); + if (m_fovUpdated) { + ELOG_DEBUG("Apply new FoV yaw %.2f -> %d, pitch %.2f -> %d" + , m_360scvp_param.paramViewPort.viewPortYaw + , m_yaw + , m_360scvp_param.paramViewPort.viewPortPitch + , m_pitch + ); + LTTNG_TRACE_INFO("HEVCTilesMerger: Apply new FoV, stream(%ld), yaw(%d), pitch(%d)", (long int)this, m_yaw+180, m_pitch); + m_360scvp_param.paramViewPort.viewPortYaw = m_yaw; + m_360scvp_param.paramViewPort.viewPortPitch = m_pitch; + + m_fovUpdated = false; + + needSetFoV = true; + } + } + + if (needSetFoV) { + I360SCVP_setViewPort(m_handle, m_360scvp_param.paramViewPort.viewPortYaw, m_360scvp_param.paramViewPort.viewPortPitch); + } + } + + I360SCVP_process(&m_360scvp_param, m_handle); + if (m_360scvp_param.outputBitstreamLen > 0) { + if (!tilesFrame.isKey) { + std::vector ps; + ps.push_back(32); + ps.push_back(33); + ps.push_back(34); + + uint32_t filted_size = filterNALs(m_360scvp_param.pOutputBitstream, m_360scvp_param.outputBitstreamLen, ps); + if (m_360scvp_param.outputBitstreamLen != filted_size) { + ELOG_DEBUG("remove vps/sps/pps size: %d", m_360scvp_param.outputBitstreamLen - filted_size); + + m_360scvp_param.outputBitstreamLen = filted_size; + } + } + + memcpy(m_frameBuffer, m_360scvp_param.pOutputBitstream, m_360scvp_param.outputBitstreamLen); + + //m_360scvp_param.outputSEILen = 0; + if (m_360scvp_param.outputSEILen > 0) { + memcpy(m_frameBuffer + m_360scvp_param.outputBitstreamLen, m_360scvp_param.pOutputSEI, m_360scvp_param.outputSEILen); + } else { + //ELOG_INFO("invalid SEI output bitstream len: %d\n", m_360scvp_param.outputSEILen); + } + + Frame outFrame; + memset(&outFrame, 0, sizeof(outFrame)); + + outFrame.format = FRAME_FORMAT_H265; + outFrame.payload = m_frameBuffer; + outFrame.length = m_360scvp_param.outputBitstreamLen + m_360scvp_param.outputSEILen; + outFrame.additionalInfo.video.width = m_viewport_w; + outFrame.additionalInfo.video.height = m_viewport_h; + outFrame.additionalInfo.video.isKeyFrame = tilesFrame.isKey; + outFrame.timeStamp = tilesFrame.timeStamp; + + ELOG_TRACE("deliverFrame, %s, %dx%d(%s), length(%d)", + getFormatStr(outFrame.format), + outFrame.additionalInfo.video.width, + outFrame.additionalInfo.video.height, + outFrame.additionalInfo.video.isKeyFrame ? "key" : "delta", + outFrame.length); + + dump(outFrame.payload, outFrame.length, outFrame.additionalInfo.video.isKeyFrame, m_bsDumpfp); + + *pOutFrame = outFrame; + } else { + ELOG_ERROR("SCVP_process error"); + } + } else { + Frame outFrame; + memset(&outFrame, 0, sizeof(outFrame)); + + outFrame.format = FRAME_FORMAT_H265; + outFrame.payload = tilesFrame.low_payload; + outFrame.length = tilesFrame.low_length; + outFrame.additionalInfo.video.width = tilesFrame.low_width; + outFrame.additionalInfo.video.height = tilesFrame.low_height; + outFrame.additionalInfo.video.isKeyFrame = tilesFrame.isKey; + outFrame.timeStamp = tilesFrame.timeStamp; + + ELOG_TRACE("Bypass TilesMerger deliverFrame, %s, %dx%d(%s), length(%d)", + getFormatStr(outFrame.format), + outFrame.additionalInfo.video.width, + outFrame.additionalInfo.video.height, + outFrame.additionalInfo.video.isKeyFrame ? "key" : "delta", + outFrame.length); + + dump(outFrame.payload, outFrame.length, outFrame.additionalInfo.video.isKeyFrame, m_bsDumpfp); + + *pOutFrame = outFrame; + } +} + +void HEVCTilesMerger::setFoV(int32_t yaw, int32_t pitch) { + boost::unique_lock ulock(m_mutex); + ELOG_DEBUG("setFoV, yaw %d(%d), pitch %d(%d)", yaw, (yaw - 180), pitch, (pitch - 90)); + + if (m_yaw != (yaw - 180) || m_pitch != (pitch - 90)) { + m_yaw = (yaw - 180); + m_pitch = (pitch - 90); + + m_fovUpdated = true; + } +} + +void HEVCTilesMerger::initDump() { + if (m_enableBsDump) { + char dumpFileName[128]; + + snprintf(dumpFileName, 128, "/tmp/stitcher-hi-res-%p.%s", this, "hevc"); + m_bsDumpfp_hi_res = fopen(dumpFileName, "wb"); + if (m_bsDumpfp_hi_res) { + ELOG_DEBUG("Enable bitstream dump, %s", dumpFileName); + } else { + ELOG_DEBUG("Can not open dump file, %s", dumpFileName); + } + + snprintf(dumpFileName, 128, "/tmp/stitcher-low-res-%p.%s", this, "hevc"); + m_bsDumpfp_low_res = fopen(dumpFileName, "wb"); + if (m_bsDumpfp_low_res) { + ELOG_DEBUG("Enable bitstream dump, %s", dumpFileName); + } else { + ELOG_DEBUG("Can not open dump file, %s", dumpFileName); + } + + snprintf(dumpFileName, 128, "/tmp/stitcher-output-%p.%s", this, "hevc"); + m_bsDumpfp = fopen(dumpFileName, "wb"); + if (m_bsDumpfp) { + ELOG_DEBUG("Enable bitstream dump, %s", dumpFileName); + } else { + ELOG_DEBUG("Can not open dump file, %s", dumpFileName); + } + } +} + +void HEVCTilesMerger::dump(uint8_t *buf, int len, bool isKey, FILE *fp) +{ + if (fp) { + fwrite(&len, 1, 4, fp); + fwrite(&isKey, 1, 1, fp); + fwrite(buf, 1, len, fp); + fflush(fp); + } +} + +#ifndef ENABLE_WEBRTC360 +}//namespace owt_base +#endif + +#endif diff --git a/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360HEVCTilesMerger.h b/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360HEVCTilesMerger.h new file mode 100644 index 00000000..c63fc7cf --- /dev/null +++ b/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360HEVCTilesMerger.h @@ -0,0 +1,101 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef HEVCTilesMerger_h +#define HEVCTilesMerger_h + +#include +#include +#include +#ifndef ENABLE_WEBRTC360 +#include +#endif + +#ifdef ENABLE_WEBRTC360 +#include "WebRTC360MediaFramePipeline.h" +#include "WebRTC360MediaUtilities.h" +#else +#include "MediaFramePipeline.h" +#include "MediaUtilities.h" +#endif +#include "360SCVPViewportAPI.h" +#include "360SCVPAPI.h" + +#ifdef ENABLE_WEBRTC360 +class HEVCTilesMerger { +#else +namespace owt_base { + +class HEVCTilesMerger : public FrameSource, public FrameDestination { + DECLARE_LOGGER(); +#endif + + struct FrameHevcTiles { + uint32_t hi_width; + uint32_t hi_height; + uint32_t hi_length; + uint8_t *hi_payload; + + uint32_t low_width; + uint32_t low_height; + uint32_t low_length; + uint8_t *low_payload; + + bool isKey; + uint32_t timeStamp; + + FrameHevcTiles () {} + FrameHevcTiles (const Frame& frame); + }; + +public: + HEVCTilesMerger(); + ~HEVCTilesMerger(); + + void onFrame(const Frame&) {} + void onFrame(const Frame&, Frame *outFrame); + + void setFoV(int32_t yaw, int32_t pitch); + +protected: + bool init(const FrameHevcTiles& tilesFrame); + void initDump(); + void dump(uint8_t *buf, int len, bool isKey, FILE *fp); + +private: + bool m_init; + void *m_handle; + param_360SCVP m_360scvp_param; + uint8_t *m_inputHiBuffer; + int m_inputHiBufferLength; + uint8_t *m_inputLowBuffer; + int m_inputLowBufferLength; + uint8_t *m_outputBuffer; + int m_outputBufferLength; + uint8_t *m_outputSEIBuffer; + int m_outputBufferSEILength; + uint8_t *m_frameBuffer; + int m_frameBufferLength; + + uint32_t m_viewport_w; + uint32_t m_viewport_h; + uint32_t m_viewPortFOV; + + int32_t m_yaw; + int32_t m_pitch; + bool m_fovUpdated; + boost::shared_mutex m_mutex; + + bool m_enableBsDump; + FILE *m_bsDumpfp_hi_res; + FILE *m_bsDumpfp_low_res; + FILE *m_bsDumpfp; +}; + +#ifndef ENABLE_WEBRTC360 +}//namespace owt_base +#endif + +#endif /* HEVCTilesMerger_h */ + diff --git a/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360MediaFramePipeline.h b/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360MediaFramePipeline.h new file mode 100644 index 00000000..17a87417 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360MediaFramePipeline.h @@ -0,0 +1,342 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef MediaFramePipeline_h +#define MediaFramePipeline_h + +#include +#include +#include +#include +#include + +enum FrameFormat { + FRAME_FORMAT_UNKNOWN = 0, + + FRAME_FORMAT_I420 = 100, + + FRAME_FORMAT_VP8 = 200, + FRAME_FORMAT_VP9, + FRAME_FORMAT_H264, + FRAME_FORMAT_H265, + + FRAME_FORMAT_MSDK = 300, + + FRAME_FORMAT_PCM_48000_2 = 800, + + FRAME_FORMAT_PCMU = 900, + FRAME_FORMAT_PCMA, + FRAME_FORMAT_OPUS, + FRAME_FORMAT_ISAC16, + FRAME_FORMAT_ISAC32, + FRAME_FORMAT_ILBC, + FRAME_FORMAT_G722_16000_1, + FRAME_FORMAT_G722_16000_2, + + FRAME_FORMAT_AAC, // ignore sample rate and channels for decoder, default is 48000_2 + FRAME_FORMAT_AAC_48000_2, // specify sample rate and channels for encoder + + FRAME_FORMAT_AC3, + FRAME_FORMAT_NELLYMOSER, + + FRAME_FORMAT_DATA, // Generic data frame. We don't know its detailed structure. +}; + +enum VideoCodecProfile { + PROFILE_UNKNOWN = 0, + + /* AVC Profiles */ + PROFILE_AVC_CONSTRAINED_BASELINE = 66 + (0x100 << 1), + PROFILE_AVC_BASELINE = 66, + PROFILE_AVC_MAIN = 77, + //PROFILE_AVC_EXTENDED = 88, + PROFILE_AVC_HIGH = 100, +}; + +struct VideoFrameSpecificInfo { + uint16_t width; + uint16_t height; + bool isKeyFrame; +}; + +struct AudioFrameSpecificInfo { + /*AudioFrameSpecificInfo() : isRtpPacket(false) {}*/ + uint8_t isRtpPacket; // FIXME: Temporarily use Frame to carry rtp-packets due to the premature AudioFrameConstructor implementation. + uint32_t nbSamples; + uint32_t sampleRate; + uint8_t channels; + uint8_t voice; + uint8_t audioLevel; +}; + +typedef union MediaSpecInfo { + VideoFrameSpecificInfo video; + AudioFrameSpecificInfo audio; +} MediaSpecInfo; + +typedef struct Frame { + FrameFormat format; + uint8_t* payload; + uint32_t length; + uint32_t timeStamp; + MediaSpecInfo additionalInfo; +} Frame; + +enum MetaDataType { + META_DATA_OWNER_ID = 0, +}; + +struct MetaData { + MetaDataType type; + uint8_t* payload; + uint32_t length; +}; + +inline FrameFormat getFormat(const std::string& codec) { + if (codec == "vp8") { + return FRAME_FORMAT_VP8; + } else if (codec == "h264") { + return FRAME_FORMAT_H264; + } else if (codec == "vp9") { + return FRAME_FORMAT_VP9; + } else if (codec == "h265") { + return FRAME_FORMAT_H265; + } else if (codec == "pcm_48000_2" || codec == "pcm_raw") { + return FRAME_FORMAT_PCM_48000_2; + } else if (codec == "pcmu") { + return FRAME_FORMAT_PCMU; + } else if (codec == "pcma") { + return FRAME_FORMAT_PCMA; + } else if (codec == "isac_16000") { + return FRAME_FORMAT_ISAC16; + } else if (codec == "isac_32000") { + return FRAME_FORMAT_ISAC32; + } else if (codec == "ilbc") { + return FRAME_FORMAT_ILBC; + } else if (codec == "g722_16000_1") { + return FRAME_FORMAT_G722_16000_1; + } else if (codec == "g722_16000_2") { + return FRAME_FORMAT_G722_16000_2; + } else if (codec == "opus_48000_2") { + return FRAME_FORMAT_OPUS; + } else if (codec.compare(0, 3, "aac") == 0) { + if (codec == "aac_48000_2") + return FRAME_FORMAT_AAC_48000_2; + else + return FRAME_FORMAT_AAC; + } else if (codec.compare(0, 3, "ac3") == 0) { + return FRAME_FORMAT_AC3; + } else if (codec.compare(0, 10, "nellymoser") == 0) { + return FRAME_FORMAT_NELLYMOSER; + } else { + return FRAME_FORMAT_UNKNOWN; + } +} + +inline const char *getFormatStr(const FrameFormat &format) { + switch(format) { + case FRAME_FORMAT_UNKNOWN: + return "UNKNOWN"; + case FRAME_FORMAT_I420: + return "I420"; + case FRAME_FORMAT_MSDK: + return "MSDK"; + case FRAME_FORMAT_VP8: + return "VP8"; + case FRAME_FORMAT_VP9: + return "VP9"; + case FRAME_FORMAT_H264: + return "H264"; + case FRAME_FORMAT_H265: + return "H265"; + case FRAME_FORMAT_PCM_48000_2: + return "PCM_48000_2"; + case FRAME_FORMAT_PCMU: + return "PCMU"; + case FRAME_FORMAT_PCMA: + return "PCMA"; + case FRAME_FORMAT_OPUS: + return "OPUS"; + case FRAME_FORMAT_ISAC16: + return "ISAC16"; + case FRAME_FORMAT_ISAC32: + return "ISAC32"; + case FRAME_FORMAT_ILBC: + return "ILBC"; + case FRAME_FORMAT_G722_16000_1: + return "G722_16000_1"; + case FRAME_FORMAT_G722_16000_2: + return "G722_16000_2"; + case FRAME_FORMAT_AAC: + return "AAC"; + case FRAME_FORMAT_AAC_48000_2: + return "AAC_48000_2"; + case FRAME_FORMAT_AC3: + return "AC3"; + case FRAME_FORMAT_NELLYMOSER: + return "NELLYMOSER"; + default: + return "INVALID"; + } +} + +inline bool isAudioFrame(const Frame& frame) { + return frame.format == FRAME_FORMAT_PCM_48000_2 + || frame.format == FRAME_FORMAT_PCMU + || frame.format == FRAME_FORMAT_PCMA + || frame.format == FRAME_FORMAT_OPUS + || frame.format == FRAME_FORMAT_ISAC16 + || frame.format == FRAME_FORMAT_ISAC32 + || frame.format == FRAME_FORMAT_ILBC + || frame.format == FRAME_FORMAT_G722_16000_1 + || frame.format == FRAME_FORMAT_G722_16000_2 + || frame.format == FRAME_FORMAT_AAC + || frame.format == FRAME_FORMAT_AAC_48000_2 + || frame.format == FRAME_FORMAT_AC3 + || frame.format == FRAME_FORMAT_NELLYMOSER; +} + +inline bool isVideoFrame(const Frame& frame) { + return frame.format == FRAME_FORMAT_I420 + || frame.format ==FRAME_FORMAT_MSDK + || frame.format == FRAME_FORMAT_VP8 + || frame.format == FRAME_FORMAT_VP9 + || frame.format == FRAME_FORMAT_H264 + || frame.format == FRAME_FORMAT_H265; +} + +inline bool isDataFrame(const Frame& frame) { + return frame.format == FRAME_FORMAT_DATA; +} + +enum FeedbackType { + VIDEO_FEEDBACK, + AUDIO_FEEDBACK +}; + +enum FeedbackCmd { + REQUEST_KEY_FRAME, + SET_BITRATE, + REQUEST_OWNER_ID, + INIT_STREAM_ID, + RTCP_PACKET // FIXME: Temporarily use FeedbackMsg to carry audio rtcp-packets due to the premature AudioFrameConstructor implementation. +}; + +struct FeedbackMsg { + FeedbackType type; + FeedbackCmd cmd; + union { + unsigned short kbps; + struct RtcpPacket{// FIXME: Temporarily use FeedbackMsg to carry audio rtcp-packets due to the premature AudioFrameConstructor implementation. + uint32_t len; + char buf[128]; + } rtcp; + } data; + struct MsgBuffer{ + uint32_t len; + char data[128]; + } buffer; + FeedbackMsg(FeedbackType t, FeedbackCmd c) : type{t}, cmd{c} {} +}; + +class FrameDestination; +class FrameSource { +public: + FrameSource() { } + virtual ~FrameSource(); + + virtual void onFeedback(const FeedbackMsg&) { }; + + void addAudioDestination(FrameDestination*); + void removeAudioDestination(FrameDestination*); + + void addVideoDestination(FrameDestination*); + void removeVideoDestination(FrameDestination*); + + void addDataDestination(FrameDestination*); + void removeDataDestination(FrameDestination*); + +protected: + void deliverFrame(const Frame&); + void deliverMetaData(const MetaData&); + +private: + std::list m_audio_dests; + boost::shared_mutex m_audio_dests_mutex; + std::list m_video_dests; + boost::shared_mutex m_video_dests_mutex; + std::list m_data_dests; + boost::shared_mutex m_data_dests_mutex; +}; + + +class FrameDestination { +public: + FrameDestination() : m_audio_src(nullptr), m_video_src(nullptr), m_data_src(nullptr) { } + virtual ~FrameDestination() { } + + virtual void onFrame(const Frame&) = 0; + virtual void onMetaData(const MetaData&) {} + virtual void onVideoSourceChanged() {} + + void setAudioSource(FrameSource*); + void unsetAudioSource(); + + void setVideoSource(FrameSource*); + void unsetVideoSource(); + + void setDataSource(FrameSource*); + void unsetDataSource(); + + bool hasAudioSource() { return m_audio_src != nullptr; } + bool hasVideoSource() { return m_video_src != nullptr; } + bool hasDataSource() { return m_data_src != nullptr; } + +protected: + void deliverFeedbackMsg(const FeedbackMsg& msg); + +private: + FrameSource* m_audio_src; + boost::shared_mutex m_audio_src_mutex; + FrameSource* m_video_src; + boost::shared_mutex m_video_src_mutex; + FrameSource* m_data_src; + boost::shared_mutex m_data_src_mutex; +}; + +class VideoFrameDecoder : public FrameSource, public FrameDestination { +public: + virtual ~VideoFrameDecoder() { } + virtual bool init(FrameFormat) = 0; +}; + +class VideoFrameProcesser : public FrameSource, public FrameDestination { +public: + virtual ~VideoFrameProcesser() { } + virtual bool init(FrameFormat format, const uint32_t width, const uint32_t height, const uint32_t frameRate) = 0; + virtual void drawText(const std::string& textSpec) = 0; + virtual void clearText() = 0; +}; + +class VideoFrameAnalyzer : public FrameSource, public FrameDestination { +public: + virtual ~VideoFrameAnalyzer() { } + virtual bool init(FrameFormat format, const uint32_t width, const uint32_t height, const uint32_t frameRate, const std::string& pluginName) = 0; +}; + +class VideoFrameEncoder : public FrameDestination { +public: + virtual ~VideoFrameEncoder() { } + + virtual FrameFormat getInputFormat() = 0; + + virtual bool canSimulcast(FrameFormat, uint32_t width, uint32_t height) = 0; + virtual bool isIdle() = 0; + virtual int32_t generateStream(uint32_t width, uint32_t height, uint32_t frameRate, uint32_t bitrateKbps, uint32_t keyFrameIntervalSeconds, FrameDestination*) = 0; + virtual void degenerateStream(int32_t streamId) = 0; + virtual void setBitrate(unsigned short kbps, int32_t streamId) = 0; + virtual void requestKeyFrame(int32_t streamId) = 0; +}; + +#endif diff --git a/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360MediaUtilities.h b/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360MediaUtilities.h new file mode 100644 index 00000000..d1eed748 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/libwebrtc360/src/WebRTC360MediaUtilities.h @@ -0,0 +1,84 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef MediaUtilities_h +#define MediaUtilities_h + +static int partial_linear_bitrate[][2] = { + {0, 0}, {76800, 400}, {307200, 800}, {921600, 2000}, {2073600, 4000}, {8294400, 16000} +}; + +inline unsigned int calcBitrate(unsigned int width, unsigned int height, float framerate = 30) { + unsigned int bitrate = 0; + unsigned int prev = 0; + unsigned int next = 0; + float portion = 0.0; + unsigned int def = width * height * framerate / 30; + int lines = sizeof(partial_linear_bitrate) / sizeof(partial_linear_bitrate[0][0]) / 2; + + // find the partial linear section and calculate bitrate + for (int i = 0; i < lines - 1; i++) { + prev = partial_linear_bitrate[i][0]; + next = partial_linear_bitrate[i+1][0]; + if (def > prev && def <= next) { + portion = static_cast(def - prev) / (next - prev); + bitrate = partial_linear_bitrate[i][1] + (partial_linear_bitrate[i+1][1] - partial_linear_bitrate[i][1]) * portion; + break; + } + } + + // set default bitrate for over large resolution + if (0 == bitrate) + bitrate = 8000; + + return bitrate; +} + +inline int findNALU(uint8_t* buf, int size, int* nal_start, int* nal_end, int* sc_len) +{ + int i = 0; + *nal_start = 0; + *nal_end = 0; + *sc_len = 0; + + while (true) { + if (size < i + 3) + return -1; /* Did not find NAL start */ + + /* ( next_bits( 24 ) == {0, 0, 1} ) */ + if (buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 1) { + i += 3; + *sc_len = 3; + break; + } + + /* ( next_bits( 32 ) == {0, 0, 0, 1} ) */ + if (size > i + 3 && buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 0 && buf[i + 3] == 1) { + i += 4; + *sc_len = 4; + break; + } + + ++i; + } + + // assert(buf[i - 1] == 1); + *nal_start = i; + + /*( next_bits( 24 ) != {0, 0, 1} )*/ + while ((size > i + 2) && + (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 1)) + ++i; + + if (size <= i + 2) + *nal_end = size; + else if (buf[i - 1] == 0) + *nal_end = i - 1; + else + *nal_end = i; + + return (*nal_end - *nal_start); +} + +#endif // MediaUtilities_h diff --git a/WebRTC-Sample/owt-server/source/libwebrtc360/src/build.sh b/WebRTC-Sample/owt-server/source/libwebrtc360/src/build.sh new file mode 100755 index 00000000..55d72a00 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/libwebrtc360/src/build.sh @@ -0,0 +1,24 @@ +#!/bin/zsh -ex + +source /opt/rh/devtoolset-7/enable + +FILE_PATH="${PWD}/" +HIGH_INPUT_4K="4k/stitcher-hi-res-0x7f799802df20.hevc" +LOW_INPUT_4K="4k/stitcher-low-res-0x7f799802df20.hevc" +HIGH_INPUT_8K="8k/stitcher-hi-res-0x7f7eec001050.hevc" +LOW_INPUT_8K="8k/stitcher-low-res-0x7f799802df20.hevc" + +mkdir -p build && cd build && cmake .. && make -j8 + +cd ../sample + +mkdir -p build && cd build && cmake .. && make -j8 + +./sample --help + +# ./sample \ +# -f 30 \ +# -r 4K \ +# -h "${FILE_PATH}${HIGH_INPUT_4K}" \ +# -l "${FILE_PATH}${LOW_INPUT_4K}" + diff --git a/WebRTC-Sample/owt-server/source/libwebrtc360/src/sample/CMakeLists.txt b/WebRTC-Sample/owt-server/source/libwebrtc360/src/sample/CMakeLists.txt new file mode 100644 index 00000000..76d0bf5f --- /dev/null +++ b/WebRTC-Sample/owt-server/source/libwebrtc360/src/sample/CMakeLists.txt @@ -0,0 +1,14 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.12) +PROJECT(live360-WebRTC-sample) + +FIND_PACKAGE(Boost REQUIRED COMPONENTS system thread program_options) +FILE(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/..) + +LINK_DIRECTORIES(../build) + +ADD_EXECUTABLE(sample ${SOURCES}) + +TARGET_COMPILE_DEFINITIONS(sample PUBLIC _ENABLE_HEVC_TILES_MERGER_ ENABLE_WEBRTC360) + +TARGET_LINK_LIBRARIES(sample live360WebRTC Boost::system Boost::thread Boost::program_options) diff --git a/WebRTC-Sample/owt-server/source/libwebrtc360/src/sample/webrtc_360_sample.cpp b/WebRTC-Sample/owt-server/source/libwebrtc360/src/sample/webrtc_360_sample.cpp new file mode 100644 index 00000000..2cb4fc55 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/libwebrtc360/src/sample/webrtc_360_sample.cpp @@ -0,0 +1,219 @@ +#include +#include "WebRTC360HEVCTilesMerger.h" + +#define HWIDTH4K 3840 +#define HHEIGHT4K 2048 +#define LWIDTH4K 1280 +#define LHEIGHT4K 768 +#define HWIDTH8K 7680 +#define HHEIGHT8K 3840 +#define LWIDTH8K 1280 +#define LHEIGHT8K 512 + +namespace po = boost::program_options; + +bool ReadBuffer(int* bufferSize, bool* isKey, uint8_t** buffer, FILE* fp) { + + int result = 0; + fread(bufferSize, sizeof(int), 1, fp); + fread(isKey, sizeof(bool), 1, fp); + *buffer = (uint8_t*)malloc(sizeof(uint8_t)*(*bufferSize)); + if (!*buffer) { + std::cout << "Memory allocate error" << std::endl; + return false; + } + + memset(*buffer, 0, sizeof(uint8_t)*(*bufferSize)); + result = fread(*buffer, *bufferSize, 1, fp); + if (!result) { + std::cout << "Reading error: " << std::endl; + free(*buffer); + *buffer = NULL; + return false; + } + + return true; +} + +void DisplayPayloadByOffset(uint8_t* src, int offset) { + + uint32_t payload = src[offset + 0] + | src[offset + 1] << 8 + | src[offset + 2] << 16 + | src[offset + 3] << 24; + std::cout << "Payload start with offset " << offset << " : " << payload << std::endl; +} + +void ConcatPointerUint8(uint8_t* src, uint8_t** target, int uint8count, int* offset) { + + std::copy(src, src+uint8count, *target+*offset); + *offset += uint8count; + +} + +void LoadBuffer(uint32_t width, uint32_t height, uint32_t bufferSize, + uint8_t* buffer, Frame* frame, int* offset) { + + uint8_t widthArray[4] = { 0 }, heightArray[4] = { 0 }, lengthArray[4] = { 0 }; + memcpy(widthArray, &width, sizeof(uint32_t)); + memcpy(heightArray, &height, sizeof(uint32_t)); + memcpy(lengthArray, &bufferSize, sizeof(uint32_t)); + ConcatPointerUint8(widthArray, &frame->payload, 4, offset); + ConcatPointerUint8(heightArray, &frame->payload, 4, offset); + ConcatPointerUint8(lengthArray, &frame->payload, 4, offset); + ConcatPointerUint8(buffer, &frame->payload, bufferSize, offset); + +} + +int main(int argc, char **argv) { + + std::string hiFileToLoad, lowFileToLoad; + std::string resolution; + int32_t output_frames = 30; + + try { + + po::options_description desc("Allowd options"); + desc.add_options() + ("help", "produce help message") + ("frame_num,f", po::value(), "set frames") + ("resolution,r", po::value(), "set resolution") + ("high_res_file,h", po::value(), "set file path") + ("low_res_file,l", po::value(), "set fiel path"); + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if(vm.count("help")) { + std::cout << desc << std::endl; + return 0; + } + + if(vm.count("frame_num")) { + output_frames = vm["frame_num"].as(); + } + + if(vm.count("resolution")) { + resolution = vm["resolution"].as(); + } + + if(vm.count("high_res_file")) { + hiFileToLoad = vm["high_res_file"].as(); + } + + if(vm.count("low_res_file")) { + lowFileToLoad = vm["low_res_file"].as(); + } + + } + catch(std::exception& e) { + std::cerr << "error" << e.what() << std::endl; + return 1; + } + catch(...) { + std::cerr << "Exception of unknown type!" << std::endl; + return 1; + } + + int hWidth = 0, hHeight = 0, lWidth = 0, lHeight = 0; + if (resolution == "4K") { + hWidth = HWIDTH4K; + hHeight = HHEIGHT4K; + lWidth = LWIDTH4K; + lHeight = LHEIGHT4K; + } else if (resolution == "8K") { + hWidth = HWIDTH8K; + hHeight = HHEIGHT8K; + lWidth = LWIDTH8K; + lHeight = LHEIGHT8K; + } else { + std::cerr << "Resolution \"-r\" must be 4K or 8K" << std::endl; + return 1; + } + + FILE* fpHigh = fopen(hiFileToLoad.c_str(), "r"); + FILE* fpLow = fopen(lowFileToLoad.c_str(), "r"); + + VideoFrameSpecificInfo* highResInfo = new VideoFrameSpecificInfo(); + memset(highResInfo, 0, sizeof(VideoFrameSpecificInfo)); + highResInfo->width = (uint16_t)hWidth; + highResInfo->height = (uint16_t)hHeight; + highResInfo->isKeyFrame = false; + VideoFrameSpecificInfo* lowResInfo = new VideoFrameSpecificInfo(); + memset(lowResInfo, 0, sizeof(VideoFrameSpecificInfo)); + lowResInfo->width = (uint16_t)lWidth; + lowResInfo->height = (uint16_t)lHeight; + lowResInfo->isKeyFrame = false; + + Frame* inFrame = new Frame(); + memset(inFrame, 0, sizeof(Frame)); + inFrame->payload = NULL; + inFrame->format = FRAME_FORMAT_H265; + memcpy(&inFrame->additionalInfo, highResInfo, sizeof(VideoFrameSpecificInfo)); + + HEVCTilesMerger* tilesMerger = new HEVCTilesMerger(); + + int offset = 0, index = 0, hiBufferSize = 0, lowBufferSize = 0; + uint32_t hiWidth = hWidth, hiHeight = hHeight; + uint32_t lowWidth = lWidth, lowHeight = lHeight; + bool hiIsKey = NULL, lowIsKey = NULL; + uint8_t* hiBuffer = NULL; + uint8_t* lowBuffer = NULL; + + while (!feof(fpHigh) || !feof(fpLow)) { + + if (index == output_frames) { + break; + } + index++; + + if (!ReadBuffer(&hiBufferSize, &hiIsKey, &hiBuffer, fpHigh)) { + std::cerr << "High res read buffer failed" << std::endl; + } else { + + int payloadSize = sizeof(int)*(hiBufferSize) + 12; + inFrame->payload = (uint8_t*)malloc(payloadSize); + memset(inFrame->payload, 0, payloadSize); + + LoadBuffer(hiWidth, hiHeight, hiBufferSize, hiBuffer, inFrame, &offset); + + free(hiBuffer); + hiBuffer = NULL; + } + + if (!ReadBuffer(&lowBufferSize, &lowIsKey, &lowBuffer, fpLow)) { + std::cerr << "Low res read buffer failed" << std::endl; + } else { + + LoadBuffer(lowWidth, lowHeight, lowBufferSize, lowBuffer, inFrame, &offset); + + if (hiIsKey == lowIsKey) { + uint8_t isKeyFrame[1] = { 0 }; + memcpy(&inFrame->additionalInfo.video.isKeyFrame, &lowIsKey, sizeof(bool)); + ConcatPointerUint8(isKeyFrame, &inFrame->payload, 1, &offset); + } else { + std::cout << "Key frame info mismatch!" << std::endl; + } + + free(lowBuffer); + lowBuffer = NULL; + } + + Frame outFrame = *inFrame; + tilesMerger->onFrame(*inFrame, &outFrame); + free(inFrame->payload); + inFrame->payload = NULL; + offset = 0; + + } + + fclose(fpHigh); + fclose(fpLow); + delete inFrame; + delete highResInfo; + delete lowResInfo; + std::cout << std::endl << " --------- Done ------------ " << std::endl; + + return 0; +} diff --git a/WebRTC-Sample/owt-server/source/rtc_frame/src/RtcAdapter.h b/WebRTC-Sample/owt-server/source/rtc_frame/src/RtcAdapter.h new file mode 100644 index 00000000..f0ef2509 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/rtc_frame/src/RtcAdapter.h @@ -0,0 +1,105 @@ +// Copyright (C) <2020> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef RTC_ADAPTER_RTC_ADAPTER_H_ +#define RTC_ADAPTER_RTC_ADAPTER_H_ + +#include + +namespace rtc_adapter { + +class AdapterDataListener { +public: + virtual void onAdapterData(char* data, int len) = 0; +}; + +class AdapterFrameListener { +public: + virtual void onAdapterFrame(const owt_base::Frame& frame) = 0; +}; + +class AdapterFeedbackListener { +public: + virtual void onFeedback(const owt_base::FeedbackMsg& msg) = 0; +}; + +struct AdapterStats { + int width = 0; + int height = 0; + owt_base::FrameFormat format = owt_base::FRAME_FORMAT_UNKNOWN; + int estimatedBandwidth = 0; +}; + +class AdapterStatsListener { +public: + virtual void onAdapterStats(const AdapterStats& stat) = 0; +}; + +class VideoReceiveAdapter { +public: + virtual int onRtpData(char* data, int len) = 0; + virtual void requestKeyFrame() = 0; +}; + +class VideoSendAdapter { +public: + virtual void onFrame(const owt_base::Frame&) = 0; + virtual int onRtcpData(char* data, int len) = 0; + virtual uint32_t ssrc() = 0; + virtual void reset() = 0; + virtual void setFoV(int32_t yaw, int32_t pitch) = 0; +}; + +class AudioReceiveAdapter { +public: + virtual int onRtpData(char* data, int len) = 0; +}; + +class AudioSendAdapter { +public: + virtual void onFrame(const owt_base::Frame&) = 0; + virtual int onRtcpData(char* data, int len) = 0; + virtual uint32_t ssrc() = 0; +}; + +class RtcAdapter { +public: + struct Config { + // SSRC of target stream + uint32_t ssrc = 0; + // Transport-cc extension ID + int transport_cc = 0; + int red_payload = 0; + int ulpfec_payload = 0; + // MID of target stream + char mid[32]; + // MID extension ID + int mid_ext = 0; + AdapterDataListener* rtp_listener = nullptr; + AdapterStatsListener* stats_listener = nullptr; + AdapterFrameListener* frame_listener = nullptr; + AdapterFeedbackListener* feedback_listener = nullptr; + }; + virtual VideoReceiveAdapter* createVideoReceiver(const Config&) = 0; + virtual void destoryVideoReceiver(VideoReceiveAdapter*) = 0; + virtual VideoSendAdapter* createVideoSender(const Config&) = 0; + virtual void destoryVideoSender(VideoSendAdapter*) = 0; + + virtual AudioReceiveAdapter* createAudioReceiver(const Config&) = 0; + virtual void destoryAudioReceiver(AudioReceiveAdapter*) = 0; + virtual AudioSendAdapter* createAudioSender(const Config&) = 0; + virtual void destoryAudioSender(AudioSendAdapter*) = 0; + virtual ~RtcAdapter(){} +}; + +class RtcAdapterFactory { +public: + static RtcAdapter* CreateRtcAdapter(); + // Use delete instead of this function + static void DestroyRtcAdapter(RtcAdapter*); +}; + +} // namespace rtc_adapter + +#endif diff --git a/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoFramePacketizer.cpp b/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoFramePacketizer.cpp new file mode 100644 index 00000000..fe0bfab4 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoFramePacketizer.cpp @@ -0,0 +1,182 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#include "VideoFramePacketizer.h" +#include "MediaUtilities.h" +#include +#include + +using namespace rtc_adapter; + +namespace owt_base { + +// To make it consistent with the webrtc library, we allow packets to be transmitted +// in up to 2 times max video bitrate if the bandwidth estimate allows it. +static const int TRANSMISSION_MAXBITRATE_MULTIPLIER = 2; + +DEFINE_LOGGER(VideoFramePacketizer, "owt.VideoFramePacketizer"); + +VideoFramePacketizer::VideoFramePacketizer(VideoFramePacketizer::Config& config) + : m_enabled(true) + , m_frameFormat(FRAME_FORMAT_UNKNOWN) + , m_frameWidth(0) + , m_frameHeight(0) + , m_ssrc(0) + , m_sendFrameCount(0) + , m_rtcAdapter(RtcAdapterFactory::CreateRtcAdapter()) + , m_videoSend(nullptr) +{ + video_sink_ = nullptr; + init(config); +} + +VideoFramePacketizer::~VideoFramePacketizer() +{ + close(); + if (m_videoSend) { + m_rtcAdapter->destoryVideoSender(m_videoSend); + m_rtcAdapter.reset(); + m_videoSend = nullptr; + } +} + +bool VideoFramePacketizer::init(VideoFramePacketizer::Config& config) +{ + if (!m_videoSend) { + // Create Send Video Stream + rtc_adapter::RtcAdapter::Config sendConfig; + if (config.enableTransportcc) { + sendConfig.transport_cc = config.transportccExt; + } + if (config.enableRed) { + sendConfig.red_payload = RED_90000_PT; + } + if (config.enableUlpfec) { + sendConfig.ulpfec_payload = ULP_90000_PT; + } + if (!config.mid.empty()) { + strncpy(sendConfig.mid, config.mid.c_str(), sizeof(sendConfig.mid) - 1); + sendConfig.mid_ext = config.midExtId; + } + sendConfig.feedback_listener = this; + sendConfig.rtp_listener = this; + sendConfig.stats_listener = this; + m_videoSend = m_rtcAdapter->createVideoSender(sendConfig); + m_ssrc = m_videoSend->ssrc(); + return true; + } + + return false; +} + +void VideoFramePacketizer::bindTransport(erizo::MediaSink* sink) +{ + boost::unique_lock lock(m_transportMutex); + video_sink_ = sink; + video_sink_->setVideoSinkSSRC(m_videoSend->ssrc()); + erizo::FeedbackSource* fbSource = video_sink_->getFeedbackSource(); + if (fbSource) + fbSource->setFeedbackSink(this); +} + +void VideoFramePacketizer::unbindTransport() +{ + boost::unique_lock lock(m_transportMutex); + if (video_sink_) { + video_sink_ = nullptr; + } +} + +void VideoFramePacketizer::enable(bool enabled) +{ + m_enabled = enabled; + if (m_enabled) { + m_sendFrameCount = 0; + if (m_videoSend) { + m_videoSend->reset(); + } + } +} + +void VideoFramePacketizer::onFeedback(const FeedbackMsg& msg) +{ + deliverFeedbackMsg(msg); +} + +void VideoFramePacketizer::onAdapterStats(const AdapterStats& stats) {} + +void VideoFramePacketizer::onAdapterData(char* data, int len) +{ + boost::shared_lock lock(m_transportMutex); + if (!video_sink_) { + return; + } + + video_sink_->deliverVideoData(std::make_shared(0, data, len, erizo::VIDEO_PACKET)); +} + +void VideoFramePacketizer::onFrame(const Frame& frame) +{ + if (!m_enabled) { + return; + } + + if (m_selfRequestKeyframe) { + //FIXME: This is a workround for peer client not send key-frame-request + if (m_sendFrameCount < 151) { + if ((m_sendFrameCount == 10) + || (m_sendFrameCount == 30) + || (m_sendFrameCount == 60) + || (m_sendFrameCount == 150)) { + // ELOG_DEBUG("Self generated key-frame-request."); + FeedbackMsg feedback = {.type = VIDEO_FEEDBACK, .cmd = REQUEST_KEY_FRAME }; + deliverFeedbackMsg(feedback); + } + m_sendFrameCount += 1; + } + } + + if (m_videoSend) { + m_videoSend->onFrame(frame); + } +} + +void VideoFramePacketizer::onVideoSourceChanged() +{ + if (m_videoSend) { + m_videoSend->reset(); + } +} + +int VideoFramePacketizer::sendFirPacket() +{ + FeedbackMsg feedback = {.type = VIDEO_FEEDBACK, .cmd = REQUEST_KEY_FRAME }; + deliverFeedbackMsg(feedback); + return 0; +} + +void VideoFramePacketizer::close() +{ + unbindTransport(); +} + +int VideoFramePacketizer::deliverFeedback_(std::shared_ptr data_packet) +{ + if (m_videoSend) { + m_videoSend->onRtcpData(data_packet->data, data_packet->length); + return data_packet->length; + } + return 0; +} + +int VideoFramePacketizer::sendPLI() +{ + return 0; +} + +void VideoFramePacketizer::setFoV(int32_t yaw, int32_t pitch) { + m_videoSend->setFoV(yaw, pitch); +} + +} diff --git a/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoFramePacketizer.h b/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoFramePacketizer.h new file mode 100644 index 00000000..0fcc01b5 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoFramePacketizer.h @@ -0,0 +1,92 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef VideoFramePacketizer_h +#define VideoFramePacketizer_h + +#include "MediaFramePipeline.h" + +#include +#include + +#include +#include +#include +#include + +#include + +namespace owt_base { +/** + * This is the class to accept the encoded frame with the given format, + * packetize the frame and send them out via the given WebRTCTransport. + * It also gives the feedback to the encoder based on the feedback from the remote. + */ +class VideoFramePacketizer : public FrameDestination, + public erizo::MediaSource, + public erizo::FeedbackSink, + public rtc_adapter::AdapterFeedbackListener, + public rtc_adapter::AdapterStatsListener, + public rtc_adapter::AdapterDataListener { + DECLARE_LOGGER(); + +public: + struct Config { + bool enableRed = false; + bool enableUlpfec = false; + bool enableTransportcc = true; + bool selfRequestKeyframe = false; + uint32_t transportccExt = 0; + std::string mid = ""; + uint32_t midExtId = 0; + }; + VideoFramePacketizer(Config& config); + ~VideoFramePacketizer(); + + void bindTransport(erizo::MediaSink* sink); + void unbindTransport(); + void enable(bool enabled); + uint32_t getSsrc() { return m_ssrc; } + + void setFoV(int32_t yaw, int32_t pitch); + + // Implements FrameDestination. + void onFrame(const Frame&); + void onVideoSourceChanged() override; + + // Implements erizo::MediaSource. + int sendFirPacket(); + + // Implements the AdapterFeedbackListener interfaces. + void onFeedback(const FeedbackMsg& msg) override; + // Implements the AdapterStatsListener interfaces. + void onAdapterStats(const rtc_adapter::AdapterStats& stats) override; + // Implements the AdapterDataListener interfaces. + void onAdapterData(char* data, int len) override; + +private: + bool init(Config& config); + void close(); + + // Implement erizo::FeedbackSink + int deliverFeedback_(std::shared_ptr data_packet); + // Implement erizo::MediaSource + int sendPLI(); + + bool m_enabled; + bool m_selfRequestKeyframe; + + FrameFormat m_frameFormat; + uint16_t m_frameWidth; + uint16_t m_frameHeight; + uint32_t m_ssrc; + + boost::shared_mutex m_transportMutex; + + uint16_t m_sendFrameCount; + std::shared_ptr m_rtcAdapter; + rtc_adapter::VideoSendAdapter* m_videoSend; +}; +} +#endif /* EncodedVideoFrameSender_h */ diff --git a/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoSendAdapter.cc b/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoSendAdapter.cc new file mode 100644 index 00000000..f22cf8b0 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoSendAdapter.cc @@ -0,0 +1,442 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#include "VideoSendAdapter.h" +#include "MediaUtilities.h" +#include "TaskRunnerPool.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace owt_base; + +namespace rtc_adapter { + +// To make it consistent with the webrtc library, we allow packets to be transmitted +// in up to 2 times max video bitrate if the bandwidth estimate allows it. +static const int TRANSMISSION_MAXBITRATE_MULTIPLIER = 2; +static const int kMaxRtpPacketSize = 1200; + +static int getNextNaluPosition(uint8_t* buffer, int buffer_size, bool& is_aud_or_sei, int& sc_len) +{ + if (buffer_size < 3) { + return -1; + } + is_aud_or_sei = false; + uint8_t* head = buffer; + uint8_t* end = buffer + buffer_size - 3; + while (head < end) { + if (head[0]) { + head++; + continue; + } + if (head[1]) { + head += 2; + continue; + } + if (head[2]) { + if (head[2] == 0x01) { + if (((head[3] & 0x1F) == 9) || ((head[3] & 0x1F) == 6)) { + is_aud_or_sei = true; + } + sc_len = 3; + return static_cast(head - buffer); + } + head += 3; + continue; + } + if (head[3] != 0x01) { + head++; + continue; + } + if (head + 1 == end) { + break; + } + if (((head[4] & 0x1F) == 9) || ((head[4] & 0x1F) == 6)) { + is_aud_or_sei = true; + } + sc_len = 4; + return static_cast(head - buffer); + } + return -1; +} + +#define MAX_NALS_PER_FRAME 128 +static int dropAUDandSEI(uint8_t* framePayload, int frameLength) +{ + uint8_t* origin_pkt_data = framePayload; + int origin_pkt_length = frameLength; + uint8_t* head = origin_pkt_data; + + std::vector nal_offset; + std::vector nal_type_is_aud_or_sei; + std::vector nal_size; + bool is_aud_or_sei = false, has_aud_or_sei = false; + + int sc_positions_length = 0; + int sc_position = 0; + int sc_len = 4; + while (sc_positions_length < MAX_NALS_PER_FRAME) { + int nalu_position = getNextNaluPosition(origin_pkt_data + sc_position, + origin_pkt_length - sc_position, is_aud_or_sei, sc_len); + if (nalu_position < 0) { + break; + } + sc_position += nalu_position; + nal_offset.push_back(sc_position); //include start code. + sc_position += sc_len; + sc_positions_length++; + if (is_aud_or_sei) { + has_aud_or_sei = true; + nal_type_is_aud_or_sei.push_back(true); + } else { + nal_type_is_aud_or_sei.push_back(false); + } + } + if (sc_positions_length == 0 || !has_aud_or_sei) + return frameLength; + // Calculate size of each NALs + for (unsigned int count = 0; count < nal_offset.size(); count++) { + if (count + 1 == nal_offset.size()) { + nal_size.push_back(origin_pkt_length - nal_offset[count]); + } else { + nal_size.push_back(nal_offset[count + 1] - nal_offset[count]); + } + } + // remove in place the AUD NALs + int new_size = 0; + for (unsigned int i = 0; i < nal_offset.size(); i++) { + if (!nal_type_is_aud_or_sei[i]) { + memmove(head + new_size, head + nal_offset[i], nal_size[i]); + new_size += nal_size[i]; + } + } + return new_size; +} + +static void dump(void* index, FrameFormat format, uint8_t* buf, int len) +{ + char dumpFileName[128]; + + snprintf(dumpFileName, 128, "/tmp/prePacketizer-%p.%s", index, getFormatStr(format)); + FILE* bsDumpfp = fopen(dumpFileName, "ab"); + if (bsDumpfp) { + fwrite(buf, 1, len, bsDumpfp); + fclose(bsDumpfp); + } +} + +VideoSendAdapterImpl::VideoSendAdapterImpl( + CallOwner* owner, + const RtcAdapter::Config& config) + : m_enableDump(false) + , m_config(config) + , m_keyFrameArrived(false) + , m_frameFormat(FRAME_FORMAT_UNKNOWN) + , m_frameWidth(0) + , m_frameHeight(0) + , m_random(rtc::TimeMicros()) + , m_ssrc(0) + , m_ssrcGenerator(SsrcGenerator::GetSsrcGenerator()) + , m_clock(nullptr) + , m_timeStampOffset(0) + , m_feedbackListener(config.feedback_listener) + , m_rtpListener(config.rtp_listener) + , m_statsListener(config.stats_listener) +{ + m_ssrc = m_ssrcGenerator->CreateSsrc(); + m_ssrcGenerator->RegisterSsrc(m_ssrc); + m_taskRunner = TaskRunnerPool::GetInstance().GetTaskRunner(); + init(); +} + +VideoSendAdapterImpl::~VideoSendAdapterImpl() +{ + m_taskRunner->DeRegisterModule(m_rtpRtcp.get()); + m_ssrcGenerator->ReturnSsrc(m_ssrc); + boost::unique_lock lock(m_rtpRtcpMutex); +} + +bool VideoSendAdapterImpl::init() +{ + m_clock = webrtc::Clock::GetRealTimeClock(); + m_retransmissionRateLimiter.reset( + new webrtc::RateLimiter(webrtc::Clock::GetRealTimeClock(), 1000)); + + m_eventLog = std::make_unique(); + webrtc::RtpRtcp::Configuration configuration; + configuration.clock = m_clock; + configuration.audio = false; + configuration.receiver_only = false; + configuration.outgoing_transport = this; + configuration.intra_frame_callback = this; + configuration.event_log = m_eventLog.get(); + configuration.retransmission_rate_limiter = m_retransmissionRateLimiter.get(); + configuration.local_media_ssrc = m_ssrc; //rtp_config.ssrcs[i]; + configuration.rtcp_fov_observer = this; + + m_rtpRtcp = webrtc::RtpRtcp::Create(configuration); + m_rtpRtcp->SetSendingStatus(true); + m_rtpRtcp->SetSendingMediaStatus(true); + m_rtpRtcp->SetRTCPStatus(webrtc::RtcpMode::kReducedSize); + // Set NACK. + m_rtpRtcp->SetStorePacketsStatus(true, 600); + if (m_config.transport_cc) { + m_rtpRtcp->RegisterRtpHeaderExtension( + webrtc::RtpExtension::kTransportSequenceNumberUri, m_config.transport_cc); + } + if (m_config.mid_ext) { + m_config.mid[sizeof(m_config.mid) - 1] = '\0'; + std::string mid(m_config.mid); + // Register MID extension + m_rtpRtcp->RegisterRtpHeaderExtension( + webrtc::RtpExtension::kMidUri, m_config.mid_ext); + m_rtpRtcp->SetMid(mid); + } + + m_rtpRtcp->SetMaxRtpPacketSize(kMaxRtpPacketSize); + + webrtc::RTPSenderVideo::Config video_config; + m_playoutDelayOracle = std::make_unique(); + m_fieldTrialConfig = std::make_unique(); + video_config.clock = configuration.clock; + video_config.rtp_sender = m_rtpRtcp->RtpSender(); + video_config.field_trials = m_fieldTrialConfig.get(); + video_config.playout_delay_oracle = m_playoutDelayOracle.get(); + if (m_config.red_payload) { + video_config.red_payload_type = m_config.red_payload; + } + if (m_config.ulpfec_payload) { + video_config.ulpfec_payload_type = m_config.ulpfec_payload; + } + + m_senderVideo = std::make_unique(video_config); + // m_params = std::make_unique(m_ssrc, nullptr); + m_taskRunner->RegisterModule(m_rtpRtcp.get()); + + return true; +} + +void VideoSendAdapterImpl::reset() +{ + m_keyFrameArrived = false; + m_timeStampOffset = 0; +} + +void VideoSendAdapterImpl::onFrame(const Frame& inFrame) +{ + Frame frame = inFrame; +#ifdef _ENABLE_HEVC_TILES_MERGER_ + if (!m_tilesMerger) + m_tilesMerger = boost::make_shared(); + m_tilesMerger->onFrame(inFrame, &frame); +#endif + + using namespace webrtc; + + if (!m_keyFrameArrived) { + if (!frame.additionalInfo.video.isKeyFrame) { + RTC_DLOG(LS_INFO) << "Key frame has not arrived, send key-frame-request."; + if (m_feedbackListener) { + FeedbackMsg feedback = {.type = VIDEO_FEEDBACK, .cmd = REQUEST_KEY_FRAME }; + m_feedbackListener->onFeedback(feedback); + } + return; + } else { + // Recalculate timestamp offset + const uint32_t kMsToRtpTimestamp = 90; + m_timeStampOffset = kMsToRtpTimestamp * m_clock->TimeInMilliseconds() - frame.timeStamp; + m_keyFrameArrived = true; + } + } + + // Recalculate timestamp for stream substitution + uint32_t timeStamp = frame.timeStamp + m_timeStampOffset; //kMsToRtpTimestamp * m_clock->TimeInMilliseconds(); + webrtc::RTPVideoHeader h; + memset(&h, 0, sizeof(webrtc::RTPVideoHeader)); + + if (frame.format != m_frameFormat + || frame.additionalInfo.video.width != m_frameWidth + || frame.additionalInfo.video.height != m_frameHeight) { + m_frameFormat = frame.format; + m_frameWidth = frame.additionalInfo.video.width; + m_frameHeight = frame.additionalInfo.video.height; + } + + h.frame_type = frame.additionalInfo.video.isKeyFrame ? VideoFrameType::kVideoFrameKey : VideoFrameType::kVideoFrameDelta; + // h.rotation = image.rotation_; + // h.content_type = image.content_type_; + // h.playout_delay = image.playout_delay_; + h.width = m_frameWidth; + h.height = m_frameHeight; + + if (frame.format == FRAME_FORMAT_VP8) { + h.codec = webrtc::VideoCodecType::kVideoCodecVP8; + auto& vp8_header = h.video_type_header.emplace(); + vp8_header.InitRTPVideoHeaderVP8(); + boost::shared_lock lock(m_rtpRtcpMutex); + m_senderVideo->SendVideo( + VP8_90000_PT, + webrtc::kVideoCodecVP8, + timeStamp, + timeStamp, + rtc::ArrayView(frame.payload, frame.length), + nullptr, + h, + m_rtpRtcp->ExpectedRetransmissionTimeMs()); + } else if (frame.format == FRAME_FORMAT_VP9) { + h.codec = webrtc::VideoCodecType::kVideoCodecVP9; + auto& vp9_header = h.video_type_header.emplace(); + vp9_header.InitRTPVideoHeaderVP9(); + vp9_header.inter_pic_predicted = !frame.additionalInfo.video.isKeyFrame; + boost::shared_lock lock(m_rtpRtcpMutex); + m_senderVideo->SendVideo( + VP9_90000_PT, + webrtc::kVideoCodecVP9, + timeStamp, + timeStamp, + rtc::ArrayView(frame.payload, frame.length), + nullptr, + h, + m_rtpRtcp->ExpectedRetransmissionTimeMs()); + } else if (frame.format == FRAME_FORMAT_H264 || frame.format == FRAME_FORMAT_H265) { + int frame_length = frame.length; + if (m_enableDump) { + dump(this, frame.format, frame.payload, frame_length); + } + + //FIXME: temporarily filter out AUD because chrome M59 could NOT handle it correctly. + //FIXME: temporarily filter out SEI because safari could NOT handle it correctly. + if (frame.format == FRAME_FORMAT_H264) { + frame_length = dropAUDandSEI(frame.payload, frame_length); + } + + int nalu_found_length = 0; + uint8_t* buffer_start = frame.payload; + int buffer_length = frame_length; + int nalu_start_offset = 0; + int nalu_end_offset = 0; + int sc_len = 0; + RTPFragmentationHeader frag_info; + + h.codec = (frame.format == FRAME_FORMAT_H264) ? webrtc::VideoCodecType::kVideoCodecH264 : webrtc::VideoCodecType::kVideoCodecH265; + while (buffer_length > 0) { + nalu_found_length = findNALU(buffer_start, buffer_length, &nalu_start_offset, &nalu_end_offset, &sc_len); + if (nalu_found_length < 0) { + /* Error, should never happen */ + break; + } else { + /* SPS, PPS, I, P*/ + uint16_t last = frag_info.fragmentationVectorSize; + frag_info.VerifyAndAllocateFragmentationHeader(last + 1); + frag_info.fragmentationOffset[last] = nalu_start_offset + (buffer_start - frame.payload); + frag_info.fragmentationLength[last] = nalu_found_length; + buffer_start += (nalu_start_offset + nalu_found_length); + buffer_length -= (nalu_start_offset + nalu_found_length); + } + } + + boost::shared_lock lock(m_rtpRtcpMutex); + if (frame.format == FRAME_FORMAT_H264) { + h.video_type_header.emplace(); + m_senderVideo->SendVideo( + H264_90000_PT, + webrtc::kVideoCodecH264, + timeStamp, + timeStamp, + rtc::ArrayView(frame.payload, frame.length), + &frag_info, + h, + m_rtpRtcp->ExpectedRetransmissionTimeMs()); + } else { + h.video_type_header.emplace(); + m_senderVideo->SendVideo( + H265_90000_PT, + webrtc::kVideoCodecH265, + timeStamp, + timeStamp, + rtc::ArrayView(frame.payload, frame.length), + &frag_info, + h, + m_rtpRtcp->ExpectedRetransmissionTimeMs()); + } + } +} + +int VideoSendAdapterImpl::onRtcpData(char* data, int len) +{ + boost::shared_lock lock(m_rtpRtcpMutex); + if (m_rtpRtcp) { + m_rtpRtcp->IncomingRtcpPacket(reinterpret_cast(data), len); + return len; + } + return 0; +} + +bool VideoSendAdapterImpl::SendRtp(const uint8_t* data, + size_t length, + const webrtc::PacketOptions& options) +{ + if (m_rtpListener) { + m_rtpListener->onAdapterData( + reinterpret_cast(const_cast(data)), length); + return true; + } + return false; +} + +bool VideoSendAdapterImpl::SendRtcp(const uint8_t* data, size_t length) +{ + const RTCPHeader* chead = reinterpret_cast(data); + uint8_t packetType = chead->getPacketType(); + if (packetType == RTCP_Sender_PT) { + if (m_rtpListener) { + m_rtpListener->onAdapterData( + reinterpret_cast(const_cast(data)), length); + return true; + } + } + return false; +} + +void VideoSendAdapterImpl::OnReceivedIntraFrameRequest(uint32_t ssrc) +{ + RTC_DLOG(LS_INFO) << "onReceivedIntraFrameRequest."; + if (m_feedbackListener) { + FeedbackMsg feedback = {.type = VIDEO_FEEDBACK, .cmd = REQUEST_KEY_FRAME }; + m_feedbackListener->onFeedback(feedback); + } +} + +//for rtcp fov +void VideoSendAdapterImpl::OnReceivedFOVFeedback(webrtc::RtcpFOVInfo &rtcp_fov_info) { + RTC_DLOG(LS_INFO) << "OnReceivedFOVFeedback, seq_nr " << rtcp_fov_info.seqnr + << " yaw: " << rtcp_fov_info.yaw << " pitch: " << rtcp_fov_info.pitch; + +#ifdef _ENABLE_HEVC_TILES_MERGER_ + if (m_tilesMerger) + { + m_tilesMerger->setFoV(rtcp_fov_info.yaw, rtcp_fov_info.pitch); + } +#endif +} + +//for http fov +void VideoSendAdapterImpl::setFoV(int32_t yaw, int32_t pitch) { +#ifdef _ENABLE_HEVC_TILES_MERGER_ + if (m_tilesMerger) + { + m_tilesMerger->setFoV(yaw, pitch); + } +#endif +} + + +} // namespace rtc_adapter diff --git a/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoSendAdapter.h b/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoSendAdapter.h new file mode 100644 index 00000000..03125fb5 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/rtc_frame/src/VideoSendAdapter.h @@ -0,0 +1,110 @@ +// Copyright (C) <2019> Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef RTC_ADAPTER_VIDEO_SEND_ADAPTER_ +#define RTC_ADAPTER_VIDEO_SEND_ADAPTER_ + +#include +#include + +#include +#include +#include + +#include "MediaFramePipeline.h" +#include "SsrcGenerator.h" +#include "WebRTCTaskRunner.h" + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef _ENABLE_HEVC_TILES_MERGER_ +#include "../../libwebrtc360/src/WebRTC360HEVCTilesMerger.h" +#endif + +namespace rtc_adapter { + +class VideoSendAdapterImpl : public VideoSendAdapter, + public webrtc::Transport, + public webrtc::RtcpIntraFrameObserver, + public webrtc::RtcpFOVObserver { +public: + VideoSendAdapterImpl(CallOwner* owner, const RtcAdapter::Config& config); + ~VideoSendAdapterImpl(); + + // Implement VideoSendAdapter + void onFrame(const owt_base::Frame&) override; + int onRtcpData(char* data, int len) override; + void reset() override; + + uint32_t ssrc() { return m_ssrc; } + + // Implement webrtc::Transport + bool SendRtp(const uint8_t* packet, + size_t length, + const webrtc::PacketOptions& options) override; + bool SendRtcp(const uint8_t* packet, size_t length) override; + + // Implements webrtc::RtcpIntraFrameObserver. + void OnReceivedIntraFrameRequest(uint32_t ssrc); + void OnReceivedSLI(uint32_t ssrc, uint8_t picture_id) {} + void OnReceivedRPSI(uint32_t ssrc, uint64_t picture_id) {} + void OnLocalSsrcChanged(uint32_t old_ssrc, uint32_t new_ssrc) {} + + // Implements webrtc::RtcpFOVObserver. + void OnReceivedFOVFeedback(webrtc::RtcpFOVInfo &rtcp_fov_info); + + void setFoV(int32_t yaw, int32_t pitch); + +private: + bool init(); + + bool m_enableDump; + RtcAdapter::Config m_config; + + bool m_keyFrameArrived; + std::unique_ptr m_retransmissionRateLimiter; + // boost::scoped_ptr m_bitrateController; + boost::scoped_ptr m_bandwidthObserver; + std::unique_ptr m_rtpRtcp; + boost::shared_mutex m_rtpRtcpMutex; + + boost::shared_ptr m_videoTransport; + boost::shared_ptr m_taskRunner; + owt_base::FrameFormat m_frameFormat; + uint16_t m_frameWidth; + uint16_t m_frameHeight; + webrtc::Random m_random; + uint32_t m_ssrc; + owt_base::SsrcGenerator* const m_ssrcGenerator; + + boost::shared_mutex m_transportMutex; + + webrtc::Clock* m_clock; + int64_t m_timeStampOffset; + + std::unique_ptr m_eventLog; + std::unique_ptr m_senderVideo; + std::unique_ptr m_playoutDelayOracle; + std::unique_ptr m_fieldTrialConfig; + + // Listeners + AdapterFeedbackListener* m_feedbackListener; + AdapterDataListener* m_rtpListener; + AdapterStatsListener* m_statsListener; + +#ifdef _ENABLE_HEVC_TILES_MERGER_ + boost::shared_ptr m_tilesMerger; +#endif + +}; +} + +#endif /* RTC_ADAPTER_VIDEO_SEND_ADAPTER_ */ diff --git a/WebRTC-Sample/owt-server/source/rtc_frame/src/binding.gyp b/WebRTC-Sample/owt-server/source/rtc_frame/src/binding.gyp new file mode 100644 index 00000000..46cc8937 --- /dev/null +++ b/WebRTC-Sample/owt-server/source/rtc_frame/src/binding.gyp @@ -0,0 +1,160 @@ +{ + 'variables': { + 'owt_root': '../../../third_party/owt-server', + 'owt_root_abs': '