├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── debian ├── changelog ├── compat ├── control ├── copyright ├── gaeguli-adaptor-demo.install ├── gaeguli-adaptor-demo.postinst ├── gaeguli-tools.install ├── libgaeguli-dev.install ├── libgaeguli-test-common-dev.install ├── libgaeguli-test-common2.install ├── libgaeguli2.install ├── rules └── source │ └── format ├── doc ├── StreamAdaptor.dia ├── meson.build └── reference │ ├── gaeguli-docs.xml │ └── meson.build ├── docker ├── Dockerfile.16.04 ├── Dockerfile.18.04 ├── Dockerfile.19.04 └── build-gaeguli.sh ├── gaeguli ├── adaptors │ ├── bandwidthadaptor.c │ ├── bandwidthadaptor.h │ ├── nulladaptor.c │ └── nulladaptor.h ├── enumtypes.c.template ├── enumtypes.h.template ├── gaeguli-internal.h ├── gaeguli.h ├── meson.build ├── pipeline.c ├── pipeline.h ├── streamadaptor.c ├── streamadaptor.h ├── target.c ├── target.h ├── types.c └── types.h ├── hooks └── pre-commit.hook ├── meson.build ├── meson_options.txt ├── tests ├── adaptor-demo │ ├── adaptor-demo.c │ ├── adaptor-demo.h │ ├── http-server.c │ ├── http-server.h │ ├── main.c │ ├── meson.build │ ├── resources │ │ ├── adaptor-demo.gresources.xml │ │ ├── adaptor-demo.js │ │ ├── index.html │ │ ├── meson.build │ │ └── style.css │ ├── tc-helper.c │ ├── tc_helper_post_install.sh │ ├── traffic-control.c │ └── traffic-control.h ├── common │ └── gaeguli │ │ └── test │ │ ├── meson.build │ │ ├── receiver.c │ │ └── receiver.h ├── meson.build ├── test-adaptor.c ├── test-pipeline.c ├── test-rtp-over-srt.c └── test-target.c └── tools ├── meson.build └── pipeline.c /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | name: Build Test 14 | runs-on: ubuntu-20.04 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Add H8L ppa 18 | run: | 19 | sudo apt-get install --assume-yes software-properties-common 20 | sudo add-apt-repository ppa:hwangsaeul/nightly 21 | sudo apt-get update 22 | 23 | - name: Install package dependencies 24 | run: | 25 | sudo apt-get install --assume-yes \ 26 | libgstreamer1.0-dev libgstreamer-plugins-bad1.0-dev \ 27 | gstreamer1.0-libav \ 28 | gstreamer1.0-plugins-good \ 29 | gstreamer1.0-plugins-bad \ 30 | gstreamer1.0-plugins-bad-dbg \ 31 | gstreamer1.0-plugins-ugly \ 32 | libsrt-dev \ 33 | libjson-glib-dev \ 34 | libsoup2.4-dev \ 35 | meson ninja-build 36 | - name: Meson Build 37 | run: | 38 | meson build 39 | - uses: actions/upload-artifact@v1 40 | if: failure() 41 | with: 42 | name: Build 43 | path: build/meson-logs/meson-log.txt 44 | - name: Meson Test 45 | run: | 46 | meson test -C build --setup debug --no-stdsplit 47 | - uses: actions/upload-artifact@v1 48 | if: failure() 49 | with: 50 | name: Test 51 | path: build/meson-logs/testlog-debug.txt 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # Mac OS X 55 | .DS_Store 56 | 57 | # build 58 | build/ 59 | 60 | # Debian packaging temporaries 61 | /debian/.debhelper/ 62 | /debian/debhelper-build-stamp 63 | /debian/files 64 | /debian/gaeguli-tools/ 65 | /debian/libgaeguli*/ 66 | /debian/tmp/ 67 | /debian/*.substvars 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://github.com/hwangsaeul/gaeguli/workflows/CI/badge.svg?branch=master) 2 | 3 | # Gaeguli 4 | *[gæguli]* is a library for video streaming using SRT that provides strong security and ultra-low latency. 5 | 6 | ## Overview 7 | *Gaeguli* implements the supporting library for handling SRT streaming. It provides 'pipeline' component 8 | that deals with receiving frames from a video source and streaming to a specific URI using SRT protocol. 9 | 10 | ## Build from sources 11 | To build the from sources follow the procedure described in 12 | 13 | [Build from sources](https://github.com/hwangsaeul/hwangsaeul.github.io/blob/master/build_from_sources.md) 14 | 15 | *Gaeguli* requires GStreamer 1.16 or newer. 16 | 17 | ## Usage examples 18 | 19 | *Gaeguli* includes an executable binary `pipeline-2.0` which serves as a demonstration of basic streaming 20 | scenarios. For full capabilities of `libgaeguli` please refer to the API documentation and for coding example 21 | see `tools/pipeline.c`. 22 | 23 | ### Stream in SRT caller mode on port 8888 24 | 25 | Sender: 26 | 27 | ```console 28 | pipeline-2.0 -d /dev/video0 srt://127.0.0.1:8888 29 | ``` 30 | 31 | Receiver: 32 | 33 | ```console 34 | gst-launch-1.0 srtsrc uri=srt://:8888 ! queue ! decodebin ! autovideosink 35 | ``` 36 | 37 | Note that a `queue` is required right after `srtsrc`. Otherwise, you will see that the time on the receiving side gradually slows down. 38 | 39 | ### Stream in SRT listener mode on port 8888 40 | 41 | Sender: 42 | 43 | ```console 44 | pipeline-2.0 -d /dev/video0 srt://:8888 45 | ``` 46 | 47 | Receiver (several independent connections at a time are possible): 48 | 49 | ```console 50 | gst-launch-1.0 srtsrc uri=srt://127.0.0.1:8888 ! queue ! decodebin ! autovideosink 51 | ``` 52 | 53 | ## PPA nightly builds 54 | 55 | Experimental versions of Gaeguli are daily generated in [launchpad](https://launchpad.net/~hwangsaeul/+archive/ubuntu/nightly). 56 | 57 | ```console 58 | $ sudo add-apt-repository ppa:hwangsaeul/nightly 59 | $ sudo apt-get update 60 | $ sudo apt-get install libgaeguli2 libgaeguli-dev gaeguli-tools 61 | ``` 62 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | gaeguli (1.99.0-0h8l2) UNRELEASED; urgency=medium 2 | 3 | * Development Package 4 | 5 | -- Justin Kim Wed, 22 Jan 2020 20:21:36 +0900 6 | 7 | gaeguli (0.10.0-0h8l0) unstable; urgency=medium 8 | 9 | [ Jakub Adam ] 10 | * Initial release. 11 | 12 | -- Justin Kim Fri, 10 Jan 2020 21:53:22 +0900 13 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: gaeguli 2 | Priority: optional 3 | Maintainer: Jakub Adam 4 | Build-Depends: 5 | debhelper (>= 10~), 6 | gstreamer1.0-plugins-bad, 7 | gstreamer1.0-plugins-good, 8 | gstreamer1.0-plugins-ugly, 9 | libgstreamer-plugins-bad1.0-dev, 10 | libgstreamer1.0-dev, 11 | libjson-glib-dev, 12 | libnl-route-3-dev, 13 | libsoup2.4-dev, 14 | libsrt-dev (>= 1.4.1~) | libsrt-gnutls-dev | libsrt-openssl-dev, 15 | meson, 16 | Standards-Version: 4.2.1 17 | Section: libs 18 | Homepage: https://github.com/hwangsaeul/gaeguli 19 | Vcs-Browser: https://github.com/hwangsaeul/gaeguli 20 | Vcs-Git: https://github.com/hwangsaeul/gaeguli.git 21 | 22 | Package: gaeguli-adaptor-demo 23 | Architecture: any 24 | Depends: 25 | libgaeguli2 (= ${binary:Version}), 26 | ${misc:Depends}, 27 | ${shlibs:Depends}, 28 | Description: Network adaptive streaming demo 29 | An application demonstrating network adaptive streaming capabilities of Gæguli. 30 | 31 | Package: gaeguli-tools 32 | Architecture: any 33 | Section: utils 34 | Depends: 35 | libgaeguli2 (= ${binary:Version}), 36 | ${misc:Depends}, 37 | ${shlibs:Depends}, 38 | Description: Ultra-low latency SRT streamer 39 | Gæguli is an SRT streamer designed for edge devices that require 40 | strong security and ultra-low latency. 41 | 42 | Package: libgaeguli-dev 43 | Section: libdevel 44 | Architecture: any 45 | Depends: 46 | libgaeguli2 (= ${binary:Version}), 47 | libgstreamer-plugins-bad1.0-dev, 48 | libsrt-dev (>= 1.4.1~) | libsrt-gnutls-dev | libsrt-openssl-dev, 49 | ${misc:Depends}, 50 | Description: Ultra-low latency SRT streamer 51 | Gæguli is an SRT streamer designed for edge devices that require 52 | strong security and ultra-low latency. 53 | . 54 | This package contains development files for gaeguli. 55 | 56 | Package: libgaeguli2 57 | Architecture: any 58 | Depends: 59 | gstreamer1.0-plugins-bad, 60 | gstreamer1.0-plugins-good, 61 | gstreamer1.0-plugins-ugly, 62 | ${misc:Depends}, 63 | ${shlibs:Depends}, 64 | Description: Ultra-low latency SRT streamer 65 | Gæguli is an SRT streamer designed for edge devices that require 66 | strong security and ultra-low latency. 67 | 68 | Package: libgaeguli-test-common2 69 | Architecture: any 70 | Depends: 71 | libgaeguli2 (= ${binary:Version}), 72 | ${misc:Depends}, 73 | ${shlibs:Depends}, 74 | Description: Gaeguli test library 75 | A library of ancillary methods for writing tests that involve Gaeguli. 76 | 77 | Package: libgaeguli-test-common-dev 78 | Section: libdevel 79 | Architecture: any 80 | Depends: 81 | libgaeguli-test-common2 (= ${binary:Version}), 82 | ${misc:Depends}, 83 | ${shlibs:Depends}, 84 | Description: Gaeguli test library (development files) 85 | A library of ancillary methods for writing tests that involve Gaeguli. 86 | . 87 | This package contains development files for the library. 88 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: gaeguli 3 | Source: https://github.com/hwangsaeul/gaeguli 4 | 5 | Files: * 6 | Copyright: 2019 SK Telecom, Co., Ltd. 7 | License: Apache 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | . 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | . 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | . 20 | On Debian systems, the full text of the Apache License, Version 2.0 21 | can be found in the file `/usr/share/common-licenses/Apache-2.0'. 22 | -------------------------------------------------------------------------------- /debian/gaeguli-adaptor-demo.install: -------------------------------------------------------------------------------- 1 | usr/bin/gaeguli-adaptor-demo 2 | usr/libexec/gaeguli-tc-helper 3 | -------------------------------------------------------------------------------- /debian/gaeguli-adaptor-demo.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | case "$1" in 6 | configure) 7 | if ! setcap cap_net_admin+ep /usr/libexec/gaeguli-tc-helper; then 8 | echo "WARNING: 'setcap cap_net_admin+ep /usr/libexec/gaeguli-tc-helper' failed." 1>&2 9 | fi 10 | ;; 11 | esac 12 | 13 | #DEBHELPER# 14 | -------------------------------------------------------------------------------- /debian/gaeguli-tools.install: -------------------------------------------------------------------------------- 1 | usr/bin/pipeline* 2 | -------------------------------------------------------------------------------- /debian/libgaeguli-dev.install: -------------------------------------------------------------------------------- 1 | usr/include/gaeguli-2.0/gaeguli/*.h 2 | usr/lib/*/libgaeguli-2.0.so 3 | usr/lib/*/pkgconfig/gaeguli-2.0.pc 4 | -------------------------------------------------------------------------------- /debian/libgaeguli-test-common-dev.install: -------------------------------------------------------------------------------- 1 | usr/include/*/gaeguli/test 2 | usr/lib/*/libgaeguli-test-common*.so 3 | usr/lib/*/pkgconfig/gaeguli-test-common-2.0.pc 4 | -------------------------------------------------------------------------------- /debian/libgaeguli-test-common2.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/libgaeguli-test-common*.so.* 2 | -------------------------------------------------------------------------------- /debian/libgaeguli2.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/libgaeguli-2.0.so.* 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DEB_BUILD_OPTIONS=nocheck 4 | 5 | %: 6 | dh $@ 7 | 8 | override_dh_auto_configure: 9 | meson debian/build --prefix=/usr 10 | 11 | override_dh_auto_build: 12 | ninja -C debian/build 13 | 14 | override_dh_auto_test: 15 | ifeq ($(filter nocheck,$(DEB_BUILD_OPTIONS)),) 16 | ninja -C debian/build test 17 | endif 18 | 19 | override_dh_auto_install: 20 | DESTDIR=${CURDIR}/debian/tmp ninja -C debian/build install 21 | 22 | override_dh_auto_clean: 23 | rm -rf debian/build 24 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /doc/StreamAdaptor.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwangsaeul/gaeguli/110822e039888b2fcc8a400e6fd20a88dd956fa2/doc/StreamAdaptor.dia -------------------------------------------------------------------------------- /doc/meson.build: -------------------------------------------------------------------------------- 1 | if get_option('gtk_doc') 2 | subdir('reference') 3 | endif 4 | -------------------------------------------------------------------------------- /doc/reference/gaeguli-docs.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | ]> 7 | 8 | 9 | 10 | Gaeguli API reference 11 | 12 | 13 | 14 | Gaeguli APIs 15 | This sections shows the available APIs for Gaeguli. 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /doc/reference/meson.build: -------------------------------------------------------------------------------- 1 | gnome.gtkdoc('gaeguli', 2 | src_dir : gaeguli_doc_incs, 3 | main_xml : 'gaeguli-docs.xml', 4 | dependencies: [ ], 5 | install : true, 6 | ) 7 | -------------------------------------------------------------------------------- /docker/Dockerfile.16.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | 5 | RUN apt-get update && \ 6 | apt-get install --assume-yes software-properties-common && \ 7 | add-apt-repository ppa:hwangsaeul/ppa && \ 8 | apt-get update && \ 9 | apt-get install --assume-yes -t xenial-backports debhelper meson && \ 10 | apt-get install --assume-yes libgstreamer1.0-dev libgstreamer-plugins-bad1.0-dev \ 11 | libsrt-dev gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \ 12 | gstreamer1.0-plugins-ugly git 13 | 14 | COPY build-gaeguli.sh /usr/local/bin 15 | ENTRYPOINT ["/usr/local/bin/build-gaeguli.sh"] 16 | -------------------------------------------------------------------------------- /docker/Dockerfile.18.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | 5 | RUN apt-get update && \ 6 | apt-get install --assume-yes software-properties-common && \ 7 | add-apt-repository ppa:hwangsaeul/ppa && \ 8 | apt-get update && \ 9 | apt-get install --assume-yes debhelper meson libgstreamer1.0-dev libgstreamer-plugins-bad1.0-dev \ 10 | libsrt-dev gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \ 11 | gstreamer1.0-plugins-ugly git 12 | 13 | COPY build-gaeguli.sh /usr/local/bin 14 | ENTRYPOINT ["/usr/local/bin/build-gaeguli.sh"] 15 | -------------------------------------------------------------------------------- /docker/Dockerfile.19.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:19.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | 5 | RUN apt-get update && \ 6 | apt-get install --assume-yes software-properties-common && \ 7 | add-apt-repository ppa:hwangsaeul/ppa && \ 8 | apt-get update && \ 9 | apt-get install --assume-yes debhelper meson libgstreamer1.0-dev libgstreamer-plugins-bad1.0-dev \ 10 | libsrt-dev gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \ 11 | gstreamer1.0-plugins-ugly git 12 | 13 | COPY build-gaeguli.sh /usr/local/bin 14 | ENTRYPOINT ["/usr/local/bin/build-gaeguli.sh"] 15 | -------------------------------------------------------------------------------- /docker/build-gaeguli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd /tmp 4 | 5 | git clone https://github.com/hwangsaeul/gaeguli.git 6 | cd gaeguli 7 | dpkg-buildpackage -us -uc 8 | cd .. 9 | cp *.buildinfo *.deb *.tar.* *.changes *.dsc *.ddeb /mnt 10 | -------------------------------------------------------------------------------- /gaeguli/adaptors/bandwidthadaptor.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #include 20 | 21 | struct _GaeguliBandwidthStreamAdaptor 22 | { 23 | GaeguliStreamAdaptor parent; 24 | 25 | guint current_bitrate; 26 | gint64 settling_time; 27 | }; 28 | 29 | /* *INDENT-OFF* */ 30 | G_DEFINE_TYPE (GaeguliBandwidthStreamAdaptor, gaeguli_bandwidth_stream_adaptor, 31 | GAEGULI_TYPE_STREAM_ADAPTOR) 32 | /* *INDENT-ON* */ 33 | 34 | GaeguliStreamAdaptor * 35 | gaeguli_bandwidth_stream_adaptor_new (GstElement * srtsink, 36 | GstStructure * baseline_parameters) 37 | { 38 | g_return_val_if_fail (srtsink != NULL, NULL); 39 | 40 | return g_object_new (GAEGULI_TYPE_BANDWIDTH_STREAM_ADAPTOR, 41 | "srtsink", srtsink, "baseline-parameters", baseline_parameters, NULL); 42 | } 43 | 44 | static void 45 | gaeguli_bandwidth_adaptor_on_enabled (GaeguliStreamAdaptor * adaptor) 46 | { 47 | GaeguliBandwidthStreamAdaptor *self = 48 | GAEGULI_BANDWIDTH_STREAM_ADAPTOR (adaptor); 49 | 50 | /* Bandwidth adaptor operates only in constant bitrate mode. */ 51 | gaeguli_stream_adaptor_signal_encoding_parameters (adaptor, 52 | GAEGULI_ENCODING_PARAMETER_RATECTRL, 53 | GAEGULI_TYPE_VIDEO_BITRATE_CONTROL, GAEGULI_VIDEO_BITRATE_CONTROL_CBR, 54 | NULL); 55 | 56 | if (!gaeguli_stream_adaptor_get_baseline_parameter_uint (adaptor, 57 | GAEGULI_ENCODING_PARAMETER_BITRATE, &self->current_bitrate)) { 58 | g_warning ("Couldn't read baseline bitrate"); 59 | } 60 | } 61 | 62 | static void 63 | gaeguli_bandwidth_adaptor_on_stats (GaeguliStreamAdaptor * adaptor, 64 | GstStructure * stats) 65 | { 66 | GaeguliBandwidthStreamAdaptor *self = 67 | GAEGULI_BANDWIDTH_STREAM_ADAPTOR (adaptor); 68 | 69 | gdouble srt_bandwidth; 70 | 71 | if (self->current_bitrate == 0) { 72 | if (!gaeguli_stream_adaptor_get_baseline_parameter_uint 73 | (GAEGULI_STREAM_ADAPTOR (self), GAEGULI_ENCODING_PARAMETER_BITRATE, 74 | &self->current_bitrate)) { 75 | g_warning ("Couldn't read baseline bitrate"); 76 | } 77 | } 78 | 79 | if (gst_structure_has_field (stats, "callers")) { 80 | GValueArray *array; 81 | 82 | array = g_value_get_boxed (gst_structure_get_value (stats, "callers")); 83 | stats = g_value_get_boxed (&array->values[array->n_values - 1]); 84 | } 85 | 86 | if (gst_structure_get_double (stats, "bandwidth-mbps", &srt_bandwidth)) { 87 | gint new_bitrate = self->current_bitrate; 88 | 89 | /* Convert to bits per second */ 90 | srt_bandwidth *= 1e6; 91 | 92 | if (srt_bandwidth < self->current_bitrate) { 93 | new_bitrate = srt_bandwidth * 1.2; 94 | } else if (srt_bandwidth > self->current_bitrate) { 95 | guint baseline_bitrate = G_MAXUINT; 96 | 97 | gaeguli_stream_adaptor_get_baseline_parameter_uint (adaptor, 98 | GAEGULI_ENCODING_PARAMETER_BITRATE, &baseline_bitrate); 99 | 100 | if (srt_bandwidth > baseline_bitrate) { 101 | if (self->settling_time < g_get_monotonic_time ()) { 102 | new_bitrate *= 1.05; 103 | self->settling_time = g_get_monotonic_time () + 1e6; 104 | } 105 | } else { 106 | new_bitrate = srt_bandwidth * 1.2; 107 | } 108 | 109 | new_bitrate = MIN (new_bitrate, baseline_bitrate); 110 | } 111 | 112 | if (self->current_bitrate != new_bitrate) { 113 | g_debug ("Changing bitrate from %u to %u", self->current_bitrate, 114 | new_bitrate); 115 | 116 | self->current_bitrate = new_bitrate; 117 | 118 | gaeguli_stream_adaptor_signal_encoding_parameters (adaptor, 119 | GAEGULI_ENCODING_PARAMETER_BITRATE, G_TYPE_UINT, 120 | self->current_bitrate, NULL); 121 | } 122 | } 123 | } 124 | 125 | static void 126 | gaeguli_bandwidth_adaptor_on_baseline_update (GaeguliStreamAdaptor * adaptor, 127 | GstStructure * baseline_params) 128 | { 129 | GaeguliBandwidthStreamAdaptor *self = 130 | GAEGULI_BANDWIDTH_STREAM_ADAPTOR (adaptor); 131 | 132 | guint new_bitrate; 133 | 134 | if (!baseline_params) { 135 | return; 136 | } 137 | 138 | gst_structure_get_uint (baseline_params, GAEGULI_ENCODING_PARAMETER_BITRATE, 139 | &new_bitrate); 140 | 141 | if (new_bitrate < self->current_bitrate || self->current_bitrate == 0) { 142 | self->current_bitrate = new_bitrate; 143 | 144 | if (gaeguli_stream_adaptor_is_enabled (adaptor)) { 145 | gaeguli_stream_adaptor_signal_encoding_parameters (adaptor, 146 | GAEGULI_ENCODING_PARAMETER_BITRATE, G_TYPE_UINT, 147 | self->current_bitrate, NULL); 148 | } 149 | } 150 | } 151 | 152 | static void 153 | gaeguli_bandwidth_stream_adaptor_init (GaeguliBandwidthStreamAdaptor * self) 154 | { 155 | } 156 | 157 | static void 158 | gaeguli_bandwidth_stream_adaptor_constructed (GObject * object) 159 | { 160 | GaeguliBandwidthStreamAdaptor *self = 161 | GAEGULI_BANDWIDTH_STREAM_ADAPTOR (object); 162 | 163 | if (!gaeguli_stream_adaptor_get_baseline_parameter_uint 164 | (GAEGULI_STREAM_ADAPTOR (self), GAEGULI_ENCODING_PARAMETER_BITRATE, 165 | &self->current_bitrate)) { 166 | g_warning ("Couldn't read initial bitrate"); 167 | } 168 | } 169 | 170 | static void 171 | gaeguli_bandwidth_stream_adaptor_class_init (GaeguliBandwidthStreamAdaptorClass 172 | * klass) 173 | { 174 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 175 | GaeguliStreamAdaptorClass *streamadaptor_class = 176 | GAEGULI_STREAM_ADAPTOR_CLASS (klass); 177 | 178 | gobject_class->constructed = gaeguli_bandwidth_stream_adaptor_constructed; 179 | streamadaptor_class->on_enabled = gaeguli_bandwidth_adaptor_on_enabled; 180 | streamadaptor_class->on_stats = gaeguli_bandwidth_adaptor_on_stats; 181 | streamadaptor_class->on_baseline_update = 182 | gaeguli_bandwidth_adaptor_on_baseline_update; 183 | } 184 | -------------------------------------------------------------------------------- /gaeguli/adaptors/bandwidthadaptor.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #ifndef __GAEGULI_BANDWIDTH_STREAM_ADAPTOR_H__ 20 | #define __GAEGULI_BANDWIDTH_STREAM_ADAPTOR_H__ 21 | 22 | #include "gaeguli/gaeguli.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define GAEGULI_TYPE_BANDWIDTH_STREAM_ADAPTOR (gaeguli_bandwidth_stream_adaptor_get_type ()) 27 | G_DECLARE_FINAL_TYPE (GaeguliBandwidthStreamAdaptor, gaeguli_bandwidth_stream_adaptor, GAEGULI, 28 | BANDWIDTH_STREAM_ADAPTOR, GaeguliStreamAdaptor) 29 | 30 | /** 31 | * gaeguli_bandwidth_stream_adaptor_new: 32 | * @srtsink: a #GstSrtSink element to collect data from 33 | * @baseline_parameters: baseline encoding parameters 34 | * 35 | * Creates a stream adaptor that adjusts stream bitrate to the measured 36 | * bandwidth of the network connection. 37 | * 38 | * Returns: a #GaeguliStreamAdaptor instance 39 | */ 40 | GaeguliStreamAdaptor *gaeguli_bandwidth_stream_adaptor_new 41 | (GstElement *srtsink, 42 | GstStructure *baseline_parameters); 43 | 44 | G_END_DECLS 45 | 46 | #endif // __GAEGULI_BANDWIDTH_STREAM_ADAPTOR_H__ 47 | -------------------------------------------------------------------------------- /gaeguli/adaptors/nulladaptor.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | struct _GaeguliNullStreamAdaptor 23 | { 24 | GaeguliStreamAdaptor parent; 25 | }; 26 | 27 | /* *INDENT-OFF* */ 28 | G_DEFINE_TYPE (GaeguliNullStreamAdaptor, gaeguli_null_stream_adaptor, 29 | GAEGULI_TYPE_STREAM_ADAPTOR) 30 | /* *INDENT-ON* */ 31 | 32 | GaeguliStreamAdaptor * 33 | gaeguli_null_stream_adaptor_new (GstElement * srtsink) 34 | { 35 | g_return_val_if_fail (srtsink != NULL, NULL); 36 | 37 | return g_object_new (GAEGULI_TYPE_NULL_STREAM_ADAPTOR, "srtsink", srtsink, 38 | NULL); 39 | } 40 | 41 | static void 42 | gaeguli_null_stream_adaptor_init (GaeguliNullStreamAdaptor * self) 43 | { 44 | } 45 | 46 | static void 47 | gaeguli_null_stream_adaptor_class_init (GaeguliNullStreamAdaptorClass * klass) 48 | { 49 | } 50 | -------------------------------------------------------------------------------- /gaeguli/adaptors/nulladaptor.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #ifndef __GAEGULI_NULL_STREAM_ADAPTOR_H__ 20 | #define __GAEGULI_NULL_STREAM_ADAPTOR_H__ 21 | 22 | #include "gaeguli/gaeguli.h" 23 | 24 | #include 25 | 26 | G_BEGIN_DECLS 27 | 28 | #define GAEGULI_TYPE_NULL_STREAM_ADAPTOR (gaeguli_null_stream_adaptor_get_type ()) 29 | G_DECLARE_FINAL_TYPE (GaeguliNullStreamAdaptor, gaeguli_null_stream_adaptor, GAEGULI, 30 | NULL_STREAM_ADAPTOR, GaeguliStreamAdaptor) 31 | 32 | /** 33 | * gaeguli_null_stream_adaptor_new: 34 | * @srtsink: a #GstSrtSink element to collect data from 35 | * 36 | * Creates a null stream adaptor that doesn't perform any adaption. 37 | * 38 | * Returns: a #GaeguliStreamAdaptor instance 39 | */ 40 | GaeguliStreamAdaptor *gaeguli_null_stream_adaptor_new 41 | (GstElement *srtsink); 42 | 43 | G_END_DECLS 44 | 45 | #endif // __GAEGULI_NULL_STREAM_ADAPTOR_H__ 46 | -------------------------------------------------------------------------------- /gaeguli/enumtypes.c.template: -------------------------------------------------------------------------------- 1 | /*** BEGIN file-header ***/ 2 | #include "enumtypes.h" 3 | 4 | #define C_ENUM(v) ((gint) v) 5 | #define C_FLAGS(v) ((guint) v) 6 | /*** END file-header ***/ 7 | 8 | /*** BEGIN file-production ***/ 9 | 10 | /*** END file-production ***/ 11 | 12 | /*** BEGIN value-header ***/ 13 | 14 | GType 15 | @enum_name@_get_type (void) 16 | { 17 | static volatile gsize gtype_id = 0; 18 | static const G@Type@Value values[] = { 19 | /*** END value-header ***/ 20 | 21 | /*** BEGIN value-production ***/ 22 | { C_ENUM(@VALUENAME@), "@VALUENAME@", "@valuenick@" }, 23 | /*** END value-production ***/ 24 | 25 | /*** BEGIN value-tail ***/ 26 | { 0, NULL, NULL } 27 | }; 28 | if (g_once_init_enter (>ype_id)) { 29 | GType new_type = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); 30 | g_once_init_leave (>ype_id, new_type); 31 | } 32 | return (GType) gtype_id; 33 | } 34 | 35 | /*** END value-tail ***/ 36 | 37 | -------------------------------------------------------------------------------- /gaeguli/enumtypes.h.template: -------------------------------------------------------------------------------- 1 | /*** BEGIN file-header ***/ 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | G_BEGIN_DECLS 8 | /*** END file-header ***/ 9 | 10 | /*** BEGIN file-production ***/ 11 | 12 | /* enumerations from "@basename@" */ 13 | /*** END file-production ***/ 14 | 15 | /*** BEGIN value-header ***/ 16 | 17 | GAEGULI_API_EXPORT 18 | GType @enum_name@_get_type (void); 19 | #define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) 20 | /*** END value-header ***/ 21 | 22 | /*** BEGIN file-tail ***/ 23 | 24 | G_END_DECLS 25 | /*** END file-tail ***/ 26 | 27 | -------------------------------------------------------------------------------- /gaeguli/gaeguli-internal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 SK Telecom Co., Ltd. 3 | * Author: Jeongseok Kim 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #ifndef __GAEGULI_INTERNAL_H__ 20 | #define __GAEGULI_INTERNAL_H__ 21 | 22 | #define GAEGULI_PIPELINE_VSRC_STR "\ 23 | %s ! capsfilter name=pre_caps ! videorate ! capsfilter name=caps ! %s ! tee name=tee allow-not-linked=1 " 24 | 25 | #define GAEGULI_PIPELINE_IMAGE_STR "\ 26 | valve name=valve drop=1 ! jpegenc name=jpegenc ! jifmux name=jifmux ! fakesink name=fakesink async=0" 27 | 28 | #define GAEGULI_PIPELINE_GENERAL_H264ENC_STR "\ 29 | queue name=enc_first ! videoconvert ! videoscale ! capsfilter name=target_caps ! \ 30 | x264enc name=enc tune=zerolatency key-int-max=%d ! \ 31 | video/x-h264, profile=baseline ! h264parse config-interval=-1 ! queue " 32 | 33 | #define GAEGULI_PIPELINE_GENERAL_H265ENC_STR "\ 34 | queue name=enc_first ! videoconvert ! videoscale ! capsfilter name=target_caps ! \ 35 | x265enc name=enc tune=zerolatency key-int-max=%d ! \ 36 | h265parse ! queue " 37 | 38 | #define GAEGULI_PIPELINE_DECODEBIN_STR "\ 39 | decodebin name=decodebin ! videoconvert ! clockoverlay name=overlay " 40 | 41 | #define GAEGULI_PIPELINE_OMXH264ENC_STR "\ 42 | omxh264enc name=enc insert-sps-pps=true insert-vui=true control-rate=1 periodicity-idr=%d ! queue " 43 | 44 | #define GAEGULI_PIPELINE_OMXH265ENC_STR "\ 45 | omxh265enc name=enc insert-sps-pps=true insert-vui=true control-rate=1 periodicity-idr=%d ! queue " 46 | 47 | #define GAEGULI_PIPELINE_NVIDIA_TX1_H264ENC_STR "\ 48 | queue name=enc_first ! nvvidconv ! capsfilter name=target_caps ! " \ 49 | GAEGULI_PIPELINE_OMXH264ENC_STR 50 | 51 | #define GAEGULI_PIPELINE_NVIDIA_TX1_H265ENC_STR "\ 52 | queue name=enc_first ! nvvidconv ! capsfilter name=target_caps ! " \ 53 | GAEGULI_PIPELINE_OMXH265ENC_STR 54 | 55 | #define GAEGULI_PIPELINE_VAAPI_H264_STR "\ 56 | queue name=enc_first ! vaapipostproc ! capsfilter name=target_caps ! \ 57 | vaapih264enc name=enc target-percentage=100 keyframe-period=%d ! \ 58 | h264parse config-interval=-1 ! queue " 59 | 60 | #define GAEGULI_PIPELINE_VAAPI_H265_STR "\ 61 | queue name=enc_first ! vaapipostproc ! capsfilter name=target_caps ! \ 62 | vaapih265enc name=enc target-percentage=100 keyframe-period=%d ! \ 63 | h265parse config-interval=-1 ! queue " 64 | 65 | #define GAEGULI_PIPELINE_MPEGTSMUX_SINK_STR "\ 66 | mpegtsmux name=muxsink_first ! tsparse set-timestamps=1 smoothing-latency=1000 ! \ 67 | srtsink name=sink uri=%s wait-for-connection=false sync=false" 68 | 69 | #define GAEGULI_PIPELINE_RTPMUX_SINK_STR "\ 70 | rtpmux name=muxsink_first ! queue ! \ 71 | srtsink name=sink uri=%s wait-for-connection=false sync=false" 72 | 73 | #define GAEGULI_RECORD_PIPELINE_MPEGTSMUX_SINK_STR "\ 74 | mpegtsmux name=muxsink_first ! tsparse set-timestamps=1 smoothing-latency=1000 ! \ 75 | filesink name=recsink location=%s " 76 | 77 | #endif // __GAEGULI_INTERNAL_H__ 78 | -------------------------------------------------------------------------------- /gaeguli/gaeguli.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 SK Telecom Co., Ltd. 3 | * Author: Jeongseok Kim 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #ifndef __GAEGULI_H__ 20 | #define __GAEGULI_H__ 21 | 22 | #define __GAEGULI_INSIDE__ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #endif // __GAEGULI_H__ 31 | -------------------------------------------------------------------------------- /gaeguli/meson.build: -------------------------------------------------------------------------------- 1 | pkg = import('pkgconfig') 2 | 3 | gaeguli_install_header_subdir = join_paths(gaeguli_api_name, 'gaeguli') 4 | 5 | configure_file(output: 'config.h', configuration: cdata) 6 | 7 | source_h = [ 8 | 'gaeguli.h', 9 | 'target.h', 10 | 'types.h', 11 | 'pipeline.h', 12 | 'streamadaptor.h', 13 | 'adaptors/bandwidthadaptor.h', 14 | ] 15 | 16 | source_c = [ 17 | 'target.c', 18 | 'types.c', 19 | 'pipeline.c', 20 | 'streamadaptor.c', 21 | 'adaptors/nulladaptor.c', 22 | 'adaptors/bandwidthadaptor.c', 23 | ] 24 | 25 | install_headers(source_h, subdir: gaeguli_install_header_subdir) 26 | 27 | gaeguli_c_args = [ 28 | '-DG_LOG_DOMAIN="GAEGULI"', 29 | '-DGAEGULI_COMPILATION', 30 | ] 31 | 32 | gaeguli_enums = gnome.mkenums( 33 | 'enumtypes.h', 34 | h_template: 'enumtypes.h.template', 35 | c_template: 'enumtypes.c.template', 36 | sources: source_h, 37 | install_dir: join_paths(get_option('includedir'), gaeguli_install_header_subdir), 38 | install_header: true, 39 | ) 40 | 41 | gaeguli_enums_h = gaeguli_enums[1] 42 | gaeguli_enums_c = gaeguli_enums[0] 43 | 44 | libgaeguli = library( 45 | 'gaeguli-@0@'.format(apiversion), 46 | gaeguli_enums, source_c, 47 | version: libversion, 48 | soversion: soversion, 49 | include_directories: gaeguli_incs, 50 | dependencies: [ gobject_dep, gio_dep, gst_dep, libsrt_dep ], 51 | c_args: gaeguli_c_args, 52 | link_args: common_ldflags, 53 | install: true 54 | ) 55 | 56 | pkg.generate(libgaeguli, 57 | libraries : [ gst_dep, libsrt_dep ], 58 | name : meson.project_name(), 59 | description : 'A SRT Streaming library', 60 | filebase : gaeguli_api_name, 61 | subdirs: gaeguli_api_name, 62 | variables: 'exec_prefix=${prefix}' 63 | ) 64 | 65 | libgaeguli_dep = declare_dependency(link_with: libgaeguli, 66 | include_directories: [ gaeguli_incs ], 67 | dependencies: [ gobject_dep, gio_dep, gst_dep ], 68 | sources: [ gaeguli_enums_h ], 69 | ) 70 | 71 | 72 | -------------------------------------------------------------------------------- /gaeguli/pipeline.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 SK Telecom Co., Ltd. 3 | * Author: Jeongseok Kim 4 | * Jakub Adam 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #ifndef __GAEGULI_PIPELINE_H__ 21 | #define __GAEGULI_PIPELINE_H__ 22 | 23 | #if !defined(__GAEGULI_INSIDE__) && !defined(GAEGULI_COMPILATION) 24 | #error "Only can be included directly." 25 | #endif 26 | 27 | #include 28 | #include 29 | 30 | typedef struct _GaeguliTarget GaeguliTarget; 31 | 32 | /** 33 | * SECTION: pipeline 34 | * @Title: GaeguliPipeline 35 | * @Short_description: Object to read video and send it using SRT 36 | * 37 | * A #GaeguliPipeline is an object capable of receiving video from different 38 | * types of sources and streaming it using SRT protocol. 39 | */ 40 | 41 | G_BEGIN_DECLS 42 | 43 | #define GAEGULI_TYPE_PIPELINE (gaeguli_pipeline_get_type ()) 44 | G_DECLARE_FINAL_TYPE (GaeguliPipeline, gaeguli_pipeline, GAEGULI, PIPELINE, GObject) 45 | 46 | /** 47 | * gaeguli_pipeline_new: 48 | * @attributes: unified parameters 49 | 50 | * Creates a new #GaeguliPipeline object 51 | * 52 | * Returns: the newly created object 53 | */ 54 | GaeguliPipeline *gaeguli_pipeline_new (GVariant *attributes); 55 | 56 | /** 57 | * gaeguli_pipeline_new_full: 58 | * @source: the source of the video 59 | * @device: the device used as source in case of V4L 60 | * @resolution: source stream resolution 61 | * @framerate: source stream frame rate 62 | * 63 | * Creates a new #GaeguliPipeline object using specific parameters. 64 | * 65 | * Returns: the newly created object 66 | */ 67 | GaeguliPipeline *gaeguli_pipeline_new_full 68 | (GaeguliVideoSource source, 69 | const gchar *device, 70 | GaeguliVideoResolution resolution, 71 | guint framerate); 72 | /** 73 | * 74 | */ 75 | GaeguliTarget *gaeguli_pipeline_add_target_full 76 | (GaeguliPipeline *self, 77 | GVariant *attributes, 78 | GError **error); 79 | 80 | /** 81 | * gaeguli_pipeline_add_srt_target: 82 | * @self: a #GaeguliPipeline object 83 | * @uri: SRT URI 84 | * @username: SRT Stream ID User Name identifying this target 85 | * @error: a #GError 86 | * 87 | * Adds a SRT target to the pipeline. 88 | * 89 | * Returns: A #GageuliTarget. The object is owned by #GaeguliPipeline. 90 | * You should g_object_ref() it to keep the reference. 91 | */ 92 | GaeguliTarget *gaeguli_pipeline_add_srt_target 93 | (GaeguliPipeline *self, 94 | const gchar *uri, 95 | const gchar *username, 96 | GError **error); 97 | 98 | /** 99 | * gaeguli_pipeline_add_srt_target_full: 100 | * @self: a #GaeguliPipeline object 101 | * @codec: codec to use for streaming 102 | * @stream_type: video transfer method 103 | * @bitrate: bitrate use for streaming 104 | * @username: SRT Stream ID User Name identifying this target 105 | * @uri: SRT URI 106 | * @error: a #GError 107 | * 108 | * Adds a SRT target to the pipeline using specific parameters. 109 | * 110 | * Returns: A #GageuliTarget. The object is owned by #GaeguliPipeline. 111 | * You should g_object_ref() it to keep the reference. 112 | */ 113 | GaeguliTarget *gaeguli_pipeline_add_srt_target_full 114 | (GaeguliPipeline *self, 115 | GaeguliVideoCodec codec, 116 | GaeguliVideoStreamType 117 | stream_type, 118 | guint bitrate, 119 | const gchar *uri, 120 | const gchar *username, 121 | GError **error); 122 | 123 | /** 124 | * gaeguli_pipeline_add_recording_target: 125 | * @self: a #GaeguliPipeline object 126 | * @location: Recording location 127 | * @error: a #GError 128 | * 129 | * Adds a Recording target to the pipeline. 130 | * 131 | * Returns: A #GageuliTarget. The object is owned by #GaeguliPipeline. 132 | * You should g_object_ref() it to keep the reference. 133 | */ 134 | GaeguliTarget *gaeguli_pipeline_add_recording_target 135 | (GaeguliPipeline *self, 136 | const gchar *location, 137 | GError **error); 138 | 139 | /** 140 | * gaeguli_pipeline_add_recording_target_full: 141 | * @self: a #GaeguliPipeline object 142 | * @codec: codec to use for streaming 143 | * @bitrate: bitrate use for streaming 144 | * @location: Recording location 145 | * @error: a #GError 146 | * 147 | * Adds a Recording target to the pipeline using specific parameters. 148 | * 149 | * Returns: A #GageuliTarget. The object is owned by #GaeguliPipeline. 150 | * You should g_object_ref() it to keep the reference. 151 | */ 152 | GaeguliTarget *gaeguli_pipeline_add_recording_target_full 153 | (GaeguliPipeline *self, 154 | GaeguliVideoCodec codec, 155 | guint bitrate, 156 | const gchar *location, 157 | GError **error); 158 | 159 | /** 160 | * gaeguli_pipeline_remove_target: 161 | * @self: a #GaeguliPipeline object 162 | * @target: the #GaeguliTarget to remove 163 | * @error: a #GError 164 | * 165 | * Removes a specific SRT target. 166 | * 167 | * Returns: an #GaeguliReturn 168 | */ 169 | GaeguliReturn gaeguli_pipeline_remove_target 170 | (GaeguliPipeline *self, 171 | GaeguliTarget *target, 172 | GError **error); 173 | 174 | /** 175 | * gaeguli_pipeline_create_snapshot_async: 176 | * @self: a #GaeguliPipeline object 177 | * @tags: a #GVariant of type #G_VARIANT_TYPE_VARDICT with tags to insert 178 | * into the snapshot in EXIF format. 179 | * @cancellable: a #GCancellable object 180 | * @callback: a #GAsyncReadyCallback to call when the request is fulfilled 181 | * @user_data: arbitrary data passed to @callback 182 | * 183 | * Asynchronously saves a single video frame as JPEG image and passes it to 184 | * @callback. 185 | */ 186 | void gaeguli_pipeline_create_snapshot_async 187 | (GaeguliPipeline *self, 188 | GVariant *tags, 189 | GCancellable *cancellable, 190 | GAsyncReadyCallback callback, 191 | gpointer user_data); 192 | 193 | /** 194 | * gaeguli_pipeline_create_snapshot_finish: 195 | * @self: a #GaeguliPipeline object 196 | * @result: a #GAsyncResult obtained from the GAsyncReadyCallback passed to gaeguli_pipeline_create_snapshot_async() 197 | * @error: Return location for error or %NULL. 198 | * 199 | * Finishes an operation started with gaeguli_pipeline_create_snapshot_async(). 200 | * 201 | * Returns: #GBytes with JPEG image data. On error returns %NULL and sets @error. 202 | */ 203 | GBytes *gaeguli_pipeline_create_snapshot_finish 204 | (GaeguliPipeline *self, 205 | GAsyncResult *result, 206 | GError **error); 207 | 208 | /** 209 | * gaeguli_pipeline_stop: 210 | * @self: a #GaeguliPipeline object 211 | * 212 | * Stops the pipeline. 213 | */ 214 | void gaeguli_pipeline_stop (GaeguliPipeline *self); 215 | 216 | /** 217 | * gaeguli_pipeline_dump_to_dot_file: 218 | * @self: a #GaeguliPipeline object 219 | * 220 | * Dumps debug info to file. 221 | */ 222 | void gaeguli_pipeline_dump_to_dot_file 223 | (GaeguliPipeline *self); 224 | 225 | G_END_DECLS 226 | 227 | #endif // __GAEGULI_PIPELINE_H__ 228 | -------------------------------------------------------------------------------- /gaeguli/streamadaptor.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #include "streamadaptor.h" 20 | 21 | #include 22 | 23 | typedef struct 24 | { 25 | GstElement *srtsink; 26 | GstStructure *baseline_parameters; 27 | guint stats_interval; 28 | guint stats_timeout_id; 29 | gboolean stream_quality_dropped; 30 | } GaeguliStreamAdaptorPrivate; 31 | 32 | /* *INDENT-OFF* */ 33 | G_DEFINE_TYPE_WITH_PRIVATE (GaeguliStreamAdaptor, gaeguli_stream_adaptor, 34 | G_TYPE_OBJECT) 35 | /* *INDENT-ON* */ 36 | 37 | enum 38 | { 39 | PROP_SRTSINK = 1, 40 | PROP_BASELINE_PARAMETERS, 41 | PROP_STATS_INTERVAL, 42 | PROP_ENABLED, 43 | }; 44 | 45 | enum 46 | { 47 | SIG_ENCODING_PARAMETERS, 48 | SIG_STREAM_QUALITY_DROPPED, 49 | SIG_STREAM_QUALITY_REGAINED, 50 | 51 | LAST_SIGNAL 52 | }; 53 | 54 | static guint signals[LAST_SIGNAL] = { 0 }; 55 | 56 | static void 57 | gaeguli_stream_adaptor_collect_stats (GaeguliStreamAdaptor * self) 58 | { 59 | GaeguliStreamAdaptorPrivate *priv = 60 | gaeguli_stream_adaptor_get_instance_private (self); 61 | GaeguliStreamAdaptorClass *klass = GAEGULI_STREAM_ADAPTOR_GET_CLASS (self); 62 | 63 | g_autoptr (GstStructure) s = NULL; 64 | 65 | g_object_get (priv->srtsink, "stats", &s, NULL); 66 | 67 | if (gst_structure_n_fields (s) != 0) { 68 | klass->on_stats (self, s); 69 | } 70 | } 71 | 72 | gboolean 73 | _stats_collection_timeout (gpointer user_data) 74 | { 75 | gaeguli_stream_adaptor_collect_stats (user_data); 76 | 77 | return G_SOURCE_CONTINUE; 78 | } 79 | 80 | static void 81 | gaeguli_stream_adaptor_start_timer (GaeguliStreamAdaptor * self) 82 | { 83 | GaeguliStreamAdaptorPrivate *priv = 84 | gaeguli_stream_adaptor_get_instance_private (self); 85 | 86 | if (GAEGULI_STREAM_ADAPTOR_GET_CLASS (self)->on_stats) { 87 | priv->stats_timeout_id = 88 | g_timeout_add (priv->stats_interval, _stats_collection_timeout, self); 89 | } 90 | } 91 | 92 | static void 93 | gaeguli_stream_adaptor_stop_timer (GaeguliStreamAdaptor * self) 94 | { 95 | GaeguliStreamAdaptorPrivate *priv = 96 | gaeguli_stream_adaptor_get_instance_private (self); 97 | 98 | g_clear_handle_id (&priv->stats_timeout_id, g_source_remove); 99 | } 100 | 101 | static gboolean 102 | _check_bitrate_drop (const GstStructure * base_params, 103 | const GstStructure * params) 104 | { 105 | guint base_bitrate; 106 | guint cur_bitrate; 107 | 108 | if (gst_structure_has_field (base_params, GAEGULI_ENCODING_PARAMETER_BITRATE) 109 | && gst_structure_has_field (params, GAEGULI_ENCODING_PARAMETER_BITRATE)) { 110 | if (gst_structure_get_uint (base_params, GAEGULI_ENCODING_PARAMETER_BITRATE, 111 | &base_bitrate) 112 | && gst_structure_get_uint (params, GAEGULI_ENCODING_PARAMETER_BITRATE, 113 | &cur_bitrate)) { 114 | if (cur_bitrate < base_bitrate) { 115 | return TRUE; 116 | } 117 | } 118 | } 119 | return FALSE; 120 | } 121 | 122 | static gboolean 123 | _check_quality_drop (const GstStructure * base_params, 124 | const GstStructure * params) 125 | { 126 | guint base_quantizer; 127 | guint cur_quantizer; 128 | /* Higher the quantizer value, higher will be the compression at the expense of quality * 129 | * Lower the quantizer value, lower will be the compression with better quality * 130 | */ 131 | if (gst_structure_has_field (base_params, 132 | GAEGULI_ENCODING_PARAMETER_QUANTIZER) 133 | && gst_structure_has_field (params, GAEGULI_ENCODING_PARAMETER_QUANTIZER)) { 134 | if (gst_structure_get_uint (base_params, 135 | GAEGULI_ENCODING_PARAMETER_QUANTIZER, &base_quantizer) 136 | && gst_structure_get_uint (params, GAEGULI_ENCODING_PARAMETER_QUANTIZER, 137 | &cur_quantizer)) { 138 | if (cur_quantizer > base_quantizer) { 139 | return TRUE; 140 | } 141 | } 142 | } 143 | return FALSE; 144 | } 145 | 146 | static void 147 | _notify_stream_quality_changes (GaeguliStreamAdaptor 148 | * self, const GstStructure * params) 149 | { 150 | GaeguliStreamAdaptorPrivate *priv = 151 | gaeguli_stream_adaptor_get_instance_private (self); 152 | const GstStructure *base_params = 153 | gaeguli_stream_adaptor_get_baseline_parameters (self); 154 | 155 | if (!base_params || !params) 156 | return; 157 | 158 | if (_check_bitrate_drop (base_params, params) || 159 | (_check_quality_drop (base_params, params))) { 160 | if (!priv->stream_quality_dropped) { 161 | priv->stream_quality_dropped = !priv->stream_quality_dropped; 162 | g_signal_emit (self, signals[SIG_STREAM_QUALITY_DROPPED], 0, params); 163 | } 164 | } else { 165 | if (priv->stream_quality_dropped) { 166 | priv->stream_quality_dropped = !priv->stream_quality_dropped; 167 | g_signal_emit (self, signals[SIG_STREAM_QUALITY_REGAINED], 0, params); 168 | } 169 | } 170 | } 171 | 172 | static void 173 | gaeguli_stream_adaptor_signal_encoding_parameters_internal (GaeguliStreamAdaptor 174 | * self, const GstStructure * params) 175 | { 176 | _notify_stream_quality_changes (self, params); 177 | g_signal_emit (self, signals[SIG_ENCODING_PARAMETERS], 0, params); 178 | } 179 | 180 | static void 181 | gaeguli_stream_adaptor_set_stats_interval (GaeguliStreamAdaptor * self, 182 | guint ms) 183 | { 184 | GaeguliStreamAdaptorPrivate *priv = 185 | gaeguli_stream_adaptor_get_instance_private (self); 186 | 187 | priv->stats_interval = ms; 188 | 189 | if (priv->stats_timeout_id != 0) { 190 | gaeguli_stream_adaptor_stop_timer (self); 191 | gaeguli_stream_adaptor_start_timer (self); 192 | } 193 | } 194 | 195 | gboolean 196 | gaeguli_stream_adaptor_is_enabled (GaeguliStreamAdaptor * self) 197 | { 198 | GaeguliStreamAdaptorPrivate *priv = 199 | gaeguli_stream_adaptor_get_instance_private (self); 200 | 201 | return priv->stats_timeout_id != 0; 202 | } 203 | 204 | const GstStructure * 205 | gaeguli_stream_adaptor_get_baseline_parameters (GaeguliStreamAdaptor * self) 206 | { 207 | GaeguliStreamAdaptorPrivate *priv; 208 | 209 | g_return_val_if_fail (self != NULL, NULL); 210 | g_return_val_if_fail (GAEGULI_IS_STREAM_ADAPTOR (self), NULL); 211 | 212 | priv = gaeguli_stream_adaptor_get_instance_private (self); 213 | 214 | return priv->baseline_parameters; 215 | } 216 | 217 | gboolean 218 | gaeguli_stream_adaptor_get_baseline_parameter_uint (GaeguliStreamAdaptor * self, 219 | const gchar * name, guint * value) 220 | { 221 | const GstStructure *s = gaeguli_stream_adaptor_get_baseline_parameters (self); 222 | 223 | return s ? gst_structure_get_uint (s, name, value) : FALSE; 224 | } 225 | 226 | void 227 | gaeguli_stream_adaptor_signal_encoding_parameters (GaeguliStreamAdaptor * self, 228 | const gchar * param, ...) 229 | { 230 | g_autoptr (GstStructure) s = NULL; 231 | va_list varargs; 232 | 233 | va_start (varargs, param); 234 | s = gst_structure_new_valist ("application/x-gaeguli-encoding-parameters", 235 | param, varargs); 236 | va_end (varargs); 237 | 238 | gaeguli_stream_adaptor_signal_encoding_parameters_internal (self, s); 239 | } 240 | 241 | static void 242 | gaeguli_stream_adaptor_init (GaeguliStreamAdaptor * self) 243 | { 244 | } 245 | 246 | static void 247 | gaeguli_stream_adaptor_set_property (GObject * object, guint property_id, 248 | const GValue * value, GParamSpec * pspec) 249 | { 250 | GaeguliStreamAdaptor *self = GAEGULI_STREAM_ADAPTOR (object); 251 | GaeguliStreamAdaptorPrivate *priv = 252 | gaeguli_stream_adaptor_get_instance_private (self); 253 | 254 | switch (property_id) { 255 | case PROP_SRTSINK: 256 | priv->srtsink = g_value_dup_object (value); 257 | break; 258 | case PROP_BASELINE_PARAMETERS:{ 259 | GaeguliStreamAdaptorClass *klass = 260 | GAEGULI_STREAM_ADAPTOR_GET_CLASS (self); 261 | 262 | priv->baseline_parameters = g_value_dup_boxed (value); 263 | 264 | if (klass->on_baseline_update) { 265 | klass->on_baseline_update (self, priv->baseline_parameters); 266 | } 267 | break; 268 | } 269 | case PROP_STATS_INTERVAL: 270 | gaeguli_stream_adaptor_set_stats_interval (self, 271 | g_value_get_uint (value)); 272 | break; 273 | case PROP_ENABLED: 274 | if (g_value_get_boolean (value)) { 275 | GaeguliStreamAdaptorClass *klass = 276 | GAEGULI_STREAM_ADAPTOR_GET_CLASS (self); 277 | 278 | gaeguli_stream_adaptor_start_timer (self); 279 | 280 | if (klass->on_enabled) { 281 | klass->on_enabled (self); 282 | } 283 | } else if (priv->stats_timeout_id) { 284 | gaeguli_stream_adaptor_stop_timer (self); 285 | 286 | /* Revert encoder settings into their initial state. */ 287 | gaeguli_stream_adaptor_signal_encoding_parameters_internal (self, 288 | priv->baseline_parameters); 289 | } 290 | break; 291 | default: 292 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 293 | } 294 | } 295 | 296 | static void 297 | gaeguli_stream_adaptor_get_property (GObject * object, guint property_id, 298 | GValue * value, GParamSpec * pspec) 299 | { 300 | GaeguliStreamAdaptor *self = GAEGULI_STREAM_ADAPTOR (object); 301 | GaeguliStreamAdaptorPrivate *priv = 302 | gaeguli_stream_adaptor_get_instance_private (self); 303 | 304 | switch (property_id) { 305 | case PROP_SRTSINK: 306 | g_value_set_object (value, priv->srtsink); 307 | break; 308 | case PROP_BASELINE_PARAMETERS: 309 | g_value_set_boxed (value, priv->baseline_parameters); 310 | break; 311 | case PROP_STATS_INTERVAL: 312 | g_value_set_uint (value, priv->stats_interval); 313 | break; 314 | case PROP_ENABLED: 315 | g_value_set_boolean (value, gaeguli_stream_adaptor_is_enabled (self)); 316 | break; 317 | default: 318 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 319 | } 320 | } 321 | 322 | static void 323 | gaeguli_stream_adaptor_dispose (GObject * object) 324 | { 325 | GaeguliStreamAdaptor *self = GAEGULI_STREAM_ADAPTOR (object); 326 | GaeguliStreamAdaptorPrivate *priv = 327 | gaeguli_stream_adaptor_get_instance_private (self); 328 | 329 | gaeguli_stream_adaptor_stop_timer (self); 330 | gst_clear_object (&priv->srtsink); 331 | gst_clear_structure (&priv->baseline_parameters); 332 | 333 | G_OBJECT_CLASS (gaeguli_stream_adaptor_parent_class)->dispose (object); 334 | } 335 | 336 | static void 337 | gaeguli_stream_adaptor_class_init (GaeguliStreamAdaptorClass * klass) 338 | { 339 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 340 | 341 | gobject_class->set_property = gaeguli_stream_adaptor_set_property; 342 | gobject_class->get_property = gaeguli_stream_adaptor_get_property; 343 | gobject_class->dispose = gaeguli_stream_adaptor_dispose; 344 | 345 | g_object_class_install_property (gobject_class, PROP_SRTSINK, 346 | g_param_spec_object ("srtsink", "SRT sink", 347 | "SRT sink", GST_TYPE_ELEMENT, 348 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); 349 | 350 | g_object_class_install_property (gobject_class, PROP_BASELINE_PARAMETERS, 351 | g_param_spec_boxed ("baseline-parameters", 352 | "Baseline encoding parameters", "Baseline encoding parameters the " 353 | "adaptor derives its proposed modifications from", GST_TYPE_STRUCTURE, 354 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); 355 | 356 | g_object_class_install_property (gobject_class, PROP_STATS_INTERVAL, 357 | g_param_spec_uint ("stats-interval", "Statistics collection interval", 358 | "Statistics collection interval in milliseconds", 1, G_MAXUINT, 10, 359 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); 360 | 361 | g_object_class_install_property (gobject_class, PROP_ENABLED, 362 | g_param_spec_boolean ("enabled", "Turns stream adaptor on or off", 363 | "Turns stream adaptor on or off", TRUE, 364 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); 365 | 366 | signals[SIG_ENCODING_PARAMETERS] = 367 | g_signal_new ("encoding-parameters", G_TYPE_FROM_CLASS (klass), 368 | G_SIGNAL_RUN_LAST, 0, NULL, 369 | NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_STRUCTURE); 370 | 371 | signals[SIG_STREAM_QUALITY_DROPPED] = 372 | g_signal_new ("stream-quality-dropped", G_TYPE_FROM_CLASS (klass), 373 | G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); 374 | 375 | signals[SIG_STREAM_QUALITY_REGAINED] = 376 | g_signal_new ("stream-quality-regained", G_TYPE_FROM_CLASS (klass), 377 | G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); 378 | } 379 | -------------------------------------------------------------------------------- /gaeguli/streamadaptor.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #ifndef __GAEGULI_STREAM_ADAPTOR_H__ 20 | #define __GAEGULI_STREAM_ADAPTOR_H__ 21 | 22 | #if !defined(__GAEGULI_INSIDE__) && !defined(GAEGULI_COMPILATION) 23 | #error "Only can be included directly." 24 | #endif 25 | 26 | #include 27 | 28 | G_BEGIN_DECLS 29 | 30 | /* Bitrate in bits/second */ 31 | #define GAEGULI_ENCODING_PARAMETER_BITRATE "bitrate" 32 | /* Constant quantizer to apply */ 33 | #define GAEGULI_ENCODING_PARAMETER_QUANTIZER "quantizer" 34 | /* Rate control mode from GaeguliRateControlMode */ 35 | #define GAEGULI_ENCODING_PARAMETER_RATECTRL "bitrate-control" 36 | 37 | #define GAEGULI_TYPE_STREAM_ADAPTOR (gaeguli_stream_adaptor_get_type ()) 38 | G_DECLARE_DERIVABLE_TYPE (GaeguliStreamAdaptor, gaeguli_stream_adaptor, GAEGULI, 39 | STREAM_ADAPTOR, GObject) 40 | 41 | struct _GaeguliStreamAdaptorClass 42 | { 43 | GObjectClass parent_class; 44 | 45 | void (* on_enabled) (GaeguliStreamAdaptor * self); 46 | void (* on_stats) (GaeguliStreamAdaptor * self, 47 | GstStructure * stats); 48 | void (* on_baseline_update) (GaeguliStreamAdaptor * self, 49 | GstStructure * baseline_parameters); 50 | }; 51 | 52 | gboolean gaeguli_stream_adaptor_is_enabled 53 | (GaeguliStreamAdaptor *self); 54 | 55 | const GstStructure * gaeguli_stream_adaptor_get_baseline_parameters 56 | (GaeguliStreamAdaptor *self); 57 | 58 | gboolean gaeguli_stream_adaptor_get_baseline_parameter_uint 59 | (GaeguliStreamAdaptor *self, 60 | const gchar *name, 61 | guint *value); 62 | 63 | void gaeguli_stream_adaptor_signal_encoding_parameters 64 | (GaeguliStreamAdaptor *self, 65 | const gchar *param, 66 | ...) G_GNUC_NULL_TERMINATED; 67 | 68 | G_END_DECLS 69 | 70 | #endif // __GAEGULI_STREAM_ADAPTOR_H__ 71 | -------------------------------------------------------------------------------- /gaeguli/target.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019-2020 SK Telecom Co., Ltd. 3 | * Author: Jeongseok Kim 4 | * Jakub Adam 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #ifndef __GAEGULI_TARGET_H__ 21 | #define __GAEGULI_TARGET_H__ 22 | 23 | #if !defined(__GAEGULI_INSIDE__) && !defined(GAEGULI_COMPILATION) 24 | #error "Only can be included directly." 25 | #endif 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | typedef struct _GaeguliPipeline GaeguliPipeline; 32 | 33 | /** 34 | * SECTION: target 35 | * @Title: GaeguliTarget 36 | * @Short_description: A SRT stream source 37 | * 38 | * A #GaeguliTarget represents an encoded video stream available on a defined 39 | * UDP port for consumption by clients connecting using SRT protocol. 40 | */ 41 | 42 | G_BEGIN_DECLS 43 | 44 | #define GAEGULI_TYPE_TARGET (gaeguli_target_get_type ()) 45 | G_DECLARE_FINAL_TYPE (GaeguliTarget, gaeguli_target, GAEGULI, TARGET, GObject) 46 | 47 | struct _GaeguliTarget 48 | { 49 | GObject parent; 50 | 51 | guint id; 52 | GstElement *pipeline; 53 | }; 54 | 55 | GaeguliTarget *gaeguli_target_new_full (GstPad *peer_pad, 56 | guint id, 57 | GVariant *attributes, 58 | GError **error); 59 | 60 | 61 | GaeguliTarget *gaeguli_target_new (GstPad *peer_pad, 62 | guint id, 63 | GaeguliVideoCodec codec, 64 | GaeguliVideoStreamType 65 | stream_type, 66 | guint bitrate, 67 | guint idr_period, 68 | const gchar *srt_uri, 69 | const gchar *username, 70 | gboolean is_record_target, 71 | const gchar *location, 72 | GError **error); 73 | 74 | void gaeguli_target_start (GaeguliTarget *self, 75 | GError **error); 76 | 77 | void gaeguli_target_unlink (GaeguliTarget *self); 78 | 79 | GaeguliTargetState gaeguli_target_get_state (GaeguliTarget *self); 80 | 81 | GaeguliSRTMode gaeguli_target_get_srt_mode (GaeguliTarget *self); 82 | 83 | const gchar *gaeguli_target_get_peer_address 84 | (GaeguliTarget *self); 85 | 86 | GVariant *gaeguli_target_get_stats (GaeguliTarget *self); 87 | 88 | GaeguliStreamAdaptor *gaeguli_target_get_stream_adaptor 89 | (GaeguliTarget *self); 90 | 91 | gboolean gaeguli_target_push_text (GaeguliTarget *self, 92 | const gchar *text); 93 | 94 | G_END_DECLS 95 | 96 | #endif // __GAEGULI_TARGET_H__ 97 | -------------------------------------------------------------------------------- /gaeguli/types.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 SK Telecom Co., Ltd. 3 | * Author: Jeongseok Kim 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #include "config.h" 20 | 21 | #include "types.h" 22 | 23 | #include 24 | 25 | /* *INDENT-OFF* */ 26 | G_DEFINE_QUARK (gaeguli-resource-error-quark, gaeguli_resource_error) 27 | G_DEFINE_QUARK (gaeguli-transmit-error-quark, gaeguli_transmit_error) 28 | /* *INDENT-ON* */ 29 | -------------------------------------------------------------------------------- /gaeguli/types.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 SK Telecom Co., Ltd. 3 | * Author: Jeongseok Kim 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #ifndef __GAEGULI_TYPES_H__ 20 | #define __GAEGULI_TYPES_H__ 21 | 22 | #if !defined(__GAEGULI_INSIDE__) && !defined(GAEGULI_COMPILATION) 23 | #error "Only can be included directly." 24 | #endif 25 | 26 | #include 27 | 28 | #ifndef _GAEGULI_EXTERN 29 | #define _GAEGULI_EXTERN extern 30 | #endif 31 | 32 | /** 33 | * SECTION: types 34 | * @Title: Gaeguli Types 35 | * @Short_description: Several types used to export APIs 36 | */ 37 | #define GAEGULI_API_EXPORT _GAEGULI_EXTERN 38 | 39 | typedef enum { 40 | GAEGULI_RETURN_FAIL = -1, 41 | GAEGULI_RETURN_OK, 42 | } GaeguliReturn; 43 | 44 | typedef enum { 45 | GAEGULI_SRT_MODE_UNKNOWN = 0, 46 | GAEGULI_SRT_MODE_CALLER, 47 | GAEGULI_SRT_MODE_LISTENER, 48 | GAEGULI_SRT_MODE_RENDEZVOUS, 49 | } GaeguliSRTMode; 50 | 51 | typedef enum { 52 | GAEGULI_VIDEO_SOURCE_UNKNOWN = 0, 53 | GAEGULI_VIDEO_SOURCE_V4L2SRC, 54 | GAEGULI_VIDEO_SOURCE_AVFVIDEOSRC, 55 | GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, 56 | GAEGULI_VIDEO_SOURCE_NVARGUSCAMERASRC, 57 | } GaeguliVideoSource; 58 | 59 | typedef enum { 60 | GAEGULI_VIDEO_STREAM_TYPE_UNKNOWN = 0, 61 | GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 62 | GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS_OVER_SRT = GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 63 | GAEGULI_VIDEO_STREAM_TYPE_RTP, 64 | GAEGULI_VIDEO_STREAM_TYPE_RTP_OVER_SRT = GAEGULI_VIDEO_STREAM_TYPE_RTP, 65 | } GaeguliVideoStreamType; 66 | 67 | typedef enum { 68 | GAEGULI_VIDEO_CODEC_UNKNOWN = 0, 69 | GAEGULI_VIDEO_CODEC_H264_X264, 70 | GAEGULI_VIDEO_CODEC_H264_VAAPI, 71 | GAEGULI_VIDEO_CODEC_H264_OMX, 72 | GAEGULI_VIDEO_CODEC_H265_X265, 73 | GAEGULI_VIDEO_CODEC_H265_VAAPI, 74 | GAEGULI_VIDEO_CODEC_H265_OMX, 75 | } GaeguliVideoCodec; 76 | 77 | typedef enum { 78 | GAEGULI_VIDEO_BITRATE_CONTROL_CBR = 1, 79 | GAEGULI_VIDEO_BITRATE_CONTROL_CQP, 80 | GAEGULI_VIDEO_BITRATE_CONTROL_VBR, 81 | } GaeguliVideoBitrateControl; 82 | 83 | typedef enum { 84 | GAEGULI_VIDEO_RESOLUTION_UNKNOWN = 0, 85 | GAEGULI_VIDEO_RESOLUTION_640X480, 86 | GAEGULI_VIDEO_RESOLUTION_1280X720, 87 | GAEGULI_VIDEO_RESOLUTION_1920X1080, 88 | GAEGULI_VIDEO_RESOLUTION_3840X2160, 89 | } GaeguliVideoResolution; 90 | 91 | typedef enum { 92 | GAEGULI_SRT_KEY_LENGTH_0 = 0, 93 | GAEGULI_SRT_KEY_LENGTH_16 = 16, 94 | GAEGULI_SRT_KEY_LENGTH_24 = 24, 95 | GAEGULI_SRT_KEY_LENGTH_32 = 32 96 | } GaeguliSRTKeyLength; 97 | 98 | typedef enum { 99 | GAEGULI_TARGET_STATE_NEW, 100 | GAEGULI_TARGET_STATE_STARTING, 101 | GAEGULI_TARGET_STATE_RUNNING, 102 | GAEGULI_TARGET_STATE_STOPPING, 103 | GAEGULI_TARGET_STATE_STOPPED, 104 | GAEGULI_TARGET_STATE_ERROR 105 | } GaeguliTargetState; 106 | 107 | typedef enum { 108 | GAEGULI_IDCT_METHOD_ISLOW = 0, 109 | GAEGULI_IDCT_METHOD_IFAST = 1, 110 | GAEGULI_IDCT_METHOD_FLOAT = 2 111 | } GaeguliIDCTMethod; 112 | 113 | #define GAEGULI_RESOURCE_ERROR (gaeguli_resource_error_quark ()) 114 | GQuark gaeguli_resource_error_quark (void); 115 | 116 | typedef enum { 117 | GAEGULI_RESOURCE_ERROR_UNSUPPORTED, 118 | GAEGULI_RESOURCE_ERROR_READ, 119 | GAEGULI_RESOURCE_ERROR_WRITE, 120 | GAEGULI_RESOURCE_ERROR_RW, 121 | GAEGULI_RESOURCE_ERROR_STOPPED, 122 | } GaeguliResourceError; 123 | 124 | #define GAEGULI_TRANSMIT_ERROR (gaeguli_transmit_error_quark ()) 125 | GQuark gaeguli_transmit_error_quark (void); 126 | 127 | typedef enum { 128 | GAEGULI_TRANSMIT_ERROR_FAILED, 129 | GAEGULI_TRANSMIT_ERROR_ADDRINUSE, 130 | GAEGULI_TRANSMIT_ERROR_MISMATCHED_CODEC 131 | } GaeguliTransmitError; 132 | 133 | #endif // __GAEGULI_TYPES_H__ 134 | -------------------------------------------------------------------------------- /hooks/pre-commit.hook: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # THE FOLLOWING SETS FORTH ATTRIBUTION NOTICES FOR THIRD PARTY SOFTWARE 4 | # THAT MAY BE CONTAINED IN PORTIONS OF THIS PRODUCT 5 | # 6 | # gst-libav (http://github.com/GStreamer/gst-libav/) 7 | # 8 | # GNU Lesser General Public License v2.1 or later 9 | # 10 | 11 | # 12 | # Check that the code follows a consistant code style 13 | # 14 | # Check for existence of indent, and error out if not present. 15 | # On some *bsd systems the binary seems to be called gnunindent, 16 | # so check for that first. 17 | 18 | version=`gnuindent --version 2>/dev/null` 19 | if test "x$version" = "x"; then 20 | version=`gindent --version 2>/dev/null` 21 | if test "x$version" = "x"; then 22 | version=`indent --version 2>/dev/null` 23 | if test "x$version" = "x"; then 24 | echo "GStreamer git pre-commit hook:" 25 | echo "Did not find GNU indent, please install it before continuing." 26 | exit 1 27 | else 28 | INDENT=indent 29 | fi 30 | else 31 | INDENT=gindent 32 | fi 33 | else 34 | INDENT=gnuindent 35 | fi 36 | 37 | case `$INDENT --version` in 38 | GNU*) 39 | ;; 40 | default) 41 | echo "GStreamer git pre-commit hook:" 42 | echo "Did not find GNU indent, please install it before continuing." 43 | echo "(Found $INDENT, but it doesn't seem to be GNU indent)" 44 | exit 1 45 | ;; 46 | esac 47 | 48 | INDENT_PARAMETERS="--braces-on-if-line \ 49 | --case-brace-indentation0 \ 50 | --case-indentation2 \ 51 | --braces-after-struct-decl-line \ 52 | --line-length80 \ 53 | --no-tabs \ 54 | --cuddle-else \ 55 | --dont-line-up-parentheses \ 56 | --continuation-indentation4 \ 57 | --honour-newlines \ 58 | --tab-size8 \ 59 | --indent-level2 \ 60 | --leave-preprocessor-space" 61 | 62 | echo "--Checking style--" 63 | for file in `git diff-index --cached --name-only HEAD --diff-filter=ACMR| grep "\.c$"` ; do 64 | # nf is the temporary checkout. This makes sure we check against the 65 | # revision in the index (and not the checked out version). 66 | nf=`git checkout-index --temp ${file} | cut -f 1` 67 | newfile=`mktemp /tmp/${nf}.XXXXXX` || exit 1 68 | $INDENT ${INDENT_PARAMETERS} \ 69 | $nf -o $newfile 2>> /dev/null 70 | # FIXME: Call indent twice as it tends to do line-breaks 71 | # different for every second call. 72 | $INDENT ${INDENT_PARAMETERS} \ 73 | $newfile 2>> /dev/null 74 | diff -u -p "${nf}" "${newfile}" 75 | r=$? 76 | rm "${newfile}" 77 | rm "${nf}" 78 | if [ $r != 0 ] ; then 79 | echo "=================================================================================================" 80 | echo " Code style error in: $file " 81 | echo " " 82 | echo " Please fix before committing. Don't forget to run git add before trying to commit again. " 83 | echo " If the whole file is to be committed, this should work (run from the top-level directory): " 84 | echo " " 85 | echo " gst-indent $file; git add $file; git commit" 86 | echo " " 87 | echo "=================================================================================================" 88 | exit 1 89 | fi 90 | done 91 | echo "--Checking style pass--" 92 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('gaeguli', 'c', 2 | version: '1.99.0', 3 | license: 'Apache-2.0', 4 | meson_version: '>= 0.49.0', 5 | default_options: [ 'warning_level=1', 6 | 'buildtype=debugoptimized', 7 | 'c_std=gnu89', 8 | 'stdsplit=false' ]) 9 | 10 | project_version = meson.project_version() 11 | version_arr = project_version.split('.') 12 | project_version_major = version_arr[0].to_int() 13 | project_version_minor = version_arr[1].to_int() 14 | project_version_micro = version_arr[2].to_int() 15 | 16 | apiversion = '2.0' 17 | soversion = 0 18 | curversion = project_version_minor * 100 + project_version_micro 19 | libversion = '@0@.@1@.0'.format(soversion, curversion) 20 | gaeguli_api_name = '@0@-@1@'.format(meson.project_name(), apiversion) 21 | 22 | prefix = get_option('prefix') 23 | 24 | cc = meson.get_compiler('c') 25 | 26 | add_project_arguments('-Wdeclaration-after-statement', language: 'c') 27 | 28 | cdata = configuration_data() 29 | cdata.set_quoted('PACKAGE_STRING', meson.project_name()) 30 | cdata.set_quoted('PACKAGE_NAME', meson.project_name()) 31 | cdata.set_quoted('PACKAGE', meson.project_name()) 32 | cdata.set_quoted('VERSION', meson.project_version()) 33 | 34 | host_system = host_machine.system() 35 | if host_system == 'linux' 36 | cdata.set('DEFAULT_VIDEO_SOURCE', 'GAEGULI_VIDEO_SOURCE_V4L2SRC') 37 | cdata.set('DEFAULT_VIDEO_RESOLUTION', 'GAEGULI_VIDEO_RESOLUTION_1920X1080') 38 | cdata.set_quoted('DEFAULT_VIDEO_SOURCE_DEVICE', '/dev/video0') 39 | elif ['darwin'].contains(host_system) 40 | cdata.set('DEFAULT_VIDEO_SOURCE', 'GAEGULI_VIDEO_SOURCE_AVFVIDEOSRC') 41 | cdata.set('DEFAULT_VIDEO_RESOLUTION', 'GAEGULI_VIDEO_RESOLUTION_640X480') 42 | cdata.set_quoted('DEFAULT_VIDEO_SOURCE_DEVICE', '') 43 | cdata.set('HAVE_OSX', 1) 44 | else 45 | cdata.set('DEFAULT_VIDEOSRC', 'GAEGULI_VIDEO_SOURCE_UNKNOWN') 46 | cdata.set('DEFAULT_VIDEO_RESOLUTION', 'GAEGULI_VIDEO_RESOLUTION_UNKNOWN') 47 | cdata.set_quoted('DEFAULT_VIDEO_SOURCE_DEVICE', '') 48 | endif 49 | 50 | cdata.set('DEFAULT_VIDEO_FRAMERATE', 15) 51 | cdata.set('DEFAULT_VIDEO_BITRATE', 1000000) 52 | cdata.set('DEFAULT_VIDEO_CODEC', 'GAEGULI_VIDEO_CODEC_H264_X264') 53 | 54 | cdata.set('_GAEGULI_EXTERN', '__attribute__((visibility("default"))) extern') 55 | 56 | configure_file(output : 'config.h', configuration : cdata) 57 | 58 | # Dependencies 59 | glib_req_version = '>= 2.44.0' 60 | 61 | glib_dep = dependency('glib-2.0', version: glib_req_version, 62 | fallback: ['glib', 'libglib_dep']) 63 | gio_dep = [dependency('gio-2.0', version: glib_req_version, 64 | fallback: ['glib', 'libgio_dep']), 65 | dependency('gio-unix-2.0', version: glib_req_version, 66 | fallback: ['glib', 'libgio_dep'])] 67 | gobject_dep = dependency('gobject-2.0', version: glib_req_version, 68 | fallback: ['glib', 'libgobject_dep']) 69 | 70 | gst_req_version = '>= 1.16.0' 71 | 72 | gst_dep = [ 73 | dependency('gstreamer-1.0', version: gst_req_version, 74 | fallback: ['gstreamer', 'gst_dep']), 75 | dependency ('gstreamer-base-1.0', version: gst_req_version, 76 | fallback: ['gstreamer', 'gst_base_dep']), 77 | dependency ('gstreamer-app-1.0', version: gst_req_version, 78 | fallback: ['gst-plugins-base', 'gst_app_dep']), 79 | dependency ('gstreamer-net-1.0', version: gst_req_version, 80 | fallback: ['gstreamer', 'gst_net_dep']), 81 | dependency ('gstreamer-mpegts-1.0', version: gst_req_version, 82 | fallback: ['gst-plugins-bad', 'gstmpegts_dep']), 83 | dependency ('gstreamer-codecparsers-1.0', version: gst_req_version, 84 | fallback: ['gst-plugins-bad', 'gstcodecparsers_dep']) 85 | ] 86 | 87 | libsrt_dep = dependency('srt', version: '>=1.4.1') 88 | 89 | gnome = import('gnome') 90 | 91 | gaeguli_incs = include_directories('.', 'gaeguli') 92 | gaeguli_doc_incs = include_directories('gaeguli') 93 | 94 | common_ldflags = [ ] 95 | 96 | subdir('gaeguli') 97 | subdir('tests') 98 | subdir('tools') 99 | subdir('doc') 100 | 101 | python3 = import('python').find_installation() 102 | run_command(python3, '-c', 103 | 'import shutil; shutil.copy("hooks/pre-commit.hook", ".git/hooks/pre-commit")') 104 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('gtk_doc', 2 | type: 'boolean', 3 | value: false, 4 | description: 'generate API reference', 5 | ) -------------------------------------------------------------------------------- /tests/adaptor-demo/adaptor-demo.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "adaptor-demo.h" 23 | #include "http-server.h" 24 | #include "traffic-control.h" 25 | 26 | struct _GaeguliAdaptorDemo 27 | { 28 | GObject parent; 29 | 30 | GaeguliPipeline *pipeline; 31 | GaeguliTarget *target; 32 | GaeguliTrafficControl *traffic_control; 33 | GaeguliHttpServer *http_server; 34 | gchar *device; 35 | gchar *srt_uri; 36 | guint stats_timeout_id; 37 | }; 38 | 39 | /* *INDENT-OFF* */ 40 | G_DEFINE_TYPE (GaeguliAdaptorDemo, gaeguli_adaptor_demo, G_TYPE_OBJECT) 41 | /* *INDENT-ON* */ 42 | 43 | enum 44 | { 45 | PROP_DEVICE = 1, 46 | }; 47 | 48 | GaeguliAdaptorDemo * 49 | gaeguli_adaptor_demo_new (const gchar * v4l2_device) 50 | { 51 | return GAEGULI_ADAPTOR_DEMO (g_object_new (GAEGULI_TYPE_ADAPTOR_DEMO, 52 | "device", v4l2_device, NULL)); 53 | } 54 | 55 | gchar * 56 | gaeguli_adaptor_demo_get_control_uri (GaeguliAdaptorDemo * self) 57 | { 58 | return gaeguli_http_server_get_uri (self->http_server); 59 | } 60 | 61 | static void 62 | gaeguli_adaptor_demo_on_property_changed (GaeguliAdaptorDemo * self, 63 | GParamSpec * pspec) 64 | { 65 | GValue value = G_VALUE_INIT; 66 | 67 | g_object_get_property (G_OBJECT (self->target), pspec->name, &value); 68 | 69 | gaeguli_http_server_send_property (self->http_server, pspec->name, &value); 70 | 71 | g_value_unset (&value); 72 | } 73 | 74 | gboolean 75 | gaeguli_adaptor_demo_process_stats (GaeguliAdaptorDemo * self) 76 | { 77 | g_autoptr (GVariant) variant = NULL; 78 | GVariantDict d; 79 | 80 | gint64 packets_sent = 0; 81 | gint packets_sent_lost = 0; 82 | gdouble bandwidth_mbps = 0; 83 | gdouble send_rate_mbps = 0; 84 | 85 | variant = gaeguli_target_get_stats (self->target); 86 | 87 | g_variant_dict_init (&d, variant); 88 | 89 | if (g_variant_dict_contains (&d, "callers")) { 90 | GVariant *array; 91 | GVariant *caller_variant; 92 | 93 | array = g_variant_dict_lookup_value (&d, "callers", G_VARIANT_TYPE_ARRAY); 94 | /* Use the last caller in the array for display. */ 95 | caller_variant = g_variant_get_child_value (array, 96 | g_variant_n_children (array) - 1); 97 | 98 | g_variant_dict_clear (&d); 99 | g_variant_dict_init (&d, caller_variant); 100 | } 101 | 102 | g_variant_dict_lookup (&d, "packets-sent", "x", &packets_sent); 103 | g_variant_dict_lookup (&d, "packets-sent-lost", "i", &packets_sent_lost); 104 | g_variant_dict_lookup (&d, "send-rate-mbps", "d", &send_rate_mbps); 105 | g_variant_dict_lookup (&d, "bandwidth-mbps", "d", &bandwidth_mbps); 106 | 107 | g_variant_dict_clear (&d); 108 | 109 | gaeguli_http_server_send_property_uint (self->http_server, 110 | "srt-packets-sent", packets_sent); 111 | gaeguli_http_server_send_property_uint (self->http_server, 112 | "srt-packets-sent-lost", packets_sent_lost); 113 | gaeguli_http_server_send_property_uint (self->http_server, 114 | "srt-send-rate", send_rate_mbps * 1e6); 115 | gaeguli_http_server_send_property_uint (self->http_server, 116 | "srt-bandwidth", bandwidth_mbps * 1e6); 117 | 118 | return G_SOURCE_CONTINUE; 119 | } 120 | 121 | static void 122 | gaeguli_adaptor_demo_on_msg_stream (GaeguliAdaptorDemo * self, JsonObject * msg) 123 | { 124 | g_autoptr (GError) error = NULL; 125 | GValue val = G_VALUE_INIT; 126 | 127 | if (json_object_get_boolean_member (msg, "state")) { 128 | if (!self->target) { 129 | static const char *property_names[] = { 130 | "bitrate", "bitrate-actual", "quantizer", "quantizer-actual", 131 | "bitrate-control", "bitrate-control-actual", "adaptive-streaming" 132 | }; 133 | GValue property_values[G_N_ELEMENTS (property_names)] = { 0 }; 134 | guint notify_signal_id = g_signal_lookup ("notify", GAEGULI_TYPE_TARGET); 135 | GaeguliVideoCodec codec = GAEGULI_VIDEO_CODEC_H264_X264; 136 | const gchar *codec_str; 137 | gint i; 138 | 139 | codec_str = json_object_get_string_member (msg, "codec"); 140 | if (!codec_str) { 141 | codec = GAEGULI_VIDEO_CODEC_H264_X264; 142 | } else if (g_str_equal (codec_str, "x265enc")) { 143 | codec = GAEGULI_VIDEO_CODEC_H265_X265; 144 | } else if (g_str_equal (codec_str, "vaapih264enc")) { 145 | codec = GAEGULI_VIDEO_CODEC_H264_VAAPI; 146 | } else if (g_str_equal (codec_str, "vaapih265enc")) { 147 | codec = GAEGULI_VIDEO_CODEC_H265_VAAPI; 148 | } 149 | 150 | self->target = gaeguli_pipeline_add_srt_target_full (self->pipeline, 151 | codec, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 2048000, 152 | "srt://:7001?mode=listener", NULL, &error); 153 | 154 | if (error) { 155 | g_printerr ("Unable to add SRT target: %s\n", error->message); 156 | g_clear_error (&error); 157 | } 158 | 159 | gaeguli_target_start (self->target, &error); 160 | 161 | if (error) { 162 | g_printerr ("Unable to start SRT target: %s\n", error->message); 163 | g_clear_error (&error); 164 | } 165 | 166 | gaeguli_http_server_send_property_string (self->http_server, "srt-uri", 167 | self->srt_uri); 168 | 169 | g_object_getv (G_OBJECT (self->target), G_N_ELEMENTS (property_names), 170 | property_names, property_values); 171 | 172 | for (i = 0; i != G_N_ELEMENTS (property_names); ++i) { 173 | gaeguli_http_server_send_property (self->http_server, property_names[i], 174 | &property_values[i]); 175 | 176 | g_signal_connect_closure_by_id (self->target, notify_signal_id, 177 | g_quark_from_static_string (property_names[i]), 178 | g_cclosure_new_swap 179 | (G_CALLBACK (gaeguli_adaptor_demo_on_property_changed), self, 180 | NULL), FALSE); 181 | } 182 | 183 | self->stats_timeout_id = g_timeout_add (500, 184 | (GSourceFunc) gaeguli_adaptor_demo_process_stats, self); 185 | 186 | // TODO: select the right network interface 187 | self->traffic_control = gaeguli_traffic_control_new ("lo"); 188 | 189 | g_object_get_property (G_OBJECT (self->traffic_control), "enabled", &val); 190 | gaeguli_http_server_send_property (self->http_server, "tc-enabled", &val); 191 | g_value_unset (&val); 192 | g_object_get_property (G_OBJECT (self->traffic_control), "bandwidth", 193 | &val); 194 | gaeguli_http_server_send_property (self->http_server, 195 | "tc-bandwidth", &val); 196 | g_value_unset (&val); 197 | } 198 | } else { 199 | if (self->target) { 200 | g_clear_handle_id (&self->stats_timeout_id, g_source_remove); 201 | gaeguli_pipeline_remove_target (self->pipeline, self->target, &error); 202 | if (error) { 203 | g_printerr ("Unable to remove SRT target: %s\n", error->message); 204 | } 205 | self->target = NULL; 206 | 207 | gaeguli_http_server_send_property_string (self->http_server, "srt-uri", 208 | ""); 209 | } 210 | } 211 | } 212 | 213 | static void 214 | gaeguli_adaptor_demo_on_msg_property (GaeguliAdaptorDemo * self, 215 | JsonObject * msg) 216 | { 217 | const gchar *name = json_object_get_string_member (msg, "name"); 218 | GObject *receiver = NULL; 219 | GValue value = G_VALUE_INIT; 220 | g_autoptr (GError) error = NULL; 221 | 222 | if (g_str_equal (name, "bitrate") || g_str_equal (name, "quantizer") || 223 | g_str_equal (name, "adaptive-streaming")) { 224 | receiver = G_OBJECT (self->target); 225 | } else if (g_str_equal (name, "bitrate-control")) { 226 | g_autoptr (GEnumClass) enum_class = 227 | g_type_class_ref (GAEGULI_TYPE_VIDEO_BITRATE_CONTROL); 228 | 229 | g_value_init (&value, GAEGULI_TYPE_VIDEO_BITRATE_CONTROL); 230 | g_value_set_enum (&value, g_enum_get_value_by_nick (enum_class, 231 | json_object_get_string_member (msg, "value"))->value); 232 | 233 | receiver = G_OBJECT (self->target); 234 | } else if (g_str_equal (name, "tc-enabled") || 235 | g_str_equal (name, "tc-bandwidth")) { 236 | receiver = G_OBJECT (self->traffic_control); 237 | name += 3; 238 | } 239 | 240 | if (receiver) { 241 | if (!G_IS_VALUE (&value)) { 242 | json_node_get_value (json_object_get_member (msg, "value"), &value); 243 | } 244 | 245 | g_object_set_property (receiver, name, &value); 246 | } 247 | 248 | g_value_unset (&value); 249 | } 250 | 251 | static void 252 | gaeguli_adaptor_demo_init (GaeguliAdaptorDemo * self) 253 | { 254 | } 255 | 256 | static void 257 | gaeguli_adaptor_demo_constructed (GObject * object) 258 | { 259 | GaeguliAdaptorDemo *self = GAEGULI_ADAPTOR_DEMO (object); 260 | 261 | g_autoptr (GResolver) resolver = g_resolver_get_default (); 262 | g_autolist (GInetAddress) addresses = NULL; 263 | g_autoptr (GError) error = NULL; 264 | 265 | self->pipeline = gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_V4L2SRC, 266 | self->device, GAEGULI_VIDEO_RESOLUTION_1920X1080, 24); 267 | self->http_server = gaeguli_http_server_new (); 268 | 269 | g_object_set (self->pipeline, "stream-adaptor", 270 | GAEGULI_TYPE_BANDWIDTH_STREAM_ADAPTOR, NULL); 271 | 272 | addresses = g_resolver_lookup_by_name (resolver, g_get_host_name (), NULL, 273 | &error); 274 | if (error) { 275 | g_printerr ("Unable to resolve local host IP"); 276 | } else { 277 | g_autofree gchar *ip = g_inet_address_to_string (addresses->data); 278 | self->srt_uri = g_strdup_printf ("srt://%s:7001", ip); 279 | } 280 | 281 | g_signal_connect_swapped (self->http_server, "message::stream", 282 | G_CALLBACK (gaeguli_adaptor_demo_on_msg_stream), self); 283 | g_signal_connect_swapped (self->http_server, "message::property", 284 | G_CALLBACK (gaeguli_adaptor_demo_on_msg_property), self); 285 | } 286 | 287 | static void 288 | gaeguli_adaptor_demo_set_property (GObject * object, guint prop_id, 289 | const GValue * value, GParamSpec * pspec) 290 | { 291 | GaeguliAdaptorDemo *self = GAEGULI_ADAPTOR_DEMO (object); 292 | 293 | switch (prop_id) { 294 | case PROP_DEVICE: 295 | self->device = g_value_dup_string (value); 296 | break; 297 | default: 298 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 299 | break; 300 | } 301 | } 302 | 303 | static void 304 | gaeguli_adaptor_demo_dispose (GObject * object) 305 | { 306 | GaeguliAdaptorDemo *self = GAEGULI_ADAPTOR_DEMO (object); 307 | 308 | g_clear_handle_id (&self->stats_timeout_id, g_source_remove); 309 | g_clear_object (&self->http_server); 310 | 311 | gaeguli_pipeline_stop (self->pipeline); 312 | g_clear_object (&self->pipeline); 313 | g_clear_pointer (&self->device, g_free); 314 | g_clear_pointer (&self->srt_uri, g_free); 315 | } 316 | 317 | static void 318 | gaeguli_adaptor_demo_class_init (GaeguliAdaptorDemoClass * klass) 319 | { 320 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 321 | 322 | gobject_class->constructed = gaeguli_adaptor_demo_constructed; 323 | gobject_class->set_property = gaeguli_adaptor_demo_set_property; 324 | gobject_class->dispose = gaeguli_adaptor_demo_dispose; 325 | 326 | g_object_class_install_property (gobject_class, PROP_DEVICE, 327 | g_param_spec_string ("device", "V4L2 device", "V4L2 device", 328 | "/dev/video0", 329 | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); 330 | } 331 | -------------------------------------------------------------------------------- /tests/adaptor-demo/adaptor-demo.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef __GAEGULI_ADAPTOR_DEMO_H__ 19 | #define __GAEGULI_ADAPTOR_DEMO_H__ 20 | 21 | #include 22 | 23 | #define GAEGULI_TYPE_ADAPTOR_DEMO gaeguli_adaptor_demo_get_type() 24 | 25 | G_DECLARE_FINAL_TYPE (GaeguliAdaptorDemo, gaeguli_adaptor_demo, GAEGULI, 26 | ADAPTOR_DEMO, GObject) 27 | 28 | GaeguliAdaptorDemo * 29 | gaeguli_adaptor_demo_new (const gchar *v4l2_device); 30 | 31 | gchar * 32 | gaeguli_adaptor_demo_get_control_uri (GaeguliAdaptorDemo *self); 33 | 34 | #endif /* __GAEGULI_ADAPTOR_DEMO_H__ */ 35 | -------------------------------------------------------------------------------- /tests/adaptor-demo/http-server.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "http-server.h" 19 | #include "gresource-adaptor-demo.h" 20 | 21 | #include 22 | #include 23 | 24 | struct _GaeguliHttpServer 25 | { 26 | GObject parent; 27 | 28 | SoupServer *soup_server; 29 | 30 | GSList *websocket_connections; 31 | }; 32 | 33 | /* *INDENT-OFF* */ 34 | G_DEFINE_TYPE (GaeguliHttpServer, gaeguli_http_server, G_TYPE_OBJECT) 35 | /* *INDENT-ON* */ 36 | 37 | enum 38 | { 39 | SIGNAL_MESSAGE, 40 | N_SIGNALS 41 | }; 42 | 43 | guint signals[N_SIGNALS]; 44 | 45 | GaeguliHttpServer * 46 | gaeguli_http_server_new () 47 | { 48 | return GAEGULI_HTTP_SERVER (g_object_new (GAEGULI_TYPE_HTTP_SERVER, NULL)); 49 | } 50 | 51 | static void 52 | http_cb (SoupServer * server, SoupMessage * msg, const char *path, 53 | GHashTable * query, SoupClientContext * client, gpointer user_data) 54 | { 55 | GResource *res = adaptor_demo_get_resource (); 56 | GBytes *bytes; 57 | 58 | if (g_str_equal (path, "/")) { 59 | path = "/index.html"; 60 | } 61 | 62 | bytes = 63 | g_resource_lookup_data (res, path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); 64 | if (bytes) { 65 | SoupBuffer *buffer; 66 | gconstpointer data; 67 | gsize size; 68 | 69 | data = g_bytes_get_data (bytes, &size); 70 | 71 | buffer = soup_buffer_new_with_owner (data, size, bytes, 72 | (GDestroyNotify) g_bytes_unref); 73 | 74 | soup_message_body_append_buffer (msg->response_body, buffer); 75 | soup_buffer_free (buffer); 76 | 77 | if (g_str_has_suffix (path, ".js")) { 78 | soup_message_headers_append (msg->response_headers, 79 | "Content-Type", "text/javascript"); 80 | } else if (g_str_has_suffix (path, ".css")) { 81 | soup_message_headers_append (msg->response_headers, 82 | "Content-Type", "text/css"); 83 | } 84 | 85 | soup_message_set_status (msg, SOUP_STATUS_OK); 86 | } else { 87 | soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); 88 | } 89 | } 90 | 91 | static void 92 | gaeguli_http_server_handle_message (GaeguliHttpServer * server, 93 | SoupWebsocketConnection * connection, GBytes * message) 94 | { 95 | gsize length = 0; 96 | const gchar *msg_data = g_bytes_get_data (message, &length); 97 | g_autoptr (JsonParser) parser = json_parser_new (); 98 | g_autoptr (GError) error = NULL; 99 | 100 | if (json_parser_load_from_data (parser, msg_data, length, &error)) { 101 | JsonObject *msg = json_node_get_object (json_parser_get_root (parser)); 102 | 103 | if (!json_object_has_member (msg, "msg")) { 104 | // Invalid message 105 | return; 106 | } 107 | 108 | g_signal_emit (server, signals[SIGNAL_MESSAGE], 109 | g_quark_from_string (json_object_get_string_member (msg, "msg")), msg); 110 | } else { 111 | g_printerr ("Error parsing message: %s\n", error->message); 112 | } 113 | } 114 | 115 | static void 116 | message_cb (SoupWebsocketConnection * connection, gint type, GBytes * message, 117 | gpointer user_data) 118 | { 119 | gaeguli_http_server_handle_message (GAEGULI_HTTP_SERVER (user_data), 120 | connection, message); 121 | } 122 | 123 | static void 124 | gaeguli_http_server_remove_websocket_connection (GaeguliHttpServer * server, 125 | SoupWebsocketConnection * connection) 126 | { 127 | gpointer client_id = g_object_get_data (G_OBJECT (connection), "client_id"); 128 | 129 | g_print ("Connection %p closed\n", client_id); 130 | 131 | server->websocket_connections = g_slist_remove (server->websocket_connections, 132 | client_id); 133 | 134 | //g_signal_emit (server, signals [SIGNAL_WS_CLIENT_DISCONNECTED], 0, client_id); 135 | } 136 | 137 | static void 138 | gaeguli_http_server_add_websocket_connection (GaeguliHttpServer * self, 139 | SoupWebsocketConnection * connection) 140 | { 141 | g_signal_connect (connection, "message", (GCallback) message_cb, self); 142 | g_signal_connect_swapped (connection, "closed", 143 | (GCallback) gaeguli_http_server_remove_websocket_connection, self); 144 | 145 | g_object_ref (connection); 146 | 147 | g_object_set_data (G_OBJECT (connection), "client_id", connection); 148 | 149 | self->websocket_connections = 150 | g_slist_append (self->websocket_connections, connection); 151 | 152 | //g_signal_emit (server, signals [SIGNAL_WS_CLIENT_CONNECTED], 0, connection); 153 | } 154 | 155 | static void 156 | websocket_cb (SoupServer * server, SoupWebsocketConnection * connection, 157 | const char *path, SoupClientContext * client, gpointer user_data) 158 | { 159 | g_print ("New connection from %s\n", soup_client_context_get_host (client)); 160 | 161 | gaeguli_http_server_add_websocket_connection (GAEGULI_HTTP_SERVER (user_data), 162 | connection); 163 | } 164 | 165 | gchar * 166 | gaeguli_http_server_get_uri (GaeguliHttpServer * self) 167 | { 168 | g_autoslist (SoupURI) uris = soup_server_get_uris (self->soup_server); 169 | 170 | return soup_uri_to_string (uris->data, FALSE); 171 | } 172 | 173 | static void 174 | gaeguli_http_server_send_to_clients (GaeguliHttpServer * self, JsonNode * msg) 175 | { 176 | g_autofree gchar *msg_str = json_to_string (msg, TRUE); 177 | GSList *it; 178 | 179 | 180 | for (it = self->websocket_connections; it; it = it->next) { 181 | SoupWebsocketConnection *connection = it->data; 182 | SoupWebsocketState socket_state; 183 | 184 | socket_state = soup_websocket_connection_get_state (connection); 185 | 186 | if (socket_state == SOUP_WEBSOCKET_STATE_OPEN) { 187 | soup_websocket_connection_send_text (connection, msg_str); 188 | } else { 189 | g_printerr ("Trying to send message using websocket that isn't open.\n"); 190 | } 191 | } 192 | } 193 | 194 | void 195 | gaeguli_http_server_send_property_string (GaeguliHttpServer * self, 196 | const gchar * name, const gchar * value) 197 | { 198 | GValue val = G_VALUE_INIT; 199 | 200 | g_value_init (&val, G_TYPE_STRING); 201 | g_value_set_string (&val, value); 202 | gaeguli_http_server_send_property (self, name, &val); 203 | g_value_unset (&val); 204 | } 205 | 206 | void 207 | gaeguli_http_server_send_property_uint (GaeguliHttpServer * self, 208 | const gchar * name, guint value) 209 | { 210 | GValue val = G_VALUE_INIT; 211 | 212 | g_value_init (&val, G_TYPE_UINT); 213 | g_value_set_uint (&val, value); 214 | gaeguli_http_server_send_property (self, name, &val); 215 | g_value_unset (&val); 216 | } 217 | 218 | void 219 | gaeguli_http_server_send_property (GaeguliHttpServer * self, const gchar * name, 220 | GValue * value) 221 | { 222 | g_autoptr (JsonBuilder) builder = json_builder_new (); 223 | g_autoptr (JsonNode) root = NULL; 224 | 225 | json_builder_begin_object (builder); 226 | json_builder_set_member_name (builder, "msg"); 227 | json_builder_add_string_value (builder, "property"); 228 | 229 | json_builder_set_member_name (builder, "name"); 230 | json_builder_add_string_value (builder, name); 231 | 232 | json_builder_set_member_name (builder, "value"); 233 | if (G_VALUE_HOLDS_STRING (value)) { 234 | json_builder_add_string_value (builder, g_value_get_string (value)); 235 | } else if (G_VALUE_HOLDS_UINT (value)) { 236 | json_builder_add_int_value (builder, g_value_get_uint (value)); 237 | } else if (G_VALUE_HOLDS_BOOLEAN (value)) { 238 | json_builder_add_boolean_value (builder, g_value_get_boolean (value)); 239 | } else if (G_VALUE_HOLDS_ENUM (value)) { 240 | g_autoptr (GEnumClass) enum_class = g_type_class_ref (G_VALUE_TYPE (value)); 241 | 242 | const gchar *val = g_enum_get_value (enum_class, 243 | g_value_get_enum (value))->value_nick; 244 | 245 | json_builder_add_string_value (builder, val); 246 | } 247 | 248 | json_builder_end_object (builder); 249 | 250 | root = json_builder_get_root (builder); 251 | 252 | gaeguli_http_server_send_to_clients (self, root); 253 | } 254 | 255 | static void 256 | gaeguli_http_server_init (GaeguliHttpServer * self) 257 | { 258 | g_autoptr (GResolver) resolver = g_resolver_get_default (); 259 | g_autoptr (GSocketAddress) address = NULL; 260 | g_autoptr (GError) error = NULL; 261 | g_autolist (GInetAddress) addresses = NULL; 262 | 263 | self->soup_server = soup_server_new (NULL, NULL); 264 | 265 | soup_server_add_handler (self->soup_server, NULL, http_cb, self, NULL); 266 | soup_server_add_websocket_handler (self->soup_server, "/ws", NULL, NULL, 267 | websocket_cb, self, NULL); 268 | 269 | addresses = g_resolver_lookup_by_name (resolver, g_get_host_name (), NULL, 270 | &error); 271 | if (error) { 272 | goto on_error; 273 | } 274 | 275 | address = g_inet_socket_address_new (addresses->data, 8080); 276 | 277 | soup_server_listen (self->soup_server, address, 0, &error); 278 | if (error) { 279 | goto on_error; 280 | } 281 | 282 | return; 283 | 284 | on_error: 285 | g_printerr ("Unable to init HTTP server: %s", error->message); 286 | } 287 | 288 | static void 289 | gaeguli_http_server_dispose (GObject * object) 290 | { 291 | GaeguliHttpServer *self = GAEGULI_HTTP_SERVER (object); 292 | 293 | soup_server_disconnect (self->soup_server); 294 | g_clear_object (&self->soup_server); 295 | } 296 | 297 | static void 298 | gaeguli_http_server_class_init (GaeguliHttpServerClass * klass) 299 | { 300 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 301 | 302 | gobject_class->dispose = gaeguli_http_server_dispose; 303 | 304 | signals[SIGNAL_MESSAGE] = 305 | g_signal_new ("message", G_OBJECT_CLASS_TYPE (klass), 306 | G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 307 | 0, NULL, NULL, NULL, G_TYPE_NONE, 1, JSON_TYPE_OBJECT); 308 | } 309 | -------------------------------------------------------------------------------- /tests/adaptor-demo/http-server.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef __GAEGULI_HTTP_SERVER_H__ 19 | #define __GAEGULI_HTTP_SERVER_H__ 20 | 21 | #include 22 | 23 | #define GAEGULI_TYPE_HTTP_SERVER gaeguli_http_server_get_type() 24 | 25 | G_DECLARE_FINAL_TYPE (GaeguliHttpServer, gaeguli_http_server, GAEGULI, 26 | HTTP_SERVER, GObject) 27 | 28 | GaeguliHttpServer * 29 | gaeguli_http_server_new (); 30 | 31 | gchar * 32 | gaeguli_http_server_get_uri (GaeguliHttpServer * self); 33 | 34 | void 35 | gaeguli_http_server_send_property (GaeguliHttpServer * self, const gchar * name, 36 | GValue * value); 37 | 38 | void 39 | gaeguli_http_server_send_property_string (GaeguliHttpServer * self, 40 | const gchar * name, const gchar * value); 41 | 42 | void 43 | gaeguli_http_server_send_property_uint (GaeguliHttpServer * self, 44 | const gchar * name, guint value); 45 | 46 | #endif /* __GAEGULI_HTTP_SERVER_H__ */ 47 | -------------------------------------------------------------------------------- /tests/adaptor-demo/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | 20 | #include "config.h" 21 | 22 | #include "adaptor-demo.h" 23 | 24 | static gboolean 25 | sigint_handler (gpointer user_data) 26 | { 27 | g_main_loop_quit (user_data); 28 | return G_SOURCE_REMOVE; 29 | } 30 | 31 | int 32 | main (int argc, char *argv[]) 33 | { 34 | g_autoptr (GMainLoop) loop = g_main_loop_new (NULL, FALSE); 35 | g_autoptr (GaeguliAdaptorDemo) app = NULL; 36 | g_autoptr (GError) error = NULL; 37 | 38 | struct 39 | { 40 | const gchar *device; 41 | } options; 42 | g_autoptr (GOptionContext) context = NULL; 43 | GOptionEntry entries[] = { 44 | {"device", 'd', 0, G_OPTION_ARG_FILENAME, &options.device, NULL, NULL}, 45 | {NULL} 46 | }; 47 | 48 | options.device = DEFAULT_VIDEO_SOURCE_DEVICE; 49 | 50 | context = g_option_context_new (NULL); 51 | g_option_context_add_main_entries (context, entries, NULL); 52 | if (!g_option_context_parse (context, &argc, &argv, &error)) { 53 | g_printerr ("%s\n", error->message); 54 | return -1; 55 | } 56 | 57 | g_unix_signal_add (SIGINT, sigint_handler, loop); 58 | 59 | app = gaeguli_adaptor_demo_new (options.device); 60 | 61 | { 62 | g_autofree gchar *http_uri = gaeguli_adaptor_demo_get_control_uri (app); 63 | g_print ("Control panel URI: %s\n", http_uri); 64 | } 65 | 66 | g_main_loop_run (loop); 67 | 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /tests/adaptor-demo/meson.build: -------------------------------------------------------------------------------- 1 | subdir('resources') 2 | 3 | sources = [ 4 | 'adaptor-demo.c', 5 | 'http-server.c', 6 | 'main.c', 7 | 'traffic-control.c', 8 | gresources 9 | ] 10 | 11 | executable('gaeguli-adaptor-demo', sources, 12 | dependencies: [ 13 | libgaeguli_dep, 14 | dependency('libsoup-2.4'), 15 | dependency('json-glib-1.0'), 16 | ], 17 | install: true, 18 | ) 19 | 20 | tc_helper_exe_name = 'gaeguli-tc-helper' 21 | tc_helper_install_dir = get_option('libexecdir') 22 | 23 | tc_helper_exe = executable(tc_helper_exe_name, 'tc-helper.c', 24 | dependencies: [ 25 | dependency('libnl-route-3.0') 26 | ], 27 | install: true, 28 | install_dir: tc_helper_install_dir 29 | ) 30 | 31 | meson.add_install_script('tc_helper_post_install.sh', 32 | tc_helper_install_dir / tc_helper_exe_name) 33 | -------------------------------------------------------------------------------- /tests/adaptor-demo/resources/adaptor-demo.gresources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | adaptor-demo.js 5 | index.html 6 | style.css 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/adaptor-demo/resources/adaptor-demo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Client { 4 | constructor() { 5 | this.onproperty = undefined 6 | this.onopen = undefined 7 | this.onerror = undefined 8 | this.__ws = undefined 9 | } 10 | 11 | connect() { 12 | if (this.__ws) { 13 | this.__ws.close() 14 | } 15 | 16 | this.__ws = new WebSocket(`ws://${window.location.host}/ws`) 17 | 18 | this.__ws.onopen = () => { 19 | if (this.onopen) { 20 | this.onopen() 21 | } 22 | } 23 | 24 | this.__ws.onmessage = message => { 25 | var msg = JSON.parse(message.data) 26 | 27 | switch (msg.msg) { 28 | case 'property': 29 | this.onproperty(msg) 30 | break 31 | } 32 | } 33 | 34 | this.__ws.onerror = (error) => { 35 | console.log(`websocket error: ${error.message}`) 36 | if (this.onerror) { 37 | this.onerror() 38 | } 39 | } 40 | } 41 | 42 | stream(state, codec) { 43 | this.__sendRequest('stream', { state: state, codec: codec }) 44 | } 45 | 46 | property(name, value) { 47 | this.__sendRequest('property', { name: name, value: value }) 48 | } 49 | 50 | __sendRequest(type, args) { 51 | var request = Object.assign({msg: type}, args) 52 | 53 | this.__ws.send(JSON.stringify(request)) 54 | } 55 | } 56 | 57 | export class AdaptorDemo { 58 | constructor() { 59 | this.__signaling = new Client() 60 | this.__signaling.onproperty = msg => { 61 | var element = document.getElementById(msg.name) 62 | if (element.type == "checkbox") { 63 | element.checked = msg.value 64 | } else { 65 | element.value = msg.value 66 | } 67 | element.dispatchEvent(new Event ('change')) 68 | } 69 | this.__signaling.connect() 70 | } 71 | 72 | property(name, value) { 73 | this.__signaling.property(name, value) 74 | } 75 | 76 | stream(state, codec) { 77 | this.__signaling.stream(state, codec) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/adaptor-demo/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 26 | 27 |
Stream from webcam:
Codec: 19 | 25 |
28 |
29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 45 | 46 | 47 | 48 | 56 | 57 | 58 | 59 | 62 | 63 | 67 | 68 | 69 | 70 | 71 | 75 | 76 | 77 | 78 | 79 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 91 | 92 | 93 | 94 | 97 | 98 | 99 | 100 | 103 | 104 | 105 | 106 | 110 | 111 | 112 | 113 | 114 | 118 | 119 | 120 |
SRT URI: 33 | 34 |
Bitrate control: 39 | 44 |
Actual bitrate control: 49 | 55 |
Bitrate: 60 | 61 | kbps 64 | 65 | 66 |
Actual bitrate: 72 | 73 | 74 | kbps
Quantizer: 80 | 81 |
Actual quantizer: 89 | 90 |
SRT packets sent: 95 | 96 |
SRT lost packets: 101 | 102 |
SRT send rate: 107 | 108 | 109 | kbps
SRT measured bandwidth: 115 | 116 | 117 | kbps
121 | 122 | 123 | 124 | 127 | 128 |
Adaptive streaming: 125 | 126 |
129 | 130 | 131 | 132 | 135 | 136 | 137 | 138 | 141 | 142 | 146 | 147 | 148 |
Traffic control: 133 | 134 |
Bandwidth limit: 139 | 140 | kbps 143 | 144 | 145 |
149 |
150 |
151 |
152 |
153 | 154 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /tests/adaptor-demo/resources/meson.build: -------------------------------------------------------------------------------- 1 | gnome = import('gnome') 2 | 3 | gresources = gnome.compile_resources( 4 | 'gresource-adaptor-demo', 5 | 'adaptor-demo.gresources.xml' 6 | ) 7 | -------------------------------------------------------------------------------- /tests/adaptor-demo/resources/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | table { 6 | background: lightblue; 7 | margin-top: 10px; 8 | margin-bottom: 10px; 9 | } 10 | 11 | fieldset { 12 | border-width: 0px; 13 | padding: 0px; 14 | margin: 0px; 15 | } 16 | 17 | input { 18 | border: 0px; 19 | text-align: right; 20 | font-size: 14px; 21 | } 22 | 23 | input:disabled { 24 | color: blue; 25 | background: transparent; 26 | } 27 | 28 | th { 29 | text-align: left; 30 | } 31 | 32 | td.value { 33 | text-align: right; 34 | } 35 | 36 | select.displayonly { 37 | appearance: none; 38 | background: transparent; 39 | border: none; 40 | color: blue; 41 | font-size: 14px; 42 | text-align: right; 43 | } 44 | 45 | .largefont { 46 | font-size: 20px; 47 | } -------------------------------------------------------------------------------- /tests/adaptor-demo/tc-helper.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | 20 | #define HZ 100 21 | 22 | int 23 | main (int argc, char **argv) 24 | { 25 | const char *interface; 26 | int bandwidth; 27 | 28 | struct nl_sock *sock; 29 | struct rtnl_qdisc *qdisc = NULL; 30 | struct nl_cache *cache; 31 | struct rtnl_link *link; 32 | int res = 0; 33 | 34 | if (argc != 3) { 35 | fprintf (stderr, "You must give interface name and bandwidth in bits per " 36 | "second as arguments\n"); 37 | return -1; 38 | } 39 | 40 | interface = argv[1]; 41 | bandwidth = atoi (argv[2]); 42 | 43 | sock = nl_socket_alloc (); 44 | 45 | res = nl_connect (sock, NETLINK_ROUTE); 46 | if (res != 0) { 47 | fprintf (stderr, "nl_connect failed: %s\n", nl_geterror (res)); 48 | goto out; 49 | } 50 | 51 | rtnl_link_alloc_cache (sock, AF_UNSPEC, &cache); 52 | link = rtnl_link_get_by_name (cache, interface); 53 | 54 | qdisc = rtnl_qdisc_alloc (); 55 | rtnl_tc_set_ifindex (TC_CAST (qdisc), rtnl_link_get_ifindex (link)); 56 | rtnl_tc_set_parent (TC_CAST (qdisc), TC_H_ROOT); 57 | rtnl_tc_set_kind (TC_CAST (qdisc), "tbf"); 58 | 59 | rtnl_link_put (link); 60 | nl_cache_put (cache); 61 | 62 | if (bandwidth > 0) { 63 | rtnl_qdisc_tbf_set_rate (qdisc, bandwidth / 8, bandwidth, 1); 64 | rtnl_qdisc_tbf_set_limit_by_latency (qdisc, 50000); 65 | 66 | res = rtnl_qdisc_add (sock, qdisc, NLM_F_CREATE); 67 | if (res != 0) { 68 | fprintf (stderr, "rtnl_qdisc_add failed: %s\n", nl_geterror (res)); 69 | goto out; 70 | } 71 | } else { 72 | rtnl_qdisc_delete (sock, qdisc); 73 | } 74 | 75 | out: 76 | if (qdisc) { 77 | rtnl_qdisc_put (qdisc); 78 | } 79 | nl_socket_free (sock); 80 | 81 | return res; 82 | } 83 | -------------------------------------------------------------------------------- /tests/adaptor-demo/tc_helper_post_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Meson install script for gaeguli-tc-helper. Sets net_admin capability. 3 | tc_helper_install_path="$1" 4 | 5 | tc_helper="$MESON_INSTALL_DESTDIR_PREFIX/$tc_helper_install_path" 6 | 7 | echo "Calling $setcap cap_net_admin+ep $tc_helper" 8 | /sbin/setcap cap_net_admin+ep "$tc_helper" || true 9 | 10 | -------------------------------------------------------------------------------- /tests/adaptor-demo/traffic-control.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "traffic-control.h" 19 | 20 | #include 21 | 22 | struct _GaeguliTrafficControl 23 | { 24 | GObject parent; 25 | 26 | gchar *interface; 27 | guint bandwidth; 28 | gboolean enabled; 29 | }; 30 | 31 | /* *INDENT-OFF* */ 32 | G_DEFINE_TYPE (GaeguliTrafficControl, gaeguli_traffic_control, G_TYPE_OBJECT) 33 | /* *INDENT-ON* */ 34 | 35 | enum 36 | { 37 | PROP_INTERFACE = 1, 38 | PROP_BANDWIDTH, 39 | PROP_ENABLED, 40 | }; 41 | 42 | const gchar *TC_HELPER = "gaeguli-tc-helper"; 43 | 44 | GaeguliTrafficControl * 45 | gaeguli_traffic_control_new (const gchar * interface) 46 | { 47 | return GAEGULI_TRAFFIC_CONTROL (g_object_new (GAEGULI_TYPE_TRAFFIC_CONTROL, 48 | "interface", interface, NULL)); 49 | } 50 | 51 | static void 52 | gaeguli_traffic_control_update_bandwidth (GaeguliTrafficControl * self) 53 | { 54 | gchar **argv; 55 | gint exit_status; 56 | g_autoptr (GError) error = NULL; 57 | g_autofree gchar *dirname = NULL; 58 | g_autofree gchar *tc_helper_path = NULL; 59 | 60 | dirname = g_path_get_dirname (gst_get_main_executable_path ()); 61 | tc_helper_path = g_build_filename (dirname, TC_HELPER, NULL); 62 | 63 | if (!g_file_test (tc_helper_path, G_FILE_TEST_IS_EXECUTABLE)) { 64 | g_free (tc_helper_path); 65 | tc_helper_path = g_build_filename ("/usr/libexec", TC_HELPER, NULL); 66 | } 67 | argv = g_new0 (gchar *, 4); 68 | argv[0] = g_steal_pointer (&tc_helper_path); 69 | argv[1] = g_strdup (self->interface); 70 | argv[2] = g_strdup_printf ("%u", self->enabled ? self->bandwidth : 0); 71 | 72 | g_spawn_sync (NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, 73 | &exit_status, &error); 74 | if (error) { 75 | g_printerr ("Unable to spawn tc-helper: %s\n", error->message); 76 | } else if (exit_status != 0) { 77 | g_printerr ("tc-helper exited with %d\n", exit_status); 78 | } 79 | 80 | g_strfreev (argv); 81 | } 82 | 83 | static void 84 | gaeguli_traffic_control_init (GaeguliTrafficControl * self) 85 | { 86 | } 87 | 88 | static void 89 | gaeguli_traffic_control_set_property (GObject * object, guint property_id, 90 | const GValue * value, GParamSpec * pspec) 91 | { 92 | GaeguliTrafficControl *self = GAEGULI_TRAFFIC_CONTROL (object); 93 | 94 | switch (property_id) { 95 | case PROP_INTERFACE: 96 | self->interface = g_value_dup_string (value); 97 | break; 98 | case PROP_BANDWIDTH:{ 99 | guint new_bandwidth = g_value_get_uint (value); 100 | 101 | if (self->bandwidth != new_bandwidth) { 102 | self->bandwidth = new_bandwidth; 103 | gaeguli_traffic_control_update_bandwidth (self); 104 | g_object_notify_by_pspec (object, pspec); 105 | } 106 | break; 107 | } 108 | case PROP_ENABLED:{ 109 | gboolean new_enabled = g_value_get_boolean (value); 110 | 111 | if (self->enabled != new_enabled) { 112 | self->enabled = new_enabled; 113 | g_object_notify_by_pspec (object, pspec); 114 | } 115 | 116 | gaeguli_traffic_control_update_bandwidth (self); 117 | break; 118 | } 119 | default: 120 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 121 | } 122 | } 123 | 124 | static void 125 | gaeguli_traffic_control_get_property (GObject * object, guint property_id, 126 | GValue * value, GParamSpec * pspec) 127 | { 128 | GaeguliTrafficControl *self = GAEGULI_TRAFFIC_CONTROL (object); 129 | 130 | switch (property_id) { 131 | case PROP_INTERFACE: 132 | g_value_set_string (value, self->interface); 133 | break; 134 | case PROP_BANDWIDTH: 135 | g_value_set_uint (value, self->bandwidth); 136 | break; 137 | case PROP_ENABLED: 138 | g_value_set_boolean (value, self->enabled); 139 | break; 140 | default: 141 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 142 | } 143 | } 144 | 145 | static void 146 | gaeguli_traffic_control_dispose (GObject * object) 147 | { 148 | GaeguliTrafficControl *self = GAEGULI_TRAFFIC_CONTROL (object); 149 | 150 | self->enabled = FALSE; 151 | gaeguli_traffic_control_update_bandwidth (self); 152 | 153 | g_clear_pointer (&self->interface, g_free); 154 | } 155 | 156 | static void 157 | gaeguli_traffic_control_class_init (GaeguliTrafficControlClass * klass) 158 | { 159 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 160 | 161 | gobject_class->set_property = gaeguli_traffic_control_set_property; 162 | gobject_class->get_property = gaeguli_traffic_control_get_property; 163 | gobject_class->dispose = gaeguli_traffic_control_dispose; 164 | 165 | g_object_class_install_property (gobject_class, PROP_INTERFACE, 166 | g_param_spec_string ("interface", "Network interface", 167 | "Network interface", NULL, 168 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); 169 | 170 | g_object_class_install_property (gobject_class, PROP_BANDWIDTH, 171 | g_param_spec_uint ("bandwidth", "Network bandwidth limit in bits/second", 172 | "Network bandwidth limit in bits/second", 1, G_MAXUINT, 1024000, 173 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | 174 | G_PARAM_STATIC_STRINGS)); 175 | 176 | g_object_class_install_property (gobject_class, PROP_ENABLED, 177 | g_param_spec_boolean ("enabled", "Enable traffic control", 178 | "Enable traffic control", FALSE, 179 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | 180 | G_PARAM_STATIC_STRINGS)); 181 | } 182 | -------------------------------------------------------------------------------- /tests/adaptor-demo/traffic-control.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef __GAEGULI_TRAFFIC_CONTROL_H__ 19 | #define __GAEGULI_TRAFFIC_CONTROL_H__ 20 | 21 | #include 22 | 23 | #define GAEGULI_TYPE_TRAFFIC_CONTROL gaeguli_traffic_control_get_type() 24 | 25 | G_DECLARE_FINAL_TYPE (GaeguliTrafficControl, gaeguli_traffic_control, GAEGULI, 26 | TRAFFIC_CONTROL, GObject) 27 | 28 | GaeguliTrafficControl * 29 | gaeguli_traffic_control_new (const gchar *interface); 30 | 31 | #endif /* __GAEGULI_TRAFFIC_CONTROL_H__ */ 32 | -------------------------------------------------------------------------------- /tests/common/gaeguli/test/meson.build: -------------------------------------------------------------------------------- 1 | sources = [ 2 | 'receiver.c', 3 | ] 4 | 5 | headers = [ 6 | 'receiver.h', 7 | ] 8 | 9 | libgaeguli_test_common_includes = include_directories('../..') 10 | 11 | libgaeguli_test_common = library( 12 | 'gaeguli-test-common-@0@'.format(apiversion), 13 | sources, 14 | version: libversion, 15 | soversion: soversion, 16 | include_directories: libgaeguli_test_common_includes, 17 | dependencies: [ libgaeguli_dep ], 18 | install: true 19 | ) 20 | 21 | install_headers(headers, subdir: join_paths(gaeguli_install_header_subdir, 'test')) 22 | 23 | pkg.generate(libgaeguli_test_common, 24 | description : 'A SRT Video Streaming Library (test ancillaries)', 25 | ) 26 | 27 | libgaeguli_test_common_dep = declare_dependency( 28 | link_with: libgaeguli_test_common, 29 | include_directories: libgaeguli_test_common_includes 30 | ) 31 | -------------------------------------------------------------------------------- /tests/common/gaeguli/test/receiver.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "receiver.h" 19 | 20 | GstElement * 21 | gaeguli_tests_create_receiver (GaeguliSRTMode mode, guint port) 22 | { 23 | g_autoptr (GError) error = NULL; 24 | g_autoptr (GstElement) receiver = NULL; 25 | g_autofree gchar *pipeline_str = NULL; 26 | gchar *mode_str = mode == GAEGULI_SRT_MODE_CALLER ? "caller" : "listener"; 27 | 28 | pipeline_str = 29 | g_strdup_printf ("srtsrc uri=srt://127.0.0.1:%d?mode=%s name=src ! " 30 | "fakesink name=sink signal-handoffs=1", port, mode_str); 31 | 32 | receiver = gst_parse_launch (pipeline_str, &error); 33 | g_assert_no_error (error); 34 | 35 | gst_element_set_state (receiver, GST_STATE_PLAYING); 36 | 37 | return g_steal_pointer (&receiver); 38 | } 39 | 40 | void 41 | gaeguli_tests_receiver_set_handoff_callback (GstElement * receiver, 42 | GCallback callback, gpointer data) 43 | { 44 | g_autoptr (GstElement) sink = NULL; 45 | gulong handler_id; 46 | 47 | sink = gst_bin_get_by_name (GST_BIN (receiver), "sink"); 48 | 49 | /* Disconnect previous handler. */ 50 | handler_id = GPOINTER_TO_SIZE (g_object_get_data (G_OBJECT (sink), 51 | "handoff-id")); 52 | if (handler_id) { 53 | g_signal_handler_disconnect (sink, handler_id); 54 | } 55 | 56 | if (callback) { 57 | handler_id = g_signal_connect (sink, "handoff", callback, data); 58 | } else { 59 | handler_id = 0; 60 | } 61 | 62 | g_object_set_data (G_OBJECT (sink), "handoff-id", 63 | GSIZE_TO_POINTER (handler_id)); 64 | } 65 | 66 | void 67 | gaeguli_tests_receiver_set_username (GstElement * receiver, 68 | const gchar * username, const gchar * resource) 69 | { 70 | g_autoptr (GstElement) src = NULL; 71 | g_autofree gchar *streamid = NULL; 72 | 73 | gst_element_set_state (receiver, GST_STATE_READY); 74 | 75 | streamid = g_strdup_printf ("#!::u=%s,r=%s", username, resource); 76 | src = gst_bin_get_by_name (GST_BIN (receiver), "src"); 77 | g_object_set (src, "streamid", streamid, NULL); 78 | 79 | gst_element_set_state (receiver, GST_STATE_PLAYING); 80 | } 81 | 82 | void 83 | gaeguli_tests_receiver_set_passphrase (GstElement * receiver, 84 | const gchar * passphrase) 85 | { 86 | g_autoptr (GstElement) src = NULL; 87 | 88 | gst_element_set_state (receiver, GST_STATE_READY); 89 | 90 | src = gst_bin_get_by_name (GST_BIN (receiver), "src"); 91 | g_object_set (src, "passphrase", passphrase, NULL); 92 | 93 | gst_element_set_state (receiver, GST_STATE_PLAYING); 94 | } 95 | -------------------------------------------------------------------------------- /tests/common/gaeguli/test/receiver.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 SK Telecom Co., Ltd. 3 | * Author: Jakub Adam 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "gaeguli/gaeguli.h" 19 | 20 | GstElement *gaeguli_tests_create_receiver (GaeguliSRTMode mode, 21 | guint port); 22 | 23 | void gaeguli_tests_receiver_set_handoff_callback 24 | (GstElement *receiver, 25 | GCallback handoff_callback, 26 | gpointer data); 27 | 28 | void gaeguli_tests_receiver_set_username 29 | (GstElement *receiver, 30 | const gchar *username, 31 | const gchar *resource); 32 | 33 | void gaeguli_tests_receiver_set_passphrase 34 | (GstElement *receiver, 35 | const gchar *passphrase); 36 | -------------------------------------------------------------------------------- /tests/meson.build: -------------------------------------------------------------------------------- 1 | subdir('common/gaeguli/test') 2 | subdir('adaptor-demo') 3 | 4 | tests = [ 5 | 'test-pipeline', 6 | 'test-rtp-over-srt', 7 | 'test-adaptor', 8 | 'test-target', 9 | ] 10 | 11 | foreach t: tests 12 | installed_test = '@0@.test'.format(t) 13 | 14 | exe = executable( 15 | t, '@0@.c'.format(t), 16 | c_args: '-DG_LOG_DOMAIN="gaeguli-tests"', 17 | dependencies: [ libgaeguli_dep, libgaeguli_test_common_dep ], 18 | install: false, 19 | ) 20 | 21 | env = environment() 22 | env.set('G_TEST_SRCDIR', meson.current_source_dir()) 23 | env.set('G_TEST_BUILDDIR', meson.current_build_dir()) 24 | 25 | test( 26 | t, exe, 27 | args: [ '--tap', '-k' ], 28 | env: env, 29 | timeout: 120, 30 | is_parallel: false 31 | ) 32 | endforeach 33 | 34 | debugenv = environment() 35 | debugenv.set('GST_DEBUG', '3') 36 | add_test_setup('debug', env: debugenv) 37 | -------------------------------------------------------------------------------- /tests/test-adaptor.c: -------------------------------------------------------------------------------- 1 | /** 2 | * tests/test-pipeline 3 | * 4 | * Copyright 2019 SK Telecom Co., Ltd. 5 | * Author: Jeongseok Kim 6 | * Jakub Adam 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | */ 21 | 22 | #include "gaeguli/test/receiver.h" 23 | 24 | #include 25 | #include 26 | 27 | GMainLoop *loop = NULL; 28 | 29 | /* GaeguliTestAdaptor class */ 30 | 31 | #define STATS_INTERVAL_MS 10 32 | #define TEST_BITRATE1 12345678 33 | #define TEST_BITRATE2 999888 34 | #define TEST_QUANTIZATION 37 35 | 36 | #define GAEGULI_TYPE_TEST_ADAPTOR (gaeguli_test_adaptor_get_type ()) 37 | 38 | /* *INDENT-OFF* */ 39 | G_DECLARE_FINAL_TYPE (GaeguliTestAdaptor, gaeguli_test_adaptor, GAEGULI, 40 | TEST_ADAPTOR, GaeguliStreamAdaptor) 41 | /* *INDENT-ON* */ 42 | 43 | struct _GaeguliTestAdaptor 44 | { 45 | GaeguliStreamAdaptor parent; 46 | 47 | gint64 last_callback; 48 | guint callbacks_left; 49 | }; 50 | 51 | /* *INDENT-OFF* */ 52 | G_DEFINE_TYPE (GaeguliTestAdaptor, gaeguli_test_adaptor, 53 | GAEGULI_TYPE_STREAM_ADAPTOR) 54 | /* *INDENT-ON* */ 55 | 56 | static void 57 | _on_encoder_property_change (GObject * encoder, GParamSpec * pspec, 58 | gpointer user_data) 59 | { 60 | guint bitrate, quantizer; 61 | 62 | g_object_get (encoder, "bitrate", &bitrate, "quantizer", &quantizer, NULL); 63 | /* x264enc takes bitrate in kbps */ 64 | if (bitrate == TEST_BITRATE1 / 1000 && quantizer == TEST_QUANTIZATION) { 65 | g_debug ("Stopping the main loop"); 66 | g_main_loop_quit (loop); 67 | } 68 | } 69 | 70 | static void 71 | gaeguli_test_adaptor_on_stats (GaeguliStreamAdaptor * self, 72 | GstStructure * stats) 73 | { 74 | GaeguliTestAdaptor *test_adaptor = GAEGULI_TEST_ADAPTOR (self); 75 | const GstStructure *baseline_params = NULL; 76 | guint64 now = g_get_monotonic_time (); 77 | guint val = 0; 78 | g_autofree gchar *stats_str = gst_structure_to_string (stats); 79 | 80 | g_debug ("Stats callback invoked with stats structure: %s", stats_str); 81 | 82 | g_assert_nonnull (stats); 83 | g_assert_cmpstr (gst_structure_get_name (stats), ==, 84 | "application/x-srt-statistics"); 85 | 86 | g_assert_cmpint (gst_structure_n_fields (stats), >, 0); 87 | 88 | if (!gst_structure_has_field (stats, "packets-sent")) { 89 | g_debug ("Socket not connected yet; keep on waiting."); 90 | return; 91 | } 92 | 93 | baseline_params = gaeguli_stream_adaptor_get_baseline_parameters 94 | (GAEGULI_STREAM_ADAPTOR (self)); 95 | 96 | g_assert_cmpstr (gst_structure_get_name (baseline_params), ==, 97 | "application/x-gaeguli-encoding-parameters"); 98 | 99 | g_assert_true (gst_structure_has_field (baseline_params, 100 | GAEGULI_ENCODING_PARAMETER_BITRATE)); 101 | g_assert_true (gst_structure_get_uint (baseline_params, 102 | GAEGULI_ENCODING_PARAMETER_BITRATE, &val)); 103 | g_assert_cmpint (val, ==, TEST_BITRATE2); 104 | 105 | g_assert_true (gst_structure_has_field (baseline_params, 106 | GAEGULI_ENCODING_PARAMETER_QUANTIZER)); 107 | 108 | g_assert_true (gst_structure_has_field (stats, "packets-sent-lost")); 109 | g_assert_true (gst_structure_has_field (stats, "packets-retransmitted")); 110 | g_assert_true (gst_structure_has_field (stats, "send-rate-mbps")); 111 | g_assert_true (gst_structure_has_field (stats, "bandwidth-mbps")); 112 | 113 | /* Check callback invocation irregularity lies within 1/5 of STATS_INTERVAL */ 114 | if (test_adaptor->last_callback != 0) { 115 | g_assert_cmpint ((now - test_adaptor->last_callback) / 1000, >=, 116 | STATS_INTERVAL_MS - STATS_INTERVAL_MS / 5); 117 | g_assert_cmpint ((now - test_adaptor->last_callback) / 1000, <=, 118 | STATS_INTERVAL_MS + STATS_INTERVAL_MS / 5); 119 | } 120 | 121 | test_adaptor->last_callback = now; 122 | 123 | if (--test_adaptor->callbacks_left == 0) { 124 | g_autoptr (GstElement) srtsink = NULL; 125 | g_autoptr (GstElement) encoder = NULL; 126 | 127 | g_debug ("Invoking change of encoding parameters"); 128 | 129 | g_object_get (self, "srtsink", &srtsink, NULL); 130 | 131 | encoder = gst_bin_get_by_name (GST_BIN (GST_ELEMENT_PARENT (srtsink)), 132 | "enc"); 133 | g_assert_nonnull (encoder); 134 | 135 | g_signal_connect (encoder, "notify", 136 | G_CALLBACK (_on_encoder_property_change), NULL); 137 | 138 | gaeguli_stream_adaptor_signal_encoding_parameters (self, 139 | GAEGULI_ENCODING_PARAMETER_BITRATE, G_TYPE_UINT, TEST_BITRATE1, 140 | GAEGULI_ENCODING_PARAMETER_QUANTIZER, G_TYPE_UINT, TEST_QUANTIZATION, 141 | NULL); 142 | } 143 | } 144 | 145 | static void 146 | _on_encoding_parameters (GaeguliTestAdaptor * adaptor, GstStructure * params, 147 | gpointer data) 148 | { 149 | guint val = 0; 150 | 151 | g_assert_cmpstr (gst_structure_get_name (params), ==, 152 | "application/x-gaeguli-encoding-parameters"); 153 | 154 | g_assert_cmpint (gst_structure_n_fields (params), ==, 2); 155 | 156 | g_assert_true (gst_structure_has_field (params, 157 | GAEGULI_ENCODING_PARAMETER_BITRATE)); 158 | g_assert_true (gst_structure_get_uint (params, 159 | GAEGULI_ENCODING_PARAMETER_BITRATE, &val)); 160 | g_assert_cmpint (val, ==, TEST_BITRATE1); 161 | 162 | g_assert_true (gst_structure_has_field (params, 163 | GAEGULI_ENCODING_PARAMETER_QUANTIZER)); 164 | g_assert_true (gst_structure_get_uint (params, 165 | GAEGULI_ENCODING_PARAMETER_QUANTIZER, &val)); 166 | g_assert_cmpint (val, ==, TEST_QUANTIZATION); 167 | } 168 | 169 | static void 170 | gaeguli_test_adaptor_init (GaeguliTestAdaptor * self) 171 | { 172 | self->callbacks_left = 3; 173 | 174 | g_signal_connect (self, "encoding-parameters", 175 | G_CALLBACK (_on_encoding_parameters), NULL); 176 | } 177 | 178 | static void 179 | gaeguli_test_adaptor_constructed (GObject * self) 180 | { 181 | g_object_set (self, "stats-interval", STATS_INTERVAL_MS, NULL); 182 | } 183 | 184 | static void 185 | gaeguli_test_adaptor_class_init (GaeguliTestAdaptorClass * klass) 186 | { 187 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 188 | GaeguliStreamAdaptorClass *streamadaptor_class = 189 | GAEGULI_STREAM_ADAPTOR_CLASS (klass); 190 | 191 | gobject_class->constructed = gaeguli_test_adaptor_constructed; 192 | streamadaptor_class->on_stats = gaeguli_test_adaptor_on_stats; 193 | } 194 | 195 | /* *** */ 196 | 197 | #define GAEGULI_TYPE_DUMMY_SRTSINK (gaeguli_dummy_srtsink_get_type ()) 198 | 199 | /* *INDENT-OFF* */ 200 | G_DECLARE_FINAL_TYPE (GaeguliDummySrtSink, gaeguli_dummy_srtsink, GAEGULI, 201 | DUMMY_SRTSINK, GstElement) 202 | /* *INDENT-ON* */ 203 | 204 | enum 205 | { 206 | PROP_STATS = 1 207 | }; 208 | 209 | struct _GaeguliDummySrtSink 210 | { 211 | GstElement parent; 212 | 213 | GstStructure *stats; 214 | }; 215 | 216 | /* *INDENT-OFF* */ 217 | G_DEFINE_TYPE (GaeguliDummySrtSink, gaeguli_dummy_srtsink, GST_TYPE_ELEMENT) 218 | /* *INDENT-ON* */ 219 | 220 | static GaeguliDummySrtSink * 221 | gaeguli_dummy_srtsink_new () 222 | { 223 | return g_object_new (GAEGULI_TYPE_DUMMY_SRTSINK, NULL); 224 | } 225 | 226 | static void G_GNUC_NULL_TERMINATED 227 | gaeguli_dummy_srtsink_set_stats (GaeguliDummySrtSink * self, const gchar * name, 228 | ...) 229 | { 230 | va_list varargs; 231 | 232 | va_start (varargs, name); 233 | gst_structure_set_valist (self->stats, name, varargs); 234 | va_end (varargs); 235 | } 236 | 237 | static void 238 | gaeguli_dummy_srtsink_init (GaeguliDummySrtSink * self) 239 | { 240 | self->stats = gst_structure_new_empty ("application/x-srt-statistics"); 241 | } 242 | 243 | static void 244 | gaeguli_dummy_srtsink_get_property (GObject * object, guint property_id, 245 | GValue * value, GParamSpec * pspec) 246 | { 247 | GaeguliDummySrtSink *self = GAEGULI_DUMMY_SRTSINK (object); 248 | 249 | switch (property_id) { 250 | case PROP_STATS: 251 | g_value_set_boxed (value, self->stats); 252 | break; 253 | default: 254 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 255 | } 256 | } 257 | 258 | static void 259 | gaeguli_dummy_srtsink_dispose (GObject * object) 260 | { 261 | GaeguliDummySrtSink *self = GAEGULI_DUMMY_SRTSINK (object); 262 | 263 | gst_clear_structure (&self->stats); 264 | } 265 | 266 | static void 267 | gaeguli_dummy_srtsink_class_init (GaeguliDummySrtSinkClass * klass) 268 | { 269 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 270 | 271 | gobject_class->get_property = gaeguli_dummy_srtsink_get_property; 272 | gobject_class->dispose = gaeguli_dummy_srtsink_dispose; 273 | 274 | g_object_class_install_property (gobject_class, PROP_STATS, 275 | g_param_spec_boxed ("stats", "Statistics", 276 | "SRT Statistics", GST_TYPE_STRUCTURE, 277 | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); 278 | } 279 | 280 | /* *** */ 281 | 282 | static void 283 | test_gaeguli_adaptor_instance () 284 | { 285 | g_autoptr (GstElement) srtsink = gst_element_factory_make ("srtsink", NULL); 286 | g_autoptr (GaeguliStreamAdaptor) adaptor = NULL; 287 | 288 | adaptor = gaeguli_null_stream_adaptor_new (srtsink); 289 | g_assert_nonnull (adaptor); 290 | } 291 | 292 | static void 293 | test_gaeguli_adaptor_stats () 294 | { 295 | g_autoptr (GaeguliPipeline) pipeline = NULL; 296 | g_autoptr (GstElement) receiver = NULL; 297 | g_autoptr (GError) error = NULL; 298 | GaeguliTarget *target; 299 | 300 | pipeline = gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 301 | GAEGULI_VIDEO_RESOLUTION_640X480, 15); 302 | g_object_set (pipeline, "stream-adaptor", GAEGULI_TYPE_TEST_ADAPTOR, NULL); 303 | 304 | target = gaeguli_pipeline_add_srt_target_full (pipeline, 305 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 306 | TEST_BITRATE2, "srt://127.0.0.1:1111", NULL, &error); 307 | g_assert_no_error (error); 308 | 309 | gaeguli_target_start (target, &error); 310 | g_assert_no_error (error); 311 | 312 | receiver = gaeguli_tests_create_receiver (GAEGULI_SRT_MODE_LISTENER, 1111); 313 | 314 | loop = g_main_loop_new (NULL, FALSE); 315 | g_main_loop_run (loop); 316 | g_clear_pointer (&loop, g_main_loop_unref); 317 | 318 | gaeguli_pipeline_stop (pipeline); 319 | gst_element_set_state (receiver, GST_STATE_NULL); 320 | } 321 | 322 | typedef struct 323 | { 324 | GMainLoop *loop; 325 | guint expected_bitrate; 326 | gboolean params_change_triggered; 327 | } BandwidthTestData; 328 | 329 | static gboolean 330 | _quit_main_loop (GMainLoop * loop) 331 | { 332 | g_main_loop_quit (loop); 333 | 334 | return G_SOURCE_REMOVE; 335 | } 336 | 337 | static void 338 | _bandwidth_on_encoding_parameters (BandwidthTestData * data, 339 | GstStructure * params) 340 | { 341 | guint val; 342 | 343 | g_assert_true (gst_structure_get_uint (params, 344 | GAEGULI_ENCODING_PARAMETER_BITRATE, &val)); 345 | 346 | g_assert_cmpuint (val, ==, data->expected_bitrate); 347 | 348 | data->params_change_triggered = TRUE; 349 | g_main_loop_quit (data->loop); 350 | } 351 | 352 | static void 353 | test_gaeguli_adaptor_bandwidth () 354 | { 355 | g_autoptr (GMainLoop) loop = g_main_loop_new (NULL, FALSE); 356 | g_autoptr (GaeguliStreamAdaptor) adaptor = NULL; 357 | g_autoptr (GaeguliDummySrtSink) dummysrt = NULL; 358 | g_autoptr (GstStructure) initial_params = NULL; 359 | BandwidthTestData data = { 0 }; 360 | 361 | data.loop = loop; 362 | 363 | dummysrt = gaeguli_dummy_srtsink_new (); 364 | initial_params = 365 | gst_structure_new ("application/x-gaeguli-encoding-parameters", 366 | GAEGULI_ENCODING_PARAMETER_BITRATE, G_TYPE_UINT, 1000000, NULL); 367 | 368 | adaptor = gaeguli_bandwidth_stream_adaptor_new (GST_ELEMENT (dummysrt), 369 | initial_params); 370 | g_object_set (adaptor, "stats-interval", STATS_INTERVAL_MS, NULL); 371 | g_signal_connect_swapped (adaptor, "encoding-parameters", 372 | (GCallback) _bandwidth_on_encoding_parameters, &data); 373 | 374 | /* Bandwidth more than 10% higher than the default bitrate - parameter change 375 | * should not trigger. */ 376 | 377 | gaeguli_dummy_srtsink_set_stats (dummysrt, "bandwidth-mbps", G_TYPE_DOUBLE, 378 | 1.2, NULL); 379 | 380 | g_timeout_add (3 * STATS_INTERVAL_MS, (GSourceFunc) _quit_main_loop, loop); 381 | 382 | g_main_loop_run (loop); 383 | 384 | g_assert_false (data.params_change_triggered); 385 | 386 | /* Bandwidth higher than default - parameter change should not trigger. */ 387 | 388 | gaeguli_dummy_srtsink_set_stats (dummysrt, "bandwidth-mbps", G_TYPE_DOUBLE, 389 | 5.0, NULL); 390 | 391 | g_timeout_add (3 * STATS_INTERVAL_MS, (GSourceFunc) _quit_main_loop, loop); 392 | 393 | g_main_loop_run (loop); 394 | 395 | g_assert_false (data.params_change_triggered); 396 | 397 | /* Bandwidth lower than default - bitrate should change to 120% of estimated 398 | * bandwidth (because it seems libsrt estimates too low). */ 399 | 400 | gaeguli_dummy_srtsink_set_stats (dummysrt, "bandwidth-mbps", G_TYPE_DOUBLE, 401 | 0.7, NULL); 402 | data.expected_bitrate = 0.7 * 1.2 * 1e6; 403 | 404 | g_main_loop_run (loop); 405 | 406 | g_assert_true (data.params_change_triggered); 407 | 408 | /* Bandwidth going back up over default bitrate - bitrate should begin 409 | * rising in 5% increments. */ 410 | 411 | gaeguli_dummy_srtsink_set_stats (dummysrt, "bandwidth-mbps", G_TYPE_DOUBLE, 412 | 2.0, NULL); 413 | data.expected_bitrate = data.expected_bitrate * 1.05; 414 | 415 | g_main_loop_run (loop); 416 | 417 | g_assert_true (data.params_change_triggered); 418 | } 419 | 420 | int 421 | main (int argc, char *argv[]) 422 | { 423 | gst_init (&argc, &argv); 424 | 425 | g_test_init (&argc, &argv, NULL); 426 | g_test_add_func ("/gaeguli/adaptor-instance", test_gaeguli_adaptor_instance); 427 | g_test_add_func ("/gaeguli/adaptor-stats", test_gaeguli_adaptor_stats); 428 | g_test_add_func ("/gaeguli/adaptor-bandwidth", 429 | test_gaeguli_adaptor_bandwidth); 430 | 431 | return g_test_run (); 432 | } 433 | -------------------------------------------------------------------------------- /tests/test-pipeline.c: -------------------------------------------------------------------------------- 1 | /** 2 | * tests/test-pipeline 3 | * 4 | * Copyright 2019 SK Telecom Co., Ltd. 5 | * Author: Jeongseok Kim 6 | * Jakub Adam 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | */ 21 | 22 | #include 23 | #include "pipeline.h" 24 | #include "gaeguli/test/receiver.h" 25 | 26 | #define TARGET_BYTES_SENT_LIMIT 10000 27 | 28 | typedef struct _TestFixture 29 | { 30 | GMainLoop *loop; 31 | GaeguliPipeline *pipeline; 32 | GaeguliTarget *target; 33 | guint port_base; 34 | } TestFixture; 35 | 36 | static void 37 | fixture_setup (TestFixture * fixture, gconstpointer unused) 38 | { 39 | fixture->loop = g_main_loop_new (NULL, FALSE); 40 | fixture->port_base = g_random_int_range (15000, 40000); 41 | } 42 | 43 | static void 44 | fixture_teardown (TestFixture * fixture, gconstpointer unused) 45 | { 46 | g_main_loop_unref (fixture->loop); 47 | } 48 | 49 | static void 50 | _stream_started_cb (GaeguliPipeline * pipeline, GaeguliTarget * target, 51 | TestFixture * fixture) 52 | { 53 | g_autoptr (GError) error = NULL; 54 | 55 | gaeguli_pipeline_remove_target (pipeline, target, &error); 56 | } 57 | 58 | static gboolean 59 | _quit_loop (TestFixture * fixture) 60 | { 61 | g_main_loop_quit (fixture->loop); 62 | return G_SOURCE_REMOVE; 63 | } 64 | 65 | static void 66 | _stream_stopped_cb (GaeguliPipeline * pipeline, GaeguliTarget * target, 67 | TestFixture * fixture) 68 | { 69 | g_debug ("got stopped signal %x", target->id); 70 | 71 | g_timeout_add (100, (GSourceFunc) _quit_loop, fixture); 72 | } 73 | 74 | static void 75 | test_gaeguli_pipeline_instance (TestFixture * fixture, gconstpointer unused) 76 | { 77 | GaeguliTarget *target; 78 | g_autoptr (GaeguliPipeline) pipeline = 79 | gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 80 | GAEGULI_VIDEO_RESOLUTION_640X480, 30); 81 | g_autoptr (GError) error = NULL; 82 | 83 | g_signal_connect (pipeline, "stream-started", G_CALLBACK (_stream_started_cb), 84 | fixture); 85 | g_signal_connect (pipeline, "stream-stopped", G_CALLBACK (_stream_stopped_cb), 86 | fixture); 87 | 88 | target = gaeguli_pipeline_add_srt_target_full (pipeline, 89 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 2048000, 90 | "srt://127.0.0.1:1111", NULL, &error); 91 | 92 | g_assert_no_error (error); 93 | g_assert_nonnull (target); 94 | g_assert_cmpuint (target->id, !=, 0); 95 | fixture->target = target; 96 | 97 | gaeguli_target_start (target, &error); 98 | g_assert_no_error (error); 99 | 100 | g_main_loop_run (fixture->loop); 101 | 102 | gaeguli_pipeline_stop (pipeline); 103 | } 104 | 105 | typedef struct 106 | { 107 | GaeguliTarget *target; 108 | gboolean closing; 109 | GstElement *receiver_pipeline; 110 | } TargetTestData; 111 | 112 | typedef struct 113 | { 114 | GMutex lock; 115 | TestFixture *fixture; 116 | GaeguliPipeline *pipeline; 117 | TargetTestData targets[5]; 118 | guint targets_to_create; 119 | } AddRemoveTestData; 120 | 121 | static gboolean 122 | add_remove_target_cb (AddRemoveTestData * data) 123 | { 124 | gint i; 125 | 126 | g_mutex_lock (&data->lock); 127 | 128 | /* If there's a free slot, create a new fifo transmit. */ 129 | for (i = 0; data->targets_to_create && i != G_N_ELEMENTS (data->targets); ++i) { 130 | TargetTestData *target = &data->targets[i]; 131 | 132 | if (target->target == NULL && !target->closing) { 133 | g_autoptr (GError) error = NULL; 134 | g_autofree gchar *uri = NULL; 135 | 136 | target->receiver_pipeline = 137 | gaeguli_tests_create_receiver (GAEGULI_SRT_MODE_LISTENER, 138 | data->fixture->port_base + i); 139 | g_assert_nonnull (target->receiver_pipeline); 140 | 141 | uri = g_strdup_printf ("srt://127.0.0.1:%d", 142 | data->fixture->port_base + i); 143 | 144 | target->target = gaeguli_pipeline_add_srt_target_full (data->pipeline, 145 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 146 | 2048000, uri, NULL, &error); 147 | g_assert_no_error (error); 148 | 149 | gaeguli_target_start (target->target, &error); 150 | g_assert_no_error (error); 151 | 152 | g_debug ("Added target %u", target->target->id); 153 | 154 | --data->targets_to_create; 155 | break; 156 | } 157 | } 158 | 159 | /* Check that all targets are in NORMAL state and data are being read. */ 160 | for (i = 0; i != G_N_ELEMENTS (data->targets); ++i) { 161 | TargetTestData *target = &data->targets[i]; 162 | g_autoptr (GVariant) variant = NULL; 163 | guint64 bytes_sent = 0; 164 | g_autoptr (GError) error = NULL; 165 | 166 | if (target->target == NULL || target->closing) { 167 | continue; 168 | } 169 | 170 | variant = gaeguli_target_get_stats (target->target); 171 | if (variant) { 172 | GVariantDict dict; 173 | 174 | g_variant_dict_init (&dict, variant); 175 | g_variant_dict_lookup (&dict, "bytes-sent", "t", &bytes_sent); 176 | g_variant_dict_clear (&dict); 177 | } 178 | 179 | g_debug ("Target %u has sent %lu B", target->target->id, bytes_sent); 180 | 181 | /* Remove targets that have read at least FIFO_READ_LIMIT_BYTES. */ 182 | if (bytes_sent >= TARGET_BYTES_SENT_LIMIT) { 183 | /* First stop the pipeline. */ 184 | gaeguli_pipeline_remove_target (data->pipeline, target->target, &error); 185 | g_assert_no_error (error); 186 | 187 | target->closing = TRUE; 188 | /* The removal gets finished in stream_stopped_cb(). */ 189 | } 190 | } 191 | 192 | g_mutex_unlock (&data->lock); 193 | 194 | return G_SOURCE_CONTINUE; 195 | } 196 | 197 | static void 198 | stream_stopped_cb (GaeguliPipeline * pipeline, GaeguliTarget * target, 199 | AddRemoveTestData * data) 200 | { 201 | gint i; 202 | gboolean have_active_targets = FALSE; 203 | 204 | g_mutex_lock (&data->lock); 205 | 206 | for (i = 0; i != G_N_ELEMENTS (data->targets); ++i) { 207 | TargetTestData *target_data = &data->targets[i]; 208 | g_autoptr (GError) error = NULL; 209 | 210 | if (target_data->target == target) { 211 | g_assert_true (target_data->closing); 212 | 213 | gst_element_set_state (target_data->receiver_pipeline, GST_STATE_NULL); 214 | gst_clear_object (&target_data->receiver_pipeline); 215 | 216 | g_debug ("Removed target %u", target_data->target->id); 217 | 218 | target_data->target = NULL; 219 | target_data->closing = FALSE; 220 | } else if (target_data->target != NULL) { 221 | have_active_targets = TRUE; 222 | } 223 | } 224 | 225 | if (!have_active_targets && data->targets_to_create == 0) { 226 | /* All fifos finished receiving, quit the test. */ 227 | g_main_loop_quit (data->fixture->loop); 228 | } 229 | 230 | g_mutex_unlock (&data->lock); 231 | } 232 | 233 | static void 234 | test_gaeguli_pipeline_add_remove_target_random (TestFixture * fixture, 235 | gconstpointer unused) 236 | { 237 | AddRemoveTestData data = { 0 }; 238 | guint idle_source; 239 | 240 | g_mutex_init (&data.lock); 241 | data.fixture = fixture; 242 | data.pipeline = gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, 243 | NULL, GAEGULI_VIDEO_RESOLUTION_640X480, 30); 244 | data.targets_to_create = 10; 245 | 246 | g_signal_connect (data.pipeline, "stream-stopped", 247 | (GCallback) stream_stopped_cb, &data); 248 | 249 | idle_source = g_timeout_add (20, (GSourceFunc) add_remove_target_cb, &data); 250 | g_main_loop_run (fixture->loop); 251 | g_source_remove (idle_source); 252 | 253 | gaeguli_pipeline_stop (data.pipeline); 254 | g_clear_object (&data.pipeline); 255 | } 256 | 257 | static void 258 | test_gaeguli_pipeline_address_in_use (void) 259 | { 260 | g_autoptr (GaeguliPipeline) pipeline = 261 | gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 262 | GAEGULI_VIDEO_RESOLUTION_640X480, 30); 263 | g_autoptr (GError) error = NULL; 264 | GaeguliTarget *target; 265 | 266 | target = gaeguli_pipeline_add_srt_target_full (pipeline, 267 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 2048000, 268 | "srt://127.0.0.1:1111?mode=listener", NULL, &error); 269 | g_assert_no_error (error); 270 | g_assert_nonnull (target); 271 | 272 | gaeguli_target_start (target, &error); 273 | g_assert_no_error (error); 274 | 275 | target = gaeguli_pipeline_add_srt_target_full (pipeline, 276 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 2048000, 277 | "srt://127.0.0.1:1111?mode=listener&latency=20", NULL, &error); 278 | g_assert_no_error (error); 279 | g_assert_nonnull (target); 280 | 281 | gaeguli_target_start (target, &error); 282 | g_assert_error (error, GAEGULI_TRANSMIT_ERROR, 283 | GAEGULI_TRANSMIT_ERROR_ADDRINUSE); 284 | 285 | gaeguli_pipeline_stop (pipeline); 286 | } 287 | 288 | typedef struct 289 | { 290 | TestFixture *fixture; 291 | GaeguliPipeline *pipeline; 292 | 293 | guint watchdog_id; 294 | 295 | GstElement *receiver1; 296 | GstElement *receiver2; 297 | 298 | gsize receiver1_buffer_cnt; 299 | gsize receiver2_buffer_cnt; 300 | gsize receiver3_buffer_cnt; 301 | 302 | gsize receiver1_buffer_cnt_last; 303 | 304 | } ClientTestData; 305 | 306 | static gboolean 307 | receiver_watchdog_cb (ClientTestData * data) 308 | { 309 | g_debug ("Watchdog timeout"); 310 | 311 | /* Check that receiver 1 haven't stopped receiving. */ 312 | g_assert_cmpuint (data->receiver1_buffer_cnt, >, 313 | data->receiver1_buffer_cnt_last); 314 | 315 | data->receiver1_buffer_cnt_last = data->receiver1_buffer_cnt; 316 | 317 | return G_SOURCE_CONTINUE; 318 | } 319 | 320 | static void 321 | receiver2_buffer_cb (GstElement * object, GstBuffer * buffer, GstPad * pad, 322 | ClientTestData * data) 323 | { 324 | ++data->receiver2_buffer_cnt; 325 | 326 | if (data->receiver2_buffer_cnt == 100) { 327 | g_debug ("Receiver 2 started receiving; exiting the main loop"); 328 | 329 | g_main_loop_quit (data->fixture->loop); 330 | } 331 | } 332 | 333 | static void 334 | receiver1_buffer_cb (GstElement * object, GstBuffer * buffer, GstPad * pad, 335 | ClientTestData * data) 336 | { 337 | ++data->receiver1_buffer_cnt; 338 | 339 | if (data->receiver1_buffer_cnt == 1) { 340 | data->watchdog_id = g_timeout_add (150, (GSourceFunc) receiver_watchdog_cb, 341 | data); 342 | } else if (data->receiver1_buffer_cnt == 100) { 343 | GaeguliTarget *target; 344 | g_autoptr (GError) error = NULL; 345 | g_autofree gchar *uri_str = NULL; 346 | 347 | g_debug ("Receiver 1 started receiving; spawning receiver 2"); 348 | 349 | uri_str = g_strdup_printf ("srt://127.0.0.1:%d?mode=listener", 350 | data->fixture->port_base + 1); 351 | 352 | target = gaeguli_pipeline_add_srt_target_full (data->pipeline, 353 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 354 | 2048000, uri_str, NULL, &error); 355 | g_assert_no_error (error); 356 | g_assert_nonnull (target); 357 | 358 | gaeguli_target_start (target, &error); 359 | g_assert_no_error (error); 360 | 361 | data->receiver2 = gaeguli_tests_create_receiver (GAEGULI_SRT_MODE_CALLER, 362 | data->fixture->port_base + 1); 363 | gaeguli_tests_receiver_set_handoff_callback (data->receiver2, 364 | (GCallback) receiver2_buffer_cb, data); 365 | } 366 | } 367 | 368 | static void 369 | test_gaeguli_pipeline_listener (TestFixture * fixture, gconstpointer unused) 370 | { 371 | g_autoptr (GaeguliPipeline) pipeline = 372 | gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 373 | GAEGULI_VIDEO_RESOLUTION_640X480, 30); 374 | g_autoptr (GError) error = NULL; 375 | g_autofree gchar *uri_str = NULL; 376 | ClientTestData data = { 0 }; 377 | GaeguliTarget *target; 378 | 379 | uri_str = g_strdup_printf ("srt://127.0.0.1:%d?mode=caller", 380 | fixture->port_base); 381 | target = gaeguli_pipeline_add_srt_target_full (pipeline, 382 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 2048000, 383 | uri_str, NULL, &error); 384 | g_assert_no_error (error); 385 | g_assert_nonnull (target); 386 | 387 | gaeguli_target_start (target, &error); 388 | g_assert_no_error (error); 389 | 390 | data.fixture = fixture; 391 | data.pipeline = pipeline; 392 | data.receiver1 = gaeguli_tests_create_receiver (GAEGULI_SRT_MODE_LISTENER, 393 | data.fixture->port_base); 394 | gaeguli_tests_receiver_set_handoff_callback (data.receiver1, 395 | (GCallback) receiver1_buffer_cb, &data); 396 | 397 | g_main_loop_run (fixture->loop); 398 | 399 | g_source_remove (data.watchdog_id); 400 | gst_element_set_state (data.receiver1, GST_STATE_NULL); 401 | g_clear_pointer (&data.receiver1, gst_object_unref); 402 | gst_element_set_state (data.receiver2, GST_STATE_NULL); 403 | g_clear_pointer (&data.receiver2, gst_object_unref); 404 | 405 | gaeguli_pipeline_stop (pipeline); 406 | } 407 | 408 | typedef struct 409 | { 410 | TestFixture *fixture; 411 | GaeguliPipeline *pipeline; 412 | guint listeners_to_create; 413 | GaeguliTarget *listeners[5]; 414 | } ListenerRandomTestData; 415 | 416 | static gboolean 417 | listener_random_cb (ListenerRandomTestData * data) 418 | { 419 | gint i = g_random_int_range (0, G_N_ELEMENTS (data->listeners)); 420 | g_autoptr (GError) error = NULL; 421 | 422 | if (data->listeners[i] == 0) { 423 | g_autofree gchar *uri = NULL; 424 | 425 | if (data->listeners_to_create == 0) { 426 | return G_SOURCE_CONTINUE; 427 | } 428 | 429 | uri = g_strdup_printf ("srt://127.0.0.1:%d?mode=listener", 430 | data->fixture->port_base + i); 431 | 432 | data->listeners[i] = gaeguli_pipeline_add_srt_target_full (data->pipeline, 433 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 434 | 2048000, uri, NULL, &error); 435 | g_assert_no_error (error); 436 | 437 | gaeguli_target_start (data->listeners[i], &error); 438 | g_assert_no_error (error); 439 | 440 | --data->listeners_to_create; 441 | g_debug ("Added a listener. %d more to go.", data->listeners_to_create); 442 | } else { 443 | gaeguli_pipeline_remove_target (data->pipeline, data->listeners[i], &error); 444 | g_assert_no_error (error); 445 | data->listeners[i] = NULL; 446 | 447 | g_debug ("Removed a listener."); 448 | 449 | if (data->listeners_to_create == 0) { 450 | for (i = 0; i != G_N_ELEMENTS (data->listeners); ++i) { 451 | if (data->listeners[i] != NULL) { 452 | return G_SOURCE_CONTINUE; 453 | } 454 | } 455 | /* All listeners have gone through their lifecycle; stop the test. */ 456 | g_main_loop_quit (data->fixture->loop); 457 | } 458 | } 459 | 460 | return G_SOURCE_CONTINUE; 461 | } 462 | 463 | static void 464 | test_gaeguli_pipeline_listener_random (TestFixture * fixture, 465 | gconstpointer unused) 466 | { 467 | g_autoptr (GaeguliPipeline) pipeline = 468 | gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 469 | GAEGULI_VIDEO_RESOLUTION_640X480, 30); 470 | 471 | ListenerRandomTestData data = { 0 }; 472 | guint timeout_source; 473 | 474 | data.fixture = fixture; 475 | data.pipeline = pipeline; 476 | data.listeners_to_create = 10; 477 | 478 | timeout_source = g_timeout_add (50, (GSourceFunc) listener_random_cb, &data); 479 | g_main_loop_run (fixture->loop); 480 | g_source_remove (timeout_source); 481 | 482 | gaeguli_pipeline_stop (pipeline); 483 | } 484 | 485 | typedef struct 486 | { 487 | GMainLoop *loop; 488 | GaeguliTarget *target; 489 | } ConnectionErrorTestData; 490 | 491 | static void 492 | _on_connection_error (GaeguliPipeline * pipeline, GaeguliTarget * target, 493 | GError * error, gpointer user_data) 494 | { 495 | ConnectionErrorTestData *data = user_data; 496 | 497 | g_assert (target == data->target); 498 | 499 | g_assert_true (g_error_matches (error, GST_RESOURCE_ERROR, 500 | GST_RESOURCE_ERROR_WRITE)); 501 | 502 | g_main_loop_quit (data->loop); 503 | } 504 | 505 | static void 506 | _on_connection_error_not_reached (GaeguliPipeline * pipeline, guint target_id, 507 | GError * error, gpointer user_data) 508 | { 509 | g_assert_not_reached (); 510 | } 511 | 512 | static void 513 | _on_buffer_received (GstElement * object, GstBuffer * buffer, GstPad * pad, 514 | gpointer data) 515 | { 516 | g_main_loop_quit (data); 517 | } 518 | 519 | static void 520 | test_gaeguli_pipeline_connection_error (TestFixture * fixture, 521 | gconstpointer unused) 522 | { 523 | ConnectionErrorTestData data; 524 | g_autoptr (GaeguliPipeline) pipeline = 525 | gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 526 | GAEGULI_VIDEO_RESOLUTION_640X480, 30); 527 | g_autoptr (GstElement) receiver = NULL; 528 | g_autoptr (GError) error = NULL; 529 | g_autofree gchar *uri = NULL; 530 | gulong handler_id; 531 | 532 | g_assert_nonnull (pipeline); 533 | 534 | uri = g_strdup_printf ("srt://127.0.0.1:%d?mode=caller", fixture->port_base); 535 | 536 | data.loop = fixture->loop; 537 | 538 | g_debug ("Running a target without a listener. This should issue errors."); 539 | data.target = gaeguli_pipeline_add_srt_target (pipeline, uri, NULL, &error); 540 | g_assert_no_error (error); 541 | 542 | handler_id = g_signal_connect (pipeline, "connection-error", 543 | (GCallback) _on_connection_error, &data); 544 | 545 | gaeguli_target_start (data.target, &error); 546 | g_assert_no_error (error); 547 | 548 | g_main_loop_run (fixture->loop); 549 | 550 | gaeguli_pipeline_remove_target (pipeline, data.target, &error); 551 | g_assert_no_error (error); 552 | 553 | g_signal_handler_disconnect (pipeline, handler_id); 554 | 555 | g_signal_connect (pipeline, "connection-error", 556 | (GCallback) _on_connection_error_not_reached, NULL); 557 | 558 | receiver = gaeguli_tests_create_receiver (GAEGULI_SRT_MODE_LISTENER, 559 | fixture->port_base); 560 | gaeguli_tests_receiver_set_handoff_callback (receiver, 561 | (GCallback) _on_buffer_received, fixture->loop); 562 | g_assert_nonnull (receiver); 563 | 564 | g_debug ("Running a target with a listener. This should report no errors."); 565 | data.target = gaeguli_pipeline_add_srt_target (pipeline, uri, NULL, &error); 566 | g_assert_no_error (error); 567 | 568 | gaeguli_target_start (data.target, &error); 569 | g_assert_no_error (error); 570 | 571 | g_main_loop_run (fixture->loop); 572 | 573 | gaeguli_pipeline_stop (pipeline); 574 | gst_element_set_state (receiver, GST_STATE_NULL); 575 | } 576 | 577 | static gboolean 578 | _stop_pipeline (TestFixture * fixture) 579 | { 580 | gaeguli_pipeline_stop (fixture->pipeline); 581 | g_main_loop_quit (fixture->loop); 582 | return G_SOURCE_REMOVE; 583 | } 584 | 585 | static void 586 | _schedule_pipeline_stop (GaeguliPipeline * pipeline, guint target_id, 587 | TestFixture * fixture) 588 | { 589 | g_timeout_add (100, (GSourceFunc) _stop_pipeline, fixture); 590 | } 591 | 592 | static void 593 | do_pipeline_cycle (TestFixture * fixture, GaeguliVideoCodec codec) 594 | { 595 | g_autoptr (GaeguliPipeline) pipeline = 596 | gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 597 | GAEGULI_VIDEO_RESOLUTION_640X480, 30); 598 | GaeguliTarget *target; 599 | g_autoptr (GError) error = NULL; 600 | 601 | target = 602 | gaeguli_pipeline_add_srt_target_full (pipeline, codec, 603 | GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 2048000, "srt://127.0.0.1:1111", NULL, 604 | &error); 605 | g_assert_no_error (error); 606 | 607 | gaeguli_target_start (target, &error); 608 | g_assert_no_error (error); 609 | 610 | fixture->pipeline = pipeline; 611 | 612 | g_signal_connect (pipeline, "stream-started", 613 | G_CALLBACK (_schedule_pipeline_stop), fixture); 614 | 615 | g_main_loop_run (fixture->loop); 616 | } 617 | 618 | static void 619 | test_gaeguli_pipeline_debug_tx1 (TestFixture * fixture, gconstpointer unused) 620 | { 621 | GaeguliVideoCodec codec = GAEGULI_VIDEO_CODEC_H264_X264; 622 | g_autoptr (GstPluginFeature) feature = NULL; 623 | guint i; 624 | 625 | feature = gst_registry_lookup_feature (gst_registry_get (), "nvvidconv"); 626 | if (feature) { 627 | codec = GAEGULI_VIDEO_CODEC_H264_OMX; 628 | } 629 | 630 | for (i = 0; i != 3; ++i) { 631 | do_pipeline_cycle (fixture, codec); 632 | } 633 | } 634 | 635 | int 636 | main (int argc, char *argv[]) 637 | { 638 | gst_init (&argc, &argv); 639 | 640 | g_test_init (&argc, &argv, NULL); 641 | 642 | /* Don't treat warnings as fatal, which is GTest default. */ 643 | g_log_set_always_fatal (G_LOG_FATAL_MASK | G_LOG_LEVEL_CRITICAL); 644 | 645 | g_test_add ("/gaeguli/pipeline-instance", TestFixture, NULL, fixture_setup, 646 | test_gaeguli_pipeline_instance, fixture_teardown); 647 | 648 | g_test_add ("/gaeguli/pipeline-add-remove-target-random", 649 | TestFixture, NULL, fixture_setup, 650 | test_gaeguli_pipeline_add_remove_target_random, fixture_teardown); 651 | 652 | g_test_add_func ("/gaeguli/pipeline-address-in-use", 653 | test_gaeguli_pipeline_address_in_use); 654 | 655 | g_test_add ("/gaeguli/pipeline-listener", 656 | TestFixture, NULL, fixture_setup, 657 | test_gaeguli_pipeline_listener, fixture_teardown); 658 | 659 | g_test_add ("/gaeguli/pipeline-listener-random", 660 | TestFixture, NULL, fixture_setup, 661 | test_gaeguli_pipeline_listener_random, fixture_teardown); 662 | 663 | g_test_add ("/gaeguli/pipeline-connection-error", 664 | TestFixture, NULL, fixture_setup, 665 | test_gaeguli_pipeline_connection_error, fixture_teardown); 666 | 667 | g_test_add ("/gaeguli/pipeline-debug-tx1", TestFixture, NULL, fixture_setup, 668 | test_gaeguli_pipeline_debug_tx1, fixture_teardown); 669 | 670 | return g_test_run (); 671 | } 672 | -------------------------------------------------------------------------------- /tests/test-rtp-over-srt.c: -------------------------------------------------------------------------------- 1 | /** 2 | * tests/test-rtp-over-srt 3 | * 4 | * Copyright 2021 SK Telecom Co., Ltd. 5 | * Author: Jeongseok Kim 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | #include 22 | #include "gaeguli/test/receiver.h" 23 | 24 | typedef struct _TestFixture 25 | { 26 | GMainLoop *loop; 27 | GaeguliPipeline *pipeline; 28 | GaeguliTarget *target; 29 | guint port; 30 | gchar *srt_uri; 31 | } TestFixture; 32 | 33 | static void 34 | fixture_setup (TestFixture * fixture, gconstpointer unused) 35 | { 36 | fixture->loop = g_main_loop_new (NULL, FALSE); 37 | fixture->port = g_random_int_range (39000, 40000); 38 | fixture->srt_uri = 39 | g_strdup_printf ("srt://127.0.0.1:%" G_GUINT32_FORMAT "?mode=caller", 40 | fixture->port); 41 | } 42 | 43 | static void 44 | fixture_teardown (TestFixture * fixture, gconstpointer unused) 45 | { 46 | g_main_loop_unref (fixture->loop); 47 | } 48 | 49 | typedef struct 50 | { 51 | TestFixture *fixture; 52 | gsize buffer_cnt; 53 | } ClientTestData; 54 | 55 | static void 56 | _test1_buffer_cb (GstElement * object, GstBuffer * buffer, GstPad * pad, 57 | ClientTestData * data) 58 | { 59 | ++data->buffer_cnt; 60 | if (++data->buffer_cnt == 50) { 61 | g_debug ("reached 50 received buffer count; exiting the main loop"); 62 | g_main_loop_quit (data->fixture->loop); 63 | } 64 | } 65 | 66 | static void 67 | test_gaeguli_pipeline_rtp_instance (TestFixture * fixture, gconstpointer unused) 68 | { 69 | GaeguliTarget *target; 70 | g_autoptr (GaeguliPipeline) pipeline = 71 | gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 72 | GAEGULI_VIDEO_RESOLUTION_640X480, 30); 73 | g_autoptr (GError) error = NULL; 74 | ClientTestData data = { 0 }; 75 | g_autoptr (GstElement) listener = 76 | gaeguli_tests_create_receiver (GAEGULI_SRT_MODE_LISTENER, fixture->port); 77 | 78 | data.fixture = fixture; 79 | 80 | gaeguli_tests_receiver_set_handoff_callback (listener, 81 | (GCallback) _test1_buffer_cb, &data); 82 | 83 | target = gaeguli_pipeline_add_srt_target_full (pipeline, 84 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_RTP_OVER_SRT, 85 | 2048000, fixture->srt_uri, NULL, &error); 86 | 87 | g_assert_no_error (error); 88 | g_assert_nonnull (target); 89 | g_assert_cmpuint (target->id, !=, 0); 90 | fixture->target = target; 91 | 92 | gaeguli_target_start (target, &error); 93 | g_assert_no_error (error); 94 | 95 | g_main_loop_run (fixture->loop); 96 | 97 | gst_element_set_state (listener, GST_STATE_NULL); 98 | gaeguli_pipeline_stop (pipeline); 99 | } 100 | 101 | int 102 | main (int argc, char *argv[]) 103 | { 104 | gst_init (&argc, &argv); 105 | 106 | g_test_init (&argc, &argv, NULL); 107 | 108 | g_test_add ("/gaeguli/pipeline-rtp-instance", TestFixture, NULL, 109 | fixture_setup, test_gaeguli_pipeline_rtp_instance, fixture_teardown); 110 | 111 | return g_test_run (); 112 | } 113 | -------------------------------------------------------------------------------- /tests/test-target.c: -------------------------------------------------------------------------------- 1 | /** 2 | * tests/test-target 3 | * 4 | * Copyright 2020 SK Telecom Co., Ltd. 5 | * Author: Jakub Adam 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | #include 22 | 23 | #include "gaeguli/test/receiver.h" 24 | 25 | #define DEFAULT_BITRATE 1500000 26 | #define CHANGED_BITRATE 3000000 27 | #define ROUNDED_BITRATE 9999999 28 | 29 | 30 | static void 31 | test_gaeguli_target_encoding_params () 32 | { 33 | g_autoptr (GaeguliPipeline) pipeline = NULL; 34 | g_autoptr (GError) error = NULL; 35 | GaeguliTarget *target; 36 | guint val; 37 | 38 | pipeline = gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 39 | GAEGULI_VIDEO_RESOLUTION_640X480, 15); 40 | 41 | target = gaeguli_pipeline_add_srt_target_full (pipeline, 42 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 43 | DEFAULT_BITRATE, "srt://127.0.0.1:1111", NULL, &error); 44 | g_assert_no_error (error); 45 | 46 | gaeguli_target_start (target, &error); 47 | g_assert_no_error (error); 48 | 49 | g_object_get (target, "bitrate", &val, NULL); 50 | g_assert_cmpuint (val, ==, DEFAULT_BITRATE); 51 | g_object_get (target, "bitrate-actual", &val, NULL); 52 | g_assert_cmpuint (val, ==, DEFAULT_BITRATE); 53 | 54 | g_object_set (target, "bitrate", CHANGED_BITRATE, NULL); 55 | g_object_get (target, "bitrate", &val, NULL); 56 | g_assert_cmpuint (val, ==, CHANGED_BITRATE); 57 | g_object_get (target, "bitrate-actual", &val, NULL); 58 | g_assert_cmpuint (val, ==, CHANGED_BITRATE); 59 | 60 | g_object_set (target, "bitrate", ROUNDED_BITRATE, NULL); 61 | g_object_get (target, "bitrate", &val, NULL); 62 | g_assert_cmpuint (val, ==, ROUNDED_BITRATE); 63 | g_object_get (target, "bitrate-actual", &val, NULL); 64 | /* Internally, x264enc accepts bitrate in kbps, so we lose the value precision 65 | * in the three least significant digits. */ 66 | g_assert_cmpuint (val, ==, ROUNDED_BITRATE - (ROUNDED_BITRATE % 1000)); 67 | 68 | gaeguli_pipeline_stop (pipeline); 69 | } 70 | 71 | static void 72 | buffer_cb (GstElement * object, GstBuffer * buffer, GstPad * pad, gpointer data) 73 | { 74 | g_main_loop_quit (data); 75 | } 76 | 77 | static void 78 | buffer_cb_not_reached (GstElement * object, GstBuffer * buffer, GstPad * pad, 79 | gpointer data) 80 | { 81 | g_assert_not_reached (); 82 | } 83 | 84 | static void 85 | connection_error_cb (GaeguliPipeline * pipeline, GaeguliTarget * target, 86 | GError * error, gpointer data) 87 | { 88 | g_assert_true (g_error_matches (error, GST_RESOURCE_ERROR, 89 | GST_RESOURCE_ERROR_NOT_AUTHORIZED)); 90 | 91 | g_main_loop_quit (data); 92 | } 93 | 94 | static void 95 | connection_error_not_reached_cb (GaeguliPipeline * pipeline, 96 | GaeguliTarget * target, GError * error, gpointer data) 97 | { 98 | g_assert_not_reached (); 99 | } 100 | 101 | static void 102 | passphrase_run (const gchar * sender_passphrase, 103 | const gchar * receiver_passphrase, GCallback error_cb, GCallback buffer_cb) 104 | { 105 | g_autoptr (GMainLoop) loop = g_main_loop_new (NULL, FALSE); 106 | g_autoptr (GaeguliPipeline) pipeline = NULL; 107 | g_autoptr (GstElement) receiver = NULL; 108 | g_autoptr (GError) error = NULL; 109 | GaeguliTarget *target; 110 | 111 | pipeline = gaeguli_pipeline_new_full (GAEGULI_VIDEO_SOURCE_VIDEOTESTSRC, NULL, 112 | GAEGULI_VIDEO_RESOLUTION_640X480, 15); 113 | g_signal_connect (pipeline, "connection-error", error_cb, loop); 114 | 115 | target = gaeguli_pipeline_add_srt_target_full (pipeline, 116 | GAEGULI_VIDEO_CODEC_H264_X264, GAEGULI_VIDEO_STREAM_TYPE_MPEG_TS, 117 | DEFAULT_BITRATE, "srt://127.0.0.1:1111", NULL, &error); 118 | g_assert_no_error (error); 119 | g_object_set (G_OBJECT (target), "passphrase", sender_passphrase, NULL); 120 | 121 | receiver = gaeguli_tests_create_receiver (GAEGULI_SRT_MODE_LISTENER, 1111); 122 | gaeguli_tests_receiver_set_handoff_callback (receiver, buffer_cb, loop); 123 | gaeguli_tests_receiver_set_passphrase (receiver, receiver_passphrase); 124 | 125 | gaeguli_target_start (target, &error); 126 | if (sender_passphrase && strlen (sender_passphrase) < 10) { 127 | g_assert_error (error, GAEGULI_TRANSMIT_ERROR, 128 | GAEGULI_TRANSMIT_ERROR_FAILED); 129 | goto out; 130 | } else { 131 | g_assert_no_error (error); 132 | } 133 | 134 | g_main_loop_run (loop); 135 | 136 | out: 137 | gst_element_set_state (receiver, GST_STATE_NULL); 138 | gaeguli_pipeline_stop (pipeline); 139 | } 140 | 141 | static void 142 | test_gaeguli_target_passphrase () 143 | { 144 | passphrase_run ("mysecretpassphrase", NULL, 145 | (GCallback) connection_error_cb, (GCallback) buffer_cb_not_reached); 146 | passphrase_run (NULL, "mysecretpassphrase", 147 | (GCallback) connection_error_cb, (GCallback) buffer_cb_not_reached); 148 | passphrase_run ("mysecretpassphrase", "notmatchingpassphrase", 149 | (GCallback) connection_error_cb, (GCallback) buffer_cb_not_reached); 150 | passphrase_run ("tooshort", "tooshort", 151 | (GCallback) connection_error_cb, (GCallback) buffer_cb_not_reached); 152 | passphrase_run ("mysecretpassphrase", "mysecretpassphrase", 153 | (GCallback) connection_error_not_reached_cb, (GCallback) buffer_cb); 154 | } 155 | 156 | int 157 | main (int argc, char *argv[]) 158 | { 159 | gst_init (&argc, &argv); 160 | 161 | g_test_init (&argc, &argv, NULL); 162 | g_test_add_func ("/gaeguli/target-encoding-params", 163 | test_gaeguli_target_encoding_params); 164 | g_test_add_func ("/gaeguli/target-passphrase", 165 | test_gaeguli_target_passphrase); 166 | 167 | return g_test_run (); 168 | } 169 | -------------------------------------------------------------------------------- /tools/meson.build: -------------------------------------------------------------------------------- 1 | tools = [ 2 | 'pipeline' 3 | ] 4 | 5 | tools_c_args = [ 6 | '-DG_LOG_DOMAIN="gaeguli-tools"', 7 | '-DGAEGULI_COMPILATION', 8 | ] 9 | 10 | foreach tool: tools 11 | exe_name = '@0@-@1@'.format(tool, apiversion) 12 | src_file = '@0@.c'.format(tool) 13 | 14 | executable(exe_name, 15 | src_file, 16 | install: true, 17 | include_directories: gaeguli_incs, 18 | dependencies : [ libgaeguli_dep ], 19 | c_args: tools_c_args, 20 | ) 21 | 22 | endforeach 23 | -------------------------------------------------------------------------------- /tools/pipeline.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 SK Telecom, Co., Ltd. 3 | * Author: Jeongseok Kim 4 | * 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include "pipeline.h" 10 | #include "target.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static guint signal_watch_intr_id; 17 | static GaeguliTarget *target; 18 | 19 | static struct 20 | { 21 | const gchar *device; 22 | const gchar *uri; 23 | const gchar *username; 24 | gboolean overlay; 25 | } options; 26 | 27 | static void 28 | activate (GApplication * app, gpointer user_data) 29 | { 30 | g_autoptr (GError) error = NULL; 31 | 32 | GaeguliPipeline *pipeline = user_data; 33 | 34 | g_application_hold (app); 35 | 36 | g_print ("Streaming to %s\n", options.uri); 37 | target = gaeguli_pipeline_add_srt_target (pipeline, options.uri, 38 | options.username, &error); 39 | gaeguli_target_start (target, &error); 40 | } 41 | 42 | static gboolean 43 | intr_handler (gpointer user_data) 44 | { 45 | GaeguliPipeline *pipeline = user_data; 46 | g_autoptr (GError) error = NULL; 47 | 48 | gaeguli_pipeline_remove_target (pipeline, target, &error); 49 | 50 | g_debug ("target removed"); 51 | 52 | return G_SOURCE_REMOVE; 53 | } 54 | 55 | static void 56 | stream_stopped_cb (GaeguliPipeline * pipeline, guint target_id, 57 | gpointer user_data) 58 | { 59 | GApplication *app = user_data; 60 | 61 | g_debug ("stream stopped"); 62 | g_application_release (app); 63 | } 64 | 65 | static gboolean 66 | uri_arg_cb (const gchar * option_name, const gchar * value, gpointer data, 67 | GError ** error) 68 | { 69 | if (!options.uri) { 70 | options.uri = g_strdup (value); 71 | } 72 | return TRUE; 73 | } 74 | 75 | int 76 | main (int argc, char *argv[]) 77 | { 78 | gboolean help = FALSE; 79 | int result; 80 | 81 | g_autoptr (GError) error = NULL; 82 | g_autoptr (GApplication) app = g_application_new (NULL, 0); 83 | 84 | g_autoptr (GOptionContext) context = NULL; 85 | GOptionEntry entries[] = { 86 | {"device", 'd', 0, G_OPTION_ARG_FILENAME, &options.device, NULL, NULL}, 87 | {"username", 'u', 0, G_OPTION_ARG_STRING, &options.username, NULL, NULL}, 88 | {"clock-overlay", 'c', 0, G_OPTION_ARG_NONE, &options.overlay, NULL, NULL}, 89 | {"help", '?', 0, G_OPTION_ARG_NONE, &help, NULL, NULL}, 90 | {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_CALLBACK, uri_arg_cb, NULL, NULL}, 91 | {NULL} 92 | }; 93 | 94 | g_autoptr (GaeguliPipeline) pipeline = NULL; 95 | 96 | options.device = DEFAULT_VIDEO_SOURCE_DEVICE; 97 | options.uri = NULL; 98 | options.username = NULL; 99 | options.overlay = FALSE; 100 | 101 | gst_init (&argc, &argv); 102 | 103 | context = g_option_context_new (NULL); 104 | g_option_context_set_help_enabled (context, FALSE); 105 | g_option_context_add_main_entries (context, entries, NULL); 106 | 107 | if (!g_option_context_parse (context, &argc, &argv, &error)) { 108 | g_printerr ("%s\n", error->message); 109 | return -1; 110 | } 111 | 112 | if (help) { 113 | g_autofree gchar *text = g_option_context_get_help (context, FALSE, NULL); 114 | g_printerr ("%s\n", text); 115 | return -1; 116 | } 117 | 118 | if (!options.uri) { 119 | g_printerr ("SRT uri not specified\n"); 120 | return -1; 121 | } 122 | if (!g_str_has_prefix (options.uri, "srt://")) { 123 | g_printerr ("Invalid SRT uri %s\n", options.uri); 124 | return -1; 125 | } 126 | 127 | pipeline = gaeguli_pipeline_new_full (DEFAULT_VIDEO_SOURCE, options.device, 128 | DEFAULT_VIDEO_RESOLUTION, DEFAULT_VIDEO_FRAMERATE); 129 | g_object_set (pipeline, "clock-overlay", options.overlay, NULL); 130 | 131 | signal_watch_intr_id = 132 | g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, pipeline); 133 | 134 | g_signal_connect (pipeline, "stream-stopped", G_CALLBACK (stream_stopped_cb), 135 | app); 136 | 137 | g_signal_connect (app, "activate", G_CALLBACK (activate), pipeline); 138 | 139 | result = g_application_run (app, argc, argv); 140 | 141 | gaeguli_pipeline_stop (pipeline); 142 | 143 | if (signal_watch_intr_id > 0) 144 | g_source_remove (signal_watch_intr_id); 145 | 146 | return result; 147 | } 148 | --------------------------------------------------------------------------------