diff --git a/.github/ISSUE_TEMPLATE/playback-issue.md b/.github/ISSUE_TEMPLATE/playback-issue.md
new file mode 100644
index 00000000..258719fa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/playback-issue.md
@@ -0,0 +1,38 @@
+---
+name: HLS/DASH/Console Playback Failure
+about: Are you experiencing a failure when playing your streamed media?
+title: "[Playback Failure]"
+labels: Playback
+assignees: ''
+
+---
+
+**NOTE:** This template is for failure to playback issues of HLS, DASH, and AWS console players. If the playback issue is related to GetMedia playback, please submit an issue on the [KVS Java Parser Library](https://github.com/aws/amazon-kinesis-video-streams-parser-library) repository. If the issue is related to HLS/DASH/Console, but the issue is not regarding a failure to playback (such as a latency issue or general question), please submit using the general question template.
+
+### Brief one-liner description of the issue:
+...
+
+### Please include the following details:
+- Problematic playback methods (HLS, DASH, and/or AWS Console), operating systems, browsers:
+ - ...
+- Working playback methods (were you able to playback the stream using using any methods?), operating systems, browsers:
+ - ...
+- Is the stream's data retention setting greater than 0?
+- Does your stream meet the [KVS video playback track requirements](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/video-playback-requirements.html)?
+- If there are fragment decoding errors:
+ - Are fragment timestamps accurate, in the correct order, and have no duplicates? ([ListFragments](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_reader_ListFragments.html) can be used to retrieve fragment information for a stream)
+ - Is your application encoding the frame data using the H.264 format?
+ - Does the resolution of the frames match the resolution specified in the Codec Private Data?
+ - Does the H.264 profile and level of the encoded frames match the profile and level specified in the Codec Private Data?
+ - Does the browser/OS support the profile/level combination?
+- If there are HLS playback failures:
+ - Is the fragment duration less than 1 second? If yes, does issue persist with fragments longer than 1 second?
+ - Is each HLS streaming session URL being used by only one player at a time?
+ - Does each fragment have a consistent number of tracks, is not changing between having both an audio and video track and only a video track, and has consistent encoder settings (resolution and frame rate not changing between fragments in each track)?
+ - Does issue persist after fetching using GetHLSStreamingSessionURL with the ContainerFormat and DiscontinuityMode parameters set to different values?
+
+
+### Logging
+Add any relevant SDK and player logs. IMPORTANT NOTE: Please make sure to NOT share AWS access credentials under any circumstance! Please make sure they are not in the logs.
+
+** If you would not like to open an issue to discuss your solution in open-platform, please email your question to kinesis-video-support@amazon.com **
diff --git a/.github/build_windows.bat b/.github/build_windows.bat
index 34776ccb..04ef5553 100644
--- a/.github/build_windows.bat
+++ b/.github/build_windows.bat
@@ -1,6 +1,6 @@
-call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
+call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
mkdir build
cd build
cmd.exe /c cmake -G "NMake Makefiles" ..
-cmake -G "NMake Makefiles" -DBUILD_TEST=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE ..
+cmake -G "NMake Makefiles" -DBUILD_TEST=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DPKG_CONFIG_EXECUTABLE="D:\\gstreamer\\1.0\\msvc_x86_64\\bin\\pkg-config.exe" ..
nmake
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..6e762bc0
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,410 @@
+name: Producer CPP SDK CI
+
+on:
+ push:
+ branches:
+ - develop
+ - master
+ pull_request:
+ branches:
+ - develop
+ - master
+
+jobs:
+ mac-os-build-clang:
+ runs-on: macos-12
+ env:
+ AWS_KVS_LOG_LEVEL: 2
+ permissions:
+ id-token: write
+ contents: read
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ brew install pkg-config openssl cmake gstreamer log4cplus
+ brew unlink openssl
+ - name: Build repository
+ run: |
+ mkdir build && cd build
+ sh -c 'cmake .. -DBUILD_TEST=TRUE -DCOMPILER_WARNINGS=TRUE;cmake .. -DBUILD_TEST=TRUE -DCOMPILER_WARNINGS=TRUE -DCMAKE_INSTALL_PREFIX=.'
+ make
+ make install
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v1-node16
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
+ role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}
+ aws-region: ${{ secrets.AWS_REGION }}
+ role-duration-seconds: 10800
+ - name: Run tests
+ run: |
+ cd build
+ ./tst/producerTest
+
+ mac-os-build-gcc:
+ runs-on: macos-12
+ permissions:
+ id-token: write
+ contents: read
+ env:
+ CC: gcc
+ CXX: g++
+ AWS_KVS_LOG_LEVEL: 2
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ brew install pkg-config openssl cmake gstreamer log4cplus
+ brew unlink openssl
+ - name: Build repository
+ run: |
+ mkdir build && cd build
+ cmake .. -DBUILD_TEST=TRUE -DCMAKE_INSTALL_PREFIX=.
+ make
+ make install
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v1-node16
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
+ role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}
+ aws-region: ${{ secrets.AWS_REGION }}
+ role-duration-seconds: 10800
+ - name: Run tests
+ run: |
+ cd build
+ ./tst/producerTest
+
+ linux-gcc-code-coverage:
+ runs-on: ubuntu-20.04
+ env:
+ AWS_KVS_LOG_LEVEL: 2
+ permissions:
+ id-token: write
+ contents: read
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ sudo apt clean && sudo apt update
+ sudo apt install -y libunwind-dev
+ sudo apt-get install -y libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ - name: Build repository
+ run: |
+ mkdir build && cd build
+ cmake .. -DCODE_COVERAGE=TRUE -DBUILD_TEST=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE -DCMAKE_INSTALL_PREFIX=.
+ make
+ make install
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v1-node16
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
+ role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}
+ aws-region: ${{ secrets.AWS_REGION }}
+ role-duration-seconds: 10800
+ - name: Run tests
+ run: |
+ cd build
+ ulimit -c unlimited -S
+ timeout --signal=SIGABRT 60m ./tst/producerTest
+ - name: Code coverage
+ run: |
+ cd build
+ for test_file in $(find CMakeFiles/KinesisVideoProducer.dir gstkvssink.dir KinesisVideoProducerJNI.dir -name '*.gcno'); do gcov $test_file; done
+ bash <(curl -s https://codecov.io/bash)
+
+ address-sanitizer:
+ runs-on: ubuntu-20.04
+ permissions:
+ id-token: write
+ contents: read
+ env:
+ CC: clang
+ CXX: clang++
+ AWS_KVS_LOG_LEVEL: 2
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ sudo apt clean && sudo apt update
+ sudo apt install -y libunwind-dev
+ sudo apt-get install -y libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ - name: Build repository
+ run: |
+ mkdir build && cd build
+ cmake .. -DBUILD_TEST=TRUE -DADDRESS_SANITIZER=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE -DCMAKE_INSTALL_PREFIX=.
+ make
+ make install
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v1-node16
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
+ role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}
+ aws-region: ${{ secrets.AWS_REGION }}
+ role-duration-seconds: 10800
+ - name: Run tests
+ run: |
+ cd build
+ ulimit -c unlimited -S
+ timeout --signal=SIGABRT 60m ./tst/producerTest
+
+ undefined-behavior-sanitizer:
+ runs-on: ubuntu-20.04
+ permissions:
+ id-token: write
+ contents: read
+ env:
+ CC: clang
+ CXX: clang++
+ AWS_KVS_LOG_LEVEL: 2
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ sudo apt clean && sudo apt update
+ sudo apt install -y libunwind-dev
+ sudo apt-get install -y libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ - name: Build repository
+ run: |
+ mkdir build && cd build
+ cmake .. -DBUILD_TEST=TRUE -DUNDEFINED_BEHAVIOR_SANITIZER=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE -DCMAKE_INSTALL_PREFIX=.
+ make
+ make install
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v1-node16
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
+ role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}
+ aws-region: ${{ secrets.AWS_REGION }}
+ role-duration-seconds: 10800
+ - name: Run tests
+ run: |
+ cd build
+ ulimit -c unlimited -S
+ timeout --signal=SIGABRT 60m ./tst/producerTest
+
+ # memory-sanitizer:
+ # runs-on: ubuntu-20.04
+ # permissions:
+ # id-token: write
+ # contents: read
+ # env:
+ # CC: clang
+ # CXX: clang++
+ # AWS_KVS_LOG_LEVEL: 2
+ # steps:
+ # - name: Clone repository
+ # uses: actions/checkout@v3
+ # - name: Configure AWS Credentials
+ # uses: aws-actions/configure-aws-credentials@v1-node16
+ # with:
+ # role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
+ # role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}
+ # aws-region: ${{ secrets.AWS_REGION }}
+ # - name: Build repository
+ # run: |
+ # mkdir build && cd build
+ # cmake .. -DBUILD_TEST=TRUE -DMEMORY_SANITIZER=TRUE -DBUILD_COMMON_LWS=TRUE
+ # make
+ # ulimit -c unlimited -S
+ # timeout --signal=SIGABRT 150m ./tst/producerTest --gtest_break_on_failure
+ # thread-sanitizer:
+ # runs-on: ubuntu-20.04
+ # permissions:
+ # id-token: write
+ # contents: read
+ # env:
+ # CC: clang
+ # CXX: clang++
+ # AWS_KVS_LOG_LEVEL: 2
+ # steps:
+ # - name: Clone repository
+ # uses: actions/checkout@v3
+ # - name: Configure AWS Credentials
+ # uses: aws-actions/configure-aws-credentials@v1-node16
+ # with:
+ # role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
+ # role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}
+ # aws-region: ${{ secrets.AWS_REGION }}
+ # - name: Build repository
+ # run: |
+ # sudo apt-get install -y libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ # mkdir build && cd build
+ # cmake .. -DBUILD_TEST=TRUE -DTHREAD_SANITIZER=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE
+ # make
+ # ulimit -c unlimited -S
+ # timeout --signal=SIGABRT 20m ./tst/producerTest
+
+ ubuntu-gcc:
+ runs-on: ubuntu-20.04
+ env:
+ AWS_KVS_LOG_LEVEL: 2
+ CC: gcc
+ CXX: g++
+ permissions:
+ id-token: write
+ contents: read
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ sudo apt clean && sudo apt update
+ sudo apt install -y libunwind-dev
+ sudo apt-get install -y libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ - name: Build repository
+ run: |
+ mkdir build && cd build
+ cmake .. -DBUILD_TEST=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE -DCMAKE_INSTALL_PREFIX=.
+ make
+ make install
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v1-node16
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
+ role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}
+ aws-region: ${{ secrets.AWS_REGION }}
+ role-duration-seconds: 10800
+ - name: Run tests
+ run: |
+ cd build
+ ulimit -c unlimited -S
+ timeout --signal=SIGABRT 60m ./tst/producerTest
+
+ windows-msvc:
+ runs-on: windows-2022
+ env:
+ AWS_KVS_LOG_LEVEL: 7
+ permissions:
+ id-token: write
+ contents: read
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ choco install nasm strawberryperl
+ choco install gstreamer --version=1.22.8
+ choco install gstreamer-devel --version=1.22.8
+ - name: Build repository
+ run: |
+ $env:Path += ';C:\Strawberry\perl\site\bin;C:\Strawberry\perl\bin;C:\Strawberry\c\bin;C:\Program Files\NASM;D:\a\amazon-kinesis-video-streams-producer-sdk-cpp\amazon-kinesis-video-streams-producer-sdk-cpp\open-source\local\lib;D:\a\amazon-kinesis-video-streams-producer-sdk-cpp\amazon-kinesis-video-streams-producer-sdk-cpp\open-source\local\bin'
+ git config --system core.longpaths true
+ .github/build_windows.bat
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v1-node16
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
+ role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}
+ aws-region: ${{ secrets.AWS_REGION }}
+ role-duration-seconds: 10800
+ - name: Run tests
+ run: |
+ $env:Path += ';C:\Strawberry\perl\site\bin;C:\Strawberry\perl\bin;C:\Strawberry\c\bin;C:\Program Files\NASM;D:\a\amazon-kinesis-video-streams-producer-sdk-cpp\amazon-kinesis-video-streams-producer-sdk-cpp\open-source\local\lib;D:\a\amazon-kinesis-video-streams-producer-sdk-cpp\amazon-kinesis-video-streams-producer-sdk-cpp\open-source\local\bin'
+ & "D:\a\amazon-kinesis-video-streams-producer-sdk-cpp\amazon-kinesis-video-streams-producer-sdk-cpp\build\tst\producerTest.exe"
+
+ arm64-cross-compilation:
+ runs-on: ubuntu-20.04
+ env:
+ CC: aarch64-linux-gnu-gcc
+ CXX: aarch64-linux-gnu-g++
+ steps:
+ - name: Install dependencies
+ run: |
+ sudo apt clean && sudo apt update
+ sudo apt install -y libunwind-dev
+ sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu
+ sudo apt-get install libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Build Repository
+ run: |
+ sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'
+ mkdir build && cd build
+ cmake .. -DBUILD_TEST=TRUE -DBUILD_OPENSSL_PLATFORM=linux-generic64 -DBUILD_LOG4CPLUS_HOST=arm-linux -DCMAKE_INSTALL_PREFIX=.
+ make
+ make install
+ file libKinesisVideoProducer.so
+
+ linux-aarch64-cross-compilation:
+ runs-on: ubuntu-20.04
+ env:
+ CC: aarch64-linux-gnu-gcc
+ CXX: aarch64-linux-gnu-g++
+ steps:
+ - name: Install dependencies
+ run: |
+ sudo apt clean && sudo apt update
+ sudo apt install -y libunwind-dev
+ sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu
+ sudo apt-get install libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Build Repository
+ run: |
+ sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'
+ mkdir build && cd build
+ cmake .. -DBUILD_TEST=TRUE -DBUILD_OPENSSL_PLATFORM=linux-aarch64 -DBUILD_LOG4CPLUS_HOST=arm-linux -DCMAKE_INSTALL_PREFIX=.
+ make
+ make install
+ file libKinesisVideoProducer.so
+
+ arm32-cross-compilation:
+ runs-on: ubuntu-20.04
+ env:
+ CC: arm-linux-gnueabi-gcc
+ CXX: arm-linux-gnueabi-g++
+ steps:
+ - name: Install dependencies
+ run: |
+ sudo apt clean && sudo apt update
+ sudo apt install -y libunwind-dev
+ sudo apt-get -y install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi binutils-arm-linux-gnueabi
+ sudo apt-get install libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Build Repository
+ run: |
+ sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'
+ mkdir build && cd build
+ cmake .. -DBUILD_TEST=TRUE -DBUILD_OPENSSL_PLATFORM=linux-generic32 -DBUILD_LOG4CPLUS_HOST=arm-linux -DCMAKE_INSTALL_PREFIX=.
+ make
+ make install
+ file libKinesisVideoProducer.so
+
+ linux-build-gcc-static:
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ sudo apt clean && sudo apt update
+ sudo apt install -y libunwind-dev
+ sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ - name: Build repository
+ run: |
+ mkdir build && cd build
+ cmake .. -DBUILD_GSTREAMER_PLUGIN=ON -DBUILD_STATIC=ON
+ make
+
+ linux-build-gcc-shared:
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ sudo apt clean && sudo apt update
+ sudo apt install -y libunwind-dev
+ sudo apt-get install libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
+ - name: Build repository
+ run: |
+ mkdir build && cd build
+ cmake .. -DBUILD_DEPENDENCIES=OFF -DBUILD_GSTREAMER_PLUGIN=ON -DBUILD_STATIC=OFF -DBUILD_SHARED_LIBS=ON
+ make
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 7d4c9724..f2fa5f73 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -13,10 +13,10 @@ name: "CodeQL"
on:
push:
- branches: [ master ]
+ branches: [ master, develop ]
pull_request:
# The branches below must be a subset of the branches above
- branches: [ master ]
+ branches: [ master, develop ]
schedule:
- cron: '41 13 * * 2'
@@ -35,11 +35,11 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -50,7 +50,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ uses: github/codeql-action/autobuild@v2
# âšī¸ Command-line programs to run using the OS shell.
# đ https://git.io/JvXDl
@@ -64,4 +64,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
diff --git a/.gitignore b/.gitignore
index 82b4966f..b4dc61e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,5 @@ open-source/libautoconf
outputs
tags
dependency
+
+.vs
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 5d756814..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,117 +0,0 @@
-language: cpp
-sudo: true
-
-branches:
- only:
- - master
-
-cache:
-- directories:
- - $HOME/.cache
-
-addons:
- apt:
- packages:
- - autoconf
- - libgstreamer1.0-dev
- - libgstreamer-plugins-base1.0-dev
- - default-jdk
-
-script:
- - export AWS_KVS_LOG_LEVEL=2
- - make
- - ulimit -c unlimited -S
- - timeout --signal=SIGABRT 20m ./tst/producerTest
-
-matrix:
- allow_failures:
- - env: allowTestFail=true
-
- include:
- # MacOS Builds
- - name: "OSX GCC"
- os: osx
- compiler: gcc
- before_install: skip # Only for Linux
- before_script:
- - mkdir build && cd build && cmake .. -DBUILD_TEST=TRUE
- script:
- - make
- - ./tst/producerTest
-
- - name: "OSX Clang"
- os: osx
- compiler: clang
- before_install: skip # Only for Linux
- before_script:
- - mkdir build && cd build && cmake .. -DBUILD_TEST=TRUE -DCOMPILER_WARNINGS=TRUE
- script:
- - make
- - ./tst/producerTest
-
- # Code Coverage
- - name: "Linux GCC Code Coverage"
- os: linux
- compiler: gcc
- before_script:
- - mkdir build && cd build && cmake .. -DCODE_COVERAGE=TRUE -DBUILD_TEST=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE
- after_success:
- - for test_file in $(find CMakeFiles/KinesisVideoProducer.dir gstkvssink.dir KinesisVideoProducerJNI.dir -name '*.gcno'); do gcov $test_file; done
- - bash <(curl -s https://codecov.io/bash)
-
- # AddressSanitizer
- - name: "Linux Clang AddressSanitizer"
- os: linux
- compiler: clang
- env: ASAN_OPTIONS=detect_odr_violation=0:detect_leaks=1
- before_script: mkdir build && cd build && cmake .. -DBUILD_TEST=TRUE -DADDRESS_SANITIZER=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE
-
- # UndefinedBehaviorSanitizer
- - name: "Linux Clang UndefinedBehaviorSanitizer"
- os: linux
- compiler: clang
- env: allowTestFail=true
- # env: UBSAN_OPTIONS=halt_on_error=1
- before_script: mkdir build && cd build && cmake .. -DBUILD_TEST=TRUE -DUNDEFINED_BEHAVIOR_SANITIZER=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE
-
- # MemorySanitizer
- - name: "Linux Clang MemorySanitizer"
- env: allowTestFail=true
- before_install:
- - mkdir build
- - docker run -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -w /src/build -dit --name msan-tester -v $(pwd):/src seaduboi/kvs-msan-tester
- - msan-tester() { docker exec -it msan-tester "$@"; }
- script:
- - msan-tester cmake .. -DBUILD_DEPENDENCIES=FALSE -DBUILD_TEST=TRUE -DMEMORY_SANITIZER=TRUE -DCMAKE_CXX_FLAGS="-stdlib=libc++ -L/usr/src/libcxx_msan/lib -lc++abi -I/usr/src/libcxx_msan/include -I/usr/src/libcxx_msan/include/c++/v1" -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE
- - msan-tester make
- - msan-tester ./tst/producerTest
-
- # ThreadSanitizer
- - name: "Linux Clang ThreadSanitizer"
- os: linux
- compiler: clang
- env: allowTestFail=true
- # env: TSAN_OPTIONS=halt_on_error=1:suppressions=../tst/suppressions/TSAN.supp
- before_script: mkdir build && cd build && cmake .. -DBUILD_TEST=TRUE -DTHREAD_SANITIZER=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE
-
- # Old Version GCC 4.4
- - name: "Linux GCC 4.4 Build"
- os: linux
- compiler: gcc
- before_script:
- - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- - sudo apt-get -q update
- - sudo apt-get -y install gcc-4.4
- - export CC=gcc-4.4 && mkdir build && cd build && cmake .. -DBUILD_TEST=TRUE -DBUILD_GSTREAMER_PLUGIN=TRUE -DBUILD_JNI=TRUE
-
- - name: "Windows MSVC"
- os: windows
- script:
- - choco install nasm strawberryperl
- - choco install gstreamer --version=1.16.2
- - choco install gstreamer-devel --version=1.16.2 # gstreamer-devel has not been approved yet. Version number must be explicit to install
- - unset CC CC_FOR_BUILD CXX CXX_FOR_BUILD # We want to use MSVC
- - export "PATH=/c/Strawberry/perl/site/bin:/c/Strawberry/perl/bin:/c/Strawberry/c/bin:/c/Program Files/NASM:`pwd`/open-source/local/lib:`pwd`/open-source/local/bin:$PATH"
- - .github/build_windows.bat
- - cd build/tst && ./producerTest.exe
-
diff --git a/CMake/Dependencies/libautoconf-CMakeLists.txt b/CMake/Dependencies/libautoconf-CMakeLists.txt
index 2f295b55..a4544aff 100644
--- a/CMake/Dependencies/libautoconf-CMakeLists.txt
+++ b/CMake/Dependencies/libautoconf-CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.6.3)
project(libautoconf-download NONE)
diff --git a/CMake/Dependencies/libautomake-CMakeLists.txt b/CMake/Dependencies/libautomake-CMakeLists.txt
index 03920f0c..8544a16f 100644
--- a/CMake/Dependencies/libautomake-CMakeLists.txt
+++ b/CMake/Dependencies/libautomake-CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.6.3)
project(libautomake-download NONE)
diff --git a/CMake/Dependencies/libcurl-CMakeLists.txt b/CMake/Dependencies/libcurl-CMakeLists.txt
index 265e9a8d..fc2398d8 100644
--- a/CMake/Dependencies/libcurl-CMakeLists.txt
+++ b/CMake/Dependencies/libcurl-CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.6.3)
project(libcurl-download NONE)
diff --git a/CMake/Dependencies/libgtest-CMakeLists.txt b/CMake/Dependencies/libgtest-CMakeLists.txt
index ebf7bf3b..db88fda4 100644
--- a/CMake/Dependencies/libgtest-CMakeLists.txt
+++ b/CMake/Dependencies/libgtest-CMakeLists.txt
@@ -1,12 +1,12 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.6.3)
project(libgtest-download NONE)
include(ExternalProject)
-
+
ExternalProject_Add(libgtest-download
GIT_REPOSITORY https://github.com/google/googletest.git
- GIT_TAG release-1.8.1
+ GIT_TAG release-1.12.1
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX}
diff --git a/CMake/Dependencies/libjsmn-CMakeLists.txt b/CMake/Dependencies/libjsmn-CMakeLists.txt
index edfed4ce..b16ca0f5 100644
--- a/CMake/Dependencies/libjsmn-CMakeLists.txt
+++ b/CMake/Dependencies/libjsmn-CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.6.3)
project(libjsmn-download NONE)
diff --git a/CMake/Dependencies/libjsmn-add-cmakelists.patch b/CMake/Dependencies/libjsmn-add-cmakelists.patch
index 5fe1aea2..133c61d9 100644
--- a/CMake/Dependencies/libjsmn-add-cmakelists.patch
+++ b/CMake/Dependencies/libjsmn-add-cmakelists.patch
@@ -4,7 +4,7 @@ index 0000000..f4a1d44
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,11 @@
-+cmake_minimum_required(VERSION 2.8)
++cmake_minimum_required(VERSION 3.6.3)
+project(jsmn C)
+
+
diff --git a/CMake/Dependencies/libkvscproducer-CMakeLists.txt b/CMake/Dependencies/libkvscproducer-CMakeLists.txt
index c24cfa7d..35532f94 100644
--- a/CMake/Dependencies/libkvscproducer-CMakeLists.txt
+++ b/CMake/Dependencies/libkvscproducer-CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.6.3)
project(libkvscproducer-download NONE)
@@ -7,7 +7,7 @@ include(ExternalProject)
# clone repo only
ExternalProject_Add(libkvscproducer-download
GIT_REPOSITORY https://github.com/awslabs/amazon-kinesis-video-streams-producer-c.git
- GIT_TAG 99c1a8cd8cec88f99c9c4ce3944b53ae341d1491
+ GIT_TAG 178109a5dbfc5288ba5cf7fab1dc1afd5e2e182b
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/kvscproducer-src"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/kvscproducer-build"
CONFIGURE_COMMAND ""
diff --git a/CMake/Dependencies/liblog4cplus-CMakeLists.txt b/CMake/Dependencies/liblog4cplus-CMakeLists.txt
index 8da0a67b..54fc8a88 100644
--- a/CMake/Dependencies/liblog4cplus-CMakeLists.txt
+++ b/CMake/Dependencies/liblog4cplus-CMakeLists.txt
@@ -1,9 +1,19 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.6.3)
find_program(MAKE_EXE NAMES make)
project(log4cplus-download NONE)
-SET(CONFIGURE_COMMAND ${CMAKE_CURRENT_BINARY_DIR}/build/src/project_log4cplus/configure "CFLAGS=${CMAKE_C_FLAGS}" --prefix=${OPEN_SRC_INSTALL_PREFIX})
+set(BUILD_SHARED_LIBS 1)
+if (BUILD_STATIC)
+ set(BUILD_SHARED_LIBS 0)
+ set(CONFIGURE_STATIC --disable-shared --enable-static)
+endif()
+
+if (DEFINED BUILD_LOG4CPLUS_HOST AND NOT BUILD_LOG4CPLUS_HOST STREQUAL OFF)
+ SET(CONFIGURE_COMMAND ${CMAKE_CURRENT_BINARY_DIR}/build/src/project_log4cplus/configure "CFLAGS=${CMAKE_C_FLAGS}" --prefix=${OPEN_SRC_INSTALL_PREFIX} ${CONFIGURE_STATIC} --host=${BUILD_LOG4CPLUS_HOST})
+else()
+ SET(CONFIGURE_COMMAND ${CMAKE_CURRENT_BINARY_DIR}/build/src/project_log4cplus/configure "CFLAGS=${CMAKE_C_FLAGS}" --prefix=${OPEN_SRC_INSTALL_PREFIX} ${CONFIGURE_STATIC})
+endif()
if (DEFINED CMAKE_OSX_SYSROOT AND NOT CMAKE_OSX_SYSROOT STREQUAL "")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -isysroot${CMAKE_OSX_SYSROOT}")
@@ -15,15 +25,15 @@ include(ExternalProject)
if (WIN32)
ExternalProject_Add(project_log4cplus
GIT_REPOSITORY https://github.com/log4cplus/log4cplus
- GIT_TAG REL_1_2_2
+ GIT_TAG REL_2_0_1
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build
TEST_COMMAND ""
- CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX} -DLOG4CPLUS_BUILD_TESTING=0 -DLOG4CPLUS_BUILD_LOGGINGSERVER=0 -DUNICODE=0
+ CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX} -DLOG4CPLUS_BUILD_TESTING=0 -DLOG4CPLUS_BUILD_LOGGINGSERVER=0 -DUNICODE=0 -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS}
)
else()
ExternalProject_Add(project_log4cplus
GIT_REPOSITORY https://github.com/log4cplus/log4cplus
- GIT_TAG REL_1_2_2
+ GIT_TAG REL_2_0_1
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build
CONFIGURE_COMMAND ${CONFIGURE_COMMAND}
BUILD_COMMAND ${MAKE_EXE}
diff --git a/CMake/Dependencies/libopenssl-CMakeLists.txt b/CMake/Dependencies/libopenssl-CMakeLists.txt
index 6d3c6597..bf4edca2 100644
--- a/CMake/Dependencies/libopenssl-CMakeLists.txt
+++ b/CMake/Dependencies/libopenssl-CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.6.3)
project(libopenssl-download NONE)
@@ -21,7 +21,7 @@ endif()
include(ExternalProject)
ExternalProject_Add(project_libopenssl
GIT_REPOSITORY https://github.com/openssl/openssl.git
- GIT_TAG OpenSSL_1_1_1g
+ GIT_TAG OpenSSL_1_1_1t
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build
CONFIGURE_COMMAND ${CONFIGURE_COMMAND}
BUILD_COMMAND ${MAKE_EXE}
diff --git a/CMake/Utilities.cmake b/CMake/Utilities.cmake
index a4c1bb65..560ebc3a 100644
--- a/CMake/Utilities.cmake
+++ b/CMake/Utilities.cmake
@@ -80,7 +80,7 @@ function(build_dependency lib_name)
# build library
configure_file(
- ${CMAKE_SOURCE_DIR}/CMake/Dependencies/lib${lib_name}-CMakeLists.txt
+ ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Dependencies/lib${lib_name}-CMakeLists.txt
${KINESIS_VIDEO_OPEN_SOURCE_SRC}/lib${lib_name}/CMakeLists.txt COPYONLY)
execute_process(
COMMAND ${CMAKE_COMMAND} ${build_args}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1836e61d..2a2a9132 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,13 +1,12 @@
-if(APPLE AND NOT DEFINED ENV{MACOSX_DEPLOYMENT_TARGET} AND NOT DEFINED ENV{SDKROOT})
- set(CMAKE_OSX_DEPLOYMENT_TARGET ${CMAKE_SYSTEM_VERSION})
-endif()
-
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.6.3)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
include(Utilities)
project(KinesisVideoProducerCpp)
+project(KinesisVideoProducerCpp VERSION 3.4.1)
+
set(CMAKE_CXX_STANDARD 11)
+include(GNUInstallDirs)
# User Flags
option(BUILD_GSTREAMER_PLUGIN "Build kvssink GStreamer plugin" OFF)
@@ -16,6 +15,8 @@ option(BUILD_STATIC "Build with static linkage" OFF)
option(ADD_MUCLIBC "Add -muclibc c flag" OFF)
option(BUILD_DEPENDENCIES "Whether or not to build depending libraries from source" ON)
option(BUILD_OPENSSL_PLATFORM "If buildng OpenSSL what is the target platform" OFF)
+option(BUILD_LOG4CPLUS_HOST "Specify host-name for log4cplus for cross-compilation" OFF)
+
# Developer Flags
option(BUILD_TEST "Build the testing tree" OFF)
@@ -26,12 +27,14 @@ option(MEMORY_SANITIZER "Build with MemorySanitizer" OFF)
option(THREAD_SANITIZER "Build with ThreadSanitizer" OFF)
option(UNDEFINED_BEHAVIOR_SANITIZER "Build with UndefinedBehaviorSanitizer" OFF)
+add_definitions(-DCPP_VERSION_STRING=\"${PROJECT_VERSION}\")
+
set(CMAKE_MACOSX_RPATH TRUE)
get_filename_component(ROOT "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE)
# static settings
if(BUILD_STATIC)
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
set(LINKAGE STATIC)
elseif(WIN32)
set(LINKAGE STATIC)
@@ -53,6 +56,7 @@ endif()
add_definitions(-DKVS_CA_CERT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/certs/cert.pem")
add_definitions(-DCMAKE_DETECTED_CACERT_PATH)
+
if(BUILD_DEPENDENCIES)
if(NOT EXISTS ${KINESIS_VIDEO_OPEN_SOURCE_SRC})
file(MAKE_DIRECTORY ${KINESIS_VIDEO_OPEN_SOURCE_SRC}/local)
@@ -73,7 +77,13 @@ if(BUILD_DEPENDENCIES)
build_dependency(autoconf)
build_dependency(automake)
endif()
- build_dependency(log4cplus)
+
+ if(BUILD_LOG4CPLUS_HOST)
+ set(BUILD_ARGS -DBUILD_LOG4CPLUS_HOST=${BUILD_LOG4CPLUS_HOST})
+ build_dependency(log4cplus ${BUILD_ARGS} -DBUILD_STATIC=${BUILD_STATIC})
+ else()
+ build_dependency(log4cplus -DBUILD_STATIC=${BUILD_STATIC})
+ endif()
message(STATUS "Finished building dependencies.")
endif()
@@ -117,7 +127,11 @@ else()
endif()
if (WIN32)
- set(PKG_CONFIG_EXECUTABLE "C:\\gstreamer\\1.0\\x86_64\\bin\\pkg-config.exe")
+ if(EXISTS "C:\\gstreamer\\1.0\\msvc_x86_64\\bin\\pkg-config.exe")
+ set(PKG_CONFIG_EXECUTABLE "C:\\gstreamer\\1.0\\msvc_x86_64\\bin\\pkg-config.exe")
+ else()
+ set(PKG_CONFIG_EXECUTABLE "D:\\gstreamer\\1.0\\msvc_x86_64\\bin\\pkg-config.exe")
+ endif()
endif()
############# Enable Sanitizers ############
@@ -168,6 +182,10 @@ include_directories(${KINESIS_VIDEO_PRODUCER_CPP_SRC}/src/credential-providers)
include_directories(${KINESIS_VIDEO_PRODUCER_CPP_SRC}/src/common)
include_directories(${KINESIS_VIDEO_PRODUCER_CPP_SRC}/src/JNI/include)
+install(
+ DIRECTORY ${KINESIS_VIDEO_PRODUCER_CPP_SRC}/src
+ DESTINATION .)
+
add_library(KinesisVideoProducer ${LINKAGE} ${PRODUCER_CPP_SOURCE_FILES})
target_link_libraries(
KinesisVideoProducer
@@ -176,6 +194,12 @@ target_link_libraries(
${Log4cplus}
${LIBCURL_LIBRARIES})
+install(
+ TARGETS KinesisVideoProducer
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+
if(BUILD_JNI)
find_package(JNI REQUIRED)
include_directories(${JNI_INCLUDE_DIRS})
@@ -190,11 +214,18 @@ if(BUILD_GSTREAMER_PLUGIN)
include_directories(${GST_APP_INCLUDE_DIRS})
link_directories(${GST_APP_LIBRARY_DIRS})
- add_library(gstkvssink MODULE ${GST_PLUGIN_SOURCE_FILES})
+ if(BUILD_STATIC)
+ add_library(gstkvssink STATIC ${GST_PLUGIN_SOURCE_FILES})
+ else()
+ add_library(gstkvssink MODULE ${GST_PLUGIN_SOURCE_FILES})
+ endif()
target_link_libraries(gstkvssink PRIVATE ${GST_APP_LIBRARIES} KinesisVideoProducer)
+ add_executable(kvssink_gstreamer_sample samples/kvssink_gstreamer_sample.cpp)
+ target_link_libraries(kvssink_gstreamer_sample ${GST_APP_LIBRARIES} KinesisVideoProducer)
+
add_executable(kvs_gstreamer_sample samples/kvs_gstreamer_sample.cpp)
- target_link_libraries(kvs_gstreamer_sample ${GST_APP_LIBRARIES} KinesisVideoProducer)
+ target_link_libraries(kvs_gstreamer_sample ${GST_APP_LIBRARIES} KinesisVideoProducer kvspic)
add_executable(kvs_gstreamer_multistream_sample samples/kvs_gstreamer_multistream_sample.cpp)
target_link_libraries(kvs_gstreamer_multistream_sample ${GST_APP_LIBRARIES} KinesisVideoProducer)
diff --git a/README.md b/README.md
index 7c3c865b..473caa5e 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
Amazon Kinesis Video Streams | Secure Video Ingestion for Analysis & Storage
-
+
@@ -24,8 +24,7 @@
* GStreamer Plugin (kvssink)
* JNI
-Amazon Kinesis Video Streams Producer SDK for C/C++ makes it easy to build an on-device application that securely connects to a video stream, and reliably publishes video and other media data to Kinesis Video Streams. It takes care of all the underlying tasks required to package the frames and fragments generated by the device's media pipeline. The SDK also hand
-les stream creation, token rotation for secure and uninterrupted streaming, processing acknowledgements returned by Kinesis Video Streams, and other tasks.
+Amazon Kinesis Video Streams Producer SDK for C/C++ makes it easy to build an on-device application that securely connects to a video stream, and reliably publishes video and other media data to Kinesis Video Streams. It takes care of all the underlying tasks required to package the frames and fragments generated by the device's media pipeline. The SDK also handles stream creation, token rotation for secure and uninterrupted streaming, processing acknowledgements returned by Kinesis Video Streams, and other tasks.
## Build
### Download
@@ -62,9 +61,22 @@ On Ubuntu and Raspberry Pi OS you can get the libraries by running
```
$ sudo apt-get install libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
```
+### Setup desired log level:
+Set up the desired log level. The log levels currently available with `log4cplus` are:
+1. `TRACE`
+2. `DEBUG`
+3. `INFO`
+4. `WARN`
+5. `ERROR`
+6. `FATAL`
+
+To set a log level, update the log level value [here](https://github.com/awslabs/amazon-kinesis-video-streams-producer-sdk-cpp/blob/master/kvs_log_configuration#L1)
+
+Note: The default log level is `DEBUG`
#### Cross-Compilation
-If you wish to cross-compile `CC` and `CXX` are respected when building the library and all its dependencies. See our [.travis.yml](.travis.yml) for an example of this. Every commit is cross compiled to ensure that it continues to work.
+If you wish to cross-compile `CC` and `CXX` are respected when building the library and all its dependencies. See our [ci.yml](https://github.com/awslabs/amazon-kinesis-video-streams-producer-sdk-cpp/blob/develop/.github/workflows/ci.yml) for an example of this. Every commit is cross compiled to ensure that it continues to work.
+Please note that GStreamer is not cross-compiled as a part of the cross-compilation of the KVS-SDK, customers will have to cross-compile it separately.
#### CMake Arguments
@@ -81,10 +93,11 @@ You can pass the following options to `cmake ..`.
* `-DTHREAD_SANITIZER` -- Build with ThreadSanitizer
* `-DUNDEFINED_BEHAVIOR_SANITIZER` Build with UndefinedBehaviorSanitizer
* `-DALIGNED_MEMORY_MODEL` Build for aligned memory model only devices. Default is OFF.
+* `-DBUILD_LOG4CPLUS_HOST` Specify host-name for log4cplus for cross-compilation. Default is OFF.
#### To Include JNI
-JNI examples are NOT built by default. If you wish to build JNI you MUST add -DBUILD_JNI=TRUE when running cmake:
+JNI examples are NOT built by default. If you wish to build JNI you MUST add `-DBUILD_JNI=TRUE` when running `cmake`:
```
cmake -DBUILD_JNI=TRUE
@@ -92,15 +105,14 @@ cmake -DBUILD_JNI=TRUE
#### To Include Building GStreamer Sample Programs
-The GStreamer plugin and samples are NOT built by default. If you wish to build them you MUST add -DBUILD_GSTREAMER_PLUGIN=TRUE when running cmake:
+The GStreamer plugin and samples are NOT built by default. If you wish to build them you MUST add `-DBUILD_GSTREAMER_PLUGIN=TRUE` when running cmake:
```
-cmake -DBUILD_GSTREAMER_PLUGIN=TRUE
+cmake -DBUILD_GSTREAMER_PLUGIN=TRUE ..
```
-
### Compiling
-After running cmake, in the same build directior run make:
+After running cmake, in the same build directory run `make`:
```
make
@@ -108,8 +120,10 @@ make
On Windows you should run `nmake` instead of `make`
-In your build directory you will now have shared objects for all the targets you have selected
+In your build directory you will now have shared objects for all the targets you have selected.
+### Installing the library
+If the library needs to be installed, run `make install`. This will install in default directory based on system. To install in another directory, run `cmake` with the `-DCMAKE_INSTALL_PREFIX` option with the desired directory before running `make install`
## Run
### GStreamer Plugin (kvssink)
@@ -170,20 +184,44 @@ The kvssink element has the following required parameters:
For examples of common use cases you can look at [Example: Kinesis Video Streams Producer SDK GStreamer Plugin](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/examples-gstreamer-plugin.html)
+#### To Include Images/Events feature
+
+The images feature is available in the sample kvs_gstreamer_audio_video_sample.cpp . To enable it include the argument "-e "
+event option is a string that can be:
+
+notification -- for a notification event
+image -- for an image event
+both -- for both
+
+The events will start on the 2nd key frame, and will reoccur every 200 key frames. If you would to change this frequence you can edit the sample.
+
+#### To run from a file
+in the kvs_gstreamer_audio_video_sample.cpp if you would like to upload from a file, include the option flag -f
+
+## Running in offline mode
+By default, the samples run in near realtime mode. To set offline mode, set streamInfo.streamCaps.streamingType to `STREAMING_TYPE_OFFLINE`, where, `streamInfo` is of type `StreamInfo`, `streamCaps` is of type `StreamCaps` and `streamingType` is of type `STREAMING_TYPE`.
## Dockerscripts
* The sample docker scripts for RTSP plugin, raspberry pi and linux can be found in the [Kinesis demos repository](https://github.com/aws-samples/amazon-kinesis-video-streams-demos/tree/master/producer-cpp).
## DEBUG
+* When building the JNI, if you run into a cmake error `Could NOT find JNI (missing: JAVA_INCLUDE_PATH JAVA_INCLUDE_PATH2 JAVA_AWT_INCLUDE_PATH)`, make sure your environment variables are set correctly:
+`export JAVA_INCLUDE_PATH2=/Library/Java/JavaVirtualMachines//Contents/Home/include` or `export JAVA_INCLUDE_PATH2=$JAVA_HOME/include` for Mac OS.
+`export JAVA_INCLUDE_PATH2='/usr/java//include'` for Linux.
* If you are successfully streaming but run into issue with playback. You can do `export KVS_DEBUG_DUMP_DATA_FILE_DIR=/path/to/directory` before streaming. Producer will then dump MKV files into that path. The file is exactly what KVS will receive. You can use [MKVToolNIX](https://mkvtoolnix.download/index.html) to check that everything looks correct. You can also try to play the MKV file in compatible players.
+* If you would like to visualize the GStreamer pipeline being constructed in a GStreamer application, include the following after the elements have been linked:
+`GST_DEBUG_BIN_TO_DOT_FILE(, GST_DEBUG_GRAPH_SHOW_ALL, );`
+For example, if the application created a pipeline object `GstPipeline* pipeline = gst_pipeline_new("test-pipeline")`, and you would like to see the visualized pipeline with filename pipeline, add:
+`GST_DEBUG_BIN_TO_DOT_FILE((GstBin*) pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");`. Also ensure to set the path to where you would like the file to be stored. `export GST_DEBUG_DUMP_DOT_DIR=.`. The file generated would be a `.dot` format. Convert to PDF to check the visualized pipeline. Also, this requires `graphviz` to be installed. So make sure to install that.
+
## FAQ
-* Is CPP-SDK and GStreamer supported on Mac/Windows/Linux (Supported Platforms)
+* Is CPP-SDK and GStreamer supported on Mac/Windows/Linux (Supported Platforms)?
Yes! We have FAQs and platform specific instructions for [Windows](docs/windows.md), [MacOS](docs/macos.md) and [Linux](docs/linux.md)
## Development
-The repository is using master branch as the aggregation and all of the feature development is done in appropriate feature branches. The PRs (Pull Requests) are cut on a feature branch and once approved with all the checks passed they can be merged by a click of a button on the PR tool. The master branch should always be build-able and all the tests should be passing. We are welcoming any contribution to the code base.
+The repository is using develop branch as the aggregation and all of the feature development is done in appropriate feature branches. The PRs (Pull Requests) are cut on a feature branch and once approved with all the checks passed they can be merged by a click of a button on the PR tool. The master branch should always be build-able and all the tests should be passing. We are welcoming any contribution to the code base. The master branch contains our most recent release cycle from develop.
### Release
The repository is under active development and even with incremental unit test coverage where some of the tests are actually full integration tests, we require more rigorous internal testing in order to 'cut' release versions. The release is cut against a particular commit that gets approved. The general philosophy is to cut a release when a set of commits contribute to a self-containing feature or when we add major internal functionality improvements.
diff --git a/docs/buffering.md b/docs/buffering.md
index 4c313036..bdb677ed 100644
--- a/docs/buffering.md
+++ b/docs/buffering.md
@@ -1,6 +1,6 @@
### Producer SDK buffering
-Producer SDK PIC handles the frame buffering by separating the physical storage called Content Store from the logical view known as Content View. The Content Store is a simple storage based on an internal heap implementation. The DeviceInfo.StorageInfo https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/master/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L1012 controls the type of the storage and the amount of memory to reserve. The Content Store is shared among all of the streams within the client object. Content View, on the other hand, contains the temporal logic of the frames that are in the buffer. The Content View is created per each stream. As the frames are pushed to the SDK using PutFrame API, the frames are being packaged and put in the Content Store and the reference with timestamp information is being produced into the Content View.
+Producer SDK PIC handles the frame buffering by separating the physical storage called Content Store from the logical view known as Content View. The Content Store is a simple storage based on an internal heap implementation. The DeviceInfo.StorageInfo https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/032aa7843f58151f41fb0ab9b473a02338e6f76f/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L1115 controls the type of the storage and the amount of memory to reserve. The Content Store is shared among all of the streams within the client object. Content View, on the other hand, contains the temporal logic of the frames that are in the buffer. The Content View is created per each stream. As the frames are pushed to the SDK using PutFrame API, the frames are being packaged and put in the Content Store and the reference with timestamp information is being produced into the Content View.

@@ -9,12 +9,12 @@ Producer SDK PIC handles the frame buffering by separating the physical storage
### Content Store
Content store is an abstraction of the underlying storage that can have different implementations. By default, the implementation is based on low-fragmentation, tightly packed heap which can provide good performance characteristics processing "rolling window"-like allocations of similar sizes with minimal waste of memory/fragmentation. Moreover, the content store abstraction allows for dynamic resizing and indirect mapping which are useful in cases of "hybrid" store chaining with spill-over (for example RAM-based heap with spill-over on eMMC-backed storage).
-Storage overflow callback (https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/master/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L1409) will be called when there is less than 5% of storage available.
+Storage overflow callback (https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/032aa7843f58151f41fb0ab9b473a02338e6f76f/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L1579) will be called when there is less than 5% of storage available.
### Content View
-Content View can be thought of as a list of frame information that includes timestamps, flags and the handle to the actual storage. The new frames are produced into the Head (and the head moves forward) and the frames are being read from Current pointer. The last frame is pointed to by Tail pointer. The tail moves forward either when the SDK receives Persisted ACK or on buffer pressure when the duration window reaches the buffer duration that's specified in StreamInfo.StreamCaps https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/master/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L882. As the frames are pushed out of the tail, their content is being purged from the Content Store. If the Tail catches up with Current, a dropped frame callback will be called to notify the application of dropped frames.
+Content View can be thought of as a list of frame information that includes timestamps, flags and the handle to the actual storage. The new frames are produced into the Head (and the head moves forward) and the frames are being read from Current pointer. The last frame is pointed to by Tail pointer. The tail moves forward either when the SDK receives Persisted ACK or on buffer pressure when the duration window reaches the buffer duration that's specified in StreamInfo.StreamCaps https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/032aa7843f58151f41fb0ab9b473a02338e6f76f/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L982. As the frames are pushed out of the tail, their content is being purged from the Content Store. If the Tail catches up with Current, a dropped frame callback will be called to notify the application of dropped frames.
The production into the buffer is done at frame granularity (PutFrame takes an entire frame) whereas the Networking stack reading the frame bits operates on bit granularity. The MKV packaging format allows the frames to be "streamable" where the frame bits can be streamed out before the last frame of the fragment can be produced into the buffer, thus, making low-latency streaming possible.
@@ -58,17 +58,17 @@ Frames can be omitted by not only "dropping" them but also "skipping" them. Ther
### Stream staleness
-KVS streaming abstracts the actual networking protocol and doesn't rely on the network-level ACKs. Moreover, the network level ACKs are still insufficient for guaranteeing the delivery of the video data to the backend. Stream staleness is calculated by measuring the time between the successive Buffering ACKs. If this time is above the threshold specified in StreamCaps (https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/master/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L941) the StreamConnectionStaleFunc callback will be executed. The default handler for this callback will reset the connection but the applications could choose to perform other actions if needed.
+KVS streaming abstracts the actual networking protocol and doesn't rely on the network-level ACKs. Moreover, the network level ACKs are still insufficient for guaranteeing the delivery of the video data to the backend. Stream staleness is calculated by measuring the time between the successive Buffering ACKs. If this time is above the threshold specified in StreamCaps (https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/032aa7843f58151f41fb0ab9b473a02338e6f76f/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L1041) the StreamConnectionStaleFunc callback will be executed. The default handler for this callback will reset the connection but the applications could choose to perform other actions if needed.
### Stream rollback
-KVS SDK is configured by default to re-connect/re-stream on error/disconnection as set in StreamInfo.StreamCaps.recoverOnError https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/master/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L926
+KVS SDK is configured by default to re-connect/re-stream on error/disconnection as set in StreamInfo.StreamCaps.recoverOnError https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/032aa7843f58151f41fb0ab9b473a02338e6f76f/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L1012
During streaming, the connection could get disconnected or an error could be generated by the backend in a form of an error ACK. In these cases, the SDK attempts to drive the internal state machinery through the states and reconnect. Once reconnected, it will need to make a decision from where to restart the streaming in order to still provide durability guarantees and ensure that the frames in the internal buffer are persisted.
1) Determine the timestamp on the internal Content View timeline on when the issue happens. For example, if it's a simple network disconnect then the Current from the content view is used whereas if there is an error ACK causing the termination of the connection and if we have a timestamp specified in the ACK then we set the time to the timestamp specified in the error ACK.
2) Check the reason for disconnection. Some error indicate that the stream has an issue (for example error ACKs) in which case the SDK determines that the processing host is still alive and the data is still in it's buffer but the fragment which errored is inherently can not be ingested even if it's retried so it needs to be skipped. There are other error class category that indicate a simple networking timeout for example, in which case we determine that the host might still be alive and the so-far ingested but not yet persisted data might still be in the ingestion host internal buffer. While, there are other cases that we determine for sure that the host itself is likely to be dead and the data in the buffer that hasn't been durably persisted is gone.
-3) If the error is determined to be caused by a "dead" host then the rollback should roll all the way to the fragment that has a timestamp of last Persisted ACK fragments next timestamp within the buffer or the rollback duration - whichever is less. Rollback duration is specified in StreamInfo.StreamCaps.replayDuration https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/master/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L948
+3) If the error is determined to be caused by a "dead" host then the rollback should roll all the way to the fragment that has a timestamp of last Persisted ACK fragments next timestamp within the buffer or the rollback duration - whichever is less. Rollback duration is specified in StreamInfo.StreamCaps.replayDuration https://github.com/awslabs/amazon-kinesis-video-streams-pic/blob/032aa7843f58151f41fb0ab9b473a02338e6f76f/src/client/include/com/amazonaws/kinesis/video/client/Include.h#L1034
4) If the error is determined to be caused by a connection issue then the host is likely to be "alive" and the not-yet persisted data is accessible from within the hosts buffer. In this case the rollback happens from the current position back until we 'replayDuration' or last Received ACK fragments next fragment. NOTE: in both 3) and 4) the Received and Persisted ACK fragment have been already ingested as the ACK timestamp for Persisted and Received ACKs is the timestamp of the first frame of the Fragment being ACK-ed.
5) Restarted stream will skip over the fragments which are marked as "skip". These are the fragments that have been determined to cause issues with the backend parsing (error ACK).
diff --git a/docs/linux.md b/docs/linux.md
index 238cae04..0d148fa1 100644
--- a/docs/linux.md
+++ b/docs/linux.md
@@ -47,15 +47,14 @@ Device found:
###### Running the `gst-launch-1.0` command to start streaming from a RTSP camera source.
```
-$ gst-launch-1.0 -v rtspsrc location=rtsp://YourCameraRtspUrl short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au ! h264parse ! kvssink stream-name=YourStreamName storage-size=128
+$ gst-launch-1.0 -v rtspsrc location=rtsp://YourCameraRtspUrl short-header=TRUE ! rtph264depay ! h264parse ! kvssink stream-name=YourStreamName storage-size=128
```
**Note:** If you are using **IoT credentials** then you can pass them as parameters to the gst-launch-1.0 command
```
-$ gst-launch-1.0 -v rtspsrc location="rtsp://YourCameraRtspUrl" short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au !
- h264parse ! kvssink stream-name="iot-stream" iot-certificate="iot-certificate,endpoint=endpoint,cert-path=/path/to/certificate,key-path=/path/to/private/key,ca-path=/path/to/ca-cert,role-aliases=role-aliases"
+$ gst-launch-1.0 -v rtspsrc location="rtsp://YourCameraRtspUrl" short-header=TRUE ! rtph264depay ! h264parse ! kvssink stream-name="iot-stream" iot-certificate="iot-certificate,endpoint=endpoint,cert-path=/path/to/certificate,key-path=/path/to/private/key,ca-path=/path/to/ca-cert,role-aliases=role-aliases"
```
-You can find the RTSP URL from your IP camera manual or manufacturers product page.
+You can find the RTSP URL from your IP camera manual or manufacturers product page. For more information on how to set up IoT/role policies and role-aliases, please refer to [iot-based-credential-provider](auth.md#iot-based-credential-provider) and https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/how-iot.html.
###### Running the `gst-launch-1.0` command to start streaming from USB camera source in **Ubuntu**.
```
@@ -101,6 +100,14 @@ if your camera supports outputting h264 encoded stream directly, then you can us
gst-launch-1.0 -v v4l2src device=/dev/video0 ! h264parse ! video/x-h264,stream-format=avc,alignment=au ! kvssink name=sink stream-name="my-stream-name" access-key="YourAccessKey" secret-key="YourSecretKey" alsasrc device=hw:1,0 ! audioconvert ! avenc_aac ! queue ! sink.
```
+##### Running the `gst-launch-1.0` command with Iot-certificate and different stream-names than the thing-name
+
+**Note:** Supply a the matching iot-thing-name (that the certificate points to) and we can stream to multiple stream-names (without the stream-name needing to be the same as the thing-name) using the same certificate credentials. iot-thing-name and stream-name can be completely different as long as there is a policy that allows the thing to write to the kinesis stream
+```
+$ gst-launch-1.0 -v rtspsrc location="rtsp://YourCameraRtspUrl" short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au !
+ h264parse ! kvssink name=aname storage-size=512 iot-certificate="iot-certificate,endpoint=xxxxx.credentials.iot.ap-southeast-2.amazonaws.com,cert-path=/greengrass/v2/thingCert.crt,key-path=/greengrass/v2/privKey.key,ca-path=/greengrass/v2/rootCA.pem,role-aliases=KvsCameraIoTRoleAlias,iot-thing-name=myThingName123" aws-region="ap-southeast-2" log-config="/etc/mtdata/kvssink-log.config" stream-name=myThingName123-video1
+```
+
##### Running the GStreamer webcam sample application
The sample application `kvs_gstreamer_sample` in the `build` directory uses GStreamer pipeline to get video data from the camera. Launch it with a stream name and it will start streaming from the camera. The user can also supply a streaming resolution (width and height) through command line arguments.
@@ -127,6 +134,14 @@ Change your current working directory to `build`. Launch the sample application
AWS_ACCESS_KEY_ID=YourAccessKeyId AWS_SECRET_ACCESS_KEY=YourSecretAccessKey ./kvs_gstreamer_sample
```
+##### Running the GStreamer sample application to upload h264 *video* with kvssink
+
+`kvs_gstreamer_sample` is functionally identical to `kvs_gstreamer_sample` except it uses kvssink instead of appsink.
+
+```
+AWS_ACCESS_KEY_ID=YourAccessKeyId AWS_SECRET_ACCESS_KEY=YourSecretAccessKey ./kvssink_gstreamer_sample
+```
+
##### Running the GStreamer sample application to upload a *audio and video* file
`kvs_gstreamer_audio_video_sample` supports uploading a video that is either MKV, MPEGTS, or MP4. The sample application expects the video is encoded in H264 and audio is encoded in AAC format. Note: If your media uses a different format, then you can revise the pipeline elements in the sample application to suit your media format.
@@ -134,7 +149,7 @@ AWS_ACCESS_KEY_ID=YourAccessKeyId AWS_SECRET_ACCESS_KEY=YourSecretAccessKey ./kv
Change your current working directory to `build`. Launch the sample application with a stream name and a path to the file and it will start streaming.
```
-AWS_ACCESS_KEY_ID=YourAccessKeyId AWS_SECRET_ACCESS_KEY=YourSecretAccessKey ./kvs_gstreamer_audio_video_sample
+AWS_ACCESS_KEY_ID=YourAccessKeyId AWS_SECRET_ACCESS_KEY=YourSecretAccessKey ./kvs_gstreamer_audio_video_sample -f
```
##### Running the GStreamer sample application to stream audio and video from live source
diff --git a/docs/macos.md b/docs/macos.md
index e575587a..0bcd3c5c 100644
--- a/docs/macos.md
+++ b/docs/macos.md
@@ -25,14 +25,13 @@ Device found:
###### Running the `gst-launch-1.0` command to start streaming from RTSP camera source.
```
-$ gst-launch-1.0 rtspsrc location=rtsp://YourCameraRtspUrl short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au ! h264parse ! kvssink stream-name=YourStreamName storage-size=128 access-key="YourAccessKey" secret-key="YourSecretKey"
+$ gst-launch-1.0 rtspsrc location=rtsp://YourCameraRtspUrl short-header=TRUE ! rtph264depay ! h264parse ! kvssink stream-name=YourStreamName storage-size=128 access-key="YourAccessKey" secret-key="YourSecretKey"
```
**Note:** If you are using **IoT credentials** then you can pass them as parameters to the gst-launch-1.0 command
```
-$ gst-launch-1.0 rtspsrc location=rtsp://YourCameraRtspUrl short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au !
- h264parse ! kvssink stream-name="iot-stream" iot-certificate="iot-certificate,endpoint=endpoint,cert-path=/path/to/certificate,key-path=/path/to/private/key,ca-path=/path/to/ca-cert,role-aliases=role-aliases"
+$ gst-launch-1.0 rtspsrc location=rtsp://YourCameraRtspUrl short-header=TRUE ! rtph264depay ! h264parse ! kvssink stream-name="iot-stream" iot-certificate="iot-certificate,endpoint=endpoint,cert-path=/path/to/certificate,key-path=/path/to/private/key,ca-path=/path/to/ca-cert,role-aliases=role-aliases"
```
You can find the RTSP URL from your IP camera manual or manufacturers product page.
@@ -60,6 +59,14 @@ The pipeline above uses default video and audio source on a Mac. If you have an
gst-launch-1.0 -v avfvideosrc device-index=1 ! videoconvert ! vtenc_h264_hw allow-frame-reordering=FALSE realtime=TRUE max-keyframe-interval=45 ! kvssink name=sink stream-name="my_stream_name" access-key="YourAccessKeyId" secret-key="YourSecretAccessKey" osxaudiosrc device=67 ! audioconvert ! avenc_aac ! queue ! sink.
```
+##### Running the `gst-launch-1.0` command with Iot-certificate and different stream-names than the thing-name
+
+**Note:** Supply a the matching iot-thing-name (that the certificate points to) and we can stream to multiple stream-names (without the stream-name needing to be the same as the thing-name) using the same certificate credentials. iot-thing-name and stream-name can be completely different as long as there is a policy that allows the thing to write to the kinesis stream
+```
+$ gst-launch-1.0 -v rtspsrc location="rtsp://YourCameraRtspUrl" short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au !
+ h264parse ! kvssink name=aname storage-size=512 iot-certificate="iot-certificate,endpoint=xxxxx.credentials.iot.ap-southeast-2.amazonaws.com,cert-path=/greengrass/v2/thingCert.crt,key-path=/greengrass/v2/privKey.key,ca-path=/greengrass/v2/rootCA.pem,role-aliases=KvsCameraIoTRoleAlias,iot-thing-name=myThingName123" aws-region="ap-southeast-2" log-config="/etc/mtdata/kvssink-log.config" stream-name=myThingName123-video1
+```
+
##### Running the GStreamer webcam sample application
The sample application `kinesis_video_gstreamer_sample_app` in the `build` directory uses GStreamer pipeline to get video data from the camera. Launch it with a stream name and it will start streaming from the camera. The user can also supply a streaming resolution (width and height) through command line arguments.
@@ -86,6 +93,15 @@ Change your current working directory to `build`. Launch the sample application
AWS_ACCESS_KEY_ID=YourAccessKeyId AWS_SECRET_ACCESS_KEY=YourSecretAccessKey ./kvs_gstreamer_sample
```
+##### Running the GStreamer sample application to upload h264 *video* with kvssink
+
+`kvssink_gstreamer_sample` is functionally identical to `kvs_gstreamer_sample` except it uses kvssink instead of appsink.
+
+```
+AWS_ACCESS_KEY_ID=YourAccessKeyId AWS_SECRET_ACCESS_KEY=YourSecretAccessKey ./kvssink_gstreamer_sample
+```
+
+
###### Running the `gst-launch-1.0` command to upload [MKV](https://www.matroska.org/) file that contains both *audio and video* in **Mac-OS**. Note that video should be H264 encoded and audio should be AAC encoded.
```
@@ -224,3 +240,9 @@ The projects depend on the following open source components. Running `CMake` wil
* gst-plugins-bad
* gst-plugins-ugly
* [x264]( https://www.videolan.org/developers/x264.html)
+
+
+###### GStreamer version 1.20 on MacOS
+
+* While running the sample application, if you encounter an error similar to: `Not all elements could be created.` for RTSP source, set the following:
+`export GST_PLUGIN_PATH=/lib/gstreamer-1.0`
diff --git a/docs/raspberry-pi.md b/docs/raspberry-pi.md
index 9741329d..a062a016 100644
--- a/docs/raspberry-pi.md
+++ b/docs/raspberry-pi.md
@@ -6,7 +6,9 @@ The following steps were tested on a Debian buster platform
Run the following commands to install the prerequisite libraries to get started:
`sudo apt-get install cmake m4 git build-essential`
-`sudo apt-get install gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-tools`
+
+`sudo apt-get install gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-tools gstreamer1.0-omx-rpi
+gstreamer1.0-plugins-base-apps`
Also, install the gstreamer1.0-omx package to get the omxh264enc hardware encoder:
@@ -69,13 +71,12 @@ Device found:
###### Running the `gst-launch-1.0` command to start streaming from a RTSP camera source.
```
-$ gst-launch-1.0 -v rtspsrc location=rtsp://YourCameraRtspUrl short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au ! h264parse ! kvssink stream-name=YourStreamName storage-size=128
+$ gst-launch-1.0 -v rtspsrc location=rtsp://YourCameraRtspUrl short-header=TRUE ! rtph264depay ! h264parse ! kvssink stream-name=YourStreamName storage-size=128
```
**Note:** If you are using **IoT credentials** then you can pass them as parameters to the gst-launch-1.0 command
```
-$ gst-launch-1.0 -v rtspsrc location="rtsp://YourCameraRtspUrl" short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au !
- h264parse ! kvssink stream-name="iot-stream" iot-certificate="iot-certificate,endpoint=endpoint,cert-path=/path/to/certificate,key-path=/path/to/private/key,ca-path=/path/to/ca-cert,role-aliases=role-aliases"
+$ gst-launch-1.0 -v rtspsrc location="rtsp://YourCameraRtspUrl" short-header=TRUE ! rtph264depay ! h264parse ! kvssink stream-name="iot-stream" iot-certificate="iot-certificate,endpoint=endpoint,cert-path=/path/to/certificate,key-path=/path/to/private/key,ca-path=/path/to/ca-cert,role-aliases=role-aliases"
```
You can find the RTSP URL from your IP camera manual or manufacturers product page.
@@ -94,6 +95,12 @@ or use a different encoder
$ gst-launch-1.0 -v v4l2src device=/dev/video0 ! videoconvert ! video/x-raw,format=I420,width=640,height=480,framerate=30/1 ! x264enc bframes=0 key-int-max=45 bitrate=500 tune=zerolatency ! video/x-h264,stream-format=avc,alignment=au ! kvssink stream-name=YourStreamName storage-size=128 access-key="YourAccessKey" secret-key="YourSecretKey"
```
+**Note:** If you are using **Raspberry PI with Bullseye** you have to use another encoder as well as `libcamerasrc` instead of `v4l2src device=/dev/video0`
+
+```
+$ gst-launch-1.0 libcamerasrc ! video/x-raw,width=640,height=480,framerate=30/1,format=I420 ! videoconvert ! v4l2h264enc extra-controls="controls,repeat_sequence_header=1" ! video/x-h264,level='(string)4' ! h264parse ! video/x-h264,stream-format=avc, alignment=au,width=640,height=480,framerate=30/1 ! kvssink stream-name="test-stream" access-key="YourAccessKey" secret-key="YourSecretKey" aws-region="YourRegion"
+```
+
###### Running the `gst-launch-1.0` command to start streaming both audio and video:
@@ -163,7 +170,7 @@ gst-launch-1.0 -v filesrc location="YourAudioVideo.mkv" ! matroskademux name=dem
###### Running the `gst-launch-1.0` command to upload MP4 file that contains both *audio and video*:
```
-gst-launch-1.0 -v filesrc location="YourAudioVideo.mp4" ! qtdemux name=demux ! queue ! h264parse ! video/x-h264,stream-format=avc,alignment=au ! kvssink name=sink stream-name="audio-video-file" access-key="YourAccessKeyId" secret-key="YourSecretAccessKey" streaming-type=offline demux. ! queue ! aacparse ! sink.
+gst-launch-1.0 -v filesrc location="YourAudioVideo.mp4" ! qtdemux name=demux ! queue ! h264parse ! video/x-h264,stream-format=avc,alignment=au ! kvssink name=sink stream-name="audio-video-file" access-key="YourAccessKeyId" secret-key="YourSecretAccessKey" streaming-type=offline demux. ! queue ! aacparse ! sink.
```
###### Running the `gst-launch-1.0` command to upload MPEG2TS file that contains both *audio and video*:
@@ -171,6 +178,13 @@ gst-launch-1.0 -v filesrc location="YourAudioVideo.mp4" ! qtdemux name=demux !
```
gst-launch-1.0 -v filesrc location="YourAudioVideo.ts" ! tsdemux name=demux ! queue ! h264parse ! video/x-h264,stream-format=avc,alignment=au ! kvssink name=sink stream-name="audio-video-file" access-key="YourAccessKeyId" secret-key="YourSecretAccessKey" streaming-type=offline demux. ! queue ! aacparse ! sink.
```
+##### Running the `gst-launch-1.0` command with Iot-certificate and different stream-names than the thing-name
+
+**Note:** Supply a the matching iot-thing-name (that the certificate points to) and we can stream to multiple stream-names (without the stream-name needing to be the same as the thing-name) using the same certificate credentials. iot-thing-name and stream-name can be completely different as long as there is a policy that allows the thing to write to the kinesis stream
+```
+$ gst-launch-1.0 -v rtspsrc location="rtsp://YourCameraRtspUrl" short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au !
+ h264parse ! kvssink name=aname storage-size=512 iot-certificate="iot-certificate,endpoint=xxxxx.credentials.iot.ap-southeast-2.amazonaws.com,cert-path=/greengrass/v2/thingCert.crt,key-path=/greengrass/v2/privKey.key,ca-path=/greengrass/v2/rootCA.pem,role-aliases=KvsCameraIoTRoleAlias,iot-thing-name=myThingName123" aws-region="ap-southeast-2" log-config="/etc/mtdata/kvssink-log.config" stream-name=myThingName123-video1
+```
##### Running the GStreamer sample application to upload a *audio and video* file
diff --git a/docs/windows.md b/docs/windows.md
index fa137d3f..8c710930 100644
--- a/docs/windows.md
+++ b/docs/windows.md
@@ -72,7 +72,7 @@ gst-launch-1.0 ksvideosrc do-timestamp=TRUE ! video/x-raw,width=640,height=480,f
**Note:** If you are using IoT credentials then you can pass them as parameters to the gst-launch-1.0 command
```
-gst-launch-1.0 rtspsrc location="rtsp://YourCameraRtspUrl" short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au ! h264parse ! kvssink stream-name="iot-stream" iot-certificate="iot-certificate,endpoint=endpoint,cert-path=/path/to/certificate,key-path=/path/to/private/key,ca-path=/path/to/ca-cert,role-aliases=role-aliases"
+gst-launch-1.0 rtspsrc location="rtsp://YourCameraRtspUrl" short-header=TRUE ! rtph264depay ! h264parse ! kvssink stream-name="iot-stream" iot-certificate="iot-certificate,endpoint=endpoint,cert-path=/path/to/certificate,key-path=/path/to/private/key,ca-path=/path/to/ca-cert,role-aliases=role-aliases"
```
2.2 Use `gst-launch-1.0` to send audio and raw video to Kinesis Video Streams
@@ -98,7 +98,7 @@ gst-launch-1.0 -v filesrc location="YourAudioVideo.mkv" ! matroskademux name=dem
###### Running the `gst-launch-1.0` command to upload MP4 file that contains both *audio and video*.
```
-gst-launch-1.0 -v filesrc location="YourAudioVideo.mp4" ! qtdemux name=demux ! queue ! h264parse ! video/x-h264,stream-format=avc,alignment=au ! kvssink name=sink stream-name="audio-video-file" access-key="YourAccessKeyId" secret-key="YourSecretAccessKey" streaming-type=offline demux. ! queue ! aacparse ! sink.
+gst-launch-1.0 -v filesrc location="YourAudioVideo.mp4" ! qtdemux name=demux ! queue ! h264parse ! video/x-h264,stream-format=avc,alignment=au ! kvssink name=sink stream-name="audio-video-file" access-key="YourAccessKeyId" secret-key="YourSecretAccessKey" streaming-type=offline demux. ! queue ! aacparse ! sink.
```
###### Running the `gst-launch-1.0` command to upload MPEG2TS file that contains both *audio and video*.
@@ -122,6 +122,8 @@ gst-launch-1.0 -v filesrc location="YourAudioVideo.ts" ! tsdemux name=demux ! q
* Run the sample demo application for sending **webcam video** by executing ` kvs_gstreamer_sample.exe my-test-stream ` or
* Run the sample application for sending **IP camera video** by executing `kvs_gstreamer_sample.exe my-test-rtsp-stream `
* Run the sample application for sending **MKV File** by executing `kvs_gstreamer_sample.exe my-test-stream `
+ * Can also use `kvssink_gstreamer_sample.exe` to upload video.
+ * `kvssink_gstreamer_sample.exe` uses kvssink to upload to kvs while `kvs_gstreamer_sample.exe` uses appsink
4. You can also run the the sample application by executing the following command which will send audio and video to Kinesis Video Streams.
* Change your current working directory to Release directory first.
diff --git a/samples/kvs_log_configuration b/kvs_log_configuration
similarity index 100%
rename from samples/kvs_log_configuration
rename to kvs_log_configuration
diff --git a/samples/include.h b/samples/include.h
new file mode 100644
index 00000000..10dc7f42
--- /dev/null
+++ b/samples/include.h
@@ -0,0 +1,8 @@
+#ifndef _KVS_SAMPLE_INCLUDE_H
+#define _KVS_SAMPLE_INCLUDE_H
+
+#define STATUS_KVS_GSTREAMER_SAMPLE_BASE 0x00080000
+#define STATUS_KVS_GSTREAMER_SAMPLE_ERROR STATUS_KVS_GSTREAMER_SAMPLE_BASE + 0x00000001
+#define STATUS_KVS_GSTREAMER_SAMPLE_INTERRUPTED STATUS_KVS_GSTREAMER_SAMPLE_BASE + 0x00000002
+
+#endif // _KVS_SAMPLE_INCLUDE_H
diff --git a/samples/kvs_gstreamer_audio_video_sample.cpp b/samples/kvs_gstreamer_audio_video_sample.cpp
index 03439cc0..38ea3837 100644
--- a/samples/kvs_gstreamer_audio_video_sample.cpp
+++ b/samples/kvs_gstreamer_audio_video_sample.cpp
@@ -66,6 +66,11 @@ LOGGER_TAG("com.amazonaws.kinesis.video.gstreamer");
#define DEFAULT_AUDIO_CODEC_ID "A_AAC"
#define DEFAULT_AUDIO_TRACKID 2
+
+#define KEYFRAME_EVENT_INTERVAL 200
+
+uint8_t gEvents = 0;
+
typedef struct _FileInfo {
_FileInfo():
path(""),
@@ -376,6 +381,7 @@ static GstFlowReturn on_new_sample(GstElement *sink, CustomData *data) {
int track_id = (string(g_stream_handle_key).back()) - '0';
g_free(g_stream_handle_key);
GstMapInfo info;
+ static uint16_t key_frame_count = 0;
info.data = nullptr;
sample = gst_app_sink_pull_sample(GST_APP_SINK (sink));
@@ -462,7 +468,24 @@ static GstFlowReturn on_new_sample(GstElement *sink, CustomData *data) {
// start cutting fragment at second video key frame because we can have audio frames before first video key frame
data->first_video_frame = false;
} else {
+ if (key_frame_count % KEYFRAME_EVENT_INTERVAL == 0) {
+ key_frame_count = 0;
+ switch(gEvents) {
+ case 1:
+ data->kinesis_video_stream->putEventMetadata(STREAM_EVENT_TYPE_NOTIFICATION, NULL);
+ break;
+ case 2:
+ data->kinesis_video_stream->putEventMetadata(STREAM_EVENT_TYPE_IMAGE_GENERATION, NULL);
+ break;
+ case 3:
+ data->kinesis_video_stream->putEventMetadata(STREAM_EVENT_TYPE_NOTIFICATION | STREAM_EVENT_TYPE_IMAGE_GENERATION, NULL);
+ break;
+ default:
+ break;
+ }
+ }
kinesis_video_flags = FRAME_FLAG_KEY_FRAME;
+ key_frame_count++;
}
}
@@ -631,10 +654,11 @@ void kinesis_video_init(CustomData *data) {
LOG_AND_THROW("No valid credential method was found");
}
- data->kinesis_video_producer = KinesisVideoProducer::createSync(move(device_info_provider),
- move(client_callback_provider),
- move(stream_callback_provider),
- move(credential_provider),
+ data->kinesis_video_producer = KinesisVideoProducer::createSync(std::move(device_info_provider),
+ std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credential_provider),
+ API_CALL_CACHE_TYPE_ALL,
defaultRegionStr);
LOG_DEBUG("Client is ready");
@@ -666,6 +690,7 @@ void kinesis_video_stream_init(CustomData *data) {
DEFAULT_FRAGMENT_ACKS,
DEFAULT_RESTART_ON_ERROR,
DEFAULT_RECALCULATE_METRICS,
+ true,
NAL_ADAPTATION_FLAG_NONE,
DEFAULT_STREAM_FRAMERATE,
DEFAULT_AVG_BANDWIDTH_BPS,
@@ -681,7 +706,7 @@ void kinesis_video_stream_init(CustomData *data) {
DEFAULT_VIDEO_TRACKID));
stream_definition->addTrack(DEFAULT_AUDIO_TRACKID, DEFAULT_AUDIO_TRACK_NAME, DEFAULT_AUDIO_CODEC_ID, MKV_TRACK_INFO_TYPE_AUDIO);
- data->kinesis_video_stream = data->kinesis_video_producer->createStreamSync(move(stream_definition));
+ data->kinesis_video_stream = data->kinesis_video_producer->createStreamSync(std::move(stream_definition));
data->stream_started.clear();
// since we are starting new putMedia, timestamp need not be padded.
@@ -982,7 +1007,7 @@ int gstreamer_init(int argc, char *argv[], CustomData &data) {
}
int main(int argc, char *argv[]) {
- PropertyConfigurator::doConfigure("../samples/kvs_log_configuration");
+ PropertyConfigurator::doConfigure("../kvs_log_configuration");
if (argc < 2) {
LOG_ERROR(
@@ -998,6 +1023,7 @@ int main(int argc, char *argv[]) {
string file_path;
int file_retry_count = PUTFRAME_FAILURE_RETRY_COUNT;
STATUS stream_status = STATUS_SUCCESS;
+ uint8_t argFlags = 0;
/* init Kinesis Video */
@@ -1008,20 +1034,50 @@ int main(int argc, char *argv[]) {
data.uploading_file = false;
if (argc >= 3) {
- // skip over stream name
- for(int i = 2; i < argc; ++i) {
- file_path = string(argv[i]);
- // file path should be at least 4 char (shortest example: a.ts)
- if (file_path.size() < 4) {
- LOG_ERROR("Invalid file path");
- return 1;
+ for(int i = 2; i < argc; i++)
+ {
+ if (!STRCMP(argv[i], "-f")) {
+ argFlags = 1;
}
- FileInfo fileInfo;
- fileInfo.path = file_path;
- data.file_list.push_back(fileInfo);
- }
+ else if (!STRCMP(argv[i], "-e")) {
+ argFlags = 2;
+ }
+ else {
+ continue;
+ }
+ i++; // found flag, next argument
+ switch(argFlags) {
+ case 1: //-f
+ {
+ file_path = string(argv[i]);
+ // file path should be at least 4 char (shortest example: a.ts)
+ if (file_path.size() < 4) {
+ LOG_ERROR("Invalid file path");
+ return 1;
+ }
+ FileInfo fileInfo;
+ fileInfo.path = file_path;
+ data.file_list.push_back(fileInfo);
- data.uploading_file = true;
+ data.uploading_file = true;
+ break;
+ }
+ case 2: //-e
+ {
+ if (!STRCMP(argv[i], "both")) {
+ gEvents = 3;
+ } else if (!STRCMP(argv[i], "image")) {
+ gEvents = 2;
+ } else if (!STRCMP(argv[i], "notification")) {
+ gEvents = 1;
+ }
+ break;
+ }
+ default:
+ LOG_ERROR("Unknown flag");
+ break;
+ }
+ }
}
try {
diff --git a/samples/kvs_gstreamer_file_uploader_sample.cpp b/samples/kvs_gstreamer_file_uploader_sample.cpp
index 5f828e5d..c9aea07a 100644
--- a/samples/kvs_gstreamer_file_uploader_sample.cpp
+++ b/samples/kvs_gstreamer_file_uploader_sample.cpp
@@ -160,7 +160,7 @@ int gstreamer_init(int argc, char* argv[], CustomData *data) {
return 0;
}
-string build_kvssink_str(string stream_name, unsigned long file_start_time) {
+string build_kvssink_str(string stream_name, unsigned long long file_start_time) {
const char** property;
stringstream ss;
const char *key_raw, *value;
@@ -259,7 +259,7 @@ int main(int argc, char* argv[]) {
}
fclose(file);
- unsigned long file_start_time = strtoul(argv[3], &ptr, 10);
+ unsigned long long file_start_time = strtoull(argv[3], &ptr, 10);
if (*ptr != '\0') {
LOG_ERROR("File start time is not a valid number");
return 1;
diff --git a/samples/kvs_gstreamer_multistream_sample.cpp b/samples/kvs_gstreamer_multistream_sample.cpp
index 1ff6241d..fe2b9e22 100644
--- a/samples/kvs_gstreamer_multistream_sample.cpp
+++ b/samples/kvs_gstreamer_multistream_sample.cpp
@@ -322,10 +322,11 @@ void kinesis_video_init(CustomData *data) {
std::chrono::seconds(180)));
unique_ptr credential_provider(new SampleCredentialProvider(*credentials_.get()));
- data->kinesis_video_producer = KinesisVideoProducer::createSync(move(device_info_provider),
- move(client_callback_provider),
- move(stream_callback_provider),
- move(credential_provider),
+ data->kinesis_video_producer = KinesisVideoProducer::createSync(std::move(device_info_provider),
+ std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credential_provider),
+ API_CALL_CACHE_TYPE_ALL,
defaultRegionStr);
LOG_DEBUG("Client is ready");
@@ -349,6 +350,7 @@ void kinesis_stream_init(string stream_name, CustomData *data, string stream_han
DEFAULT_FRAGMENT_ACKS,
DEFAULT_RESTART_ON_ERROR,
DEFAULT_RECALCULATE_METRICS,
+ true,
NAL_ADAPTATION_FLAG_NONE,
DEFAULT_STREAM_FRAMERATE,
DEFAULT_AVG_BANDWIDTH_BPS,
@@ -359,7 +361,7 @@ void kinesis_stream_init(string stream_name, CustomData *data, string stream_han
DEFAULT_TRACKNAME,
nullptr,
0));
- auto kvs_stream = data->kinesis_video_producer->createStreamSync(move(stream_definition));
+ auto kvs_stream = data->kinesis_video_producer->createStreamSync(std::move(stream_definition));
data->kinesis_video_stream_handles[stream_handle_key] = kvs_stream;
data->frame_data_size_map[stream_handle_key] = DEFAULT_BUFFER_SIZE;
data->frame_data_map[stream_handle_key] = new uint8_t[DEFAULT_BUFFER_SIZE];
@@ -380,7 +382,7 @@ static void cb_rtsp_pad_created(GstElement *element, GstPad *pad, gpointer data)
}
int gstreamer_init(int argc, char *argv[]) {
- PropertyConfigurator::doConfigure("../samples/kvs_log_configuration");
+ PropertyConfigurator::doConfigure("../kvs_log_configuration");
if (argc < 3) {
LOG_ERROR(
diff --git a/samples/kvs_gstreamer_sample.cpp b/samples/kvs_gstreamer_sample.cpp
index 18636ba2..30642848 100644
--- a/samples/kvs_gstreamer_sample.cpp
+++ b/samples/kvs_gstreamer_sample.cpp
@@ -404,6 +404,10 @@ static GstFlowReturn on_new_sample(GstElement *sink, CustomData *data) {
put_frame(data->kinesis_video_stream, info.data, info.size, std::chrono::nanoseconds(buffer->pts),
std::chrono::nanoseconds(buffer->dts), kinesis_video_flags);
+
+ if (CHECK_FRAME_FLAG_KEY_FRAME(kinesis_video_flags)) {
+ data->kinesis_video_stream->putEventMetadata(STREAM_EVENT_TYPE_NOTIFICATION | STREAM_EVENT_TYPE_IMAGE_GENERATION, NULL);
+ }
}
CleanUp:
@@ -441,8 +445,10 @@ static bool format_supported_by_source(GstCaps *src_caps, GstCaps *query_caps, i
static bool resolution_supported(GstCaps *src_caps, GstCaps *query_caps_raw, GstCaps *query_caps_h264,
CustomData &data, int width, int height, int framerate) {
if (query_caps_h264 && format_supported_by_source(src_caps, query_caps_h264, width, height, framerate)) {
+ LOG_DEBUG("src supports h264")
data.h264_stream_supported = true;
} else if (query_caps_raw && format_supported_by_source(src_caps, query_caps_raw, width, height, framerate)) {
+ LOG_DEBUG("src supports raw")
data.h264_stream_supported = false;
} else {
return false;
@@ -528,10 +534,11 @@ void kinesis_video_init(CustomData *data) {
LOG_AND_THROW("No valid credential method was found");
}
- data->kinesis_video_producer = KinesisVideoProducer::createSync(move(device_info_provider),
- move(client_callback_provider),
- move(stream_callback_provider),
- move(credential_provider),
+ data->kinesis_video_producer = KinesisVideoProducer::createSync(std::move(device_info_provider),
+ std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credential_provider),
+ API_CALL_CACHE_TYPE_ALL,
defaultRegionStr);
LOG_DEBUG("Client is ready");
@@ -542,8 +549,8 @@ void kinesis_video_stream_init(CustomData *data) {
map tags;
char tag_name[MAX_TAG_NAME_LEN];
char tag_val[MAX_TAG_VALUE_LEN];
- SPRINTF(tag_name, "piTag");
- SPRINTF(tag_val, "piValue");
+ SNPRINTF(tag_name, MAX_TAG_NAME_LEN, "piTag");
+ SNPRINTF(tag_val, MAX_TAG_VALUE_LEN, "piValue");
STREAMING_TYPE streaming_type = DEFAULT_STREAMING_TYPE;
data->use_absolute_fragment_times = DEFAULT_ABSOLUTE_FRAGMENT_TIMES;
@@ -569,6 +576,7 @@ void kinesis_video_stream_init(CustomData *data) {
DEFAULT_FRAGMENT_ACKS,
DEFAULT_RESTART_ON_ERROR,
DEFAULT_RECALCULATE_METRICS,
+ true,
0,
DEFAULT_STREAM_FRAMERATE,
DEFAULT_AVG_BANDWIDTH_BPS,
@@ -579,7 +587,7 @@ void kinesis_video_stream_init(CustomData *data) {
DEFAULT_TRACKNAME,
nullptr,
0));
- data->kinesis_video_stream = data->kinesis_video_producer->createStreamSync(move(stream_definition));
+ data->kinesis_video_stream = data->kinesis_video_producer->createStreamSync(std::move(stream_definition));
// reset state
data->stream_status = STATUS_SUCCESS;
@@ -685,28 +693,66 @@ int gstreamer_live_source_init(int argc, char* argv[], CustomData *data, GstElem
gst-launch-1.0 v4l2src device=/dev/video0 ! video/x-raw,format=I420,width=1280,height=720,framerate=15/1 ! x264enc pass=quant bframes=0 ! video/x-h264,profile=baseline,format=I420,width=1280,height=720,framerate=15/1 ! matroskamux ! filesink location=test.mkv
*/
source_filter = gst_element_factory_make("capsfilter", "source_filter");
+ if (!source_filter) {
+ LOG_ERROR("Failed to create capsfilter (1)");
+ return 1;
+ }
filter = gst_element_factory_make("capsfilter", "encoder_filter");
+ if (!filter) {
+ LOG_ERROR("Failed to create capsfilter (2)");
+ return 1;
+ }
appsink = gst_element_factory_make("appsink", "appsink");
+ if (!appsink) {
+ LOG_ERROR("Failed to create appsink");
+ return 1;
+ }
h264parse = gst_element_factory_make("h264parse", "h264parse"); // needed to enforce avc stream format
+ if (!h264parse) {
+ LOG_ERROR("Failed to create h264parse");
+ return 1;
+ }
// Attempt to create vtenc encoder
encoder = gst_element_factory_make("vtenc_h264_hw", "encoder");
if (encoder) {
- source = gst_element_factory_make("autovideosrc", "source");
vtenc = true;
+ source = gst_element_factory_make("videotestsrc", "source");
+ if (source) {
+ LOG_DEBUG("Using videotestsrc");
+ } else {
+ LOG_ERROR("Failed to create videotestsrc");
+ return 1;
+ }
} else {
// Failed creating vtenc - check pi hardware encoder
encoder = gst_element_factory_make("omxh264enc", "encoder");
if (encoder) {
+ LOG_DEBUG("Using omxh264enc");
isOnRpi = true;
} else {
// - attempt x264enc
- encoder = gst_element_factory_make("x264enc", "encoder");
isOnRpi = false;
+ encoder = gst_element_factory_make("x264enc", "encoder");
+ if (encoder) {
+ LOG_DEBUG("Using x264enc");
+ } else {
+ LOG_ERROR("Failed to create x264enc");
+ return 1;
+ }
}
source = gst_element_factory_make("v4l2src", "source");
- if (!source) {
+ if (source) {
+ LOG_DEBUG("Using v4l2src");
+ } else {
+ LOG_DEBUG("Failed to create v4l2src, trying ksvideosrc")
source = gst_element_factory_make("ksvideosrc", "source");
+ if (source) {
+ LOG_DEBUG("Using ksvideosrc");
+ } else {
+ LOG_ERROR("Failed to create ksvideosrc");
+ return 1;
+ }
}
vtenc = false;
}
@@ -717,7 +763,9 @@ int gstreamer_live_source_init(int argc, char* argv[], CustomData *data, GstElem
}
/* configure source */
- if (!vtenc) {
+ if (vtenc) {
+ g_object_set(G_OBJECT (source), "is-live", TRUE, NULL);
+ } else {
g_object_set(G_OBJECT (source), "do-timestamp", TRUE, "device", "/dev/video0", NULL);
}
@@ -831,6 +879,7 @@ int gstreamer_live_source_init(int argc, char* argv[], CustomData *data, GstElem
/* build the pipeline */
if (!data->h264_stream_supported) {
+ LOG_DEBUG("Constructing pipeline with encoding element")
gst_bin_add_many(GST_BIN (pipeline), source, video_convert, source_filter, encoder, h264parse, filter,
appsink, NULL);
if (!gst_element_link_many(source, video_convert, source_filter, encoder, h264parse, filter, appsink, NULL)) {
@@ -839,6 +888,7 @@ int gstreamer_live_source_init(int argc, char* argv[], CustomData *data, GstElem
return 1;
}
} else {
+ LOG_DEBUG("Constructing pipeline without encoding element")
gst_bin_add_many(GST_BIN (pipeline), source, source_filter, h264parse, filter, appsink, NULL);
if (!gst_element_link_many(source, source_filter, h264parse, filter, appsink, NULL)) {
g_printerr("Elements could not be linked.\n");
@@ -1036,7 +1086,7 @@ int gstreamer_init(int argc, char* argv[], CustomData *data) {
}
int main(int argc, char* argv[]) {
- PropertyConfigurator::doConfigure("../samples/kvs_log_configuration");
+ PropertyConfigurator::doConfigure("../kvs_log_configuration");
if (argc < 2) {
LOG_ERROR(
@@ -1112,7 +1162,10 @@ int main(int argc, char* argv[]) {
LOG_DEBUG("Attempt to upload file: " << data.file_list[i].path);
// control will return after gstreamer_init after file eos or any GST_ERROR was put on the bus.
- gstreamer_init(argc, argv, &data);
+ if (gstreamer_init(argc, argv, &data) != 0) {
+ LOG_ERROR("Failed to initialize gstreamer");
+ return 1;
+ }
// check if any stream error occurred.
stream_status = data.stream_status.load();
@@ -1162,7 +1215,10 @@ int main(int argc, char* argv[]) {
} else {
// non file uploading scenario
- gstreamer_init(argc, argv, &data);
+ if (gstreamer_init(argc, argv, &data) != 0) {
+ LOG_ERROR("Failed to initialize gstreamer");
+ return 1;
+ }
if (STATUS_SUCCEEDED(stream_status)) {
// if stream_status is success after eos, send out remaining frames.
data.kinesis_video_stream->stopSync();
diff --git a/samples/kvssink_gstreamer_sample.cpp b/samples/kvssink_gstreamer_sample.cpp
new file mode 100644
index 00000000..195254b6
--- /dev/null
+++ b/samples/kvssink_gstreamer_sample.cpp
@@ -0,0 +1,880 @@
+#include
+#include
+#include
+#include
+#include "KinesisVideoProducer.h"
+#include
+#include
+#include
+#include
+#include "gstreamer/gstkvssink.h"
+#include
+#include "include.h"
+
+using namespace std;
+using namespace std::chrono;
+using namespace com::amazonaws::kinesis::video;
+using namespace log4cplus;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int gstreamer_init(int, char **);
+
+#ifdef __cplusplus
+}
+#endif
+
+LOGGER_TAG("com.amazonaws.kinesis.video.gstreamer");
+
+typedef enum _StreamSource {
+ FILE_SOURCE,
+ LIVE_SOURCE,
+ RTSP_SOURCE
+} StreamSource;
+
+typedef struct _FileInfo {
+ _FileInfo() :
+ path(""),
+ last_fragment_ts(0) {}
+
+ string path;
+ uint64_t last_fragment_ts;
+} FileInfo;
+
+typedef struct _CustomData {
+
+ _CustomData() :
+ streamSource(LIVE_SOURCE),
+ h264_stream_supported(false),
+ synthetic_dts(0),
+ last_unpersisted_file_idx(0),
+ stream_status(STATUS_SUCCESS),
+ base_pts(0),
+ max_frame_pts(0),
+ key_frame_pts(0),
+ main_loop(NULL),
+ first_pts(GST_CLOCK_TIME_NONE),
+ use_absolute_fragment_times(true),
+ max_runtime(0) {
+ producer_start_time = chrono::duration_cast(systemCurrentTime().time_since_epoch()).count();
+ }
+
+ GMainLoop *main_loop;
+ unique_ptr kinesis_video_producer;
+ shared_ptr kinesis_video_stream;
+ bool stream_started;
+ bool h264_stream_supported;
+ char *stream_name;
+ mutex file_list_mtx;
+
+ // list of files to upload.
+ vector file_list;
+
+ // index of file in file_list that application is currently trying to upload.
+ uint32_t current_file_idx;
+
+ // index of last file in file_list that haven't been persisted.
+ atomic_uint last_unpersisted_file_idx;
+
+ // stores any error status code reported by StreamErrorCallback.
+ atomic_uint stream_status;
+
+ // Since each file's timestamp start at 0, need to add all subsequent file's timestamp to base_pts starting from the
+ // second file to avoid fragment overlapping. When starting a new putMedia session, this should be set to 0.
+ // Unit: ns
+ uint64_t base_pts;
+
+ // Max pts in a file. This will be added to the base_pts for the next file. When starting a new putMedia session,
+ // this should be set to 0.
+ // Unit: ns
+ uint64_t max_frame_pts;
+
+ // When uploading file, store the pts of frames that has flag FRAME_FLAG_KEY_FRAME. When the entire file has been uploaded,
+ // key_frame_pts contains the timetamp of the last fragment in the file. key_frame_pts is then stored into last_fragment_ts
+ // of the file.
+ // Unit: ns
+ uint64_t key_frame_pts;
+
+ // Used in file uploading only. Assuming frame timestamp are relative. Add producer_start_time to each frame's
+ // timestamp to convert them to absolute timestamp. This way fragments dont overlap after token rotation when doing
+ // file uploading.
+ uint64_t producer_start_time;
+
+ volatile StreamSource streamSource;
+
+ string rtsp_url;
+
+ unique_ptr credential;
+
+ uint64_t synthetic_dts;
+
+ bool use_absolute_fragment_times;
+
+ // Pts of first video frame
+ uint64_t first_pts;
+
+ // Used to determine how long the stream should run (seconds)
+ // Does not apply for file uploads
+ int max_runtime;
+} CustomData;
+
+// CustomData
+CustomData data_global;
+
+static bool format_supported_by_source(GstCaps *src_caps, GstCaps *query_caps, int width, int height, int framerate) {
+ gst_caps_set_simple(query_caps,
+ "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height,
+ "framerate", GST_TYPE_FRACTION, framerate, 1,
+ NULL);
+ bool is_match = gst_caps_can_intersect(query_caps, src_caps);
+
+ // in case the camera has fps as 10000000/333333
+ if (!is_match) {
+ gst_caps_set_simple(query_caps,
+ "framerate", GST_TYPE_FRACTION_RANGE, framerate, 1, framerate + 1, 1,
+ NULL);
+ is_match = gst_caps_can_intersect(query_caps, src_caps);
+ }
+
+ return is_match;
+}
+
+static bool resolution_supported(GstCaps *src_caps, GstCaps *query_caps_raw, GstCaps *query_caps_h264,
+ CustomData &data, int width, int height, int framerate) {
+ if (query_caps_h264 && format_supported_by_source(src_caps, query_caps_h264, width, height, framerate)) {
+ LOG_DEBUG("src supports h264")
+ data.h264_stream_supported = true;
+ } else if (query_caps_raw && format_supported_by_source(src_caps, query_caps_raw, width, height, framerate)) {
+ LOG_DEBUG("src supports raw")
+ data.h264_stream_supported = false;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+/* callback when eos (End of Stream) is posted on bus */
+static void eos_cb(GstElement *sink, GstMessage *message, CustomData *data) {
+ if (data->streamSource == FILE_SOURCE) {
+ // bookkeeping base_pts. add 1ms to avoid overlap.
+ data->base_pts += +data->max_frame_pts + duration_cast(milliseconds(1)).count();
+ data->max_frame_pts = 0;
+
+ {
+ std::unique_lock lk(data->file_list_mtx);
+ // store file's last fragment's timestamp.
+ data->file_list.at(data->current_file_idx).last_fragment_ts = data->key_frame_pts;
+ }
+ }
+ LOG_DEBUG("Terminating pipeline due to EOS");
+ g_main_loop_quit(data->main_loop);
+}
+
+/* This function is called when an error message is posted on the bus */
+static void error_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
+ GError *err;
+ gchar *debug_info;
+
+ /* Print error details on the screen */
+ gst_message_parse_error(msg, &err, &debug_info);
+ g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message);
+ g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none");
+ g_clear_error(&err);
+ g_free(debug_info);
+
+ g_main_loop_quit(data->main_loop);
+ data->stream_status = STATUS_KVS_GSTREAMER_SAMPLE_ERROR;
+}
+
+/* callback when each RTSP stream has been created */
+static void pad_added_cb(GstElement *element, GstPad *pad, GstElement *target) {
+ GstPad *target_sink = gst_element_get_static_pad(GST_ELEMENT(target), "sink");
+ GstPadLinkReturn link_ret;
+ gchar *pad_name = gst_pad_get_name(pad);
+ g_print("New pad found: %s\n", pad_name);
+
+ link_ret = gst_pad_link(pad, target_sink);
+
+ if (link_ret == GST_PAD_LINK_OK) {
+ LOG_INFO("Pad link successful");
+ } else {
+ LOG_INFO("Pad link failed");
+ }
+
+ gst_object_unref(target_sink);
+ g_free(pad_name);
+}
+
+/* Function will wait maxruntime before closing stream */
+void timer(CustomData *data) {
+ THREAD_SLEEP(data->max_runtime);
+ LOG_DEBUG("max runtime elapsed. exiting");
+ g_main_loop_quit(data->main_loop);
+ data->stream_status = STATUS_SUCCESS;
+}
+
+/* Function handles sigint signal */
+void sigint_handler(int sigint){
+ LOG_DEBUG("SIGINT received. Exiting graceully");
+
+ if(data_global.main_loop != NULL){
+ g_main_loop_quit(data_global.main_loop);
+ }
+ data_global.stream_status = STATUS_KVS_GSTREAMER_SAMPLE_INTERRUPTED;
+}
+
+void determine_credentials(GstElement *kvssink, CustomData *data) {
+
+ char const *iot_credential_endpoint;
+ char const *cert_path;
+ char const *private_key_path;
+ char const *role_alias;
+ char const *ca_cert_path;
+ char const *credential_path;
+ if (nullptr != (iot_credential_endpoint = getenv("IOT_GET_CREDENTIAL_ENDPOINT")) &&
+ nullptr != (cert_path = getenv("CERT_PATH")) &&
+ nullptr != (private_key_path = getenv("PRIVATE_KEY_PATH")) &&
+ nullptr != (role_alias = getenv("ROLE_ALIAS")) &&
+ nullptr != (ca_cert_path = getenv("CA_CERT_PATH"))) {
+ // set the IoT Credentials if provided in envvar
+ GstStructure *iot_credentials = gst_structure_new(
+ "iot-certificate",
+ "iot-thing-name", G_TYPE_STRING, data->stream_name,
+ "endpoint", G_TYPE_STRING, iot_credential_endpoint,
+ "cert-path", G_TYPE_STRING, cert_path,
+ "key-path", G_TYPE_STRING, private_key_path,
+ "ca-path", G_TYPE_STRING, ca_cert_path,
+ "role-aliases", G_TYPE_STRING, role_alias, NULL);
+
+ g_object_set(G_OBJECT (kvssink), "iot-certificate", iot_credentials, NULL);
+ gst_structure_free(iot_credentials);
+ // kvssink will search for long term credentials in envvar automatically so no need to include here
+ // if no long credentials or IoT credentials provided will look for credential file as last resort
+ } else if(nullptr != (credential_path = getenv("AWS_CREDENTIAL_PATH"))){
+ g_object_set(G_OBJECT (kvssink), "credential-path", credential_path, NULL);
+ }
+}
+
+int gstreamer_live_source_init(int argc, char *argv[], CustomData *data, GstElement *pipeline) {
+
+ bool vtenc = false, isOnRpi = false;
+
+ /* init stream format */
+ int width = 0, height = 0, framerate = 25, bitrateInKBPS = 512;
+ // index 1 is stream name which is already processed
+ for (int i = 2; i < argc; i++) {
+ if (i < argc) {
+ if ((0 == STRCMPI(argv[i], "-w")) ||
+ (0 == STRCMPI(argv[i], "/w")) ||
+ (0 == STRCMPI(argv[i], "--w"))) {
+ // process the width
+ if (STATUS_FAILED(STRTOI32(argv[i + 1], NULL, 10, &width))) {
+ return 1;
+ }
+ } else if ((0 == STRCMPI(argv[i], "-h")) ||
+ (0 == STRCMPI(argv[i], "/h")) ||
+ (0 == STRCMPI(argv[i], "--h"))) {
+ // process the height
+ if (STATUS_FAILED(STRTOI32(argv[i + 1], NULL, 10, &height))) {
+ return 1;
+ }
+ } else if ((0 == STRCMPI(argv[i], "-f")) ||
+ (0 == STRCMPI(argv[i], "/f")) ||
+ (0 == STRCMPI(argv[i], "--f"))) {
+ // process the framerate
+ if (STATUS_FAILED(STRTOI32(argv[i + 1], NULL, 10, &framerate))) {
+ return 1;
+ }
+ } else if ((0 == STRCMPI(argv[i], "-b")) ||
+ (0 == STRCMPI(argv[i], "/b")) ||
+ (0 == STRCMPI(argv[i], "--b"))) {
+ // process the bitrate
+ if (STATUS_FAILED(STRTOI32(argv[i + 1], NULL, 10, &bitrateInKBPS))) {
+ return 1;
+ }
+ } else if ((0 == STRCMPI(argv[i], "-runtime")) ||
+ (0 == STRCMPI(argv[i], "/runtime")) ||
+ (0 == STRCMPI(argv[i], "--runtime"))) {
+ // process the max runtime
+ if (STATUS_FAILED(STRTOI32(argv[i + 1], NULL, 10, &(data->max_runtime)))) {
+ return 1;
+ }
+ // skip the index
+ }
+ i++;
+ } else if (0 == STRCMPI(argv[i], "-?") ||
+ 0 == STRCMPI(argv[i], "--?") ||
+ 0 == STRCMPI(argv[i], "--help")) {
+ g_printerr("Invalid arguments\n");
+ return 1;
+ } else if (argv[i][0] == '/' ||
+ argv[i][0] == '-') {
+ // Unknown option
+ g_printerr("Invalid arguments\n");
+ return 1;
+ }
+ }
+
+ if ((width == 0 && height != 0) || (width != 0 && height == 0)) {
+ g_printerr("Invalid resolution\n");
+ return 1;
+ }
+
+ if(framerate <= 0 || bitrateInKBPS <= 0) {
+ g_printerr("Invalid input arguments\n");
+ return 1;
+ }
+
+ LOG_DEBUG("Streaming with live source and width: " << width << ", height: " << height << ", fps: " << framerate
+ << ", bitrateInKBPS" << bitrateInKBPS);
+
+ GstElement *source_filter, *filter, *kvssink, *h264parse, *encoder, *source, *video_convert;
+
+ /* create the elemnents */
+ source_filter = gst_element_factory_make("capsfilter", "source_filter");
+ if (!source_filter) {
+ LOG_ERROR("Failed to create capsfilter (1)");
+ return 1;
+ }
+ filter = gst_element_factory_make("capsfilter", "encoder_filter");
+ if (!filter) {
+ LOG_ERROR("Failed to create capsfilter (2)");
+ return 1;
+ }
+ kvssink = gst_element_factory_make("kvssink", "kvssink");
+ if (!kvssink) {
+ LOG_ERROR("Failed to create kvssink");
+ return 1;
+ }
+ h264parse = gst_element_factory_make("h264parse", "h264parse"); // needed to enforce avc stream format
+ if (!h264parse) {
+ LOG_ERROR("Failed to create h264parse");
+ return 1;
+ }
+
+ // Attempt to create vtenc encoder
+ encoder = gst_element_factory_make("vtenc_h264_hw", "encoder");
+ if (encoder) {
+ vtenc = true;
+ source = gst_element_factory_make("videotestsrc", "source");
+ if (source) {
+ LOG_DEBUG("Using videotestsrc");
+ } else {
+ LOG_ERROR("Failed to create videotestsrc");
+ return 1;
+ }
+ } else {
+ // Failed creating vtenc - check pi hardware encoder
+ encoder = gst_element_factory_make("omxh264enc", "encoder");
+ if (encoder) {
+ LOG_DEBUG("Using omxh264enc")
+ isOnRpi = true;
+ } else {
+ // - attempt x264enc
+ isOnRpi = false;
+ encoder = gst_element_factory_make("x264enc", "encoder");
+ if (encoder) {
+ LOG_DEBUG("Using x264enc");
+ } else {
+ LOG_ERROR("Failed to create x264enc");
+ return 1;
+ }
+ }
+ source = gst_element_factory_make("v4l2src", "source");
+ if (source) {
+ LOG_DEBUG("Using v4l2src");
+ } else {
+ LOG_DEBUG("Failed to create v4l2src, trying ksvideosrc")
+ source = gst_element_factory_make("ksvideosrc", "source");
+ if (source) {
+ LOG_DEBUG("Using ksvideosrc");
+ } else {
+ LOG_ERROR("Failed to create ksvideosrc");
+ return 1;
+ }
+ }
+ vtenc = false;
+ }
+
+ if (!pipeline || !source || !source_filter || !encoder || !filter || !kvssink || !h264parse) {
+ g_printerr("Not all elements could be created.\n");
+ return 1;
+ }
+
+ /* configure source */
+ if (vtenc) {
+ g_object_set(G_OBJECT(source), "is-live", TRUE, NULL);
+ } else {
+ g_object_set(G_OBJECT(source), "do-timestamp", TRUE, "device", "/dev/video0", NULL);
+ }
+
+ /* Determine whether device supports h264 encoding and select a streaming resolution supported by the device*/
+ if (GST_STATE_CHANGE_FAILURE == gst_element_set_state(source, GST_STATE_READY)) {
+ g_printerr("Unable to set the source to ready state.\n");
+ return 1;
+ }
+
+ GstPad *srcpad = gst_element_get_static_pad(source, "src");
+ GstCaps *src_caps = gst_pad_query_caps(srcpad, NULL);
+ gst_element_set_state(source, GST_STATE_NULL);
+
+ GstCaps *query_caps_raw = gst_caps_new_simple("video/x-raw",
+ "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height,
+ NULL);
+ GstCaps *query_caps_h264 = gst_caps_new_simple("video/x-h264",
+ "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height,
+ NULL);
+
+ if (width != 0 && height != 0) {
+ if (!resolution_supported(src_caps, query_caps_raw, query_caps_h264, *data, width, height, framerate)) {
+ g_printerr("Resolution %dx%d not supported by video source\n", width, height);
+ return 1;
+ }
+ } else {
+ vector res_width = {640, 1280, 1920};
+ vector res_height = {480, 720, 1080};
+ vector fps = {30, 25, 20};
+ bool found_resolution = false;
+ for (int i = 0; i < res_width.size(); i++) {
+ width = res_width[i];
+ height = res_height[i];
+ for (int j = 0; j < fps.size(); j++) {
+ framerate = fps[j];
+ if (resolution_supported(src_caps, query_caps_raw, query_caps_h264, *data, width, height, framerate)) {
+ found_resolution = true;
+ break;
+ }
+ }
+ if (found_resolution) {
+ break;
+ }
+ }
+ if (!found_resolution) {
+ g_printerr(
+ "Default list of resolutions (1920x1080, 1280x720, 640x480) are not supported by video source\n");
+ return 1;
+ }
+ }
+
+ gst_caps_unref(src_caps);
+ gst_object_unref(srcpad);
+
+ /* create the elemnents needed for the corresponding pipeline */
+ if (!data->h264_stream_supported) {
+ video_convert = gst_element_factory_make("videoconvert", "video_convert");
+
+ if (!video_convert) {
+ g_printerr("Not all elements could be created.\n");
+ return 1;
+ }
+ }
+
+ /* source filter */
+ if (!data->h264_stream_supported) {
+ gst_caps_set_simple(query_caps_raw,
+ "format", G_TYPE_STRING, "I420",
+ NULL);
+ g_object_set(G_OBJECT(source_filter), "caps", query_caps_raw, NULL);
+ } else {
+ gst_caps_set_simple(query_caps_h264,
+ "stream-format", G_TYPE_STRING, "byte-stream",
+ "alignment", G_TYPE_STRING, "au",
+ NULL);
+ g_object_set(G_OBJECT(source_filter), "caps", query_caps_h264, NULL);
+ }
+ gst_caps_unref(query_caps_h264);
+ gst_caps_unref(query_caps_raw);
+
+ /* configure encoder */
+ if (!data->h264_stream_supported) {
+ if (vtenc) {
+ g_object_set(G_OBJECT(encoder), "allow-frame-reordering", FALSE, "realtime", TRUE, "max-keyframe-interval",
+ 45, "bitrate", bitrateInKBPS, NULL);
+ } else if (isOnRpi) {
+ g_object_set(G_OBJECT(encoder), "control-rate", 2, "target-bitrate", bitrateInKBPS * 1000,
+ "periodicty-idr", 45, "inline-header", FALSE, NULL);
+ } else {
+ g_object_set(G_OBJECT(encoder), "bframes", 0, "key-int-max", 45, "bitrate", bitrateInKBPS, NULL);
+ }
+ }
+
+
+ /* configure filter */
+ GstCaps *h264_caps = gst_caps_new_simple("video/x-h264",
+ "stream-format", G_TYPE_STRING, "avc",
+ "alignment", G_TYPE_STRING, "au",
+ NULL);
+ if (!data->h264_stream_supported) {
+ gst_caps_set_simple(h264_caps, "profile", G_TYPE_STRING, "baseline",
+ NULL);
+ }
+ g_object_set(G_OBJECT(filter), "caps", h264_caps, NULL);
+ gst_caps_unref(h264_caps);
+
+ /* configure kvssink */
+ g_object_set(G_OBJECT(kvssink), "stream-name", data->stream_name, "storage-size", 128, NULL);
+ determine_credentials(kvssink, data);
+
+ /* build the pipeline */
+ if (!data->h264_stream_supported) {
+ gst_bin_add_many(GST_BIN(pipeline), source, video_convert, source_filter, encoder, h264parse, filter,
+ kvssink, NULL);
+ LOG_DEBUG("Constructing pipeline with encoding element")
+ if (!gst_element_link_many(source, video_convert, source_filter, encoder, h264parse, filter, kvssink, NULL)) {
+ g_printerr("Elements could not be linked.\n");
+ gst_object_unref(pipeline);
+ return 1;
+ }
+ } else {
+ LOG_DEBUG("Constructing pipeline without encoding element")
+ gst_bin_add_many(GST_BIN(pipeline), source, source_filter, h264parse, filter, kvssink, NULL);
+ if (!gst_element_link_many(source, source_filter, h264parse, filter, kvssink, NULL)) {
+ g_printerr("Elements could not be linked.\n");
+ gst_object_unref(pipeline);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int gstreamer_rtsp_source_init(int argc, char *argv[], CustomData *data, GstElement *pipeline) {
+ // process runtime if provided
+ if (argc == 5){
+ if ((0 == STRCMPI(argv[3], "-runtime")) ||
+ (0 == STRCMPI(argv[3], "/runtime")) ||
+ (0 == STRCMPI(argv[3], "--runtime"))){
+ // process the max runtime
+ if (STATUS_FAILED(STRTOI32(argv[4], NULL, 10, &(data->max_runtime)))) {
+ return 1;
+ }
+ }
+ }
+ GstElement *filter, *kvssink, *depay, *source, *h264parse;
+
+ filter = gst_element_factory_make("capsfilter", "filter");
+ kvssink = gst_element_factory_make("kvssink", "kvssink");
+ depay = gst_element_factory_make("rtph264depay", "depay");
+ source = gst_element_factory_make("rtspsrc", "source");
+ h264parse = gst_element_factory_make("h264parse", "h264parse");
+
+ if (!pipeline || !source || !depay || !kvssink || !filter || !h264parse) {
+ g_printerr("Not all elements could be created.\n");
+ return 1;
+ }
+
+ // configure filter
+ GstCaps *h264_caps = gst_caps_new_simple("video/x-h264",
+ "stream-format", G_TYPE_STRING, "avc",
+ "alignment", G_TYPE_STRING, "au",
+ NULL);
+ g_object_set(G_OBJECT(filter), "caps", h264_caps, NULL);
+ gst_caps_unref(h264_caps);
+
+ // configure kvssink
+ g_object_set(G_OBJECT(kvssink), "stream-name", data->stream_name, "storage-size", 128, NULL);
+ determine_credentials(kvssink, data);
+
+ // configure rtspsrc
+ g_object_set(G_OBJECT(source),
+ "location", data->rtsp_url.c_str(),
+ "short-header", true, // Necessary for target camera
+ NULL);
+
+ g_signal_connect(source, "pad-added", G_CALLBACK(pad_added_cb), depay);
+
+ /* build the pipeline */
+ gst_bin_add_many(GST_BIN(pipeline), source,
+ depay, h264parse, filter, kvssink,
+ NULL);
+
+ /* Leave the actual source out - this will be done when the pad is added */
+ if (!gst_element_link_many(depay, filter, h264parse,
+ kvssink,
+ NULL)) {
+
+ g_printerr("Elements could not be linked.\n");
+ gst_object_unref(pipeline);
+ return 1;
+ }
+
+ return 0;
+}
+
+int gstreamer_file_source_init(CustomData *data, GstElement *pipeline) {
+
+ GstElement *demux, *kvssink, *filesrc, *h264parse, *filter, *queue;
+ string file_suffix;
+ string file_path = data->file_list.at(data->current_file_idx).path;
+
+ filter = gst_element_factory_make("capsfilter", "filter");
+ kvssink = gst_element_factory_make("kvssink", "kvssink");
+ filesrc = gst_element_factory_make("filesrc", "filesrc");
+ h264parse = gst_element_factory_make("h264parse", "h264parse");
+ queue = gst_element_factory_make("queue", "queue");
+
+ // set demux based off filetype
+ file_suffix = file_path.substr(file_path.size() - 3);
+ if (file_suffix.compare("mkv") == 0) {
+ demux = gst_element_factory_make("matroskademux", "demux");
+ } else if (file_suffix.compare("mp4") == 0) {
+ demux = gst_element_factory_make("qtdemux", "demux");
+ } else if (file_suffix.compare(".ts") == 0) {
+ demux = gst_element_factory_make("tsdemux", "demux");
+ } else {
+ LOG_ERROR("File format not supported. Supported ones are mp4, mkv and ts. File suffix: " << file_suffix);
+ return 1;
+ }
+
+
+ if (!demux || !filesrc || !h264parse || !kvssink || !pipeline || !filter) {
+ g_printerr("Not all elements could be created:\n");
+ return 1;
+ }
+
+ // configure filter
+ GstCaps *h264_caps = gst_caps_new_simple("video/x-h264",
+ "stream-format", G_TYPE_STRING, "avc",
+ "alignment", G_TYPE_STRING, "au",
+ NULL);
+ g_object_set(G_OBJECT(filter), "caps", h264_caps, NULL);
+ gst_caps_unref(h264_caps);
+
+ // configure kvssink
+ g_object_set(G_OBJECT(kvssink), "stream-name", data->stream_name, "streaming-type", STREAMING_TYPE_OFFLINE, "storage-size", 128, NULL);
+ determine_credentials(kvssink, data);
+
+ // configure filesrc
+ g_object_set(G_OBJECT(filesrc), "location", file_path.c_str(), NULL);
+
+ // configure demux
+ g_signal_connect(demux, "pad-added", G_CALLBACK(pad_added_cb), queue);
+
+
+ /* build the pipeline */
+ gst_bin_add_many(GST_BIN(pipeline), demux,
+ filesrc, filter, kvssink, h264parse, queue,
+ NULL);
+
+ if (!gst_element_link_many(filesrc, demux,
+ NULL)) {
+ g_printerr("Elements could not be linked.\n");
+ gst_object_unref(pipeline);
+ return 1;
+ }
+
+ if (!gst_element_link_many(queue, h264parse, filter, kvssink,
+ NULL)) {
+ g_printerr("Video elements could not be linked.\n");
+ gst_object_unref(pipeline);
+ return 1;
+ }
+
+ return 0;
+}
+
+int gstreamer_init(int argc, char *argv[], CustomData *data) {
+
+ /* init GStreamer */
+ gst_init(&argc, &argv);
+
+ GstElement *pipeline;
+ int ret;
+ GstStateChangeReturn gst_ret;
+
+ // Reset first frame pts
+ data->first_pts = GST_CLOCK_TIME_NONE;
+
+ switch (data->streamSource) {
+ case LIVE_SOURCE:
+ LOG_INFO("Streaming from live source");
+ pipeline = gst_pipeline_new("live-kinesis-pipeline");
+ ret = gstreamer_live_source_init(argc, argv, data, pipeline);
+ break;
+ case RTSP_SOURCE:
+ LOG_INFO("Streaming from rtsp source");
+ pipeline = gst_pipeline_new("rtsp-kinesis-pipeline");
+ ret = gstreamer_rtsp_source_init(argc, argv, data, pipeline);
+ break;
+ case FILE_SOURCE:
+ LOG_INFO("Streaming from file source");
+ pipeline = gst_pipeline_new("file-kinesis-pipeline");
+ ret = gstreamer_file_source_init(data, pipeline);
+ break;
+ }
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
+ GstBus *bus = gst_element_get_bus(pipeline);
+ gst_bus_add_signal_watch(bus);
+ g_signal_connect(G_OBJECT(bus), "message::error", (GCallback) error_cb, data);
+ g_signal_connect(G_OBJECT(bus), "message::eos", G_CALLBACK(eos_cb), data);
+ gst_object_unref(bus);
+ /* start streaming */
+ gst_ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
+ if (gst_ret == GST_STATE_CHANGE_FAILURE) {
+ g_printerr("Unable to set the pipeline to the playing state.\n");
+ gst_object_unref(pipeline);
+ data->stream_status = STATUS_KVS_GSTREAMER_SAMPLE_ERROR;
+ return 1;
+ }
+ // set timer if valid runtime provided (non-positive values are ignored)
+ if (data->streamSource != FILE_SOURCE && data->max_runtime > 0){
+ LOG_DEBUG("Timeout is " << data->max_runtime << " seconds.");
+ std::thread stream_timer(timer, data);
+ stream_timer.detach();
+ }
+ LOG_DEBUG("before main loop");
+ data->main_loop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(data->main_loop);
+ LOG_DEBUG("after main loop")
+
+
+ /* free resources */
+ gst_bus_remove_signal_watch(bus);
+ gst_element_set_state(pipeline, GST_STATE_NULL);
+ gst_object_unref(pipeline);
+ g_main_loop_unref(data->main_loop);
+ data->main_loop = NULL;
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ PropertyConfigurator::doConfigure("../kvs_log_configuration");
+
+ signal(SIGINT, sigint_handler);
+
+ if (argc < 2) {
+ LOG_ERROR(
+ "Usage: AWS_ACCESS_KEY_ID=SAMPLEKEY AWS_SECRET_ACCESS_KEY=SAMPLESECRET ./kvssink_gstreamer_sample_app my-stream-name -w width -h height -f framerate -b bitrateInKBPS -runtime runtimeInSeconds\n \
+ or AWS_ACCESS_KEY_ID=SAMPLEKEY AWS_SECRET_ACCESS_KEY=SAMPLESECRET ./kvssink_gstreamer_sample_app my-stream-name\n \
+ or AWS_ACCESS_KEY_ID=SAMPLEKEY AWS_SECRET_ACCESS_KEY=SAMPLESECRET ./kvssink_gstreamer_sample_app my-stream-name rtsp-url -runtime runtimeInSeconds\n \
+ or AWS_ACCESS_KEY_ID=SAMPLEKEY AWS_SECRET_ACCESS_KEY=SAMPLESECRET ./kvssink_gstreamer_sample_app my-stream-name path/to/file1 path/to/file2 ...\n");
+ return 1;
+ }
+
+ const int PUTFRAME_FAILURE_RETRY_COUNT = 3;
+
+ char stream_name[MAX_STREAM_NAME_LEN + 1];
+ int ret = 0;
+ int file_retry_count = PUTFRAME_FAILURE_RETRY_COUNT;
+ STATUS stream_status = STATUS_SUCCESS;
+
+ STRNCPY(stream_name, argv[1], MAX_STREAM_NAME_LEN);
+ stream_name[MAX_STREAM_NAME_LEN] = '\0';
+ data_global.stream_name = stream_name;
+
+ data_global.streamSource = LIVE_SOURCE;
+ if (argc > 2) {
+ string third_arg = string(argv[2]);
+ // config options for live source begin with -
+ if (third_arg[0] != '-') {
+ string prefix = third_arg.substr(0, 4);
+ string suffix = third_arg.substr(third_arg.size() - 3);
+ if (prefix.compare("rtsp") == 0) {
+ data_global.streamSource = RTSP_SOURCE;
+ data_global.rtsp_url = string(argv[2]);
+
+ } else if (suffix.compare("mkv") == 0 ||
+ suffix.compare("mp4") == 0 ||
+ suffix.compare(".ts") == 0) {
+ data_global.streamSource = FILE_SOURCE;
+ // skip over stream name
+ for (int i = 2; i < argc; ++i) {
+ string file_path = string(argv[i]);
+ // file path should be at least 4 char (shortest example: a.ts)
+ if (file_path.size() < 4) {
+ LOG_ERROR("Invalid file path");
+ return 1;
+ }
+ FileInfo fileInfo;
+ fileInfo.path = file_path;
+ data_global.file_list.push_back(fileInfo);
+ }
+ }
+ }
+ }
+
+ bool do_retry = true;
+
+ if (data_global.streamSource == FILE_SOURCE) {
+ do {
+ uint32_t i = data_global.last_unpersisted_file_idx.load();
+ bool continue_uploading = true;
+
+ for (; i < data_global.file_list.size() && continue_uploading; ++i) {
+
+ data_global.current_file_idx = i;
+ LOG_DEBUG("Attempt to upload file: " << data_global.file_list[i].path);
+
+ // control will return after gstreamer_init after file eos or any GST_ERROR was put on the bus.
+ if (gstreamer_init(argc, argv, &data_global) != 0) {
+ return 1;
+ }
+
+ // check if any stream error occurred.
+ stream_status = data_global.stream_status.load();
+
+ if (STATUS_FAILED(stream_status)) {
+ continue_uploading = false;
+ do_retry = false;
+ if (stream_status == GST_FLOW_ERROR) {
+ LOG_ERROR("Fatal stream error occurred: " << stream_status << ". Terminating.");
+ } else if(stream_status == STATUS_KVS_GSTREAMER_SAMPLE_INTERRUPTED){
+ LOG_ERROR("File upload interrupted. Terminating.");
+ continue_uploading = false;
+ }else { // non fatal case. retry upload
+ LOG_ERROR("stream error occurred: " << stream_status << ". Terminating.");
+ do_retry = true;
+ }
+ } else {
+ LOG_INFO("Finished sending file to kvs producer: " << data_global.file_list[i].path);
+ data_global.last_unpersisted_file_idx += 1;
+ // check if we just finished sending the last file.
+ if (i == data_global.file_list.size() - 1) {
+ LOG_INFO("All files have been persisted");
+ do_retry = false;
+ }
+ }
+ }
+
+ if (do_retry) {
+ file_retry_count--;
+ if (file_retry_count == 0) {
+ i = data_global.last_unpersisted_file_idx.load();
+ LOG_ERROR("Failed to upload file " << data_global.file_list[i].path << " after retrying. Terminating.");
+ do_retry = false; // exit while loop
+ } else {
+ // reset state
+ data_global.stream_status = STATUS_SUCCESS;
+ data_global.stream_started = false;
+ }
+ }
+ } while (do_retry);
+
+ } else {
+ // non file uploading scenario
+ if (gstreamer_init(argc, argv, &data_global) != 0) {
+ return 1;
+ }
+ stream_status = data_global.stream_status.load();
+ if (STATUS_SUCCEEDED(stream_status)) {
+ LOG_INFO("Stream succeeded");
+ } else if(stream_status == STATUS_KVS_GSTREAMER_SAMPLE_INTERRUPTED){
+ LOG_INFO("Stream Interrupted");
+ } else {
+ LOG_INFO("Stream Failed");
+ }
+ }
+
+ return 0;
+}
+
diff --git a/src/Auth.cpp b/src/Auth.cpp
index 4d44518f..d34c8c30 100644
--- a/src/Auth.cpp
+++ b/src/Auth.cpp
@@ -10,6 +10,7 @@ using std::mutex;
CredentialProvider::CredentialProvider()
: next_rotation_time_(0),
security_token_(NULL) {
+ MEMSET(&callbacks_, 0, SIZEOF(callbacks_));
}
void CredentialProvider::getCredentials(Credentials& credentials) {
@@ -89,6 +90,7 @@ DeviceCertToTokenFunc CredentialProvider::deviceCertToTokenCallback() {
STATUS CredentialProvider::getStreamingTokenHandler(UINT64 custom_data, PCHAR stream_name, STREAM_ACCESS_MODE access_mode, PServiceCallContext p_service_call_context) {
LOG_DEBUG("getStreamingTokenHandler invoked");
+ STATUS status = STATUS_SUCCESS;
UNUSED_PARAM(stream_name);
UNUSED_PARAM(access_mode);
@@ -110,14 +112,25 @@ STATUS CredentialProvider::getStreamingTokenHandler(UINT64 custom_data, PCHAR st
freeAwsCredentials(&this_obj->security_token_);
// Store the buffer so we can release it at the end
- createAwsCredentials((PCHAR) access_key.c_str(), access_key_len, (PCHAR) secret_key.c_str(), secret_key_len,
- (PCHAR) session_token.c_str(), session_token_len, expiration, &this_obj->security_token_);
+ if(IS_EMPTY_STRING(session_token.c_str())) {
+ status = createAwsCredentials((PCHAR) access_key.c_str(), access_key_len, (PCHAR) secret_key.c_str(), secret_key_len,
+ nullptr, 0, expiration, &this_obj->security_token_);
+
+ } else {
+ status = createAwsCredentials((PCHAR) access_key.c_str(), access_key_len, (PCHAR) secret_key.c_str(), secret_key_len,
+ (PCHAR) session_token.c_str(), session_token_len, expiration, &this_obj->security_token_);
+
+ }
- STATUS status = getStreamingTokenResultEvent(
- p_service_call_context->customData, SERVICE_CALL_RESULT_OK,
- reinterpret_cast(this_obj->security_token_),
- this_obj->security_token_->size,
- expiration);
+ if(STATUS_SUCCEEDED(status)) {
+ status = getStreamingTokenResultEvent(
+ p_service_call_context->customData, SERVICE_CALL_RESULT_OK,
+ reinterpret_cast(this_obj->security_token_),
+ this_obj->security_token_->size,
+ expiration);
+ } else {
+ LOG_ERROR("getStreamingTokenHandler failed with code " << std::hex << status);
+ }
return status;
}
@@ -126,10 +139,11 @@ STATUS CredentialProvider::getSecurityTokenHandler(UINT64 custom_data, PBYTE* pp
LOG_DEBUG("getSecurityTokenHandler invoked");
auto this_obj = reinterpret_cast(custom_data);
-
+ STATUS status = STATUS_SUCCESS;
Credentials credentials;
this_obj->getCredentials(credentials);
+
auto access_key = credentials.getAccessKey();
auto access_key_len = access_key.length();
auto secret_key = credentials.getSecretKey();
@@ -144,13 +158,26 @@ STATUS CredentialProvider::getSecurityTokenHandler(UINT64 custom_data, PBYTE* pp
*p_expiration = credentials.getExpiration().count() * HUNDREDS_OF_NANOS_IN_A_SECOND;
// Store the buffer so we can release it at the end
- createAwsCredentials((PCHAR) access_key.c_str(), access_key_len, (PCHAR) secret_key.c_str(), secret_key_len,
- (PCHAR) session_token.c_str(), session_token_len, *p_expiration, &this_obj->security_token_);
+ if(IS_EMPTY_STRING(session_token.c_str())) {
+ // Store the buffer so we can release it at the end
+ status = createAwsCredentials((PCHAR) access_key.c_str(), access_key_len, (PCHAR) secret_key.c_str(), secret_key_len,
+ nullptr, 0, *p_expiration, &this_obj->security_token_);
+
+ } else {
+ // Store the buffer so we can release it at the end
+ status = createAwsCredentials((PCHAR) access_key.c_str(), access_key_len, (PCHAR) secret_key.c_str(), secret_key_len,
+ (PCHAR) session_token.c_str(), session_token_len, *p_expiration, &this_obj->security_token_);
+
+ }
- *pp_token = (PBYTE) (this_obj->security_token_);
- *p_size = this_obj->security_token_->size;
+ if(STATUS_SUCCEEDED(status)) {
+ *pp_token = (PBYTE) (this_obj->security_token_);
+ *p_size = this_obj->security_token_->size;
+ } else {
+ LOG_ERROR("getSecurityTokenHandler failed with code " << std::hex << status);
+ }
- return STATUS_SUCCESS;
+ return status;
}
} // namespace video
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1882f812..b5a83c49 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.6.3)
project(KinesisVideoProducer)
diff --git a/src/CachingEndpointOnlyCallbackProvider.cpp b/src/CachingEndpointOnlyCallbackProvider.cpp
index 215a6758..2cb5050c 100644
--- a/src/CachingEndpointOnlyCallbackProvider.cpp
+++ b/src/CachingEndpointOnlyCallbackProvider.cpp
@@ -12,18 +12,18 @@ using std::unique_ptr;
using std::string;
CachingEndpointOnlyCallbackProvider::CachingEndpointOnlyCallbackProvider(
- unique_ptr client_callback_provider,
- unique_ptr stream_callback_provider,
- unique_ptr credentials_provider,
+ std::unique_ptr client_callback_provider,
+ std::unique_ptr stream_callback_provider,
+ std::unique_ptr credentials_provider,
const string& region,
const string& control_plane_uri,
const std::string &user_agent_name,
const std::string &custom_user_agent,
const std::string &cert_path,
std::chrono::duration caching_update_period) : CachingEndpointOnlyCallbackProvider(
- move(client_callback_provider),
- move(stream_callback_provider),
- move(credentials_provider),
+ std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credentials_provider),
region,
control_plane_uri,
user_agent_name,
@@ -42,9 +42,9 @@ CachingEndpointOnlyCallbackProvider::CachingEndpointOnlyCallbackProvider(
const std::string &custom_user_agent,
const std::string &cert_path,
uint64_t cache_update_period) : DefaultCallbackProvider(
- move(client_callback_provider),
- move(stream_callback_provider),
- move(credentials_provider),
+ std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credentials_provider),
region,
control_plane_uri,
user_agent_name,
diff --git a/src/DefaultCallbackProvider.cpp b/src/DefaultCallbackProvider.cpp
index 2d08b842..49a39fb1 100644
--- a/src/DefaultCallbackProvider.cpp
+++ b/src/DefaultCallbackProvider.cpp
@@ -334,9 +334,9 @@ DefaultCallbackProvider::DefaultCallbackProvider(
const std::string &cert_path,
bool is_caching_endpoint,
std::chrono::duration caching_update_period) : DefaultCallbackProvider (
- move(client_callback_provider),
- move(stream_callback_provider),
- move(credentials_provider),
+ std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credentials_provider),
region,
control_plane_uri,
user_agent_name,
@@ -357,9 +357,9 @@ DefaultCallbackProvider::DefaultCallbackProvider(
const std::string &cert_path,
API_CALL_CACHE_TYPE api_call_caching,
std::chrono::duration caching_update_period) : DefaultCallbackProvider (
- move(client_callback_provider),
- move(stream_callback_provider),
- move(credentials_provider),
+ std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credentials_provider),
region,
control_plane_uri,
user_agent_name,
@@ -380,9 +380,9 @@ DefaultCallbackProvider::DefaultCallbackProvider(
const std::string &cert_path,
bool is_caching_endpoint,
uint64_t caching_update_period) : DefaultCallbackProvider (
- move(client_callback_provider),
- move(stream_callback_provider),
- move(credentials_provider),
+ std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credentials_provider),
region,
control_plane_uri,
user_agent_name,
@@ -406,11 +406,12 @@ DefaultCallbackProvider::DefaultCallbackProvider(
: region_(region),
service_(std::string(KINESIS_VIDEO_SERVICE_NAME)),
control_plane_uri_(control_plane_uri),
- cert_path_(cert_path) {
+ cert_path_(cert_path),
+ resources_(new CallbackProviderResources()) {
STATUS retStatus = STATUS_SUCCESS;
- client_callback_provider_ = move(client_callback_provider);
- stream_callback_provider_ = move(stream_callback_provider);
- credentials_provider_ = move(credentials_provider);
+ client_callback_provider_ = std::move(client_callback_provider);
+ stream_callback_provider_ = std::move(stream_callback_provider);
+ credentials_provider_ = std::move(credentials_provider);
PStreamCallbacks pContinuoutsRetryStreamCallbacks = NULL;
std::string custom_user_agent_ = CPP_SDK_CUSTOM_USERAGENT + custom_user_agent;
@@ -421,6 +422,10 @@ DefaultCallbackProvider::DefaultCallbackProvider(
+ "."
+ region_
+ CONTROL_PLANE_URI_POSTFIX;
+ // If region is in CN, add CN region uri postfix
+ if(region_.rfind("cn-", 0) == 0) {
+ control_plane_uri_ += ".cn";
+ }
}
getStreamCallbacks();
@@ -433,22 +438,18 @@ DefaultCallbackProvider::DefaultCallbackProvider(
STRING_TO_PCHAR(region),
STRING_TO_PCHAR(control_plane_uri),
STRING_TO_PCHAR(cert_path),
- (PCHAR) DEFAULT_USER_AGENT_NAME,
+ STRING_TO_PCHAR (user_agent_name),
STRING_TO_PCHAR(custom_user_agent_),
- &client_callbacks_))) {
+ &resources_->client_callbacks_))) {
std::stringstream status_strstrm;
status_strstrm << std::hex << retStatus;
LOG_AND_THROW("Unable to create default callback provider. Error status: 0x" + status_strstrm.str());
}
- auth_callbacks_ = credentials_provider_->getCallbacks(client_callbacks_);
- addStreamCallbacks(client_callbacks_, &stream_callbacks_);
- addProducerCallbacks(client_callbacks_, &producer_callbacks_);
- setPlatformCallbacks(client_callbacks_, &platform_callbacks_);
- createContinuousRetryStreamCallbacks(client_callbacks_, &pContinuoutsRetryStreamCallbacks);
-}
-
-DefaultCallbackProvider::~DefaultCallbackProvider() {
- freeCallbacksProvider(&client_callbacks_);
+ auth_callbacks_ = credentials_provider_->getCallbacks(resources_->client_callbacks_);
+ addStreamCallbacks(resources_->client_callbacks_, &stream_callbacks_);
+ addProducerCallbacks(resources_->client_callbacks_, &producer_callbacks_);
+ setPlatformCallbacks(resources_->client_callbacks_, &platform_callbacks_);
+ createContinuousRetryStreamCallbacks(resources_->client_callbacks_, &pContinuoutsRetryStreamCallbacks);
}
StreamCallbacks DefaultCallbackProvider::getStreamCallbacks() {
@@ -489,7 +490,7 @@ PlatformCallbacks DefaultCallbackProvider::getPlatformCallbacks() {
}
DefaultCallbackProvider::callback_t DefaultCallbackProvider::getCallbacks() {
- return *client_callbacks_;
+ return *resources_->client_callbacks_;
}
GetStreamingTokenFunc DefaultCallbackProvider::getStreamingTokenCallback() {
diff --git a/src/DefaultCallbackProvider.h b/src/DefaultCallbackProvider.h
index 52ee1805..44193ad5 100644
--- a/src/DefaultCallbackProvider.h
+++ b/src/DefaultCallbackProvider.h
@@ -57,7 +57,7 @@ class DefaultCallbackProvider : public CallbackProvider {
const std::string &user_agent_name = EMPTY_STRING,
const std::string &custom_user_agent = EMPTY_STRING,
const std::string &cert_path = EMPTY_STRING,
- API_CALL_CACHE_TYPE api_call_caching = API_CALL_CACHE_TYPE_NONE,
+ API_CALL_CACHE_TYPE api_call_caching = API_CALL_CACHE_TYPE_ALL,
std::chrono::duration caching_update_period = std::chrono::seconds(DEFAULT_ENDPOINT_CACHE_UPDATE_PERIOD / HUNDREDS_OF_NANOS_IN_A_SECOND));
explicit DefaultCallbackProvider(
@@ -72,7 +72,7 @@ class DefaultCallbackProvider : public CallbackProvider {
API_CALL_CACHE_TYPE api_call_caching,
uint64_t caching_update_period);
- virtual ~DefaultCallbackProvider();
+ ~DefaultCallbackProvider() = default;
callback_t getCallbacks() override;
@@ -443,7 +443,19 @@ class DefaultCallbackProvider : public CallbackProvider {
/**
* Stores all callbacks from PIC
*/
- PClientCallbacks client_callbacks_;
+ class CallbackProviderResources {
+ public:
+ CallbackProviderResources() : client_callbacks_(nullptr) {}
+ ~CallbackProviderResources() {
+ if (client_callbacks_ != nullptr) {
+ freeCallbacksProvider(&client_callbacks_);
+ }
+ }
+
+ PClientCallbacks client_callbacks_;
+ };
+
+ std::unique_ptr resources_;
/**
* Stores all auth callbacks from C++
diff --git a/src/JNI/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.cpp b/src/JNI/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.cpp
index 5ff969fd..621673a9 100644
--- a/src/JNI/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.cpp
+++ b/src/JNI/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.cpp
@@ -2,14 +2,19 @@
* Implementation of Kinesis Video Producer client wrapper
*/
#define LOG_CLASS "KinesisVideoClientWrapper"
+#define MAX_LOG_MESSAGE_LENGTH 1024 * 10
#include "com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h"
+// initializing static members of the class
+JavaVM* KinesisVideoClientWrapper::mJvm = NULL;
+jobject KinesisVideoClientWrapper::mGlobalJniObjRef = NULL;
+jmethodID KinesisVideoClientWrapper::mLogPrintMethodId = NULL;
+
+
KinesisVideoClientWrapper::KinesisVideoClientWrapper(JNIEnv* env,
jobject thiz,
- jobject deviceInfo) : mClientHandle(INVALID_CLIENT_HANDLE_VALUE),
- mJvm(NULL),
- mGlobalJniObjRef(NULL)
+ jobject deviceInfo): mClientHandle(INVALID_CLIENT_HANDLE_VALUE)
{
UINT32 retStatus;
@@ -1001,18 +1006,13 @@ BOOL KinesisVideoClientWrapper::setCallbacks(JNIEnv* env, jobject thiz)
mClientCallbacks.clientReadyFn = clientReadyFunc;
mClientCallbacks.createDeviceFn = createDeviceFunc;
mClientCallbacks.deviceCertToTokenFn = deviceCertToTokenFunc;
+ mClientCallbacks.logPrintFn = logPrintFunc;
// TODO: Currently we set the shutdown callbacks to NULL.
// We need to expose these in the near future
mClientCallbacks.clientShutdownFn = NULL;
mClientCallbacks.streamShutdownFn = NULL;
- // We do not expose logging functionality to Java
- // as the signature of the function does not have "custom_data"
- // to properly map to the client object.
- // We will use the default logger for Java.
- mClientCallbacks.logPrintFn = NULL;
-
// Extract the method IDs for the callbacks and set a global reference
jclass thizCls = env->GetObjectClass(thiz);
if (thizCls == NULL) {
@@ -1171,6 +1171,12 @@ BOOL KinesisVideoClientWrapper::setCallbacks(JNIEnv* env, jobject thiz)
return FALSE;
}
+ mLogPrintMethodId = env->GetMethodID(thizCls, "logPrint", "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+ if (mLogPrintMethodId == NULL) {
+ DLOGE("Couldn't find method id logPrint");
+ return FALSE;
+ }
+
return TRUE;
}
@@ -2613,3 +2619,73 @@ AUTH_INFO_TYPE KinesisVideoClientWrapper::authInfoTypeFromInt(UINT32 authInfoTyp
default: return AUTH_INFO_UNDEFINED;
}
}
+
+VOID KinesisVideoClientWrapper::logPrintFunc(UINT32 level, PCHAR tag, PCHAR fmt, ...)
+{
+ JNIEnv *env;
+ BOOL detached = FALSE;
+ STATUS retStatus = STATUS_SUCCESS;
+ jstring jstrTag = NULL, jstrFmt = NULL, jstrBuffer = NULL;
+ CHAR buffer[MAX_LOG_MESSAGE_LENGTH];
+
+ CHECK(mJvm != NULL && mGlobalJniObjRef != NULL);
+
+ INT32 envState = mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6);
+ if (envState == JNI_EDETACHED) {
+ if (mJvm->AttachCurrentThread((PVOID*) &env, NULL) != 0) {
+ goto CleanUp;
+ }
+ detached = TRUE;
+ }
+
+ va_list list;
+ va_start(list, fmt);
+ vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, fmt, list);
+ va_end(list);
+
+ if (tag != NULL && fmt != NULL && STRLEN(buffer) > 0) {
+ jstrTag = env->NewStringUTF(tag);
+ jstrFmt = env->NewStringUTF(fmt);
+ jstrBuffer = env->NewStringUTF(buffer);
+ }
+
+ CHK(jstrTag != NULL, STATUS_NOT_ENOUGH_MEMORY);
+ CHK(jstrFmt != NULL, STATUS_NOT_ENOUGH_MEMORY);
+ CHK(jstrBuffer != NULL, STATUS_NOT_ENOUGH_MEMORY);
+
+ env->CallVoidMethod(mGlobalJniObjRef, mLogPrintMethodId, level, jstrTag, jstrFmt, jstrBuffer);
+
+ CHK_JVM_EXCEPTION(env);
+
+ /*
+ Sample logs from PIC as displayed by log4j2 in Java Producer SDK
+ 2021-12-10 10:01:53,874 [main] TRACE c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] KinesisVideoProducerJNI - Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createKinesisVideoStream(): Enter
+ 2021-12-10 10:01:53,875 [main] INFO c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] KinesisVideoProducerJNI - Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createKinesisVideoStream(): Creating Kinesis Video stream.
+ 2021-12-10 10:01:53,875 [main] INFO c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] KinesisVideoClient - createKinesisVideoStream(): Creating Kinesis Video Stream.
+ 2021-12-10 10:01:53,875 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Kinesis Video Stream Info
+
+ 2021-12-10 10:01:53,875 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Kinesis Video Stream Info
+ 2021-12-10 10:01:53,875 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Stream name: NewStreamJava12
+ 2021-12-10 10:01:53,875 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Streaming type: STREAMING_TYPE_REALTIME
+ 2021-12-10 10:01:53,876 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Content type: video/h264
+ */
+
+CleanUp:
+
+ if (jstrTag != NULL) {
+ env->DeleteLocalRef(jstrTag);
+ }
+
+ if (jstrFmt != NULL) {
+ env->DeleteLocalRef(jstrFmt);
+ }
+
+ if (jstrBuffer != NULL) {
+ env->DeleteLocalRef(jstrBuffer);
+ }
+
+ // Detach the thread if we have attached it to JVM
+ if (detached) {
+ mJvm->DetachCurrentThread();
+ }
+}
\ No newline at end of file
diff --git a/src/JNI/com/amazonaws/kinesis/video/producer/jni/Parameters.cpp b/src/JNI/com/amazonaws/kinesis/video/producer/jni/Parameters.cpp
index 2ce79984..515122e5 100644
--- a/src/JNI/com/amazonaws/kinesis/video/producer/jni/Parameters.cpp
+++ b/src/JNI/com/amazonaws/kinesis/video/producer/jni/Parameters.cpp
@@ -201,6 +201,22 @@ BOOL setClientInfo(JNIEnv *env, jobject clientInfo, PClientInfo pClientInfo) {
CHK_JVM_EXCEPTION(env);
}
+ methodId = env->GetMethodID(cls, "getServiceConnectionTimeout", "()J");
+ if (methodId == NULL) {
+ DLOGW("Couldn't find method id getServiceConnectionTimeout");
+ } else {
+ pClientInfo->serviceCallConnectionTimeout = env->CallLongMethod(clientInfo, methodId);
+ CHK_JVM_EXCEPTION(env);
+ }
+
+ methodId = env->GetMethodID(cls, "getServiceCompletionTimeout", "()J");
+ if (methodId == NULL) {
+ DLOGW("Couldn't find method id getServiceCompletionTimeout");
+ } else {
+ pClientInfo->serviceCallCompletionTimeout = env->CallLongMethod(clientInfo, methodId);
+ CHK_JVM_EXCEPTION(env);
+ }
+
methodId = env->GetMethodID(cls, "getOfflineBufferAvailabilityTimeout", "()J");
if (methodId == NULL) {
DLOGW("Couldn't find method id getOfflineBufferAvailabilityTimeout");
@@ -225,6 +241,14 @@ BOOL setClientInfo(JNIEnv *env, jobject clientInfo, PClientInfo pClientInfo) {
CHK_JVM_EXCEPTION(env);
}
+ methodId = env->GetMethodID(cls, "getAutomaticStreamingFlags", "()I");
+ if (methodId == NULL) {
+ DLOGW("Couldn't find method id getAutomaticStreamingFlags");
+ } else {
+ pClientInfo->automaticStreamingFlags = (AUTOMATIC_STREAMING_FLAGS) env->CallIntMethod(clientInfo, methodId);
+ CHK_JVM_EXCEPTION(env);
+ }
+
CleanUp:
return STATUS_FAILED(retStatus) ? FALSE : TRUE;
}
@@ -429,6 +453,15 @@ BOOL setStreamInfo(JNIEnv* env, jobject streamInfo, PStreamInfo pStreamInfo)
CHK_JVM_EXCEPTION(env);
}
+ methodId = env->GetMethodID(cls, "isAllowStreamCreation", "()Z");
+ if (methodId == NULL) {
+ DLOGW("Couldn't find method id isAllowStreamCreation");
+ } else {
+ pStreamInfo->streamCaps.allowStreamCreation = env->CallBooleanMethod(streamInfo, methodId);
+ CHK_JVM_EXCEPTION(env);
+ }
+
+
methodId = env->GetMethodID(cls, "getMaxLatency", "()J");
if (methodId == NULL) {
DLOGW("Couldn't find method id getMaxLatency");
@@ -713,6 +746,14 @@ BOOL setStreamInfo(JNIEnv* env, jobject streamInfo, PStreamInfo pStreamInfo)
CHK_JVM_EXCEPTION(env);
}
+ methodId = env->GetMethodID(cls, "getStorePressurePolicy", "()I");
+ if (methodId == NULL) {
+ DLOGW("Couldn't find method id getStorePressurePolicy");
+ } else {
+ pStreamInfo->streamCaps.storePressurePolicy = (CONTENT_STORE_PRESSURE_POLICY) env->CallIntMethod(streamInfo, methodId);
+ CHK_JVM_EXCEPTION(env);
+ }
+
CleanUp:
return STATUS_FAILED(retStatus) ? FALSE : TRUE;
}
diff --git a/src/JNI/include/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h b/src/JNI/include/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h
index e3768cf5..cbe27bfe 100644
--- a/src/JNI/include/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h
+++ b/src/JNI/include/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h
@@ -54,8 +54,8 @@
class KinesisVideoClientWrapper
{
CLIENT_HANDLE mClientHandle;
- JavaVM *mJvm;
- jobject mGlobalJniObjRef;
+ static JavaVM *mJvm; // scope revised to static to make it accessible from static function- logPrintFunc
+ static jobject mGlobalJniObjRef; // scope revised to static to make it accessible from static function- logPrintFunc
ClientCallbacks mClientCallbacks;
DeviceInfo mDeviceInfo;
AuthInfo mAuthInfo;
@@ -86,6 +86,7 @@ class KinesisVideoClientWrapper
jmethodID mClientReadyMethodId;
jmethodID mCreateDeviceMethodId;
jmethodID mDeviceCertToTokenMethodId;
+ static jmethodID mLogPrintMethodId;
//////////////////////////////////////////////////////////////////////////////////////
// Internal private methods
@@ -166,6 +167,7 @@ class KinesisVideoClientWrapper
static STATUS deviceCertToTokenFunc(UINT64,
PCHAR,
PServiceCallContext);
+ static VOID logPrintFunc(UINT32, PCHAR, PCHAR, ...);
public:
KinesisVideoClientWrapper(JNIEnv* env,
diff --git a/src/KinesisVideoProducer.cpp b/src/KinesisVideoProducer.cpp
index a8af0c0b..69c57236 100644
--- a/src/KinesisVideoProducer.cpp
+++ b/src/KinesisVideoProducer.cpp
@@ -18,9 +18,9 @@ unique_ptr KinesisVideoProducer::create(
const std::string &control_plane_uri,
const std::string &user_agent_name) {
- unique_ptr callback_provider(new DefaultCallbackProvider(move(client_callback_provider),
- move(stream_callback_provider),
- move(credential_provider),
+ unique_ptr callback_provider(new DefaultCallbackProvider(std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credential_provider),
region,
control_plane_uri,
user_agent_name,
@@ -29,7 +29,7 @@ unique_ptr KinesisVideoProducer::create(
false,
DEFAULT_ENDPOINT_CACHE_UPDATE_PERIOD));
- return KinesisVideoProducer::create(move(device_info_provider), move(callback_provider));
+ return KinesisVideoProducer::create(std::move(device_info_provider), std::move(callback_provider));
}
unique_ptr KinesisVideoProducer::create(
@@ -52,7 +52,7 @@ unique_ptr KinesisVideoProducer::create(
}
kinesis_video_producer->client_handle_ = client_handle;
- kinesis_video_producer->callback_provider_ = move(callback_provider);
+ kinesis_video_producer->callback_provider_ = std::move(callback_provider);
return kinesis_video_producer;
}
@@ -68,9 +68,9 @@ unique_ptr KinesisVideoProducer::createSync(
bool is_caching_endpoint,
uint64_t caching_update_period) {
- unique_ptr callback_provider(new DefaultCallbackProvider(move(client_callback_provider),
- move(stream_callback_provider),
- move(credential_provider),
+ unique_ptr callback_provider(new DefaultCallbackProvider(std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credential_provider),
region,
control_plane_uri,
user_agent_name,
@@ -79,7 +79,32 @@ unique_ptr KinesisVideoProducer::createSync(
is_caching_endpoint,
caching_update_period));
- return KinesisVideoProducer::createSync(move(device_info_provider), move(callback_provider));
+ return KinesisVideoProducer::createSync(std::move(device_info_provider), std::move(callback_provider));
+}
+
+unique_ptr KinesisVideoProducer::createSync(
+ unique_ptr device_info_provider,
+ unique_ptr client_callback_provider,
+ unique_ptr stream_callback_provider,
+ unique_ptr credential_provider,
+ API_CALL_CACHE_TYPE api_call_caching,
+ const std::string ®ion,
+ const std::string &control_plane_uri,
+ const std::string &user_agent_name,
+ uint64_t caching_update_period) {
+
+ unique_ptr callback_provider(new DefaultCallbackProvider(std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credential_provider),
+ region,
+ control_plane_uri,
+ user_agent_name,
+ device_info_provider->getCustomUserAgent(),
+ device_info_provider->getCertPath(),
+ api_call_caching,
+ caching_update_period));
+
+ return KinesisVideoProducer::createSync(std::move(device_info_provider), std::move(callback_provider));
}
unique_ptr KinesisVideoProducer::createSync(
@@ -102,7 +127,7 @@ unique_ptr KinesisVideoProducer::createSync(
}
kinesis_video_producer->client_handle_ = client_handle;
- kinesis_video_producer->callback_provider_ = move(callback_provider);
+ kinesis_video_producer->callback_provider_ = std::move(callback_provider);
return kinesis_video_producer;
}
@@ -179,7 +204,13 @@ void KinesisVideoProducer::freeStreams() {
for (auto i = 0; i < num_streams; i++) {
auto stream = active_streams_.getAt(0);
- freeStream(stream);
+ try {
+ freeStream(stream);
+ LOG_INFO("Completed freeing stream " << stream->stream_name_);
+ } catch (std::runtime_error &err) {
+ LOG_ERROR("Failed to free stream " << stream->stream_name_ << ". Error: " << err.what());
+ }
+
}
}
}
@@ -190,12 +221,12 @@ KinesisVideoProducer::~KinesisVideoProducer() {
// Freeing the underlying client object
freeKinesisVideoClient();
+ LOG_INFO("Completed freeing client");
}
KinesisVideoProducerMetrics KinesisVideoProducer::getMetrics() const {
STATUS status = ::getKinesisVideoMetrics(client_handle_, (PClientMetrics) client_metrics_.getRawMetrics());
- LOG_AND_THROW_IF(STATUS_FAILED(status), "Failed to get producer metrics with: " << status);
-
+ LOG_AND_THROW_IF(STATUS_FAILED(status), "Failed to get producer client metrics with: " << status);
return client_metrics_;
}
diff --git a/src/KinesisVideoProducer.h b/src/KinesisVideoProducer.h
index 8acd34db..da10236f 100644
--- a/src/KinesisVideoProducer.h
+++ b/src/KinesisVideoProducer.h
@@ -40,7 +40,7 @@ namespace com { namespace amazonaws { namespace kinesis { namespace video {
* We will add extra 10 milliseconds to account for thread scheduling to ensure the callback is complete.
*/
#define CLIENT_STREAM_CLOSED_CALLBACK_AWAIT_TIME_MILLIS (10 + TIMEOUT_AFTER_STREAM_STOPPED + TIMEOUT_WAIT_FOR_CURL_BUFFER)
-
+#define CONTROL_PLANE_URI_ENV_VAR ((PCHAR) "CONTROL_PLANE_URI")
/**
* Kinesis Video client interface for real time streaming. The structure of this class is that each instance of type
* is a singleton where T is the implementation of the DeviceInfoProvider interface and U is the implementation of the
@@ -84,6 +84,17 @@ class KinesisVideoProducer {
bool is_caching_endpoint = false,
uint64_t caching_update_period = DEFAULT_ENDPOINT_CACHE_UPDATE_PERIOD);
+ static std::unique_ptr createSync(
+ std::unique_ptr device_info_provider,
+ std::unique_ptr client_callback_provider,
+ std::unique_ptr stream_callback_provider,
+ std::unique_ptr credential_provider,
+ API_CALL_CACHE_TYPE api_call_caching = API_CALL_CACHE_TYPE_ALL,
+ const std::string ®ion = DEFAULT_AWS_REGION,
+ const std::string &control_plane_uri = "",
+ const std::string &user_agent_name = DEFAULT_USER_AGENT_NAME,
+ uint64_t caching_update_period = DEFAULT_ENDPOINT_CACHE_UPDATE_PERIOD);
+
static std::unique_ptr createSync(
std::unique_ptr device_info_provider,
std::unique_ptr callback_provider);
diff --git a/src/KinesisVideoStream.cpp b/src/KinesisVideoStream.cpp
index a6549f33..fc030629 100644
--- a/src/KinesisVideoStream.cpp
+++ b/src/KinesisVideoStream.cpp
@@ -19,15 +19,16 @@ KinesisVideoStream::KinesisVideoStream(const KinesisVideoProducer& kinesis_video
}
}
-bool KinesisVideoStream::putFrame(KinesisVideoFrame frame) const {
+bool KinesisVideoStream::putFrame(KinesisVideoFrame& frame) const {
if (debug_dump_frame_info_) {
- LOG_DEBUG("pts: " << frame.presentationTs << ", dts: " << frame.decodingTs << ", duration: " << frame.duration << ", size: " << frame.size << ", trackId: " << frame.trackId
+ LOG_DEBUG("[" << this->stream_name_ << "] pts: " << frame.presentationTs << ", dts: " << frame.decodingTs << ", duration: " << frame.duration << ", size: " << frame.size << ", trackId: " << frame.trackId
<< ", isKey: " << CHECK_FRAME_FLAG_KEY_FRAME(frame.flags));
}
assert(0 != stream_handle_);
STATUS status = putKinesisVideoFrame(stream_handle_, &frame);
if (STATUS_FAILED(status)) {
+ LOG_ERROR("Put frame for " << this->stream_name_ << " failed with 0x" << std::hex << status);
return false;
}
@@ -35,26 +36,32 @@ bool KinesisVideoStream::putFrame(KinesisVideoFrame frame) const {
// TODO: this will create too much spam in case of audio
if (CHECK_FRAME_FLAG_KEY_FRAME(frame.flags)) {
// Extract metrics and print out
- auto stream_metrics = getMetrics();
- auto client_metrics = kinesis_video_producer_.getMetrics();
- auto total_transfer_tate = 8 * client_metrics.getTotalTransferRate();
- auto transfer_rate = 8 * stream_metrics.getCurrentTransferRate();
-
- LOG_DEBUG("Kinesis Video client and stream metrics"
- << "\n\t>> Overall storage byte size: " << client_metrics.getContentStoreSizeSize()
- << "\n\t>> Available storage byte size: " << client_metrics.getContentStoreAvailableSize()
- << "\n\t>> Allocated storage byte size: " << client_metrics.getContentStoreAllocatedSize()
- << "\n\t>> Total view allocation byte size: " << client_metrics.getTotalContentViewsSize()
- << "\n\t>> Total streams elementary frame rate (fps): " << client_metrics.getTotalElementaryFrameRate()
- << "\n\t>> Total streams transfer rate (bps): " << total_transfer_tate << " (" << total_transfer_tate / 1024 << " Kbps)"
- << "\n\t>> Current view duration (ms): " << stream_metrics.getCurrentViewDuration().count()
- << "\n\t>> Overall view duration (ms): " << stream_metrics.getOverallViewDuration().count()
- << "\n\t>> Current view byte size: " << stream_metrics.getCurrentViewSize()
- << "\n\t>> Overall view byte size: " << stream_metrics.getOverallViewSize()
- << "\n\t>> Current elementary frame rate (fps): " << stream_metrics.getCurrentElementaryFrameRate()
- << "\n\t>> Current transfer rate (bps): " << transfer_rate << " (" << transfer_rate / 1024 << " Kbps)");
+ try {
+ auto stream_metrics = getMetrics();
+ auto client_metrics = kinesis_video_producer_.getMetrics();
+ auto total_transfer_tate = 8 * client_metrics.getTotalTransferRate();
+ auto transfer_rate = 8 * stream_metrics.getCurrentTransferRate();
+
+ LOG_DEBUG("Kinesis Video client and stream metrics for "
+ << this->stream_name_
+ << "\n\t>> Overall storage byte size: " << client_metrics.getContentStoreSizeSize()
+ << "\n\t>> Available storage byte size: " << client_metrics.getContentStoreAvailableSize()
+ << "\n\t>> Allocated storage byte size: " << client_metrics.getContentStoreAllocatedSize()
+ << "\n\t>> Total view allocation byte size: " << client_metrics.getTotalContentViewsSize()
+ << "\n\t>> Total streams elementary frame rate (fps): " << client_metrics.getTotalElementaryFrameRate()
+ << "\n\t>> Total streams transfer rate (bps): " << total_transfer_tate << " (" << total_transfer_tate / 1024 << " Kbps)"
+ << "\n\t>> Current view duration (ms): " << stream_metrics.getCurrentViewDuration().count()
+ << "\n\t>> Overall view duration (ms): " << stream_metrics.getOverallViewDuration().count()
+ << "\n\t>> Current view byte size: " << stream_metrics.getCurrentViewSize()
+ << "\n\t>> Overall view byte size: " << stream_metrics.getOverallViewSize()
+ << "\n\t>> Current elementary frame rate (fps): " << stream_metrics.getCurrentElementaryFrameRate()
+ << "\n\t>> Current transfer rate (bps): " << transfer_rate << " (" << transfer_rate / 1024 << " Kbps)");
+ } catch (std::runtime_error &err) {
+ LOG_ERROR("Failed to get metrics. Error: " << err.what());
+ }
}
+ // Even if metrics fail, we do not want to return false for putFrame. We just log the error
return true;
}
@@ -66,19 +73,19 @@ bool KinesisVideoStream::start(const std::string& hexEncodedCodecPrivateData, ui
STATUS status;
if (STATUS_FAILED(status = hexDecode((PCHAR) pStrCpd, 0, NULL, &size))) {
- LOG_ERROR("Failed to get the size of the buffer for hex decoding the codec private data with: " << status);
+ LOG_ERROR("Failed to get the size of the buffer for hex decoding the codec private data with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
return false;
}
// Allocate the buffer needed
pBuffer = reinterpret_cast(malloc(size));
if (nullptr == pBuffer) {
- LOG_ERROR("Failed to allocate enough buffer for hex decoding. Size: " << size);
+ LOG_ERROR("Failed to allocate enough buffer for hex decoding. Size: " << size << " for stream name: " << this->stream_name_);
return false;
}
if (STATUS_FAILED(status = hexDecode((PCHAR) pStrCpd, 0, pBuffer, &size))) {
- LOG_ERROR("Failed to hex decode the codec private data with: " << status);
+ LOG_ERROR("Failed to hex decode the codec private data with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
::free(pBuffer);
return false;
}
@@ -97,7 +104,7 @@ bool KinesisVideoStream::start(const unsigned char* codecPrivateData, size_t cod
if (STATUS_FAILED(status = kinesisVideoStreamFormatChanged(stream_handle_, (UINT32) codecPrivateDataSize,
(PBYTE) codecPrivateData, (UINT64) trackId))) {
- LOG_ERROR("Failed to set the codec private data with: " << status);
+ LOG_ERROR("Failed to set the codec private data with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
return false;
}
@@ -115,7 +122,7 @@ bool KinesisVideoStream::resetConnection() {
STATUS status = STATUS_SUCCESS;
if (STATUS_FAILED(status = kinesisVideoStreamResetConnection(stream_handle_))) {
- LOG_ERROR("Failed to reset the connection with: " << status);
+ LOG_ERROR("Failed to reset the connection with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
return false;
}
@@ -126,7 +133,7 @@ bool KinesisVideoStream::resetStream() {
STATUS status = STATUS_SUCCESS;
if (STATUS_FAILED(status = kinesisVideoStreamResetStream(stream_handle_))) {
- LOG_ERROR("Failed to reset the stream with: " << status);
+ LOG_ERROR("Failed to reset the stream with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
return false;
}
@@ -134,7 +141,7 @@ bool KinesisVideoStream::resetStream() {
}
void KinesisVideoStream::free() {
- LOG_INFO("Freeing Kinesis Video Stream " << stream_name_);
+ LOG_INFO("Freeing Kinesis Video Stream for " << this->stream_name_);
// Free the underlying stream
std::call_once(free_kinesis_video_stream_flag_, freeKinesisVideoStream, getStreamHandle());
@@ -144,7 +151,7 @@ bool KinesisVideoStream::stop() {
STATUS status;
if (STATUS_FAILED(status = stopKinesisVideoStream(stream_handle_))) {
- LOG_ERROR("Failed to stop the stream with: " << status);
+ LOG_ERROR("Failed to stop the stream with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
return false;
}
@@ -153,9 +160,8 @@ bool KinesisVideoStream::stop() {
bool KinesisVideoStream::stopSync() {
STATUS status;
-
if (STATUS_FAILED(status = stopKinesisVideoStreamSync(stream_handle_))) {
- LOG_ERROR("Failed to stop the stream with: " << status);
+ LOG_ERROR("Failed to stop the stream with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
return false;
}
@@ -164,17 +170,28 @@ bool KinesisVideoStream::stopSync() {
KinesisVideoStreamMetrics KinesisVideoStream::getMetrics() const {
STATUS status = ::getKinesisVideoStreamMetrics(stream_handle_, (PStreamMetrics) stream_metrics_.getRawMetrics());
- LOG_AND_THROW_IF(STATUS_FAILED(status), "Failed to get stream metrics with: " << status);
+ LOG_AND_THROW_IF(STATUS_FAILED(status), "Failed to get stream metrics with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
return stream_metrics_;
}
-bool KinesisVideoStream::putFragmentMetadata(const std::string &name, const std::string &value, bool persistent){
+bool KinesisVideoStream::putFragmentMetadata(const std::string &name, const std::string &value, bool persistent) {
const char* pMetadataName = name.c_str();
const char* pMetadataValue = value.c_str();
STATUS status = ::putKinesisVideoFragmentMetadata(stream_handle_, (PCHAR) pMetadataName, (PCHAR) pMetadataValue, persistent);
if (STATUS_FAILED(status)) {
- LOG_ERROR("Failed to insert fragment metadata with: " << status);
+ LOG_ERROR("Failed to insert fragment metadata with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
+ return false;
+ }
+
+ return true;
+}
+
+bool KinesisVideoStream::putEventMetadata(uint32_t event, PStreamEventMetadata pStreamEventMetadata) {
+ STATUS status = ::putKinesisVideoEventMetadata(stream_handle_, event, pStreamEventMetadata);
+
+ if (STATUS_FAILED(status)) {
+ LOG_ERROR("Failed to insert fragment metadata with: 0x" << std::hex << status << " for stream name: " << this->stream_name_);
return false;
}
diff --git a/src/KinesisVideoStream.h b/src/KinesisVideoStream.h
index 6358c07d..294b109d 100644
--- a/src/KinesisVideoStream.h
+++ b/src/KinesisVideoStream.h
@@ -60,7 +60,7 @@ class KinesisVideoStream {
* @param frame The frame to be packaged and streamed.
* @return true if the encoder accepted the frame and false otherwise.
*/
- bool putFrame(KinesisVideoFrame frame) const;
+ bool putFrame(KinesisVideoFrame& frame) const;
/**
* Gets the stream metrics.
@@ -83,6 +83,18 @@ class KinesisVideoStream {
*/
bool putFragmentMetadata(const std::string& name, const std::string& value, bool persistent = true);
+ /**
+ * Appends an MKV associated with an event
+ *
+ * NOTE: the MKV is added after each fragment cluster.
+ * This is a limitation of MKV format as Tags are level 1 elements.
+ * Instead, they will be accumulated and inserted in-between the fragments and at the end of the stream.
+ * @param 1 event - enum value from enum STREAM_EVENT_TYPE. These are bit flags and can be bitwise OR'd
+ * to send multiple.
+ * @param 2 pStreamEventMetadata - optional param to include custom metadata. Limited to 5 name/value pairs.
+ *
+ */
+ bool putEventMetadata(uint32_t event, PStreamEventMetadata pStreamEventMetadata = NULL);
/**
* Initializes the track identified by trackId with a hex-encoded codec private data
* and puts the stream in a state that it is ready to receive frames via putFrame().
@@ -197,7 +209,7 @@ class KinesisVideoStream {
/**
* Whether the stream is closed
*/
- volatile bool stream_closed_;
+ volatile bool stream_closed_ = false;
/**
* Stream metrics
diff --git a/src/Logger.h b/src/Logger.h
index 1fda992b..072e6db3 100644
--- a/src/Logger.h
+++ b/src/Logger.h
@@ -31,7 +31,7 @@ namespace com { namespace amazonaws { namespace kinesis { namespace video {
#else
#define _LOG_CONFIGURE_CONSOLE(level, logToStdErr) \
log4cplus::helpers::SharedObjectPtr _appender(new log4cplus::ConsoleAppender()); \
- _appender->setLayout(move(std::unique_ptr(new log4cplus::PatternLayout("%D [%t] ")))); \
+ _appender->setLayout(std::move(std::unique_ptr(new log4cplus::PatternLayout("%D [%t] ")))); \
log4cplus::BasicConfigurator::doConfigure(log4cplus::Logger::getDefaultHierarchy(), logToStdErr); \
log4cplus::Logger::getRoot().addAppender(_appender); \
log4cplus::Logger::getRoot().setLogLevel(log4cplus::getLogLevelManager().fromString(level));
diff --git a/src/StreamCallbackProvider.h b/src/StreamCallbackProvider.h
index e0f163db..807d65ab 100644
--- a/src/StreamCallbackProvider.h
+++ b/src/StreamCallbackProvider.h
@@ -32,6 +32,10 @@ class StreamCallbackProvider {
*/
virtual UINT64 getCallbackCustomData() = 0;
+ virtual BufferDurationOverflowPressureFunc getBufferDurationOverFlowCallback() {
+ return nullptr;
+ }
+
/**
* The function returned by this callback takes two arguments:
* - UINT64 custom_data: Custom handle passed by the caller.
diff --git a/src/StreamDefinition.cpp b/src/StreamDefinition.cpp
index 0d734c54..044b7cbf 100644
--- a/src/StreamDefinition.cpp
+++ b/src/StreamDefinition.cpp
@@ -30,6 +30,7 @@ StreamDefinition::StreamDefinition(
bool fragment_acks,
bool restart_on_error,
bool recalculate_metrics,
+ bool allowStreamCreation,
uint32_t nal_adaptation_flags,
uint32_t frame_rate,
uint32_t avg_bandwidth_bps,
@@ -71,6 +72,7 @@ StreamDefinition::StreamDefinition(
stream_info_.streamCaps.fragmentAcks = fragment_acks;
stream_info_.streamCaps.recoverOnError = restart_on_error;
stream_info_.streamCaps.recalculateMetrics = recalculate_metrics;
+ stream_info_.streamCaps.allowStreamCreation = allowStreamCreation;
stream_info_.streamCaps.nalAdaptationFlags = nal_adaptation_flags;
stream_info_.streamCaps.frameRate = frame_rate;
stream_info_.streamCaps.avgBandwidthBps = avg_bandwidth_bps;
diff --git a/src/StreamDefinition.h b/src/StreamDefinition.h
index 8ce2a7d6..d320326a 100644
--- a/src/StreamDefinition.h
+++ b/src/StreamDefinition.h
@@ -57,6 +57,7 @@ class StreamDefinition {
bool fragment_acks = true,
bool restart_on_error = true,
bool recalculate_metrics = true,
+ bool allow_stream_creation = true,
uint32_t nal_adaptation_flags = NAL_ADAPTATION_ANNEXB_NALS | NAL_ADAPTATION_ANNEXB_CPD_NALS,
uint32_t frame_rate = 25,
uint32_t avg_bandwidth_bps = 4 * 1024 * 1024,
diff --git a/src/credential-providers/IotCertCredentialProvider.cpp b/src/credential-providers/IotCertCredentialProvider.cpp
index 6f8ffc7e..9036fee8 100644
--- a/src/credential-providers/IotCertCredentialProvider.cpp
+++ b/src/credential-providers/IotCertCredentialProvider.cpp
@@ -9,17 +9,20 @@ IotCertCredentialProvider::callback_t IotCertCredentialProvider::getCallbacks(PC
STATUS retStatus = STATUS_SUCCESS;
LOG_DEBUG("Creating IoT auth callbacks.");
- if (STATUS_FAILED(retStatus = createIotAuthCallbacks(client_callbacks,
- STRING_TO_PCHAR(iot_get_credential_endpoint_),
- STRING_TO_PCHAR(cert_path_),
- STRING_TO_PCHAR(private_key_path_),
- STRING_TO_PCHAR(ca_cert_path_),
- STRING_TO_PCHAR(role_alias_),
- STRING_TO_PCHAR(stream_name_),
- &iot_callbacks))) {
+ if (STATUS_FAILED(retStatus = createIotAuthCallbacksWithTimeouts(client_callbacks,
+ STRING_TO_PCHAR(iot_get_credential_endpoint_),
+ STRING_TO_PCHAR(cert_path_),
+ STRING_TO_PCHAR(private_key_path_),
+ STRING_TO_PCHAR(ca_cert_path_),
+ STRING_TO_PCHAR(role_alias_),
+ STRING_TO_PCHAR(stream_name_),
+ connectionTimeout_,
+ completionTimeout_,
+ &iot_callbacks))) {
std::stringstream status_strstrm;
status_strstrm << std::hex << retStatus;
LOG_AND_THROW("Unable to create Iot Credential provider. Error status: 0x" + status_strstrm.str());
}
+
return *iot_callbacks;
}
diff --git a/src/credential-providers/IotCertCredentialProvider.h b/src/credential-providers/IotCertCredentialProvider.h
index 9ccb7b20..180769ce 100644
--- a/src/credential-providers/IotCertCredentialProvider.h
+++ b/src/credential-providers/IotCertCredentialProvider.h
@@ -6,9 +6,10 @@
namespace com { namespace amazonaws { namespace kinesis { namespace video {
class IotCertCredentialProvider : public CredentialProvider {
- PAuthCallbacks iot_callbacks;
+ PAuthCallbacks iot_callbacks = nullptr;
const std::string iot_get_credential_endpoint_, cert_path_, private_key_path_, ca_cert_path_,
role_alias_,stream_name_;
+ uint64_t connectionTimeout_ = 0, completionTimeout_ = 0;
public:
IotCertCredentialProvider(const std::string iot_get_credential_endpoint,
@@ -16,13 +17,30 @@ namespace com { namespace amazonaws { namespace kinesis { namespace video {
const std::string private_key_path,
const std::string role_alias,
const std::string ca_cert_path,
- const std::string stream_name):
+ const std::string stream_name):
iot_get_credential_endpoint_(iot_get_credential_endpoint),
cert_path_(cert_path),
private_key_path_(private_key_path),
role_alias_(role_alias),
ca_cert_path_(ca_cert_path),
- stream_name_(stream_name) {}
+ stream_name_(stream_name) {}
+
+ IotCertCredentialProvider(const std::string iot_get_credential_endpoint,
+ const std::string cert_path,
+ const std::string private_key_path,
+ const std::string role_alias,
+ const std::string ca_cert_path,
+ const std::string stream_name,
+ uint64_t connectionTimeout,
+ uint64_t completionTimeout):
+ iot_get_credential_endpoint_(iot_get_credential_endpoint),
+ cert_path_(cert_path),
+ private_key_path_(private_key_path),
+ role_alias_(role_alias),
+ ca_cert_path_(ca_cert_path),
+ stream_name_(stream_name),
+ connectionTimeout_ (connectionTimeout),
+ completionTimeout_ (completionTimeout) {}
void updateCredentials(Credentials& credentials) override {
// no-op as credential update is handled in c producer iot auth callbacks
diff --git a/src/credential-providers/RotatingCredentialProvider.h b/src/credential-providers/RotatingCredentialProvider.h
index f7ceeb23..e6a4d43a 100644
--- a/src/credential-providers/RotatingCredentialProvider.h
+++ b/src/credential-providers/RotatingCredentialProvider.h
@@ -7,7 +7,7 @@ namespace com { namespace amazonaws { namespace kinesis { namespace video {
class RotatingCredentialProvider : public CredentialProvider {
std::string credential_file_path_;
- PAuthCallbacks rotating_callbacks;
+ PAuthCallbacks rotating_callbacks = nullptr;
public:
RotatingCredentialProvider(std::string credential_file_path): credential_file_path_(credential_file_path) {}
void updateCredentials(Credentials& credentials) override {
diff --git a/src/gstreamer/KvsSinkDeviceInfoProvider.cpp b/src/gstreamer/KvsSinkDeviceInfoProvider.cpp
index 4e8ab12c..471915ae 100644
--- a/src/gstreamer/KvsSinkDeviceInfoProvider.cpp
+++ b/src/gstreamer/KvsSinkDeviceInfoProvider.cpp
@@ -6,5 +6,8 @@ KvsSinkDeviceInfoProvider::device_info_t KvsSinkDeviceInfoProvider::getDeviceInf
auto device_info = DefaultDeviceInfoProvider::getDeviceInfo();
// Set the storage size to user specified size in MB
device_info.storageInfo.storageSize = static_cast(storage_size_mb_) * 1024 * 1024;
+ device_info.clientInfo.stopStreamTimeout = static_cast(stop_stream_timeout_sec_ * HUNDREDS_OF_NANOS_IN_A_SECOND);
+ device_info.clientInfo.serviceCallCompletionTimeout = static_cast(service_call_completion_timeout_sec_ * HUNDREDS_OF_NANOS_IN_A_SECOND);
+ device_info.clientInfo.serviceCallConnectionTimeout = static_cast(service_call_connection_timeout_sec_ * HUNDREDS_OF_NANOS_IN_A_SECOND);
return device_info;
}
diff --git a/src/gstreamer/KvsSinkDeviceInfoProvider.h b/src/gstreamer/KvsSinkDeviceInfoProvider.h
index 07a99fd7..d6d949c6 100644
--- a/src/gstreamer/KvsSinkDeviceInfoProvider.h
+++ b/src/gstreamer/KvsSinkDeviceInfoProvider.h
@@ -6,8 +6,18 @@
namespace com { namespace amazonaws { namespace kinesis { namespace video {
class KvsSinkDeviceInfoProvider: public DefaultDeviceInfoProvider {
uint64_t storage_size_mb_;
+ uint64_t stop_stream_timeout_sec_;
+ uint64_t service_call_connection_timeout_sec_;
+ uint64_t service_call_completion_timeout_sec_;
public:
- KvsSinkDeviceInfoProvider(uint64_t storage_size_mb): storage_size_mb_(storage_size_mb) {}
+ KvsSinkDeviceInfoProvider(uint64_t storage_size_mb,
+ uint64_t stop_stream_timeout_sec,
+ uint64_t service_call_connection_timeout_sec,
+ uint64_t service_call_completion_timeout_sec):
+ storage_size_mb_(storage_size_mb),
+ stop_stream_timeout_sec_(stop_stream_timeout_sec),
+ service_call_connection_timeout_sec_(service_call_connection_timeout_sec),
+ service_call_completion_timeout_sec_(service_call_completion_timeout_sec) {}
device_info_t getDeviceInfo() override;
};
}
diff --git a/src/gstreamer/KvsSinkStreamCallbackProvider.cpp b/src/gstreamer/KvsSinkStreamCallbackProvider.cpp
index 419208e0..3d1e5624 100644
--- a/src/gstreamer/KvsSinkStreamCallbackProvider.cpp
+++ b/src/gstreamer/KvsSinkStreamCallbackProvider.cpp
@@ -4,6 +4,17 @@ LOGGER_TAG("com.amazonaws.kinesis.video.gstkvs");
using namespace com::amazonaws::kinesis::video;
+STATUS
+KvsSinkStreamCallbackProvider::bufferDurationOverflowPressureHandler(UINT64 custom_data, STREAM_HANDLE stream_handle, UINT64 remainDuration) {
+ UNUSED_PARAM(custom_data);
+ return STATUS_SUCCESS;
+}
+
+STATUS
+KvsSinkStreamCallbackProvider::streamUnderflowReportHandler(UINT64 custom_data, STREAM_HANDLE stream_handle) {
+ return STATUS_SUCCESS;
+}
+
STATUS
KvsSinkStreamCallbackProvider::streamConnectionStaleHandler(UINT64 custom_data,
STREAM_HANDLE stream_handle,
@@ -19,12 +30,11 @@ KvsSinkStreamCallbackProvider::streamErrorReportHandler(UINT64 custom_data,
UPLOAD_HANDLE upload_handle,
UINT64 errored_timecode,
STATUS status_code) {
- LOG_ERROR("Reported stream error. Errored timecode: " << errored_timecode << " Status: 0x" << std::hex << status_code);
auto customDataObj = reinterpret_cast(custom_data);
-
- // ignore if the sdk can recover from the error
- if (!IS_RECOVERABLE_ERROR(status_code)) {
+ LOG_ERROR("Reported stream error. Errored timecode: " << errored_timecode << " Status: 0x" << std::hex << status_code << " for " << customDataObj->kvs_sink->stream_name);
+ if(customDataObj != NULL && (!IS_RECOVERABLE_ERROR(status_code))) {
customDataObj->stream_status = status_code;
+ g_signal_emit(G_OBJECT(customDataObj->kvs_sink), customDataObj->err_signal_id, 0, status_code);
}
return STATUS_SUCCESS;
@@ -39,12 +49,21 @@ KvsSinkStreamCallbackProvider::droppedFrameReportHandler(UINT64 custom_data,
return STATUS_SUCCESS; // continue streaming
}
+STATUS
+KvsSinkStreamCallbackProvider::droppedFragmentReportHandler(UINT64 custom_data,
+ STREAM_HANDLE stream_handle,
+ UINT64 dropped_fragment_timecode) {
+ UNUSED_PARAM(custom_data);
+ LOG_WARN("Reported droppedFrame callback for stream handle " << stream_handle << ". Dropped fragment timecode in 100ns: " << dropped_fragment_timecode);
+ return STATUS_SUCCESS; // continue streaming
+}
+
STATUS
KvsSinkStreamCallbackProvider::streamLatencyPressureHandler(UINT64 custom_data,
STREAM_HANDLE stream_handle,
UINT64 current_buffer_duration) {
UNUSED_PARAM(custom_data);
- LOG_DEBUG("Reported streamLatencyPressure callback for stream handle " << stream_handle << ". Current buffer duration in 100ns: " << current_buffer_duration);
+ LOG_WARN("Reported streamLatencyPressure callback for stream handle " << stream_handle << ". Current buffer duration in 100ns: " << current_buffer_duration);
return STATUS_SUCCESS;
}
@@ -52,8 +71,27 @@ STATUS
KvsSinkStreamCallbackProvider::streamClosedHandler(UINT64 custom_data,
STREAM_HANDLE stream_handle,
UPLOAD_HANDLE upload_handle) {
- UNUSED_PARAM(custom_data);
- LOG_DEBUG("Reported streamClosed callback for stream handle " << stream_handle << ". Upload handle " << upload_handle);
+ std::string streamName = "";
+ auto customDataObj = reinterpret_cast(custom_data);
+ if(customDataObj != NULL && customDataObj->kvs_sink != NULL) {
+ streamName = customDataObj->kvs_sink->stream_name;
+ }
+ LOG_DEBUG("[" << streamName << "]Reported streamClosed callback");
+ return STATUS_SUCCESS;
+}
+
+STATUS
+KvsSinkStreamCallbackProvider::fragmentAckReceivedHandler(UINT64 custom_data,
+ STREAM_HANDLE stream_handle,
+ UPLOAD_HANDLE upload_handle,
+ PFragmentAck pFragmentAck) {
+ auto customDataObj = reinterpret_cast(custom_data);
+
+ if(customDataObj != NULL && customDataObj->kvs_sink != NULL && pFragmentAck != NULL) {
+ LOG_TRACE("[" << customDataObj->kvs_sink->stream_name << "] Ack timestamp for " << pFragmentAck->ackType << " is " << pFragmentAck->timestamp);
+ g_signal_emit(G_OBJECT(customDataObj->kvs_sink), customDataObj->ack_signal_id, 0, pFragmentAck);
+ }
+
return STATUS_SUCCESS;
}
diff --git a/src/gstreamer/KvsSinkStreamCallbackProvider.h b/src/gstreamer/KvsSinkStreamCallbackProvider.h
index d936abbb..282e1e60 100644
--- a/src/gstreamer/KvsSinkStreamCallbackProvider.h
+++ b/src/gstreamer/KvsSinkStreamCallbackProvider.h
@@ -14,6 +14,14 @@ namespace com { namespace amazonaws { namespace kinesis { namespace video {
return reinterpret_cast (data.get());
}
+ StreamUnderflowReportFunc getStreamUnderflowReportCallback() override {
+ return streamUnderflowReportHandler;
+ }
+
+ BufferDurationOverflowPressureFunc getBufferDurationOverFlowCallback() override {
+ return bufferDurationOverflowPressureHandler;
+ }
+
StreamConnectionStaleFunc getStreamConnectionStaleCallback() override {
return streamConnectionStaleHandler;
};
@@ -30,17 +38,40 @@ namespace com { namespace amazonaws { namespace kinesis { namespace video {
return streamLatencyPressureHandler;
}
+ DroppedFragmentReportFunc getDroppedFragmentReportCallback() override {
+ return droppedFragmentReportHandler;
+ }
+
StreamClosedFunc getStreamClosedCallback() override {
return streamClosedHandler;
}
+ FragmentAckReceivedFunc getFragmentAckReceivedCallback() override{
+ return fragmentAckReceivedHandler;
+ }
+
private:
+ static STATUS
+ streamUnderflowReportHandler(UINT64 custom_data, STREAM_HANDLE stream_handle);
+
+ static STATUS
+ bufferDurationOverflowPressureHandler(UINT64 custom_data, STREAM_HANDLE stream_handle, UINT64 remainDuration);
+
+ static STATUS
+ streamLatencyPressureHandler(UINT64 custom_data, STREAM_HANDLE stream_handle,
+ UINT64 current_buffer_duration);
+
static STATUS
streamConnectionStaleHandler(UINT64 custom_data, STREAM_HANDLE stream_handle,
UINT64 last_buffering_ack);
static STATUS
- streamErrorReportHandler(UINT64 custom_data, STREAM_HANDLE stream_handle, UPLOAD_HANDLE upload_handle, UINT64 errored_timecode,
+ droppedFragmentReportHandler(UINT64 custom_data, STREAM_HANDLE stream_handle,
+ UINT64 fragment_timecode);
+
+ static STATUS
+ streamErrorReportHandler(UINT64 custom_data, STREAM_HANDLE stream_handle,
+ UPLOAD_HANDLE upload_handle, UINT64 errored_timecode,
STATUS status_code);
static STATUS
@@ -48,11 +79,10 @@ namespace com { namespace amazonaws { namespace kinesis { namespace video {
UINT64 dropped_frame_timecode);
static STATUS
- streamLatencyPressureHandler(UINT64 custom_data, STREAM_HANDLE stream_handle,
- UINT64 current_buffer_duration);
+ streamClosedHandler(UINT64 custom_data, STREAM_HANDLE stream_handle, UPLOAD_HANDLE upload_handle);
static STATUS
- streamClosedHandler(UINT64 custom_data, STREAM_HANDLE stream_handle, UPLOAD_HANDLE upload_handle);
+ fragmentAckReceivedHandler(UINT64 custom_data, STREAM_HANDLE stream_handle, UPLOAD_HANDLE upload_handle, PFragmentAck pFragmentAck);
};
}
}
diff --git a/src/gstreamer/Util/KvsSinkUtil.cpp b/src/gstreamer/Util/KvsSinkUtil.cpp
index 11d4e1c2..097c311f 100644
--- a/src/gstreamer/Util/KvsSinkUtil.cpp
+++ b/src/gstreamer/Util/KvsSinkUtil.cpp
@@ -8,7 +8,10 @@ static const std::set iot_param_set = {IOT_GET_CREDENTIAL_ENDPOINT,
CERTIFICATE_PATH,
PRIVATE_KEY_PATH,
CA_CERT_PATH,
- ROLE_ALIASES};
+ ROLE_ALIASES,
+ IOT_THING_NAME,
+ IOT_CONNECTION_TIMEOUT,
+ IOT_COMPLETION_TIMEOUT};
static const time_t time_point = std::time(NULL);
static const long timezone_offset =
@@ -66,6 +69,19 @@ gboolean parseIotCredentialGstructure(GstStructure *g_struct, std::mapfirst);
}
+
+ if(params_key_set.count(IOT_THING_NAME) == 0) {
+ params_key_set.insert(IOT_THING_NAME);
+ }
+
+ if(params_key_set.count(IOT_CONNECTION_TIMEOUT) == 0) {
+ params_key_set.insert(IOT_CONNECTION_TIMEOUT);
+ }
+
+ if(params_key_set.count(IOT_COMPLETION_TIMEOUT) == 0) {
+ params_key_set.insert(IOT_COMPLETION_TIMEOUT);
+ }
+
if (params_key_set != iot_param_set) {
std::ostringstream ostream;
std::copy(iot_param_set.begin(), iot_param_set.end(), std::ostream_iterator(ostream, ","));
diff --git a/src/gstreamer/Util/KvsSinkUtil.h b/src/gstreamer/Util/KvsSinkUtil.h
index b28f285b..a4476490 100644
--- a/src/gstreamer/Util/KvsSinkUtil.h
+++ b/src/gstreamer/Util/KvsSinkUtil.h
@@ -13,6 +13,9 @@
#define PRIVATE_KEY_PATH "key-path"
#define CA_CERT_PATH "ca-path"
#define ROLE_ALIASES "role-aliases"
+#define IOT_THING_NAME "iot-thing-name"
+#define IOT_CONNECTION_TIMEOUT "connection-timeout"
+#define IOT_COMPLETION_TIMEOUT "completion-timeout"
namespace kvs_sink_util{
diff --git a/src/gstreamer/gstkvssink.cpp b/src/gstreamer/gstkvssink.cpp
index 9c362f6e..83b9a4b1 100644
--- a/src/gstreamer/gstkvssink.cpp
+++ b/src/gstreamer/gstkvssink.cpp
@@ -88,8 +88,11 @@ GST_DEBUG_CATEGORY_STATIC (gst_kvs_sink_debug);
#define DEFAULT_ABSOLUTE_FRAGMENT_TIMES TRUE
#define DEFAULT_FRAGMENT_ACKS TRUE
#define DEFAULT_RESTART_ON_ERROR TRUE
+#define DEFAULT_ALLOW_CREATE_STREAM TRUE
#define DEFAULT_RECALCULATE_METRICS TRUE
#define DEFAULT_DISABLE_BUFFER_CLIPPING FALSE
+#define DEFAULT_USE_ORIGINAL_PTS FALSE
+#define DEFAULT_ENABLE_METRICS FALSE
#define DEFAULT_STREAM_FRAMERATE 25
#define DEFAULT_STREAM_FRAMERATE_HIGH_DENSITY 100
#define DEFAULT_AVG_BANDWIDTH_BPS (4 * 1024 * 1024)
@@ -101,10 +104,16 @@ GST_DEBUG_CATEGORY_STATIC (gst_kvs_sink_debug);
#define DEFAULT_TRACKNAME "kinesis_video"
#define DEFAULT_ACCESS_KEY "access_key"
#define DEFAULT_SECRET_KEY "secret_key"
+#define DEFAULT_SESSION_TOKEN "session_token"
#define DEFAULT_REGION "us-west-2"
#define DEFAULT_ROTATION_PERIOD_SECONDS 3600
-#define DEFAULT_LOG_FILE_PATH "./kvs_log_configuration"
+#define DEFAULT_LOG_FILE_PATH "../kvs_log_configuration"
#define DEFAULT_STORAGE_SIZE_MB 128
+#define DEFAULT_STOP_STREAM_TIMEOUT_SEC 120
+#define DEFAULT_SERVICE_CONNECTION_TIMEOUT_SEC 5
+#define DEFAULT_SERVICE_COMPLETION_TIMEOUT_SEC 10
+#define DEFAULT_IOT_CONNECTION_TIMEOUT_SEC 3
+#define DEFAULT_IOT_COMPLETION_TIMEOUT_SEC 5
#define DEFAULT_CREDENTIAL_FILE_PATH ".kvs/credential"
#define DEFAULT_FRAME_DURATION_MS 2
@@ -112,7 +121,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_kvs_sink_debug);
#define KVS_ADD_METADATA_NAME "name"
#define KVS_ADD_METADATA_VALUE "value"
#define KVS_ADD_METADATA_PERSISTENT "persist"
-#define KVS_CLIENT_USER_AGENT_NAME "AWS-SDK-KVS-CLIENT"
+#define KVS_CLIENT_USER_AGENT_NAME "AWS-SDK-KVS-CPP-CLIENT"
#define DEFAULT_AUDIO_TRACK_NAME "audio"
#define DEFAULT_AUDIO_CODEC_ID_AAC "A_AAC"
@@ -128,6 +137,12 @@ GST_DEBUG_CATEGORY_STATIC (gst_kvs_sink_debug);
#define MAX_GSTREAMER_MEDIA_TYPE_LEN 16
+namespace KvsSinkSignals {
+ guint err_signal_id;
+ guint ack_signal_id;
+ guint metric_signal_id;
+};
+
enum {
PROP_0,
PROP_STREAM_NAME,
@@ -152,15 +167,23 @@ enum {
PROP_TRACK_NAME,
PROP_ACCESS_KEY,
PROP_SECRET_KEY,
+ PROP_SESSION_TOKEN,
PROP_AWS_REGION,
PROP_ROTATION_PERIOD,
PROP_LOG_CONFIG_PATH,
PROP_STORAGE_SIZE,
+ PROP_STOP_STREAM_TIMEOUT,
+ PROP_SERVICE_CONNECTION_TIMEOUT,
+ PROP_SERVICE_COMPLETION_TIMEOUT,
PROP_CREDENTIAL_FILE_PATH,
PROP_IOT_CERTIFICATE,
PROP_STREAM_TAGS,
PROP_FILE_START_TIME,
- PROP_DISABLE_BUFFER_CLIPPING
+ PROP_DISABLE_BUFFER_CLIPPING,
+ PROP_USE_ORIGINAL_PTS,
+ PROP_GET_METRICS,
+ PROP_ALLOW_CREATE_STREAM,
+ PROP_USER_AGENT_NAME
};
#define GST_TYPE_KVS_SINK_STREAMING_TYPE (gst_kvs_sink_streaming_type_get_type())
@@ -232,21 +255,31 @@ static GstPad* gst_kvs_sink_request_new_pad (GstElement *element, GstPadTemplate
const gchar* name, const GstCaps *caps);
static void gst_kvs_sink_release_pad (GstElement *element, GstPad *pad);
+void closed(UINT64 custom_data, STREAM_HANDLE stream_handle, UPLOAD_HANDLE upload_handle) {
+ LOG_INFO("Closed connection with stream handle "<data;
- unique_ptr device_info_provider(new KvsSinkDeviceInfoProvider(kvssink->storage_size));
+ unique_ptr device_info_provider(new KvsSinkDeviceInfoProvider(kvssink->storage_size,
+ kvssink->stop_stream_timeout,
+ kvssink->service_connection_timeout,
+ kvssink->service_completion_timeout));
unique_ptr client_callback_provider(new KvsSinkClientCallbackProvider());
unique_ptr stream_callback_provider(new KvsSinkStreamCallbackProvider(data));
+ kvssink->data->kvs_sink = kvssink;
+
char const *access_key;
char const *secret_key;
char const *session_token;
char const *default_region;
+ char const *control_plane_uri;
string access_key_str;
string secret_key_str;
string session_token_str;
string region_str;
+ string control_plane_uri_str = "";
bool credential_is_static = true;
// This needs to happen after we've read in ALL of the properties
@@ -255,6 +288,8 @@ void kinesis_video_producer_init(GstKvsSink *kvssink)
GST_DEBUG_FUNCPTR(gst_collect_pads_clip_running_time), kvssink);
}
+ kvssink->data->kvs_sink = kvssink;
+
if (0 == strcmp(kvssink->access_key, DEFAULT_ACCESS_KEY)) { // if no static credential is available in plugin property.
if (nullptr == (access_key = getenv(ACCESS_KEY_ENV_VAR))
|| nullptr == (secret_key = getenv(SECRET_KEY_ENV_VAR))) { // if no static credential is available in env var.
@@ -264,10 +299,6 @@ void kinesis_video_producer_init(GstKvsSink *kvssink)
} else {
access_key_str = string(access_key);
secret_key_str = string(secret_key);
- session_token_str = "";
- if (nullptr != (session_token = getenv(SESSION_TOKEN_ENV_VAR))) {
- session_token_str = string(session_token);
- }
}
} else {
@@ -275,6 +306,18 @@ void kinesis_video_producer_init(GstKvsSink *kvssink)
secret_key_str = string(kvssink->secret_key);
}
+ // Handle session token seperately, since this is optional with long term credentials
+ if (0 == strcmp(kvssink->session_token, DEFAULT_SESSION_TOKEN)) {
+ session_token_str = "";
+ if (nullptr != (session_token = getenv(SESSION_TOKEN_ENV_VAR))) {
+ LOG_INFO("Setting session token from env for " << kvssink->stream_name);
+ session_token_str = string(session_token);
+ }
+ } else {
+ LOG_INFO("Setting session token from config for " << kvssink->stream_name);
+ session_token_str = string(kvssink->session_token);
+ }
+
if (nullptr == (default_region = getenv(DEFAULT_REGION_ENV_VAR))) {
region_str = string(kvssink->aws_region);
} else {
@@ -283,35 +326,59 @@ void kinesis_video_producer_init(GstKvsSink *kvssink)
unique_ptr credential_provider;
- if (credential_is_static) {
- kvssink->credentials_.reset(new Credentials(access_key_str,
- secret_key_str,
- session_token_str,
- std::chrono::seconds(DEFAULT_ROTATION_PERIOD_SECONDS)));
- credential_provider.reset(new StaticCredentialProvider(*kvssink->credentials_));
- } else if (kvssink->iot_certificate) {
+ if (kvssink->iot_certificate) {
+ LOG_INFO("Using iot credential provider within KVS sink for " << kvssink->stream_name);
std::map iot_cert_params;
+ uint64_t iot_connection_timeout = DEFAULT_IOT_CONNECTION_TIMEOUT_SEC * HUNDREDS_OF_NANOS_IN_A_SECOND;
+ uint64_t iot_completion_timeout = DEFAULT_IOT_COMPLETION_TIMEOUT_SEC * HUNDREDS_OF_NANOS_IN_A_SECOND;
if (!kvs_sink_util::parseIotCredentialGstructure(kvssink->iot_certificate, iot_cert_params)){
- LOG_AND_THROW("Failed to parse Iot credentials");
+ LOG_AND_THROW("Failed to parse Iot credentials for " << kvssink->stream_name);
+ }
+ std::map::iterator it = iot_cert_params.find(IOT_THING_NAME);
+ if (it == iot_cert_params.end()) {
+ iot_cert_params.insert( std::pair(IOT_THING_NAME, kvssink->stream_name) );
+ }
+
+ if (!iot_cert_params[IOT_CONNECTION_TIMEOUT].empty()) {
+ iot_connection_timeout = std::stoull(iot_cert_params[IOT_CONNECTION_TIMEOUT]) * HUNDREDS_OF_NANOS_IN_A_SECOND;
+ }
+
+ if (!iot_cert_params[IOT_COMPLETION_TIMEOUT].empty()) {
+ iot_completion_timeout = std::stoull(iot_cert_params[IOT_COMPLETION_TIMEOUT]) * HUNDREDS_OF_NANOS_IN_A_SECOND;
}
credential_provider.reset(new IotCertCredentialProvider(iot_cert_params[IOT_GET_CREDENTIAL_ENDPOINT],
- iot_cert_params[CERTIFICATE_PATH],
- iot_cert_params[PRIVATE_KEY_PATH],
- iot_cert_params[ROLE_ALIASES],
- iot_cert_params[CA_CERT_PATH],
- kvssink->stream_name));
+ iot_cert_params[CERTIFICATE_PATH],
+ iot_cert_params[PRIVATE_KEY_PATH],
+ iot_cert_params[ROLE_ALIASES],
+ iot_cert_params[CA_CERT_PATH],
+ iot_cert_params[IOT_THING_NAME],
+ iot_connection_timeout,
+ iot_completion_timeout));
+ } else if (credential_is_static) {
+ kvssink->credentials_.reset(new Credentials(access_key_str,
+ secret_key_str,
+ session_token_str,
+ std::chrono::seconds(DEFAULT_ROTATION_PERIOD_SECONDS)));
+ credential_provider.reset(new StaticCredentialProvider(*kvssink->credentials_));
} else {
credential_provider.reset(new RotatingCredentialProvider(kvssink->credential_file_path));
}
- data->kinesis_video_producer = KinesisVideoProducer::createSync(move(device_info_provider),
- move(client_callback_provider),
- move(stream_callback_provider),
- move(credential_provider),
+ // Handle env for providing CP URL
+ if (nullptr != (control_plane_uri = getenv(CONTROL_PLANE_URI_ENV_VAR))) {
+ LOG_INFO("Getting URL from env for " << kvssink->stream_name);
+ control_plane_uri_str = string(control_plane_uri);
+ }
+ LOG_INFO("User agent string: " << kvssink->user_agent);
+ data->kinesis_video_producer = KinesisVideoProducer::createSync(std::move(device_info_provider),
+ std::move(client_callback_provider),
+ std::move(stream_callback_provider),
+ std::move(credential_provider),
+ API_CALL_CACHE_TYPE_ALL,
region_str,
- "",
- KVS_CLIENT_USER_AGENT_NAME);
+ control_plane_uri_str,
+ kvssink->user_agent);
}
void create_kinesis_video_stream(GstKvsSink *kvssink) {
@@ -323,7 +390,7 @@ void create_kinesis_video_stream(GstKvsSink *kvssink) {
gboolean ret;
ret = kvs_sink_util::gstructToMap(kvssink->stream_tags, &stream_tags);
if (!ret) {
- LOG_WARN("Failed to parse stream tags");
+ LOG_WARN("Failed to parse stream tags for " << kvssink->stream_name);
} else {
p_stream_tags = &stream_tags;
}
@@ -365,12 +432,13 @@ void create_kinesis_video_stream(GstKvsSink *kvssink) {
duration_cast (seconds(kvssink->max_latency_seconds)),
milliseconds(kvssink->fragment_duration_miliseconds),
milliseconds(kvssink->timecode_scale_milliseconds),
- kvssink->key_frame_fragmentation,//Construct a fragment at each key frame
- kvssink->frame_timecodes,//Use provided frame timecode
- kvssink->absolute_fragment_times,//Relative timecode
- kvssink->fragment_acks,//Ack on fragment is enabled
- kvssink->restart_on_error,//SDK will restart when error happens
- kvssink->recalculate_metrics,//recalculate_metrics
+ kvssink->key_frame_fragmentation,// Construct a fragment at each key frame
+ kvssink->frame_timecodes,// Use provided frame timecode
+ kvssink->absolute_fragment_times,// Relative timecode
+ kvssink->fragment_acks,// Ack on fragment is enabled
+ kvssink->restart_on_error,// SDK will restart when error happens
+ kvssink->recalculate_metrics,// recalculate_metrics
+ kvssink->allow_create_stream, // allow stream creation if stream does not exist
0,
kvssink->framerate,
kvssink->avg_bandwidth_bps,
@@ -391,7 +459,7 @@ void create_kinesis_video_stream(GstKvsSink *kvssink) {
stream_definition->setFrameOrderMode(FRAME_ORDERING_MODE_MULTI_TRACK_AV_COMPARE_PTS_ONE_MS_COMPENSATE_EOFR);
}
- data->kinesis_video_stream = data->kinesis_video_producer->createStreamSync(move(stream_definition));
+ data->kinesis_video_stream = data->kinesis_video_producer->createStreamSync(std::move(stream_definition));
data->frame_count = 0;
cout << "Stream is ready" << endl;
}
@@ -403,7 +471,7 @@ bool kinesis_video_stream_init(GstKvsSink *kvssink, string &err_msg) {
bool do_retry = true;
while(do_retry) {
try {
- LOG_INFO("try creating stream");
+ LOG_INFO("Try creating stream for " << kvssink->stream_name);
// stream is freed when createStreamSync fails
create_kinesis_video_stream(kvssink);
break;
@@ -429,6 +497,7 @@ static void
gst_kvs_sink_class_init(GstKvsSinkClass *klass) {
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
+ GstKvsSinkClass *basesink_class = (GstKvsSinkClass *) klass;
gobject_class = G_OBJECT_CLASS (klass);
gstelement_class = GST_ELEMENT_CLASS (klass);
@@ -441,6 +510,10 @@ gst_kvs_sink_class_init(GstKvsSinkClass *klass) {
g_param_spec_string ("stream-name", "Stream Name",
"Name of the destination stream", DEFAULT_STREAM_NAME, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+ g_object_class_install_property (gobject_class, PROP_USER_AGENT_NAME,
+ g_param_spec_string ("user-agent", "Custom user agent name",
+ "Name of the user agent", KVS_CLIENT_USER_AGENT_NAME, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
g_object_class_install_property (gobject_class, PROP_RETENTION_PERIOD,
g_param_spec_uint ("retention-period", "Retention Period",
"Length of time stream is preserved. Unit: hours", 0, G_MAXUINT, DEFAULT_RETENTION_PERIOD_HOURS, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
@@ -531,6 +604,10 @@ gst_kvs_sink_class_init(GstKvsSinkClass *klass) {
g_param_spec_string ("secret-key", "Secret Key",
"AWS Secret Key", DEFAULT_SECRET_KEY, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+ g_object_class_install_property (gobject_class, PROP_SESSION_TOKEN,
+ g_param_spec_string ("session-token", "Session token",
+ "AWS Session token", DEFAULT_SESSION_TOKEN, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
g_object_class_install_property (gobject_class, PROP_AWS_REGION,
g_param_spec_string ("aws-region", "AWS Region",
"AWS Region", DEFAULT_REGION, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
@@ -547,6 +624,18 @@ gst_kvs_sink_class_init(GstKvsSinkClass *klass) {
g_param_spec_uint ("storage-size", "Storage Size",
"Storage Size. Unit: MB", 0, G_MAXUINT, DEFAULT_STORAGE_SIZE_MB, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+ g_object_class_install_property (gobject_class, PROP_STOP_STREAM_TIMEOUT,
+ g_param_spec_uint ("stop-stream-timeout", "Stop stream timeout",
+ "Stop stream timeout: seconds", 0, G_MAXUINT, DEFAULT_STOP_STREAM_TIMEOUT_SEC, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+ g_object_class_install_property (gobject_class, PROP_SERVICE_CONNECTION_TIMEOUT,
+ g_param_spec_uint ("connection-timeout", "Service call connection timeout",
+ "Service call connection timeout: seconds", 0, G_MAXUINT, DEFAULT_SERVICE_CONNECTION_TIMEOUT_SEC, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+ g_object_class_install_property (gobject_class, PROP_SERVICE_COMPLETION_TIMEOUT,
+ g_param_spec_uint ("completion-timeout", "Service call completion timeout",
+ "Service call completion timeout: seconds. Should be more than connection timeout. If it isnt, SDK will override with defaults", 0, G_MAXUINT, DEFAULT_SERVICE_COMPLETION_TIMEOUT_SEC, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
g_object_class_install_property (gobject_class, PROP_CREDENTIAL_FILE_PATH,
g_param_spec_string ("credential-path", "Credential File Path",
"Credential File Path", DEFAULT_CREDENTIAL_FILE_PATH, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
@@ -571,6 +660,21 @@ gst_kvs_sink_class_init(GstKvsSinkClass *klass) {
"Set to true only if your src/mux elements produce GST_CLOCK_TIME_NONE for segment start times. It is non-standard behavior to set this to true, only use if there are known issues with your src/mux segment start/stop times.", DEFAULT_DISABLE_BUFFER_CLIPPING,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+ g_object_class_install_property (gobject_class, PROP_USE_ORIGINAL_PTS,
+ g_param_spec_boolean ("use-original-pts", "Use Original PTS",
+ "Set to true only if you want to use the original presentation time stamp on the buffer and that timestamp is expected to be a valid epoch value in nanoseconds. Most encoders will not have a valid PTS", DEFAULT_USE_ORIGINAL_PTS,
+ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+ g_object_class_install_property (gobject_class, PROP_GET_METRICS,
+ g_param_spec_boolean ("get-kvs-metrics", "Get client and stream level metrics on every key frame",
+ "Set to true if you want to read on the producer streamMetrics and clientMetrics object every key frame. Disabled by default", DEFAULT_ENABLE_METRICS,
+ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+ g_object_class_install_property (gobject_class, PROP_ALLOW_CREATE_STREAM,
+ g_param_spec_boolean ("allow-create-stream", "Allow creating stream if stream does not exist",
+ "Set to true if allowing create stream call, false otherwise", DEFAULT_ALLOW_CREATE_STREAM,
+ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
gst_element_class_set_static_metadata(gstelement_class,
"KVS Sink",
"Sink/Video/Network",
@@ -583,6 +687,14 @@ gst_kvs_sink_class_init(GstKvsSinkClass *klass) {
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_kvs_sink_change_state);
gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_kvs_sink_request_new_pad);
gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_kvs_sink_release_pad);
+
+ KvsSinkSignals::err_signal_id = g_signal_new("stream-error", G_TYPE_FROM_CLASS(gobject_class), (GSignalFlags)(G_SIGNAL_RUN_LAST), G_STRUCT_OFFSET (GstKvsSinkClass, sink_stream_error), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT64);
+ KvsSinkSignals::ack_signal_id = g_signal_new("fragment-ack", G_TYPE_FROM_CLASS(gobject_class),
+ (GSignalFlags)(G_SIGNAL_ACTION), G_STRUCT_OFFSET (GstKvsSinkClass, sink_fragment_ack),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_POINTER);
+ KvsSinkSignals::metric_signal_id = g_signal_new("stream-client-metric", G_TYPE_FROM_CLASS(gobject_class),
+ (GSignalFlags)(G_SIGNAL_ACTION), G_STRUCT_OFFSET (GstKvsSinkClass, sink_stream_metric),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_POINTER);
}
static void
@@ -599,6 +711,7 @@ gst_kvs_sink_init(GstKvsSink *kvssink) {
// Stream definition
kvssink->stream_name = g_strdup (DEFAULT_STREAM_NAME);
+ kvssink->user_agent = g_strdup(KVS_CLIENT_USER_AGENT_NAME "/" KVSSINK_USER_AGENT_POSTFIX_VERSION);
kvssink->retention_period_hours = DEFAULT_RETENTION_PERIOD_HOURS;
kvssink->kms_key_id = g_strdup (DEFAULT_KMS_KEY_ID);
kvssink->streaming_type = DEFAULT_STREAMING_TYPE;
@@ -611,6 +724,7 @@ gst_kvs_sink_init(GstKvsSink *kvssink) {
kvssink->fragment_acks = DEFAULT_FRAGMENT_ACKS;
kvssink->restart_on_error = DEFAULT_RESTART_ON_ERROR;
kvssink->recalculate_metrics = DEFAULT_RECALCULATE_METRICS;
+ kvssink->allow_create_stream = DEFAULT_ALLOW_CREATE_STREAM;
kvssink->framerate = DEFAULT_STREAM_FRAMERATE;
kvssink->avg_bandwidth_bps = DEFAULT_AVG_BANDWIDTH_BPS;
kvssink->buffer_duration_seconds = DEFAULT_BUFFER_DURATION_SECONDS;
@@ -621,10 +735,14 @@ gst_kvs_sink_init(GstKvsSink *kvssink) {
kvssink->track_name = g_strdup (DEFAULT_TRACKNAME);
kvssink->access_key = g_strdup (DEFAULT_ACCESS_KEY);
kvssink->secret_key = g_strdup (DEFAULT_SECRET_KEY);
+ kvssink->session_token = g_strdup(DEFAULT_SESSION_TOKEN);
kvssink->aws_region = g_strdup (DEFAULT_REGION);
kvssink->rotation_period = DEFAULT_ROTATION_PERIOD_SECONDS;
kvssink->log_config_path = g_strdup (DEFAULT_LOG_FILE_PATH);
kvssink->storage_size = DEFAULT_STORAGE_SIZE_MB;
+ kvssink->stop_stream_timeout = DEFAULT_STOP_STREAM_TIMEOUT_SEC;
+ kvssink->service_connection_timeout = DEFAULT_SERVICE_CONNECTION_TIMEOUT_SEC;
+ kvssink->service_completion_timeout = DEFAULT_SERVICE_COMPLETION_TIMEOUT_SEC;
kvssink->credential_file_path = g_strdup (DEFAULT_CREDENTIAL_FILE_PATH);
kvssink->file_start_time = (uint64_t) chrono::duration_cast(
systemCurrentTime().time_since_epoch()).count();
@@ -632,12 +750,15 @@ gst_kvs_sink_init(GstKvsSink *kvssink) {
kvssink->audio_codec_id = g_strdup (DEFAULT_AUDIO_CODEC_ID_AAC);
kvssink->data = make_shared();
+ kvssink->data->err_signal_id = KvsSinkSignals::err_signal_id;
+ kvssink->data->ack_signal_id = KvsSinkSignals::ack_signal_id;
+ kvssink->data->metric_signal_id = KvsSinkSignals::metric_signal_id;
// Mark plugin as sink
GST_OBJECT_FLAG_SET (kvssink, GST_ELEMENT_FLAG_SINK);
LOGGER_TAG("com.amazonaws.kinesis.video.gstkvs");
- LOG_CONFIGURE_STDOUT("DEBUG")
+ LOG_CONFIGURE_STDOUT("DEBUG");
}
static void
@@ -647,12 +768,19 @@ gst_kvs_sink_finalize(GObject *object) {
gst_object_unref(kvssink->collect);
g_free(kvssink->stream_name);
+ g_free(kvssink->user_agent);
g_free(kvssink->content_type);
g_free(kvssink->codec_id);
g_free(kvssink->track_name);
g_free(kvssink->secret_key);
g_free(kvssink->access_key);
+ g_free(kvssink->session_token);
+ g_free(kvssink->aws_region);
g_free(kvssink->audio_codec_id);
+ g_free(kvssink->kms_key_id);
+ g_free(kvssink->log_config_path);
+ g_free(kvssink->credential_file_path);
+
if (kvssink->iot_certificate) {
gst_structure_free (kvssink->iot_certificate);
}
@@ -677,6 +805,10 @@ gst_kvs_sink_set_property(GObject *object, guint prop_id,
g_free(kvssink->stream_name);
kvssink->stream_name = g_strdup (g_value_get_string (value));
break;
+ case PROP_USER_AGENT_NAME:
+ g_free(kvssink->user_agent);
+ kvssink->user_agent = g_strdup (g_value_get_string (value));
+ break;
case PROP_RETENTION_PERIOD:
kvssink->retention_period_hours = g_value_get_uint (value);
break;
@@ -742,6 +874,10 @@ gst_kvs_sink_set_property(GObject *object, guint prop_id,
g_free(kvssink->secret_key);
kvssink->secret_key = g_strdup (g_value_get_string (value));
break;
+ case PROP_SESSION_TOKEN:
+ g_free(kvssink->session_token);
+ kvssink->session_token = g_strdup (g_value_get_string (value));
+ break;
case PROP_AWS_REGION:
g_free(kvssink->aws_region);
kvssink->aws_region = g_strdup (g_value_get_string (value));
@@ -750,6 +886,7 @@ gst_kvs_sink_set_property(GObject *object, guint prop_id,
kvssink->rotation_period = g_value_get_uint (value);
break;
case PROP_LOG_CONFIG_PATH:
+ g_free(kvssink->log_config_path);
kvssink->log_config_path = g_strdup (g_value_get_string (value));
break;
case PROP_FRAMERATE:
@@ -758,7 +895,20 @@ gst_kvs_sink_set_property(GObject *object, guint prop_id,
case PROP_STORAGE_SIZE:
kvssink->storage_size = g_value_get_uint (value);
break;
+ case PROP_STOP_STREAM_TIMEOUT:
+ kvssink->stop_stream_timeout = g_value_get_uint (value);
+ break;
+
+ case PROP_SERVICE_CONNECTION_TIMEOUT:
+ kvssink->service_connection_timeout = g_value_get_uint (value);
+ break;
+
+ case PROP_SERVICE_COMPLETION_TIMEOUT:
+ kvssink->service_completion_timeout = g_value_get_uint (value);
+ break;
+
case PROP_CREDENTIAL_FILE_PATH:
+ g_free(kvssink->credential_file_path);
kvssink->credential_file_path = g_strdup (g_value_get_string (value));
break;
case PROP_IOT_CERTIFICATE: {
@@ -785,6 +935,15 @@ gst_kvs_sink_set_property(GObject *object, guint prop_id,
case PROP_DISABLE_BUFFER_CLIPPING:
kvssink->disable_buffer_clipping = g_value_get_boolean(value);
break;
+ case PROP_USE_ORIGINAL_PTS:
+ kvssink->data->use_original_pts = g_value_get_boolean(value);
+ break;
+ case PROP_GET_METRICS:
+ kvssink->data->get_metrics = g_value_get_boolean(value);
+ break;
+ case PROP_ALLOW_CREATE_STREAM:
+ kvssink->allow_create_stream = g_value_get_boolean(value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -802,6 +961,9 @@ gst_kvs_sink_get_property(GObject *object, guint prop_id, GValue *value,
case PROP_STREAM_NAME:
g_value_set_string (value, kvssink->stream_name);
break;
+ case PROP_USER_AGENT_NAME:
+ g_value_set_string (value, kvssink->user_agent);
+ break;
case PROP_RETENTION_PERIOD:
g_value_set_uint (value, kvssink->retention_period_hours);
break;
@@ -862,6 +1024,9 @@ gst_kvs_sink_get_property(GObject *object, guint prop_id, GValue *value,
case PROP_SECRET_KEY:
g_value_set_string (value, kvssink->secret_key);
break;
+ case PROP_SESSION_TOKEN:
+ g_value_set_string (value, kvssink->session_token);
+ break;
case PROP_AWS_REGION:
g_value_set_string (value, kvssink->aws_region);
break;
@@ -877,6 +1042,18 @@ gst_kvs_sink_get_property(GObject *object, guint prop_id, GValue *value,
case PROP_STORAGE_SIZE:
g_value_set_uint (value, kvssink->storage_size);
break;
+ case PROP_STOP_STREAM_TIMEOUT:
+ g_value_set_uint (value, kvssink->stop_stream_timeout);
+ break;
+
+ case PROP_SERVICE_CONNECTION_TIMEOUT:
+ g_value_set_uint (value, kvssink->service_connection_timeout);
+ break;
+
+ case PROP_SERVICE_COMPLETION_TIMEOUT:
+ g_value_set_uint (value, kvssink->service_completion_timeout);
+ break;
+
case PROP_CREDENTIAL_FILE_PATH:
g_value_set_string (value, kvssink->credential_file_path);
break;
@@ -892,6 +1069,15 @@ gst_kvs_sink_get_property(GObject *object, guint prop_id, GValue *value,
case PROP_DISABLE_BUFFER_CLIPPING:
g_value_set_boolean (value, kvssink->disable_buffer_clipping);
break;
+ case PROP_USE_ORIGINAL_PTS:
+ g_value_set_boolean (value, kvssink->data->use_original_pts);
+ break;
+ case PROP_GET_METRICS:
+ g_value_set_boolean (value, kvssink->data->get_metrics);
+ break;
+ case PROP_ALLOW_CREATE_STREAM:
+ g_value_set_boolean (value, kvssink->allow_create_stream);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -977,12 +1163,12 @@ gst_kvs_sink_handle_sink_event (GstCollectPads *pads,
goto CleanUp;
}
- LOG_INFO("received kvs-add-metadata event");
+ LOG_INFO("received kvs-add-metadata event for " << kvssink->stream_name);
if (NULL == gst_structure_get_string(structure, KVS_ADD_METADATA_NAME) ||
NULL == gst_structure_get_string(structure, KVS_ADD_METADATA_VALUE) ||
!gst_structure_get_boolean(structure, KVS_ADD_METADATA_PERSISTENT, &persistent)) {
- LOG_WARN("Event structure contains invalid field: " << std::string(gst_structure_to_string (structure)));
+ LOG_WARN("Event structure contains invalid field: " << std::string(gst_structure_to_string (structure)) << " for " << kvssink->stream_name);
goto CleanUp;
}
@@ -992,12 +1178,16 @@ gst_kvs_sink_handle_sink_event (GstCollectPads *pads,
bool result = data->kinesis_video_stream->putFragmentMetadata(metadata_name, metadata_value, is_persist);
if (!result) {
- LOG_WARN("Failed to putFragmentMetadata. name: " << metadata_name << ", value: " << metadata_value << ", persistent: " << is_persist);
+ LOG_WARN("Failed to putFragmentMetadata. name: " << metadata_name << ", value: " << metadata_value << ", persistent: " << is_persist << " for " << kvssink->stream_name);
}
gst_event_unref (event);
event = NULL;
break;
}
+ case GST_EVENT_EOS: {
+ LOG_INFO("EOS Event received in sink for " << kvssink->stream_name);
+ break;
+ }
default:
break;
}
@@ -1023,12 +1213,25 @@ void create_kinesis_video_frame(Frame *frame, const nanoseconds &pts, const nano
frame->trackId = static_cast(track_id);
}
-bool
-put_frame(shared_ptr kinesis_video_stream, void *frame_data, size_t len, const nanoseconds &pts,
+bool put_frame(shared_ptr data, void *frame_data, size_t len, const nanoseconds &pts,
const nanoseconds &dts, FRAME_FLAGS flags, uint64_t track_id, uint32_t index) {
+
Frame frame;
create_kinesis_video_frame(&frame, pts, dts, flags, frame_data, len, track_id, index);
- return kinesis_video_stream->putFrame(frame);
+ bool ret = data->kinesis_video_stream->putFrame(frame);
+ if (data->get_metrics && ret) {
+ if (CHECK_FRAME_FLAG_KEY_FRAME(flags) || data->on_first_frame) {
+ KvsSinkMetric *kvs_sink_metric = new KvsSinkMetric();
+ kvs_sink_metric->stream_metrics = data->kinesis_video_stream->getMetrics();
+ kvs_sink_metric->client_metrics = data->kinesis_video_producer->getMetrics();
+ kvs_sink_metric->frame_pts = frame.presentationTs;
+ kvs_sink_metric->on_first_frame = data->on_first_frame;
+ data->on_first_frame = false;
+ g_signal_emit(G_OBJECT(data->kvs_sink), data->metric_signal_id, 0, kvs_sink_metric);
+ delete kvs_sink_metric;
+ }
+ }
+ return ret;
}
static GstFlowReturn
@@ -1048,11 +1251,19 @@ gst_kvs_sink_handle_buffer (GstCollectPads * pads,
GstMapInfo info;
info.data = NULL;
-
// eos reached
if (buf == NULL && track_data == NULL) {
- data->kinesis_video_stream->stopSync();
- LOG_INFO("Sending eos");
+ LOG_INFO("Received event for " << kvssink->stream_name);
+ // Need this check in case pipeline is already being set to NULL and
+ // stream is being or/already stopped. Although stopSync() is an idempotent call,
+ // we want to avoid an extra call. It is not possible for this callback to be invoked
+ // after stopSync() since we stop collecting on pads before invoking. But having this
+ // check anyways in case it happens
+ if (!data->streamingStopped.load()) {
+ data->kinesis_video_stream->stopSync();
+ data->streamingStopped.store(true);
+ LOG_INFO("Sending eos for " << kvssink->stream_name);
+ }
// send out eos message to gstreamer bus
message = gst_message_new_eos (GST_OBJECT_CAST (kvssink));
@@ -1068,7 +1279,7 @@ gst_kvs_sink_handle_buffer (GstCollectPads * pads,
if (IS_OFFLINE_STREAMING_MODE(kvssink->streaming_type) || !IS_RETRIABLE_ERROR(stream_status)) {
// fatal cases
GST_ELEMENT_ERROR (kvssink, STREAM, FAILED, (NULL),
- ("Stream error occurred. Status: 0x%08x", stream_status));
+ ("[%s] Stream error occurred. Status: 0x%08x", kvssink->stream_name, stream_status));
ret = GST_FLOW_ERROR;
goto CleanUp;
} else {
@@ -1079,70 +1290,81 @@ gst_kvs_sink_handle_buffer (GstCollectPads * pads,
}
}
- isDroppable = GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_CORRUPTED) ||
- GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DECODE_ONLY) ||
- (GST_BUFFER_FLAGS(buf) == GST_BUFFER_FLAG_DISCONT) ||
- (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT) && GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT)) ||
- // drop if buffer contains header and has invalid timestamp
- (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_HEADER) && (!GST_BUFFER_PTS_IS_VALID(buf) || !GST_BUFFER_DTS_IS_VALID(buf)));
-
- if (isDroppable) {
- LOG_DEBUG("Dropping frame with flag: " << GST_BUFFER_FLAGS(buf));
- goto CleanUp;
- }
+ if (buf != NULL) {
+ isDroppable = GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_CORRUPTED) ||
+ GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DECODE_ONLY) ||
+ (GST_BUFFER_FLAGS(buf) == GST_BUFFER_FLAG_DISCONT) ||
+ (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT) && GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT)) ||
+ // drop if buffer contains header and has invalid timestamp
+ (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_HEADER) && (!GST_BUFFER_PTS_IS_VALID(buf) || !GST_BUFFER_DTS_IS_VALID(buf)));
+ if (isDroppable) {
+ LOG_DEBUG("Dropping frame with flag: " << GST_BUFFER_FLAGS(buf) << " for " << kvssink->stream_name);
+ goto CleanUp;
+ }
- // In offline mode, if user specifies a file_start_time, the stream will be configured to use absolute
- // timestamp. Therefore in here we add the file_start_time to frame pts to create absolute timestamp.
- // If user did not specify file_start_time, file_start_time will be 0 and has no effect.
- if (IS_OFFLINE_STREAMING_MODE(kvssink->streaming_type)) {
- buf->dts = 0; // if offline mode, i.e. streaming a file, the dts from gstreamer is undefined.
- buf->pts += data->pts_base;
- } else if (!GST_BUFFER_DTS_IS_VALID(buf)) {
- buf->dts = data->last_dts + DEFAULT_FRAME_DURATION_MS * HUNDREDS_OF_NANOS_IN_A_MILLISECOND * DEFAULT_TIME_UNIT_IN_NANOS;
- }
+ // In offline mode, if user specifies a file_start_time, the stream will be configured to use absolute
+ // timestamp. Therefore in here we add the file_start_time to frame pts to create absolute timestamp.
+ // If user did not specify file_start_time, file_start_time will be 0 and has no effect.
+ if (IS_OFFLINE_STREAMING_MODE(kvssink->streaming_type)) {
+ if (!data->use_original_pts) {
+ buf->dts = 0; // if offline mode, i.e. streaming a file, the dts from gstreamer is undefined.
+ buf->pts += data->pts_base;
+ } else {
+ buf->pts = buf->dts;
+ }
+ } else if (!GST_BUFFER_DTS_IS_VALID(buf)) {
+ buf->dts = data->last_dts + DEFAULT_FRAME_DURATION_MS * HUNDREDS_OF_NANOS_IN_A_MILLISECOND * DEFAULT_TIME_UNIT_IN_NANOS;
+ }
- data->last_dts = buf->dts;
- track_id = kvs_sink_track_data->track_id;
+ data->last_dts = buf->dts;
+ track_id = kvs_sink_track_data->track_id;
- if (!gst_buffer_map(buf, &info, GST_MAP_READ)){
- goto CleanUp;
- }
+ if (!gst_buffer_map(buf, &info, GST_MAP_READ)){
+ goto CleanUp;
+ }
- delta = GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT);
+ delta = GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT);
- switch (data->media_type) {
- case AUDIO_ONLY:
- case VIDEO_ONLY:
- if (!delta) {
- kinesis_video_flags = FRAME_FLAG_KEY_FRAME;
- }
- break;
- case AUDIO_VIDEO:
- if(!delta && kvs_sink_track_data->track_type == MKV_TRACK_INFO_TYPE_VIDEO) {
- if (data->first_video_frame) {
- data->first_video_frame = false;
- } else {
+ switch (data->media_type) {
+ case AUDIO_ONLY:
+ case VIDEO_ONLY:
+ if (!delta) {
kinesis_video_flags = FRAME_FLAG_KEY_FRAME;
}
+ break;
+ case AUDIO_VIDEO:
+ if (!delta && kvs_sink_track_data->track_type == MKV_TRACK_INFO_TYPE_VIDEO) {
+ if (data->first_video_frame) {
+ data->first_video_frame = false;
+ }
+ kinesis_video_flags = FRAME_FLAG_KEY_FRAME;
+ }
+ break;
+ }
+ if (!IS_OFFLINE_STREAMING_MODE(kvssink->streaming_type)) {
+ if (data->first_pts == GST_CLOCK_TIME_NONE) {
+ data->first_pts = buf->pts;
}
- break;
- }
- if (!IS_OFFLINE_STREAMING_MODE(kvssink->streaming_type)) {
- if (data->first_pts == GST_CLOCK_TIME_NONE) {
- data->first_pts = buf->pts;
- }
- if (data->producer_start_time == GST_CLOCK_TIME_NONE) {
- data->producer_start_time = (uint64_t) chrono::duration_cast(
- systemCurrentTime().time_since_epoch()).count();
+ if (data->producer_start_time == GST_CLOCK_TIME_NONE) {
+ data->producer_start_time = (uint64_t) chrono::duration_cast(
+ systemCurrentTime().time_since_epoch()).count();
+ }
+
+ if (!data->use_original_pts) {
+ buf->pts += data->producer_start_time - data->first_pts;
+ } else {
+ buf->pts = buf->dts;
+ }
}
- buf->pts += data->producer_start_time - data->first_pts;
- }
- put_frame(data->kinesis_video_stream, info.data, info.size,
- std::chrono::nanoseconds(buf->pts),
- std::chrono::nanoseconds(buf->dts), kinesis_video_flags, track_id, data->frame_count);
- data->frame_count++;
+ put_frame(kvssink->data, info.data, info.size,
+ std::chrono::nanoseconds(buf->pts),
+ std::chrono::nanoseconds(buf->dts), kinesis_video_flags, track_id, data->frame_count);
+ data->frame_count++;
+ } else {
+ LOG_WARN("GStreamer buffer is invalid for " << kvssink->stream_name);
+ }
CleanUp:
if (info.data != NULL) {
@@ -1293,7 +1515,7 @@ init_track_data(GstKvsSink *kvssink) {
video_content_type = g_strdup(MKV_H265_CONTENT_TYPE);
} else {
// no-op, should result in a caps negotiation error before getting here.
- LOG_AND_THROW("Error, media type " << media_type << "not accepted by kvssink");
+ LOG_AND_THROW("Error, media type " << media_type << "not accepted by kvssink" << " for " << kvssink->stream_name);
}
gst_caps_unref(caps);
@@ -1329,13 +1551,19 @@ init_track_data(GstKvsSink *kvssink) {
switch (kvssink->data->media_type) {
case AUDIO_VIDEO:
- kvssink->content_type = g_strjoin(",", video_content_type, audio_content_type, NULL);
+ if (video_content_type != NULL && audio_content_type != NULL) {
+ kvssink->content_type = g_strjoin(",", video_content_type, audio_content_type, NULL);
+ }
break;
case AUDIO_ONLY:
- kvssink->content_type = g_strdup(audio_content_type);
+ if (audio_content_type != NULL) {
+ kvssink->content_type = g_strdup(audio_content_type);
+ }
break;
case VIDEO_ONLY:
- kvssink->content_type = g_strdup(video_content_type);
+ if (video_content_type != NULL) {
+ kvssink->content_type = g_strdup(video_content_type);
+ }
break;
}
@@ -1353,8 +1581,14 @@ gst_kvs_sink_change_state(GstElement *element, GstStateChange transition) {
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
- log4cplus::initialize();
- log4cplus::PropertyConfigurator::doConfigure(kvssink->log_config_path);
+ if (kvssink->log_config_path != NULL) {
+ log4cplus::initialize();
+ log4cplus::PropertyConfigurator::doConfigure(kvssink->log_config_path);
+ LOG_INFO("Logger config being used: " << kvssink->log_config_path);
+ } else {
+ LOG_INFO("Logger already configured...skipping");
+ }
+
try {
kinesis_video_producer_init(kvssink);
init_track_data(kvssink);
@@ -1377,7 +1611,22 @@ gst_kvs_sink_change_state(GstElement *element, GstStateChange transition) {
gst_collect_pads_start (kvssink->collect);
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
+ LOG_INFO("Stopping kvssink for " << kvssink->stream_name);
gst_collect_pads_stop (kvssink->collect);
+
+ // Need this check in case an EOS was received in the buffer handler and
+ // stream was already stopped. Although stopSync() is an idempotent call,
+ // we want to avoid an extra call
+ if (!data->streamingStopped.load()) {
+ data->kinesis_video_stream->stopSync();
+ data->streamingStopped.store(true);
+ } else {
+ LOG_INFO("Streaming already stopped for " << kvssink->stream_name);
+ }
+ LOG_INFO("Stopped kvssink for " << kvssink->stream_name);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ LOG_INFO("Pipeline state changed to NULL in kvssink");
break;
default:
break;
@@ -1417,4 +1666,4 @@ GST_PLUGIN_DEFINE (
"Proprietary",
"GStreamer",
"http://gstreamer.net/"
-)
+)
\ No newline at end of file
diff --git a/src/gstreamer/gstkvssink.h b/src/gstreamer/gstkvssink.h
index 63a2fca4..8f0b1a13 100644
--- a/src/gstreamer/gstkvssink.h
+++ b/src/gstreamer/gstkvssink.h
@@ -57,10 +57,16 @@ G_BEGIN_DECLS
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_KVS_SINK))
#define GST_KVS_SINK_CAST(obj) ((GstKvsSink *)obj)
+#ifdef CPP_VERSION_STRING
+#define KVSSINK_USER_AGENT_POSTFIX_VERSION CPP_VERSION_STRING
+#else
+#define KVSSINK_USER_AGENT_POSTFIX_VERSION "UNKNOWN"
+#endif
typedef struct _GstKvsSink GstKvsSink;
typedef struct _GstKvsSinkClass GstKvsSinkClass;
typedef struct _KvsSinkCustomData KvsSinkCustomData;
+typedef struct _KvsSinkMetric KvsSinkMetric;
/* all information needed for one track */
typedef struct _GstKvsSinkTrackData {
@@ -88,6 +94,7 @@ struct _GstKvsSink {
// Stream definition
gchar *stream_name;
+ gchar *user_agent;
guint retention_period_hours;
gchar *kms_key_id;
STREAMING_TYPE streaming_type;
@@ -101,6 +108,7 @@ struct _GstKvsSink {
gboolean fragment_acks;
gboolean restart_on_error;
gboolean recalculate_metrics;
+ gboolean allow_create_stream;
gboolean disable_buffer_clipping;
guint framerate;
guint avg_bandwidth_bps;
@@ -111,10 +119,14 @@ struct _GstKvsSink {
gchar *track_name;
gchar *access_key;
gchar *secret_key;
+ gchar *session_token;
gchar *aws_region;
guint rotation_period;
gchar *log_config_path;
guint storage_size;
+ guint stop_stream_timeout;
+ guint service_connection_timeout;
+ guint service_completion_timeout;
gchar *credential_file_path;
GstStructure *iot_certificate;
GstStructure *stream_tags;
@@ -127,12 +139,16 @@ struct _GstKvsSink {
guint num_audio_streams;
guint num_video_streams;
+
std::unique_ptr credentials_;
std::shared_ptr data;
};
struct _GstKvsSinkClass {
GstElementClass parent_class;
+ void (*sink_fragment_ack) (GstKvsSink *kvssink, gpointer user_data);
+ void (*sink_stream_metric) (GstKvsSink *kvssink, gpointer user_data);
+ void (*sink_stream_error) (GstKvsSink *kvssink, gpointer user_data);
};
GType gst_kvs_sink_get_type (void);
@@ -147,17 +163,26 @@ struct _KvsSinkCustomData {
pts_base(0),
media_type(VIDEO_ONLY),
first_video_frame(true),
+ use_original_pts(false),
+ get_metrics(false),
+ on_first_frame(true),
frame_count(0),
first_pts(GST_CLOCK_TIME_NONE),
- producer_start_time(GST_CLOCK_TIME_NONE) {}
+ producer_start_time(GST_CLOCK_TIME_NONE),
+ streamingStopped(false) {}
std::unique_ptr kinesis_video_producer;
std::shared_ptr kinesis_video_stream;
std::unordered_set track_cpd_received;
- GstKvsSink *kvsSink;
+ GstKvsSink *kvs_sink = nullptr;
MediaType media_type;
bool first_video_frame;
+ bool use_original_pts;
+ bool get_metrics;
uint32_t frame_count;
+ bool on_first_frame;
+ std::atomic streamingStopped;
+ uint64_t frame_pts;
std::atomic_uint stream_status;
@@ -165,6 +190,21 @@ struct _KvsSinkCustomData {
uint64_t pts_base;
uint64_t first_pts;
uint64_t producer_start_time;
+ guint err_signal_id = 0;
+ guint ack_signal_id = 0;
+ guint metric_signal_id = 0;
+ uint64_t start_time; // [nanoSeconds]
+};
+
+struct _KvsSinkMetric {
+ _KvsSinkMetric():
+ frame_pts(0),
+ on_first_frame(true)
+ {}
+ KinesisVideoStreamMetrics stream_metrics = KinesisVideoStreamMetrics();
+ KinesisVideoProducerMetrics client_metrics = KinesisVideoProducerMetrics();
+ uint64_t frame_pts;
+ bool on_first_frame;
};
#endif /* __GST_KVS_SINK_H__ */
diff --git a/tst/CMakeLists.txt b/tst/CMakeLists.txt
index b691c5fe..3282e525 100644
--- a/tst/CMakeLists.txt
+++ b/tst/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.6.3)
project(producerTest)
file(GLOB PRODUCER_TEST_SOURCES *.cpp)
diff --git a/tst/ProducerApiTest.cpp b/tst/ProducerApiTest.cpp
index bbbf68f0..46cb9226 100644
--- a/tst/ProducerApiTest.cpp
+++ b/tst/ProducerApiTest.cpp
@@ -140,10 +140,45 @@ PVOID ProducerTestBase::basicProducerRoutine(KinesisVideoStream* kinesis_video_s
return NULL;
}
+TEST_F(ProducerApiTest, invalid_credentials)
+{
+ std::unique_ptr credentials;
+ std::unique_ptr credential_provider;
+ credentials.reset(new Credentials("",
+ "SecretKey",
+ "SessionToken",
+ std::chrono::seconds(TEST_STREAMING_TOKEN_DURATION_IN_SECONDS)));
+
+ credential_provider.reset(new TestCredentialProvider(*credentials.get(), TEST_STREAMING_TOKEN_DURATION_IN_SECONDS));
+ EXPECT_THROW(CreateProducer(std::move(credential_provider)), std::runtime_error);
+
+ credentials.reset(new Credentials("AccessKey",
+ "",
+ "SessionToken",
+ std::chrono::seconds(TEST_STREAMING_TOKEN_DURATION_IN_SECONDS)));
+ credential_provider.reset(new TestCredentialProvider(*credentials.get(), TEST_STREAMING_TOKEN_DURATION_IN_SECONDS));
+ EXPECT_THROW(CreateProducer(std::move(credential_provider)), std::runtime_error);
+
+ credentials.reset(new Credentials("",
+ "",
+ "SessionToken",
+ std::chrono::seconds(TEST_STREAMING_TOKEN_DURATION_IN_SECONDS)));
+ credential_provider.reset(new TestCredentialProvider(*credentials.get(), TEST_STREAMING_TOKEN_DURATION_IN_SECONDS));
+ EXPECT_THROW(CreateProducer(std::move(credential_provider)), std::runtime_error);
+
+ credentials.reset(new Credentials("AccessKey",
+ "SecretKey",
+ "",
+ std::chrono::seconds(TEST_STREAMING_TOKEN_DURATION_IN_SECONDS)));
+ credential_provider.reset(new TestCredentialProvider(*credentials.get(), TEST_STREAMING_TOKEN_DURATION_IN_SECONDS));
+ CreateProducer(std::move(credential_provider)); // expect no exception thrown since empty session token is allowed
+}
+
TEST_F(ProducerApiTest, create_free_stream)
{
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -181,6 +216,7 @@ TEST_F(ProducerApiTest, DISABLED_create_produce_offline_stream)
{
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -224,6 +260,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_stream)
{
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -280,7 +317,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_stream)
EXPECT_TRUE(kinesis_video_stream->stopSync()) << "Timed out awaiting for the stream stop notification";
EXPECT_TRUE(gProducerApiTest->stop_called_) << "Status of stopped state " << gProducerApiTest->stop_called_;
- kinesis_video_producer_->freeStream(move(streams_[0]));
+ kinesis_video_producer_->freeStream(std::move(streams_[0]));
streams_[0] = nullptr;
}
}
@@ -289,6 +326,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_stream_endpoint_cached)
{
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -345,7 +383,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_stream_endpoint_cached)
EXPECT_TRUE(kinesis_video_stream->stopSync()) << "Timed out awaiting for the stream stop notification";
EXPECT_TRUE(gProducerApiTest->stop_called_) << "Status of stopped state " << gProducerApiTest->stop_called_;
- kinesis_video_producer_->freeStream(move(streams_[0]));
+ kinesis_video_producer_->freeStream(std::move(streams_[0]));
streams_[0] = nullptr;
}
}
@@ -354,6 +392,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_stream_all_cached)
{
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -410,7 +449,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_stream_all_cached)
EXPECT_TRUE(kinesis_video_stream->stopSync()) << "Timed out awaiting for the stream stop notification";
EXPECT_TRUE(gProducerApiTest->stop_called_) << "Status of stopped state " << gProducerApiTest->stop_called_;
- kinesis_video_producer_->freeStream(move(streams_[0]));
+ kinesis_video_producer_->freeStream(std::move(streams_[0]));
streams_[0] = nullptr;
}
}
@@ -419,6 +458,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_reset_stream_endpoint_cached)
{
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -478,7 +518,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_reset_stream_endpoint_cached)
kinesis_video_stream->resetStream();
}
- kinesis_video_producer_->freeStream(move(streams_[0]));
+ kinesis_video_producer_->freeStream(std::move(streams_[0]));
streams_[0] = nullptr;
}
@@ -486,6 +526,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_reset_stream_all_cached)
{
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -545,7 +586,7 @@ TEST_F(ProducerApiTest, create_produce_start_stop_reset_stream_all_cached)
kinesis_video_stream->resetStream();
}
- kinesis_video_producer_->freeStream(move(streams_[0]));
+ kinesis_video_producer_->freeStream(std::move(streams_[0]));
streams_[0] = nullptr;
}
@@ -553,6 +594,7 @@ TEST_F(ProducerApiTest, create_produce_stream)
{
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -631,6 +673,7 @@ TEST_F(ProducerApiTest, create_caching_endpoing_produce_stream)
{
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -673,8 +716,7 @@ TEST_F(ProducerApiTest, create_caching_endpoing_produce_stream)
TEST_F(ProducerApiTest, exceed_max_track_count)
{
CreateProducer();
- char stream_name[MAX_STREAM_NAME_LEN];
- sprintf(stream_name, "ScaryTestStream");
+ std::string stream_name = "ScaryTestStream";
const string testTrackName = "testTrackName", testCodecId = "testCodecId";
// add 4 tracks
@@ -693,14 +735,13 @@ TEST_F(ProducerApiTest, exceed_max_track_count)
stream_definition->addTrack(1, testTrackName, testCodecId, MKV_TRACK_INFO_TYPE_VIDEO);
stream_definition->addTrack(2, testTrackName, testCodecId, MKV_TRACK_INFO_TYPE_AUDIO);
stream_definition->addTrack(3, testTrackName, testCodecId, MKV_TRACK_INFO_TYPE_VIDEO);
- EXPECT_ANY_THROW(kinesis_video_producer_->createStream(move(stream_definition)));
+ EXPECT_ANY_THROW(kinesis_video_producer_->createStream(std::move(stream_definition)));
}
TEST_F(ProducerApiTest, segment_uuid_variations)
{
CreateProducer();
- char stream_name[MAX_STREAM_NAME_LEN];
- sprintf(stream_name, "ScaryTestStream");
+ std::string stream_name = "ScaryTestStream";
const string testTrackName = "testTrackName", testCodecId = "testCodecId";
// Empty
@@ -719,6 +760,7 @@ TEST_F(ProducerApiTest, segment_uuid_variations)
true,
true,
true,
+ true,
NAL_ADAPTATION_ANNEXB_NALS | NAL_ADAPTATION_ANNEXB_CPD_NALS,
25,
4 * 1024 * 1024,
@@ -733,7 +775,7 @@ TEST_F(ProducerApiTest, segment_uuid_variations)
vector(),
DEFAULT_TRACK_ID));
- EXPECT_NE(nullptr, kinesis_video_producer_->createStreamSync(move(stream_definition)));
+ EXPECT_NE(nullptr, kinesis_video_producer_->createStreamSync(std::move(stream_definition)));
kinesis_video_producer_->freeStreams();
// Valid
@@ -754,6 +796,7 @@ TEST_F(ProducerApiTest, segment_uuid_variations)
true,
true,
true,
+ true,
NAL_ADAPTATION_ANNEXB_NALS | NAL_ADAPTATION_ANNEXB_CPD_NALS,
25,
4 * 1024 * 1024,
@@ -768,7 +811,7 @@ TEST_F(ProducerApiTest, segment_uuid_variations)
vector(),
DEFAULT_TRACK_ID));
- EXPECT_NE(nullptr, kinesis_video_producer_->createStreamSync(move(stream_definition)));
+ EXPECT_NE(nullptr, kinesis_video_producer_->createStreamSync(std::move(stream_definition)));
kinesis_video_producer_->freeStreams();
// invalid - larger
@@ -789,6 +832,7 @@ TEST_F(ProducerApiTest, segment_uuid_variations)
true,
true,
true,
+ true,
NAL_ADAPTATION_ANNEXB_NALS | NAL_ADAPTATION_ANNEXB_CPD_NALS,
25,
4 * 1024 * 1024,
@@ -803,7 +847,7 @@ TEST_F(ProducerApiTest, segment_uuid_variations)
vector(),
DEFAULT_TRACK_ID));
- EXPECT_NE(nullptr, kinesis_video_producer_->createStreamSync(move(stream_definition)));
+ EXPECT_NE(nullptr, kinesis_video_producer_->createStreamSync(std::move(stream_definition)));
kinesis_video_producer_->freeStreams();
// shorter length
@@ -823,6 +867,7 @@ TEST_F(ProducerApiTest, segment_uuid_variations)
true,
true,
true,
+ true,
NAL_ADAPTATION_ANNEXB_NALS | NAL_ADAPTATION_ANNEXB_CPD_NALS,
25,
4 * 1024 * 1024,
diff --git a/tst/ProducerFunctionalityTest.cpp b/tst/ProducerFunctionalityTest.cpp
index 291fe889..e62b8827 100644
--- a/tst/ProducerFunctionalityTest.cpp
+++ b/tst/ProducerFunctionalityTest.cpp
@@ -11,6 +11,7 @@ class ProducerFunctionalityTest : public ProducerTestBase {
TEST_F(ProducerFunctionalityTest, start_stopsync_terminate) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -27,6 +28,7 @@ TEST_F(ProducerFunctionalityTest, start_stopsync_terminate) {
TEST_F(ProducerFunctionalityTest, offline_upload_limited_buffer_duration) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -83,6 +85,7 @@ TEST_F(ProducerFunctionalityTest, offline_upload_limited_buffer_duration) {
TEST_F(ProducerFunctionalityTest, offline_upload_limited_storage) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -142,6 +145,7 @@ TEST_F(ProducerFunctionalityTest, offline_upload_limited_storage) {
TEST_F(ProducerFunctionalityTest, intermittent_file_upload) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -212,6 +216,7 @@ TEST_F(ProducerFunctionalityTest, intermittent_file_upload) {
TEST_F(ProducerFunctionalityTest, high_fragment_rate_file_upload) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -270,6 +275,7 @@ TEST_F(ProducerFunctionalityTest, high_fragment_rate_file_upload) {
TEST_F(ProducerFunctionalityTest, offline_mode_token_rotation_block_on_space) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -338,6 +344,7 @@ TEST_F(ProducerFunctionalityTest, offline_mode_token_rotation_block_on_space) {
TEST_F(ProducerFunctionalityTest, realtime_intermittent_no_latency_pressure_eofr) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -425,6 +432,7 @@ TEST_F(ProducerFunctionalityTest, realtime_intermittent_no_latency_pressure_eofr
TEST_F(ProducerFunctionalityTest, DISABLED_realtime_intermittent_no_latency_pressure_auto) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -509,6 +517,7 @@ TEST_F(ProducerFunctionalityTest, DISABLED_realtime_intermittent_no_latency_pres
TEST_F(ProducerFunctionalityTest, realtime_intermittent_latency_pressure) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
@@ -518,7 +527,7 @@ TEST_F(ProducerFunctionalityTest, realtime_intermittent_latency_pressure) {
buffering_ack_in_sequence_ = true;
key_frame_interval_ = 60;
- total_frame_count_ = 6 * key_frame_interval_;
+ total_frame_count_ = 7 * key_frame_interval_;
frame_duration_ = 16 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND;
UINT64 startTime = 0;
@@ -596,6 +605,7 @@ TEST_F(ProducerFunctionalityTest, realtime_intermittent_latency_pressure) {
TEST_F(ProducerFunctionalityTest, realtime_auto_intermittent_latency_pressure) {
// Check if it's run with the env vars set if not bail out
if (!access_key_set_) {
+ LOG_WARN("Creds not set");
return;
}
diff --git a/tst/ProducerTestFixture.h b/tst/ProducerTestFixture.h
index 714c2a45..d47a4903 100644
--- a/tst/ProducerTestFixture.h
+++ b/tst/ProducerTestFixture.h
@@ -208,7 +208,7 @@ class ProducerTestBase : public ::testing::Test {
LOG_DEBUG("Freeing stream " << streams_[i]->getStreamName());
// Freeing the stream
- kinesis_video_producer_->freeStream(move(streams_[i]));
+ kinesis_video_producer_->freeStream(std::move(streams_[i]));
streams_[i] = nullptr;
}
}
@@ -308,6 +308,31 @@ class ProducerTestBase : public ::testing::Test {
credential_provider_.reset(new TestCredentialProvider(*credentials_.get(), token_rotation_seconds_));
}
+ void CreateProducer(std::unique_ptr credential_provider, bool cachingEndpointProvider = false, AUTOMATIC_STREAMING_FLAGS automaticStreamingFlags = AUTOMATIC_STREAMING_INTERMITTENT_PRODUCER) {
+ device_provider_.reset(new TestDeviceInfoProvider(device_storage_size_, automaticStreamingFlags));
+ client_callback_provider_.reset(new TestClientCallbackProvider(this));
+ stream_callback_provider_.reset(new TestStreamCallbackProvider(this));
+
+ try {
+ std::unique_ptr defaultCallbackProvider;
+ defaultCallbackProvider.reset(new DefaultCallbackProvider(
+ std::move(client_callback_provider_),
+ std::move(stream_callback_provider_),
+ std::move(credential_provider),
+ defaultRegion_,
+ EMPTY_STRING,
+ EMPTY_STRING,
+ EMPTY_STRING,
+ caCertPath_,
+ cachingEndpointProvider ? API_CALL_CACHE_TYPE_ENDPOINT_ONLY : API_CALL_CACHE_TYPE_NONE,
+ DEFAULT_ENDPOINT_CACHE_UPDATE_PERIOD));
+
+ kinesis_video_producer_ = KinesisVideoProducer::createSync(std::move(device_provider_),
+ std::move(defaultCallbackProvider));
+ } catch (std::runtime_error) {
+ throw std::runtime_error("Failed");
+ }
+ }
void CreateProducer(bool cachingEndpointProvider = false, AUTOMATIC_STREAMING_FLAGS automaticStreamingFlags = AUTOMATIC_STREAMING_INTERMITTENT_PRODUCER) {
CreateProducer(cachingEndpointProvider ? API_CALL_CACHE_TYPE_ENDPOINT_ONLY : API_CALL_CACHE_TYPE_NONE,automaticStreamingFlags);
@@ -316,6 +341,7 @@ class ProducerTestBase : public ::testing::Test {
void CreateProducer(API_CALL_CACHE_TYPE api_call_caching, AUTOMATIC_STREAMING_FLAGS automaticStreamingFlags) {
// Create the producer client
CreateCredentialProvider();
+
device_provider_.reset(new TestDeviceInfoProvider(device_storage_size_, automaticStreamingFlags));
client_callback_provider_.reset(new TestClientCallbackProvider(this));
stream_callback_provider_.reset(new TestStreamCallbackProvider(this));
@@ -323,9 +349,9 @@ class ProducerTestBase : public ::testing::Test {
try {
std::unique_ptr defaultCallbackProvider;
defaultCallbackProvider.reset(new DefaultCallbackProvider(
- move(client_callback_provider_),
- move(stream_callback_provider_),
- move(credential_provider_),
+ std::move(client_callback_provider_),
+ std::move(stream_callback_provider_),
+ std::move(credential_provider_),
defaultRegion_,
EMPTY_STRING,
EMPTY_STRING,
@@ -335,8 +361,8 @@ class ProducerTestBase : public ::testing::Test {
DEFAULT_ENDPOINT_CACHE_UPDATE_PERIOD));
// testDefaultCallbackProvider = reinterpret_cast(defaultCallbackProvider.get());
- kinesis_video_producer_ = KinesisVideoProducer::createSync(move(device_provider_),
- move(defaultCallbackProvider));
+ kinesis_video_producer_ = KinesisVideoProducer::createSync(std::move(device_provider_),
+ std::move(defaultCallbackProvider));
} catch (std::runtime_error) {
ASSERT_TRUE(false) << "Failed creating kinesis video producer";
}
@@ -347,39 +373,40 @@ class ProducerTestBase : public ::testing::Test {
uint32_t max_stream_latency_ms = TEST_MAX_STREAM_LATENCY_IN_MILLIS,
int buffer_duration_seconds = 120) {
char stream_name[MAX_STREAM_NAME_LEN];
- sprintf(stream_name, "ScaryTestStream_%d", index);
+ snprintf(stream_name, MAX_STREAM_NAME_LEN, "ScaryTestStream_%d", index);
std::map tags;
char tag_name[MAX_TAG_NAME_LEN];
char tag_val[MAX_TAG_VALUE_LEN];
for (int i = 0; i < 5; i++) {
- sprintf(tag_name, "testTag_%d_%d", index, i);
- sprintf(tag_val, "testTag_%d_%d_Value", index, i);
+ snprintf(tag_name, MAX_TAG_NAME_LEN, "testTag_%d_%d", index, i);
+ snprintf(tag_val, MAX_TAG_VALUE_LEN, "testTag_%d_%d_Value", index, i);
tags.emplace(std::make_pair(tag_name, tag_val));
}
std::unique_ptr stream_definition(new StreamDefinition(stream_name,
- std::chrono::hours(2),
- &tags,
- "",
- streaming_type,
- "video/h264",
- std::chrono::milliseconds(max_stream_latency_ms),
- std::chrono::seconds(2),
- std::chrono::milliseconds(1),
- true,
- true,
- true,
- true,
- true,
- true,
- 0,
- 25,
- 4 * 1024 * 1024,
- std::chrono::seconds(buffer_duration_seconds),
- std::chrono::seconds(buffer_duration_seconds),
- std::chrono::seconds(50)));
- return kinesis_video_producer_->createStreamSync(move(stream_definition));
+ std::chrono::hours(2),
+ &tags,
+ "",
+ streaming_type,
+ "video/h264",
+ std::chrono::milliseconds(max_stream_latency_ms),
+ std::chrono::seconds(2),
+ std::chrono::milliseconds(1),
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ 0,
+ 25,
+ 4 * 1024 * 1024,
+ std::chrono::seconds(buffer_duration_seconds),
+ std::chrono::seconds(buffer_duration_seconds),
+ std::chrono::seconds(50)));
+ return kinesis_video_producer_->createStreamSync(std::move(stream_definition));
};
virtual void SetUp() {
diff --git a/tst/gstreamer/gstkvstest.cpp b/tst/gstreamer/gstkvstest.cpp
index 1304db14..a99df487 100644
--- a/tst/gstreamer/gstkvstest.cpp
+++ b/tst/gstreamer/gstkvstest.cpp
@@ -2,12 +2,8 @@
#include
#include
-#define ACCESS_KEY_ENV_VAR "AWS_ACCESS_KEY_ID"
-#define SECRET_KEY_ENV_VAR "AWS_SECRET_ACCESS_KEY"
-
using namespace std;
-
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,