diff --git a/.github/workflows/README.md b/.github/workflows/README.md index dc30ebc0d6a..acc2296fe65 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -8,12 +8,21 @@ - `build-docs.yaml`: Build Packager docs. Runs only on Linux. - - `docker-image.yaml`: + - `build-docker.yaml`: Build the official Docker image. - `lint.yaml`: Lint Shaka Packager. + - `publish-docs.yaml`: + Publish Packager docs. Runs on the latest release. + + - `publish-docker.yaml`: + Publish the official docker image. Runs on all releases. + + - `publish-npm.yaml`: + Publish binaries to NPM. Runs on all releases. + - `test-linux-distros.yaml`: Test the build on all Linux distros via docker. @@ -22,23 +31,23 @@ - `lint.yaml` - `build.yaml` - `build-docs.yaml` - - `docker-image.yaml` + - `build-docker.yaml` - `test-linux-distros.yaml` - - On release tag (`github-release.yaml`): - - Create a draft release - - Invoke: - - `lint.yaml` - - `build.yaml` - - `test-linux-distros.yaml` - - Publish the release with binaries from `build.yaml` attached - - - On release published: - - `docker-hub-release.yaml`, publishes the official Docker image - - `npm-release.yaml`, publishes the official NPM package - - `update-docs.yaml`: - - Invoke `build-docs.yaml` - - Push the output to the `gh-pages` branch +## Release workflow + - `release-please.yaml` + - Updates changelogs, version numbers based on conventional commits syntax + and semantic versioning + - https://conventionalcommits.org/ + - https://semver.org/ + - Generates/updates a PR on each push + - When the PR is merged, runs additional steps: + - Creates a GitHub release + - Invokes `publish-docs.yaml` to publish the docs + - Invokes `publish-docker.yaml` to publish the docker image + - Invokes `build.yaml` + - Attaches the binaries from `build.yaml` to the GitHub release + - Invokes `publish-npm.yaml` to publish the binaries to NPM ## Common workflows from shaka-project - `sync-labels.yaml` @@ -46,6 +55,9 @@ - `validate-pr-title.yaml` ## Required Repo Secrets + - `RELEASE_PLEASE_TOKEN`: A PAT for `shaka-bot` to run the `release-please` + action. If missing, the release workflow will use the default + `GITHUB_TOKEN` - `DOCKERHUB_CI_USERNAME`: The username of the Docker Hub CI account - `DOCKERHUB_CI_TOKEN`: An access token for Docker Hub - To generate, visit https://hub.docker.com/settings/security diff --git a/.github/workflows/build-attach-artifacts.yaml b/.github/workflows/build-attach-artifacts.yaml index a7c3f2b0e04..9fd889677bc 100644 --- a/.github/workflows/build-attach-artifacts.yaml +++ b/.github/workflows/build-attach-artifacts.yaml @@ -99,10 +99,10 @@ jobs: echo "::endgroup::" SUFFIX="-${{ matrix.os_name }}-${{ matrix.target_arch }}" - EXE_SUFFIX="$SUFFIX${{ matrix.exe_ext}}" + LIB_SUFFIX="$SUFFIX${{ matrix.lib_ext}}" echo "::group::Copy libpackager" - cp libpackager${{ matrix.exe_ext }} $ARTIFACTS/libpackager$EXE_SUFFIX + cp libpackager${{ matrix.lib_ext }} $ARTIFACTS/libpackager$LIB_SUFFIX echo "::endgroup::" - name: Upload release build artifacts diff --git a/.github/workflows/build-docker.yaml b/.github/workflows/build-docker.yaml new file mode 100644 index 00000000000..99f831cde2e --- /dev/null +++ b/.github/workflows/build-docker.yaml @@ -0,0 +1,37 @@ +# Copyright 2022 Google LLC +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# A workflow to build the official docker image. +name: Official Docker image + +# Runs when called from another workflow. +on: + workflow_call: + inputs: + ref: + required: true + type: string + +# By default, run all commands in a bash shell. On Windows, the default would +# otherwise be powershell. +defaults: + run: + shell: bash + +jobs: + official_docker_image: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + submodules: recursive + + - name: Build + shell: bash + run: docker buildx build . diff --git a/.github/workflows/build-docs.yaml b/.github/workflows/build-docs.yaml index 76d3e9862d5..899f8da0f86 100644 --- a/.github/workflows/build-docs.yaml +++ b/.github/workflows/build-docs.yaml @@ -31,7 +31,7 @@ jobs: steps: - name: Install dependencies run: | - sudo apt install -y doxygen + sudo apt install -y doxygen graphviz python3 -m pip install \ sphinx==7.1.2 \ sphinxcontrib.plantuml \ @@ -40,7 +40,7 @@ jobs: breathe - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} @@ -61,8 +61,17 @@ jobs: cp -a build/doxygen/html gh-pages/docs cp docs/index.html gh-pages/index.html + # Now set permissions on the generated docs. + # https://github.com/actions/upload-pages-artifact#file-permissions + chmod -R +rX gh-pages/ + + - name: Upload docs artifacts + uses: actions/upload-pages-artifact@v3 + with: + path: gh-pages + - name: Debug - uses: mxschmitt/action-tmate@v3.6 + uses: mxschmitt/action-tmate@v3.17 with: limit-access-to-actor: true if: failure() && inputs.debug diff --git a/.github/workflows/build-matrix.json b/.github/workflows/build-matrix.json index a5d08e0217d..2ba9f71594b 100644 --- a/.github/workflows/build-matrix.json +++ b/.github/workflows/build-matrix.json @@ -5,7 +5,8 @@ "os": "ubuntu-20.04", "os_name": "linux", "target_arch": "x64", - "exe_ext": ".so", + "lib_ext": ".so", + "exe_ext": "", "generator": "Ninja" }, { @@ -13,7 +14,8 @@ "os": "macos-13", "os_name": "osx", "target_arch": "x64", - "exe_ext": ".dylib", + "lib_ext": ".dylib", + "exe_ext": "", "generator": "Ninja" }, { @@ -21,8 +23,9 @@ "os": "macos-14", "os_name": "osx", "target_arch": "arm64", - "exe_ext": ".dylib", + "lib_ext": ".dylib", + "exe_ext": "", "generator": "Ninja" } ] -} \ No newline at end of file +} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4230c2e50f2..f873df8736a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -26,12 +26,6 @@ on: type: boolean default: false - secrets: - # The GITHUB_TOKEN name is reserved, but not passed through implicitly. - # So we call our secret parameter simply TOKEN. - TOKEN: - required: false - # By default, run all commands in a bash shell. On Windows, the default would # otherwise be powershell. defaults: @@ -50,7 +44,7 @@ jobs: INCLUDE: ${{ steps.configure.outputs.INCLUDE }} OS: ${{ steps.configure.outputs.OS }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} @@ -98,26 +92,64 @@ jobs: run: git config --global core.autocrlf false - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} submodules: recursive + fetch-tags: true - name: Install Linux deps if: runner.os == 'Linux' # NOTE: CMake is already installed in GitHub Actions VMs, but not - # necessarily in a self-hosted runner. + # necessarily in a self-hosted runner. We also need a minimum version + # that may be greater than what is available in Ubuntu, so we set up + # the official CMake PPA first. run: | - sudo apt update && sudo apt install -y \ + kitware_key_url="https://apt.kitware.com/keys/kitware-archive-latest.asc" + kitware_key_path="/usr/share/keyrings/kitware-archive-keyring.gpg" + kitware_sources_path="/etc/apt/sources.list.d/kitware.list" + + curl -sL "$kitware_key_url" | gpg --dearmor - \ + | sudo tee "$kitware_key_path" >/dev/null + + . /etc/lsb-release # Defines $DISTRIB_CODENAME (jammy, focal, etc) + + echo "deb [signed-by=$kitware_key_path] https://apt.kitware.com/ubuntu/ $DISTRIB_CODENAME main" \ + | sudo tee "$kitware_sources_path" >/dev/null + + sudo apt update + sudo apt install -y \ cmake \ ninja-build - name: Install Mac deps if: runner.os == 'macOS' - # NOTE: GitHub Action VMs do not install ninja by default. + # NOTE: GitHub Actions VMs on Mac do not install ninja by default. run: | brew install ninja + - name: Check Mac CPU architecture + if: runner.os == 'macOS' + # In case we get confused about GitHub's mac VM image labels, + # explicitly check that the CPU type matches our expectations. + run: | + if [[ "${{matrix.target_arch}}" == "arm64" ]]; then + CORRECT_LABEL="arm64" + else + CORRECT_LABEL="x86_64" + fi + + LABEL=$(uname -m) + echo "Hardware label: \"$LABEL\"" + + if [[ "$LABEL" != "$CORRECT_LABEL" ]]; then + echo "Wrong hardware label \"$LABEL\", expecting \"$CORRECT_LABEL\"." + echo "Full uname string: $(uname -a)" + echo "Full sysctl CPU info:" + sysctl machdep.cpu + exit 1 + fi + - name: Generate build files run: | mkdir -p build/ @@ -142,9 +174,32 @@ jobs: export PACKAGER_LOW_MEMORY_BUILD=yes fi + # Do fully static release builds on Linux. + BUILD_CONFIG="${{ matrix.build_type }}-${{ matrix.lib_type }}" + if [[ "${{ runner.os }}" == "Linux" && \ + "$BUILD_CONFIG" == "Release-static" ]]; then + # Enable build settings for fully-static. + FULLY_STATIC="ON" + + # Use a musl toolchain, since glibc static executables are not + # portable. + if [[ "${{matrix.target_arch}}" == "arm64" ]]; then + MUSL_ARCH="aarch64" + else + MUSL_ARCH="x86_64" + fi + curl -LO https://musl.cc/"$MUSL_ARCH"-linux-musl-native.tgz + tar xf "$MUSL_ARCH"-linux-musl-native.tgz + export CC=`pwd`/"$MUSL_ARCH"-linux-musl-native/bin/"$MUSL_ARCH"-linux-musl-gcc + export CXX=`pwd`/"$MUSL_ARCH"-linux-musl-native/bin/"$MUSL_ARCH"-linux-musl-g++ + else + FULLY_STATIC="OFF" + fi + cmake \ -DCMAKE_BUILD_TYPE="${{ matrix.build_type }}" \ -DBUILD_SHARED_LIBS="$BUILD_SHARED_LIBS" \ + -DFULLY_STATIC="$FULLY_STATIC" \ -DBUILD_LIVE_TEST="$BUILD_LIVE_TEST" \ -S . \ -B build/ @@ -160,74 +215,81 @@ jobs: run: ctest -C "${{ matrix.build_type }}" -V --test-dir build/ - name: Publish Test Report - uses: mikepenz/action-junit-report@150e2f992e4fad1379da2056d1d1c279f520e058 + uses: mikepenz/action-junit-report@v4 if: ${{ always() }} with: report_paths: 'junit-reports/TEST-*.xml' - # TODO(joeyparrish): Prepare artifacts when build system is complete again -# - name: Prepare artifacts (static release only) -# run: | -# BUILD_CONFIG="${{ matrix.build_type }}-${{ matrix.lib_type }}" -# if [[ "$BUILD_CONFIG" != "Release-static" ]]; then -# echo "Skipping artifacts for $BUILD_CONFIG." -# exit 0 -# fi -# if [[ "${{ runner.os }}" == "Linux" ]]; then -# echo "::group::Check for static executables" -# ( -# cd build/Release -# # Prove that we built static executables on Linux. First, check that -# # the executables exist, and fail if they do not. Then check "ldd", -# # which will fail if the executable is not dynamically linked. If -# # "ldd" succeeds, we fail the workflow. Finally, we call "true" so -# # that the last executed statement will be a success, and the step -# # won't be failed if we get that far. -# ls packager mpd_generator >/dev/null || exit 1 -# ldd packager 2>&1 && exit 1 -# ldd mpd_generator 2>&1 && exit 1 -# true -# ) -# echo "::endgroup::" -# fi -# echo "::group::Prepare artifacts folder" -# mkdir artifacts -# ARTIFACTS="$GITHUB_WORKSPACE/artifacts" -# cd build/Release -# echo "::endgroup::" -# echo "::group::Strip executables" -# strip packager${{ matrix.exe_ext }} -# strip mpd_generator${{ matrix.exe_ext }} -# echo "::endgroup::" -# SUFFIX="-${{ matrix.os_name }}-${{ matrix.target_arch }}" -# EXE_SUFFIX="$SUFFIX${{ matrix.exe_ext}}" -# echo "::group::Copy packager" -# cp packager${{ matrix.exe_ext }} $ARTIFACTS/packager$EXE_SUFFIX -# echo "::endgroup::" -# echo "::group::Copy mpd_generator" -# cp mpd_generator${{ matrix.exe_ext }} $ARTIFACTS/mpd_generator$EXE_SUFFIX -# echo "::endgroup::" -# # The pssh-box bundle is OS and architecture independent. So only do -# # it on this one OS and architecture, and give it a more generic -# # filename. -# if [[ '${{ matrix.os_name }}' == 'linux' && '${{ matrix.target_arch }}' == 'x64' ]]; then -# echo "::group::Tar pssh-box" -# tar -czf $ARTIFACTS/pssh-box.py.tar.gz pyproto pssh-box.py -# echo "::endgroup::" -# fi - - # TODO(joeyparrish): Attach artifacts when build system is complete again -# - name: Attach artifacts to release -# if: matrix.build_type == 'Release' && matrix.lib_type == 'static' -# uses: dwenegar/upload-release-assets@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# release_id: ${{ needs.draft_release.outputs.release_id }} -# assets_path: artifacts + - name: Prepare artifacts (static release only) + run: | + BUILD_CONFIG="${{ matrix.build_type }}-${{ matrix.lib_type }}" + if [[ "$BUILD_CONFIG" != "Release-static" ]]; then + echo "Skipping artifacts for $BUILD_CONFIG." + exit 0 + fi + + # Check static executables + if [[ "${{ runner.os }}" == "Linux" ]]; then + echo "::group::Check static executables" + for exe in build/packager/{packager,mpd_generator}; do + # Capture information about the executables, but also let it be + # logged to stdout. + ldd "$exe" | tee static.log + # The phrase "statically linked" means we got it right. Fail if + # we don't find it. + if ! cat static.log | grep -q statically; then + exit 1 + fi + done + echo "::endgroup::" + fi + + echo "::group::Prepare artifacts folder" + mkdir artifacts + ARTIFACTS="$GITHUB_WORKSPACE/artifacts" + if [[ "${{ runner.os }}" == "Windows" ]]; then + cd build/packager/Release + else + cd build/packager + fi + echo "::endgroup::" + + echo "::group::Strip executables" + strip packager${{ matrix.exe_ext }} + strip mpd_generator${{ matrix.exe_ext }} + echo "::endgroup::" + + SUFFIX="-${{ matrix.os_name }}-${{ matrix.target_arch }}" + EXE_SUFFIX="$SUFFIX${{ matrix.exe_ext}}" + + echo "::group::Copy packager" + cp packager${{ matrix.exe_ext }} $ARTIFACTS/packager$EXE_SUFFIX + echo "::endgroup::" + + echo "::group::Copy mpd_generator" + cp mpd_generator${{ matrix.exe_ext }} $ARTIFACTS/mpd_generator$EXE_SUFFIX + echo "::endgroup::" + + # The pssh-box bundle is OS and architecture independent. So only do + # it on this one OS and architecture, and give it a more generic + # filename. + if [[ '${{ matrix.os_name }}' == 'linux' && '${{ matrix.target_arch }}' == 'x64' ]]; then + echo "::group::Tar pssh-box" + tar -czf $ARTIFACTS/pssh-box.py.tar.gz pssh-box.py pssh-box-protos + echo "::endgroup::" + fi + + - name: Upload static release build artifacts + uses: actions/upload-artifact@v4 + if: matrix.build_type == 'Release' && matrix.lib_type == 'static' + with: + name: artifacts-${{ matrix.os_name }}-${{ matrix.target_arch }} + path: artifacts/* + if-no-files-found: error + retention-days: 5 - name: Debug - uses: mxschmitt/action-tmate@v3.6 + uses: mxschmitt/action-tmate@v3.17 with: limit-access-to-actor: true if: failure() && inputs.debug diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 81b9b1dd2d5..1d43098ee2d 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} # We must use 'fetch-depth: 2', or else the linter won't have another @@ -45,8 +45,8 @@ jobs: # NOTE: Must use base.sha instead of base.ref, since we don't have # access to the branch name that base.ref would give us. - # NOTE: Must also use fetch-depth: 2 in actions/checkout to have access - # to the base ref for comparison. + # NOTE: Must also use fetch-depth: 2 in actions/checkout to have + # access to the base ref for comparison. packager/tools/git/check_formatting.py \ --binary /usr/bin/clang-format \ ${{ github.event.pull_request.base.sha || 'HEAD^' }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 0128c2376f7..0c991ad87f7 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -19,13 +19,14 @@ on: inputs: ref: description: "The ref to build and test." - required: False + required: false + type: string # If another instance of this workflow is started for the same PR, cancel the # old one. If a PR is updated and a new test run is started, the old test run # will be cancelled automatically to conserve resources. concurrency: - group: ${{ github.workflow }}-${{ github.event.inputs.ref || github.ref }} + group: ${{ github.workflow }}-${{ inputs.ref || github.ref }} cancel-in-progress: true jobs: @@ -37,14 +38,14 @@ jobs: name: Lint uses: ./.github/workflows/lint.yaml with: - ref: ${{ github.event.inputs.ref || github.ref }} + ref: ${{ inputs.ref || github.ref }} build_and_test: needs: [lint, settings] name: Build and test uses: ./.github/workflows/build.yaml with: - ref: ${{ github.event.inputs.ref || github.ref }} + ref: ${{ inputs.ref || github.ref }} self_hosted: ${{ needs.settings.outputs.self_hosted != '' }} debug: ${{ needs.settings.outputs.debug != '' }} @@ -53,15 +54,15 @@ jobs: name: Build docs uses: ./.github/workflows/build-docs.yaml with: - ref: ${{ github.event.inputs.ref || github.ref }} + ref: ${{ inputs.ref || github.ref }} debug: ${{ needs.settings.outputs.debug != '' }} official_docker_image: needs: lint name: Official Docker image - uses: ./.github/workflows/docker-image.yaml + uses: ./.github/workflows/build-docker.yaml with: - ref: ${{ github.event.inputs.ref || github.ref }} + ref: ${{ inputs.ref || github.ref }} test_supported_linux_distros: # Doesn't really "need" it, but let's not waste time on a series of docker @@ -70,4 +71,4 @@ jobs: name: Test Linux distros uses: ./.github/workflows/test-linux-distros.yaml with: - ref: ${{ github.event.inputs.ref || github.ref }} + ref: ${{ inputs.ref || github.ref }} diff --git a/.github/workflows/publish-docker.yaml b/.github/workflows/publish-docker.yaml new file mode 100644 index 00000000000..c7b01b9141c --- /dev/null +++ b/.github/workflows/publish-docker.yaml @@ -0,0 +1,79 @@ +# Copyright 2022 Google LLC +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# A workflow to publish the official docker image. +name: Publish to Docker Hub + +# Runs when called from another workflow. +# Can also be run manually for debugging purposes. +on: + workflow_call: + inputs: + tag: + required: true + type: string + latest: + required: true + type: boolean + secrets: + DOCKERHUB_CI_USERNAME: + required: true + DOCKERHUB_CI_TOKEN: + required: true + DOCKERHUB_PACKAGE_NAME: + required: true + # For manual debugging: + workflow_dispatch: + inputs: + tag: + description: The tag to build from and to push to. + required: true + type: string + latest: + description: If true, push to the "latest" tag. + required: true + type: boolean + +jobs: + publish_docker_hub: + name: Publish to Docker Hub + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} + submodules: recursive + fetch-tags: true + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_CI_USERNAME }} + password: ${{ secrets.DOCKERHUB_CI_TOKEN }} + + - name: Push to Docker Hub + uses: docker/build-push-action@v5 + with: + # Important: use actions/checkout source, which has the right tags! + # Without context: ., this action will try to fetch git source + # itself, and it will be unable to determine the correct version + # number. + context: . + push: true + tags: ${{ secrets.DOCKERHUB_PACKAGE_NAME }}:${{ inputs.tag }} + + - name: Push to Docker Hub as "latest" + if: ${{ inputs.latest }} + uses: docker/build-push-action@v5 + with: + # Important: use actions/checkout source, which has the right tags! + # Without context: ., this action will try to fetch git source + # itself, and it will be unable to determine the correct version + # number. + context: . + push: true + tags: ${{ secrets.DOCKERHUB_PACKAGE_NAME }}:latest diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml new file mode 100644 index 00000000000..33c63d4b110 --- /dev/null +++ b/.github/workflows/publish-docs.yaml @@ -0,0 +1,51 @@ +# Copyright 2022 Google LLC +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# A workflow to publish the docs to GitHub Pages. +name: Publish Docs + +# Runs when called from another workflow. +# Can also be run manually for debugging purposes. +on: + workflow_call: + inputs: + ref: + required: true + type: string + # For manual debugging: + workflow_dispatch: + inputs: + ref: + description: "The ref to build docs from." + required: true + type: string + +jobs: + build_docs: + name: Build docs + uses: ./.github/workflows/build-docs.yaml + with: + ref: ${{ inputs.ref }} + + publish_docs: + name: Publish updated docs + needs: build_docs + runs-on: ubuntu-latest + + # Grant GITHUB_TOKEN the permissions required to deploy to Pages + permissions: + pages: write + id-token: write + + # Deploy to the github-pages environment + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/publish-npm.yaml b/.github/workflows/publish-npm.yaml new file mode 100644 index 00000000000..a8172c1797b --- /dev/null +++ b/.github/workflows/publish-npm.yaml @@ -0,0 +1,99 @@ +# Copyright 2022 Google LLC +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# A workflow to publish the official NPM package. +name: Publish to NPM + +# Runs when called from another workflow. +# Can also be run manually for debugging purposes. +on: + workflow_call: + inputs: + tag: + required: true + type: string + latest: + required: true + type: boolean + secrets: + NPM_CI_TOKEN: + required: true + NPM_PACKAGE_NAME: + required: true + # For manual debugging: + workflow_dispatch: + inputs: + tag: + description: The tag to build from. + required: true + type: string + latest: + description: If true, push to the "latest" tag. + required: true + type: boolean + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} + + - uses: actions/setup-node@v4 + with: + node-version: 16 + registry-url: 'https://registry.npmjs.org' + + - name: Compute tags + run: | + # NPM publish always sets a tag. If you don't provide an explicit + # tag, you get the "latest" tag by default, but we want "latest" to + # always point to the highest version number. So we set an explicit + # tag on every "publish" command, then follow up with a command to + # set "latest" only if this release was the highest version yet. + + # The explicit tag is based on the branch. If the git tag is v4.4.1, + # the branch was v4.4.x, and the explicit NPM tag should be + # v4.4-latest. + GIT_TAG_NAME=${{ inputs.tag }} + NPM_TAG=$(echo "$GIT_TAG_NAME" | cut -f 1-2 -d .)-latest + echo "NPM_TAG=$NPM_TAG" >> $GITHUB_ENV + + # Since we also set the package version on-the-fly during publication, + # compute that here. It's the tag without the leading "v". + NPM_VERSION=$(echo "$GIT_TAG_NAME" | sed -e 's/^v//') + echo "NPM_VERSION=$NPM_VERSION" >> $GITHUB_ENV + + # Debug the decisions made here. + echo "This release: $GIT_TAG_NAME" + echo "NPM tag: $NPM_TAG" + echo "NPM version: $NPM_VERSION" + + - name: Set package name and version + run: | + # These substitutions use | because the package name could contain + # both / and @, but | should not appear in package names. + sed npm/package.json -i \ + -e 's|"name": ""|"name": "${{ secrets.NPM_PACKAGE_NAME }}"|' \ + -e 's|"version": ""|"version": "${{ env.NPM_VERSION }}"|' + + - name: Publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_CI_TOKEN }} + run: | + cd npm + + # Publish with an explicit tag. + # Also publish with explicit public access, to allow scoped packages. + npm publish --tag "$NPM_TAG" --access=public + + # Add the "latest" tag if needed. + if [[ "${{ inputs.latest }}" == "true" ]]; then + npm dist-tag add "${{ secrets.NPM_PACKAGE_NAME }}@$NPM_VERSION" latest + fi diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml new file mode 100644 index 00000000000..9c55857de77 --- /dev/null +++ b/.github/workflows/release-please.yaml @@ -0,0 +1,147 @@ +# Copyright 2023 Google LLC +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +name: Release + +on: + push: + branches: + - main + - v[0-9]* + +jobs: + settings: + name: Settings + uses: ./.github/workflows/settings.yaml + + release: + runs-on: ubuntu-latest + needs: settings + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + # Create/update release PR + - uses: google-github-actions/release-please-action@v4 + id: release + with: + # Make sure we create the PR against the correct branch. + target-branch: ${{ github.ref_name }} + # Use a special shaka-bot access token for releases. + token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} + # See also settings in these files: + manifest-file: .release-please-manifest.json + config-file: .release-please-config.json + + # The jobs below are all conditional on a release having been created by + # someone merging the release PR. + + # Several actions either only run on the latest release or run with different + # options on the latest release. Here we compute if this is the highest + # version number (what we are calling "latest" for NPM, Docker, and the + # docs). You can have a more recent release from an older branch, but this + # would not qualify as "latest" here. + compute: + name: Compute latest release flag + runs-on: ubuntu-latest + needs: release + if: needs.release.outputs.release_created + outputs: + latest: ${{ steps.compute.outputs.latest }} + steps: + - uses: actions/checkout@v4 + with: + fetch-tags: true + + - name: Compute latest + id: compute + run: | + GIT_TAG_NAME=${{ needs.release.outputs.tag_name }} + RELEASE_TAGS=$(git tag | grep ^v[0-9]) + LATEST_RELEASE=$(echo "$RELEASE_TAGS" | sort --version-sort | tail -1) + if [[ "$GIT_TAG_NAME" == "$LATEST_RELEASE" ]]; then + LATEST=true + else + LATEST=false + fi + echo latest=$LATEST >> $GITHUB_OUTPUT + + # Debug the decisions made here. + echo "This release: $GIT_TAG_NAME" + echo "Latest release: $LATEST_RELEASE" + echo "This release is latest: $LATEST" + + # Publish docs to GitHub Pages + docs: + name: Update docs + needs: [release, compute] + # Only if this is the latest release + if: needs.release.outputs.release_created && needs.compute.outputs.latest + uses: ./.github/workflows/publish-docs.yaml + with: + ref: ${{ github.ref }} + + # Publish official docker image + docker: + name: Update docker image + needs: [release, compute] + if: needs.release.outputs.release_created + uses: ./.github/workflows/publish-docker.yaml + with: + tag: ${{ needs.release.outputs.tag_name }} + latest: ${{ needs.compute.outputs.latest == 'true' }} + secrets: + DOCKERHUB_CI_USERNAME: ${{ secrets.DOCKERHUB_CI_USERNAME }} + DOCKERHUB_CI_TOKEN: ${{ secrets.DOCKERHUB_CI_TOKEN }} + DOCKERHUB_PACKAGE_NAME: ${{ secrets.DOCKERHUB_PACKAGE_NAME }} + + # Do a complete build + build: + name: Build + needs: [settings, release] + if: needs.release.outputs.release_created + uses: ./.github/workflows/build.yaml + with: + ref: ${{ github.ref }} + self_hosted: ${{ needs.settings.outputs.self_hosted != '' }} + debug: ${{ needs.settings.outputs.debug != '' }} + + # Attach build artifacts to the release + artifacts: + name: Artifacts + runs-on: ubuntu-latest + needs: [release, build] + if: needs.release.outputs.release_created + steps: + - uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Debug + run: find -ls + + - name: Attach packager to release + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} + run: | + gh -R ${{ github.repository }} release upload \ + ${{ needs.release.outputs.tag_name }} artifacts/* \ + --clobber + + # Surprisingly, Shaka Packager binaries can be installed via npm. + # Publish NPM package updates. + npm: + name: Update NPM + needs: [release, compute, artifacts] + if: needs.release.outputs.release_created + uses: ./.github/workflows/publish-npm.yaml + with: + tag: ${{ needs.release.outputs.tag_name }} + latest: ${{ needs.compute.outputs.latest == 'true' }} + secrets: + NPM_CI_TOKEN: ${{ secrets.NPM_CI_TOKEN }} + NPM_PACKAGE_NAME: ${{ secrets.NPM_PACKAGE_NAME }} diff --git a/.github/workflows/sync-labels.yaml b/.github/workflows/sync-labels.yaml index 47e73a9a443..0b679eab380 100644 --- a/.github/workflows/sync-labels.yaml +++ b/.github/workflows/sync-labels.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: shaka-project/shaka-github-tools diff --git a/.github/workflows/test-linux-distros.yaml b/.github/workflows/test-linux-distros.yaml index 8391675e390..992900b174b 100644 --- a/.github/workflows/test-linux-distros.yaml +++ b/.github/workflows/test-linux-distros.yaml @@ -29,7 +29,7 @@ jobs: outputs: MATRIX: ${{ steps.configure.outputs.MATRIX }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} submodules: recursive diff --git a/.github/workflows/update-issues.yaml b/.github/workflows/update-issues.yaml index e69eba5e819..6424ce19aec 100644 --- a/.github/workflows/update-issues.yaml +++ b/.github/workflows/update-issues.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: shaka-project/shaka-github-tools diff --git a/.gitmodules b/.gitmodules index c4ffadcbec8..b986b41076d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,7 +15,7 @@ url = https://github.com/Mbed-TLS/mbedtls [submodule "packager/third_party/zlib/source"] path = packager/third_party/zlib/source - url = https://github.com/joeyparrish/zlib + url = https://github.com/madler/zlib [submodule "packager/third_party/libpng/source"] path = packager/third_party/libpng/source url = https://github.com/glennrp/libpng @@ -34,3 +34,6 @@ [submodule "packager/third_party/c-ares/source"] path = packager/third_party/c-ares/source url = https://github.com/c-ares/c-ares +[submodule "packager/third_party/mimalloc/source"] + path = packager/third_party/mimalloc/source + url = https://github.com/microsoft/mimalloc diff --git a/.release-please-config.json b/.release-please-config.json new file mode 100644 index 00000000000..3b2ecb248db --- /dev/null +++ b/.release-please-config.json @@ -0,0 +1,11 @@ +{ + "packages": { + ".": { + "include-component-in-tag": false, + "include-v-in-tag": true, + "component": "", + "release-type-comment": "This is not really a go project, but go projects in release-please only update CHANGELOG.md and nothing else. This is what we want.", + "release-type": "go" + } + } +} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000000..95a37e3ee86 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "3.2.0" +} diff --git a/AUTHORS b/AUTHORS index 5aac7364718..975b98ef8b8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,6 +18,7 @@ Alen Vrecko Amazon Music <*@amazon.com> Anders Hasselqvist Audible <*@audible.com> +Cyfrowy Polsat SA <*@cyfrowypolsat.pl> Chun-da Chen Daniel Cantarín Dennis E. Mungai (Brainiarc7) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af92af1d19..355e8e8ea83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,140 @@ +# Changelog + + +## [3.2.0](https://github.com/shaka-project/shaka-packager/compare/v3.1.0...v3.2.0) (2024-05-11) + + +### Features + +* support Dolby Vision profile 8.x (HEVC) and 10.x (AV1) in HLS and DASH ([#1396](https://github.com/shaka-project/shaka-packager/issues/1396)) ([a99cfe0](https://github.com/shaka-project/shaka-packager/commit/a99cfe036f09de51b488f87f4cb126a1bcd3a286)) + + +### Bug Fixes + +* adaptation set IDs were referenced by lowest representation ID ([#1394](https://github.com/shaka-project/shaka-packager/issues/1394)) ([94db9c9](https://github.com/shaka-project/shaka-packager/commit/94db9c9db3e73073925205355dd61a6dc9785065)), closes [#1393](https://github.com/shaka-project/shaka-packager/issues/1393) +* escape media URLs in MPD ([#1395](https://github.com/shaka-project/shaka-packager/issues/1395)) ([98b44d0](https://github.com/shaka-project/shaka-packager/commit/98b44d01df6a952466b5a1667818da877502da97)) +* set yuv full range flag to 1 for VP9 with sRGB ([#1398](https://github.com/shaka-project/shaka-packager/issues/1398)) ([f6f60e5](https://github.com/shaka-project/shaka-packager/commit/f6f60e5fff8d5c9b13fbf65f494eba651050ccb9)) + +## [3.1.0](https://github.com/shaka-project/shaka-packager/compare/v3.0.4...v3.1.0) (2024-05-03) + + +### Features + +* add missing DASH roles from ISO/IEC 23009-1 section 5.8.5.5 ([#1390](https://github.com/shaka-project/shaka-packager/issues/1390)) ([fe885b3](https://github.com/shaka-project/shaka-packager/commit/fe885b3ade020b197a04fc63ee41fd90e7e11a14)) +* get start number from muxer and specify initial sequence number ([#879](https://github.com/shaka-project/shaka-packager/issues/879)) ([bb104fe](https://github.com/shaka-project/shaka-packager/commit/bb104fef5d745ac3a0a8c1e6fb4f1b1a9b27d8ae)) +* teletext formatting ([#1384](https://github.com/shaka-project/shaka-packager/issues/1384)) ([4b5e80d](https://github.com/shaka-project/shaka-packager/commit/4b5e80d02c10fd1ddb8f7e0f2f1a8608782d8442)) + +## [3.0.4](https://github.com/shaka-project/shaka-packager/compare/v3.0.3...v3.0.4) (2024-03-27) + + +### Bug Fixes + +* BaseURL missing when MPD base path is empty ([#1380](https://github.com/shaka-project/shaka-packager/issues/1380)) ([90c3c3f](https://github.com/shaka-project/shaka-packager/commit/90c3c3f9b3e8e36706b9769e574aa316e8bbb351)), closes [#1378](https://github.com/shaka-project/shaka-packager/issues/1378) +* Fix NPM binary selection on ARM Macs ([#1376](https://github.com/shaka-project/shaka-packager/issues/1376)) ([733af91](https://github.com/shaka-project/shaka-packager/commit/733af9128dfa7c46dd7a9fe3f8361ab50a829afe)), closes [#1375](https://github.com/shaka-project/shaka-packager/issues/1375) + +## [3.0.3](https://github.com/shaka-project/shaka-packager/compare/v3.0.2...v3.0.3) (2024-03-12) + + +### Bug Fixes + +* Fix NPM binary publication ([#1371](https://github.com/shaka-project/shaka-packager/issues/1371)) ([4cb6536](https://github.com/shaka-project/shaka-packager/commit/4cb653606047b086affc111321187c7889fa238a)), closes [#1369](https://github.com/shaka-project/shaka-packager/issues/1369) +* Fix tags in official Docker images and binaries ([#1370](https://github.com/shaka-project/shaka-packager/issues/1370)) ([d83c7b1](https://github.com/shaka-project/shaka-packager/commit/d83c7b1d4505a08ef578390115cf28bea77404c2)), closes [#1366](https://github.com/shaka-project/shaka-packager/issues/1366) + +## [3.0.2](https://github.com/shaka-project/shaka-packager/compare/v3.0.1...v3.0.2) (2024-03-07) + + +### Bug Fixes + +* duplicate representation id for TTML when forced ordering is on ([#1364](https://github.com/shaka-project/shaka-packager/issues/1364)) ([0fd815a](https://github.com/shaka-project/shaka-packager/commit/0fd815a160cc4546ea0c13ac916727777f5cdd41)), closes [#1362](https://github.com/shaka-project/shaka-packager/issues/1362) + +## [3.0.1](https://github.com/shaka-project/shaka-packager/compare/v3.0.0...v3.0.1) (2024-03-05) + + +### Bug Fixes + +* **CI:** Add Mac-arm64 to build matrix ([#1359](https://github.com/shaka-project/shaka-packager/issues/1359)) ([c456ad6](https://github.com/shaka-project/shaka-packager/commit/c456ad64d1291bcf057c22a5c34479fcb4bbda55)) +* **CI:** Add missing Linux arm64 builds to release ([9c033b9](https://github.com/shaka-project/shaka-packager/commit/9c033b9d40087a9a4eef6a013d17fd696c44459c)) + +## [3.0.0](https://github.com/shaka-project/shaka-packager/compare/v2.6.1...v3.0.0) (2024-02-28) + + +### ⚠ BREAKING CHANGES + +* Update all dependencies +* Drop Python 2 support in all scripts +* Replace glog with absl::log, tweak log output and flags +* Replace gyp build system with CMake + +### Features + +* Add input support for EBU Teletext in MPEG-TS ([#1344](https://github.com/shaka-project/shaka-packager/issues/1344)) ([71c175d](https://github.com/shaka-project/shaka-packager/commit/71c175d4b8fd7dd1ebb2df8ce06c575f54666738)) +* Add install target to build system ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* Add PlayReady support in HLS. ([#1011](https://github.com/shaka-project/shaka-packager/issues/1011)) ([96efc5a](https://github.com/shaka-project/shaka-packager/commit/96efc5aa70c2152a242f36644100161bc562d3f7)) +* add startwithSAP/subsegmentstartswithSAP for audio tracks ([#1346](https://github.com/shaka-project/shaka-packager/issues/1346)) ([d23cce8](https://github.com/shaka-project/shaka-packager/commit/d23cce85b93263a4c7541d9761145be54dd1348d)) +* Add support for ALAC codec ([#1299](https://github.com/shaka-project/shaka-packager/issues/1299)) ([b68ec87](https://github.com/shaka-project/shaka-packager/commit/b68ec87f6a552c27882746a14415bb334041d86b)) +* Add support for single file TS for HLS ([#934](https://github.com/shaka-project/shaka-packager/issues/934)) ([4aa4b4b](https://github.com/shaka-project/shaka-packager/commit/4aa4b4b9aac08fdcf10954de37b9982761fa42c1)) +* Add support for the EXT-X-START tag ([#973](https://github.com/shaka-project/shaka-packager/issues/973)) ([76eb2c1](https://github.com/shaka-project/shaka-packager/commit/76eb2c1575d4c8b32782292dc5216c444a6f2b27)) +* Add xHE-AAC support ([#1092](https://github.com/shaka-project/shaka-packager/issues/1092)) ([5d998fc](https://github.com/shaka-project/shaka-packager/commit/5d998fca7fb1d3d9c98f5b26561f842edcb2a925)) +* Allow LIVE UDP WebVTT input ([#1349](https://github.com/shaka-project/shaka-packager/issues/1349)) ([89376d3](https://github.com/shaka-project/shaka-packager/commit/89376d3c4d3f3005de64dd4bc1668297024dfe46)) +* **DASH:** Add Label element. ([#1175](https://github.com/shaka-project/shaka-packager/issues/1175)) ([b1c5a74](https://github.com/shaka-project/shaka-packager/commit/b1c5a7433e1c148fb6648aadd36c14840e1a4b50)) +* **DASH:** Add video transfer characteristics. ([#1210](https://github.com/shaka-project/shaka-packager/issues/1210)) ([8465f5f](https://github.com/shaka-project/shaka-packager/commit/8465f5f020b5c6152d24107a6d164301e05c3176)) +* default text zero bias ([#1330](https://github.com/shaka-project/shaka-packager/issues/1330)) ([2ba67bc](https://github.com/shaka-project/shaka-packager/commit/2ba67bc24cf6116349ad16d3bfa1a121c95a3173)) +* Drop Python 2 support in all scripts ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* Generate the entire AV1 codec string when the colr atom is present ([#1205](https://github.com/shaka-project/shaka-packager/issues/1205)) ([cc9a691](https://github.com/shaka-project/shaka-packager/commit/cc9a691aef946dfb4d68077d3a741ef1b88d2f21)), closes [#1007](https://github.com/shaka-project/shaka-packager/issues/1007) +* HLS / DASH support forced subtitle ([#1020](https://github.com/shaka-project/shaka-packager/issues/1020)) ([f73ad0d](https://github.com/shaka-project/shaka-packager/commit/f73ad0d9614ba5b1ba552385c0157b7017dba204)) +* Move all third-party deps into git submodules ([#1083](https://github.com/shaka-project/shaka-packager/issues/1083)) ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* order streams in manifest based on command-line order ([#1329](https://github.com/shaka-project/shaka-packager/issues/1329)) ([aad2a12](https://github.com/shaka-project/shaka-packager/commit/aad2a12a9d6b79abe511d66e9e8d936e0ba46cb4)) +* Parse MPEG-TS PMT ES language and maximum bitrate descriptors ([#369](https://github.com/shaka-project/shaka-packager/issues/369)) ([#1311](https://github.com/shaka-project/shaka-packager/issues/1311)) ([c09eb83](https://github.com/shaka-project/shaka-packager/commit/c09eb831b85c93eb122373ce82b64c354d68c3c1)) +* Portable, fully-static release executables on Linux ([#1351](https://github.com/shaka-project/shaka-packager/issues/1351)) ([9be7c2b](https://github.com/shaka-project/shaka-packager/commit/9be7c2b1ac63d2299ed8293c381ef5877003b5c4)) +* Replace glog with absl::log, tweak log output and flags ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* Replace gyp build system with CMake ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)), closes [#1047](https://github.com/shaka-project/shaka-packager/issues/1047) +* Respect the file mode for HttpFiles ([#1081](https://github.com/shaka-project/shaka-packager/issues/1081)) ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* This patch adds support for DTS:X Profile 2 audio in MP4 files. ([#1303](https://github.com/shaka-project/shaka-packager/issues/1303)) ([07f780d](https://github.com/shaka-project/shaka-packager/commit/07f780dae19d7953bd27b646d9410ed68b4b580e)) +* Update all dependencies ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* Write colr atom to muxed mp4 ([#1261](https://github.com/shaka-project/shaka-packager/issues/1261)) ([f264bef](https://github.com/shaka-project/shaka-packager/commit/f264befe868a9fbf054697a6d06218cc25de055d)), closes [#1202](https://github.com/shaka-project/shaka-packager/issues/1202) + + +### Bug Fixes + +* Accept 100% when parsing WEBVTT regions ([#1006](https://github.com/shaka-project/shaka-packager/issues/1006)) ([e1b0c7c](https://github.com/shaka-project/shaka-packager/commit/e1b0c7c45431327fd3ce193514a5407d07b39b22)), closes [#1004](https://github.com/shaka-project/shaka-packager/issues/1004) +* Add missing <cstdint> includes ([#1306](https://github.com/shaka-project/shaka-packager/issues/1306)) ([ba5c771](https://github.com/shaka-project/shaka-packager/commit/ba5c77155a6b0263f48f24f93033c7a386bc83b6)), closes [#1305](https://github.com/shaka-project/shaka-packager/issues/1305) +* Always log to stderr by default ([#1350](https://github.com/shaka-project/shaka-packager/issues/1350)) ([35c2f46](https://github.com/shaka-project/shaka-packager/commit/35c2f4642830e1446701abe17ae6d3b96d6043ae)), closes [#1325](https://github.com/shaka-project/shaka-packager/issues/1325) +* AudioSampleEntry size caluations due to bad merge ([#1354](https://github.com/shaka-project/shaka-packager/issues/1354)) ([615720e](https://github.com/shaka-project/shaka-packager/commit/615720e7dd9699f2d90c63d4bb6ac45c0e804d32)) +* dash_roles add role=description for DVS audio per DASH-IF-IOP-v4.3 ([#1054](https://github.com/shaka-project/shaka-packager/issues/1054)) ([dc03952](https://github.com/shaka-project/shaka-packager/commit/dc0395291a090b342beaab2e89c627dc33ee89b0)) +* Don't close upstream on HttpFile::Flush ([#1201](https://github.com/shaka-project/shaka-packager/issues/1201)) ([53d91cd](https://github.com/shaka-project/shaka-packager/commit/53d91cd0f1295a0c3456cb1a34e5235a0316c523)), closes [#1196](https://github.com/shaka-project/shaka-packager/issues/1196) +* duration formatting and update mpd testdata to reflect new format ([#1320](https://github.com/shaka-project/shaka-packager/issues/1320)) ([56bd823](https://github.com/shaka-project/shaka-packager/commit/56bd823339bbb9ba94ed60a84c554864b42cf94a)) +* Fix build errors related to std::numeric_limits ([#972](https://github.com/shaka-project/shaka-packager/issues/972)) ([9996c73](https://github.com/shaka-project/shaka-packager/commit/9996c736aea79e0cce22bee18dc7dcabfffff47b)) +* Fix build on FreeBSD ([#1287](https://github.com/shaka-project/shaka-packager/issues/1287)) ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* Fix clang build ([#1288](https://github.com/shaka-project/shaka-packager/issues/1288)) ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* Fix failure on very short WebVTT files ([#1216](https://github.com/shaka-project/shaka-packager/issues/1216)) ([dab165d](https://github.com/shaka-project/shaka-packager/commit/dab165d3e5d979e2e5ff783d91d948357b932078)), closes [#1217](https://github.com/shaka-project/shaka-packager/issues/1217) +* Fix handling of non-interleaved multi track FMP4 files ([#1214](https://github.com/shaka-project/shaka-packager/issues/1214)) ([dcf3225](https://github.com/shaka-project/shaka-packager/commit/dcf32258ffd725bc3de06c9bceb86fc8a403ecba)), closes [#1213](https://github.com/shaka-project/shaka-packager/issues/1213) +* Fix issues with `collections.abc` in Python 3.10+ ([#1188](https://github.com/shaka-project/shaka-packager/issues/1188)) ([80e0240](https://github.com/shaka-project/shaka-packager/commit/80e024013df87a4bfeb265c8ea83cfa2a0c5db0f)), closes [#1192](https://github.com/shaka-project/shaka-packager/issues/1192) +* Fix local files with UTF8 names ([#1246](https://github.com/shaka-project/shaka-packager/issues/1246)) ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* Fix missing newline at the end of usage ([#1352](https://github.com/shaka-project/shaka-packager/issues/1352)) ([6276584](https://github.com/shaka-project/shaka-packager/commit/6276584de784025380f46dca379795ed6ee42b8f)) +* Fix Python 3.10+ compatibility in scripts ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* Fix uninitialized value found by Valgrind ([#1336](https://github.com/shaka-project/shaka-packager/issues/1336)) ([7ef5167](https://github.com/shaka-project/shaka-packager/commit/7ef51671f1a221443bcd000ccb13189ee6ccf749)) +* Fix various build issues on macOS ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* Fix various build issues on Windows ([3e71302](https://github.com/shaka-project/shaka-packager/commit/3e71302ba46e5164db91495c5da5ba07fc88cfca)) +* hls, set the DEFAULT explicitly to NO. Supports native HLS players. ([#1170](https://github.com/shaka-project/shaka-packager/issues/1170)) ([1ab6818](https://github.com/shaka-project/shaka-packager/commit/1ab68188326685ab4e427e7a6eab0694e0b0b60a)), closes [#1169](https://github.com/shaka-project/shaka-packager/issues/1169) +* http_file: Close upload cache on task exit ([#1348](https://github.com/shaka-project/shaka-packager/issues/1348)) ([6acdcc3](https://github.com/shaka-project/shaka-packager/commit/6acdcc394a1583b22c3c16cee3e419eea6c6e4f9)), closes [#1347](https://github.com/shaka-project/shaka-packager/issues/1347) +* Indexing `bytes` produces `int` on python3 for `pssh-box.py` ([#1228](https://github.com/shaka-project/shaka-packager/issues/1228)) ([d9d3c7f](https://github.com/shaka-project/shaka-packager/commit/d9d3c7f8be13e493a99b2ff4b72a402c441c1666)), closes [#1227](https://github.com/shaka-project/shaka-packager/issues/1227) +* Low Latency DASH: include the "availabilityTimeComplete=false" attribute ([#1198](https://github.com/shaka-project/shaka-packager/issues/1198)) ([d687ad1](https://github.com/shaka-project/shaka-packager/commit/d687ad1ed00da260c4f4e5169b042ef4291052a0)) +* misleading log output when HLS target duration updates (fixes [#969](https://github.com/shaka-project/shaka-packager/issues/969)) ([#971](https://github.com/shaka-project/shaka-packager/issues/971)) ([f7b3986](https://github.com/shaka-project/shaka-packager/commit/f7b3986818915ab3d7d3dc31b8316fcef0384294)) +* **MP4:** Add compatible brand dby1 for Dolby content. ([#1211](https://github.com/shaka-project/shaka-packager/issues/1211)) ([520926c](https://github.com/shaka-project/shaka-packager/commit/520926c27ad0d183127e4548c4564af33a2ad2f3)) +* Parse one frame mpeg-ts video ([#1015](https://github.com/shaka-project/shaka-packager/issues/1015)) ([b221aa9](https://github.com/shaka-project/shaka-packager/commit/b221aa9caf4f8357a696f3265d1e2a5bf504dbd9)), closes [#1013](https://github.com/shaka-project/shaka-packager/issues/1013) +* preserve case for stream descriptors ([#1321](https://github.com/shaka-project/shaka-packager/issues/1321)) ([5d44368](https://github.com/shaka-project/shaka-packager/commit/5d44368478bbd0cd8bd06f0dfa739f8cfa032ddc)) +* Prevent crash in GetEarliestTimestamp() if periods are empty ([#1173](https://github.com/shaka-project/shaka-packager/issues/1173)) ([d6f28d4](https://github.com/shaka-project/shaka-packager/commit/d6f28d456c6ec5ecf39c868447d85294a698166d)), closes [#1172](https://github.com/shaka-project/shaka-packager/issues/1172) +* PTS diverge DTS when DTS close to 2pow33 and PTS more than 0 ([#1050](https://github.com/shaka-project/shaka-packager/issues/1050)) ([ab8ab12](https://github.com/shaka-project/shaka-packager/commit/ab8ab12d098c352372014180bd2cb5407e018739)), closes [#1049](https://github.com/shaka-project/shaka-packager/issues/1049) +* remove extra block assumptions in mbedtls integration ([#1323](https://github.com/shaka-project/shaka-packager/issues/1323)) ([db59ad5](https://github.com/shaka-project/shaka-packager/commit/db59ad582a353fa1311563bdde93c49449159859)), closes [#1316](https://github.com/shaka-project/shaka-packager/issues/1316) +* Restore support for legacy FairPlay system ID ([#1357](https://github.com/shaka-project/shaka-packager/issues/1357)) ([4d22e99](https://github.com/shaka-project/shaka-packager/commit/4d22e99f8e7777557f5de3d5439f7e7b397e4323)) +* Roll back depot_tools, bypass vpython ([#1045](https://github.com/shaka-project/shaka-packager/issues/1045)) ([3fd538a](https://github.com/shaka-project/shaka-packager/commit/3fd538a587184a87e2b41a526e089247007aa526)), closes [#1023](https://github.com/shaka-project/shaka-packager/issues/1023) +* set array_completeness in HEVCDecoderConfigurationRecord correctly ([#975](https://github.com/shaka-project/shaka-packager/issues/975)) ([270888a](https://github.com/shaka-project/shaka-packager/commit/270888abb12b2181ff84071f2c2685bd196de6fe)) +* TTML generator timestamp millisecond formatting ([#1179](https://github.com/shaka-project/shaka-packager/issues/1179)) ([494769c](https://github.com/shaka-project/shaka-packager/commit/494769ca864e04d582f707934a6573cad78d2e8c)), closes [#1180](https://github.com/shaka-project/shaka-packager/issues/1180) +* Update golden files for ttml tests and failing hls unit tests. ([#1226](https://github.com/shaka-project/shaka-packager/issues/1226)) ([ac47e52](https://github.com/shaka-project/shaka-packager/commit/ac47e529ad7b69cc232f7f96e2a042990505776f)) +* Update to use official FairPlay UUID. ([#1281](https://github.com/shaka-project/shaka-packager/issues/1281)) ([ac59b9e](https://github.com/shaka-project/shaka-packager/commit/ac59b9ebc94018b216c657854ca5163c9d2e7f31)) +* use a better estimate of frame rate for cases with very short first sample durations ([#838](https://github.com/shaka-project/shaka-packager/issues/838)) ([5644041](https://github.com/shaka-project/shaka-packager/commit/56440413aa32a13cbfbb41a6d5ee611bae02ab2e)) +* webvtt single cue do not fail on EOS ([#1061](https://github.com/shaka-project/shaka-packager/issues/1061)) ([b9d477b](https://github.com/shaka-project/shaka-packager/commit/b9d477b969f34124dfe184b4ac1d00ea8faf0a7d)), closes [#1018](https://github.com/shaka-project/shaka-packager/issues/1018) + ## [2.6.1] - 2021-10-14 ### Fixed - Fix crash in static-linked linux builds (#996) diff --git a/CMakeLists.txt b/CMakeLists.txt index ebc194a00c4..5b96a2429e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ # Root-level CMake build file. # Minimum CMake version. This must be in the root level CMakeLists.txt. -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.24) # These policy settings should be included before the project definition. include("packager/policies.cmake") @@ -15,14 +15,18 @@ include("packager/policies.cmake") # Project name. May not contain spaces. Versioning is managed elsewhere. project(shaka-packager VERSION "") -# The only build option for Shaka Packager is whether to build a shared -# libpackager library. By default, don't. +# Whether to build a shared libpackager library. By default, don't. option(BUILD_SHARED_LIBS "Build libpackager as a shared library" OFF) +# Whether to attempt a static linking of the command line front-end. +# Only supported on Linux and with musl. This will also cause us to link +# against mimalloc to replace the standard allocator in musl, which is slow. +option(FULLY_STATIC "Attempt fully static linking of all CLI apps" OFF) + # Enable CMake's test infrastructure. enable_testing() -option(SKIP_INTEGRATION_TESTS "Skip the packager integration tests" ON) +option(SKIP_INTEGRATION_TESTS "Skip the packager integration tests" OFF) # Subdirectories with their own CMakeLists.txt add_subdirectory(packager) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f146ab0e21d..9ba03ca1b7e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -42,6 +42,7 @@ Kongqun Yang Leandro Moreira Leo Law Marcus Spangenberg +Michal Wierzbicki Ole Andre Birkedal Piotr Srebrny Prakash Duggaraju @@ -53,6 +54,7 @@ Sanil Raut Sergio Ammirata Thomas Inskip Tim Lansen +Torbjörn Einarsson Vincent Nguyen Weiguo Shao diff --git a/Dockerfile b/Dockerfile index 2b820f7eb7e..e46dccb060d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.12 as builder +FROM alpine:3.19 as builder # Install utilities, libraries, and dev tools. RUN apk add --no-cache \ @@ -10,21 +10,20 @@ RUN apk add --no-cache \ # merged. WORKDIR shaka-packager COPY . /shaka-packager/ -RUN mkdir build +RUN rm -rf build RUN cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -G Ninja RUN cmake --build build/ --config Debug --parallel # Copy only result binaries to our final image. -FROM alpine:3.12 +FROM alpine:3.19 RUN apk add --no-cache libstdc++ python3 -# TODO(joeyparrish): Copy binaries when build system is complete -#COPY --from=builder /shaka-packager/build/packager \ -# /shaka-packager/build/mpd_generator \ -# /shaka-packager/build/pssh-box.py \ -# /usr/bin/ +COPY --from=builder /shaka-packager/build/packager/packager \ + /shaka-packager/build/packager/mpd_generator \ + /shaka-packager/build/packager/pssh-box.py \ + /usr/bin/ # Copy pyproto directory, which is needed by pssh-box.py script. This line # cannot be combined with the line above as Docker's copy command skips the # directory itself. See https://github.com/moby/moby/issues/15858 for details. -# TODO(joeyparrish): Copy binaries when build system is complete -#COPY --from=builder /shaka-packager/build/pyproto /usr/bin/pyproto +COPY --from=builder /shaka-packager/build/packager/pssh-box-protos \ + /usr/bin/pssh-box-protos diff --git a/docs/source/build_instructions.md b/docs/source/build_instructions.md index d63394cc1a5..3f830ac5b82 100644 --- a/docs/source/build_instructions.md +++ b/docs/source/build_instructions.md @@ -17,6 +17,9 @@ sudo apt-get install -y \ Note that `git` must be v1.7.6 or above to support relative paths in submodules. +Note also that `cmake` must be v3.24 or above to support a linker setting +needed for `absl::log_flags`. + ## Mac system requirements * [Xcode](https://developer.apple.com/xcode) 7.3+. @@ -103,6 +106,18 @@ After configuring CMake you can run the build with cmake --build build --parallel ``` +To build portable, fully-static executables on Linux, you will need either musl +as your system libc, or a musl toolchain. (See [musl.cc](https://musl.cc). +To create a portable, fully-static build for Linux, configure CMake with: + +```shell + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS="OFF" \ + -DFULLY_STATIC="ON" \ + -DCMAKE_C_COMPILER=/path/to/x86_64-linux-musl-gcc \ + -DCMAKE_CXX_COMPILER=/path/to/x86_64-linux-musl-g++ +``` + #### Windows Windows build instructions are similar. Using Tools > Command Line > diff --git a/docs/source/options/chunking_options.rst b/docs/source/options/chunking_options.rst index 560a2a7cc8c..4bb909e0437 100644 --- a/docs/source/options/chunking_options.rst +++ b/docs/source/options/chunking_options.rst @@ -21,3 +21,7 @@ Chunking options Force fragments to begin with stream access points. This flag implies *segment_sap_aligned*. Default enabled. + +--start_segment_number + + Indicates the startNumber in DASH SegmentTemplate and HLS segment name. diff --git a/docs/source/options/dash_options.rst b/docs/source/options/dash_options.rst index e47ae3774f3..e0424e0dcc1 100644 --- a/docs/source/options/dash_options.rst +++ b/docs/source/options/dash_options.rst @@ -99,4 +99,15 @@ DASH options --low_latency_dash_mode If enabled, LL-DASH streaming will be used, - reducing overall latency by decoupling latency from segment duration. \ No newline at end of file + reducing overall latency by decoupling latency from segment duration. + +--force_cl_index + + True forces the muxer to order streams in the order given + on the command-line. False uses the previous unordered behavior. + +--dash_label + + Optional. Will add Label tag to adapation set and will be taken into + consideration along with codecs, language, media type (audio, video etc) + and container type to create different adaptation sets. diff --git a/docs/source/options/dash_stream_descriptors.rst b/docs/source/options/dash_stream_descriptors.rst index a6a6a101aed..a50f7fadaa6 100644 --- a/docs/source/options/dash_stream_descriptors.rst +++ b/docs/source/options/dash_stream_descriptors.rst @@ -10,7 +10,10 @@ DASH specific stream descriptor fields :dash_roles (roles): - Optional semicolon separated list of values for DASH Role element. The + optional semicolon separated list of values for DASH Role element. The value should be one of: **caption**, **subtitle**, **main**, **alternate**, - **supplementary**, **commentary**, **description** and **dub**. See - DASH (ISO/IEC 23009-1) specification for details. + **supplementary**, **commentary**, **dub**, **description**, **sign**, + **metadata**, **enhanced-audio- intelligibility**, **emergency**, + **forced-subtitle**, **easyreader**, and **karaoke**. + + See DASH (ISO/IEC 23009-1) specification for details. diff --git a/docs/source/options/hls_options.rst b/docs/source/options/hls_options.rst index c03e39e751a..8199195d26e 100644 --- a/docs/source/options/hls_options.rst +++ b/docs/source/options/hls_options.rst @@ -76,7 +76,20 @@ HLS options The EXT-X-MEDIA-SEQUENCE documentation can be read here: https://tools.ietf.org/html/rfc8216#section-4.3.3.2. +--hls_start_time_offset + + Sets EXT-X-START on the media playlists to specify the preferred point + at wich the player should start playing. + A positive number indicates a time offset from the beginning of the playlist. + A negative number indicates a negative time offset from the end of the + last media segment in the playlist. + --hls_only=0|1 Optional. Defaults to 0 if not specified. If it is set to 1, indicates the stream is HLS only. + +--force_cl_index + + True forces the muxer to order streams in the order given + on the command-line. False uses the previous unordered behavior. \ No newline at end of file diff --git a/docs/source/options/stream_descriptors.rst b/docs/source/options/stream_descriptors.rst index 7b5aaf16667..b7d041dfb52 100644 --- a/docs/source/options/stream_descriptors.rst +++ b/docs/source/options/stream_descriptors.rst @@ -60,6 +60,20 @@ These are the available fields: For subtitles in MP4, you can specify 'vtt+mp4' or 'ttml+mp4' to control which text format is used. +:input_format (format): + + Optional value which specifies the format of the input files or + streams. If not specified, it will be autodetected, which in some + cases may fail. + + For example, a live UDP WebVTT input stream may be up and streaming + long before a shaka packager instance consumes it, and therefore + shaka packager never gets the initial "WEBVTT" header string. In + such a case, shaka packager can't properly autodetect the stream + format as WebVTT, and thus doesn't process it. But stating + 'input_format=webvtt' as selector parameter will tell shaka packager + to omit autodetection and consider WebVTT format for that stream. + :trick_play_factor (tpf): Optional value which specifies the trick play, a.k.a. trick mode, stream @@ -73,6 +87,15 @@ These are the available fields: CEA allows specifying up to 4 streams within a single video stream. If not specified, all subtitles will be merged together. +:forced_subtitle: + + Optional boolean value (0|1). If set to 1 indicates that this stream is a + Forced Narrative subtitle that should be displayed when subtitles are otherwise + off, for example used to caption short portions of the audio that might be in + a foreign language. For DASH this will set role to **forced_subtitle**, for HLS + it will set FORCED=YES and AUTOSELECT=YES. Only valid for subtitles. + + .. include:: /options/drm_stream_descriptors.rst .. include:: /options/dash_stream_descriptors.rst .. include:: /options/hls_stream_descriptors.rst diff --git a/docs/source/options/widevine_encryption_options.rst b/docs/source/options/widevine_encryption_options.rst index 9d875a69755..597f0f9c480 100644 --- a/docs/source/options/widevine_encryption_options.rst +++ b/docs/source/options/widevine_encryption_options.rst @@ -9,6 +9,10 @@ Widevine encryption options --protection_systems is not specified. Use --protection_systems to generate multiple protection systems. +--enable_entitlement_license + + Enable entitlement license in the Widevine encryption request. + --enable_widevine_decryption Enable decryption with Widevine key server. User should provide either diff --git a/docs/source/tutorials/text.rst b/docs/source/tutorials/text.rst index 502c3093362..8291a29794b 100644 --- a/docs/source/tutorials/text.rst +++ b/docs/source/tutorials/text.rst @@ -45,3 +45,9 @@ Examples in=in_en.vtt,stream=text,language=en,output=out_en.mp4 \ in=in_sp.vtt,stream=text,language=sp,output=out_sp.mp4 \ in=in_fr.vtt,stream=text,language=fr,output=out_fr.mp4 + +* Get a single 3-digit page from DVB-teletext and set language for output formats stpp (TTML in mp4), wvtt (WebVTT in mp4) and HLS WebVTT:: + + $ packager in=input.ts,stream=text,cc_index=888,lang=en,format=ttml+mp4,output=output.mp4 + $ packager in=input.ts,stream=text,cc_index=888,lang=en,output=output.mp4 + $ packager in=input.ts,stream=text,cc_index=888,segment_template=text/$Number$.vtt,playlist_name=text/main.m3u8,hls_group_id=text,hls_name=ENGLISH diff --git a/include/packager/chunking_params.h b/include/packager/chunking_params.h index e6c69615a00..546b6b5b57c 100644 --- a/include/packager/chunking_params.h +++ b/include/packager/chunking_params.h @@ -39,6 +39,9 @@ struct ChunkingParams { /// Enable VTT text chunking adjustment when the sample end time falls outside /// the segment end time. bool adjust_sample_boundaries = false; + + /// Indicates the startNumber in DASH SegmentTemplate and HLS segment name. + int64_t start_segment_number = 1; }; } // namespace shaka diff --git a/include/packager/crypto_params.h b/include/packager/crypto_params.h index 845d91cf42f..1d87f817c1d 100644 --- a/include/packager/crypto_params.h +++ b/include/packager/crypto_params.h @@ -153,7 +153,7 @@ struct EncryptionParams { RawKeyParams raw_key; /// The protection systems to generate, multiple can be OR'd together. - ProtectionSystem protection_systems; + ProtectionSystem protection_systems = ProtectionSystem::kNone; /// Extra XML data to add to PlayReady data. std::string playready_extra_header_data; diff --git a/include/packager/hls_params.h b/include/packager/hls_params.h index a476beb412e..e8c86aa9063 100644 --- a/include/packager/hls_params.h +++ b/include/packager/hls_params.h @@ -8,6 +8,7 @@ #define PACKAGER_PUBLIC_HLS_PARAMS_H_ #include +#include #include namespace shaka { @@ -63,6 +64,12 @@ struct HlsParams { /// Custom EXT-X-MEDIA-SEQUENCE value to allow continuous media playback /// across packager restarts. See #691 for details. uint32_t media_sequence_number = 0; + /// Sets EXT-X-START on the media playlists to specify the preferred point + /// at wich the player should start playing. + /// A positive number indicates a time offset from the beginning of the + /// playlist. A negative number indicates a negative time offset from the end + /// of the last media segment in the playlist. + std::optional start_time_offset; }; } // namespace shaka diff --git a/include/packager/mp4_output_params.h b/include/packager/mp4_output_params.h index 18a10e38d2c..3f029df6963 100644 --- a/include/packager/mp4_output_params.h +++ b/include/packager/mp4_output_params.h @@ -30,7 +30,7 @@ struct Mp4OutputParams { /// User-specified sequence number to be set in the moof header. /// The moof header sequence number starts at 1 so values less than 1 will be /// set to 1. - uint32_t sequence_number = 1; + uint32_t sequence_number = 0; }; } // namespace shaka diff --git a/include/packager/packager.h b/include/packager/packager.h index 71733735c12..a0c0f6c8260 100644 --- a/include/packager/packager.h +++ b/include/packager/packager.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -46,6 +47,10 @@ struct PackagingParams { /// audio) timestamps to compensate for possible negative timestamps in the /// input. int32_t transport_stream_timestamp_offset_ms = 0; + // the threshold used to determine if we should assume that the text stream + // actually starts at time zero + int32_t default_text_zero_bias_ms = 0; + /// Chunking (segmentation) related parameters. ChunkingParams chunking_params; @@ -58,6 +63,7 @@ struct PackagingParams { /// Only use a single thread to generate output. This is useful in tests to /// avoid non-deterministic outputs. bool single_threaded = false; + /// DASH MPD related parameters. MpdParams mpd_params; /// HLS related parameters. @@ -93,6 +99,9 @@ struct PackagingParams { /// Defines a single input/output stream. struct StreamDescriptor { + /// index of the stream to enforce ordering + std::optional index; + /// Input/source media file path or network stream URL. Required. std::string input; @@ -159,6 +168,17 @@ struct StreamDescriptor { bool dash_only = false; /// Set to true to indicate that the stream is for hls only. bool hls_only = false; + + /// Optional value which specifies input container format. + /// Useful for live streaming situations, like auto-detecting webvtt without + /// its initial header. + std::string input_format; + + /// Optional, indicates if this is a Forced Narrative subtitle stream. + bool forced_subtitle = false; + + /// Optional for DASH output. It defines the Label element in Adaptation Set. + std::string dash_label; }; class SHAKA_EXPORT Packager { diff --git a/link-test/CMakeLists.txt b/link-test/CMakeLists.txt index 3fc0d097d3b..4fce0f1b92c 100644 --- a/link-test/CMakeLists.txt +++ b/link-test/CMakeLists.txt @@ -13,7 +13,7 @@ if(BUILD_SHARED_LIBS) # Custom commands aren't targets, but have outputs. add_custom_command( - DEPENDS mpd_generator packager libpackager + DEPENDS mpd_generator packager libpackager pssh_box_py WORKING_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT ${TEST_INSTALL_DIR} COMMAND diff --git a/npm/.npmignore b/npm/.npmignore new file mode 100644 index 00000000000..9d17c9c4d9f --- /dev/null +++ b/npm/.npmignore @@ -0,0 +1,8 @@ +# This file is required for us, even if empty. +# +# The default .npmignore would disallow bin/, which contains binaries for our +# NPM release. Every release before v3.0.3 was missing .npmignore, and +# therefore had no binaries. These were unusable in NPM, and have been marked +# as deprecated because of it. +# +# See also https://github.com/shaka-project/shaka-packager/issues/1369 diff --git a/npm/index.js b/npm/index.js index 97ba60547eb..215dc573e4c 100755 --- a/npm/index.js +++ b/npm/index.js @@ -4,15 +4,32 @@ var path = require('path'); var spawnSync = require('child_process').spawnSync; -// Command names per-platform: +// Command names per-platform (process.platform) and per-architecture +// (process.arch): var commandNames = { - linux: 'packager-linux', - darwin: 'packager-osx', - win32: 'packager-win.exe', + linux: { + 'x64': 'packager-linux-x64', + 'arm64': 'packager-linux-arm64', + }, + darwin: { + 'x64': 'packager-osx-x64', + 'arm64': 'packager-osx-arm64', + }, + win32: { + 'x64': 'packager-win-x64.exe', + }, }; // Find the platform-specific binary: -var binaryPath = path.resolve(__dirname, 'bin', commandNames[process.platform]); +if (!(process.platform in commandNames)) { + throw new Error('Platform not supported: ' + process.platform); +} +if (!(process.arch in commandNames[process.platform])) { + throw new Error( + 'Architecture not supported: ' + process.platform + '/' + process.arch); +} +var commandName = commandNames[process.platform][process.arch]; +var binaryPath = path.resolve(__dirname, 'bin', commandName); // Find the args to pass to that binary: // argv[0] is node itself, and argv[1] is the script. diff --git a/npm/package.json b/npm/package.json index 15711fc1603..4c271343567 100644 --- a/npm/package.json +++ b/npm/package.json @@ -2,6 +2,7 @@ "name": "", "description": "A media packaging tool and SDK.", "version": "", + "private": false, "homepage": "https://github.com/shaka-project/shaka-packager", "author": "Google", "maintainers": [ diff --git a/npm/prepublish.js b/npm/prepublish.js index dc758e4be0c..b6a2c49eb13 100755 --- a/npm/prepublish.js +++ b/npm/prepublish.js @@ -5,11 +5,20 @@ var fs = require('fs'); var path = require('path'); var spawnSync = require('child_process').spawnSync; -// Command names per-platform: +// Command names per-platform (process.platform) and per-architecture +// (process.arch): var commandNames = { - linux: 'packager-linux', - darwin: 'packager-osx', - win32: 'packager-win.exe', + linux: { + 'x64': 'packager-linux-x64', + 'arm64': 'packager-linux-arm64', + }, + darwin: { + 'x64': 'packager-osx-x64', + 'arm64': 'packager-osx-arm64', + }, + win32: { + 'x64': 'packager-win-x64.exe', + }, }; // Get the current package version: @@ -44,12 +53,23 @@ fs.readdirSync(binFolderPath).forEach(function(childName) { }); for (var platform in commandNames) { - // Find the destination for this binary: - var command = commandNames[platform]; - var binaryPath = path.resolve(binFolderPath, command); - - download(urlBase + command, binaryPath); - fs.chmodSync(binaryPath, 0755); + for (var arch in commandNames[platform]) { + // Find the destination for this binary: + var command = commandNames[platform][arch]; + var binaryPath = path.resolve(binFolderPath, command); + + try { + download(urlBase + command, binaryPath); + fs.chmodSync(binaryPath, 0755); + } catch (error) { + if (arch == 'arm64') { + // Optional. Forks may not have arm64 builds available. Ignore. + } else { + // Required. Re-throw and fail. + throw error; + } + } + } } // Fetch LICENSE and README files from the same tag, and include them in the @@ -83,6 +103,6 @@ function download(url, outputPath) { console.log('Downloading', url, 'to', outputPath); var returnValue = spawnSync('curl', args, options); if (returnValue.status != 0) { - process.exit(returnValue.status); + throw new Error('Download of ' + url + ' failed: ' + returnValue.status); } } diff --git a/packager/CMakeLists.txt b/packager/CMakeLists.txt index a0dec369edf..838d3f5848a 100644 --- a/packager/CMakeLists.txt +++ b/packager/CMakeLists.txt @@ -45,7 +45,7 @@ if(MSVC) else() # Lots of warnings and all warnings as errors. # Note that we can't use -Wpedantic due to absl's int128 headers. - add_compile_options(-Wall -Wextra -Wno-documentation -Wno-deprecated-declarations -Wno-deprecated -Wno-documentation-deprecated-sync) + add_compile_options(-Wall -Wextra -Werror) # Several warning suppression flags are required on one compiler version and # not understood by another. Do not treat these as errors. add_compile_options(-Wno-unknown-warning-option) @@ -61,12 +61,20 @@ include_directories(..) # Public include folder, to reference public headers as packager/foo.h include_directories(../include) +# Include settings for optional fully-static binaries. +include("fully-static.cmake") + # Include our module for gtest-based testing. include("gtest.cmake") # Include our module for building protos. include("protobuf.cmake") +# Find Python3 used by integration tests, license notice and version string +if(NOT Python3_EXECUTABLE) + find_package(Python3 COMPONENTS Interpreter REQUIRED) +endif() + # Subdirectories with their own CMakeLists.txt, all of whose targets are built. add_subdirectory(file) add_subdirectory(kv_pairs) @@ -120,36 +128,23 @@ set(libpackager_deps ) # A static library target is always built. -add_library(libpackager_static STATIC ${libpackager_sources}) -target_link_libraries(libpackager_static ${libpackager_deps}) -# And always installed as libpackager.a: -if(NOT MSVC) - set_property(TARGET libpackager_static PROPERTY OUTPUT_NAME packager) +if(BUILD_SHARED_LIBS) + add_library(libpackager SHARED ${libpackager_sources}) + target_link_libraries(libpackager ${libpackager_deps}) + target_compile_definitions(libpackager PUBLIC SHAKA_IMPLEMENTATION) else() - set_property(TARGET libpackager_static PROPERTY OUTPUT_NAME libpackager) + add_library(libpackager STATIC ${libpackager_sources}) + target_link_libraries(libpackager ${libpackager_deps}) endif() -# A shared library target is conditional (default OFF): -if(BUILD_SHARED_LIBS) - add_library(libpackager_shared SHARED ${libpackager_sources}) - target_link_libraries(libpackager_shared ${libpackager_deps}) - target_compile_definitions(libpackager_shared PUBLIC SHAKA_IMPLEMENTATION) - - # And always installed as libpackager.so / libpackager.dll: - if(NOT MSVC) - set_property(TARGET libpackager_shared PROPERTY OUTPUT_NAME packager) - else() - set_property(TARGET libpackager_shared PROPERTY OUTPUT_NAME libpackager) - endif() - - # If we're building a shared library, this is what the "libpackager" target - # aliases to. - add_library(libpackager ALIAS libpackager_shared) +# The library is always installed as +# libpackager.so / libpackager.dll / libpackager.a / libpackager.lib: +if(NOT MSVC) + # The "lib" prefix is implied outside of MSVC. + set_property(TARGET libpackager PROPERTY OUTPUT_NAME packager) else() - # If we're not building a shared library, the "libpackager" target aliases to - # the static library. - add_library(libpackager ALIAS libpackager_static) + set_property(TARGET libpackager PROPERTY OUTPUT_NAME libpackager) endif() add_executable(packager @@ -178,8 +173,6 @@ add_executable(packager app/stream_descriptor.h app/validate_flag.cc app/validate_flag.h - app/vlog_flags.cc - app/vlog_flags.h app/widevine_encryption_flags.cc app/widevine_encryption_flags.h ) @@ -187,29 +180,31 @@ target_link_libraries(packager absl::flags absl::flags_parse absl::log - absl::log_flags + # See https://github.com/abseil/abseil-cpp/blob/c14dfbf9/absl/log/CMakeLists.txt#L464-L467 + $ absl::strings hex_bytes_flags libpackager license_notice string_utils + ${EXTRA_EXE_LIBRARIES} ) add_executable(mpd_generator app/mpd_generator.cc app/mpd_generator_flags.h - app/vlog_flags.cc - app/vlog_flags.h ) target_link_libraries(mpd_generator absl::flags absl::flags_parse absl::log - absl::log_flags + # See https://github.com/abseil/abseil-cpp/blob/c14dfbf9/absl/log/CMakeLists.txt#L464-L467 + $ absl::strings license_notice mpd_builder mpd_util + ${EXTRA_EXE_LIBRARIES} ) add_executable(packager_test @@ -264,9 +259,22 @@ add_custom_target(packager_test_py_copy ALL if(NOT SKIP_INTEGRATION_TESTS) add_test (NAME packager_test_py - COMMAND ${PYTHON_EXECUTABLE} packager_test.py + COMMAND "${Python3_EXECUTABLE}" packager_test.py WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) + + set(test_environment_vars "PACKAGER_SRC_DIR=${CMAKE_SOURCE_DIR}") + list(APPEND test_environment_vars "PACKAGER_BIN=$") + list(APPEND test_environment_vars "MPD_GENERATOR_BIN=$") + if(BUILD_SHARED_LIBS) + list(APPEND test_environment_vars "BUILD_TYPE=shared") + else() + list(APPEND test_environment_vars "BUILD_TYPE=static") + endif() + + set_tests_properties(packager_test_py PROPERTIES + ENVIRONMENT "${test_environment_vars}" + ) endif() configure_file(packager.pc.in packager.pc @ONLY) @@ -274,19 +282,13 @@ configure_file(packager.pc.in packager.pc @ONLY) # Always install the binaries. install(TARGETS mpd_generator packager) -# Always install the python tools. -install(PROGRAMS ${CMAKE_BINARY_DIR}/pssh-box.py - DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(DIRECTORY ${CMAKE_BINARY_DIR}/pssh-box-protos - DESTINATION ${CMAKE_INSTALL_BINDIR}) - # With shared libraries, also install the library, headers, and pkgconfig. # The static library isn't usable as a standalone because it doesn't include # its static dependencies (zlib, absl, etc). if(BUILD_SHARED_LIBS) - install(TARGETS libpackager_shared) + install(TARGETS libpackager) install(DIRECTORY ../include/packager DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - install(FILES ${CMAKE_BINARY_DIR}/packager/packager.pc + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/packager.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) endif() diff --git a/packager/app/hls_flags.cc b/packager/app/hls_flags.cc index ac301722cd4..653830ca4f7 100644 --- a/packager/app/hls_flags.cc +++ b/packager/app/hls_flags.cc @@ -6,6 +6,8 @@ #include +#include + ABSL_FLAG(std::string, hls_master_playlist_output, "", @@ -35,3 +37,12 @@ ABSL_FLAG(int32_t, "EXT-X-MEDIA-SEQUENCE value, which allows continuous media " "sequence across packager restarts. See #691 for more " "information about the reasoning of this and its use cases."); +ABSL_FLAG(std::optional, + hls_start_time_offset, + std::nullopt, + "Floating-point number. Sets EXT-X-START on the media playlists " + "to specify the preferred point at wich the player should start " + "playing. A positive number indicates a time offset from the " + "beginning of the playlist. A negative number indicates a " + "negative time offset from the end of the last media segment " + "in the playlist."); diff --git a/packager/app/hls_flags.h b/packager/app/hls_flags.h index 09c5a3f6f89..5bb7ae754ab 100644 --- a/packager/app/hls_flags.h +++ b/packager/app/hls_flags.h @@ -15,5 +15,6 @@ ABSL_DECLARE_FLAG(std::string, hls_base_url); ABSL_DECLARE_FLAG(std::string, hls_key_uri); ABSL_DECLARE_FLAG(std::string, hls_playlist_type); ABSL_DECLARE_FLAG(int32_t, hls_media_sequence_number); +ABSL_DECLARE_FLAG(std::optional, hls_start_time_offset); #endif // PACKAGER_APP_HLS_FLAGS_H_ diff --git a/packager/app/manifest_flags.cc b/packager/app/manifest_flags.cc index dc72fb3ed62..0a6c97ffe57 100644 --- a/packager/app/manifest_flags.cc +++ b/packager/app/manifest_flags.cc @@ -37,3 +37,8 @@ ABSL_FLAG(std::string, "", "Same as above, but this applies to text tracks only, and " "overrides the default language for text tracks."); +ABSL_FLAG(bool, + force_cl_index, + true, + "True forces the muxer to order streams in the order given " + "on the command-line. False uses the previous unordered behavior."); diff --git a/packager/app/manifest_flags.h b/packager/app/manifest_flags.h index 59b6507df4b..77296a08687 100644 --- a/packager/app/manifest_flags.h +++ b/packager/app/manifest_flags.h @@ -16,5 +16,6 @@ ABSL_DECLARE_FLAG(double, time_shift_buffer_depth); ABSL_DECLARE_FLAG(uint64_t, preserved_segments_outside_live_window); ABSL_DECLARE_FLAG(std::string, default_language); ABSL_DECLARE_FLAG(std::string, default_text_language); +ABSL_DECLARE_FLAG(bool, force_cl_index); #endif // PACKAGER_APP_MANIFEST_FLAGS_H_ diff --git a/packager/app/mpd_generator.cc b/packager/app/mpd_generator.cc index 757a62c4f2a..e4cec7ed485 100644 --- a/packager/app/mpd_generator.cc +++ b/packager/app/mpd_generator.cc @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -32,6 +31,9 @@ ABSL_FLAG(std::string, "", "Packager version for testing. Should be used for testing only."); +// From absl/log: +ABSL_DECLARE_FLAG(int, stderrthreshold); + namespace shaka { namespace { const char kUsage[] = @@ -110,6 +112,13 @@ int MpdMain(int argc, char** argv) { auto usage = absl::StrFormat(kUsage, argv[0]); absl::SetProgramUsageMessage(usage); + + // Before parsing the command line, change the default value of some flags + // provided by libraries. + + // Always log to stderr. Log levels are still controlled by --minloglevel. + absl::SetFlag(&FLAGS_stderrthreshold, 0); + absl::ParseCommandLine(argc, argv); if (absl::GetFlag(FLAGS_licenses)) { @@ -124,8 +133,6 @@ int MpdMain(int argc, char** argv) { return status; } - handle_vlog_flags(); - absl::InitializeLog(); if (!absl::GetFlag(FLAGS_test_packager_version).empty()) diff --git a/packager/app/muxer_factory.cc b/packager/app/muxer_factory.cc index d1aa3f27147..d2f9a6406fb 100644 --- a/packager/app/muxer_factory.cc +++ b/packager/app/muxer_factory.cc @@ -91,7 +91,7 @@ std::shared_ptr MuxerFactory::CreateMuxer( return muxer; } -void MuxerFactory::OverrideClock(Clock* clock) { +void MuxerFactory::OverrideClock(std::shared_ptr clock) { clock_ = clock; } } // namespace media diff --git a/packager/app/muxer_factory.h b/packager/app/muxer_factory.h index 0098d141ffa..217262e7d2b 100644 --- a/packager/app/muxer_factory.h +++ b/packager/app/muxer_factory.h @@ -40,7 +40,7 @@ class MuxerFactory { /// For testing, if you need to replace the clock that muxers work with /// this will replace the clock for all muxers created after this call. - void OverrideClock(Clock* clock); + void OverrideClock(std::shared_ptr clock); void SetTsStreamOffset(int32_t offset_ms) { transport_stream_timestamp_offset_ms_ = offset_ms; @@ -53,13 +53,12 @@ class MuxerFactory { const Mp4OutputParams mp4_params_; const std::string temp_dir_; int32_t transport_stream_timestamp_offset_ms_ = 0; - Clock* clock_ = nullptr; - - // enable init segment packaging separately bool init_segment_only_; // enable null TS packet stuffing bool enable_null_ts_packet_stuffing_; + + std::shared_ptr clock_ = nullptr; }; } // namespace media diff --git a/packager/app/muxer_flags.cc b/packager/app/muxer_flags.cc index bbee0ccf040..c3418b905b0 100644 --- a/packager/app/muxer_flags.cc +++ b/packager/app/muxer_flags.cc @@ -62,3 +62,20 @@ ABSL_FLAG(int32_t, "input. For example, timestamps from ISO-BMFF after adjusted by " "EditList could be negative. In transport streams, timestamps are " "not allowed to be less than zero."); +ABSL_FLAG( + int32_t, + default_text_zero_bias_ms, + 0, + "A positive value, in milliseconds. It is the threshold used to " + "determine if we should assume that the text stream actually starts " + "at time zero. If the first sample comes before default_text_zero_bias_ms, " + "then the start will be padded as the stream is assumed to start at zero. " + "If the first sample comes after default_text_zero_bias_ms then the start " + "of the stream will not be padded as we cannot assume the start time of " + "the stream."); + +ABSL_FLAG(int64_t, + start_segment_number, + 1, + "Indicates the startNumber in DASH SegmentTemplate and HLS " + "segment name."); diff --git a/packager/app/muxer_flags.h b/packager/app/muxer_flags.h index 20ee2f72b72..527ca779396 100644 --- a/packager/app/muxer_flags.h +++ b/packager/app/muxer_flags.h @@ -21,5 +21,7 @@ ABSL_DECLARE_FLAG(bool, generate_sidx_in_media_segments); ABSL_DECLARE_FLAG(std::string, temp_dir); ABSL_DECLARE_FLAG(bool, mp4_include_pssh_in_stream); ABSL_DECLARE_FLAG(int32_t, transport_stream_timestamp_offset_ms); +ABSL_DECLARE_FLAG(int32_t, default_text_zero_bias_ms); +ABSL_DECLARE_FLAG(int64_t, start_segment_number); #endif // APP_MUXER_FLAGS_H_ diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 7651a0cd01c..8e542aeef6a 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -59,6 +58,9 @@ ABSL_FLAG(bool, false, "If enabled, only use one thread when generating content."); +// From absl/log: +ABSL_DECLARE_FLAG(int, stderrthreshold); + namespace shaka { namespace { @@ -90,6 +92,10 @@ const char kUsage[] = " - output_format (format): Optional value which specifies the format\n" " of the output files (MP4 or WebM). If not specified, it will be\n" " derived from the file extension of the output file.\n" + " - input_format (format): Optional value which specifies the format\n" + " of the input files or streams. If not specified, it will be\n" + " autodetected, which in some cases (such as live UDP webvtt) may\n" + " fail.\n" " - skip_encryption=0|1: Optional. Defaults to 0 if not specified. If\n" " it is set to 1, no encryption of the stream will be made.\n" " - drm_label: Optional value for custom DRM label, which defines the\n" @@ -120,9 +126,18 @@ const char kUsage[] = " list of values for DASH Accessibility elements. The value should be\n" " in the format: scheme_id_uri=value.\n" " - dash_roles (roles): Optional semicolon separated list of values for\n" - " DASH Role elements. The value should be one of: caption, subtitle,\n" - " main, alternate, supplementary, commentary, description and dub. See\n" - " DASH (ISO/IEC 23009-1) specification for details.\n"; + " DASH Role elements. The value should be one of: caption, subtitle, \n" + " main, alternate, supplementary, commentary, dub, description, sign, \n" + " metadata, enhanced-audio- intelligibility, emergency, \n" + " forced-subtitle, easyreader, and karaoke. See DASH\n" + " (ISO/IEC 23009-1) specification for details.\n" + " - forced_subtitle: Optional boolean value (0|1). If set to 1 \n" + " indicates that this stream is a Forced Narrative subtitle that \n" + " should be displayed when subtitles are otherwise off, for example \n" + " used to caption short portions of the audio that might be in a \n" + " foreign language. For DASH this will set role to forced_subtitle, \n" + " for HLS it will set FORCED=YES and AUTOSELECT=YES. \n" + " Only valid for subtitles.\n"; // Labels for parameters in RawKey key info. const char kDrmLabelLabel[] = "label"; @@ -345,6 +360,8 @@ std::optional GetPackagingParams() { absl::GetFlag(FLAGS_segment_sap_aligned); chunking_params.subsegment_sap_aligned = absl::GetFlag(FLAGS_fragment_sap_aligned); + chunking_params.start_segment_number = + absl::GetFlag(FLAGS_start_segment_number); int num_key_providers = 0; EncryptionParams& encryption_params = packaging_params.encryption_params; @@ -461,6 +478,8 @@ std::optional GetPackagingParams() { packaging_params.transport_stream_timestamp_offset_ms = absl::GetFlag(FLAGS_transport_stream_timestamp_offset_ms); + packaging_params.default_text_zero_bias_ms = + absl::GetFlag(FLAGS_default_text_zero_bias_ms); packaging_params.output_media_info = absl::GetFlag(FLAGS_output_media_info); @@ -523,6 +542,7 @@ std::optional GetPackagingParams() { hls_params.default_text_language = absl::GetFlag(FLAGS_default_text_language); hls_params.media_sequence_number = absl::GetFlag(FLAGS_hls_media_sequence_number); + hls_params.start_time_offset = absl::GetFlag(FLAGS_hls_start_time_offset); TestParams& test_params = packaging_params.test_params; test_params.dump_stream_info = absl::GetFlag(FLAGS_dump_stream_info); @@ -546,6 +566,12 @@ int PackagerMain(int argc, char** argv) { auto usage = absl::StrFormat(kUsage, argv[0]); absl::SetProgramUsageMessage(usage); + // Before parsing the command line, change the default value of some flags + // provided by libraries. + + // Always log to stderr. Log levels are still controlled by --minloglevel. + absl::SetFlag(&FLAGS_stderrthreshold, 0); + auto remaining_args = absl::ParseCommandLine(argc, argv); if (absl::GetFlag(FLAGS_licenses)) { for (const char* line : kLicenseNotice) @@ -562,8 +588,6 @@ int PackagerMain(int argc, char** argv) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kWarning); } - handle_vlog_flags(); - absl::InitializeLog(); if (!ValidateWidevineCryptoFlags() || !ValidateRawKeyCryptoFlags() || @@ -584,6 +608,14 @@ int PackagerMain(int argc, char** argv) { return kArgumentValidationFailed; stream_descriptors.push_back(stream_descriptor.value()); } + + if (absl::GetFlag(FLAGS_force_cl_index)) { + int index = 0; + for (auto& descriptor : stream_descriptors) { + descriptor.index = index++; + } + } + Packager packager; Status status = packager.Initialize(packaging_params.value(), stream_descriptors); diff --git a/packager/app/stream_descriptor.cc b/packager/app/stream_descriptor.cc index c65c7e1256b..afcc4fcb3d7 100644 --- a/packager/app/stream_descriptor.cc +++ b/packager/app/stream_descriptor.cc @@ -39,6 +39,9 @@ enum FieldType { kDashRolesField, kDashOnlyField, kHlsOnlyField, + kDashLabelField, + kForcedSubtitleField, + kInputFormatField, }; struct FieldNameToTypeMapping { @@ -86,6 +89,9 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = { {"role", kDashRolesField}, {"dash_only", kDashOnlyField}, {"hls_only", kHlsOnlyField}, + {"dash_label", kDashLabelField}, + {"forced_subtitle", kForcedSubtitleField}, + {"input_format", kInputFormatField}, }; FieldType GetFieldType(const std::string& field_name) { @@ -250,12 +256,42 @@ std::optional ParseStreamDescriptor( } descriptor.hls_only = hls_only_value > 0; break; + case kDashLabelField: + descriptor.dash_label = pair.second; + break; + case kForcedSubtitleField: + unsigned forced_subtitle_value; + if (!absl::SimpleAtoi(pair.second, &forced_subtitle_value)) { + LOG(ERROR) << "Non-numeric option for forced field " + "specified (" + << pair.second << ")."; + return std::nullopt; + } + if (forced_subtitle_value > 1) { + LOG(ERROR) << "forced should be either 0 or 1."; + return std::nullopt; + } + descriptor.forced_subtitle = forced_subtitle_value > 0; + break; + case kInputFormatField: { + descriptor.input_format = pair.second; + break; + } default: LOG(ERROR) << "Unknown field in stream descriptor (\"" << pair.first << "\")."; return std::nullopt; } } + + if (descriptor.forced_subtitle) { + auto itr = std::find(descriptor.dash_roles.begin(), + descriptor.dash_roles.end(), "forced-subtitle"); + if (itr == descriptor.dash_roles.end()) { + descriptor.dash_roles.push_back("forced-subtitle"); + } + } + return descriptor; } diff --git a/packager/app/test/packager_app.py b/packager/app/test/packager_app.py index a8c87521594..c736749c3d0 100644 --- a/packager/app/test/packager_app.py +++ b/packager/app/test/packager_app.py @@ -17,10 +17,8 @@ class PackagerApp(object): """Main integration class for testing the packager binaries.""" def __init__(self): - self.packager_binary = os.path.join(test_env.SCRIPT_DIR, - self._GetBinaryName('packager')) - self.mpd_generator_binary = os.path.join( - test_env.SCRIPT_DIR, self._GetBinaryName('mpd_generator')) + self.packager_binary = test_env.PACKAGER_BIN + self.mpd_generator_binary = test_env.MPD_GENERATOR_BIN # Set this to empty for now in case GetCommandLine() is called before # Package(). self.packaging_command_line = '' @@ -28,15 +26,10 @@ def __init__(self): 'Please run from output directory, e.g. out/Debug/packager_test.py\n' ' Missing: ' + self.packager_binary) - def _GetBinaryName(self, name): - if platform.system() == 'Windows': - name += '.exe' - return name - def GetEnv(self): env = os.environ.copy() if (platform.system() == 'Darwin' and - test_env.options.libpackager_type == 'shared_library'): + test_env.BUILD_TYPE == 'shared'): env['DYLD_FALLBACK_LIBRARY_PATH'] = test_env.SCRIPT_DIR return env diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 1640de409f6..75508206190 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -304,12 +304,14 @@ def _GetStream(self, dash_accessibilities=None, dash_roles=None, dash_only=None, + dash_label=None, trick_play_factor=None, drm_label=None, skip_encryption=None, bandwidth=None, split_content_on_ad_cues=False, - test_file=None): + test_file=None, + forced_subtitle=None): """Get a stream descriptor as a string. @@ -334,6 +336,7 @@ def _GetStream(self, dash_accessibilities: Accessibility element for the DASH stream. dash_roles: Role element for the DASH stream. dash_only: If set to true, will indicate that the stream is for DASH only. + dash_label: Label element for the DASH stream. trick_play_factor: Signals the stream is to be used for a trick play stream and which key frames to use. A trick play factor of 0 is the same as not specifying a trick play factor. @@ -345,8 +348,9 @@ def _GetStream(self, into multiple files, with a total of NumAdCues + 1 files. test_file: The input file to use. If the input file is not specified, a default file will be used. - - + forced_subtitle: If set to true, it marks this as a Forced Narrative + subtitle, marked in DASH using forced-subtitle role and + in HLS using FORCED=YES. Returns: A string that makes up a single stream descriptor for input to the packager. @@ -400,6 +404,12 @@ def _GetStream(self, if dash_only: stream.Append('dash_only', 1) + if forced_subtitle: + stream.Append('forced_subtitle', 1) + + if dash_label: + stream.Append('dash_label', dash_label) + requires_init_segment = segmented and base_ext not in [ 'aac', 'ac3', 'ec3', 'ts', 'vtt', 'ttml', ] @@ -474,7 +484,10 @@ def _GetFlags(self, segment_duration=1.0, use_fake_clock=True, allow_codec_switching=False, - dash_force_segment_list=False): + dash_force_segment_list=False, + force_cl_index=None, + start_segment_number=None, + use_dovi_supplemental_codecs=None): flags = ['--single_threaded'] if not strip_parameter_set_nalus: @@ -532,6 +545,9 @@ def _GetFlags(self, if not dash_if_iop: flags.append('--generate_dash_if_iop_compliant_mpd=false') + if use_dovi_supplemental_codecs: + flags.append('--use_dovi_supplemental_codecs') + if output_media_info: flags.append('--output_media_info') if output_dash: @@ -558,6 +574,14 @@ def _GetFlags(self, if allow_codec_switching: flags += ['--allow_codec_switching'] + if force_cl_index is True: + flags += ['--force_cl_index'] + elif force_cl_index is False: + flags += ['--noforce_cl_index'] + + if start_segment_number is not None: + flags += ['--start_segment_number', str(start_segment_number)] + if ad_cues: flags += ['--ad_cues', ad_cues] @@ -661,7 +685,9 @@ def testVersion(self): assert_regex( self.packager.Version(), '^packager(.exe)? version ' - r'((?P[\w\.]+)-)?(?P[a-f\d]+)-(debug|release)[\r\n]+.*$') + r'((?P[A-Za-z\d\.-]+)-)?(?P[a-f\d]+)' + r'-(debug|release)[\r\n]+.*$') + def testDumpStreamInfo(self): test_file = os.path.join(self.test_data_dir, 'bear-640x360.mp4') @@ -739,7 +765,8 @@ def testAudioVideoWithTwoTrickPlay(self): self._GetStream('video', trick_play_factor=2), ] - self.assertPackageSuccess(streams, self._GetFlags(output_dash=True)) + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + force_cl_index=False)) self._CheckTestResults('audio-video-with-two-trick-play') def testAudioVideoWithTwoTrickPlayDecreasingRate(self): @@ -750,7 +777,8 @@ def testAudioVideoWithTwoTrickPlayDecreasingRate(self): self._GetStream('video', trick_play_factor=1), ] - self.assertPackageSuccess(streams, self._GetFlags(output_dash=True)) + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + force_cl_index=False)) # Since the stream descriptors are sorted in packager app, a different # order of trick play factors gets the same mpd. self._CheckTestResults('audio-video-with-two-trick-play') @@ -781,6 +809,36 @@ def testDashOnlyAndHlsOnly(self): self._GetFlags(output_dash=True, output_hls=True)) self._CheckTestResults('hls-only-dash-only') + def testDashLabel(self): + streams = [ + self._GetStream('video', dash_label='Main'), + self._GetStream('audio', dash_label='English'), + ] + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True)) + self._CheckTestResults('dash-label') + + def testForcedSubtitle(self): + streams = [ + self._GetStream('audio', hls=True), + self._GetStream('video', hls=True), + ] + + streams += self._GetStreams( + ['text'], + test_files=['bear-english.vtt'], + forced_subtitle=True) + + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + output_hls=True)) + self._CheckTestResults('forced-subtitle') + + def testDashStartNumber(self): + audio_video_streams = self._GetStreams(['audio', 'video'], segmented=True) + streams = audio_video_streams + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + start_segment_number=0)) + self._CheckTestResults('dash-start-number') + def testAudioVideoWithLanguageOverride(self): self.assertPackageSuccess( self._GetStreams(['audio', 'video'], language='por', hls=True), @@ -889,6 +947,13 @@ def testAacHe(self): self._GetFlags(output_dash=True)) self._CheckTestResults('acc-he') + def testDtsx(self): + self.assertPackageSuccess( + self._GetStreams( + ['audio'], test_files=['bear-dtsx.mp4']), + self._GetFlags(output_dash=True)) + self._CheckTestResults('dtsx-dash') + def testVideoAudioWebVTT(self): audio_video_streams = self._GetStreams(['audio', 'video']) text_stream = self._GetStreams(['text'], test_files=['bear-english.vtt']) @@ -1397,6 +1462,42 @@ def testDolbyVisionProfile8WithEncryption(self): self.assertPackageSuccess(streams, flags) self._CheckTestResults('dolby-vision-profile-8-with-encryption') + # TODO(cosmin): shared_library build does not support + # use_dovi_supplemental_codecs + @unittest.skipIf( + test_env.BUILD_TYPE == 'shared', + 'libpackager shared_library does not support ' + '--use_dovi_supplemental_codecs flag.' + ) + def testDolbyVisionProfile8UsingSupplementalCodecs(self): + streams = [ + self._GetStream('video', test_file='sparks_dovi_8.mp4') + ] + flags = self._GetFlags(output_dash=True, + output_hls=True, + use_dovi_supplemental_codecs=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('dolby-vision-profile-8-supplemental-codecs') + + # TODO(cosmin): shared_library build does not support + # use_dovi_supplemental_codecs + @unittest.skipIf( + test_env.BUILD_TYPE == 'shared', + 'libpackager shared_library does not support ' + '--use_dovi_supplemental_codecs flag.' + ) + def testDolbyVisionProfile10UsingSupplementalCodecs(self): + streams = [ + self._GetStream('video', test_file='sparks_dovi_10.mp4') + ] + flags = self._GetFlags(output_dash=True, + output_hls=True, + use_dovi_supplemental_codecs=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('dolby-vision-profile-10-supplemental-codecs') + def testVp8Mp4WithEncryption(self): streams = [ self._GetStream('video', @@ -1432,6 +1533,15 @@ def testFlacWithEncryption(self): self.assertPackageSuccess(streams, flags) self._CheckTestResults('flac-with-encryption', verify_decryption=True) + def testAlac(self): + streams = [ + self._GetStream('audio', test_file='bear-alac.mp4'), + ] + flags = self._GetFlags(output_dash=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('audio-alac') + def testAv1Mp4WithEncryption(self): self.assertPackageSuccess( self._GetStreams(['video'], test_files=['bear-av1.mp4']), @@ -1456,8 +1566,8 @@ def testWvmInput(self): # TODO(kqyang): Fix shared_library not supporting strip_parameter_set_nalus # problem. - @unittest.skipUnless( - test_env.options.libpackager_type == 'static_library', + @unittest.skipIf( + test_env.BUILD_TYPE == 'shared', 'libpackager shared_library does not support ' '--strip_parameter_set_nalus flag.' ) @@ -1552,6 +1662,13 @@ def testEc3AndHlsSingleSegmentMp4Encrypted(self): self._GetFlags(encryption=True, output_hls=True)) self._CheckTestResults('ec3-and-hls-single-segment-mp4-encrypted') + def testHlsSingleSegmentTs(self): + self.assertPackageSuccess( + self._GetStreams( + ['audio', 'video'], hls=True, test_files=['bear-640x360.ts']), + self._GetFlags(output_hls=True)) + self._CheckTestResults('hls-single-segment-ts') + def testEc3PackedAudioEncrypted(self): streams = [ self._GetStream( @@ -1629,7 +1746,9 @@ def testLiveStaticProfileWithTimeInSegmentName(self): def testAllowCodecSwitching(self): streams = [ + self._GetStream('video', test_file='bear-1280x720-hevc.mp4'), self._GetStream('video', test_file='bear-640x360-hevc.mp4'), + self._GetStream('video', test_file='bear-640x360-vp9.mp4'), self._GetStream('video', test_file='bear-640x360.mp4'), self._GetStream('video', test_file='bear-1280x720.mp4'), self._GetStream('audio', test_file='bear-640x360.mp4'), @@ -1657,6 +1776,61 @@ def testAllowCodecSwitchingWithEncryptionAndTrickplay(self): self._CheckTestResults( 'audio-video-with-codec-switching-encryption-trick-play') + def testForcedCommandlineOrdering(self): + streams = [ + self._GetStream('text', test_file='bear-english.vtt'), + self._GetStream('audio', test_file='bear-640x360.mp4'), + self._GetStream('video', test_file='bear-640x360-hevc.mp4'), + self._GetStream('video', test_file='bear-1280x720.mp4'), + self._GetStream('video', test_file='bear-640x360.mp4'), + ] + + self.assertPackageSuccess(streams, + self._GetFlags(output_dash=True, output_hls=True, + force_cl_index=True)) + self._CheckTestResults('forced-commandline-ordering') + + def testForcedCommandlineOrderingWithTTML(self): + streams = [ + self._GetStream('video', test_file='bear-640x360.mp4'), + self._GetStream('audio', test_file='bear-640x360.mp4'), + self._GetStream('text', test_file='bear-english.ttml'), + ] + + self.assertPackageSuccess(streams, + self._GetFlags(output_dash=True, output_hls=False, + force_cl_index=True)) + self._CheckTestResults('forced-commandline-ordering-ttml') + + def testAllowCodecSwitchingWithCommandlineOrdering(self): + streams = [ + self._GetStream('audio', test_file='bear-640x360.mp4'), + self._GetStream('video', test_file='bear-640x360-hevc.mp4'), + self._GetStream('video', test_file='bear-640x360.mp4'), + self._GetStream('video', test_file='bear-1280x720.mp4'), + ] + + self.assertPackageSuccess(streams, + self._GetFlags(output_dash=True, + allow_codec_switching=True, + force_cl_index=True)) + self._CheckTestResults( + 'audio-video-with-codec-switching-and-forced-commandline_order') + + def testAudioVideoWithTrickPlayAndCommandlineOrdering(self): + streams = [ + self._GetStream('audio', test_file='bear-640x360.mp4'), + self._GetStream('video', test_file='bear-640x360-hevc.mp4'), + self._GetStream('video', test_file='bear-640x360.mp4'), + self._GetStream('video', test_file='bear-1280x720.mp4', + trick_play_factor=1), + ] + + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + force_cl_index=True)) + self._CheckTestResults( + 'audio-video-with-trick-play-and-forced-commandline-order') + def testLiveProfileAndEncryption(self): self.assertPackageSuccess( self._GetStreams(['audio', 'video'], segmented=True), diff --git a/packager/app/test/test_env.py b/packager/app/test/test_env.py index 602d9b9bb39..f26a46523f7 100644 --- a/packager/app/test/test_env.py +++ b/packager/app/test/test_env.py @@ -14,21 +14,38 @@ import argparse import os +import platform import sys +def GetBinaryName(name): + if platform.system() == 'Windows': + name += '.exe' + return name + # Define static global objects and attributes. SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -SRC_DIR = os.path.join(SCRIPT_DIR, os.pardir, os.pardir) +SRC_DIR = os.environ.get('PACKAGER_SRC_DIR') +if not SRC_DIR: + # fallback to computing src dir from script dir + SRC_DIR = os.path.join(SCRIPT_DIR, os.pardir, os.pardir) + +PACKAGER_BIN = os.environ.get('PACKAGER_BIN') +if not PACKAGER_BIN: + PACKAGER_BIN = os.path.join(SCRIPT_DIR, + GetBinaryName('packager')) + +MPD_GENERATOR_BIN = os.environ.get('MPD_GENERATOR_BIN') +if not MPD_GENERATOR_BIN: + MPD_GENERATOR_BIN = os.path.join(SCRIPT_DIR, + GetBinaryName('mpd_generator')) + +BUILD_TYPE = os.environ.get('BUILD_TYPE', 'static') # Parse arguments and calculate dynamic global objects and attributes. parser = argparse.ArgumentParser() - parser.add_argument('--test_update_golden_files', action='store_true') -parser.add_argument('--libpackager_type', default='static_library', - choices=['static_library', 'shared_library']) - parser.add_argument('--v') parser.add_argument('--vmodule') # Overwrite the test to encryption key/iv specified in the command line. diff --git a/packager/app/test/testdata/acc-he/output.mpd b/packager/app/test/testdata/acc-he/output.mpd index 47df48cf4d4..e0376baa3d5 100644 --- a/packager/app/test/testdata/acc-he/output.mpd +++ b/packager/app/test/testdata/acc-he/output.mpd @@ -1,8 +1,8 @@ - + - + bear-640x360-aac_he-silent_right-audio.mp4 diff --git a/packager/app/test/testdata/audio-alac/bear-alac-audio.mp4 b/packager/app/test/testdata/audio-alac/bear-alac-audio.mp4 new file mode 100644 index 00000000000..22396432391 Binary files /dev/null and b/packager/app/test/testdata/audio-alac/bear-alac-audio.mp4 differ diff --git a/packager/app/test/testdata/audio-alac/output.mpd b/packager/app/test/testdata/audio-alac/output.mpd new file mode 100644 index 00000000000..7678c0fed6e --- /dev/null +++ b/packager/app/test/testdata/audio-alac/output.mpd @@ -0,0 +1,15 @@ + + + + + + + + bear-alac-audio.mp4 + + + + + + + diff --git a/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/output.mpd b/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/output.mpd index 0c3c3a1794a..3c01ae5ee44 100644 --- a/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/output.mpd +++ b/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/output.mpd @@ -1,19 +1,11 @@ - + - - - bear-640x360-video.mp4 - - - - - - + - + bear-640x360-audio.mp4 @@ -21,6 +13,14 @@ + + + bear-640x360-video.mp4 + + + + + diff --git a/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-1280x720-video.mp4 b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-1280x720-video.mp4 new file mode 100644 index 00000000000..eb0e007054e Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-1280x720-video.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-640x360-audio.mp4 b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-640x360-audio.mp4 new file mode 100644 index 00000000000..10077f9af8a Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-640x360-audio.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-640x360-hevc-video.mp4 b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-640x360-hevc-video.mp4 new file mode 100644 index 00000000000..9efb29c9f21 Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-640x360-hevc-video.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-640x360-video.mp4 b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-640x360-video.mp4 new file mode 100644 index 00000000000..de83807979b Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/bear-640x360-video.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/output.mpd b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/output.mpd new file mode 100644 index 00000000000..95da685a0f2 --- /dev/null +++ b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/output.mpd @@ -0,0 +1,41 @@ + + + + + + + + bear-640x360-audio.mp4 + + + + + + + + + + bear-640x360-hevc-video.mp4 + + + + + + + + + + bear-640x360-video.mp4 + + + + + + bear-1280x720-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/audio-video-with-codec-switching-encryption-trick-play/output.mpd b/packager/app/test/testdata/audio-video-with-codec-switching-encryption-trick-play/output.mpd index 05a57e70925..0519cd9ef50 100644 --- a/packager/app/test/testdata/audio-video-with-codec-switching-encryption-trick-play/output.mpd +++ b/packager/app/test/testdata/audio-video-with-codec-switching-encryption-trick-play/output.mpd @@ -1,55 +1,55 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + - - bear-1280x720-video.mp4 - - - - - - bear-640x360-video.mp4 - - + + bear-640x360-hevc-video.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-1280x720-video-trick_play_factor_1.mp4 + + + + bear-640x360-video.mp4 + + + + + + bear-1280x720-video.mp4 - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - - bear-640x360-hevc-video.mp4 - - + + + bear-1280x720-video-trick_play_factor_1.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/audio-video-with-codec-switching/bear-1280x720-hevc-video.mp4 b/packager/app/test/testdata/audio-video-with-codec-switching/bear-1280x720-hevc-video.mp4 new file mode 100644 index 00000000000..5d9af3a83b2 Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-codec-switching/bear-1280x720-hevc-video.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-codec-switching/bear-640x360-vp9-video.mp4 b/packager/app/test/testdata/audio-video-with-codec-switching/bear-640x360-vp9-video.mp4 new file mode 100644 index 00000000000..67ebd7e445f Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-codec-switching/bear-640x360-vp9-video.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-codec-switching/output.mpd b/packager/app/test/testdata/audio-video-with-codec-switching/output.mpd index 2b04331b473..d4db35ce576 100644 --- a/packager/app/test/testdata/audio-video-with-codec-switching/output.mpd +++ b/packager/app/test/testdata/audio-video-with-codec-switching/output.mpd @@ -1,35 +1,51 @@ - + - - + + - - bear-1280x720-video.mp4 - - + + bear-1280x720-hevc-video.mp4 + + - - bear-640x360-video.mp4 - - + + bear-640x360-hevc-video.mp4 + + - + - - bear-640x360-hevc-video.mp4 - - + + bear-640x360-vp9-video.mp4 + + + + + + + + + + bear-640x360-video.mp4 + + + + + + bear-1280x720-video.mp4 + + - - + + bear-640x360-audio.mp4 diff --git a/packager/app/test/testdata/audio-video-with-language-override-with-subtag/output.mpd b/packager/app/test/testdata/audio-video-with-language-override-with-subtag/output.mpd index 84354aa26a8..00bec7e2159 100644 --- a/packager/app/test/testdata/audio-video-with-language-override-with-subtag/output.mpd +++ b/packager/app/test/testdata/audio-video-with-language-override-with-subtag/output.mpd @@ -1,17 +1,9 @@ - + - - - bear-640x360-video.mp4 - - - - - - - + + bear-640x360-audio.mp4 @@ -19,5 +11,13 @@ + + + bear-640x360-video.mp4 + + + + + diff --git a/packager/app/test/testdata/audio-video-with-language-override/output.mpd b/packager/app/test/testdata/audio-video-with-language-override/output.mpd index e32fae08d68..c75de98f00c 100644 --- a/packager/app/test/testdata/audio-video-with-language-override/output.mpd +++ b/packager/app/test/testdata/audio-video-with-language-override/output.mpd @@ -1,18 +1,10 @@ - + - - - bear-640x360-video.mp4 - - - - - - + - + bear-640x360-audio.mp4 @@ -20,5 +12,13 @@ + + + bear-640x360-video.mp4 + + + + + diff --git a/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-1280x720-video-trick_play_factor_1.mp4 b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-1280x720-video-trick_play_factor_1.mp4 new file mode 100644 index 00000000000..8690215ac26 Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-1280x720-video-trick_play_factor_1.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-640x360-audio.mp4 b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-640x360-audio.mp4 new file mode 100644 index 00000000000..10077f9af8a Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-640x360-audio.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-640x360-hevc-video.mp4 b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-640x360-hevc-video.mp4 new file mode 100644 index 00000000000..9efb29c9f21 Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-640x360-hevc-video.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-640x360-video.mp4 b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-640x360-video.mp4 new file mode 100644 index 00000000000..de83807979b Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/bear-640x360-video.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/output.mpd b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/output.mpd new file mode 100644 index 00000000000..95cb7de42c0 --- /dev/null +++ b/packager/app/test/testdata/audio-video-with-trick-play-and-forced-commandline-order/output.mpd @@ -0,0 +1,40 @@ + + + + + + + + bear-640x360-audio.mp4 + + + + + + + + bear-640x360-hevc-video.mp4 + + + + + + + + bear-640x360-video.mp4 + + + + + + + + + bear-1280x720-video-trick_play_factor_1.mp4 + + + + + + + diff --git a/packager/app/test/testdata/audio-video-with-trick-play/output.mpd b/packager/app/test/testdata/audio-video-with-trick-play/output.mpd index 1c4090e7851..00f3fe96fee 100644 --- a/packager/app/test/testdata/audio-video-with-trick-play/output.mpd +++ b/packager/app/test/testdata/audio-video-with-trick-play/output.mpd @@ -1,32 +1,32 @@ - + - - + + + + bear-640x360-audio.mp4 + + + + + + + bear-640x360-video.mp4 - + - + bear-640x360-video-trick_play_factor_1.mp4 - - - - bear-640x360-audio.mp4 - - - - - diff --git a/packager/app/test/testdata/audio-video-with-two-trick-play/output.mpd b/packager/app/test/testdata/audio-video-with-two-trick-play/output.mpd index 1a1f6e252a9..bee30c7943a 100644 --- a/packager/app/test/testdata/audio-video-with-two-trick-play/output.mpd +++ b/packager/app/test/testdata/audio-video-with-two-trick-play/output.mpd @@ -1,6 +1,6 @@ - + @@ -10,7 +10,7 @@ - + bear-640x360-video-trick_play_factor_1.mp4 @@ -18,14 +18,14 @@ - + bear-640x360-video-trick_play_factor_2.mp4 - + bear-640x360-audio.mp4 diff --git a/packager/app/test/testdata/audio-video/output.mpd b/packager/app/test/testdata/audio-video/output.mpd index f2a01183022..fe3119bb05d 100644 --- a/packager/app/test/testdata/audio-video/output.mpd +++ b/packager/app/test/testdata/audio-video/output.mpd @@ -1,17 +1,9 @@ - + - - - bear-640x360-video.mp4 - - - - - - - + + bear-640x360-audio.mp4 @@ -19,5 +11,13 @@ + + + bear-640x360-video.mp4 + + + + + diff --git a/packager/app/test/testdata/av1-mp4-to-webm/bear-av1-video.webm b/packager/app/test/testdata/av1-mp4-to-webm/bear-av1-video.webm index 516d346448f..b7c2be7c03b 100644 Binary files a/packager/app/test/testdata/av1-mp4-to-webm/bear-av1-video.webm and b/packager/app/test/testdata/av1-mp4-to-webm/bear-av1-video.webm differ diff --git a/packager/app/test/testdata/av1-mp4-to-webm/output.mpd b/packager/app/test/testdata/av1-mp4-to-webm/output.mpd index ac1a512117a..7b01b5e892d 100644 --- a/packager/app/test/testdata/av1-mp4-to-webm/output.mpd +++ b/packager/app/test/testdata/av1-mp4-to-webm/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/av1-mp4-with-encryption/output.mpd b/packager/app/test/testdata/av1-mp4-with-encryption/output.mpd index 1645e8b8f77..429f4a4d220 100644 --- a/packager/app/test/testdata/av1-mp4-with-encryption/output.mpd +++ b/packager/app/test/testdata/av1-mp4-with-encryption/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/av1-mp4/output.mpd b/packager/app/test/testdata/av1-mp4/output.mpd index 4dae182162e..22b7fced3a7 100644 --- a/packager/app/test/testdata/av1-mp4/output.mpd +++ b/packager/app/test/testdata/av1-mp4/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/av1-webm-with-encryption/bear-av1-video.webm b/packager/app/test/testdata/av1-webm-with-encryption/bear-av1-video.webm index 71de3b4da34..02d57091636 100644 Binary files a/packager/app/test/testdata/av1-webm-with-encryption/bear-av1-video.webm and b/packager/app/test/testdata/av1-webm-with-encryption/bear-av1-video.webm differ diff --git a/packager/app/test/testdata/av1-webm-with-encryption/decrypted-bear-av1-video-0.webm b/packager/app/test/testdata/av1-webm-with-encryption/decrypted-bear-av1-video-0.webm index f03d3d08efe..54a4b609449 100644 Binary files a/packager/app/test/testdata/av1-webm-with-encryption/decrypted-bear-av1-video-0.webm and b/packager/app/test/testdata/av1-webm-with-encryption/decrypted-bear-av1-video-0.webm differ diff --git a/packager/app/test/testdata/av1-webm-with-encryption/output.mpd b/packager/app/test/testdata/av1-webm-with-encryption/output.mpd index 55e9281e083..ffc8aa5564f 100644 --- a/packager/app/test/testdata/av1-webm-with-encryption/output.mpd +++ b/packager/app/test/testdata/av1-webm-with-encryption/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/av1-webm/output.mpd b/packager/app/test/testdata/av1-webm/output.mpd index d1fbb073746..753d70c3d15 100644 --- a/packager/app/test/testdata/av1-webm/output.mpd +++ b/packager/app/test/testdata/av1-webm/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/avc-aac-ts/output.mpd b/packager/app/test/testdata/avc-aac-ts/output.mpd index 268efb85f1d..a2384645445 100644 --- a/packager/app/test/testdata/avc-aac-ts/output.mpd +++ b/packager/app/test/testdata/avc-aac-ts/output.mpd @@ -2,18 +2,8 @@ - - - - - - - - - - - - + + @@ -24,5 +14,15 @@ + + + + + + + + + + diff --git a/packager/app/test/testdata/avc-ts-live-playlist-dash-dynamic-with-segment-deletion/output.mpd b/packager/app/test/testdata/avc-ts-live-playlist-dash-dynamic-with-segment-deletion/output.mpd index 6a17a94f216..762fd7c69b5 100644 --- a/packager/app/test/testdata/avc-ts-live-playlist-dash-dynamic-with-segment-deletion/output.mpd +++ b/packager/app/test/testdata/avc-ts-live-playlist-dash-dynamic-with-segment-deletion/output.mpd @@ -1,8 +1,8 @@ - + - + diff --git a/packager/app/test/testdata/bandwidth-override/output.mpd b/packager/app/test/testdata/bandwidth-override/output.mpd index 1bdaf75b54e..21e779f058a 100644 --- a/packager/app/test/testdata/bandwidth-override/output.mpd +++ b/packager/app/test/testdata/bandwidth-override/output.mpd @@ -1,17 +1,9 @@ - + - - - bear-640x360-video.mp4 - - - - - - - + + bear-640x360-audio.mp4 @@ -19,5 +11,13 @@ + + + bear-640x360-video.mp4 + + + + + diff --git a/packager/app/test/testdata/bear-640x360-av-cenc-golden.mpd b/packager/app/test/testdata/bear-640x360-av-cenc-golden.mpd index 2003c002390..06e6e251efb 100644 --- a/packager/app/test/testdata/bear-640x360-av-cenc-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-cenc-golden.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/dash-label/bear-640x360-audio.mp4 b/packager/app/test/testdata/dash-label/bear-640x360-audio.mp4 new file mode 100644 index 00000000000..10077f9af8a Binary files /dev/null and b/packager/app/test/testdata/dash-label/bear-640x360-audio.mp4 differ diff --git a/packager/app/test/testdata/dash-label/bear-640x360-video.mp4 b/packager/app/test/testdata/dash-label/bear-640x360-video.mp4 new file mode 100644 index 00000000000..de83807979b Binary files /dev/null and b/packager/app/test/testdata/dash-label/bear-640x360-video.mp4 differ diff --git a/packager/app/test/testdata/dash-label/output.mpd b/packager/app/test/testdata/dash-label/output.mpd new file mode 100644 index 00000000000..7bcd23108f5 --- /dev/null +++ b/packager/app/test/testdata/dash-label/output.mpd @@ -0,0 +1,25 @@ + + + + + + + + bear-640x360-video.mp4 + + + + + + + + + + bear-640x360-audio.mp4 + + + + + + + diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-0.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-0.m4s new file mode 100644 index 00000000000..6e56dd8555c Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-0.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-1.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-1.m4s new file mode 100644 index 00000000000..5bf76aa0d1a Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-1.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-2.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-2.m4s new file mode 100644 index 00000000000..d6bcbb5de8c Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-2.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-init.mp4 b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-init.mp4 new file mode 100644 index 00000000000..3176a92d7ff Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-init.mp4 differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-0.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-video-0.m4s new file mode 100644 index 00000000000..2c90c468835 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-0.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-1.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-video-1.m4s new file mode 100644 index 00000000000..c6b356a8e91 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-1.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-2.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-video-2.m4s new file mode 100644 index 00000000000..4edeba974ee Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-2.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-init.mp4 b/packager/app/test/testdata/dash-start-number/bear-640x360-video-init.mp4 new file mode 100644 index 00000000000..874b16341f7 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-init.mp4 differ diff --git a/packager/app/test/testdata/dash-start-number/output.mpd b/packager/app/test/testdata/dash-start-number/output.mpd new file mode 100644 index 00000000000..81965d0568d --- /dev/null +++ b/packager/app/test/testdata/dash-start-number/output.mpd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packager/app/test/testdata/dash-with-bandwidth-override/output.mpd b/packager/app/test/testdata/dash-with-bandwidth-override/output.mpd index db3c0e3d32f..8ca00770b01 100644 --- a/packager/app/test/testdata/dash-with-bandwidth-override/output.mpd +++ b/packager/app/test/testdata/dash-with-bandwidth-override/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.m3u8 b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.m3u8 new file mode 100644 index 00000000000..d9bf5154cca --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.m3u8 @@ -0,0 +1,7 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-STREAM-INF:BANDWIDTH=550702,AVERAGE-BANDWIDTH=577484,CODECS="av01.0.04M.10.0.111.09.16.09.0",SUPPLEMENTAL-CODECS="dav1.10.01/db1p",RESOLUTION=640x360,FRAME-RATE=59.940,VIDEO-RANGE=PQ,CLOSED-CAPTIONS=NONE +stream_0.m3u8 diff --git a/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.mpd b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.mpd new file mode 100644 index 00000000000..2a43ee4e413 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.mpd @@ -0,0 +1,17 @@ + + + + + + + + + + sparks_dovi_10-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/sparks_dovi_10-video.mp4 b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/sparks_dovi_10-video.mp4 new file mode 100644 index 00000000000..b4cd29e4ab8 Binary files /dev/null and b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/sparks_dovi_10-video.mp4 differ diff --git a/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/stream_0.m3u8 b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/stream_0.m3u8 new file mode 100644 index 00000000000..06ba5a56fc1 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/stream_0.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:6 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="sparks_dovi_10-video.mp4",BYTERANGE="871@0" +#EXTINF:5.355, +#EXT-X-BYTERANGE:368650@927 +sparks_dovi_10-video.mp4 +#EXTINF:0.667, +#EXT-X-BYTERANGE:66100 +sparks_dovi_10-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd b/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd index 9f02235c0b7..da522e3591e 100644 --- a/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd +++ b/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd @@ -1,8 +1,10 @@ - + + + diff --git a/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.m3u8 b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.m3u8 new file mode 100644 index 00000000000..c18631c4c68 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.m3u8 @@ -0,0 +1,7 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-STREAM-INF:BANDWIDTH=807837,AVERAGE-BANDWIDTH=748074,CODECS="hvc1.2.4.L90.90",SUPPLEMENTAL-CODECS="dvh1.08.01/db2g",RESOLUTION=640x360,FRAME-RATE=59.940,VIDEO-RANGE=PQ,CLOSED-CAPTIONS=NONE +stream_0.m3u8 diff --git a/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.mpd b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.mpd new file mode 100644 index 00000000000..5d06e41c784 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.mpd @@ -0,0 +1,17 @@ + + + + + + + + + + sparks_dovi_8-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/sparks_dovi_8-video.mp4 b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/sparks_dovi_8-video.mp4 new file mode 100644 index 00000000000..ce05290a78d Binary files /dev/null and b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/sparks_dovi_8-video.mp4 differ diff --git a/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/stream_0.m3u8 b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/stream_0.m3u8 new file mode 100644 index 00000000000..6b90ac6a7e6 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/stream_0.m3u8 @@ -0,0 +1,19 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="sparks_dovi_8-video.mp4",BYTERANGE="992@0" +#EXTINF:2.002, +#EXT-X-BYTERANGE:172013@1072 +sparks_dovi_8-video.mp4 +#EXTINF:2.002, +#EXT-X-BYTERANGE:186781 +sparks_dovi_8-video.mp4 +#EXTINF:2.002, +#EXT-X-BYTERANGE:202161 +sparks_dovi_8-video.mp4 +#EXTINF:0.017, +#EXT-X-BYTERANGE:2221 +sparks_dovi_8-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd b/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd index 04c010c0081..7f5da8fad93 100644 --- a/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd +++ b/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd @@ -1,8 +1,10 @@ - + + + @@ -16,12 +18,14 @@ + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + sparks_dovi_8-video.mp4 diff --git a/packager/app/test/testdata/dtsx-dash/bear-dtsx-audio.mp4 b/packager/app/test/testdata/dtsx-dash/bear-dtsx-audio.mp4 new file mode 100644 index 00000000000..8b0bbd767e3 Binary files /dev/null and b/packager/app/test/testdata/dtsx-dash/bear-dtsx-audio.mp4 differ diff --git a/packager/app/test/testdata/dtsx-dash/output.mpd b/packager/app/test/testdata/dtsx-dash/output.mpd new file mode 100644 index 00000000000..8677077d25e --- /dev/null +++ b/packager/app/test/testdata/dtsx-dash/output.mpd @@ -0,0 +1,15 @@ + + + + + + + + bear-dtsx-audio.mp4 + + + + + + + diff --git a/packager/app/test/testdata/ec3-and-hls-single-segment-mp4-encrypted/output.m3u8 b/packager/app/test/testdata/ec3-and-hls-single-segment-mp4-encrypted/output.m3u8 index 7ec849768fe..5db89137c52 100644 --- a/packager/app/test/testdata/ec3-and-hls-single-segment-mp4-encrypted/output.m3u8 +++ b/packager/app/test/testdata/ec3-and-hls-single-segment-mp4-encrypted/output.m3u8 @@ -5,7 +5,7 @@ #EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-ec3-audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2" -#EXT-X-STREAM-INF:BANDWIDTH=1174214,AVERAGE-BANDWIDTH=1061802,CODECS="avc1.64001e,ec-3",RESOLUTION=640x360,FRAME-RATE=9.990,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE +#EXT-X-STREAM-INF:BANDWIDTH=1174214,AVERAGE-BANDWIDTH=1061802,CODECS="avc1.64001e,ec-3",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE bear-640x360-ec3-video.m3u8 #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=218705,AVERAGE-BANDWIDTH=159315,CODECS="avc1.64001e",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,URI="bear-640x360-ec3-video-iframe.m3u8" diff --git a/packager/app/test/testdata/encryption-and-ad-cues-and-dash-trick-play/output.mpd b/packager/app/test/testdata/encryption-and-ad-cues-and-dash-trick-play/output.mpd index fee260794ee..0ec3c23e7c0 100644 --- a/packager/app/test/testdata/encryption-and-ad-cues-and-dash-trick-play/output.mpd +++ b/packager/app/test/testdata/encryption-and-ad-cues-and-dash-trick-play/output.mpd @@ -1,8 +1,8 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== @@ -41,8 +41,8 @@ - - + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/encryption-and-ad-cues-split-content/output.mpd b/packager/app/test/testdata/encryption-and-ad-cues-split-content/output.mpd index 0b0a577c87b..74006ed7b49 100644 --- a/packager/app/test/testdata/encryption-and-ad-cues-split-content/output.mpd +++ b/packager/app/test/testdata/encryption-and-ad-cues-split-content/output.mpd @@ -1,25 +1,13 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video1.mp4 - - - - - - - - - AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - + bear-640x360-audio1.mp4 @@ -27,26 +15,26 @@ - - - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video2.mp4 - + + bear-640x360-video1.mp4 + - + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + bear-640x360-audio2.mp4 @@ -54,5 +42,17 @@ + + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== + + + bear-640x360-video2.mp4 + + + + + diff --git a/packager/app/test/testdata/encryption-and-ad-cues/output.mpd b/packager/app/test/testdata/encryption-and-ad-cues/output.mpd index 04d51ffce26..50050924c61 100644 --- a/packager/app/test/testdata/encryption-and-ad-cues/output.mpd +++ b/packager/app/test/testdata/encryption-and-ad-cues/output.mpd @@ -1,8 +1,8 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== @@ -28,8 +28,8 @@ - - + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/encryption-and-no-clear-lead/output.mpd b/packager/app/test/testdata/encryption-and-no-clear-lead/output.mpd index 5df9fff7312..99574347e4d 100644 --- a/packager/app/test/testdata/encryption-and-no-clear-lead/output.mpd +++ b/packager/app/test/testdata/encryption-and-no-clear-lead/output.mpd @@ -1,29 +1,29 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-and-no-pssh-in-stream/output.mpd b/packager/app/test/testdata/encryption-and-no-pssh-in-stream/output.mpd index d65a05a8cca..377e3198f84 100644 --- a/packager/app/test/testdata/encryption-and-no-pssh-in-stream/output.mpd +++ b/packager/app/test/testdata/encryption-and-no-pssh-in-stream/output.mpd @@ -1,29 +1,29 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-and-non-dash-if-iop/output.mpd b/packager/app/test/testdata/encryption-and-non-dash-if-iop/output.mpd index 59a9a506cdf..9b0a75df762 100644 --- a/packager/app/test/testdata/encryption-and-non-dash-if-iop/output.mpd +++ b/packager/app/test/testdata/encryption-and-non-dash-if-iop/output.mpd @@ -1,29 +1,29 @@ - + - - + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - bear-640x360-video.mp4 - - + bear-640x360-audio.mp4 + + - - - + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - bear-640x360-audio.mp4 - - + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info-segmentlist/output.mpd b/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info-segmentlist/output.mpd index 10b8be623f5..26636f0451c 100644 --- a/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info-segmentlist/output.mpd +++ b/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info-segmentlist/output.mpd @@ -1,8 +1,8 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/bear-640x360-video.mp4.media_info b/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/bear-640x360-video.mp4.media_info index f13a0e9fa89..e9054215f7d 100644 --- a/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/bear-640x360-video.mp4.media_info +++ b/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/bear-640x360-video.mp4.media_info @@ -8,6 +8,8 @@ video_info { decoder_config: "\001d\000\036\377\341\000\031gd\000\036\254\331@\240/\371p\021\000\000\003\003\351\000\000\352`\017\026-\226\001\000\006h\353\343\313\"\300" pixel_width: 1 pixel_height: 1 + supplemental_codec: "" + compatible_brand: 0 } init_range { begin: 0 diff --git a/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/output.mpd b/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/output.mpd index f0acf21b9e1..6f032fcfa85 100644 --- a/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/output.mpd +++ b/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/encryption-and-output-media-info/bear-640x360-video.mp4.media_info b/packager/app/test/testdata/encryption-and-output-media-info/bear-640x360-video.mp4.media_info index f13a0e9fa89..e9054215f7d 100644 --- a/packager/app/test/testdata/encryption-and-output-media-info/bear-640x360-video.mp4.media_info +++ b/packager/app/test/testdata/encryption-and-output-media-info/bear-640x360-video.mp4.media_info @@ -8,6 +8,8 @@ video_info { decoder_config: "\001d\000\036\377\341\000\031gd\000\036\254\331@\240/\371p\021\000\000\003\003\351\000\000\352`\017\026-\226\001\000\006h\353\343\313\"\300" pixel_width: 1 pixel_height: 1 + supplemental_codec: "" + compatible_brand: 0 } init_range { begin: 0 diff --git a/packager/app/test/testdata/encryption-and-trick-play/output.mpd b/packager/app/test/testdata/encryption-and-trick-play/output.mpd index 0c89c0ffffa..5c00589ac23 100644 --- a/packager/app/test/testdata/encryption-and-trick-play/output.mpd +++ b/packager/app/test/testdata/encryption-and-trick-play/output.mpd @@ -1,42 +1,42 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-video-trick_play_factor_1.mp4 + + bear-640x360-video.mp4 - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-audio.mp4 - - + + + bear-640x360-video-trick_play_factor_1.mp4 + + diff --git a/packager/app/test/testdata/encryption-and-two-trick-plays/output.mpd b/packager/app/test/testdata/encryption-and-two-trick-plays/output.mpd index 7af4b85e327..910f1375c72 100644 --- a/packager/app/test/testdata/encryption-and-two-trick-plays/output.mpd +++ b/packager/app/test/testdata/encryption-and-two-trick-plays/output.mpd @@ -1,50 +1,50 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + + + bear-640x360-audio.mp4 + + + + + + + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== + + bear-640x360-video.mp4 - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + bear-640x360-video-trick_play_factor_1.mp4 - + bear-640x360-video-trick_play_factor_2.mp4 - - - - AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - - bear-640x360-audio.mp4 - - - - - diff --git a/packager/app/test/testdata/encryption-cbc-1/output.mpd b/packager/app/test/testdata/encryption-cbc-1/output.mpd index 102dea96669..1f73d8372ff 100644 --- a/packager/app/test/testdata/encryption-cbc-1/output.mpd +++ b/packager/app/test/testdata/encryption-cbc-1/output.mpd @@ -1,29 +1,29 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-cbcs-with-full-protection/output.mpd b/packager/app/test/testdata/encryption-cbcs-with-full-protection/output.mpd index c66e3390534..bd2d40a5600 100644 --- a/packager/app/test/testdata/encryption-cbcs-with-full-protection/output.mpd +++ b/packager/app/test/testdata/encryption-cbcs-with-full-protection/output.mpd @@ -1,29 +1,29 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-cbcs/output.mpd b/packager/app/test/testdata/encryption-cbcs/output.mpd index c66e3390534..bd2d40a5600 100644 --- a/packager/app/test/testdata/encryption-cbcs/output.mpd +++ b/packager/app/test/testdata/encryption-cbcs/output.mpd @@ -1,29 +1,29 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-cens/output.mpd b/packager/app/test/testdata/encryption-cens/output.mpd index ec298fdb9a5..d73a8c7636b 100644 --- a/packager/app/test/testdata/encryption-cens/output.mpd +++ b/packager/app/test/testdata/encryption-cens/output.mpd @@ -1,29 +1,29 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-multi-keys-with-stream-label/output.mpd b/packager/app/test/testdata/encryption-multi-keys-with-stream-label/output.mpd index 2d75e36fd32..eb7f66b3903 100644 --- a/packager/app/test/testdata/encryption-multi-keys-with-stream-label/output.mpd +++ b/packager/app/test/testdata/encryption-multi-keys-with-stream-label/output.mpd @@ -1,29 +1,29 @@ - + - - + + AAAARHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAIQERITFBUWFxgZICEiIyQlICEiIyQlJicoKTAxMjM0NQAAAAA= - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - - + + AAAARHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAIQERITFBUWFxgZICEiIyQlICEiIyQlJicoKTAxMjM0NQAAAAA= - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-multi-keys/output.mpd b/packager/app/test/testdata/encryption-multi-keys/output.mpd index f198fdf1c26..a11a0e0d493 100644 --- a/packager/app/test/testdata/encryption-multi-keys/output.mpd +++ b/packager/app/test/testdata/encryption-multi-keys/output.mpd @@ -1,29 +1,29 @@ - + - - + + AAAARHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAIQERITFBUWFxgZICEiIyQlICEiIyQlJicoKTAxMjM0NQAAAAA= - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - - + + AAAARHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAIQERITFBUWFxgZICEiIyQlICEiIyQlJicoKTAxMjM0NQAAAAA= - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-of-only-video-stream/output.mpd b/packager/app/test/testdata/encryption-of-only-video-stream/output.mpd index a979523d642..ad04e1a1171 100644 --- a/packager/app/test/testdata/encryption-of-only-video-stream/output.mpd +++ b/packager/app/test/testdata/encryption-of-only-video-stream/output.mpd @@ -1,27 +1,27 @@ - + - + + + + bear-640x360-audio-skip_encryption.mp4 + + + + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + bear-640x360-video.mp4 - - - - bear-640x360-audio-skip_encryption.mp4 - - - - - diff --git a/packager/app/test/testdata/encryption-using-explicit-pssh/output.mpd b/packager/app/test/testdata/encryption-using-explicit-pssh/output.mpd index fcc323a89a0..4e0cddb5070 100644 --- a/packager/app/test/testdata/encryption-using-explicit-pssh/output.mpd +++ b/packager/app/test/testdata/encryption-using-explicit-pssh/output.mpd @@ -1,29 +1,29 @@ - + - + AAAAIHBzc2gAAAAAEHfv7MCyTQKs4zweUuL7SwAAAAA= - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAAIHBzc2gAAAAAEHfv7MCyTQKs4zweUuL7SwAAAAA= - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-using-fixed-key/output.mpd b/packager/app/test/testdata/encryption-using-fixed-key/output.mpd index 04aa865d887..53a9ffb7595 100644 --- a/packager/app/test/testdata/encryption-using-fixed-key/output.mpd +++ b/packager/app/test/testdata/encryption-using-fixed-key/output.mpd @@ -1,29 +1,29 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-with-multi-drms/output.mpd b/packager/app/test/testdata/encryption-with-multi-drms/output.mpd index c9e0362357c..9ec14689306 100644 --- a/packager/app/test/testdata/encryption-with-multi-drms/output.mpd +++ b/packager/app/test/testdata/encryption-with-multi-drms/output.mpd @@ -1,8 +1,8 @@ - + - + AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBOAEQATQB5AE0AVABZADEATwBEAGMANQBNAEQARQB5AE0AegBRADEATgBnAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AGwANQBMAG8AVQBnAEsAOQBLAEMAZwA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== @@ -16,14 +16,15 @@ urn:marlin:kid:31323334353637383930313233343536 - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBOAEQATQB5AE0AVABZADEATwBEAGMANQBNAEQARQB5AE0AegBRADEATgBnAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AGwANQBMAG8AVQBnAEsAOQBLAEMAZwA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== @@ -37,11 +38,10 @@ urn:marlin:kid:31323334353637383930313233343536 - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/encryption-with-multi-drms/stream_0.m3u8 b/packager/app/test/testdata/encryption-with-multi-drms/stream_0.m3u8 index 1eecba5c115..5f5caf3af6f 100644 --- a/packager/app/test/testdata/encryption-with-multi-drms/stream_0.m3u8 +++ b/packager/app/test/testdata/encryption-with-multi-drms/stream_0.m3u8 @@ -4,6 +4,7 @@ #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-MAP:URI="bear-640x360-audio.mp4",BYTERANGE="1568@0" +#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;charset=UTF-16;base64,BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATgBEAE0AeQBNAFQAWQAxAE8ARABjADUATQBEAEUAeQBNAHoAUQAxAE4AZwA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBsADUATABvAFUAZwBLADkASwBDAGcAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=",KEYFORMATVERSIONS="1",KEYFORMAT="com.microsoft.playready" #EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY=",KEYID=0x31323334353637383930313233343536,KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" #EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="skd://www.license.com/getkey?KeyId=31323334-3536-3738-3930-313233343536",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery" #EXTINF:1.022, diff --git a/packager/app/test/testdata/encryption-with-multi-drms/stream_1.m3u8 b/packager/app/test/testdata/encryption-with-multi-drms/stream_1.m3u8 index 6b261d7f392..443fdfa39a7 100644 --- a/packager/app/test/testdata/encryption-with-multi-drms/stream_1.m3u8 +++ b/packager/app/test/testdata/encryption-with-multi-drms/stream_1.m3u8 @@ -4,6 +4,7 @@ #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="1692@0" +#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;charset=UTF-16;base64,BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATgBEAE0AeQBNAFQAWQAxAE8ARABjADUATQBEAEUAeQBNAHoAUQAxAE4AZwA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBsADUATABvAFUAZwBLADkASwBDAGcAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=",KEYFORMATVERSIONS="1",KEYFORMAT="com.microsoft.playready" #EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY=",KEYID=0x31323334353637383930313233343536,KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" #EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="skd://www.license.com/getkey?KeyId=31323334-3536-3738-3930-313233343536",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery" #EXTINF:1.001, diff --git a/packager/app/test/testdata/encryption/output.mpd b/packager/app/test/testdata/encryption/output.mpd index 04aa865d887..53a9ffb7595 100644 --- a/packager/app/test/testdata/encryption/output.mpd +++ b/packager/app/test/testdata/encryption/output.mpd @@ -1,29 +1,29 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - bear-640x360-video.mp4 - - + + + bear-640x360-audio.mp4 + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - bear-640x360-audio.mp4 - - + + bear-640x360-video.mp4 + + diff --git a/packager/app/test/testdata/first-stream/output.mpd b/packager/app/test/testdata/first-stream/output.mpd index 403ab3a0070..4308b2e51c7 100644 --- a/packager/app/test/testdata/first-stream/output.mpd +++ b/packager/app/test/testdata/first-stream/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/flac-with-encryption/output.mpd b/packager/app/test/testdata/flac-with-encryption/output.mpd index dc59de5862d..00f26890295 100644 --- a/packager/app/test/testdata/flac-with-encryption/output.mpd +++ b/packager/app/test/testdata/flac-with-encryption/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/forced-commandline-ordering-ttml/bear-640x360-audio.mp4 b/packager/app/test/testdata/forced-commandline-ordering-ttml/bear-640x360-audio.mp4 new file mode 100644 index 00000000000..10077f9af8a Binary files /dev/null and b/packager/app/test/testdata/forced-commandline-ordering-ttml/bear-640x360-audio.mp4 differ diff --git a/packager/app/test/testdata/forced-commandline-ordering-ttml/bear-640x360-video.mp4 b/packager/app/test/testdata/forced-commandline-ordering-ttml/bear-640x360-video.mp4 new file mode 100644 index 00000000000..de83807979b Binary files /dev/null and b/packager/app/test/testdata/forced-commandline-ordering-ttml/bear-640x360-video.mp4 differ diff --git a/packager/app/test/testdata/forced-commandline-ordering-ttml/bear-english-text.ttml b/packager/app/test/testdata/forced-commandline-ordering-ttml/bear-english-text.ttml new file mode 100644 index 00000000000..978056a2ab2 --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering-ttml/bear-english-text.ttml @@ -0,0 +1,9 @@ + + + +
+

Yup, that's a bear, eh.

+

He 's... um... doing bear-like stuff.

+
+ +
diff --git a/packager/app/test/testdata/forced-commandline-ordering-ttml/output.mpd b/packager/app/test/testdata/forced-commandline-ordering-ttml/output.mpd new file mode 100644 index 00000000000..b53225c8684 --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering-ttml/output.mpd @@ -0,0 +1,28 @@ + + + + + + + bear-640x360-video.mp4 + + + + + + + + + bear-640x360-audio.mp4 + + + + + + + + bear-english-text.ttml + + + + diff --git a/packager/app/test/testdata/forced-commandline-ordering/bear-1280x720-video.mp4 b/packager/app/test/testdata/forced-commandline-ordering/bear-1280x720-video.mp4 new file mode 100644 index 00000000000..eb0e007054e Binary files /dev/null and b/packager/app/test/testdata/forced-commandline-ordering/bear-1280x720-video.mp4 differ diff --git a/packager/app/test/testdata/forced-commandline-ordering/bear-640x360-audio.mp4 b/packager/app/test/testdata/forced-commandline-ordering/bear-640x360-audio.mp4 new file mode 100644 index 00000000000..10077f9af8a Binary files /dev/null and b/packager/app/test/testdata/forced-commandline-ordering/bear-640x360-audio.mp4 differ diff --git a/packager/app/test/testdata/forced-commandline-ordering/bear-640x360-hevc-video.mp4 b/packager/app/test/testdata/forced-commandline-ordering/bear-640x360-hevc-video.mp4 new file mode 100644 index 00000000000..9efb29c9f21 Binary files /dev/null and b/packager/app/test/testdata/forced-commandline-ordering/bear-640x360-hevc-video.mp4 differ diff --git a/packager/app/test/testdata/forced-commandline-ordering/bear-640x360-video.mp4 b/packager/app/test/testdata/forced-commandline-ordering/bear-640x360-video.mp4 new file mode 100644 index 00000000000..de83807979b Binary files /dev/null and b/packager/app/test/testdata/forced-commandline-ordering/bear-640x360-video.mp4 differ diff --git a/packager/app/test/testdata/forced-commandline-ordering/bear-english-text.vtt b/packager/app/test/testdata/forced-commandline-ordering/bear-english-text.vtt new file mode 100644 index 00000000000..18ae752fe8e --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering/bear-english-text.vtt @@ -0,0 +1,11 @@ +WEBVTT + +STYLE +::cue { color:lime } + +00:00:00.000 --> 00:00:00.800 align:center +Yup, that's a bear, eh. + +00:00:01.000 --> 00:00:04.700 align:center +He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/forced-commandline-ordering/output.m3u8 b/packager/app/test/testdata/forced-commandline-ordering/output.m3u8 new file mode 100644 index 00000000000..43698a91dda --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering/output.m3u8 @@ -0,0 +1,15 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_2.m3u8",GROUP-ID="default-audio-group",NAME="stream_2",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2" + +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_4.m3u8",GROUP-ID="default-text-group",NAME="stream_4",DEFAULT=NO,AUTOSELECT=YES + +#EXT-X-STREAM-INF:BANDWIDTH=410745,AVERAGE-BANDWIDTH=378029,CODECS="hvc1.1.6.L63.90,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",SUBTITLES="default-text-group",CLOSED-CAPTIONS=NONE +stream_1.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2760619,AVERAGE-BANDWIDTH=2511928,CODECS="avc1.64001f,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=29.970,AUDIO="default-audio-group",SUBTITLES="default-text-group",CLOSED-CAPTIONS=NONE +stream_0.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=1106817,AVERAGE-BANDWIDTH=1004632,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",SUBTITLES="default-text-group",CLOSED-CAPTIONS=NONE +stream_3.m3u8 diff --git a/packager/app/test/testdata/forced-commandline-ordering/output.mpd b/packager/app/test/testdata/forced-commandline-ordering/output.mpd new file mode 100644 index 00000000000..8e1683a3b7b --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering/output.mpd @@ -0,0 +1,43 @@ + + + + + + + + bear-english-text.vtt + + + + + + bear-640x360-audio.mp4 + + + + + + + + bear-640x360-hevc-video.mp4 + + + + + + + + bear-1280x720-video.mp4 + + + + + + bear-640x360-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/forced-commandline-ordering/stream_0.m3u8 b/packager/app/test/testdata/forced-commandline-ordering/stream_0.m3u8 new file mode 100644 index 00000000000..c3b150ffa2f --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering/stream_0.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-1280x720-video.mp4",BYTERANGE="869@0" +#EXTINF:1.001, +#EXT-X-BYTERANGE:267311@937 +bear-1280x720-video.mp4 +#EXTINF:1.001, +#EXT-X-BYTERANGE:328739 +bear-1280x720-video.mp4 +#EXTINF:0.734, +#EXT-X-BYTERANGE:220240 +bear-1280x720-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-commandline-ordering/stream_1.m3u8 b/packager/app/test/testdata/forced-commandline-ordering/stream_1.m3u8 new file mode 100644 index 00000000000..ba803304466 --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering/stream_1.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-640x360-hevc-video.mp4",BYTERANGE="1910@0" +#EXTINF:1.001, +#EXT-X-BYTERANGE:26885@1978 +bear-640x360-hevc-video.mp4 +#EXTINF:1.001, +#EXT-X-BYTERANGE:34711 +bear-640x360-hevc-video.mp4 +#EXTINF:0.801, +#EXT-X-BYTERANGE:26992 +bear-640x360-hevc-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-commandline-ordering/stream_2.m3u8 b/packager/app/test/testdata/forced-commandline-ordering/stream_2.m3u8 new file mode 100644 index 00000000000..4722e68aa9d --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering/stream_2.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-640x360-audio.mp4",BYTERANGE="804@0" +#EXTINF:1.022, +#EXT-X-BYTERANGE:17028@872 +bear-640x360-audio.mp4 +#EXTINF:0.998, +#EXT-X-BYTERANGE:16285 +bear-640x360-audio.mp4 +#EXTINF:0.720, +#EXT-X-BYTERANGE:9558 +bear-640x360-audio.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-commandline-ordering/stream_3.m3u8 b/packager/app/test/testdata/forced-commandline-ordering/stream_3.m3u8 new file mode 100644 index 00000000000..b687af701eb --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering/stream_3.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="870@0" +#EXTINF:1.001, +#EXT-X-BYTERANGE:99313@938 +bear-640x360-video.mp4 +#EXTINF:1.001, +#EXT-X-BYTERANGE:121807 +bear-640x360-video.mp4 +#EXTINF:0.734, +#EXT-X-BYTERANGE:79662 +bear-640x360-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-commandline-ordering/stream_4.m3u8 b/packager/app/test/testdata/forced-commandline-ordering/stream_4.m3u8 new file mode 100644 index 00000000000..1ed204e2746 --- /dev/null +++ b/packager/app/test/testdata/forced-commandline-ordering/stream_4.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:4.700, +bear-english-text.vtt +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.m3u8 b/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.m3u8 new file mode 100644 index 00000000000..4722e68aa9d --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-640x360-audio.mp4",BYTERANGE="804@0" +#EXTINF:1.022, +#EXT-X-BYTERANGE:17028@872 +bear-640x360-audio.mp4 +#EXTINF:0.998, +#EXT-X-BYTERANGE:16285 +bear-640x360-audio.mp4 +#EXTINF:0.720, +#EXT-X-BYTERANGE:9558 +bear-640x360-audio.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.mp4 b/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.mp4 new file mode 100644 index 00000000000..10077f9af8a Binary files /dev/null and b/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.mp4 differ diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/forced-subtitle/bear-640x360-video-iframe.m3u8 new file mode 100644 index 00000000000..6642032a393 --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/bear-640x360-video-iframe.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-I-FRAMES-ONLY +#EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="870@0" +#EXTINF:1.001, +#EXT-X-BYTERANGE:15581@938 +bear-640x360-video.mp4 +#EXTINF:1.001, +#EXT-X-BYTERANGE:18221@100251 +bear-640x360-video.mp4 +#EXTINF:0.734, +#EXT-X-BYTERANGE:19663@222058 +bear-640x360-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-video.m3u8 b/packager/app/test/testdata/forced-subtitle/bear-640x360-video.m3u8 new file mode 100644 index 00000000000..b687af701eb --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/bear-640x360-video.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="870@0" +#EXTINF:1.001, +#EXT-X-BYTERANGE:99313@938 +bear-640x360-video.mp4 +#EXTINF:1.001, +#EXT-X-BYTERANGE:121807 +bear-640x360-video.mp4 +#EXTINF:0.734, +#EXT-X-BYTERANGE:79662 +bear-640x360-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-video.mp4 b/packager/app/test/testdata/forced-subtitle/bear-640x360-video.mp4 new file mode 100644 index 00000000000..de83807979b Binary files /dev/null and b/packager/app/test/testdata/forced-subtitle/bear-640x360-video.mp4 differ diff --git a/packager/app/test/testdata/forced-subtitle/bear-english-text.vtt b/packager/app/test/testdata/forced-subtitle/bear-english-text.vtt new file mode 100644 index 00000000000..18ae752fe8e --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/bear-english-text.vtt @@ -0,0 +1,11 @@ +WEBVTT + +STYLE +::cue { color:lime } + +00:00:00.000 --> 00:00:00.800 align:center +Yup, that's a bear, eh. + +00:00:01.000 --> 00:00:04.700 align:center +He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/forced-subtitle/output.m3u8 b/packager/app/test/testdata/forced-subtitle/output.m3u8 new file mode 100644 index 00000000000..183b69e84b7 --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/output.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2" + +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_2.m3u8",GROUP-ID="default-text-group",NAME="stream_2",DEFAULT=NO,AUTOSELECT=YES,FORCED=YES + +#EXT-X-STREAM-INF:BANDWIDTH=1106817,AVERAGE-BANDWIDTH=1004632,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",SUBTITLES="default-text-group",CLOSED-CAPTIONS=NONE +bear-640x360-video.m3u8 + +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=214292,AVERAGE-BANDWIDTH=156327,CODECS="avc1.64001e",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,URI="bear-640x360-video-iframe.m3u8" diff --git a/packager/app/test/testdata/forced-subtitle/output.mpd b/packager/app/test/testdata/forced-subtitle/output.mpd new file mode 100644 index 00000000000..e557fb28ea0 --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/output.mpd @@ -0,0 +1,29 @@ + + + + + + + + bear-640x360-audio.mp4 + + + + + + + + bear-640x360-video.mp4 + + + + + + + + + bear-english-text.vtt + + + + diff --git a/packager/app/test/testdata/forced-subtitle/stream_2.m3u8 b/packager/app/test/testdata/forced-subtitle/stream_2.m3u8 new file mode 100644 index 00000000000..1ed204e2746 --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/stream_2.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:4.700, +bear-english-text.vtt +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hdr10-with-encryption/output.mpd b/packager/app/test/testdata/hdr10-with-encryption/output.mpd index 56001f85c7a..3eaf56c203d 100644 --- a/packager/app/test/testdata/hdr10-with-encryption/output.mpd +++ b/packager/app/test/testdata/hdr10-with-encryption/output.mpd @@ -1,8 +1,10 @@ - + + + diff --git a/packager/app/test/testdata/hevc-with-encryption/output.mpd b/packager/app/test/testdata/hevc-with-encryption/output.mpd index 2f638e04d5c..55f3918a951 100644 --- a/packager/app/test/testdata/hevc-with-encryption/output.mpd +++ b/packager/app/test/testdata/hevc-with-encryption/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/hls-only-dash-only-captions/output.mpd b/packager/app/test/testdata/hls-only-dash-only-captions/output.mpd index 03a5990310f..b760e778ee2 100644 --- a/packager/app/test/testdata/hls-only-dash-only-captions/output.mpd +++ b/packager/app/test/testdata/hls-only-dash-only-captions/output.mpd @@ -2,18 +2,8 @@ - - - - - - - - - - - - + + @@ -24,6 +14,16 @@ + + + + + + + + + + diff --git a/packager/app/test/testdata/hls-only-dash-only/output.mpd b/packager/app/test/testdata/hls-only-dash-only/output.mpd index 5ec891d03cf..55e6c71d2bf 100644 --- a/packager/app/test/testdata/hls-only-dash-only/output.mpd +++ b/packager/app/test/testdata/hls-only-dash-only/output.mpd @@ -1,9 +1,9 @@ - + - - + + bear-640x360-audio.mp4 diff --git a/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-audio.m3u8 b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-audio.m3u8 new file mode 100644 index 00000000000..d7c015a03f4 --- /dev/null +++ b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-audio.m3u8 @@ -0,0 +1,15 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:0.975, +#EXT-X-BYTERANGE:23312@0 +bear-640x360-audio.ts +#EXTINF:0.998, +#EXT-X-BYTERANGE:24252 +bear-640x360-audio.ts +#EXTINF:0.789, +#EXT-X-BYTERANGE:17296 +bear-640x360-audio.ts +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-audio.ts b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-audio.ts new file mode 100644 index 00000000000..eaed17eef15 Binary files /dev/null and b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-audio.ts differ diff --git a/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video-iframe.m3u8 new file mode 100644 index 00000000000..adfc872a9dd --- /dev/null +++ b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video-iframe.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-I-FRAMES-ONLY +#EXTINF:1.001, +#EXT-X-BYTERANGE:15604@376 +bear-640x360-video.ts +#EXTINF:1.001, +#EXT-X-BYTERANGE:18236@105656 +bear-640x360-video.ts +#EXTINF:0.734, +#EXT-X-BYTERANGE:19928@233684 +bear-640x360-video.ts +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video.m3u8 b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video.m3u8 new file mode 100644 index 00000000000..a1cd8ce3d84 --- /dev/null +++ b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video.m3u8 @@ -0,0 +1,15 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:1.001, +#EXT-X-BYTERANGE:105280@0 +bear-640x360-video.ts +#EXTINF:1.001, +#EXT-X-BYTERANGE:128028 +bear-640x360-video.ts +#EXTINF:0.734, +#EXT-X-BYTERANGE:84600 +bear-640x360-video.ts +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video.ts b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video.ts new file mode 100644 index 00000000000..3e52b07d4e6 Binary files /dev/null and b/packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video.ts differ diff --git a/packager/app/test/testdata/hls-single-segment-ts/output.m3u8 b/packager/app/test/testdata/hls-single-segment-ts/output.m3u8 new file mode 100644 index 00000000000..b827ca1dc84 --- /dev/null +++ b/packager/app/test/testdata/hls-single-segment-ts/output.m3u8 @@ -0,0 +1,11 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2" + +#EXT-X-STREAM-INF:BANDWIDTH=1217520,AVERAGE-BANDWIDTH=1117320,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE +bear-640x360-video.m3u8 + +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=217180,AVERAGE-BANDWIDTH=157213,CODECS="avc1.64001e",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,URI="bear-640x360-video-iframe.m3u8" diff --git a/packager/app/test/testdata/live-profile-and-encryption-and-mult-files/output.mpd b/packager/app/test/testdata/live-profile-and-encryption-and-mult-files/output.mpd index b2fd725df92..9437851ec19 100644 --- a/packager/app/test/testdata/live-profile-and-encryption-and-mult-files/output.mpd +++ b/packager/app/test/testdata/live-profile-and-encryption-and-mult-files/output.mpd @@ -2,68 +2,68 @@ - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - + + + - - + + + - - + + + - - + + + - - + + + - - + + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - + + - - - + + - - - + + - - - + + - - - + + - - - + + diff --git a/packager/app/test/testdata/live-profile-and-encryption-and-non-dash-if-iop/output.mpd b/packager/app/test/testdata/live-profile-and-encryption-and-non-dash-if-iop/output.mpd index 121193085c5..cf50fffd4e6 100644 --- a/packager/app/test/testdata/live-profile-and-encryption-and-non-dash-if-iop/output.mpd +++ b/packager/app/test/testdata/live-profile-and-encryption-and-non-dash-if-iop/output.mpd @@ -2,32 +2,32 @@ - - + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + - - + + + - - - + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + - - - + + diff --git a/packager/app/test/testdata/live-profile-and-encryption/output.mpd b/packager/app/test/testdata/live-profile-and-encryption/output.mpd index b660ce33d6b..fba35c610f5 100644 --- a/packager/app/test/testdata/live-profile-and-encryption/output.mpd +++ b/packager/app/test/testdata/live-profile-and-encryption/output.mpd @@ -2,32 +2,32 @@ - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - + + + - - + + + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - - - + + - - - + + diff --git a/packager/app/test/testdata/live-profile-and-key-rotation-and-no-pssh-in-stream/output.mpd b/packager/app/test/testdata/live-profile-and-key-rotation-and-no-pssh-in-stream/output.mpd index 9ebc81dfe1f..2ed6d6621eb 100644 --- a/packager/app/test/testdata/live-profile-and-key-rotation-and-no-pssh-in-stream/output.mpd +++ b/packager/app/test/testdata/live-profile-and-key-rotation-and-no-pssh-in-stream/output.mpd @@ -2,28 +2,28 @@ - + - - + + + - - + + + - + - - - + + - - - + + diff --git a/packager/app/test/testdata/live-profile-and-key-rotation-and-non-dash-if-iop/output.mpd b/packager/app/test/testdata/live-profile-and-key-rotation-and-non-dash-if-iop/output.mpd index 77ba7447eaf..efcb25c193d 100644 --- a/packager/app/test/testdata/live-profile-and-key-rotation-and-non-dash-if-iop/output.mpd +++ b/packager/app/test/testdata/live-profile-and-key-rotation-and-non-dash-if-iop/output.mpd @@ -2,28 +2,28 @@ - - + + + - + - - + + + - - - + + - + - - - + + diff --git a/packager/app/test/testdata/live-profile-and-key-rotation-cbcs/output.mpd b/packager/app/test/testdata/live-profile-and-key-rotation-cbcs/output.mpd index 4b51a0e35fa..eeec8f59c84 100644 --- a/packager/app/test/testdata/live-profile-and-key-rotation-cbcs/output.mpd +++ b/packager/app/test/testdata/live-profile-and-key-rotation-cbcs/output.mpd @@ -2,28 +2,28 @@ - + - - + + + - - + + + - + - - - + + - - - + + diff --git a/packager/app/test/testdata/live-profile-and-key-rotation/output.mpd b/packager/app/test/testdata/live-profile-and-key-rotation/output.mpd index 861518a1f7c..78be4297467 100644 --- a/packager/app/test/testdata/live-profile-and-key-rotation/output.mpd +++ b/packager/app/test/testdata/live-profile-and-key-rotation/output.mpd @@ -2,28 +2,28 @@ - + - - + + + - - + + + - + - - - + + - - - + + diff --git a/packager/app/test/testdata/live-profile-with-webm/bear-640x360-audio-init.webm b/packager/app/test/testdata/live-profile-with-webm/bear-640x360-audio-init.webm index 1e827299e41..dbcd9030ae6 100644 Binary files a/packager/app/test/testdata/live-profile-with-webm/bear-640x360-audio-init.webm and b/packager/app/test/testdata/live-profile-with-webm/bear-640x360-audio-init.webm differ diff --git a/packager/app/test/testdata/live-profile-with-webm/bear-640x360-video-init.webm b/packager/app/test/testdata/live-profile-with-webm/bear-640x360-video-init.webm index 5f9e729b372..381bd4050d3 100644 Binary files a/packager/app/test/testdata/live-profile-with-webm/bear-640x360-video-init.webm and b/packager/app/test/testdata/live-profile-with-webm/bear-640x360-video-init.webm differ diff --git a/packager/app/test/testdata/live-profile-with-webm/output.m3u8 b/packager/app/test/testdata/live-profile-with-webm/output.m3u8 index 57ad801cfc0..cfcfc457ead 100644 --- a/packager/app/test/testdata/live-profile-with-webm/output.m3u8 +++ b/packager/app/test/testdata/live-profile-with-webm/output.m3u8 @@ -5,5 +5,5 @@ #EXT-X-MEDIA:TYPE=AUDIO,URI="stream_0.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2" -#EXT-X-STREAM-INF:BANDWIDTH=556353,AVERAGE-BANDWIDTH=412719,CODECS="vp08.00.10.08.01.02.02.02.00,vorbis",RESOLUTION=640x360,FRAME-RATE=30.303,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE +#EXT-X-STREAM-INF:BANDWIDTH=556353,AVERAGE-BANDWIDTH=412719,CODECS="vp08.00.10.08.01.02.02.02.00,vorbis",RESOLUTION=640x360,FRAME-RATE=29.412,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE stream_1.m3u8 diff --git a/packager/app/test/testdata/live-profile-with-webm/output.mpd b/packager/app/test/testdata/live-profile-with-webm/output.mpd index c41814ca158..ea922c3284c 100644 --- a/packager/app/test/testdata/live-profile-with-webm/output.mpd +++ b/packager/app/test/testdata/live-profile-with-webm/output.mpd @@ -14,7 +14,7 @@ - + diff --git a/packager/app/test/testdata/live-profile/output.mpd b/packager/app/test/testdata/live-profile/output.mpd index 8f3cbde818b..5a2ac4c9f4a 100644 --- a/packager/app/test/testdata/live-profile/output.mpd +++ b/packager/app/test/testdata/live-profile/output.mpd @@ -2,18 +2,8 @@ - - - - - - - - - - - - + + @@ -24,6 +14,16 @@ + + + + + + + + + + diff --git a/packager/app/test/testdata/live-static-profile-and-ad-cues/output.mpd b/packager/app/test/testdata/live-static-profile-and-ad-cues/output.mpd index a0f13f5c9c5..2773dcf6112 100644 --- a/packager/app/test/testdata/live-static-profile-and-ad-cues/output.mpd +++ b/packager/app/test/testdata/live-static-profile-and-ad-cues/output.mpd @@ -1,6 +1,6 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/packager/app/test/testdata/live-static-profile-with-time-in-segment-name/output.mpd b/packager/app/test/testdata/live-static-profile-with-time-in-segment-name/output.mpd index 5983d6f9b58..d584f61eb7d 100644 --- a/packager/app/test/testdata/live-static-profile-with-time-in-segment-name/output.mpd +++ b/packager/app/test/testdata/live-static-profile-with-time-in-segment-name/output.mpd @@ -1,19 +1,9 @@ - + - - - - - - - - - - - - + + @@ -24,5 +14,15 @@ + + + + + + + + + + diff --git a/packager/app/test/testdata/live-static-profile/output.mpd b/packager/app/test/testdata/live-static-profile/output.mpd index b851f0164c4..1673295b61f 100644 --- a/packager/app/test/testdata/live-static-profile/output.mpd +++ b/packager/app/test/testdata/live-static-profile/output.mpd @@ -1,19 +1,9 @@ - + - - - - - - - - - - - - + + @@ -24,5 +14,15 @@ + + + + + + + + + + diff --git a/packager/app/test/testdata/mp4-trailing-moov/output.mpd b/packager/app/test/testdata/mp4-trailing-moov/output.mpd index 8a2dd9e9591..c1139bd5649 100644 --- a/packager/app/test/testdata/mp4-trailing-moov/output.mpd +++ b/packager/app/test/testdata/mp4-trailing-moov/output.mpd @@ -1,17 +1,9 @@ - + - - - bear-640x360-trailing-moov-video.mp4 - - - - - - - + + bear-640x360-trailing-moov-audio.mp4 @@ -19,5 +11,13 @@ + + + bear-640x360-trailing-moov-video.mp4 + + + + + diff --git a/packager/app/test/testdata/opus-vp9-mp4-with-encryption/output.mpd b/packager/app/test/testdata/opus-vp9-mp4-with-encryption/output.mpd index c274e66ccdc..42125e65fd2 100644 --- a/packager/app/test/testdata/opus-vp9-mp4-with-encryption/output.mpd +++ b/packager/app/test/testdata/opus-vp9-mp4-with-encryption/output.mpd @@ -1,6 +1,6 @@ - + @@ -15,7 +15,7 @@ - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-1.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-1.m4s index 1de1cd08d37..319e9a7cddc 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-1.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-1.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-2.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-2.m4s index 29351536a0b..ac847dfc255 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-2.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-2.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-3.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-3.m4s index 8b81e6d983d..db5c1dd6f6b 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-3.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-3.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-4.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-4.m4s index 9c86b0e5e2e..32792d0cfcd 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-4.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-4.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-5.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-5.m4s index e0dc6e3b8d4..224ccd2c68c 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-5.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-5.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/output.mpd b/packager/app/test/testdata/segmented-ttml-mp4/output.mpd index 35ddebf4a19..bc8c0c7591c 100644 --- a/packager/app/test/testdata/segmented-ttml-mp4/output.mpd +++ b/packager/app/test/testdata/segmented-ttml-mp4/output.mpd @@ -4,7 +4,7 @@ - + diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-1.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-1.ttml index 94a5092c9d3..2bd77c0beda 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-1.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-1.ttml @@ -1,6 +1,10 @@ - + + + + +

Yup, that's a bear, eh.

diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-2.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-2.ttml index 8048787ec79..10a7664c862 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-2.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-2.ttml @@ -1,6 +1,10 @@ - + + + + +

He 's... um... doing bear-like stuff.

diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-3.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-3.ttml index 8048787ec79..10a7664c862 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-3.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-3.ttml @@ -1,6 +1,10 @@ - + + + + +

He 's... um... doing bear-like stuff.

diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-4.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-4.ttml index 8048787ec79..10a7664c862 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-4.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-4.ttml @@ -1,6 +1,10 @@ - + + + + +

He 's... um... doing bear-like stuff.

diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-5.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-5.ttml index 8048787ec79..10a7664c862 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-5.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-5.ttml @@ -1,6 +1,10 @@ - + + + + +

He 's... um... doing bear-like stuff.

diff --git a/packager/app/test/testdata/segmented-ttml-text/output.mpd b/packager/app/test/testdata/segmented-ttml-text/output.mpd index 9b9e87e2ed3..398aeeca607 100644 --- a/packager/app/test/testdata/segmented-ttml-text/output.mpd +++ b/packager/app/test/testdata/segmented-ttml-text/output.mpd @@ -4,7 +4,7 @@ - + diff --git a/packager/app/test/testdata/single-file-webvtt-text/output.mpd b/packager/app/test/testdata/single-file-webvtt-text/output.mpd index 598d0f6b053..f6ecb41c227 100644 --- a/packager/app/test/testdata/single-file-webvtt-text/output.mpd +++ b/packager/app/test/testdata/single-file-webvtt-text/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/video-audio-text/output.mpd b/packager/app/test/testdata/video-audio-text/output.mpd index 04ea40d3acb..7d789849474 100644 --- a/packager/app/test/testdata/video-audio-text/output.mpd +++ b/packager/app/test/testdata/video-audio-text/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/video-audio-ttml/output.mpd b/packager/app/test/testdata/video-audio-ttml/output.mpd index 78ce66034a0..9c87c4c8935 100644 --- a/packager/app/test/testdata/video-audio-ttml/output.mpd +++ b/packager/app/test/testdata/video-audio-ttml/output.mpd @@ -1,10 +1,14 @@ - + - - - bear-english-text.ttml + + + + bear-640x360-audio.mp4 + + + @@ -15,13 +19,9 @@ - - - - bear-640x360-audio.mp4 - - - + + + bear-english-text.ttml diff --git a/packager/app/test/testdata/video-audio-webvtt/output.mpd b/packager/app/test/testdata/video-audio-webvtt/output.mpd index de1f653f3e9..f681078a7ee 100644 --- a/packager/app/test/testdata/video-audio-webvtt/output.mpd +++ b/packager/app/test/testdata/video-audio-webvtt/output.mpd @@ -1,17 +1,9 @@ - + - - - bear-640x360-video.mp4 - - - - - - - + + bear-640x360-audio.mp4 @@ -19,6 +11,14 @@ + + + bear-640x360-video.mp4 + + + + + diff --git a/packager/app/test/testdata/video-no-edit-list/output.mpd b/packager/app/test/testdata/video-no-edit-list/output.mpd index 07e21ed6434..9e415d6b546 100644 --- a/packager/app/test/testdata/video-no-edit-list/output.mpd +++ b/packager/app/test/testdata/video-no-edit-list/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/video-non-square-pixel/output.mpd b/packager/app/test/testdata/video-non-square-pixel/output.mpd index b5bf03a14cd..ff3e2ea9c51 100644 --- a/packager/app/test/testdata/video-non-square-pixel/output.mpd +++ b/packager/app/test/testdata/video-non-square-pixel/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/vorbis-webm/bear-320x240-audio-only-audio.webm b/packager/app/test/testdata/vorbis-webm/bear-320x240-audio-only-audio.webm index 108993f61b7..33f77d4bd55 100644 Binary files a/packager/app/test/testdata/vorbis-webm/bear-320x240-audio-only-audio.webm and b/packager/app/test/testdata/vorbis-webm/bear-320x240-audio-only-audio.webm differ diff --git a/packager/app/test/testdata/vorbis-webm/output.mpd b/packager/app/test/testdata/vorbis-webm/output.mpd index 407db1ba504..54328d1080f 100644 --- a/packager/app/test/testdata/vorbis-webm/output.mpd +++ b/packager/app/test/testdata/vorbis-webm/output.mpd @@ -1,6 +1,6 @@ - + diff --git a/packager/app/test/testdata/vp8-mp4-with-encryption/output.mpd b/packager/app/test/testdata/vp8-mp4-with-encryption/output.mpd index 5004c6925e0..80dbde48de6 100644 --- a/packager/app/test/testdata/vp8-mp4-with-encryption/output.mpd +++ b/packager/app/test/testdata/vp8-mp4-with-encryption/output.mpd @@ -1,8 +1,8 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/vp8-webm/bear-640x360-video.webm b/packager/app/test/testdata/vp8-webm/bear-640x360-video.webm index 34404d12bc6..94be98d666a 100644 Binary files a/packager/app/test/testdata/vp8-webm/bear-640x360-video.webm and b/packager/app/test/testdata/vp8-webm/bear-640x360-video.webm differ diff --git a/packager/app/test/testdata/vp8-webm/output.mpd b/packager/app/test/testdata/vp8-webm/output.mpd index bc97ab36ff1..5eecef53b49 100644 --- a/packager/app/test/testdata/vp8-webm/output.mpd +++ b/packager/app/test/testdata/vp8-webm/output.mpd @@ -1,8 +1,8 @@ - + - + bear-640x360-video.webm diff --git a/packager/app/test/testdata/vp9-webm-with-blockgroup/bear-vp9-blockgroup-video.webm b/packager/app/test/testdata/vp9-webm-with-blockgroup/bear-vp9-blockgroup-video.webm index 4bdba082ea0..4fc67d86669 100644 Binary files a/packager/app/test/testdata/vp9-webm-with-blockgroup/bear-vp9-blockgroup-video.webm and b/packager/app/test/testdata/vp9-webm-with-blockgroup/bear-vp9-blockgroup-video.webm differ diff --git a/packager/app/test/testdata/vp9-webm-with-blockgroup/output.mpd b/packager/app/test/testdata/vp9-webm-with-blockgroup/output.mpd index eb9ad6a14ec..c78caf168da 100644 --- a/packager/app/test/testdata/vp9-webm-with-blockgroup/output.mpd +++ b/packager/app/test/testdata/vp9-webm-with-blockgroup/output.mpd @@ -1,8 +1,8 @@ - + - + bear-vp9-blockgroup-video.webm diff --git a/packager/app/test/testdata/vp9-webm/bear-320x240-vp9-opus-audio.webm b/packager/app/test/testdata/vp9-webm/bear-320x240-vp9-opus-audio.webm index c27b06eb72e..2f47e163a60 100644 Binary files a/packager/app/test/testdata/vp9-webm/bear-320x240-vp9-opus-audio.webm and b/packager/app/test/testdata/vp9-webm/bear-320x240-vp9-opus-audio.webm differ diff --git a/packager/app/test/testdata/vp9-webm/bear-320x240-vp9-opus-video.webm b/packager/app/test/testdata/vp9-webm/bear-320x240-vp9-opus-video.webm index d42a39076ea..bbf17b001d4 100644 Binary files a/packager/app/test/testdata/vp9-webm/bear-320x240-vp9-opus-video.webm and b/packager/app/test/testdata/vp9-webm/bear-320x240-vp9-opus-video.webm differ diff --git a/packager/app/test/testdata/vp9-webm/output.mpd b/packager/app/test/testdata/vp9-webm/output.mpd index bfc7ffb7497..5e109a04efe 100644 --- a/packager/app/test/testdata/vp9-webm/output.mpd +++ b/packager/app/test/testdata/vp9-webm/output.mpd @@ -1,6 +1,6 @@ - + @@ -11,7 +11,7 @@ - + bear-320x240-vp9-opus-video.webm diff --git a/packager/app/test/testdata/vtt-text-to-mp4-with-ad-cues/output.mpd b/packager/app/test/testdata/vtt-text-to-mp4-with-ad-cues/output.mpd index 3ec08889a85..75bf1c3bfe2 100644 --- a/packager/app/test/testdata/vtt-text-to-mp4-with-ad-cues/output.mpd +++ b/packager/app/test/testdata/vtt-text-to-mp4-with-ad-cues/output.mpd @@ -1,23 +1,23 @@ - + - - - + + + + - + + - - - - + + + - - + @@ -34,22 +34,22 @@ - - - - + + + + + - + - - - - + + + - + diff --git a/packager/app/test/testdata/webm-subsample-encryption/bear-320x180-vp9-altref-video.webm b/packager/app/test/testdata/webm-subsample-encryption/bear-320x180-vp9-altref-video.webm index f625d7037a5..bdacb30adf5 100644 Binary files a/packager/app/test/testdata/webm-subsample-encryption/bear-320x180-vp9-altref-video.webm and b/packager/app/test/testdata/webm-subsample-encryption/bear-320x180-vp9-altref-video.webm differ diff --git a/packager/app/test/testdata/webm-subsample-encryption/decrypted-bear-320x180-vp9-altref-video-0.webm b/packager/app/test/testdata/webm-subsample-encryption/decrypted-bear-320x180-vp9-altref-video-0.webm index 2c47cbe0c0b..d0f9ee11dd0 100644 Binary files a/packager/app/test/testdata/webm-subsample-encryption/decrypted-bear-320x180-vp9-altref-video-0.webm and b/packager/app/test/testdata/webm-subsample-encryption/decrypted-bear-320x180-vp9-altref-video-0.webm differ diff --git a/packager/app/test/testdata/webm-subsample-encryption/output.mpd b/packager/app/test/testdata/webm-subsample-encryption/output.mpd index f61ba9c2bcc..a68d23d32cd 100644 --- a/packager/app/test/testdata/webm-subsample-encryption/output.mpd +++ b/packager/app/test/testdata/webm-subsample-encryption/output.mpd @@ -1,8 +1,8 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/webm-vp9-full-sample-encryption/bear-320x180-vp9-altref-video.webm b/packager/app/test/testdata/webm-vp9-full-sample-encryption/bear-320x180-vp9-altref-video.webm index f625d7037a5..bdacb30adf5 100644 Binary files a/packager/app/test/testdata/webm-vp9-full-sample-encryption/bear-320x180-vp9-altref-video.webm and b/packager/app/test/testdata/webm-vp9-full-sample-encryption/bear-320x180-vp9-altref-video.webm differ diff --git a/packager/app/test/testdata/webm-vp9-full-sample-encryption/decrypted-bear-320x180-vp9-altref-video-0.webm b/packager/app/test/testdata/webm-vp9-full-sample-encryption/decrypted-bear-320x180-vp9-altref-video-0.webm index 2c47cbe0c0b..d0f9ee11dd0 100644 Binary files a/packager/app/test/testdata/webm-vp9-full-sample-encryption/decrypted-bear-320x180-vp9-altref-video-0.webm and b/packager/app/test/testdata/webm-vp9-full-sample-encryption/decrypted-bear-320x180-vp9-altref-video-0.webm differ diff --git a/packager/app/test/testdata/webm-vp9-full-sample-encryption/output.mpd b/packager/app/test/testdata/webm-vp9-full-sample-encryption/output.mpd index f61ba9c2bcc..a68d23d32cd 100644 --- a/packager/app/test/testdata/webm-vp9-full-sample-encryption/output.mpd +++ b/packager/app/test/testdata/webm-vp9-full-sample-encryption/output.mpd @@ -1,8 +1,8 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/webm-with-encryption/bear-640x360-video.webm b/packager/app/test/testdata/webm-with-encryption/bear-640x360-video.webm index 87d129de1e3..f616ab54cb3 100644 Binary files a/packager/app/test/testdata/webm-with-encryption/bear-640x360-video.webm and b/packager/app/test/testdata/webm-with-encryption/bear-640x360-video.webm differ diff --git a/packager/app/test/testdata/webm-with-encryption/decrypted-bear-640x360-video-0.webm b/packager/app/test/testdata/webm-with-encryption/decrypted-bear-640x360-video-0.webm index 34404d12bc6..94be98d666a 100644 Binary files a/packager/app/test/testdata/webm-with-encryption/decrypted-bear-640x360-video-0.webm and b/packager/app/test/testdata/webm-with-encryption/decrypted-bear-640x360-video-0.webm differ diff --git a/packager/app/test/testdata/webm-with-encryption/output.mpd b/packager/app/test/testdata/webm-with-encryption/output.mpd index 7873d4485d8..ee3390f9480 100644 --- a/packager/app/test/testdata/webm-with-encryption/output.mpd +++ b/packager/app/test/testdata/webm-with-encryption/output.mpd @@ -1,8 +1,8 @@ - + - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/wvm-input-without-stripping-parameters-set-nalus/output.mpd b/packager/app/test/testdata/wvm-input-without-stripping-parameters-set-nalus/output.mpd index 41c75f59796..0107e5f7635 100644 --- a/packager/app/test/testdata/wvm-input-without-stripping-parameters-set-nalus/output.mpd +++ b/packager/app/test/testdata/wvm-input-without-stripping-parameters-set-nalus/output.mpd @@ -1,6 +1,6 @@ - + @@ -16,7 +16,7 @@ - + bear-multi-configs-1.mp4 diff --git a/packager/app/test/testdata/wvm-input/output.mpd b/packager/app/test/testdata/wvm-input/output.mpd index 60b5f1c1f97..240810b3223 100644 --- a/packager/app/test/testdata/wvm-input/output.mpd +++ b/packager/app/test/testdata/wvm-input/output.mpd @@ -1,6 +1,6 @@ - + @@ -16,7 +16,7 @@ - + bear-multi-configs-1.mp4 diff --git a/packager/file/http_file.cc b/packager/file/http_file.cc index e2a27e2d0dd..b50fef6f072 100644 --- a/packager/file/http_file.cc +++ b/packager/file/http_file.cc @@ -372,6 +372,12 @@ void HttpFile::ThreadMain() { error_message); } + // In some cases it is possible that the server has already closed the + // connection without reading the request body. This can for example happen + // when the server responds with a non-successful status code. In this case we + // need to make sure to close the upload cache here, otherwise some other + // thread may block forever on Flush(). + upload_cache_.Close(); download_cache_.Close(); task_exit_event_.Notify(); } diff --git a/packager/fully-static.cmake b/packager/fully-static.cmake new file mode 100644 index 00000000000..3190b99ad67 --- /dev/null +++ b/packager/fully-static.cmake @@ -0,0 +1,27 @@ +# Copyright 2024 Google LLC. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# Fully-static build settings. +if(FULLY_STATIC) + # This is the "object" version of mimalloc, as opposed to the library + # version. This is important for a static override of malloc and friends. + set(EXTRA_EXE_LIBRARIES $) + + # Keep the linker from searching for dynamic libraries. + set(CMAKE_LINK_SEARCH_START_STATIC OFF) + set(CMAKE_LINK_SEARCH_END_STATIC OFF) + + # Tell CMake not to plan to relink the executables, which wouldn't make sense + # in this context and causes CMake to fail at configure time when using a + # musl toolchain for static builds. + set(CMAKE_SKIP_BUILD_RPATH ON) + + # Set extra linker options necessary for fully static linking. These apply + # to all executables, which is critical when using a musl toolchain. Without + # applying these to all executables, we could create dynamic musl executables + # as intermediate outputs, which then could not run on a glibc host system. + add_link_options(-static-libgcc -static-libstdc++ -static) +endif() diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index 752207baaa3..8cc5eb714d1 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -228,6 +228,15 @@ void BuildStreamInfTag(const MediaPlaylist& playlist, variant.text_codecs.end()); tag.AddQuotedString("CODECS", absl::StrJoin(all_codecs, ",")); + if (playlist.supplemental_codec() != "" && + playlist.compatible_brand() != media::FOURCC_NULL) { + std::vector supplemental_codecs; + supplemental_codecs.push_back(playlist.supplemental_codec()); + supplemental_codecs.push_back(FourCCToString(playlist.compatible_brand())); + tag.AddQuotedString("SUPPLEMENTAL-CODECS", + absl::StrJoin(supplemental_codecs, "/")); + } + uint32_t width; uint32_t height; if (playlist.GetDisplayResolution(&width, &height)) { @@ -318,11 +327,16 @@ void BuildMediaTag(const MediaPlaylist& playlist, } else { tag.AddString("DEFAULT", "NO"); } - if (is_autoselect) { tag.AddString("AUTOSELECT", "YES"); } + if (playlist.stream_type() == + MediaPlaylist::MediaPlaylistStreamType::kSubtitle && + playlist.forced_subtitle()) { + tag.AddString("FORCED", "YES"); + } + const std::vector& characteristics = playlist.characteristics(); if (!characteristics.empty()) { tag.AddQuotedString("CHARACTERISTICS", absl::StrJoin(characteristics, ",")); @@ -360,7 +374,7 @@ void BuildMediaTag(const MediaPlaylist& playlist, } void BuildMediaTags( - const std::map>& groups, + std::list>>& groups, const std::string& default_language, const std::string& base_url, std::string* out) { @@ -401,12 +415,30 @@ void BuildMediaTags( } } + if (playlist->stream_type() == + MediaPlaylist::MediaPlaylistStreamType::kSubtitle && + playlist->forced_subtitle()) { + is_autoselect = true; + } + BuildMediaTag(*playlist, group_id, is_default, is_autoselect, base_url, out); } } } +bool ListOrderFn(const MediaPlaylist*& a, const MediaPlaylist*& b) { + return a->GetMediaInfo().index() < b->GetMediaInfo().index(); +} + +bool GroupOrderFn(std::pair>& a, + std::pair>& b) { + a.second.sort(ListOrderFn); + b.second.sort(ListOrderFn); + return a.second.front()->GetMediaInfo().index() < + b.second.front()->GetMediaInfo().index(); +} + void AppendPlaylists(const std::string& default_audio_language, const std::string& default_text_language, const std::string& base_url, @@ -417,7 +449,12 @@ void AppendPlaylists(const std::string& default_audio_language, subtitle_playlist_groups; std::list video_playlists; std::list iframe_playlists; + + bool has_index = true; + for (const MediaPlaylist* playlist : playlists) { + has_index = has_index && playlist->GetMediaInfo().has_index(); + switch (playlist->stream_type()) { case MediaPlaylist::MediaPlaylistStreamType::kAudio: audio_playlist_groups[GetGroupId(*playlist)].push_back(playlist); @@ -437,15 +474,37 @@ void AppendPlaylists(const std::string& default_audio_language, } } + // convert the std::map to std::list and reorder it if indexes were provided + std::list>> + audio_groups_list(audio_playlist_groups.begin(), + audio_playlist_groups.end()); + std::list>> + subtitle_groups_list(subtitle_playlist_groups.begin(), + subtitle_playlist_groups.end()); + if (has_index) { + audio_groups_list.sort(GroupOrderFn); + for (const auto& group : audio_groups_list) { + std::list group_playlists = group.second; + group_playlists.sort(ListOrderFn); + } + subtitle_groups_list.sort(GroupOrderFn); + for (const auto& group : subtitle_groups_list) { + std::list group_playlists = group.second; + group_playlists.sort(ListOrderFn); + } + video_playlists.sort(ListOrderFn); + iframe_playlists.sort(ListOrderFn); + } + if (!audio_playlist_groups.empty()) { content->append("\n"); - BuildMediaTags(audio_playlist_groups, default_audio_language, base_url, + BuildMediaTags(audio_groups_list, default_audio_language, base_url, content); } if (!subtitle_playlist_groups.empty()) { content->append("\n"); - BuildMediaTags(subtitle_playlist_groups, default_text_language, base_url, + BuildMediaTags(subtitle_groups_list, default_text_language, base_url, content); } @@ -472,7 +531,7 @@ void AppendPlaylists(const std::string& default_audio_language, if (!audio_playlist_groups.empty() && video_playlists.empty() && subtitle_playlist_groups.empty()) { content->append("\n"); - for (const auto& playlist_group : audio_playlist_groups) { + for (const auto& playlist_group : audio_groups_list) { Variant variant; // Populate |audio_group_id|, which will be propagated to "AUDIO" field. // Leaving other fields, e.g. xxx_audio_bitrate in |Variant|, as diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc index 887e5e4fe7f..4294e052590 100644 --- a/packager/hls/base/master_playlist_unittest.cc +++ b/packager/hls/base/master_playlist_unittest.cc @@ -187,11 +187,9 @@ TEST_F(MasterPlaylistTest, const uint64_t kMaxBitrate = 435889; const uint64_t kAvgBitrate = 235889; - master_playlist_.reset(new MasterPlaylist( - kDefaultMasterPlaylistName, - kDefaultAudioLanguage, - kDefaultTextLanguage, - kIsIndependentSegments)); + master_playlist_.reset( + new MasterPlaylist(kDefaultMasterPlaylistName, kDefaultAudioLanguage, + kDefaultTextLanguage, kIsIndependentSegments)); std::unique_ptr mock_playlist = CreateVideoPlaylist("media1.m3u8", "avc1", kMaxBitrate, kAvgBitrate); diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 1b129c2555f..b480c0e2ce6 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -109,7 +110,8 @@ std::string CreatePlaylistHeader( HlsPlaylistType type, MediaPlaylist::MediaPlaylistStreamType stream_type, uint32_t media_sequence_number, - int discontinuity_sequence_number) { + int discontinuity_sequence_number, + std::optional start_time_offset) { const std::string version = GetPackagerVersion(); std::string version_line; if (!version.empty()) { @@ -151,6 +153,10 @@ std::string CreatePlaylistHeader( MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly) { absl::StrAppendFormat(&header, "#EXT-X-I-FRAMES-ONLY\n"); } + if (start_time_offset.has_value()) { + absl::StrAppendFormat(&header, "#EXT-X-START:TIME-OFFSET=%f\n", + start_time_offset.value()); + } // Put EXT-X-MAP at the end since the rest of the playlist is about the // segment and key info. @@ -373,6 +379,10 @@ void MediaPlaylist::SetCharacteristicsForTesting( characteristics_ = characteristics; } +void MediaPlaylist::SetForcedSubtitleForTesting(const bool forced_subtitle) { + forced_subtitle_ = forced_subtitle; +} + bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) { const int32_t time_scale = GetTimeScale(media_info); if (time_scale == 0) { @@ -383,6 +393,13 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) { if (media_info.has_video_info()) { stream_type_ = MediaPlaylistStreamType::kVideo; codec_ = AdjustVideoCodec(media_info.video_info().codec()); + if (media_info.video_info().has_supplemental_codec() && + media_info.video_info().has_compatible_brand()) { + supplemental_codec_ = + AdjustVideoCodec(media_info.video_info().supplemental_codec()); + compatible_brand_ = static_cast( + media_info.video_info().compatible_brand()); + } } else if (media_info.has_audio_info()) { stream_type_ = MediaPlaylistStreamType::kAudio; codec_ = media_info.audio_info().codec(); @@ -400,6 +417,8 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) { std::vector(media_info_.hls_characteristics().begin(), media_info_.hls_characteristics().end()); + forced_subtitle_ = media_info_.forced_subtitle(); + return true; } @@ -479,7 +498,8 @@ bool MediaPlaylist::WriteToFile(const std::filesystem::path& file_path) { std::string content = CreatePlaylistHeader( media_info_, target_duration_, hls_params_.playlist_type, stream_type_, - media_sequence_number_, discontinuity_sequence_number_); + media_sequence_number_, discontinuity_sequence_number_, + hls_params_.start_time_offset); for (const auto& entry : entries_) absl::StrAppendFormat(&content, "%s\n", entry->ToString().c_str()); @@ -513,8 +533,8 @@ void MediaPlaylist::SetTargetDuration(int32_t target_duration) { if (target_duration_set_) { if (target_duration_ == target_duration) return; - VLOG(1) << "Updating target duration from " << target_duration << " to " - << target_duration_; + VLOG(1) << "Updating target duration from " << target_duration_ << " to " + << target_duration; } target_duration_ = target_duration; target_duration_set_ = true; @@ -563,10 +583,23 @@ std::string MediaPlaylist::GetVideoRange() const { // https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-02#section-4.4.4.2 switch (media_info_.video_info().transfer_characteristics()) { case 1: + case 6: + case 13: + case 14: + // Dolby Vision profile 8.4 may have a transfer_characteristics 14, the + // actual value refers to preferred_transfer_characteristic value in SEI + // message, using compatible brand as a workaround + if (!supplemental_codec_.empty() && + compatible_brand_ == media::FOURCC_db4g) + return "HLG"; + else + return "SDR"; + case 15: return "SDR"; case 16: - case 18: return "PQ"; + case 18: + return "HLG"; default: // Leave it empty if we do not have the transfer characteristics // information. @@ -724,9 +757,9 @@ void MediaPlaylist::RemoveOldSegment(int64_t start_time) { if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) return; - segments_to_be_removed_.push_back( - media::GetSegmentName(media_info_.segment_template(), start_time, - media_sequence_number_, media_info_.bandwidth())); + segments_to_be_removed_.push_back(media::GetSegmentName( + media_info_.segment_template(), start_time, media_sequence_number_ + 1, + media_info_.bandwidth())); while (segments_to_be_removed_.size() > hls_params_.preserved_segments_outside_live_window) { VLOG(2) << "Deleting " << segments_to_be_removed_.front(); diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index a97f251e86e..75127ccf7c1 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -17,6 +17,7 @@ #include #include #include +#include "packager/media/base/fourccs.h" namespace shaka { @@ -80,6 +81,8 @@ class MediaPlaylist { const std::string& group_id() const { return group_id_; } MediaPlaylistStreamType stream_type() const { return stream_type_; } const std::string& codec() const { return codec_; } + const std::string& supplemental_codec() const { return supplemental_codec_; } + const media::FourCC& compatible_brand() const { return compatible_brand_; } /// For testing only. void SetStreamTypeForTesting(MediaPlaylistStreamType stream_type); @@ -90,6 +93,9 @@ class MediaPlaylist { /// For testing only. void SetLanguageForTesting(const std::string& language); + /// For testing only. + void SetForcedSubtitleForTesting(const bool forced_subtitle); + /// For testing only. void SetCharacteristicsForTesting( const std::vector& characteristics); @@ -99,6 +105,7 @@ class MediaPlaylist { /// to this playlist. /// @return true on success, false otherwise. virtual bool SetMediaInfo(const MediaInfo& media_info); + MediaInfo GetMediaInfo() const { return media_info_; } /// Set the sample duration. Sample duration is used to generate frame rate. /// Sample duration is not available right away especially. This allows @@ -222,6 +229,8 @@ class MediaPlaylist { return characteristics_; } + bool forced_subtitle() const { return forced_subtitle_; } + bool is_dvs() const { // HLS Authoring Specification for Apple Devices // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices#overview @@ -259,8 +268,11 @@ class MediaPlaylist { // Whether to use byte range for SegmentInfoEntry. bool use_byte_range_ = false; std::string codec_; + std::string supplemental_codec_; + media::FourCC compatible_brand_; std::string language_; std::vector characteristics_; + bool forced_subtitle_ = false; uint32_t media_sequence_number_ = 0; bool inserted_discontinuity_tag_ = false; int discontinuity_sequence_number_ = 0; diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index 580dee4b836..7b17b412e2b 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -51,6 +51,10 @@ class MediaPlaylistTest : public ::testing::Test { default_group_id_("default_group_id") { hls_params_.playlist_type = type; hls_params_.time_shift_buffer_depth = kTimeShiftBufferDepth; + + // NOTE: hls_params_ is passed by and stored by reference in MediaPlaylist, + // so changed made to it through mutable_hls_params() after this point + // still affect what the playlist see in its own hls_params_ later. media_playlist_.reset(new MediaPlaylist(hls_params_, default_file_name_, default_name_, default_group_id_)); } @@ -658,6 +662,90 @@ TEST_F(MediaPlaylistMultiSegmentTest, MultipleEncryptionInfo) { ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } +TEST_F(MediaPlaylistSingleSegmentTest, StartTimeEmpty) { + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/shaka-project/shaka-packager " + "version test\n" + "#EXT-X-TARGETDURATION:0\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-ENDLIST\n"; + + // Because this is std::nullopt, the tag isn't in the playlist at all. + mutable_hls_params()->start_time_offset = std::nullopt; + + ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_)); + + const char kMemoryFilePath[] = "memory://media.m3u8"; + EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath)); + + ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); +} + +TEST_F(MediaPlaylistSingleSegmentTest, StartTimeZero) { + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/shaka-project/shaka-packager " + "version test\n" + "#EXT-X-TARGETDURATION:0\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-START:TIME-OFFSET=0.000000\n" + "#EXT-X-ENDLIST\n"; + + mutable_hls_params()->start_time_offset = 0; + + ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_)); + + const char kMemoryFilePath[] = "memory://media.m3u8"; + EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath)); + + ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); +} + +TEST_F(MediaPlaylistSingleSegmentTest, StartTimePositive) { + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/shaka-project/shaka-packager " + "version test\n" + "#EXT-X-TARGETDURATION:0\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-START:TIME-OFFSET=20.000000\n" + "#EXT-X-ENDLIST\n"; + + mutable_hls_params()->start_time_offset = 20; + + ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_)); + + const char kMemoryFilePath[] = "memory://media.m3u8"; + EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath)); + + ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); +} + +TEST_F(MediaPlaylistSingleSegmentTest, StartTimeNegative) { + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/shaka-project/shaka-packager " + "version test\n" + "#EXT-X-TARGETDURATION:0\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-START:TIME-OFFSET=-3.141590\n" + "#EXT-X-ENDLIST\n"; + + mutable_hls_params()->start_time_offset = -3.14159; + + ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_)); + + const char kMemoryFilePath[] = "memory://media.m3u8"; + EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath)); + + ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); +} + class LiveMediaPlaylistTest : public MediaPlaylistMultiSegmentTest { protected: LiveMediaPlaylistTest() @@ -1139,7 +1227,7 @@ INSTANTIATE_TEST_CASE_P(VideoRanges, Values(VideoRangeTestData{"hvc1.2.4.L63.90", 0, ""}, VideoRangeTestData{"hvc1.2.4.L63.90", 1, "SDR"}, VideoRangeTestData{"hvc1.2.4.L63.90", 16, "PQ"}, - VideoRangeTestData{"hvc1.2.4.L63.90", 18, "PQ"}, + VideoRangeTestData{"hvc1.2.4.L63.90", 18, "HLG"}, VideoRangeTestData{"dvh1.05.08", 0, "PQ"})); } // namespace hls diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index 9b85f83bd10..2ab7087aa5b 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -36,6 +36,7 @@ namespace hls { namespace { const char kUriBase64Prefix[] = "data:text/plain;base64,"; +const char kUriBase64Utf16Prefix[] = "data:text/plain;charset=UTF-16;base64,"; const char kUriFairPlayPrefix[] = "skd://"; const char kWidevineDashIfIopUUID[] = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; @@ -57,6 +58,18 @@ bool IsFairPlaySystemId(const std::vector& system_id) { media::kFairPlaySystemId); } +bool IsLegacyFairPlaySystemId(const std::vector& system_id) { + return system_id.size() == std::size(media::kLegacyFairPlaySystemId) && + std::equal(system_id.begin(), system_id.end(), + media::kLegacyFairPlaySystemId); +} + +bool IsPlayReadySystemId(const std::vector& system_id) { + return system_id.size() == std::size(media::kPlayReadySystemId) && + std::equal(system_id.begin(), system_id.end(), + media::kPlayReadySystemId); +} + std::string Base64EncodeData(const std::string& prefix, const std::string& data) { std::string data_base64; @@ -65,7 +78,7 @@ std::string Base64EncodeData(const std::string& prefix, } std::string VectorToString(const std::vector& v) { - return std::string(v.begin(), v.end()); + return std::string(v.begin(), v.end()); } // Segment URL is relative to either output directory or the directory @@ -455,7 +468,7 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate( iv, "identity", "", media_playlist.get()); return true; } - if (IsFairPlaySystemId(system_id)) { + if (IsFairPlaySystemId(system_id) || IsLegacyFairPlaySystemId(system_id)) { std::string key_uri = hls_params().key_uri; if (key_uri.empty()) { // Use key_id as the key_uri. The player needs to have custom logic to @@ -471,6 +484,20 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate( "1", media_playlist.get()); return true; } + if (IsPlayReadySystemId(system_id)) { + std::unique_ptr b = + media::PsshBoxBuilder::ParseFromBox( + protection_system_specific_data.data(), + protection_system_specific_data.size()); + std::string pssh_data(reinterpret_cast(b->pssh_data().data()), + b->pssh_data().size()); + std::string key_uri_data_base64 = + Base64EncodeData(kUriBase64Utf16Prefix, pssh_data); + NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64, + empty_key_id, iv, "com.microsoft.playready", + "1", media_playlist.get()); + return true; + } LOG(WARNING) << "HLS: Ignore unknown or unsupported system ID: " << absl::BytesToHexString(absl::string_view( diff --git a/packager/live_packager_test.cc b/packager/live_packager_test.cc index 43cb1e34836..7b9efef4594 100644 --- a/packager/live_packager_test.cc +++ b/packager/live_packager_test.cc @@ -1467,7 +1467,8 @@ TEST_P(TimedTextParameterizedTest, VerifyTimedText) { std::vector actual_buf(out.SegmentData(), out.SegmentData() + out.SegmentSize()); - ASSERT_EQ(expected_buf, actual_buf); + + EXPECT_EQ(expected_buf, actual_buf); } } } diff --git a/packager/macros/logging.h b/packager/macros/logging.h index 00be3041b44..ebec01bf46e 100644 --- a/packager/macros/logging.h +++ b/packager/macros/logging.h @@ -14,20 +14,4 @@ /// You can use the insertion operator to add specific logs to this. #define NOTIMPLEMENTED() LOG(ERROR) << "NOTIMPLEMENTED: " -#define VLOG(verboselevel) \ - LOG(LEVEL(static_cast(-verboselevel))) - -#define VLOG_IS_ON(verboselevel) \ - (static_cast(absl::MinLogLevel()) <= -verboselevel) - -#ifndef NDEBUG -#define DVLOG(verboselevel) VLOG(verboselevel) -#else -// We need this expression to work with << after it, so this is a simple way to -// turn DVLOG into a no-op in release builds. -#define DVLOG(verboselevel) \ - if (false) \ - VLOG(verboselevel) -#endif - #endif // PACKAGER_MACROS_LOGGING_H_ diff --git a/packager/media/base/aes_decryptor.cc b/packager/media/base/aes_decryptor.cc index 91820b72ab0..68fa99d9f60 100644 --- a/packager/media/base/aes_decryptor.cc +++ b/packager/media/base/aes_decryptor.cc @@ -48,8 +48,7 @@ bool AesCbcDecryptor::InitializeWithIv(const std::vector& key, } size_t AesCbcDecryptor::RequiredOutputSize(size_t plaintext_size) { - // mbedtls requires a buffer large enough for one extra block. - return plaintext_size + AES_BLOCK_SIZE; + return plaintext_size; } bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext, @@ -60,18 +59,12 @@ bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext, // Plaintext size is the same as ciphertext size except for pkcs5 padding. // Will update later if using pkcs5 padding. For pkcs5 padding, we still // need at least |ciphertext_size| bytes for intermediate operation. - // mbedtls requires a buffer large enough for one extra block. - // TODO: extra addition of AES_BLOCK_SIZE causes failure for Video segments - // encrypted with SAMPLE AES - const size_t required_plaintext_size = ciphertext_size; - if (*plaintext_size < required_plaintext_size) { - LOG(ERROR) << "Expecting output size of at least " - << required_plaintext_size << " bytes."; + if (*plaintext_size < ciphertext_size) { + LOG(ERROR) << "Expecting output size of at least " << ciphertext_size + << " bytes."; return false; } - // TODO: extra addition of AES_BLOCK_SIZE causes failure for Video segments - // encrypted with SAMPLE AES - *plaintext_size = required_plaintext_size; + *plaintext_size = ciphertext_size; // If the ciphertext size is 0, this can be a no-op decrypt, so long as the // padding mode isn't PKCS5. @@ -87,15 +80,9 @@ bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext, const size_t residual_block_size = ciphertext_size % AES_BLOCK_SIZE; const size_t cbc_size = ciphertext_size - residual_block_size; - - // Copy the residual block early, since mbedtls may overwrite one extra block - // of the output, and input and output may be the same buffer. - std::vector residual_block(ciphertext + cbc_size, - ciphertext + ciphertext_size); - DCHECK_EQ(residual_block.size(), residual_block_size); - if (residual_block_size == 0) { - CbcDecryptBlocks(ciphertext, ciphertext_size, plaintext); + CbcDecryptBlocks(ciphertext, ciphertext_size, plaintext, + internal_iv_.data()); if (padding_scheme_ != kPkcs5Padding) return true; @@ -109,10 +96,11 @@ bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext, *plaintext_size -= num_padding_bytes; return true; } else if (padding_scheme_ == kNoPadding) { - CbcDecryptBlocks(ciphertext, cbc_size, plaintext); - + if (cbc_size > 0) { + CbcDecryptBlocks(ciphertext, cbc_size, plaintext, internal_iv_.data()); + } // The residual block is not encrypted. - memcpy(plaintext + cbc_size, residual_block.data(), residual_block_size); + memcpy(plaintext + cbc_size, ciphertext + cbc_size, residual_block_size); return true; } else if (padding_scheme_ != kCtsPadding) { LOG(ERROR) << "Expecting cipher text size to be multiple of " @@ -127,49 +115,44 @@ bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext, return true; } - // Copy the next-to-last block early, since mbedtls may overwrite one extra - // block of the output, and input and output may be the same buffer. - // NOTE: Before this point, there may not be such a block. Here, we know - // this is safe. - std::vector next_to_last_block( - ciphertext + cbc_size - AES_BLOCK_SIZE, ciphertext + cbc_size); - // AES-CBC decrypt everything up to the next-to-last full block. if (cbc_size > AES_BLOCK_SIZE) { - CbcDecryptBlocks(ciphertext, cbc_size - AES_BLOCK_SIZE, plaintext); + CbcDecryptBlocks(ciphertext, cbc_size - AES_BLOCK_SIZE, plaintext, + internal_iv_.data()); + } + + const uint8_t* next_to_last_ciphertext_block = + ciphertext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE; + uint8_t* next_to_last_plaintext_block = + plaintext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE; + + // Determine what the last IV should be so that we can "skip ahead" in the + // CBC decryption. + std::vector last_iv( + ciphertext + ciphertext_size - residual_block_size, + ciphertext + ciphertext_size); + last_iv.resize(AES_BLOCK_SIZE, 0); + + // Decrypt the next-to-last block using the IV determined above. This decrypts + // the residual block bits. + CbcDecryptBlocks(next_to_last_ciphertext_block, AES_BLOCK_SIZE, + next_to_last_plaintext_block, last_iv.data()); + + // Swap back the residual block bits and the next-to-last block. + if (plaintext == ciphertext) { + std::swap_ranges(next_to_last_plaintext_block, + next_to_last_plaintext_block + residual_block_size, + next_to_last_plaintext_block + AES_BLOCK_SIZE); + } else { + memcpy(next_to_last_plaintext_block + AES_BLOCK_SIZE, + next_to_last_plaintext_block, residual_block_size); + memcpy(next_to_last_plaintext_block, + next_to_last_ciphertext_block + AES_BLOCK_SIZE, residual_block_size); } - uint8_t* next_to_last_plaintext_block = plaintext + cbc_size - AES_BLOCK_SIZE; - - // The next-to-last block should be decrypted first in ECB mode, which is - // effectively what you get with an IV of all zeroes. - std::vector backup_iv(internal_iv_); - internal_iv_.assign(AES_BLOCK_SIZE, 0); - // mbedtls requires a buffer large enough for one extra block. - std::vector stolen_bits(AES_BLOCK_SIZE * 2); - CbcDecryptBlocks(next_to_last_block.data(), AES_BLOCK_SIZE, - stolen_bits.data()); - - // Reconstruct the final two blocks of ciphertext. - std::vector reconstructed_blocks(AES_BLOCK_SIZE * 2); - memcpy(reconstructed_blocks.data(), residual_block.data(), - residual_block_size); - memcpy(reconstructed_blocks.data() + residual_block_size, - stolen_bits.data() + residual_block_size, - AES_BLOCK_SIZE - residual_block_size); - memcpy(reconstructed_blocks.data() + AES_BLOCK_SIZE, - next_to_last_block.data(), AES_BLOCK_SIZE); - - // Decrypt the last two blocks. - internal_iv_ = backup_iv; - // mbedtls requires a buffer large enough for one extra block. - std::vector final_output_blocks(AES_BLOCK_SIZE * 3); - CbcDecryptBlocks(reconstructed_blocks.data(), AES_BLOCK_SIZE * 2, - final_output_blocks.data()); - - // Copy the final output. - memcpy(next_to_last_plaintext_block, final_output_blocks.data(), - AES_BLOCK_SIZE + residual_block_size); + // Decrypt the next-to-last full block. + CbcDecryptBlocks(next_to_last_plaintext_block, AES_BLOCK_SIZE, + next_to_last_plaintext_block, internal_iv_.data()); return true; } @@ -180,7 +163,8 @@ void AesCbcDecryptor::SetIvInternal() { void AesCbcDecryptor::CbcDecryptBlocks(const uint8_t* ciphertext, size_t ciphertext_size, - uint8_t* plaintext) { + uint8_t* plaintext, + uint8_t* iv) { CHECK_EQ(ciphertext_size % AES_BLOCK_SIZE, 0u); CHECK_GT(ciphertext_size, 0u); @@ -190,14 +174,12 @@ void AesCbcDecryptor::CbcDecryptBlocks(const uint8_t* ciphertext, std::vector next_iv(last_block, last_block + AES_BLOCK_SIZE); size_t output_size = 0; - CHECK_EQ(mbedtls_cipher_crypt(&cipher_ctx_, internal_iv_.data(), - AES_BLOCK_SIZE, ciphertext, ciphertext_size, - plaintext, &output_size), + CHECK_EQ(mbedtls_cipher_crypt(&cipher_ctx_, iv, AES_BLOCK_SIZE, ciphertext, + ciphertext_size, plaintext, &output_size), 0); DCHECK_EQ(output_size % AES_BLOCK_SIZE, 0u); - // Update the internal IV. - internal_iv_ = next_iv; + memcpy(iv, next_iv.data(), next_iv.size()); } } // namespace media diff --git a/packager/media/base/aes_decryptor.h b/packager/media/base/aes_decryptor.h index c28145acf0b..840e12b3997 100644 --- a/packager/media/base/aes_decryptor.h +++ b/packager/media/base/aes_decryptor.h @@ -58,7 +58,8 @@ class AesCbcDecryptor : public AesCryptor { void SetIvInternal() override; void CbcDecryptBlocks(const uint8_t* plaintext, size_t plaintext_size, - uint8_t* ciphertext); + uint8_t* ciphertext, + uint8_t* iv); const CbcPaddingScheme padding_scheme_; // 16-byte internal iv for crypto operations. diff --git a/packager/media/base/aes_encryptor.cc b/packager/media/base/aes_encryptor.cc index 6250464a2d7..2dc47078574 100644 --- a/packager/media/base/aes_encryptor.cc +++ b/packager/media/base/aes_encryptor.cc @@ -129,9 +129,6 @@ bool AesCbcEncryptor::InitializeWithIv(const std::vector& key, } size_t AesCbcEncryptor::RequiredOutputSize(size_t plaintext_size) { - // mbedtls requires a buffer large enough for one extra block. - // TODO: the extra addition of AES_BLOCK_SIZE seems to be causing issues for - // video only Sample-AES encryption and a recent regression in functionality. return plaintext_size + NumPaddingBytes(plaintext_size); } @@ -162,7 +159,7 @@ bool AesCbcEncryptor::CryptInternal(const uint8_t* plaintext, DCHECK_EQ(residual_block.size(), residual_block_size); if (cbc_size != 0) { - CbcEncryptBlocks(plaintext, cbc_size, ciphertext); + CbcEncryptBlocks(plaintext, cbc_size, ciphertext, internal_iv_.data()); } else if (padding_scheme_ == kCtsPadding) { // Don't have a full block, leave unencrypted. memcpy(ciphertext, plaintext, plaintext_size); @@ -185,21 +182,16 @@ bool AesCbcEncryptor::CryptInternal(const uint8_t* plaintext, // Pad residue block with PKCS5 padding. residual_block.resize(AES_BLOCK_SIZE, static_cast(num_padding_bytes)); - CbcEncryptBlocks(residual_block.data(), AES_BLOCK_SIZE, - residual_ciphertext_block); + residual_ciphertext_block, internal_iv_.data()); } else { DCHECK_EQ(num_padding_bytes, 0u); DCHECK_EQ(padding_scheme_, kCtsPadding); // Zero-pad the residual block and encrypt using CBC. residual_block.resize(AES_BLOCK_SIZE, 0); - // mbedtls requires an extra block in the output buffer, and it cannot be - // the same as the input buffer. - std::vector encrypted_residual_block(AES_BLOCK_SIZE * 2); - CbcEncryptBlocks(residual_block.data(), AES_BLOCK_SIZE, - encrypted_residual_block.data()); + residual_block.data(), internal_iv_.data()); // Replace the last full block with the zero-padded, encrypted residual // block, and replace the residual block with the equivalent portion of the @@ -210,8 +202,8 @@ bool AesCbcEncryptor::CryptInternal(const uint8_t* plaintext, // https://en.wikipedia.org/wiki/Ciphertext_stealing#CS2 memcpy(residual_ciphertext_block, residual_ciphertext_block - AES_BLOCK_SIZE, residual_block_size); - memcpy(residual_ciphertext_block - AES_BLOCK_SIZE, - encrypted_residual_block.data(), AES_BLOCK_SIZE); + memcpy(residual_ciphertext_block - AES_BLOCK_SIZE, residual_block.data(), + AES_BLOCK_SIZE); } return true; } @@ -229,20 +221,20 @@ size_t AesCbcEncryptor::NumPaddingBytes(size_t size) const { void AesCbcEncryptor::CbcEncryptBlocks(const uint8_t* plaintext, size_t plaintext_size, - uint8_t* ciphertext) { + uint8_t* ciphertext, + uint8_t* iv) { CHECK_EQ(plaintext_size % AES_BLOCK_SIZE, 0u); size_t output_size = 0; - CHECK_EQ( - mbedtls_cipher_crypt(&cipher_ctx_, internal_iv_.data(), AES_BLOCK_SIZE, - plaintext, plaintext_size, ciphertext, &output_size), - 0); + CHECK_EQ(mbedtls_cipher_crypt(&cipher_ctx_, iv, AES_BLOCK_SIZE, plaintext, + plaintext_size, ciphertext, &output_size), + 0); CHECK_EQ(output_size % AES_BLOCK_SIZE, 0u); CHECK_GT(output_size, 0u); uint8_t* last_block = ciphertext + output_size - AES_BLOCK_SIZE; - internal_iv_.assign(last_block, last_block + AES_BLOCK_SIZE); + memcpy(iv, last_block, AES_BLOCK_SIZE); } } // namespace media diff --git a/packager/media/base/aes_encryptor.h b/packager/media/base/aes_encryptor.h index 0fdee5d32ed..ee1e90df23f 100644 --- a/packager/media/base/aes_encryptor.h +++ b/packager/media/base/aes_encryptor.h @@ -96,7 +96,8 @@ class AesCbcEncryptor : public AesCryptor { void CbcEncryptBlocks(const uint8_t* plaintext, size_t plaintext_size, - uint8_t* ciphertext); + uint8_t* ciphertext, + uint8_t* iv); const CbcPaddingScheme padding_scheme_; // 16-byte internal iv for crypto operations. diff --git a/packager/media/base/audio_stream_info.cc b/packager/media/base/audio_stream_info.cc index 35a2a5e68b6..bb1a6b1975c 100644 --- a/packager/media/base/audio_stream_info.cc +++ b/packager/media/base/audio_stream_info.cc @@ -25,6 +25,8 @@ std::string AudioCodecToString(Codec codec) { return "AAC"; case kCodecAC3: return "AC3"; + case kCodecALAC: + return "ALAC"; case kCodecDTSC: return "DTSC"; case kCodecDTSE: @@ -138,6 +140,8 @@ std::string AudioStreamInfo::GetCodecString(Codec codec, return "mp4a.40." + absl::StrFormat("%hhu", audio_object_type); case kCodecAC3: return "ac-3"; + case kCodecALAC: + return "alac"; case kCodecDTSC: return "dtsc"; case kCodecDTSE: @@ -150,6 +154,8 @@ std::string AudioStreamInfo::GetCodecString(Codec codec, return "dts-"; case kCodecDTSP: return "dts+"; + case kCodecDTSX: + return "dtsx"; case kCodecEAC3: return "ec-3"; case kCodecAC4: diff --git a/packager/media/base/audio_stream_info.h b/packager/media/base/audio_stream_info.h index 7e1bfadc93a..ed6af816739 100644 --- a/packager/media/base/audio_stream_info.h +++ b/packager/media/base/audio_stream_info.h @@ -60,6 +60,10 @@ class AudioStreamInfo : public StreamInfo { sampling_frequency_ = sampling_frequency; } + void set_max_bitrate(const uint32_t max_bitrate) { + max_bitrate_ = max_bitrate; + } + /// @param audio_object_type is only used by AAC Codec, ignored otherwise. /// @return The codec string. static std::string GetCodecString(Codec codec, uint8_t audio_object_type); diff --git a/packager/media/base/fourccs.h b/packager/media/base/fourccs.h index dd6ec2de21c..dee70bfd425 100644 --- a/packager/media/base/fourccs.h +++ b/packager/media/base/fourccs.h @@ -24,6 +24,7 @@ enum FourCC : uint32_t { FOURCC_ac_3 = 0x61632d33, // "ac-3" FOURCC_ac_4 = 0x61632d34, // "ac-4" FOURCC_ac3d = 0x61633364, + FOURCC_alac = 0x616c6163, FOURCC_apad = 0x61706164, FOURCC_av01 = 0x61763031, FOURCC_av1C = 0x61763143, @@ -47,6 +48,11 @@ enum FourCC : uint32_t { FOURCC_dac3 = 0x64616333, FOURCC_dac4 = 0x64616334, FOURCC_dash = 0x64617368, + FOURCC_dav1 = 0x64617631, + FOURCC_db1p = 0x64623170, + FOURCC_db2g = 0x64623267, + FOURCC_db4g = 0x64623467, + FOURCC_db4h = 0x64623468, FOURCC_dby1 = 0x64627931, FOURCC_ddts = 0x64647473, FOURCC_dec3 = 0x64656333, @@ -59,6 +65,7 @@ enum FourCC : uint32_t { FOURCC_dtsl = 0x6474736c, FOURCC_dtsm = 0x6474732d, // "dts-" FOURCC_dtsp = 0x6474732b, // "dts+" + FOURCC_dtsx = 0x64747378, // "dtsx" FOURCC_dvcC = 0x64766343, FOURCC_dvh1 = 0x64766831, FOURCC_dvhe = 0x64766865, @@ -152,8 +159,9 @@ enum FourCC : uint32_t { FOURCC_trex = 0x74726578, FOURCC_trun = 0x7472756e, FOURCC_udta = 0x75647461, - FOURCC_url = 0x75726c20, // "url " - FOURCC_urn = 0x75726e20, // "urn " + FOURCC_udts = 0x75647473, // "udts" + FOURCC_url = 0x75726c20, // "url " + FOURCC_urn = 0x75726e20, // "urn " FOURCC_uuid = 0x75756964, FOURCC_vide = 0x76696465, FOURCC_vlab = 0x766c6162, diff --git a/packager/media/base/id3_tag_unittest.cc b/packager/media/base/id3_tag_unittest.cc index 761a6688f53..8157907212e 100644 --- a/packager/media/base/id3_tag_unittest.cc +++ b/packager/media/base/id3_tag_unittest.cc @@ -6,6 +6,10 @@ #include +#include + +#include + #include #include diff --git a/packager/media/base/media_handler.h b/packager/media/base/media_handler.h index e2975ad52e5..9608145a2b8 100644 --- a/packager/media/base/media_handler.h +++ b/packager/media/base/media_handler.h @@ -59,6 +59,7 @@ struct SegmentInfo { bool is_encrypted = false; int64_t start_timestamp = -1; int64_t duration = 0; + int64_t segment_number = 1; // This is only available if key rotation is enabled. Note that we may have // a |key_rotation_encryption_config| even if the segment is not encrypted, // which is the case for clear lead. diff --git a/packager/media/base/media_handler_test_base.cc b/packager/media/base/media_handler_test_base.cc index 0a5b28e91c3..a9c78f91009 100644 --- a/packager/media/base/media_handler_test_base.cc +++ b/packager/media/base/media_handler_test_base.cc @@ -31,7 +31,10 @@ const uint32_t kWidth = 10u; const uint32_t kHeight = 20u; const uint32_t kPixelWidth = 2u; const uint32_t kPixelHeight = 3u; +const uint8_t kColorPrimaries = 0; +const uint8_t kMatrixCoefficients = 0; const uint8_t kTransferCharacteristics = 0; + const int16_t kTrickPlayFactor = 0; const uint8_t kNaluLengthSize = 1u; const bool kEncrypted = true; @@ -207,8 +210,9 @@ std::unique_ptr MediaHandlerTestBase::GetVideoStreamInfo( return std::unique_ptr(new VideoStreamInfo( kTrackId, time_scale, kDuration, codec, H26xStreamFormat::kUnSpecified, kCodecString, kCodecConfig, sizeof(kCodecConfig), width, height, - kPixelWidth, kPixelHeight, kTransferCharacteristics, kTrickPlayFactor, - kNaluLengthSize, kLanguage, !kEncrypted)); + kPixelWidth, kPixelHeight, kColorPrimaries, kMatrixCoefficients, + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + !kEncrypted)); } std::unique_ptr MediaHandlerTestBase::GetAudioStreamInfo( @@ -251,11 +255,13 @@ std::shared_ptr MediaHandlerTestBase::GetMediaSample( std::unique_ptr MediaHandlerTestBase::GetSegmentInfo( int64_t start_timestamp, int64_t duration, - bool is_subsegment) const { + bool is_subsegment, + int64_t segment_number) const { std::unique_ptr info(new SegmentInfo); info->start_timestamp = start_timestamp; info->duration = duration; info->is_subsegment = is_subsegment; + info->segment_number = segment_number; return info; } diff --git a/packager/media/base/media_handler_test_base.h b/packager/media/base/media_handler_test_base.h index 980948a5e4a..6b5cf3c2741 100644 --- a/packager/media/base/media_handler_test_base.h +++ b/packager/media/base/media_handler_test_base.h @@ -325,7 +325,8 @@ class MediaHandlerTestBase : public ::testing::Test { std::unique_ptr GetSegmentInfo(int64_t start_timestamp, int64_t duration, - bool is_subsegment) const; + bool is_subsegment, + int64_t segment_number) const; std::unique_ptr GetTextStreamInfo(int32_t timescale) const; diff --git a/packager/media/base/muxer.h b/packager/media/base/muxer.h index 8a073cb2bbe..9e997623834 100644 --- a/packager/media/base/muxer.h +++ b/packager/media/base/muxer.h @@ -54,7 +54,7 @@ class Muxer : public MediaHandler { /// If no clock is injected, the code uses std::chrone::system_clock::now() /// to generate the time-stamps. /// @param clock is the Clock to be injected. - void set_clock(Clock* clock) { clock_.reset(clock); } + void set_clock(std::shared_ptr clock) { clock_ = clock; } protected: /// @name MediaHandler implementation overrides. @@ -112,12 +112,12 @@ class Muxer : public MediaHandler { std::unique_ptr muxer_listener_; std::unique_ptr progress_listener_; - std::unique_ptr clock_; + std::shared_ptr clock_; // In VOD single segment case with Ad Cues, |output_file_name| is allowed to // be a template. In this case, there will be NumAdCues + 1 files generated. std::string output_file_template_; - size_t output_file_index_ = 0; + size_t output_file_index_ = 1; }; } // namespace media diff --git a/packager/media/base/muxer_util.cc b/packager/media/base/muxer_util.cc index bd6028da6f7..a928d868f9e 100644 --- a/packager/media/base/muxer_util.cc +++ b/packager/media/base/muxer_util.cc @@ -110,7 +110,7 @@ Status ValidateSegmentTemplate(const std::string& segment_template) { std::string GetSegmentName(const std::string& segment_template, int64_t segment_start_time, - uint32_t segment_index, + uint32_t segment_number, uint32_t bandwidth) { DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template)); @@ -154,7 +154,7 @@ std::string GetSegmentName(const std::string& segment_template, absl::UntypedFormatSpec format(format_tag); if (identifier == "Number") { // SegmentNumber starts from 1. - format_args.emplace_back(static_cast(segment_index + 1)); + format_args.emplace_back(static_cast(segment_number)); } else if (identifier == "Time") { format_args.emplace_back(static_cast(segment_start_time)); } else if (identifier == "Bandwidth") { diff --git a/packager/media/base/muxer_util.h b/packager/media/base/muxer_util.h index 02473ecb511..7cf797c0226 100644 --- a/packager/media/base/muxer_util.h +++ b/packager/media/base/muxer_util.h @@ -29,12 +29,12 @@ Status ValidateSegmentTemplate(const std::string& segment_template); /// @param segment_template is the segment template pattern, which should /// comply with ISO/IEC 23009-1:2012 5.3.9.4.4. /// @param segment_start_time specifies the segment start time. -/// @param segment_index specifies the segment index. +/// @param segment_number specifies the segment number. /// @param bandwidth represents the bit rate, in bits/sec, of the stream. /// @return The segment name with identifier substituted. std::string GetSegmentName(const std::string& segment_template, int64_t segment_start_time, - uint32_t segment_index, + uint32_t segment_number, uint32_t bandwidth); } // namespace media diff --git a/packager/media/base/muxer_util_unittest.cc b/packager/media/base/muxer_util_unittest.cc index 0f410ff8851..58e58122b49 100644 --- a/packager/media/base/muxer_util_unittest.cc +++ b/packager/media/base/muxer_util_unittest.cc @@ -62,95 +62,57 @@ TEST(MuxerUtilTest, ValidateSegmentTemplateWithFormatTag) { TEST(MuxerUtilTest, GetSegmentName) { const int64_t kSegmentStartTime = 180180; - const uint32_t kSegmentIndex = 11; + const uint32_t kSegmentNumber = 12; const uint32_t kBandwidth = 1234; - EXPECT_EQ("12", GetSegmentName("$Number$", - kSegmentStartTime, - kSegmentIndex, + EXPECT_EQ("12", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber, kBandwidth)); - EXPECT_EQ("012", - GetSegmentName("$Number%03d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - EXPECT_EQ( - "12$foo$00012", - GetSegmentName( - "$Number%01d$$$foo$$$Number%05d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - - EXPECT_EQ("180180", - GetSegmentName("$Time$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("012", GetSegmentName("$Number%03d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + EXPECT_EQ("12$foo$00012", + GetSegmentName("$Number%01d$$$foo$$$Number%05d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + + EXPECT_EQ("180180", GetSegmentName("$Time$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); EXPECT_EQ("foo$_$18018000180180.m4s", GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + kSegmentStartTime, kSegmentNumber, kBandwidth)); // Combo values. - EXPECT_EQ("12-1234", - GetSegmentName("$Number$-$Bandwidth$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("12-1234", GetSegmentName("$Number$-$Bandwidth$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); EXPECT_EQ("012-001234", - GetSegmentName("$Number%03d$-$Bandwidth%06d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + GetSegmentName("$Number%03d$-$Bandwidth%06d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); // Format specifier edge cases. - EXPECT_EQ("12", - GetSegmentName("$Number%00d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - EXPECT_EQ("00012", - GetSegmentName("$Number%005d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("12", GetSegmentName("$Number%00d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + EXPECT_EQ("00012", GetSegmentName("$Number%005d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) { const int64_t kSegmentStartTime = 0; - const uint32_t kSegmentIndex = 0; + const uint32_t kSegmentNumber = 1; const uint32_t kBandwidth = 0; - EXPECT_EQ("1", GetSegmentName("$Number$", - kSegmentStartTime, - kSegmentIndex, + EXPECT_EQ("1", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber, kBandwidth)); - EXPECT_EQ("001", - GetSegmentName("$Number%03d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - - EXPECT_EQ("0", GetSegmentName("$Time$", - kSegmentStartTime, - kSegmentIndex, + EXPECT_EQ("001", GetSegmentName("$Number%03d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + + EXPECT_EQ("0", GetSegmentName("$Time$", kSegmentStartTime, kSegmentNumber, kBandwidth)); - EXPECT_EQ("00000000.m4s", - GetSegmentName("$Time%08d$.m4s", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("00000000.m4s", GetSegmentName("$Time%08d$.m4s", kSegmentStartTime, + kSegmentNumber, kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameLargeTime) { const int64_t kSegmentStartTime = 1601599839840ULL; - const uint32_t kSegmentIndex = 8888888; + const uint32_t kSegmentNumber = 8888889; const uint32_t kBandwidth = 444444; - EXPECT_EQ("1601599839840", - GetSegmentName("$Time$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("1601599839840", GetSegmentName("$Time$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); } } // namespace media diff --git a/packager/media/base/playready_pssh_generator.cc b/packager/media/base/playready_pssh_generator.cc index 862722bd871..40f5cfec769 100644 --- a/packager/media/base/playready_pssh_generator.cc +++ b/packager/media/base/playready_pssh_generator.cc @@ -75,8 +75,7 @@ void AesEcbEncrypt(const std::vector& key, const std::vector& plaintext, std::vector* ciphertext) { CHECK_EQ(plaintext.size() % AES_BLOCK_SIZE, 0u); - // mbedtls requires an extra block worth of output buffer. - ciphertext->resize(plaintext.size() + AES_BLOCK_SIZE); + ciphertext->resize(plaintext.size()); mbedtls_cipher_context_t ctx; mbedtls_cipher_init(&ctx); @@ -98,8 +97,6 @@ void AesEcbEncrypt(const std::vector& key, plaintext.data(), plaintext.size(), ciphertext->data(), &output_size), 0); - // Truncate the output to the correct size. - ciphertext->resize(plaintext.size()); mbedtls_cipher_free(&ctx); } diff --git a/packager/media/base/protection_system_ids.h b/packager/media/base/protection_system_ids.h index 7948e7ecebc..5ddaf8cb071 100644 --- a/packager/media/base/protection_system_ids.h +++ b/packager/media/base/protection_system_ids.h @@ -19,11 +19,15 @@ const uint8_t kCommonSystemId[] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b}; -// Unofficial FairPlay system id extracted from -// https://forums.developer.apple.com/thread/6185. -const uint8_t kFairPlaySystemId[] = {0x29, 0x70, 0x1F, 0xE4, 0x3C, 0xC7, - 0x4A, 0x34, 0x8C, 0x5B, 0xAE, 0x90, - 0xC7, 0x43, 0x9A, 0x47}; +const uint8_t kFairPlaySystemId[] = {0x94, 0xCE, 0x86, 0xFB, 0x07, 0xFF, + 0x4F, 0x43, 0xAD, 0xB8, 0x93, 0xD2, + 0xFA, 0x96, 0x8C, 0xA2}; + +// this is a legacy system ID used for FairPlay in old packager versions, kept +// for backwards compatibility only +const uint8_t kLegacyFairPlaySystemId[] = {0x29, 0x70, 0x1F, 0xE4, 0x3C, 0xC7, + 0x4A, 0x34, 0x8C, 0x5B, 0xAE, 0x90, + 0xC7, 0x43, 0x9A, 0x47}; // Marlin Adaptive Streaming Specification – Simple Profile, V1.0. const uint8_t kMarlinSystemId[] = {0x5E, 0x62, 0x9A, 0xF5, 0x38, 0xDA, diff --git a/packager/media/base/stream_info.h b/packager/media/base/stream_info.h index 947d40818ca..0a84d898db7 100644 --- a/packager/media/base/stream_info.h +++ b/packager/media/base/stream_info.h @@ -41,6 +41,7 @@ enum Codec { kCodecAAC = kCodecAudio, kCodecAC3, kCodecAC4, + kCodecALAC, // TODO(kqyang): Use kCodecDTS and a kDtsStreamFormat for the various DTS // streams. kCodecDTSC, @@ -49,6 +50,7 @@ enum Codec { kCodecDTSL, kCodecDTSM, kCodecDTSP, + kCodecDTSX, kCodecEAC3, kCodecFlac, kCodecOpus, diff --git a/packager/media/base/text_muxer.cc b/packager/media/base/text_muxer.cc index 70c967423c5..e6e07dcab1c 100644 --- a/packager/media/base/text_muxer.cc +++ b/packager/media/base/text_muxer.cc @@ -50,9 +50,13 @@ Status TextMuxer::Finalize() { // Insert a dummy value so the HLS generator will generate a segment list. ranges.subsegment_ranges.emplace_back(); + // The segment number does not matter for single segment output. + const uint32_t kArbitrarySegmentNumber = 0; + muxer_listener()->OnNewSegment( options().output_file_name, 0, - duration_seconds * streams()[0]->time_scale(), size); + duration_seconds * streams()[0]->time_scale(), size, + kArbitrarySegmentNumber); } muxer_listener()->OnMediaEnd(ranges, duration_seconds); @@ -82,17 +86,20 @@ Status TextMuxer::FinalizeSegment(size_t stream_id, const std::string& segment_template = options().segment_template; DCHECK(!segment_template.empty()); - const uint32_t index = segment_index_++; + const int64_t start = segment_info.start_timestamp; const int64_t duration = segment_info.duration; + const uint32_t segment_number = segment_info.segment_number; + const uint32_t bandwidth = options().bandwidth; const std::string filename = - GetSegmentName(segment_template, start, index, bandwidth); + GetSegmentName(segment_template, start, segment_number, bandwidth); uint64_t size; RETURN_IF_ERROR(WriteToFile(filename, &size)); - muxer_listener()->OnNewSegment(filename, start, duration, size); + muxer_listener()->OnNewSegment(filename, start, duration, size, + segment_number); return Status::OK; } diff --git a/packager/media/base/text_muxer.h b/packager/media/base/text_muxer.h index 0b4c9be58de..68ad1bd18f5 100644 --- a/packager/media/base/text_muxer.h +++ b/packager/media/base/text_muxer.h @@ -38,7 +38,6 @@ class TextMuxer : public Muxer { int64_t total_duration_ms_ = 0; int64_t last_cue_ms_ = 0; - uint32_t segment_index_ = 0; }; } // namespace media diff --git a/packager/media/base/text_sample.h b/packager/media/base/text_sample.h index 81f78eac676..22b46479842 100644 --- a/packager/media/base/text_sample.h +++ b/packager/media/base/text_sample.h @@ -80,6 +80,11 @@ struct TextFragmentStyle { std::optional underline; std::optional bold; std::optional italic; + // The colors could be any string that can be interpreted as + // a color in TTML (or WebVTT). As a start, the 8 teletext colors are used, + // i.e. black, red, green, yellow, blue, magenta, cyan, and white + std::string color; + std::string backgroundColor; }; /// Represents a recursive structure of styled blocks of text. Only one of diff --git a/packager/media/base/video_stream_info.cc b/packager/media/base/video_stream_info.cc index 1512ce83c2d..b557482bf2e 100644 --- a/packager/media/base/video_stream_info.cc +++ b/packager/media/base/video_stream_info.cc @@ -50,6 +50,8 @@ VideoStreamInfo::VideoStreamInfo(int track_id, uint32_t height, uint32_t pixel_width, uint32_t pixel_height, + uint8_t color_primaries, + uint8_t matrix_coefficients, uint8_t transfer_characteristics, uint32_t trick_play_factor, uint8_t nalu_length_size, @@ -71,6 +73,8 @@ VideoStreamInfo::VideoStreamInfo(int track_id, pixel_width_(pixel_width), pixel_height_(pixel_height), transfer_characteristics_(transfer_characteristics), + color_primaries_(color_primaries), + matrix_coefficients_(matrix_coefficients), trick_play_factor_(trick_play_factor), nalu_length_size_(nalu_length_size) {} diff --git a/packager/media/base/video_stream_info.h b/packager/media/base/video_stream_info.h index 5e6f24345d7..8f5e705ee4f 100644 --- a/packager/media/base/video_stream_info.h +++ b/packager/media/base/video_stream_info.h @@ -39,6 +39,8 @@ class VideoStreamInfo : public StreamInfo { uint32_t height, uint32_t pixel_width, uint32_t pixel_height, + uint8_t color_primaries, + uint8_t matrix_coefficients, uint8_t transfer_characteristics, uint32_t trick_play_factor, uint8_t nalu_length_size, @@ -54,6 +56,8 @@ class VideoStreamInfo : public StreamInfo { std::unique_ptr Clone() const override; /// @} + const std::string supplemental_codec() const { return supplemental_codec_; } + FourCC compatible_brand() const { return compatible_brand_; } const std::vector& extra_config() const { return extra_config_; } H26xStreamFormat h26x_stream_format() const { return h26x_stream_format_; } uint32_t width() const { return width_; } @@ -65,12 +69,22 @@ class VideoStreamInfo : public StreamInfo { /// @return 0 if unknown. uint32_t pixel_height() const { return pixel_height_; } uint8_t transfer_characteristics() const { return transfer_characteristics_; } + uint8_t color_primaries() const { return color_primaries_; } + uint8_t matrix_coefficients() const { return matrix_coefficients_; } uint8_t nalu_length_size() const { return nalu_length_size_; } uint32_t trick_play_factor() const { return trick_play_factor_; } uint32_t playback_rate() const { return playback_rate_; } const std::vector& eme_init_data() const { return eme_init_data_; } const std::vector& colr_data() const { return colr_data_; } + void set_supplemental_codec(const std::string supplemental_codec) { + supplemental_codec_ = supplemental_codec; + } + + void set_compatible_brand(const FourCC compatible_brand) { + compatible_brand_ = compatible_brand; + } + void set_extra_config(const std::vector& extra_config) { extra_config_ = extra_config; } @@ -81,6 +95,12 @@ class VideoStreamInfo : public StreamInfo { void set_transfer_characteristics(uint8_t transfer_characteristics) { transfer_characteristics_ = transfer_characteristics; } + void set_color_primaries(uint8_t color_primaries) { + color_primaries_ = color_primaries; + } + void set_matrix_coefficients(uint8_t matrix_coefficients) { + matrix_coefficients_ = matrix_coefficients; + } void set_trick_play_factor(uint32_t trick_play_factor) { trick_play_factor_ = trick_play_factor; } @@ -98,6 +118,8 @@ class VideoStreamInfo : public StreamInfo { private: // Extra codec configuration in a stream of mp4 boxes. It is only applicable // to mp4 container only. It is needed by some codecs, e.g. Dolby Vision. + std::string supplemental_codec_ = ""; + FourCC compatible_brand_ = FOURCC_NULL; std::vector extra_config_; H26xStreamFormat h26x_stream_format_; uint32_t width_; @@ -108,6 +130,8 @@ class VideoStreamInfo : public StreamInfo { uint32_t pixel_width_; uint32_t pixel_height_; uint8_t transfer_characteristics_ = 0; + uint8_t color_primaries_ = 0; + uint8_t matrix_coefficients_ = 0; uint32_t trick_play_factor_ = 0; // Non-zero for trick-play streams. // Playback rate is the attribute for trick play stream, which signals the diff --git a/packager/media/chunking/chunking_handler.cc b/packager/media/chunking/chunking_handler.cc index bcabf961274..388683a8678 100644 --- a/packager/media/chunking/chunking_handler.cc +++ b/packager/media/chunking/chunking_handler.cc @@ -34,6 +34,7 @@ bool IsNewSegmentIndex(int64_t new_index, int64_t current_index) { ChunkingHandler::ChunkingHandler(const ChunkingParams& chunking_params) : chunking_params_(chunking_params) { CHECK_NE(chunking_params.segment_duration_in_seconds, 0u); + segment_number_ = chunking_params.start_segment_number; } Status ChunkingHandler::InitializeInternal() { @@ -163,17 +164,20 @@ Status ChunkingHandler::OnMediaSample( return DispatchMediaSample(kStreamIndex, std::move(sample)); } -Status ChunkingHandler::EndSegmentIfStarted() const { +Status ChunkingHandler::EndSegmentIfStarted() { if (!segment_start_time_) return Status::OK; auto segment_info = std::make_shared(); segment_info->start_timestamp = segment_start_time_.value(); segment_info->duration = max_segment_time_ - segment_start_time_.value(); + segment_info->segment_number = segment_number_++; + if (chunking_params_.low_latency_dash_mode) { segment_info->is_chunk = true; segment_info->is_final_chunk_in_seg = true; } + return DispatchSegmentInfo(kStreamIndex, std::move(segment_info)); } diff --git a/packager/media/chunking/chunking_handler.h b/packager/media/chunking/chunking_handler.h index 717ccec3e10..8706e2bf128 100644 --- a/packager/media/chunking/chunking_handler.h +++ b/packager/media/chunking/chunking_handler.h @@ -60,7 +60,7 @@ class ChunkingHandler : public MediaHandler { Status OnCueEvent(std::shared_ptr event); Status OnMediaSample(std::shared_ptr sample); - Status EndSegmentIfStarted() const; + Status EndSegmentIfStarted(); Status EndSubsegmentIfStarted() const; bool IsSubsegmentEnabled() { @@ -74,8 +74,13 @@ class ChunkingHandler : public MediaHandler { int64_t segment_duration_ = 0; int64_t subsegment_duration_ = 0; + // Segment number that keeps monotically increasing. + // Set to start_segment_number in constructor. + int64_t segment_number_ = 1; + // Current segment index, useful to determine where to do chunking. int64_t current_segment_index_ = -1; + // Current subsegment index, useful to determine where to do chunking. int64_t current_subsegment_index_ = -1; diff --git a/packager/media/chunking/text_chunker.cc b/packager/media/chunking/text_chunker.cc index 9b4d0627834..0293faaf525 100644 --- a/packager/media/chunking/text_chunker.cc +++ b/packager/media/chunking/text_chunker.cc @@ -17,10 +17,12 @@ const size_t kStreamIndex = 0; } // namespace TextChunker::TextChunker(double segment_duration_in_seconds, + int64_t start_segment_number, int64_t timed_text_decode_time, bool adjust_sample_boundaries) : segment_duration_in_seconds_(segment_duration_in_seconds), segment_start_(timed_text_decode_time), + segment_number_(start_segment_number), adjust_sample_boundaries_(adjust_sample_boundaries){}; Status TextChunker::Process(std::unique_ptr data) { @@ -64,14 +66,12 @@ Status TextChunker::OnCueEvent(std::shared_ptr event) { // Convert the event's time to be scaled to the time of each sample. const int64_t event_time = ScaleTime(event->time_in_seconds); - // Output all full segments before the segment that the cue event interupts. while (segment_start_ + segment_duration_ < event_time) { RETURN_IF_ERROR(DispatchSegment(segment_duration_)); } const int64_t shorten_duration = event_time - segment_start_; - RETURN_IF_ERROR(DispatchSegment(shorten_duration)); return DispatchCueEvent(kStreamIndex, std::move(event)); } @@ -99,7 +99,7 @@ Status TextChunker::OnTextSample(std::shared_ptr sample) { // TODO??: Is this the right approach in the case when the sample starts // before the segment end but the sample end time is after the segment // end. This in an effort to prevent a duplicate moof - mdat pair. - if (sample->EndTime() > segment_start_ + segment_duration_ && + if (sample->EndTime() >= segment_start_ + segment_duration_ && adjust_sample_boundaries_) { sample = std::make_shared(sample->id(), sample->start_time(), segment_start_ + segment_duration_, @@ -123,6 +123,8 @@ Status TextChunker::DispatchSegment(int64_t duration) { std::shared_ptr info = std::make_shared(); info->start_timestamp = segment_start_; info->duration = duration; + info->segment_number = segment_number_++; + RETURN_IF_ERROR(DispatchSegmentInfo(kStreamIndex, std::move(info))); // Move onto the next segment. diff --git a/packager/media/chunking/text_chunker.h b/packager/media/chunking/text_chunker.h index a109922b07f..9ed706800c1 100644 --- a/packager/media/chunking/text_chunker.h +++ b/packager/media/chunking/text_chunker.h @@ -21,6 +21,7 @@ namespace media { class TextChunker : public MediaHandler { public: explicit TextChunker(double segment_duration_in_seconds, + int64_t start_segment_number, int64_t timed_text_decode_time = -1, bool adjust_sample_boundaries = false); @@ -54,6 +55,10 @@ class TextChunker : public MediaHandler { int64_t segment_start_ = -1; // Set when the first sample comes in. int64_t segment_duration_ = -1; // Set in OnStreamInfo. + // Segment number that keeps monotically increasing. + // Set to start_segment_number in constructor. + int64_t segment_number_ = 1; + // Only for Live Packaging to address the case when a sample ends after the // segment end which results in duplicate moof mdat pairs. bool adjust_sample_boundaries_ = false; diff --git a/packager/media/chunking/text_chunker_unittest.cc b/packager/media/chunking/text_chunker_unittest.cc index 69372f9d0ad..d1f4a0f0bfa 100644 --- a/packager/media/chunking/text_chunker_unittest.cc +++ b/packager/media/chunking/text_chunker_unittest.cc @@ -30,6 +30,8 @@ const size_t kOutput = 0; const bool kEncrypted = true; const bool kSubSegment = true; +const int64_t kStartSegmentNumber = 1; + const char* kNoId = ""; const char* kNoPayload = ""; } // namespace @@ -38,7 +40,8 @@ class TextChunkerTest : public MediaHandlerTestBase { protected: Status Init(double segment_duration) { return SetUpAndInitializeGraph( - std::make_shared(segment_duration), kInputs, kOutputs); + std::make_shared(segment_duration, kStartSegmentNumber), + kInputs, kOutputs); } }; diff --git a/packager/media/codecs/CMakeLists.txt b/packager/media/codecs/CMakeLists.txt index 0b31e4cd24b..b5bcecbe019 100644 --- a/packager/media/codecs/CMakeLists.txt +++ b/packager/media/codecs/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(media_codecs STATIC avc_decoder_configuration_record.cc decoder_configuration_record.cc dovi_decoder_configuration_record.cc + dts_audio_specific_config.cc ec3_audio_util.cc ac4_audio_util.cc es_descriptor.cc diff --git a/packager/media/codecs/decoder_configuration_record.h b/packager/media/codecs/decoder_configuration_record.h index b826484e719..45221cf5281 100644 --- a/packager/media/codecs/decoder_configuration_record.h +++ b/packager/media/codecs/decoder_configuration_record.h @@ -48,6 +48,12 @@ class DecoderConfigurationRecord { /// @return Transfer characteristics of the config. uint8_t transfer_characteristics() const { return transfer_characteristics_; } + /// @return Colour Primaries of the config. + uint8_t color_primaries() const { return color_primaries_; } + + /// @return Matrix Coeffs of the config. + uint8_t matrix_coefficients() const { return matrix_coefficients_; } + protected: DecoderConfigurationRecord(); @@ -71,6 +77,15 @@ class DecoderConfigurationRecord { transfer_characteristics_ = transfer_characteristics; } + /// Sets the colour primaries. + void set_color_primaries(uint8_t color_primaries) { + color_primaries_ = color_primaries; + } + /// Sets the matrix coeffs. + void set_matrix_coefficients(uint8_t matrix_coefficients) { + matrix_coefficients_ = matrix_coefficients; + } + private: // Performs the actual parsing of the data. virtual bool ParseInternal() = 0; @@ -86,6 +101,9 @@ class DecoderConfigurationRecord { // The parameter is extracted from SPS. uint8_t transfer_characteristics_ = 0; + uint8_t color_primaries_ = 0; + uint8_t matrix_coefficients_ = 0; + DISALLOW_COPY_AND_ASSIGN(DecoderConfigurationRecord); }; diff --git a/packager/media/codecs/dovi_decoder_configuration_record.cc b/packager/media/codecs/dovi_decoder_configuration_record.cc index 4814c70a919..44d5dbfc8b9 100644 --- a/packager/media/codecs/dovi_decoder_configuration_record.cc +++ b/packager/media/codecs/dovi_decoder_configuration_record.cc @@ -23,7 +23,9 @@ bool DOVIDecoderConfigurationRecord::Parse(const std::vector& data) { uint8_t minor_version = 0; RCHECK(reader.ReadBits(8, &major_version) && major_version == 1 && reader.ReadBits(8, &minor_version) && minor_version == 0 && - reader.ReadBits(7, &profile_) && reader.ReadBits(6, &level_)); + reader.ReadBits(7, &profile_) && reader.ReadBits(6, &level_) && + reader.SkipBits(3) && + reader.ReadBits(4, &bl_signal_compatibility_id_)); return true; } @@ -35,5 +37,24 @@ std::string DOVIDecoderConfigurationRecord::GetCodecString( profile_, level_); } +FourCC DOVIDecoderConfigurationRecord::GetDoViCompatibleBrand( + const uint8_t transfer_characteristics) const { + // Dolby Vision Streams within the ISO Base Media File Format Version 2.4: + switch (bl_signal_compatibility_id_) { + case 1: + return FOURCC_db1p; + case 2: + return FOURCC_db2g; + case 4: + if (transfer_characteristics == 14) { + return FOURCC_db4g; + } + // transfer_characteristics == 18 + return FOURCC_db4h; + default: + return FOURCC_NULL; + } +} + } // namespace media } // namespace shaka diff --git a/packager/media/codecs/dovi_decoder_configuration_record.h b/packager/media/codecs/dovi_decoder_configuration_record.h index 64e9445f281..da61e8adeda 100644 --- a/packager/media/codecs/dovi_decoder_configuration_record.h +++ b/packager/media/codecs/dovi_decoder_configuration_record.h @@ -35,6 +35,10 @@ class DOVIDecoderConfigurationRecord { /// DASH and HLS manifests. std::string GetCodecString(FourCC codec_fourcc) const; + /// @return The compatiable brand in the format defined by + /// https://mp4ra.org/#/brands. + FourCC GetDoViCompatibleBrand(const uint8_t transfer_characteristics) const; + private: DOVIDecoderConfigurationRecord(const DOVIDecoderConfigurationRecord&) = delete; @@ -42,6 +46,7 @@ class DOVIDecoderConfigurationRecord { const DOVIDecoderConfigurationRecord&) = delete; uint8_t profile_ = 0; + uint8_t bl_signal_compatibility_id_ = 0; uint8_t level_ = 0; }; diff --git a/packager/media/codecs/dts_audio_specific_config.cc b/packager/media/codecs/dts_audio_specific_config.cc new file mode 100644 index 00000000000..45b9343d055 --- /dev/null +++ b/packager/media/codecs/dts_audio_specific_config.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2023 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +namespace shaka { +namespace media { + +bool GetDTSXChannelMask(const std::vector& udts, uint32_t& mask) { + // udts is the DTS-UHD Specific Box: ETSI TS 103 491 V1.2.1 Table B-2 + // DecoderProfileCode(6 bits) + // FrameDurationCode(2 bits) + // MaxPayloadCode(3 bits) + // NumPresentationsCode(5 bits) + // ChannelMask (32 bits) + BitReader bit_reader(udts.data(), udts.size()); + RCHECK(bit_reader.SkipBits(16)); + RCHECK(bit_reader.ReadBits(32, &mask)); + return true; +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/codecs/dts_audio_specific_config.h b/packager/media/codecs/dts_audio_specific_config.h new file mode 100644 index 00000000000..f4b1cdd1621 --- /dev/null +++ b/packager/media/codecs/dts_audio_specific_config.h @@ -0,0 +1,24 @@ +// Copyright (c) 2023 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGER_MEDIA_CODECS_DTS_AUDIO_SPECIFIC_CONFIG_H_ +#define PACKAGER_MEDIA_CODECS_DTS_AUDIO_SPECIFIC_CONFIG_H_ + +#include +#include + +#include + +namespace shaka { +namespace media { + +class BitReader; + +bool GetDTSXChannelMask(const std::vector& udts, uint32_t& mask); + +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_CODECS_DTS_AUDIO_SPECIFIC_CONFIG_H_ diff --git a/packager/media/codecs/dts_audio_specific_config_unittest.cc b/packager/media/codecs/dts_audio_specific_config_unittest.cc new file mode 100644 index 00000000000..e71941e8938 --- /dev/null +++ b/packager/media/codecs/dts_audio_specific_config_unittest.cc @@ -0,0 +1,37 @@ +// Copyright 2023 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "packager/media/codecs/dts_audio_specific_config.h" + +namespace shaka { +namespace media { + +TEST(DTSAudioSpecificConfigTest, BasicProfileTest) { + uint8_t buffer[] = {0x01, 0x20, 0x00, 0x00, 0x0, 0x3F, 0x80, 0x00}; + std::vector data(std::begin(buffer), std::end(buffer)); + uint32_t mask; + EXPECT_TRUE(GetDTSXChannelMask(data, mask)); + EXPECT_EQ(0x3F, mask); +} + +TEST(DTSAudioSpecificConfigTest, ChannelMaskBytes) { + uint8_t buffer[] = {0x01, 0x20, 0x12, 0x34, 0x56, 0x78, 0x80, 0x00}; + std::vector data(std::begin(buffer), std::end(buffer)); + uint32_t mask; + EXPECT_TRUE(GetDTSXChannelMask(data, mask)); + EXPECT_EQ(0x12345678, mask); +} + +TEST(DTSAudioSpecificConfigTest, Truncated) { + uint8_t buffer[] = {0x01, 0x20, 0x00, 0x00, 0x00}; + std::vector data(std::begin(buffer), std::end(buffer)); + uint32_t mask; + EXPECT_FALSE(GetDTSXChannelMask(data, mask)); +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/codecs/h264_parser.cc b/packager/media/codecs/h264_parser.cc index 8b86133b1de..c25dcbb0c9d 100644 --- a/packager/media/codecs/h264_parser.cc +++ b/packager/media/codecs/h264_parser.cc @@ -519,9 +519,9 @@ H264Parser::Result H264Parser::ParseVUIParameters(H26xBitReader* br, READ_BOOL_OR_RETURN(&data); // video_full_range_flag READ_BOOL_OR_RETURN(&data); // colour_description_present_flag if (data) { - READ_BITS_OR_RETURN(8, &data); // colour primaries + READ_BITS_OR_RETURN(8, &sps->color_primaries); // colour primaries READ_BITS_OR_RETURN(8, &sps->transfer_characteristics); - READ_BITS_OR_RETURN(8, &data); // matrix coeffs + READ_BITS_OR_RETURN(8, &sps->matrix_coefficients); // matrix coeffs } } diff --git a/packager/media/codecs/h264_parser.h b/packager/media/codecs/h264_parser.h index 423fe5a40d4..2752e31a388 100644 --- a/packager/media/codecs/h264_parser.h +++ b/packager/media/codecs/h264_parser.h @@ -81,6 +81,8 @@ struct H264Sps { int sar_width; // Set to 0 when not specified. int sar_height; // Set to 0 when not specified. int transfer_characteristics; + int color_primaries; + int matrix_coefficients; bool timing_info_present_flag; long num_units_in_tick; diff --git a/packager/media/codecs/h265_byte_to_unit_stream_converter.cc b/packager/media/codecs/h265_byte_to_unit_stream_converter.cc index 1c177a8d5e9..3235b0a0cae 100644 --- a/packager/media/codecs/h265_byte_to_unit_stream_converter.cc +++ b/packager/media/codecs/h265_byte_to_unit_stream_converter.cc @@ -74,21 +74,24 @@ bool H265ByteToUnitStreamConverter::GetDecoderConfigurationRecord( buffer.AppendInt(static_cast(kUnitStreamNaluLengthSize - 1)); buffer.AppendInt(static_cast(3) /* numOfArrays */); + // More parameter set NALUs may follow when strip_parameter_set_nalus is + // disabled. + const uint8_t array_completeness = strip_parameter_set_nalus() ? 0x80 : 0; + // VPS - const uint8_t kArrayCompleteness = 0x80; - buffer.AppendInt(static_cast(kArrayCompleteness | Nalu::H265_VPS)); + buffer.AppendInt(static_cast(array_completeness | Nalu::H265_VPS)); buffer.AppendInt(static_cast(1) /* numNalus */); buffer.AppendInt(static_cast(last_vps_.size())); buffer.AppendVector(last_vps_); // SPS - buffer.AppendInt(static_cast(kArrayCompleteness | Nalu::H265_SPS)); + buffer.AppendInt(static_cast(array_completeness | Nalu::H265_SPS)); buffer.AppendInt(static_cast(1) /* numNalus */); buffer.AppendInt(static_cast(last_sps_.size())); buffer.AppendVector(last_sps_); // PPS - buffer.AppendInt(static_cast(kArrayCompleteness | Nalu::H265_PPS)); + buffer.AppendInt(static_cast(array_completeness | Nalu::H265_PPS)); buffer.AppendInt(static_cast(1) /* numNalus */); buffer.AppendInt(static_cast(last_pps_.size())); buffer.AppendVector(last_pps_); diff --git a/packager/media/codecs/h265_parser.cc b/packager/media/codecs/h265_parser.cc index bd7f2a411ce..7c3cab7bf51 100644 --- a/packager/media/codecs/h265_parser.cc +++ b/packager/media/codecs/h265_parser.cc @@ -680,9 +680,11 @@ H265Parser::Result H265Parser::ParseVuiParameters(int max_num_sub_layers_minus1, bool colour_description_present_flag; TRUE_OR_RETURN(br->ReadBool(&colour_description_present_flag)); if (colour_description_present_flag) { - TRUE_OR_RETURN(br->SkipBits(8)); // colour_primaries + TRUE_OR_RETURN( + br->ReadBits(8, &vui->color_primaries)); // color_primaries TRUE_OR_RETURN(br->ReadBits(8, &vui->transfer_characteristics)); - TRUE_OR_RETURN(br->SkipBits(8)); // matrix_coeffs + TRUE_OR_RETURN( + br->ReadBits(8, &vui->matrix_coefficients)); // matrix_coeffs } } diff --git a/packager/media/codecs/h265_parser.h b/packager/media/codecs/h265_parser.h index a36f82e8abc..febb2e0731d 100644 --- a/packager/media/codecs/h265_parser.h +++ b/packager/media/codecs/h265_parser.h @@ -52,6 +52,8 @@ struct H265VuiParameters { int sar_width = 0; int sar_height = 0; int transfer_characteristics = 0; + int color_primaries = 0; + int matrix_coefficients = 0; bool vui_timing_info_present_flag = false; long vui_num_units_in_tick = 0; diff --git a/packager/media/codecs/hevc_decoder_configuration_record.cc b/packager/media/codecs/hevc_decoder_configuration_record.cc index 9fef0fe06e1..f171fa56fce 100644 --- a/packager/media/codecs/hevc_decoder_configuration_record.cc +++ b/packager/media/codecs/hevc_decoder_configuration_record.cc @@ -120,6 +120,10 @@ bool HEVCDecoderConfigurationRecord::ParseInternal() { RCHECK(parser.ParseSps(nalu, &sps_id) == H265Parser::kOk); set_transfer_characteristics( parser.GetSps(sps_id)->vui_parameters.transfer_characteristics); + set_color_primaries( + parser.GetSps(sps_id)->vui_parameters.color_primaries); + set_matrix_coefficients( + parser.GetSps(sps_id)->vui_parameters.matrix_coefficients); } } } diff --git a/packager/media/codecs/vp9_parser.cc b/packager/media/codecs/vp9_parser.cc index 1e2499cc82b..12fc472a90b 100644 --- a/packager/media/codecs/vp9_parser.cc +++ b/packager/media/codecs/vp9_parser.cc @@ -285,6 +285,7 @@ bool ReadBitDepthAndColorSpace(BitReader* reader, } } else { // Assume 4:4:4 for colorspace SRGB. + yuv_full_range = true; chroma_subsampling = VPCodecConfigurationRecord::CHROMA_444; if (codec_config->profile() & 1) { bool reserved; diff --git a/packager/media/crypto/encryption_handler_unittest.cc b/packager/media/crypto/encryption_handler_unittest.cc index a45790a72c1..a1833def744 100644 --- a/packager/media/crypto/encryption_handler_unittest.cc +++ b/packager/media/crypto/encryption_handler_unittest.cc @@ -357,7 +357,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithNoKeyRotation) { kIsKeyFrame, kData, kDataSize)))); ASSERT_OK(Process(StreamData::FromSegmentInfo( kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration, - !kIsSubsegment)))); + !kIsSubsegment, i + 1)))); const bool is_encrypted = i == 2; const auto& output_stream_data = GetOutputStreamDataVector(); EXPECT_THAT(output_stream_data, @@ -436,7 +436,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) { kIsKeyFrame, kData, kDataSize)))); ASSERT_OK(Process(StreamData::FromSegmentInfo( kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration, - !kIsSubsegment)))); + !kIsSubsegment, i)))); const bool is_encrypted = i >= 2; const auto& output_stream_data = GetOutputStreamDataVector(); EXPECT_THAT(output_stream_data, diff --git a/packager/media/crypto/subsample_generator_unittest.cc b/packager/media/crypto/subsample_generator_unittest.cc index c384f45adf6..e12dc721166 100644 --- a/packager/media/crypto/subsample_generator_unittest.cc +++ b/packager/media/crypto/subsample_generator_unittest.cc @@ -63,6 +63,8 @@ VideoStreamInfo GetVideoStreamInfo(Codec codec) { const uint16_t kHeight = 20u; const uint32_t kPixelWidth = 2u; const uint32_t kPixelHeight = 3u; + const uint8_t kColorPrimaries = 0; + const uint8_t kMatrixCoefficients = 0; const uint8_t kTransferCharacteristics = 0; const int16_t kTrickPlayFactor = 0; const uint8_t kNaluLengthSize = 1u; @@ -85,8 +87,9 @@ VideoStreamInfo GetVideoStreamInfo(Codec codec) { return VideoStreamInfo( kTrackId, kTimeScale, kDuration, codec, H26xStreamFormat::kUnSpecified, kCodecString, codec_config, codec_config_size, kWidth, kHeight, - kPixelWidth, kPixelHeight, kTransferCharacteristics, kTrickPlayFactor, - kNaluLengthSize, kLanguage, !kEncrypted); + kPixelWidth, kPixelHeight, kColorPrimaries, kMatrixCoefficients, + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + !kEncrypted); } AudioStreamInfo GetAudioStreamInfo(Codec codec) { diff --git a/packager/media/demuxer/demuxer.cc b/packager/media/demuxer/demuxer.cc index 40284d5eebe..d4853d37b83 100644 --- a/packager/media/demuxer/demuxer.cc +++ b/packager/media/demuxer/demuxer.cc @@ -170,21 +170,25 @@ Status Demuxer::InitializeParser() { "Cannot open file for reading " + file_name_); } - // Read enough bytes before detecting the container. int64_t bytes_read = 0; bool eof = false; - while (static_cast(bytes_read) < kInitBufSize) { - int64_t read_result = - media_file_->Read(buffer_.get() + bytes_read, kInitBufSize); - if (read_result < 0) - return Status(error::FILE_FAILURE, "Cannot read file " + file_name_); - if (read_result == 0) { - eof = true; - break; + if (input_format_.empty()) { + // Read enough bytes before detecting the container. + while (static_cast(bytes_read) < kInitBufSize) { + int64_t read_result = + media_file_->Read(buffer_.get() + bytes_read, kInitBufSize); + if (read_result < 0) + return Status(error::FILE_FAILURE, "Cannot read file " + file_name_); + if (read_result == 0) { + eof = true; + break; + } + bytes_read += read_result; } - bytes_read += read_result; + container_name_ = DetermineContainer(buffer_.get(), bytes_read); + } else { + container_name_ = DetermineContainerFromFormatName(input_format_); } - container_name_ = DetermineContainer(buffer_.get(), bytes_read); // Initialize media parser. switch (container_name_) { diff --git a/packager/media/demuxer/demuxer.h b/packager/media/demuxer/demuxer.h index facd90333fa..c21b1b11711 100644 --- a/packager/media/demuxer/demuxer.h +++ b/packager/media/demuxer/demuxer.h @@ -88,6 +88,10 @@ class Demuxer : public OriginHandler { webvtt_header_only_output_segment_ = webvtt_header_only_output_segment; } + void set_input_format(std::string input_format) { + input_format_ = input_format; + } + protected: /// @name MediaHandler implementation overrides. /// @{ @@ -167,6 +171,9 @@ class Demuxer : public OriginHandler { Status init_event_status_; std::shared_ptr dash_event_handler_; + + // Explicitly defined input format, for avoiding autodetection. + std::string input_format_; }; } // namespace media diff --git a/packager/media/event/combined_muxer_listener.cc b/packager/media/event/combined_muxer_listener.cc index a1135655198..76381b7c36b 100644 --- a/packager/media/event/combined_muxer_listener.cc +++ b/packager/media/event/combined_muxer_listener.cc @@ -71,9 +71,11 @@ void CombinedMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void CombinedMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { for (auto& listener : muxer_listeners_) { - listener->OnNewSegment(file_name, start_time, duration, segment_file_size); + listener->OnNewSegment(file_name, start_time, duration, segment_file_size, + segment_number); } } diff --git a/packager/media/event/combined_muxer_listener.h b/packager/media/event/combined_muxer_listener.h index fdeda6123c9..1f40e5c1e10 100644 --- a/packager/media/event/combined_muxer_listener.h +++ b/packager/media/event/combined_muxer_listener.h @@ -44,7 +44,8 @@ class CombinedMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnCompletedSegment(int64_t duration, uint64_t segment_file_size) override; void OnKeyFrame(int64_t timestamp, diff --git a/packager/media/event/event_info.h b/packager/media/event/event_info.h index ea1162a36e1..0b8a4a93023 100644 --- a/packager/media/event/event_info.h +++ b/packager/media/event/event_info.h @@ -20,6 +20,7 @@ struct SegmentEventInfo { // The below two fields are only useful for Segment. int64_t duration; uint64_t segment_file_size; + int64_t segment_number; }; struct KeyFrameEvent { diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index 316a46293da..3e619766519 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -26,13 +26,17 @@ HlsNotifyMuxerListener::HlsNotifyMuxerListener( const std::string& ext_x_media_name, const std::string& ext_x_media_group_id, const std::vector& characteristics, - hls::HlsNotifier* hls_notifier) + bool forced_subtitle, + hls::HlsNotifier* hls_notifier, + std::optional index) : playlist_name_(playlist_name), iframes_only_(iframes_only), ext_x_media_name_(ext_x_media_name), ext_x_media_group_id_(ext_x_media_group_id), characteristics_(characteristics), - hls_notifier_(hls_notifier) { + forced_subtitle_(forced_subtitle), + hls_notifier_(hls_notifier), + index_(index) { DCHECK(hls_notifier); } @@ -101,6 +105,12 @@ void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options, for (const std::string& characteristic : characteristics_) media_info->add_hls_characteristics(characteristic); } + if (forced_subtitle_) { + media_info->set_forced_subtitle(forced_subtitle_); + } + if (index_.has_value()) + media_info->set_index(index_.value()); + if (protection_scheme_ != FOURCC_NULL) { internal::SetContentProtectionFields(protection_scheme_, next_key_id_, next_key_system_infos_, @@ -234,11 +244,13 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { if (!media_info_->has_segment_template()) { EventInfo event_info; event_info.type = EventInfoType::kSegment; - event_info.segment_info = {start_time, duration, segment_file_size}; + event_info.segment_info = {start_time, duration, segment_file_size, + segment_number}; event_info_.push_back(event_info); } else { // For multisegment, it always starts from the beginning of the file. diff --git a/packager/media/event/hls_notify_muxer_listener.h b/packager/media/event/hls_notify_muxer_listener.h index a1e2f6e330e..aa76fa8e84e 100644 --- a/packager/media/event/hls_notify_muxer_listener.h +++ b/packager/media/event/hls_notify_muxer_listener.h @@ -39,13 +39,18 @@ class HlsNotifyMuxerListener : public MuxerListener { /// @param characteristics is the characteristics for this playlist. This is /// the value of CHARACTERISTICS attribute for EXT-X-MEDIA. This may be /// empty. + /// @param forced is the HLS FORCED SUBTITLE setting for this playlist. This + /// is the value of FORCED attribute for EXT-X-MEDIA. This may be + /// empty. /// @param hls_notifier used by this listener. Ownership does not transfer. HlsNotifyMuxerListener(const std::string& playlist_name, bool iframes_only, const std::string& ext_x_media_name, const std::string& ext_x_media_group_id, const std::vector& characteristics, - hls::HlsNotifier* hls_notifier); + bool forced, + hls::HlsNotifier* hls_notifier, + std::optional index); ~HlsNotifyMuxerListener() override; /// @name MuxerListener implementation overrides. @@ -67,7 +72,8 @@ class HlsNotifyMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size) override; @@ -85,8 +91,10 @@ class HlsNotifyMuxerListener : public MuxerListener { const std::string ext_x_media_name_; const std::string ext_x_media_group_id_; const std::vector characteristics_; + const bool forced_subtitle_; hls::HlsNotifier* const hls_notifier_; std::optional stream_id_; + std::optional index_; bool must_notify_encryption_start_ = false; // Cached encryption info before OnMediaStart() is called. diff --git a/packager/media/event/hls_notify_muxer_listener_unittest.cc b/packager/media/event/hls_notify_muxer_listener_unittest.cc index 863095434f7..1bf9e2e4f10 100644 --- a/packager/media/event/hls_notify_muxer_listener_unittest.cc +++ b/packager/media/event/hls_notify_muxer_listener_unittest.cc @@ -79,6 +79,7 @@ const uint64_t kSegmentStartOffset = 10000; const int64_t kSegmentStartTime = 19283; const int64_t kSegmentDuration = 98028; const uint64_t kSegmentSize = 756739; +const int64_t kAnySegmentNumber = 10; const int64_t kCueStartTime = kSegmentStartTime; @@ -99,6 +100,7 @@ const char kDefaultName[] = "DEFAULTNAME"; const char kDefaultGroupId[] = "DEFAULTGROUPID"; const char kCharactersticA[] = "public.accessibility.transcribes-spoken-dialog"; const char kCharactersticB[] = "public.easy-to-read"; +const bool kForced = false; MATCHER_P(HasEncryptionScheme, expected_scheme, "") { *result_listener << "it has_protected_content: " @@ -121,7 +123,9 @@ class HlsNotifyMuxerListenerTest : public ::testing::Test { kDefaultName, kDefaultGroupId, std::vector{kCharactersticA, kCharactersticB}, - &mock_notifier_) {} + kForced, + &mock_notifier_, + 0) {} MuxerListener::MediaRanges GetMediaRanges( const std::vector& segment_ranges) { @@ -346,7 +350,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) { kSegmentDuration, _, kSegmentSize)); listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, kAnySegmentNumber); } // Verify that the notifier is called for every segment in OnMediaEnd if @@ -364,7 +368,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) { listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kCueStartTime)); EXPECT_CALL( @@ -394,7 +398,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { listener_.OnMediaStart(muxer_options1, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename1.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); listener_.OnCueEvent(kCueStartTime, "dummy cue data"); EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) @@ -411,7 +415,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { listener_.OnMediaStart(muxer_options2, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename2.mp4", kSegmentStartTime + kSegmentDuration, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, kAnySegmentNumber); EXPECT_CALL(mock_notifier_, NotifyNewSegment(_, StrEq("filename2.mp4"), kSegmentStartTime + kSegmentDuration, _, _, _)); @@ -437,7 +441,7 @@ TEST_F(HlsNotifyMuxerListenerTest, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); EXPECT_CALL( mock_notifier_, NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime, @@ -458,7 +462,9 @@ class HlsNotifyMuxerListenerKeyFrameTest : public TestWithParam { kDefaultName, kDefaultGroupId, std::vector(), // no characteristics. - &mock_notifier_) {} + kForced, + &mock_notifier_, + 0) {} MockHlsNotifier mock_notifier_; HlsNotifyMuxerListener listener_; @@ -499,7 +505,7 @@ TEST_P(HlsNotifyMuxerListenerKeyFrameTest, NoSegmentTemplate) { listener_.OnKeyFrame(kKeyFrameTimestamp, kKeyFrameStartByteOffset, kKeyFrameSize); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); EXPECT_CALL(mock_notifier_, NotifyKeyFrame(_, kKeyFrameTimestamp, diff --git a/packager/media/event/mock_muxer_listener.h b/packager/media/event/mock_muxer_listener.h index 82713ae3f5d..970bc3b1186 100644 --- a/packager/media/event/mock_muxer_listener.h +++ b/packager/media/event/mock_muxer_listener.h @@ -56,11 +56,12 @@ class MockMuxerListener : public MuxerListener { void OnMediaEnd(const MediaRanges& range, float duration_seconds) override; - MOCK_METHOD4(OnNewSegment, + MOCK_METHOD5(OnNewSegment, void(const std::string& segment_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size)); + uint64_t segment_file_size, + int64_t segment_number)); MOCK_METHOD3(OnKeyFrame, void(int64_t timestamp, diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index 903c2c12d7c..79b58d8cbfb 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -85,6 +85,12 @@ void MpdNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options, media_info->add_dash_roles(role); } + if (index_.has_value()) + media_info->set_index(index_.value()); + + if (!dash_label_.empty()) + media_info->set_dash_label(dash_label_); + if (is_encrypted_) { internal::SetContentProtectionFields(protection_scheme_, default_key_id_, key_system_info_, media_info.get()); @@ -176,7 +182,8 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, mpd_notifier_->NotifyNewSegment( notification_id_.value(), event_info.segment_info.start_time, event_info.segment_info.duration, - event_info.segment_info.segment_file_size); + event_info.segment_info.segment_file_size, + event_info.segment_info.segment_number); break; case EventInfoType::kKeyFrame: // NO-OP for DASH. @@ -194,17 +201,20 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { UNUSED(file_name); if (mpd_notifier_->dash_profile() == DashProfile::kLive) { mpd_notifier_->NotifyNewSegment(notification_id_.value(), start_time, - duration, segment_file_size); + duration, segment_file_size, + segment_number); if (mpd_notifier_->mpd_type() == MpdType::kDynamic) mpd_notifier_->Flush(); } else { EventInfo event_info; event_info.type = EventInfoType::kSegment; - event_info.segment_info = {start_time, duration, segment_file_size}; + event_info.segment_info = {start_time, duration, segment_file_size, + segment_number}; event_info_.push_back(event_info); } } diff --git a/packager/media/event/mpd_notify_muxer_listener.h b/packager/media/event/mpd_notify_muxer_listener.h index 8e5aef57748..98697a01e79 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -52,7 +52,8 @@ class MpdNotifyMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnCompletedSegment(int64_t duration, uint64_t segment_file_size) override; void OnKeyFrame(int64_t timestamp, @@ -67,6 +68,10 @@ class MpdNotifyMuxerListener : public MuxerListener { void set_roles(const std::vector& roles) { roles_ = roles; } + void set_index(std::optional idx) { index_ = idx; } + + void set_dash_label(std::string label) { dash_label_ = label; } + private: MpdNotifyMuxerListener(const MpdNotifyMuxerListener&) = delete; MpdNotifyMuxerListener& operator=(const MpdNotifyMuxerListener&) = delete; @@ -79,6 +84,9 @@ class MpdNotifyMuxerListener : public MuxerListener { std::vector accessibilities_; std::vector roles_; + std::string dash_label_; + + std::optional index_ = 0; bool is_encrypted_ = false; // Storage for values passed to OnEncryptionInfoReady(). diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index e20463a273a..d2d13aea5d8 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -301,6 +301,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReady) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -313,7 +315,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReady) { "reference_time_scale: 1111\n" "container_type: 1\n" "media_file_name: 'test_output_file_name.mp4'\n" - "media_duration_seconds: 10.5\n"; + "media_duration_seconds: 10.5\n" + "index: 0\n"; const int32_t kReferenceTimeScale = 1111; // Should match the protobuf. @@ -348,6 +351,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReadySegmentList) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -361,6 +366,7 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReadySegmentList) { "container_type: 1\n" "media_file_name: 'test_output_file_name.mp4'\n" "media_duration_seconds: 10.5\n" + "index: 0\n" "subsegment_ranges {\n" " begin: 222\n" " end: 9999\n" @@ -395,27 +401,31 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); InSequence s; EXPECT_CALL(*notifier_, NotifyNewContainer( ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -431,29 +441,33 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegmentSegmentList) { const int64_t kStartTime1 = 0; const int64_t kDuration1 = 1000; const uint64_t kSegmentFileSize1 = 29812u; + const int64_t kSegmentNumber1 = 1; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); InSequence s; EXPECT_CALL(*notifier_, NotifyNewContainer( ExpectMediaInfoEq(kExpectedDefaultMediaInfoSubsegmentRange), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -483,15 +497,18 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; // Expectation for first file before OnMediaEnd. EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options1, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); @@ -500,8 +517,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { EXPECT_CALL(*notifier_, NotifyNewContainer(EqualsProto(expected_media_info1), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); @@ -510,13 +527,14 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { listener_->OnMediaStart(muxer_options2, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); // Expectation for second file OnMediaEnd. EXPECT_CALL(*notifier_, NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -542,17 +560,20 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) { const int64_t kStartTime1 = 0; const int64_t kDuration1 = 1000; const uint64_t kSegmentFileSize1 = 29812u; + const int64_t kSegmentNumber1 = 1; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; // Expectation for first file before OnMediaEnd. EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options1, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); @@ -561,8 +582,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) { EXPECT_CALL(*notifier_, NotifyNewContainer(EqualsProto(expected_media_info1), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); @@ -571,13 +592,14 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) { listener_->OnMediaStart(muxer_options2, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); // Expectation for second file OnMediaEnd. EXPECT_CALL(*notifier_, NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -598,8 +620,11 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "media_duration_seconds: 20.0\n" + "index: 0\n" "init_segment_name: \"liveinit.mp4\"\n" "segment_template: \"live-$NUMBER$.mp4\"\n" "reference_time_scale: 1000\n" @@ -610,6 +635,8 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { const uint64_t kDuration = 1000u; const uint64_t kSegmentSize1 = 29812u; const uint64_t kSegmentSize2 = 30128u; + const int64_t kSegmentNumber1 = 1; + const int64_t kSegmentNumber2 = 2; EXPECT_CALL(*notifier_, NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) @@ -619,11 +646,11 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { EXPECT_CALL(*notifier_, NotifyAvailabilityTimeOffset(_)) .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifySegmentDuration(_)).WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration, kSegmentSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration, + kSegmentSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration, kSegmentSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration, + kSegmentSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()).Times(2); listener_->OnMediaStart(muxer_options, *video_stream_info, @@ -632,9 +659,11 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { listener_->OnSampleDurationReady(kDuration); listener_->OnAvailabilityOffsetReady(); listener_->OnSegmentDurationReady(); - listener_->OnNewSegment("", kStartTime1, kDuration, kSegmentSize1); + listener_->OnNewSegment("", kStartTime1, kDuration, kSegmentSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration, kSegmentSize2); + listener_->OnNewSegment("", kStartTime2, kDuration, kSegmentSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()).Times(0); @@ -659,8 +688,11 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "media_duration_seconds: 20.0\n" + "index: 0\n" "init_segment_name: \"liveinit.mp4\"\n" "segment_template: \"live-$NUMBER$.mp4\"\n" "reference_time_scale: 1000\n" @@ -669,7 +701,9 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { " default_key_id: \"defaultkeyid\"\n" " content_protection_entry {\n" " uuid: '00010203-0405-0607-0809-0a0b0c0d0e0f'\n" - " pssh: \"" + std::string(kExpectedDefaultPsshBox) + "\"\n" + " pssh: \"" + + std::string(kExpectedDefaultPsshBox) + + "\"\n" " }\n" " protection_scheme: 'cbcs'\n" " include_mspr_pro: 1\n" @@ -680,7 +714,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; + const std::vector default_key_id( kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1); @@ -689,14 +726,14 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { EXPECT_CALL(*notifier_, NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); // Flush should only be called once in OnMediaEnd. if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); @@ -707,9 +744,11 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()) @@ -736,8 +775,11 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "media_duration_seconds: 20.0\n" + "index: 0\n" "init_segment_name: \"liveinit.mp4\"\n" "segment_template: \"live-$NUMBER$.mp4\"\n" "reference_time_scale: 1000\n" @@ -753,7 +795,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; + const std::vector default_key_id( kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1); @@ -762,13 +807,13 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); // Flush should only be called once in OnMediaEnd. if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); @@ -782,8 +827,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { listener_->OnEncryptionInfoReady(kNonInitialEncryptionInfo, FOURCC_cbc1, std::vector(), iv, GetDefaultKeySystemInfo()); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()) diff --git a/packager/media/event/multi_codec_muxer_listener_unittest.cc b/packager/media/event/multi_codec_muxer_listener_unittest.cc index 6a75983d30a..64d79d848fb 100644 --- a/packager/media/event/multi_codec_muxer_listener_unittest.cc +++ b/packager/media/event/multi_codec_muxer_listener_unittest.cc @@ -27,6 +27,8 @@ const int64_t kSegmentStartTime = 19283; const int64_t kSegmentDuration = 98028; const uint64_t kSegmentSize = 756739; const int32_t kTimescale = 90000; +const int64_t kSegmentNumber = 10; + MuxerListener::ContainerType kContainer = MuxerListener::kContainerMpeg2ts; } // namespace @@ -79,10 +81,11 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartSingleCodec) { EXPECT_CALL(*listener_for_first_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, kSegmentNumber)); multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, + kSegmentNumber); } TEST_F(MultiCodecMuxerListenerTest, OnMediaStartTwoCodecs) { @@ -114,13 +117,14 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartTwoCodecs) { EXPECT_CALL(*listener_for_first_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, kSegmentNumber)); EXPECT_CALL(*listener_for_second_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, kSegmentNumber)); multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, + kSegmentNumber); } } // namespace media diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index b0a86e0fba5..2eb704154c9 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -133,10 +133,12 @@ class MuxerListener { /// @param duration is the duration of the segment, relative to the timescale /// specified by MediaInfo passed to OnMediaStart(). /// @param segment_file_size is the segment size in bytes. + /// @param segment_number is the segment number. virtual void OnNewSegment(const std::string& segment_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) = 0; + uint64_t segment_file_size, + int64_t segment_number) = 0; /// Called when a segment has been muxed and the entire file has been written. /// For Low Latency only. Note that it should be called after OnNewSegment. diff --git a/packager/media/event/muxer_listener_factory.cc b/packager/media/event/muxer_listener_factory.cc index ea63fdc353c..896f01c0dd6 100644 --- a/packager/media/event/muxer_listener_factory.cc +++ b/packager/media/event/muxer_listener_factory.cc @@ -44,6 +44,8 @@ std::unique_ptr CreateMpdListenerInternal( auto listener = std::make_unique(notifier); listener->set_accessibilities(stream.dash_accessiblities); listener->set_roles(stream.dash_roles); + listener->set_index(stream.index); + listener->set_dash_label(stream.dash_label); return listener; } @@ -60,6 +62,7 @@ std::list> CreateHlsListenersInternal( const std::string& group_id = stream.hls_group_id; const std::string& iframe_playlist_name = stream.hls_iframe_playlist_name; const std::vector& characteristics = stream.hls_characteristics; + const bool forced_subtitle = stream.forced_subtitle; if (name.empty()) { name = absl::StrFormat("stream_%d", stream_index); @@ -72,11 +75,12 @@ std::list> CreateHlsListenersInternal( const bool kIFramesOnly = true; std::list> listeners; listeners.emplace_back(new HlsNotifyMuxerListener( - playlist_name, !kIFramesOnly, name, group_id, characteristics, notifier)); + playlist_name, !kIFramesOnly, name, group_id, characteristics, + forced_subtitle, notifier, stream.index)); if (!iframe_playlist_name.empty()) { listeners.emplace_back(new HlsNotifyMuxerListener( iframe_playlist_name, kIFramesOnly, name, group_id, - std::vector(), notifier)); + std::vector(), forced_subtitle, notifier, stream.index)); } return listeners; } diff --git a/packager/media/event/muxer_listener_factory.h b/packager/media/event/muxer_listener_factory.h index ffebf8080b2..375e7c12b7e 100644 --- a/packager/media/event/muxer_listener_factory.h +++ b/packager/media/event/muxer_listener_factory.h @@ -8,6 +8,7 @@ #define PACKAGER_MEDIA_EVENT_MUXER_LISTENER_FACTORY_H_ #include +#include #include #include @@ -39,6 +40,10 @@ class MuxerListenerFactory { // told to output media info. std::string media_info_output; + // Explicit input format, for avoiding autodetection when needed. + // This is useful for cases such as live WebVTT through UDP. + std::string input_format; + // HLS specific values needed to write to HLS manifests. Will only be used // if an HlsNotifier is given to the factory. std::string hls_group_id; @@ -46,6 +51,7 @@ class MuxerListenerFactory { std::string hls_playlist_name; std::string hls_iframe_playlist_name; std::vector hls_characteristics; + bool forced_subtitle = false; bool hls_only = false; // DASH specific values needed to write DASH mpd. Will only be used if an @@ -53,6 +59,8 @@ class MuxerListenerFactory { std::vector dash_accessiblities; std::vector dash_roles; bool dash_only = false; + std::optional index; + std::string dash_label; }; /// Create a new muxer listener. diff --git a/packager/media/event/muxer_listener_internal.cc b/packager/media/event/muxer_listener_internal.cc index 0f86bd80c3f..0bc150fd0cf 100644 --- a/packager/media/event/muxer_listener_internal.cc +++ b/packager/media/event/muxer_listener_internal.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,8 @@ void AddVideoInfo(const VideoStreamInfo* video_stream_info, DCHECK(video_stream_info); DCHECK(media_info); MediaInfo_VideoInfo* video_info = media_info->mutable_video_info(); + video_info->set_supplemental_codec(video_stream_info->supplemental_codec()); + video_info->set_compatible_brand(video_stream_info->compatible_brand()); video_info->set_codec(video_stream_info->codec_string()); video_info->set_width(video_stream_info->width()); video_info->set_height(video_stream_info->height()); @@ -95,6 +98,13 @@ void AddVideoInfo(const VideoStreamInfo* video_stream_info, video_info->set_transfer_characteristics( video_stream_info->transfer_characteristics()); } + if (video_stream_info->color_primaries() > 0) { + video_info->set_color_primaries(video_stream_info->color_primaries()); + } + if (video_stream_info->matrix_coefficients() > 0) { + video_info->set_matrix_coefficients( + video_stream_info->matrix_coefficients()); + } } void AddAudioInfo(const AudioStreamInfo* audio_stream_info, @@ -165,6 +175,16 @@ void AddAudioInfo(const AudioStreamInfo* audio_stream_info, codec_data->set_ac4_ims_flag(ac4_ims_flag); codec_data->set_ac4_cbi_flag(ac4_cbi_flag); } + + if (audio_stream_info->codec() == kCodecDTSX) { + auto* codec_data = audio_info->mutable_codec_specific_data(); + uint32_t channel_mask; + if (!GetDTSXChannelMask(codec_config, channel_mask)) { + LOG(ERROR) << "Failed to parse DTSX channel mask."; + return; + } + codec_data->set_channel_mask(channel_mask); + } } void AddTextInfo(const TextStreamInfo& text_stream_info, diff --git a/packager/media/event/muxer_listener_internal_unittest.cc b/packager/media/event/muxer_listener_internal_unittest.cc index 6d9d4c8eb6b..309d802e777 100644 --- a/packager/media/event/muxer_listener_internal_unittest.cc +++ b/packager/media/event/muxer_listener_internal_unittest.cc @@ -72,6 +72,24 @@ TEST_F(MuxerListenerInternalVideoStreamTest, TransferCharacteristics) { EXPECT_EQ(18u, media_info.video_info().transfer_characteristics()); } +class MuxerListenerInternalAudioStreamTest : public MuxerListenerInternalTest { +}; + +// AddAudioInfo function should parse the channel mask +TEST_F(MuxerListenerInternalAudioStreamTest, DTSX) { + MediaInfo media_info; + std::shared_ptr audio_info = CreateAudioStreamInfo( + GetAudioStreamInfoParams(kCodecDTSX, "dtsx", + {0x01, 0x20, 0x00, 0x00, 0x0, 0x3F, 0x80, + 0x00})); // Channel mask = 3F + ASSERT_TRUE(GenerateMediaInfo(MuxerOptions(), *audio_info, + kReferenceTimeScale, + MuxerListener::kContainerMp4, &media_info)); + MediaInfo_AudioInfo* info = media_info.mutable_audio_info(); + auto* codec_data = info->mutable_codec_specific_data(); + EXPECT_EQ(0x3F, codec_data->channel_mask()); +} + } // namespace internal } // namespace media } // namespace shaka diff --git a/packager/media/event/muxer_listener_test_helper.cc b/packager/media/event/muxer_listener_test_helper.cc index 2736998de83..f8029763446 100644 --- a/packager/media/event/muxer_listener_test_helper.cc +++ b/packager/media/event/muxer_listener_test_helper.cc @@ -23,6 +23,8 @@ std::shared_ptr CreateVideoStreamInfo( H26xStreamFormat::kUnSpecified, param.codec_string, param.codec_config.data(), param.codec_config.size(), param.width, param.height, param.pixel_width, param.pixel_height, + 0, // color_primaries + 0, // matrix_coefficients 0, // transfer_characteristics 0, // trick_play_factor param.nalu_length_size, param.language, param.is_encrypted); @@ -104,5 +106,53 @@ std::vector GetDefaultKeySystemInfo() { std::end(kExpectedDefaultPsshBox) - 1}}}; } +AudioStreamInfoParameters::AudioStreamInfoParameters() {} +AudioStreamInfoParameters::~AudioStreamInfoParameters() {} + +std::shared_ptr CreateAudioStreamInfo( + const AudioStreamInfoParameters& param) { + return std::make_shared( + param.track_id, param.time_scale, param.duration, param.codec, + param.codec_string, param.codec_config.data(), param.codec_config.size(), + param.sample_bits, param.num_channels, param.sampling_frequency, + param.seek_preroll_ns, param.codec_delay_ns, param.max_bitrate, + param.avg_bitrate, param.language, param.is_encrypted); +} + +AudioStreamInfoParameters GetAudioStreamInfoParams( + Codec codec, + const char* codec_string, + const std::vector& codec_config) { + const int kTrackId = 0; + const int32_t kTimeScale = 10; + const int64_t kAudioStreamDuration = 200; + const char* kLanuageUndefined = "und"; + const uint8_t kSampleBits = 16; + const uint8_t kNumChannels = 6; + const uint32_t kSamplingFrequency = 48000; + const uint64_t kSeekPrerollNs = 0; + const uint64_t kCodecDelayNs = 0; + const uint32_t kMaxBitrate = 0; + const uint32_t kAvgBitrate = 0; + const bool kEncryptedFlag = false; + AudioStreamInfoParameters params; + params.track_id = kTrackId; + params.time_scale = kTimeScale; + params.duration = kAudioStreamDuration; + params.codec = codec; + params.codec_string = codec_string; + params.language = kLanuageUndefined; + params.sample_bits = kSampleBits; + params.num_channels = kNumChannels; + params.sampling_frequency = kSamplingFrequency; + params.seek_preroll_ns = kSeekPrerollNs; + params.codec_delay_ns = kCodecDelayNs; + params.max_bitrate = kMaxBitrate; + params.avg_bitrate = kAvgBitrate; + params.codec_config = codec_config; + params.is_encrypted = kEncryptedFlag; + return params; +} + } // namespace media } // namespace shaka diff --git a/packager/media/event/muxer_listener_test_helper.h b/packager/media/event/muxer_listener_test_helper.h index de20a8cfef4..6eff3ed9664 100644 --- a/packager/media/event/muxer_listener_test_helper.h +++ b/packager/media/event/muxer_listener_test_helper.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,8 @@ const char kExpectedDefaultPsshBox[] = "expected_pssh_box"; const char kExpectedDefaultMediaInfo[] = "video_info {\n" " codec: 'avc1.010101'\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" " width: 720\n" " height: 480\n" " time_scale: 10\n" @@ -42,7 +45,8 @@ const char kExpectedDefaultMediaInfo[] = "reference_time_scale: 1000\n" "container_type: 1\n" "media_file_name: 'test_output_file_name.mp4'\n" - "media_duration_seconds: 10.5\n"; + "media_duration_seconds: 10.5\n" + "index: 0\n"; const char kExpectedDefaultMediaInfoSubsegmentRange[] = "video_info {\n" @@ -52,6 +56,8 @@ const char kExpectedDefaultMediaInfoSubsegmentRange[] = " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -65,6 +71,7 @@ const char kExpectedDefaultMediaInfoSubsegmentRange[] = "container_type: 1\n" "media_file_name: 'test_output_file_name.mp4'\n" "media_duration_seconds: 10.5\n" + "index: 0\n" "subsegment_ranges {\n" " begin: 222\n" " end: 9999\n" @@ -93,6 +100,29 @@ struct VideoStreamInfoParameters { bool is_encrypted; }; +// Struct that gets passed for to CreateAudioStreamInfo() to create a +// StreamInfo instance. Useful for generating multiple AudioStreamInfo with +// slightly different parameters. +struct AudioStreamInfoParameters { + AudioStreamInfoParameters(); + ~AudioStreamInfoParameters(); + int track_id; + int32_t time_scale; + int64_t duration; + Codec codec; + std::string codec_string; + std::vector codec_config; + uint8_t sample_bits; + uint8_t num_channels; + uint32_t sampling_frequency; + uint64_t seek_preroll_ns; + uint64_t codec_delay_ns; + uint32_t max_bitrate; + uint32_t avg_bitrate; + std::string language; + bool is_encrypted; +}; + struct OnNewSegmentParameters { std::string file_name; int64_t start_time; @@ -113,6 +143,16 @@ std::shared_ptr CreateVideoStreamInfo( // Returns the "default" VideoStreamInfoParameters for testing. VideoStreamInfoParameters GetDefaultVideoStreamInfoParams(); +// Creates StreamInfo instance from AudioStreamInfoParameters. +std::shared_ptr CreateAudioStreamInfo( + const AudioStreamInfoParameters& param); + +// Returns the "default" configuration for testing given codec and parameters. +AudioStreamInfoParameters GetAudioStreamInfoParams( + Codec codec, + const char* codec_string, + const std::vector& codec_config); + // Returns the "default" values for OnMediaEnd(). OnMediaEndParameters GetDefaultOnMediaEndParams(); diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.cc b/packager/media/event/vod_media_info_dump_muxer_listener.cc index e0108f6831b..148503c581d 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener.cc @@ -95,7 +95,8 @@ void VodMediaInfoDumpMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void VodMediaInfoDumpMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { UNUSED(file_name); UNUSED(start_time); const double segment_duration_seconds = diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.h b/packager/media/event/vod_media_info_dump_muxer_listener.h index 4eacb4c6714..42dd57da22d 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.h +++ b/packager/media/event/vod_media_info_dump_muxer_listener.h @@ -49,7 +49,8 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size) override; diff --git a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc index f3926cd3a14..157c36a43e1 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc @@ -102,8 +102,10 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test { } void FireOnNewSegmentWithParams(const OnNewSegmentParameters& params) { + const int64_t kSegmentNumber = 1; listener_->OnNewSegment(params.file_name, params.start_time, - params.duration, params.segment_file_size); + params.duration, params.segment_file_size, + kSegmentNumber); } void FireOnMediaEndWithParams(const OnMediaEndParameters& params) { @@ -136,6 +138,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, UnencryptedStream_Normal) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -168,6 +172,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, EncryptedStream_Normal) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -216,6 +222,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, CheckPixelWidthAndHeightSet) { " time_scale: 10\n" " pixel_width: 8\n" " pixel_height: 9\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -258,6 +266,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, CheckBandwidth) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -295,6 +305,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, UnencryptedStream_Normal_SegmentList) " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" diff --git a/packager/media/formats/mp2t/CMakeLists.txt b/packager/media/formats/mp2t/CMakeLists.txt index 6c4d5b7b5a4..a86fa017b25 100644 --- a/packager/media/formats/mp2t/CMakeLists.txt +++ b/packager/media/formats/mp2t/CMakeLists.txt @@ -22,6 +22,9 @@ add_library(mp2t STATIC es_parser_h265.h es_parser_h26x.cc es_parser_h26x.h + es_parser_teletext.cc + es_parser_teletext.h + es_parser_teletext_tables.h es_parser.h mp2t_media_parser.cc mp2t_media_parser.h @@ -33,6 +36,7 @@ add_library(mp2t STATIC pes_packet_generator.h program_map_table_writer.cc program_map_table_writer.h + ts_audio_type.h ts_muxer.cc ts_muxer.h ts_packet.cc @@ -68,6 +72,7 @@ ac3_header_unittest.cc adts_header_unittest.cc es_parser_h264_unittest.cc es_parser_h26x_unittest.cc +es_parser_teletext_unittest.cc mp2t_media_parser_unittest.cc mpeg1_header_unittest.cc pes_packet_generator_unittest.cc diff --git a/packager/media/formats/mp2t/es_parser.h b/packager/media/formats/mp2t/es_parser.h index 2cb6f472eac..1225260c3ad 100644 --- a/packager/media/formats/mp2t/es_parser.h +++ b/packager/media/formats/mp2t/es_parser.h @@ -7,6 +7,7 @@ #include #include +#include namespace shaka { namespace media { diff --git a/packager/media/formats/mp2t/es_parser_h264.cc b/packager/media/formats/mp2t/es_parser_h264.cc index e450c81202c..eb4eec2d1e8 100644 --- a/packager/media/formats/mp2t/es_parser_h264.cc +++ b/packager/media/formats/mp2t/es_parser_h264.cc @@ -174,7 +174,8 @@ bool EsParserH264::UpdateVideoDecoderConfig(int pps_id) { codec_fourcc, decoder_config_record[1], decoder_config_record[2], decoder_config_record[3]), decoder_config_record.data(), decoder_config_record.size(), coded_width, - coded_height, pixel_width, pixel_height, sps->transfer_characteristics, 0, + coded_height, pixel_width, pixel_height, sps->color_primaries, + sps->matrix_coefficients, sps->transfer_characteristics, 0, nalu_length_size, std::string(), false); DVLOG(1) << "Profile IDC: " << sps->profile_idc; DVLOG(1) << "Level IDC: " << sps->level_idc; diff --git a/packager/media/formats/mp2t/es_parser_h265.cc b/packager/media/formats/mp2t/es_parser_h265.cc index 319ce47bcff..b89dd3f3f4b 100644 --- a/packager/media/formats/mp2t/es_parser_h265.cc +++ b/packager/media/formats/mp2t/es_parser_h265.cc @@ -177,8 +177,10 @@ bool EsParserH265::UpdateVideoDecoderConfig(int pps_id) { pid(), kMpeg2Timescale, kInfiniteDuration, kCodecH265, stream_format, decoder_config.GetCodecString(codec_fourcc), decoder_config_record.data(), decoder_config_record.size(), coded_width, coded_height, pixel_width, - pixel_height, sps->vui_parameters.transfer_characteristics, 0, - nalu_length_size, std::string(), false); + pixel_height, sps->vui_parameters.color_primaries, + sps->vui_parameters.matrix_coefficients, + sps->vui_parameters.transfer_characteristics, 0, nalu_length_size, + std::string(), false); // Video config notification. new_stream_info_cb_(last_video_decoder_config_); diff --git a/packager/media/formats/mp2t/es_parser_teletext.cc b/packager/media/formats/mp2t/es_parser_teletext.cc new file mode 100644 index 00000000000..770e19318b0 --- /dev/null +++ b/packager/media/formats/mp2t/es_parser_teletext.cc @@ -0,0 +1,619 @@ +// Copyright 2020 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include + +#include +#include +#include +#include +#include + +namespace shaka { +namespace media { +namespace mp2t { + +namespace { + +constexpr const char* kRegionTeletextPrefix = "ttx_"; + +const uint8_t EBU_TELETEXT_WITH_SUBTITLING = 0x03; +const int kPayloadSize = 40; +const int kNumTriplets = 13; + +template +constexpr T bit(T value, const size_t bit_pos) { + return (value >> bit_pos) & 0x1; +} + +uint8_t ReadHamming(BitReader& reader) { + uint8_t bits; + RCHECK(reader.ReadBits(8, &bits)); + return TELETEXT_HAMMING_8_4[bits]; +} + +bool Hamming_24_18(const uint32_t value, uint32_t& out_result) { + uint32_t result = value; + + uint8_t test = 0; + for (uint8_t i = 0; i < 23; i++) { + test ^= ((result >> i) & 0x01) * (i + 0x21); + } + test ^= ((result >> 0x17) & 0x01) * 0x20; + + if ((test & 0x1f) != 0x1f) { + if ((test & 0x20) == 0x20) { + return false; + } + result ^= 1 << (0x1e - test); + } + + out_result = (result & 0x000004) >> 2 | (result & 0x000070) >> 3 | + (result & 0x007f00) >> 4 | (result & 0x7f0000) >> 5; + return true; +} + +bool ParseSubtitlingDescriptor( + const uint8_t* descriptor, + const size_t size, + std::unordered_map& result) { + BitReader reader(descriptor, size); + RCHECK(reader.SkipBits(8)); + + size_t data_size; + RCHECK(reader.ReadBits(8, &data_size)); + RCHECK(data_size + 2 <= size); + + for (size_t i = 0; i < data_size; i += 8) { + uint32_t lang_code; + RCHECK(reader.ReadBits(24, &lang_code)); + uint8_t ignored_teletext_type; + RCHECK(reader.ReadBits(5, &ignored_teletext_type)); + uint8_t magazine_number; + RCHECK(reader.ReadBits(3, &magazine_number)); + if (magazine_number == 0) { + magazine_number = 8; + } + + uint8_t page_number_tens; + RCHECK(reader.ReadBits(4, &page_number_tens)); + uint8_t page_number_units; + RCHECK(reader.ReadBits(4, &page_number_units)); + const uint8_t page_number = page_number_tens * 10 + page_number_units; + + std::string lang(3, '\0'); + lang[0] = static_cast((lang_code >> 16) & 0xff); + lang[1] = static_cast((lang_code >> 8) & 0xff); + lang[2] = static_cast((lang_code >> 0) & 0xff); + + const uint16_t index = magazine_number * 100 + page_number; + result.emplace(index, std::move(lang)); + } + + return true; +} + +} // namespace + +EsParserTeletext::EsParserTeletext(const uint32_t pid, + const NewStreamInfoCB& new_stream_info_cb, + const EmitTextSampleCB& emit_sample_cb, + const uint8_t* descriptor, + const size_t descriptor_length) + : EsParser(pid), + new_stream_info_cb_(new_stream_info_cb), + emit_sample_cb_(emit_sample_cb), + magazine_(0), + page_number_(0), + charset_code_(0), + current_charset_{}, + last_pts_(0) { + if (!ParseSubtitlingDescriptor(descriptor, descriptor_length, languages_)) { + LOG(ERROR) << "Unable to parse teletext_descriptor"; + } + UpdateCharset(); +} + +bool EsParserTeletext::Parse(const uint8_t* buf, + int size, + int64_t pts, + int64_t dts) { + if (!sent_info_) { + sent_info_ = true; + auto info = std::make_shared(pid(), kMpeg2Timescale, + kInfiniteDuration, kCodecText, + "", "", 0, 0, ""); + for (const auto& pair : languages_) { + info->AddSubStream(pair.first, {pair.second}); + } + + new_stream_info_cb_(info); + } + + return ParseInternal(buf, size, pts); +} + +bool EsParserTeletext::Flush() { + std::vector keys; + for (const auto& entry : page_state_) { + keys.push_back(entry.first); + } + + for (const auto key : keys) { + SendPending(key, last_pts_); + } + + return true; +} + +void EsParserTeletext::Reset() { + page_state_.clear(); + magazine_ = 0; + page_number_ = 0; + sent_info_ = false; + charset_code_ = 0; + UpdateCharset(); +} + +bool EsParserTeletext::ParseInternal(const uint8_t* data, + const size_t size, + const int64_t pts) { + BitReader reader(data, size); + RCHECK(reader.SkipBits(8)); + std::vector rows; + + while (reader.bits_available()) { + uint8_t data_unit_id; + RCHECK(reader.ReadBits(8, &data_unit_id)); + + uint8_t data_unit_length; + RCHECK(reader.ReadBits(8, &data_unit_length)); + + if (data_unit_id != EBU_TELETEXT_WITH_SUBTITLING) { + RCHECK(reader.SkipBytes(data_unit_length)); + continue; + } + + if (data_unit_length != 44) { + // Teletext data unit length is always 44 bytes + LOG(ERROR) << "Bad Teletext data length"; + break; + } + + RCHECK(reader.SkipBits(16)); + + uint16_t address_bits; + RCHECK(reader.ReadBits(16, &address_bits)); + + uint8_t magazine = bit(address_bits, 14) + 2 * bit(address_bits, 12) + + 4 * bit(address_bits, 10); + + if (magazine == 0) { + magazine = 8; + } + + const uint8_t packet_nr = + (bit(address_bits, 8) + 2 * bit(address_bits, 6) + + 4 * bit(address_bits, 4) + 8 * bit(address_bits, 2) + + 16 * bit(address_bits, 0)); + const uint8_t* data_block = reader.current_byte_ptr(); + RCHECK(reader.SkipBytes(40)); + + TextRow row; + if (ParseDataBlock(pts, data_block, packet_nr, magazine, row)) { + rows.emplace_back(std::move(row)); + } + } + + if (rows.empty()) { + return true; + } + const uint16_t index = magazine_ * 100 + page_number_; + auto page_state_itr = page_state_.find(index); + if (page_state_itr == page_state_.end()) { + page_state_.emplace(index, TextBlock{std::move(rows), {}, last_pts_}); + + } else { + for (auto& row : rows) { + auto& page_state_lines = page_state_itr->second.rows; + page_state_lines.emplace_back(std::move(row)); + } + rows.clear(); + } + + return true; +} + +bool EsParserTeletext::ParseDataBlock(const int64_t pts, + const uint8_t* data_block, + const uint8_t packet_nr, + const uint8_t magazine, + TextRow& row) { + if (packet_nr == 0) { + last_pts_ = pts; + BitReader reader(data_block, 32); + + const uint8_t page_number_units = ReadHamming(reader); + const uint8_t page_number_tens = ReadHamming(reader); + if (page_number_units == 0xf || page_number_tens == 0xf) { + RCHECK(reader.SkipBits(40)); + return false; + } + const uint8_t page_number = 10 * page_number_tens + page_number_units; + + const uint16_t index = magazine * 100 + page_number; + SendPending(index, pts); + + page_number_ = page_number; + magazine_ = magazine; + + RCHECK(reader.SkipBits(40)); + const uint8_t subcode_c11_c14 = ReadHamming(reader); + const uint8_t charset_code = subcode_c11_c14 >> 1; + if (charset_code != charset_code_) { + charset_code_ = charset_code; + UpdateCharset(); + } + + return false; + + } else if (packet_nr == 26) { + ParsePacket26(data_block); + return false; + + } else if (packet_nr > 26) { + return false; + } + + row = BuildRow(data_block, packet_nr); + return true; +} + +void EsParserTeletext::UpdateCharset() { + memcpy(current_charset_, TELETEXT_CHARSET_G0_LATIN, + sizeof(TELETEXT_CHARSET_G0_LATIN)); + if (charset_code_ > 7) { + return; + } + const auto teletext_national_subset = + static_cast(charset_code_); + + switch (teletext_national_subset) { + case TELETEXT_NATIONAL_SUBSET::ENGLISH: + UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_ENGLISH); + break; + case TELETEXT_NATIONAL_SUBSET::FRENCH: + UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_FRENCH); + break; + case TELETEXT_NATIONAL_SUBSET::SWEDISH_FINNISH_HUNGARIAN: + UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_SWEDISH_FINNISH_HUNGARIAN); + break; + case TELETEXT_NATIONAL_SUBSET::CZECH_SLOVAK: + UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_CZECH_SLOVAK); + break; + case TELETEXT_NATIONAL_SUBSET::GERMAN: + UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_GERMAN); + break; + case TELETEXT_NATIONAL_SUBSET::PORTUGUESE_SPANISH: + UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_PORTUGUESE_SPANISH); + break; + case TELETEXT_NATIONAL_SUBSET::ITALIAN: + UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_ITALIAN); + break; + case TELETEXT_NATIONAL_SUBSET::NONE: + default: + break; + } +} + +void EsParserTeletext::SendPending(const uint16_t index, const int64_t pts) { + auto page_state_itr = page_state_.find(index); + + if (page_state_itr == page_state_.end() || + page_state_itr->second.rows.empty()) { + return; + } + + const auto& pending_rows = page_state_itr->second.rows; + const auto pending_pts = page_state_itr->second.pts; + + TextSettings text_settings; + std::shared_ptr text_sample; + std::vector sub_fragments; + + if (pending_rows.size() == 1) { + // This is a single line of formatted text. + // Propagate row number/2 and alignment + const float line_nr = float(pending_rows[0].row_number) / 2.0; + text_settings.line = TextNumber(line_nr, TextUnitType::kLines); + text_settings.region = kRegionTeletextPrefix + std::to_string(int(line_nr)); + text_settings.text_alignment = pending_rows[0].alignment; + text_sample = std::make_shared( + "", pending_pts, pts, text_settings, pending_rows[0].fragment); + text_sample->set_sub_stream_index(index); + emit_sample_cb_(text_sample); + page_state_.erase(index); + return; + } else { + int32_t latest_row_nr = -1; + bool last_double_height = false; + bool new_sample = true; + for (const auto& row : pending_rows) { + int row_nr = row.row_number; + bool double_height = row.double_height; + int row_step = last_double_height ? 2 : 1; + if (latest_row_nr != -1) { // Not the first row + if (row_nr != latest_row_nr + row_step) { + // Send what has been collected since not adjacent + text_sample = + std::make_shared("", pending_pts, pts, text_settings, + TextFragment({}, sub_fragments)); + text_sample->set_sub_stream_index(index); + emit_sample_cb_(text_sample); + new_sample = true; + } else { + // Add a newline and the next row to the current sample + sub_fragments.push_back(TextFragment({}, true)); + sub_fragments.push_back(row.fragment); + new_sample = false; + } + } + if (new_sample) { + const float line_nr = float(row.row_number) / 2.0; + text_settings.line = TextNumber(line_nr, TextUnitType::kLines); + text_settings.region = + kRegionTeletextPrefix + std::to_string(int(line_nr)); + text_settings.text_alignment = row.alignment; + sub_fragments.clear(); + sub_fragments.push_back(row.fragment); + } + last_double_height = double_height; + latest_row_nr = row_nr; + } + } + + text_sample = std::make_shared( + "", pending_pts, pts, text_settings, TextFragment({}, sub_fragments)); + text_sample->set_sub_stream_index(index); + emit_sample_cb_(text_sample); + + page_state_.erase(index); +} + +// BuildRow builds a row with alignment information. +EsParserTeletext::TextRow EsParserTeletext::BuildRow(const uint8_t* data_block, + const uint8_t row) const { + std::string next_string; + next_string.reserve(kPayloadSize * 2); + + const uint16_t index = magazine_ * 100 + page_number_; + const auto page_state_itr = page_state_.find(index); + + const std::unordered_map* column_replacement_map = + nullptr; + if (page_state_itr != page_state_.cend()) { + const auto row_itr = + page_state_itr->second.packet_26_replacements.find(row); + if (row_itr != page_state_itr->second.packet_26_replacements.cend()) { + column_replacement_map = &(row_itr->second); + } + } + + int32_t start_pos = 0; + int32_t end_pos = 0; + bool double_height = false; + TextFragmentStyle text_style = TextFragmentStyle(); + text_style.color = "white"; + text_style.backgroundColor = "black"; + // A typical 40 character line looks like: + // doubleHeight, [color] spaces, Start, Start, text, End End, spaces + for (size_t i = 0; i < kPayloadSize; ++i) { + if (column_replacement_map) { + const auto column_itr = column_replacement_map->find(i); + if (column_itr != column_replacement_map->cend()) { + next_string.append(column_itr->second); + continue; + } + } + + char next_char = + static_cast(TELETEXT_BITREVERSE_8[data_block[i]] & 0x7f); + + if (next_char < 0x20) { + // Here are control characters, which are not printable. + // These include colors, double-height, flashing, etc. + // We only handle one-foreground color and double-height. + switch (next_char) { + case 0x0: // Alpha Black (not included in Level 1.5) + // color = ColorBlack + break; + case 0x1: + text_style.color = "red"; + break; + case 0x2: + text_style.color = "green"; + break; + case 0x3: + text_style.color = "yellow"; + break; + case 0x4: + text_style.color = "blue"; + break; + case 0x5: + text_style.color = "magenta"; + break; + case 0x6: + text_style.color = "cyan"; + break; + case 0x7: + text_style.color = "white"; + break; + case 0x08: // Flash (not handled) + break; + case 0x09: // Steady (not handled) + break; + case 0xa: // End Box + end_pos = i - 1; + break; + case 0xb: // Start Box, typically twice due to double height + start_pos = i + 1; + continue; // Do not propagate as a space + break; + case 0xc: // Normal size + break; + case 0xd: // Double height, typically always used + double_height = true; + break; + case 0x1c: // Black background (not handled) + break; + case 0x1d: // Set background color from text color. + text_style.backgroundColor = text_style.color; + text_style.color = "black"; // Avoid having same as background + break; + default: + // Rest of codes below 0x20 are not part of Level 1.5 or related to + // mosaic graphics (non-text) + break; + } + next_char = + 0x20; // These characters result in a space if between start and end + } + if (start_pos == 0 || end_pos != 0) { // Not between start and end + continue; + } + switch (next_char) { + case '&': + next_string.append("&"); + break; + case '<': + next_string.append("<"); + break; + default: { + const std::string replacement(current_charset_[next_char - 0x20]); + next_string.append(replacement); + } break; + } + } + if (end_pos == 0) { + end_pos = kPayloadSize - 1; + } + + // Using start_pos and end_pos we approximated alignment of text + // depending on the number of spaces to the left and right of the text. + auto left_right_diff = start_pos - (kPayloadSize - 1 - end_pos); + TextAlignment alignment; + if (left_right_diff > 4) { + alignment = TextAlignment::kRight; + } else if (left_right_diff < -4) { + alignment = TextAlignment::kLeft; + } else { + alignment = TextAlignment::kCenter; + } + const auto text_row = TextRow( + {alignment, row, double_height, {TextFragment(text_style, next_string)}}); + + return text_row; +} + +void EsParserTeletext::ParsePacket26(const uint8_t* data_block) { + const uint16_t index = magazine_ * 100 + page_number_; + auto page_state_itr = page_state_.find(index); + if (page_state_itr == page_state_.end()) { + page_state_.emplace(index, TextBlock{{}, {}, last_pts_}); + } + auto& replacement_map = page_state_[index].packet_26_replacements; + + uint8_t row = 0; + + std::vector x26_triplets; + x26_triplets.reserve(kNumTriplets); + for (uint8_t i = 1; i < kPayloadSize; i += 3) { + const uint32_t bytes = (TELETEXT_BITREVERSE_8[data_block[i + 2]] << 16) | + (TELETEXT_BITREVERSE_8[data_block[i + 1]] << 8) | + TELETEXT_BITREVERSE_8[data_block[i]]; + uint32_t triplet; + if (Hamming_24_18(bytes, triplet)) { + x26_triplets.emplace_back(triplet); + } + } + + for (const auto triplet : x26_triplets) { + const uint8_t mode = (triplet & 0x7c0) >> 6; + const uint8_t address = triplet & 0x3f; + const uint8_t row_address_group = (address >= 0x28) && (address <= 0x3f); + + if ((mode == 0x4) && (row_address_group == 0x1)) { + row = address - 0x28; + if (row == 0x0) { + row = 0x18; + } + } + + if (mode >= 0x11 && mode <= 0x1f && row_address_group == 0x1) { + break; + } + + const uint8_t data = (triplet & 0x3f800) >> 11; + + if (mode == 0x0f && row_address_group == 0x0 && data > 0x1f) { + SetPacket26ReplacementString(replacement_map, row, address, + reinterpret_cast( + TELETEXT_CHARSET_G2_LATIN[data - 0x20])); + } + + if (mode == 0x10 && row_address_group == 0x0 && data == 0x40) { + SetPacket26ReplacementString(replacement_map, row, address, "@"); + } + + if (mode < 0x11 || mode > 0x1f || row_address_group != 0x0) { + continue; + } + + if (data >= 0x41 && data <= 0x5a) { + SetPacket26ReplacementString( + replacement_map, row, address, + reinterpret_cast( + TELETEXT_G2_LATIN_ACCENTS[mode - 0x11][data - 0x41])); + + } else if (data >= 0x61 && data <= 0x7a) { + SetPacket26ReplacementString( + replacement_map, row, address, + reinterpret_cast( + TELETEXT_G2_LATIN_ACCENTS[mode - 0x11][data - 0x47])); + + } else if ((data & 0x7f) >= 0x20) { + SetPacket26ReplacementString( + replacement_map, row, address, + reinterpret_cast( + TELETEXT_CHARSET_G0_LATIN[(data & 0x7f) - 0x20])); + } + } +} + +void EsParserTeletext::UpdateNationalSubset( + const uint8_t national_subset[13][3]) { + for (size_t i = 0; i < 13; ++i) { + const size_t position = TELETEXT_NATIONAL_CHAR_INDEX_G0[i]; + memcpy(current_charset_[position], national_subset[i], 3); + } +} + +void EsParserTeletext::SetPacket26ReplacementString( + RowColReplacementMap& replacement_map, + const uint8_t row, + const uint8_t column, + std::string&& replacement_string) { + auto replacement_map_itr = replacement_map.find(row); + if (replacement_map_itr == replacement_map.cend()) { + replacement_map.emplace(row, std::unordered_map{}); + } + auto& column_map = replacement_map[row]; + column_map.emplace(column, std::move(replacement_string)); +} + +} // namespace mp2t +} // namespace media +} // namespace shaka diff --git a/packager/media/formats/mp2t/es_parser_teletext.h b/packager/media/formats/mp2t/es_parser_teletext.h new file mode 100644 index 00000000000..0efde38f3e8 --- /dev/null +++ b/packager/media/formats/mp2t/es_parser_teletext.h @@ -0,0 +1,89 @@ +// Copyright 2020 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef PACKAGER_MEDIA_FORMATS_MP2T_ES_PARSER_TELETEXT_H_ +#define PACKAGER_MEDIA_FORMATS_MP2T_ES_PARSER_TELETEXT_H_ + +#include +#include +#include +#include + +#include +#include + +namespace shaka { +namespace media { +namespace mp2t { + +class EsParserTeletext : public EsParser { + public: + EsParserTeletext(const uint32_t pid, + const NewStreamInfoCB& new_stream_info_cb, + const EmitTextSampleCB& emit_sample_cb, + const uint8_t* descriptor, + const size_t descriptor_length); + + EsParserTeletext(const EsParserTeletext&) = delete; + EsParserTeletext& operator=(const EsParserTeletext&) = delete; + + bool Parse(const uint8_t* buf, int size, int64_t pts, int64_t dts) override; + bool Flush() override; + void Reset() override; + + private: + using RowColReplacementMap = + std::unordered_map>; + + struct TextRow { + TextAlignment alignment; + int row_number; + bool double_height; + TextFragment fragment; + }; + + struct TextBlock { + std::vector rows; + RowColReplacementMap packet_26_replacements; + int64_t pts; + }; + + bool ParseInternal(const uint8_t* data, const size_t size, const int64_t pts); + bool ParseDataBlock(const int64_t pts, + const uint8_t* data_block, + const uint8_t packet_nr, + const uint8_t magazine, + TextRow& display_text); + void UpdateCharset(); + void SendPending(const uint16_t index, const int64_t pts); + TextRow BuildRow(const uint8_t* data_block, const uint8_t row) const; + void ParsePacket26(const uint8_t* data_block); + void UpdateNationalSubset(const uint8_t national_subset[13][3]); + + static void SetPacket26ReplacementString( + RowColReplacementMap& replacement_map, + const uint8_t row, + const uint8_t column, + std::string&& replacement_string); + + NewStreamInfoCB new_stream_info_cb_; + EmitTextSampleCB emit_sample_cb_; + + std::unordered_map languages_; + bool sent_info_ = false; + uint8_t magazine_; + uint8_t page_number_; + std::unordered_map page_state_; + uint8_t charset_code_; + char current_charset_[96][3]; + int64_t last_pts_; +}; + +} // namespace mp2t +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_ES_PARSER_TELETEXT_H_ diff --git a/packager/media/formats/mp2t/es_parser_teletext_tables.h b/packager/media/formats/mp2t/es_parser_teletext_tables.h new file mode 100644 index 00000000000..25e7560a6bf --- /dev/null +++ b/packager/media/formats/mp2t/es_parser_teletext_tables.h @@ -0,0 +1,434 @@ +// Copyright 2020 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef PACKAGER_MEDIA_FORMATS_MP2T_ES_PARSER_TELETEXT_TABLES_H_ +#define PACKAGER_MEDIA_FORMATS_MP2T_ES_PARSER_TELETEXT_TABLES_H_ + +#include + +namespace shaka { +namespace media { +namespace mp2t { + +const uint8_t TELETEXT_BITREVERSE_8[] = { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, + 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, + 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, + 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, + 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, + 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, + 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, + 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, + 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, + 0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, + 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, + 0x3f, 0xbf, 0x7f, 0xff}; + +const uint8_t TELETEXT_HAMMING_8_4[] = { + 0x00, 0x08, 0x00, 0x08, 0x04, 0x0c, 0x04, 0x0c, 0x00, 0x08, 0x00, 0x08, + 0x04, 0x0c, 0x04, 0x0c, 0x02, 0x0a, 0x02, 0x0a, 0x06, 0x0e, 0x06, 0x0e, + 0x02, 0x0a, 0x02, 0x0a, 0x06, 0x0e, 0x06, 0x0e, 0x00, 0x08, 0x00, 0x08, + 0x04, 0x0c, 0x04, 0x0c, 0x00, 0x08, 0x00, 0x08, 0x04, 0x0c, 0x04, 0x0c, + 0x02, 0x0a, 0x02, 0x0a, 0x06, 0x0e, 0x06, 0x0e, 0x02, 0x0a, 0x02, 0x0a, + 0x06, 0x0e, 0x06, 0x0e, 0x01, 0x09, 0x01, 0x09, 0x05, 0x0d, 0x05, 0x0d, + 0x01, 0x09, 0x01, 0x09, 0x05, 0x0d, 0x05, 0x0d, 0x03, 0x0b, 0x03, 0x0b, + 0x07, 0x0f, 0x07, 0x0f, 0x03, 0x0b, 0x03, 0x0b, 0x07, 0x0f, 0x07, 0x0f, + 0x01, 0x09, 0x01, 0x09, 0x05, 0x0d, 0x05, 0x0d, 0x01, 0x09, 0x01, 0x09, + 0x05, 0x0d, 0x05, 0x0d, 0x03, 0x0b, 0x03, 0x0b, 0x07, 0x0f, 0x07, 0x0f, + 0x03, 0x0b, 0x03, 0x0b, 0x07, 0x0f, 0x07, 0x0f, 0x00, 0x08, 0x00, 0x08, + 0x04, 0x0c, 0x04, 0x0c, 0x00, 0x08, 0x00, 0x08, 0x04, 0x0c, 0x04, 0x0c, + 0x02, 0x0a, 0x02, 0x0a, 0x06, 0x0e, 0x06, 0x0e, 0x02, 0x0a, 0x02, 0x0a, + 0x06, 0x0e, 0x06, 0x0e, 0x00, 0x08, 0x00, 0x08, 0x04, 0x0c, 0x04, 0x0c, + 0x00, 0x08, 0x00, 0x08, 0x04, 0x0c, 0x04, 0x0c, 0x02, 0x0a, 0x02, 0x0a, + 0x06, 0x0e, 0x06, 0x0e, 0x02, 0x0a, 0x02, 0x0a, 0x06, 0x0e, 0x06, 0x0e, + 0x01, 0x09, 0x01, 0x09, 0x05, 0x0d, 0x05, 0x0d, 0x01, 0x09, 0x01, 0x09, + 0x05, 0x0d, 0x05, 0x0d, 0x03, 0x0b, 0x03, 0x0b, 0x07, 0x0f, 0x07, 0x0f, + 0x03, 0x0b, 0x03, 0x0b, 0x07, 0x0f, 0x07, 0x0f, 0x01, 0x09, 0x01, 0x09, + 0x05, 0x0d, 0x05, 0x0d, 0x01, 0x09, 0x01, 0x09, 0x05, 0x0d, 0x05, 0x0d, + 0x03, 0x0b, 0x03, 0x0b, 0x07, 0x0f, 0x07, 0x0f, 0x03, 0x0b, 0x03, 0x0b, + 0x07, 0x0f, 0x07, 0x0f}; + +const uint8_t TELETEXT_CHARSET_G0_LATIN[96][3] = { + {0x20, 0x0, 0x0}, {0x21, 0x0, 0x0}, {0x22, 0x0, 0x0}, {0xc2, 0xa3, 0x0}, + {0x24, 0x0, 0x0}, {0x25, 0x0, 0x0}, {0x26, 0x0, 0x0}, {0x27, 0x0, 0x0}, + {0x28, 0x0, 0x0}, {0x29, 0x0, 0x0}, {0x2a, 0x0, 0x0}, {0x2b, 0x0, 0x0}, + {0x2c, 0x0, 0x0}, {0x2d, 0x0, 0x0}, {0x2e, 0x0, 0x0}, {0x2f, 0x0, 0x0}, + {0x30, 0x0, 0x0}, {0x31, 0x0, 0x0}, {0x32, 0x0, 0x0}, {0x33, 0x0, 0x0}, + {0x34, 0x0, 0x0}, {0x35, 0x0, 0x0}, {0x36, 0x0, 0x0}, {0x37, 0x0, 0x0}, + {0x38, 0x0, 0x0}, {0x39, 0x0, 0x0}, {0x3a, 0x0, 0x0}, {0x3b, 0x0, 0x0}, + {0x3c, 0x0, 0x0}, {0x3d, 0x0, 0x0}, {0x3e, 0x0, 0x0}, {0x3f, 0x0, 0x0}, + {0x40, 0x0, 0x0}, {0x41, 0x0, 0x0}, {0x42, 0x0, 0x0}, {0x43, 0x0, 0x0}, + {0x44, 0x0, 0x0}, {0x45, 0x0, 0x0}, {0x46, 0x0, 0x0}, {0x47, 0x0, 0x0}, + {0x48, 0x0, 0x0}, {0x49, 0x0, 0x0}, {0x4a, 0x0, 0x0}, {0x4b, 0x0, 0x0}, + {0x4c, 0x0, 0x0}, {0x4d, 0x0, 0x0}, {0x4e, 0x0, 0x0}, {0x4f, 0x0, 0x0}, + {0x50, 0x0, 0x0}, {0x51, 0x0, 0x0}, {0x52, 0x0, 0x0}, {0x53, 0x0, 0x0}, + {0x54, 0x0, 0x0}, {0x55, 0x0, 0x0}, {0x56, 0x0, 0x0}, {0x57, 0x0, 0x0}, + {0x58, 0x0, 0x0}, {0x59, 0x0, 0x0}, {0x5a, 0x0, 0x0}, {0xc2, 0xab, 0x0}, + {0xc2, 0xbd, 0x0}, {0xc2, 0xbb, 0x0}, {0x5e, 0x0, 0x0}, {0x23, 0x0, 0x0}, + {0x2d, 0x0, 0x0}, {0x61, 0x0, 0x0}, {0x62, 0x0, 0x0}, {0x63, 0x0, 0x0}, + {0x64, 0x0, 0x0}, {0x65, 0x0, 0x0}, {0x66, 0x0, 0x0}, {0x67, 0x0, 0x0}, + {0x68, 0x0, 0x0}, {0x69, 0x0, 0x0}, {0x6a, 0x0, 0x0}, {0x6b, 0x0, 0x0}, + {0x6c, 0x0, 0x0}, {0x6d, 0x0, 0x0}, {0x6e, 0x0, 0x0}, {0x6f, 0x0, 0x0}, + {0x70, 0x0, 0x0}, {0x71, 0x0, 0x0}, {0x72, 0x0, 0x0}, {0x73, 0x0, 0x0}, + {0x74, 0x0, 0x0}, {0x75, 0x0, 0x0}, {0x76, 0x0, 0x0}, {0x77, 0x0, 0x0}, + {0x78, 0x0, 0x0}, {0x79, 0x0, 0x0}, {0x7a, 0x0, 0x0}, {0xc2, 0xbc, 0x0}, + {0xc2, 0xa6, 0x0}, {0xc2, 0xbe, 0x0}, {0xc3, 0xb7, 0x0}, {0x7f, 0x0, 0x0}}; + +const size_t TELETEXT_NATIONAL_CHAR_INDEX_G0[13] = { + 0x03, 0x04, 0x20, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, 0x5b, 0x5c, 0x5d, 0x5e}; + +const uint8_t TELETEXT_NATIONAL_SUBSET_ENGLISH[13][3] = { + {0xc2, 0xa3, 0x0}, {0x24, 0x0, 0x0}, {0x40, 0x0, 0x0}, {0xc2, 0xab, 0x0}, + {0xc2, 0xbd, 0x0}, {0xc2, 0xbb, 0x0}, {0x5e, 0x0, 0x0}, {0x23, 0x0, 0x0}, + {0x2d, 0x0, 0x0}, {0xc2, 0xbc, 0x0}, {0xc2, 0xa6, 0x0}, {0xc2, 0xbe, 0x0}, + {0xc3, 0xb7, 0x0}}; + +const uint8_t TELETEXT_NATIONAL_SUBSET_FRENCH[13][3] = { + {0xc3, 0xa9, 0x0}, {0xc3, 0xaf, 0x0}, {0xc3, 0xa0, 0x0}, {0xc3, 0xab, 0x0}, + {0xc3, 0xaa, 0x0}, {0xc3, 0xb9, 0x0}, {0xc3, 0xae, 0x0}, {0x23, 0x0, 0x0}, + {0xc3, 0xa8, 0x0}, {0xc3, 0xa2, 0x0}, {0xc3, 0xb4, 0x0}, {0xc3, 0xbb, 0x0}, + {0xc3, 0xa7, 0x0}}; + +const uint8_t TELETEXT_NATIONAL_SUBSET_SWEDISH_FINNISH_HUNGARIAN[13][3] = { + {0x23, 0x0, 0x0}, {0xc2, 0xa4, 0x0}, {0xc3, 0x89, 0x0}, {0xc3, 0x84, 0x0}, + {0xc3, 0x96, 0x0}, {0xc3, 0x85, 0x0}, {0xc3, 0x9c, 0x0}, {0x5f, 0x0, 0x0}, + {0xc3, 0xa9, 0x0}, {0xc3, 0xa4, 0x0}, {0xc3, 0xb6, 0x0}, {0xc3, 0xa5, 0x0}, + {0xc3, 0xbc, 0x0}}; + +const uint8_t TELETEXT_NATIONAL_SUBSET_CZECH_SLOVAK[13][3] = { + {0x23, 0x0, 0x0}, {0xc5, 0xaf, 0x0}, {0xc4, 0x8d, 0x0}, {0xc5, 0xa5, 0x0}, + {0xc5, 0xbe, 0x0}, {0xc3, 0xbd, 0x0}, {0xc3, 0xad, 0x0}, {0xc5, 0x99, 0x0}, + {0xc3, 0xa9, 0x0}, {0xc3, 0xa1, 0x0}, {0xc4, 0x9b, 0x0}, {0xc3, 0xba, 0x0}, + {0xc5, 0xa1, 0x0}}; + +const uint8_t TELETEXT_NATIONAL_SUBSET_GERMAN[13][3] = { + {0x23, 0x0, 0x0}, {0x24, 0x0, 0x0}, {0xc2, 0xa7, 0x0}, {0xc3, 0x84, 0x0}, + {0xc3, 0x96, 0x0}, {0xc3, 0x9c, 0x0}, {0x5e, 0x0, 0x0}, {0x5f, 0x0, 0x0}, + {0xc2, 0xb0, 0x0}, {0xc3, 0xa4, 0x0}, {0xc3, 0xb6, 0x0}, {0xc3, 0xbc, 0x0}, + {0xc3, 0x9f, 0x0}}; + +const uint8_t TELETEXT_NATIONAL_SUBSET_PORTUGUESE_SPANISH[13][3] = { + {0xc3, 0xa7, 0x0}, {0x24, 0x0, 0x0}, {0xc2, 0xa1, 0x0}, {0xc3, 0xa1, 0x0}, + {0xc3, 0xa9, 0x0}, {0xc3, 0xad, 0x0}, {0xc3, 0xb3, 0x0}, {0xc3, 0xba, 0x0}, + {0xc2, 0xbf, 0x0}, {0xc3, 0xbc, 0x0}, {0xc3, 0xb1, 0x0}, {0xc3, 0xa8, 0x0}, + {0xc3, 0xa0, 0x0}}; + +const uint8_t TELETEXT_NATIONAL_SUBSET_ITALIAN[13][3] = { + {0xc2, 0xa3, 0x0}, {0x24, 0x0, 0x0}, {0xc3, 0xa9, 0x0}, {0xc2, 0xb0, 0x0}, + {0xc3, 0xa7, 0x0}, {0xc2, 0xbb, 0x0}, {0x5e, 0x0, 0x0}, {0x23, 0x0, 0x0}, + {0xc3, 0xb9, 0x0}, {0xc3, 0xa0, 0x0}, {0xc3, 0xb2, 0x0}, {0xc3, 0xa8, 0x0}, + {0xc3, 0xac, 0x0}}; + +enum class TELETEXT_NATIONAL_SUBSET : uint8_t { + ENGLISH = 0, + FRENCH = 1, + SWEDISH_FINNISH_HUNGARIAN = 2, + CZECH_SLOVAK = 3, + GERMAN = 4, + PORTUGUESE_SPANISH = 5, + ITALIAN = 6, + NONE = 7 +}; + +const uint8_t TELETEXT_CHARSET_G2_LATIN[96][3] = { + {0x20, 0x0, 0x0}, {0xc2, 0xa1, 0x0}, {0xc2, 0xa2, 0x0}, {0xc2, 0xa3, 0x0}, + {0x24, 0x0, 0x0}, {0xc2, 0xa5, 0x0}, {0x23, 0x0, 0x0}, {0xc2, 0xa7, 0x0}, + {0xc2, 0xa4, 0x0}, {0x20, 0x18, 0x0}, {0x20, 0x1c, 0x0}, {0xc2, 0xab, 0x0}, + {0x21, 0x90, 0x0}, {0x21, 0x91, 0x0}, {0x21, 0x92, 0x0}, {0x21, 0x93, 0x0}, + {0xc2, 0xb0, 0x0}, {0xc2, 0xb1, 0x0}, {0xc2, 0xb2, 0x0}, {0xc2, 0xb3, 0x0}, + {0xc3, 0x97, 0x0}, {0xc2, 0xb5, 0x0}, {0xc2, 0xb6, 0x0}, {0xc2, 0xb7, 0x0}, + {0xc3, 0xb7, 0x0}, {0x20, 0x19, 0x0}, {0x20, 0x1d, 0x0}, {0xc2, 0xbb, 0x0}, + {0xc2, 0xbc, 0x0}, {0xc2, 0xbd, 0x0}, {0xc2, 0xbe, 0x0}, {0xbf, 0x0, 0x0}, + {0x20, 0x0, 0x0}, {0xcc, 0x80, 0x0}, {0xcc, 0x81, 0x0}, {0xcc, 0x82, 0x0}, + {0xcc, 0x83, 0x0}, {0xcc, 0x84, 0x0}, {0xcc, 0x86, 0x0}, {0xcc, 0x87, 0x0}, + {0xcc, 0x88, 0x0}, {0x0, 0x0, 0x0}, {0xcc, 0x8a, 0x0}, {0xcc, 0xa7, 0x0}, + {0x5f, 0x0, 0x0}, {0xcc, 0x8b, 0x0}, {0xcc, 0xa8, 0x0}, {0xcc, 0x8c, 0x0}, + {0x20, 0x15, 0x0}, {0xc2, 0xb9, 0x0}, {0xc2, 0xae, 0x0}, {0xc2, 0xa9, 0x0}, + {0x21, 0x22, 0x0}, {0x26, 0x6a, 0x0}, {0x20, 0xac, 0x0}, {0x20, 0x30, 0x0}, + {0xce, 0xb1, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x21, 0x5b, 0x0}, {0x21, 0x5c, 0x0}, {0x21, 0x5d, 0x0}, {0x21, 0x5e, 0x0}, + {0xce, 0xa9, 0x0}, {0xc3, 0x86, 0x0}, {0xc4, 0x90, 0x0}, {0xc2, 0xaa, 0x0}, + {0xc4, 0xa6, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0xb2, 0x0}, {0xc4, 0xbf, 0x0}, + {0xc5, 0x81, 0x0}, {0xc3, 0x98, 0x0}, {0xc5, 0x92, 0x0}, {0xc2, 0xba, 0x0}, + {0xc3, 0x9e, 0x0}, {0xc5, 0xa6, 0x0}, {0xc5, 0x8a, 0x0}, {0xc5, 0x89, 0x0}, + {0xc4, 0xb8, 0x0}, {0xc3, 0xa6, 0x0}, {0xc4, 0x91, 0x0}, {0xc3, 0xb0, 0x0}, + {0xc4, 0xa7, 0x0}, {0xc4, 0xb1, 0x0}, {0xc4, 0xb3, 0x0}, {0xc5, 0x80, 0x0}, + {0xc5, 0x82, 0x0}, {0xc3, 0xb8, 0x0}, {0xc5, 0x93, 0x0}, {0xc3, 0x9f, 0x0}, + {0xc3, 0xbe, 0x0}, {0xc5, 0xa7, 0x0}, {0xc5, 0x8b, 0x0}, {0x20, 0x0, 0x0}}; + +const uint8_t TELETEXT_G2_LATIN_ACCENTS[15][52][3] = { + { + {0xc3, 0x80, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0x88, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0x8c, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0x92, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0x99, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xa0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xa8, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xac, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0xb2, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xb9, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + }, + { + {0xc3, 0x81, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x86, 0x0}, + {0x0, 0x0, 0x0}, {0xc3, 0x89, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0x8d, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0xb9, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0x83, 0x0}, {0xc3, 0x93, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0x94, 0x0}, + {0xc5, 0x9a, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0x9a, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0x9d, 0x0}, {0xc5, 0xb9, 0x0}, {0xc3, 0xa1, 0x0}, + {0x0, 0x0, 0x0}, {0xc4, 0x87, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0xa9, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0xa3, 0x0}, + {0x0, 0x0, 0x0}, {0xc3, 0xad, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc4, 0xba, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0x84, 0x0}, {0xc3, 0xb3, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0x95, 0x0}, {0xc5, 0x9b, 0x0}, + {0x0, 0x0, 0x0}, {0xc3, 0xba, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xbd, 0x0}, + {0xc5, 0xba, 0x0}, + }, + { + {0xc3, 0x82, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x88, 0x0}, + {0x0, 0x0, 0x0}, {0xc3, 0x8a, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0x9c, 0x0}, {0xc4, 0xa4, 0x0}, {0xc3, 0x8e, 0x0}, + {0xc4, 0xb4, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0x94, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0x9c, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0x9b, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0xb4, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xb6, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xa2, 0x0}, + {0x0, 0x0, 0x0}, {0xc4, 0x89, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0xaa, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x9d, 0x0}, + {0xc4, 0xa5, 0x0}, {0xc3, 0xae, 0x0}, {0xc4, 0xb5, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc3, 0xb4, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0x9d, 0x0}, + {0x0, 0x0, 0x0}, {0xc3, 0xbb, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xb5, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0xb7, 0x0}, + {0x0, 0x0, 0x0}, + }, + { + {0xc3, 0x83, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0xa8, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc3, 0x91, 0x0}, {0xc3, 0x95, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0xa8, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xa3, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc4, 0xa9, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0xb1, 0x0}, {0xc3, 0xb5, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0xa9, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, + }, + { + {0xc4, 0x80, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0x92, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0xaa, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0x8c, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xaa, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x81, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x93, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0xab, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0x8d, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0xab, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + }, + { + {0xc4, 0x82, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x9e, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xac, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x83, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0x9f, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0xad, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + }, + { + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x8a, 0x0}, + {0x0, 0x0, 0x0}, {0xc4, 0x96, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0xa0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0xb0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0xbb, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc4, 0x8b, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0x97, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0xa1, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xbc, 0x0}, + }, + { + {0xc3, 0x84, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0x8b, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0x8f, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0x96, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0x9c, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xb8, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xa4, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xab, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xaf, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc3, 0xb6, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xbc, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xbf, 0x0}, {0x0, 0x0, 0x0}, + }, + { + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + }, + { + {0xc3, 0x85, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xae, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0xa5, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0xaf, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + }, + { + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc3, 0x87, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0xa2, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc4, 0xb6, 0x0}, {0xc4, 0xbb, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0x85, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0x96, 0x0}, + {0xc5, 0x9e, 0x0}, {0xc5, 0xa2, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc3, 0xa7, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0xb7, 0x0}, {0xc4, 0xbc, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0x86, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0x97, 0x0}, {0xc5, 0x9f, 0x0}, + {0xc5, 0xa3, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, + }, + { + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + }, + { + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0x90, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xb0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0x91, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0xb1, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + }, + { + {0xc4, 0x84, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0x98, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc4, 0xae, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xb2, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x85, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x99, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0xaf, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0xb3, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + }, + { + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0x8c, 0x0}, + {0xc4, 0x8e, 0x0}, {0xc4, 0x9a, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc4, 0xbd, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0x87, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0xc5, 0x98, 0x0}, + {0xc5, 0xa0, 0x0}, {0xc5, 0xa4, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0xbd, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc4, 0x8d, 0x0}, {0xc4, 0x8f, 0x0}, + {0xc4, 0x9b, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc4, 0xbe, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0x88, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0xc5, 0x99, 0x0}, {0xc5, 0xa1, 0x0}, + {0xc5, 0xa5, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, {0x0, 0x0, 0x0}, + {0xc5, 0xbe, 0x0}, + }}; + +} // namespace mp2t +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_ES_PARSER_TELETEXT_TABLES_H_ diff --git a/packager/media/formats/mp2t/es_parser_teletext_unittest.cc b/packager/media/formats/mp2t/es_parser_teletext_unittest.cc new file mode 100644 index 00000000000..64f52b97a84 --- /dev/null +++ b/packager/media/formats/mp2t/es_parser_teletext_unittest.cc @@ -0,0 +1,427 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include +#include +#include + +#include "packager/media/base/text_sample.h" +#include "packager/media/base/text_stream_info.h" +#include "packager/media/formats/mp2t/es_parser_teletext.h" + +namespace shaka { +namespace media { +namespace mp2t { + +namespace { + +const uint8_t DESCRIPTOR[] = {0x56, 0x0a, 0x63, 0x61, 0x74, 0x09, + 0x00, 0x63, 0x61, 0x74, 0x10, 0x88}; + +const uint8_t PES_283413[] = { + 0x10, 0x02, 0x2c, 0xe7, 0xe4, 0x92, 0x85, 0x80, 0xfe, 0x6b, 0x97, 0xce, + 0x97, 0x2f, 0xa7, 0xce, 0x40, 0xfe, 0x0b, 0xfb, 0x46, 0x37, 0x97, 0xc7, + 0xc1, 0x04, 0xfe, 0x32, 0x86, 0x04, 0x43, 0xf7, 0x2f, 0x97, 0xe6, 0x86, + 0x61, 0xfe, 0x05, 0x76, 0x26, 0xa7, 0x1f, 0x04, 0x2a, 0x6b, 0xc2, 0x03, + 0x2c, 0xe8, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, 0x0b, 0xf4, + 0xd9, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, 0x04, 0x04, + 0x04, 0x8c, 0x8c, 0x8c, 0x04, 0x8c, 0x6d, 0x5d, 0x0d, 0x03, 0x2c, 0xe9, + 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, 0x0b, 0xf4, 0xd9, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, 0x04, 0x04, 0x04, 0x8c, + 0x8c, 0x8c, 0x04, 0x8c, 0x6d, 0x5d, 0x0d, 0x03, 0x2c, 0xea, 0xe4, 0xa8, + 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, 0x0b, 0xf4, 0xd9, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, 0x04, 0x04, 0x04, 0x8c, 0x8c, 0x8c, + 0x04, 0x8c, 0x6d, 0x5d, 0x0d, 0x03, 0x2c, 0xc7, 0xe4, 0xa8, 0x6d, 0xa8, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0xb7, 0xc0, 0x00, 0x03, 0x2c, 0xc8, 0xe4, 0x0b, 0x6d, 0xa8, 0x57, 0x57, + 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, + 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, + 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, 0xa8, 0xec, + 0x82, 0x03, 0x2c, 0xc9, 0xe4, 0xa8, 0xd9, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0xb0, 0xd0, 0xd0, + 0x43, 0xf7, 0x76, 0x04, 0x26, 0x97, 0x86, 0x85, 0x51, 0x51, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + +const uint8_t PES_407876[] = { + 0x10, 0x02, 0x2c, 0xe7, 0xe4, 0x7a, 0x85, 0x80, 0xfe, 0x0b, 0x7f, 0xe6, + 0x75, 0xd5, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x61, 0xfe, 0x0b, 0x7f, 0xe6, 0x75, 0xb5, 0x03, + 0x2c, 0xe8, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, 0x0b, 0xf4, + 0xd9, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, 0x04, 0x04, + 0x04, 0x04, 0x8c, 0x6d, 0x5d, 0x0d, 0xcd, 0x5d, 0x4c, 0x03, 0x2c, 0xe9, + 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, 0x0b, 0xf4, 0xd9, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, 0x04, 0x04, 0x04, 0x04, + 0x8c, 0x6d, 0x5d, 0x0d, 0xcd, 0x5d, 0x4c, 0x03, 0x2c, 0xea, 0xe4, 0xa8, + 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, 0x0b, 0xf4, 0xd9, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x8c, 0x6d, + 0x5d, 0x0d, 0xcd, 0x5d, 0x4c, 0x03, 0x2c, 0xc7, 0xe4, 0xa8, 0x6d, 0xa8, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0xf9, 0x2d, 0x00, 0x03, 0x2c, 0xc8, 0xe4, 0x0b, 0x6d, 0xa8, 0x57, 0x57, + 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, + 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, + 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, 0xa8, 0x00, + 0x00, 0x02, 0x2c, 0xc9, 0xe4, 0x40, 0xa8, 0xa8, 0xa8, 0x40, 0x0b, 0xa8, + 0xa8, 0xa8, 0x31, 0x2a, 0x6b, 0xcd, 0x2a, 0xa2, 0x1a, 0x2a, 0xc1, 0x2a, + 0x6b, 0xcd, 0xe0, 0x8c, 0x0d, 0x0d, 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, + 0xc1, 0x04, 0x04, 0x04, 0x8c, 0x6d, 0x5d, 0x0d, 0xcd, 0x5d, 0x4c}; + +const uint8_t PES_8768632[] = { + 0x10, 0x02, 0x2c, 0xe7, 0xe4, 0x92, 0x0b, 0xc8, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x02, + 0x2c, 0xe8, 0xe4, 0x92, 0xe3, 0x40, 0x73, 0xf7, 0x2f, 0xba, 0xc7, 0x97, + 0xa7, 0xce, 0x04, 0xa7, 0xce, 0x0e, 0xf7, 0x4f, 0x2f, 0x97, 0x6e, 0xa7, + 0xce, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x8c, 0xad, 0x0d, 0x02, 0x2c, 0xe9, + 0xe4, 0x31, 0x31, 0xc8, 0xb9, 0x01, 0xb0, 0x0b, 0x4f, 0xf7, 0xe6, 0x4f, + 0x86, 0xb6, 0x86, 0xc7, 0x97, 0x7a, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x75, 0x75, 0xcd, 0x0d, 0x0d, 0x02, 0x2c, 0xea, 0xe4, 0x31, + 0xd9, 0xe0, 0xb9, 0x01, 0x04, 0x92, 0x76, 0x67, 0xf7, 0x4f, 0xb6, 0x86, + 0xc7, 0x97, 0x7a, 0x04, 0x2a, 0x6b, 0xc2, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x4c, 0x1c, 0x0d, 0x02, 0x2c, 0xc7, 0xe4, 0x92, 0x85, 0x80, + 0xfe, 0x62, 0x75, 0xcb, 0x86, 0x37, 0x86, 0x04, 0xb6, 0x86, 0xce, 0x75, + 0x04, 0x40, 0xfe, 0x43, 0x7f, 0xce, 0x8f, 0xae, 0xa7, 0x2f, 0xc1, 0xfe, + 0x62, 0xae, 0x2f, 0x46, 0xf7, 0x37, 0x61, 0xfe, 0xa2, 0xce, 0x0e, 0xf7, + 0x4f, 0x2f, 0xce, 0x03, 0x2c, 0xc8, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, + 0x0b, 0xa8, 0x0b, 0xf4, 0xd9, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x23, 0xc7, 0x75, + 0x8c, 0x1c, 0x04, 0x04, 0x04, 0x97, 0x2f, 0xae, 0x37, 0x86, 0x4f, 0xce, + 0x75, 0x03, 0x2c, 0xc9, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xd9, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, + 0x04, 0x04, 0x04, 0x97, 0x2f, 0xae, 0x37, 0x86, 0x4f, 0xce, 0x75}; + +const uint8_t PES_8773087[] = { + 0x10, 0x03, 0x2c, 0xe7, 0xe4, 0xa8, 0x6d, 0xa8, 0x2e, 0xfe, 0xff, 0x2e, + 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, + 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, + 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0xdc, 0x08, 0x00, 0x03, + 0x2c, 0xe8, 0xe4, 0x0b, 0x6d, 0xa8, 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, + 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, + 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, + 0x57, 0x57, 0x57, 0xf4, 0x57, 0x7a, 0xa8, 0x25, 0xee, 0x03, 0x2c, 0xe9, + 0xe4, 0xa8, 0x31, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0xb0, 0xd0, 0xd0, 0xb5, 0xcb, + 0xba, 0xfd, 0x51, 0x51, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x2c, 0xea, 0xe4, 0xa8, + 0xd9, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0xb0, 0xd0, 0xd0, 0xb5, 0xcb, 0xba, 0x75, + 0x51, 0x51, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x02, 0x2c, 0xc7, 0xe4, 0x92, 0xa8, 0x40, + 0x1c, 0x40, 0x0b, 0xa8, 0xa8, 0x92, 0xd9, 0x2a, 0x6b, 0xcd, 0x2a, 0xa2, + 0x1a, 0x2a, 0xc1, 0x2a, 0x6b, 0xcd, 0xe0, 0x4c, 0x6d, 0x8c, 0x04, 0x23, + 0xc7, 0x75, 0x8c, 0x1c, 0xc1, 0x04, 0x04, 0x2f, 0xae, 0x37, 0x86, 0x4f, + 0xce, 0x75, 0x75, 0x02, 0x2c, 0xc8, 0xe4, 0x92, 0xa8, 0x40, 0x1c, 0x40, + 0x0b, 0xa8, 0xa8, 0x92, 0xd9, 0x2a, 0x6b, 0xcd, 0x2a, 0xa2, 0x1a, 0x2a, + 0xc1, 0x2a, 0x6b, 0xcd, 0xe0, 0x4c, 0x6d, 0x8c, 0x04, 0x23, 0xc7, 0x75, + 0x8c, 0x1c, 0xc1, 0x04, 0x04, 0x2f, 0xae, 0x37, 0x86, 0x4f, 0xce, 0x75, + 0x75, 0x02, 0x2c, 0xc9, 0xe4, 0x92, 0xa8, 0x40, 0x1c, 0x40, 0x0b, 0xa8, + 0xa8, 0x92, 0xd9, 0x2a, 0x6b, 0xcd, 0x2a, 0xa2, 0x1a, 0x2a, 0xc1, 0x2a, + 0x6b, 0xcd, 0xe0, 0x4c, 0x6d, 0x8c, 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, + 0xc1, 0x04, 0x04, 0x2f, 0xae, 0x37, 0x86, 0x4f, 0xce, 0x75, 0x75}; + +const uint8_t PES_8937764[] = { + 0x10, 0x02, 0x2c, 0xe7, 0xe4, 0x31, 0xe3, 0x04, 0x61, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x86, 0xc7, 0x86, 0xce, 0x86, + 0xce, 0x15, 0x86, 0x94, 0x2f, 0x6e, 0xcd, 0x75, 0xc7, 0x86, 0x2f, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x02, + 0x2c, 0xe8, 0xe4, 0x92, 0x31, 0x04, 0x80, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x15, 0x86, + 0x94, 0xbc, 0x86, 0x4f, 0x4f, 0xf7, 0x6e, 0x86, 0x04, 0x02, 0x2c, 0xe9, + 0xe4, 0x31, 0x31, 0x80, 0xb9, 0x01, 0xb0, 0xa2, 0x37, 0x04, 0x2f, 0xa7, + 0xb6, 0x0e, 0xce, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x75, 0x75, 0x8c, 0x6d, 0x0d, 0x02, 0x2c, 0xea, 0xe4, 0x31, + 0xd9, 0xe0, 0xb9, 0x01, 0x04, 0x05, 0x76, 0x26, 0xa7, 0x1f, 0x04, 0x4f, + 0x7f, 0x0e, 0x97, 0x26, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x4c, 0x1c, 0x4c, 0x02, 0x2c, 0xc7, 0xe4, 0x92, 0x85, 0x80, + 0xfe, 0x05, 0x76, 0x26, 0xa7, 0x1f, 0x40, 0xfe, 0x0b, 0x4f, 0xf7, 0xe6, + 0x4f, 0x86, 0xb6, 0x86, 0xc7, 0x97, 0x7a, 0xc1, 0xfe, 0xa2, 0xce, 0x0e, + 0xf7, 0x4f, 0x2f, 0xce, 0x61, 0xfe, 0x32, 0xf7, 0x2f, 0xa7, 0x4f, 0x97, + 0xa7, 0xce, 0x04, 0x03, 0x2c, 0xc8, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, + 0x0b, 0xa8, 0x0b, 0xf4, 0xd9, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x23, 0xc7, 0x75, + 0x8c, 0x1c, 0x04, 0x04, 0x04, 0x86, 0x4f, 0xce, 0x75, 0x75, 0x75, 0x8c, + 0x8c, 0x03, 0x2c, 0xc9, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xd9, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, + 0x04, 0x04, 0x04, 0x86, 0x4f, 0xce, 0x75, 0x75, 0x75, 0x8c, 0x8c}; + +// Start (packet0, page88) with packet26 and row 18 left (packet18) +const uint8_t PES_867681[] = { + 0x10, 0x03, 0x2c, 0xf6, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xa8, 0xb3, 0x83, 0x32, 0xa2, 0x73, 0x2a, 0xa2, 0x73, 0x23, + 0x83, 0x73, 0x2a, 0xcb, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, + 0x2c, 0xd4, 0xe4, 0xa8, 0x6d, 0xa8, 0x9e, 0xc9, 0x00, 0x4e, 0x93, 0xa7, + 0x90, 0x53, 0xa7, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x03, 0x2c, 0xd6, + 0xe4, 0xa8, 0xe3, 0xb0, 0x04, 0x04, 0x04, 0x04, 0xd0, 0xd0, 0xb5, 0x32, + 0xae, 0xd6, 0xa7, 0x04, 0x85, 0x51, 0x51, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + +// row 22 right (packet22) +const uint8_t PES_871281[] = { + 0x10, 0x03, 0x2c, 0xf4, 0xe4, 0xa8, 0xd9, 0xb0, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0xd0, 0xd0, 0xb5, 0x52, 0xa7, 0x04, 0x6e, 0x86, 0x97, 0xce, + 0x04, 0x86, 0xae, 0x1f, 0x04, 0xc7, 0xf7, 0xae, 0x4f, 0xce, 0x04, 0x26, + 0xe5, 0xa7, 0x2f, 0xa7, 0x75, 0x51, 0x51, 0x04, 0x04, 0x04, 0x04}; + +// End (packet 0, page 88) +const uint8_t PES_1011695[] = { + 0x10, 0x03, 0x2c, 0xf6, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xa8, 0xb3, 0x83, 0x32, 0xa2, 0x73, 0x2a, 0xa2, 0x73, 0x23, + 0x83, 0x73, 0x2a, 0xcb, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + +// Start (packet0, page 88) with packet26 and row 20 (centered yellow) +// (packet20) +const uint8_t PES_1033297[] = { + 0x10, 0x03, 0x2c, 0xf6, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xa8, 0xb3, 0x83, 0x32, 0xa2, 0x73, 0x2a, 0xa2, 0x73, 0x23, + 0x83, 0x73, 0x2a, 0xcb, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, + 0x2c, 0xd4, 0xe4, 0xa8, 0x6d, 0xa8, 0x06, 0xc9, 0x01, 0x62, 0x93, 0xa6, + 0x9e, 0xc9, 0x00, 0xf4, 0xa2, 0x86, 0x06, 0xa3, 0xa7, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x03, 0x2c, 0xd6, + 0xe4, 0xa8, 0x31, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0xb0, 0xc1, 0xd0, 0xd0, 0x52, 0xe5, 0x86, 0x97, 0x04, + 0x37, 0xf7, 0xae, 0x0e, 0xa7, 0x51, 0x51, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + +// row 22 centered blue on yellow background (packet22) +const uint8_t PES_1036900[] = { + 0x10, 0x03, 0x2c, 0xf4, 0xe4, 0xa8, 0xd9, 0x04, 0x04, 0xb0, 0xc1, 0xb9, + 0x20, 0xd0, 0xd0, 0x37, 0xe5, 0x97, 0x76, 0x97, 0x2f, 0x97, 0x86, 0x2f, + 0x97, 0xf7, 0x76, 0x04, 0x86, 0x04, 0x37, 0xe5, 0x86, 0x37, 0xe6, 0xa7, + 0x46, 0x4f, 0xa7, 0x75, 0x51, 0x51, 0x04, 0x04, 0x04, 0x04, 0x04}; + +// End (packet 0, page 88) +const uint8_t PES_1173713[] = { + 0x10, 0x03, 0x2c, 0xf6, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xa8, 0xb3, 0x83, 0x32, 0xa2, 0x73, 0x2a, 0xa2, 0x73, 0x23, + 0x83, 0x73, 0x2a, 0xcb, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + +const uint32_t kPesPid = 123; + +} // namespace + +class EsParserTeletextTest : public ::testing::Test { + public: + void OnNewStreamInfo(uint32_t pes_pid, + std::shared_ptr stream_info) { + stream_info_ = stream_info; + } + void OnEmitTextSample(uint32_t pes_pid, + std::shared_ptr text_sample) { + text_sample_ = text_sample; + text_samples_.push_back(text_sample); + } + + protected: + std::shared_ptr stream_info_; + std::vector> text_samples_; + std::shared_ptr text_sample_; +}; + +TEST_F(EsParserTeletextTest, descriptor_substreams_has_index_888_language_cat) { + auto on_new_stream = std::bind(&EsParserTeletextTest::OnNewStreamInfo, this, + kPesPid, std::placeholders::_1); + auto on_emit_text = std::bind(&EsParserTeletextTest::OnEmitTextSample, this, + kPesPid, std::placeholders::_1); + + std::unique_ptr es_parser_teletext(new EsParserTeletext( + kPesPid, on_new_stream, on_emit_text, DESCRIPTOR, 12)); + + const auto parse_result = + es_parser_teletext->Parse(PES_283413, sizeof(PES_283413), 283413, 0); + EXPECT_TRUE(parse_result); + + EXPECT_NE(nullptr, stream_info_.get()); + auto text_stream_info = static_cast(stream_info_.get()); + auto& sub_streams = text_stream_info->sub_streams(); + + EXPECT_EQ(2, sub_streams.size()); + auto sub_streams_itr = sub_streams.find(888); + EXPECT_NE(sub_streams.end(), sub_streams_itr); + EXPECT_EQ("cat", sub_streams_itr->second.language); +} + +TEST_F(EsParserTeletextTest, pes_283413_line_emitted_on_next_pes) { + auto on_new_stream = std::bind(&EsParserTeletextTest::OnNewStreamInfo, this, + kPesPid, std::placeholders::_1); + auto on_emit_text = std::bind(&EsParserTeletextTest::OnEmitTextSample, this, + kPesPid, std::placeholders::_1); + + std::unique_ptr es_parser_teletext(new EsParserTeletext( + kPesPid, on_new_stream, on_emit_text, DESCRIPTOR, 12)); + + auto parse_result = + es_parser_teletext->Parse(PES_283413, sizeof(PES_283413), 283413, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_407876, sizeof(PES_407876), 407876, 0); + EXPECT_TRUE(parse_result); + + EXPECT_NE(nullptr, text_sample_.get()); + EXPECT_EQ(283413, text_sample_->start_time()); + EXPECT_EQ(407876, text_sample_->EndTime()); + EXPECT_EQ("Bon dia!", text_sample_->body().body); + EXPECT_EQ("black", text_sample_->body().style.backgroundColor); + EXPECT_EQ("white", text_sample_->body().style.color); + TextSettings settings = text_sample_->settings(); + EXPECT_EQ(TextAlignment::kCenter, settings.text_alignment); + EXPECT_TRUE(settings.line.has_value()); + EXPECT_EQ(11, settings.line.value().value); + EXPECT_EQ(TextUnitType::kLines, settings.line.value().type); +} + +TEST_F(EsParserTeletextTest, multiple_lines_with_same_pts) { + auto on_new_stream = std::bind(&EsParserTeletextTest::OnNewStreamInfo, this, + kPesPid, std::placeholders::_1); + auto on_emit_text = std::bind(&EsParserTeletextTest::OnEmitTextSample, this, + kPesPid, std::placeholders::_1); + + std::unique_ptr es_parser_teletext(new EsParserTeletext( + kPesPid, on_new_stream, on_emit_text, DESCRIPTOR, 12)); + + auto parse_result = + es_parser_teletext->Parse(PES_8768632, sizeof(PES_8768632), 8768632, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_8773087, sizeof(PES_8773087), 8773087, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_8937764, sizeof(PES_8937764), 8937764, 0); + EXPECT_TRUE(parse_result); + + EXPECT_NE(nullptr, text_sample_.get()); + EXPECT_EQ(8768632, text_sample_->start_time()); + EXPECT_EQ(8937764, text_sample_->EndTime()); + EXPECT_EQ(3, text_sample_->body().sub_fragments.size()); + EXPECT_EQ("-Sí?", text_sample_->body().sub_fragments[0].body); + EXPECT_TRUE(text_sample_->body().sub_fragments[1].newline); + EXPECT_EQ("-Sí.", text_sample_->body().sub_fragments[2].body); + TextSettings settings = text_sample_->settings(); + EXPECT_EQ(10, settings.line.value().value); + EXPECT_EQ("ttx_10", settings.region); + EXPECT_EQ(1, text_samples_.size()); +} + +// separate_lines_with_slightly_different_pts has the original lines +// 18 and 22, with different alignment, which means that they should +// result in two parallel text samples. +TEST_F(EsParserTeletextTest, separate_lines_with_slightly_different_pts) { + auto on_new_stream = std::bind(&EsParserTeletextTest::OnNewStreamInfo, this, + kPesPid, std::placeholders::_1); + auto on_emit_text = std::bind(&EsParserTeletextTest::OnEmitTextSample, this, + kPesPid, std::placeholders::_1); + + std::unique_ptr es_parser_teletext(new EsParserTeletext( + kPesPid, on_new_stream, on_emit_text, DESCRIPTOR, 12)); + + auto parse_result = + es_parser_teletext->Parse(PES_867681, sizeof(PES_867681), 867681, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_871281, sizeof(PES_871281), 871281, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_1011695, sizeof(PES_1011695), 1011695, 0); + EXPECT_TRUE(parse_result); + + EXPECT_NE(nullptr, text_sample_.get()); + EXPECT_EQ(2, text_samples_.size()); + // The subtitles should get the same start and end time + EXPECT_EQ(867681, text_samples_[0]->start_time()); + EXPECT_EQ(867681, text_samples_[1]->start_time()); + EXPECT_EQ(1011695, text_samples_[0]->EndTime()); + EXPECT_EQ(1011695, text_samples_[0]->EndTime()); + EXPECT_EQ(1, text_samples_[0]->body().sub_fragments.size()); + EXPECT_EQ(1, text_samples_[1]->body().sub_fragments.size()); + EXPECT_EQ("-Luke !", text_samples_[0]->body().sub_fragments[0].body); + EXPECT_EQ("ttx_9", text_samples_[0]->settings().region); + EXPECT_EQ(TextAlignment::kLeft, text_samples_[0]->settings().text_alignment); + EXPECT_EQ("-Je vais aux cours d'été.", + text_samples_[1]->body().sub_fragments[0].body); + EXPECT_EQ(11, text_samples_[1]->settings().line.value().value); + EXPECT_EQ("ttx_11", text_samples_[1]->settings().region); + EXPECT_EQ(TextAlignment::kCenter, + text_samples_[1]->settings().text_alignment); +} + +// consecutive_lines_with_slightly_different_pts has the original lines +// 20 and 22 with same alignment, which means that they should +// result in one text sample with two lines. +TEST_F(EsParserTeletextTest, consecutive_lines_with_slightly_different_pts) { + auto on_new_stream = std::bind(&EsParserTeletextTest::OnNewStreamInfo, this, + kPesPid, std::placeholders::_1); + auto on_emit_text = std::bind(&EsParserTeletextTest::OnEmitTextSample, this, + kPesPid, std::placeholders::_1); + + std::unique_ptr es_parser_teletext(new EsParserTeletext( + kPesPid, on_new_stream, on_emit_text, DESCRIPTOR, 12)); + + auto parse_result = + es_parser_teletext->Parse(PES_1033297, sizeof(PES_1033297), 1033297, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_1036900, sizeof(PES_1036900), 1036900, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_1173713, sizeof(PES_1173713), 1173713, 0); + EXPECT_TRUE(parse_result); + + EXPECT_NE(nullptr, text_sample_.get()); + EXPECT_EQ(1, text_samples_.size()); + // The subtitles should get the same start and end time + EXPECT_EQ(1033297, text_sample_->start_time()); + EXPECT_EQ(1173713, text_sample_->EndTime()); + EXPECT_EQ(3, text_sample_->body().sub_fragments.size()); + TextSettings settings = text_sample_->settings(); + EXPECT_EQ(10, settings.line.value().value); + EXPECT_EQ("ttx_10", settings.region); + EXPECT_EQ(TextAlignment::kCenter, settings.text_alignment); + EXPECT_EQ("J'ai loupé", text_sample_->body().sub_fragments[0].body); + EXPECT_EQ("yellow", text_sample_->body().sub_fragments[0].style.color); + EXPECT_TRUE(text_sample_->body().sub_fragments[1].newline); + EXPECT_EQ("l'initiation à l'algèbre.", + text_sample_->body().sub_fragments[2].body); + EXPECT_EQ("yellow", + text_sample_->body().sub_fragments[2].style.backgroundColor); + EXPECT_EQ("blue", text_sample_->body().sub_fragments[2].style.color); +} + +} // namespace mp2t +} // namespace media +} // namespace shaka diff --git a/packager/media/formats/mp2t/mp2t_media_parser.cc b/packager/media/formats/mp2t/mp2t_media_parser.cc index a83c95ad27d..0683d954a32 100644 --- a/packager/media/formats/mp2t/mp2t_media_parser.cc +++ b/packager/media/formats/mp2t/mp2t_media_parser.cc @@ -18,7 +18,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -274,7 +276,8 @@ void Mp2tMediaParser::RegisterPmt(int program_number, int pmt_pid) { DVLOG(1) << "Create a new PMT parser"; std::unique_ptr pmt_section_parser(new TsSectionPmt(std::bind( &Mp2tMediaParser::RegisterPes, this, pmt_pid, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4))); + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5, std::placeholders::_6, std::placeholders::_7))); std::unique_ptr pmt_pid_state( new PidState(pmt_pid, PidState::kPidPmt, std::move(pmt_section_parser))); pmt_pid_state->Enable(); @@ -284,13 +287,19 @@ void Mp2tMediaParser::RegisterPmt(int program_number, int pmt_pid) { void Mp2tMediaParser::RegisterPes(int pmt_pid, int pes_pid, TsStreamType stream_type, + uint32_t max_bitrate, + const std::string& lang, + TsAudioType audio_type, const uint8_t* descriptor, size_t descriptor_length) { if (pids_.count(pes_pid) != 0) return; DVLOG(1) << "RegisterPes:" << " pes_pid=" << pes_pid << " stream_type=" << std::hex - << static_cast(stream_type) << std::dec; + << static_cast(stream_type) << std::dec + << "max_bitrate=" << max_bitrate << " lang=" << lang + << "audio_type=" << std::hex << static_cast(audio_type) + << std::dec; // Create a stream parser corresponding to the stream type. PidState::PidType pid_type = PidState::kPidVideoPes; @@ -321,6 +330,12 @@ void Mp2tMediaParser::RegisterPes(int pmt_pid, descriptor, descriptor_length)); pid_type = PidState::kPidTextPes; break; + case TsStreamType::kTeletextSubtitles: + es_parser.reset(new EsParserTeletext(pes_pid, on_new_stream, on_emit_text, + descriptor, descriptor_length)); + pid_type = PidState::kPidTextPes; + break; + default: { auto type = static_cast(stream_type); DCHECK(type <= 0xff); @@ -340,6 +355,10 @@ void Mp2tMediaParser::RegisterPes(int pmt_pid, new PidState(pes_pid, pid_type, std::move(pes_section_parser))); pes_pid_state->Enable(); pids_.emplace(pes_pid, std::move(pes_pid_state)); + + // Store PES metadata. + pes_metadata_.insert( + std::make_pair(pes_pid, PesMetadata{max_bitrate, lang, audio_type})); } void Mp2tMediaParser::OnNewStreamInfo( @@ -358,6 +377,17 @@ void Mp2tMediaParser::OnNewStreamInfo( if (new_stream_info) { // Set the stream configuration information for the PID. + auto pes_metadata = pes_metadata_.find(pes_pid); + DCHECK(pes_metadata != pes_metadata_.end()); + if (!pes_metadata->second.language.empty()) + new_stream_info->set_language(pes_metadata->second.language); + if (new_stream_info->stream_type() == kStreamAudio) { + auto* audio_info = static_cast(new_stream_info.get()); + audio_info->set_max_bitrate(pes_metadata->second.max_bitrate); + // TODO(modernletter) Add some field for audio type to AudioStreamInfo + // and set here from audio_type + } + pid_state->second->set_config(new_stream_info); } else { LOG(WARNING) << "Ignoring unsupported stream with pid=" << pes_pid; diff --git a/packager/media/formats/mp2t/mp2t_media_parser.h b/packager/media/formats/mp2t/mp2t_media_parser.h index 5ddcc74ac50..1cbceee4d50 100644 --- a/packager/media/formats/mp2t/mp2t_media_parser.h +++ b/packager/media/formats/mp2t/mp2t_media_parser.h @@ -9,11 +9,13 @@ #include #include #include +#include #include #include #include #include +#include #include namespace shaka { @@ -27,6 +29,12 @@ class PidState; class TsPacket; class TsSection; +struct PesMetadata { + uint32_t max_bitrate; + std::string language; + TsAudioType audio_type; +}; + class Mp2tMediaParser : public MediaParser { public: Mp2tMediaParser(); @@ -50,10 +58,15 @@ class Mp2tMediaParser : public MediaParser { // Callback invoked to register a PES pid. // Possible values for |media_type| are defined in: // ISO-13818.1 / ITU H.222 Table 2.34 "Media type assignments". + // Possible values for |audio_type| are defined in: + // ISO-13818.1 / ITU H.222 Table 2-60 "Audio type values". // |pes_pid| is part of the Program Map Table refered by |pmt_pid|. void RegisterPes(int pmt_pid, int pes_pid, TsStreamType media_type, + uint32_t max_bitrate, + const std::string& lang, + TsAudioType audio_type, const uint8_t* descriptor, size_t descriptor_length); @@ -94,6 +107,9 @@ class Mp2tMediaParser : public MediaParser { // has a deterministic order. std::map> pids_; + // Map of PIDs and their metadata. + std::map pes_metadata_; + // Whether |init_cb_| has been invoked. bool is_initialized_; diff --git a/packager/media/formats/mp2t/mp2t_media_parser_unittest.cc b/packager/media/formats/mp2t/mp2t_media_parser_unittest.cc index aeb44b248bb..60808daa3b1 100644 --- a/packager/media/formats/mp2t/mp2t_media_parser_unittest.cc +++ b/packager/media/formats/mp2t/mp2t_media_parser_unittest.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -190,6 +191,19 @@ TEST_F(Mp2tMediaParserTest, PtsZeroDtsWrapAround) { EXPECT_GT(video_max_pts_, static_cast(1) << 33); } +TEST_F(Mp2tMediaParserTest, PmtEsDescriptors) { + //"bear-eng-visualy-impaired-audio.ts" consist of audio stream marked as + // english audio with commentary for visualy impaired viewer and max + // bitrate set to ~128kbps + + ParseMpeg2TsFile("bear-visualy-impaired-eng-audio.ts", 188); + EXPECT_TRUE(parser_->Flush()); + EXPECT_STREQ("eng", stream_map_[257]->language().c_str()); + + auto* audio_info = static_cast(stream_map_[257].get()); + EXPECT_EQ(131600, audio_info->max_bitrate()); +} + } // namespace mp2t } // namespace media } // namespace shaka diff --git a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc index c47e8aa53f8..c3cb0021e08 100644 --- a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc +++ b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc @@ -84,6 +84,8 @@ const uint32_t kWidth = 1280; const uint32_t kHeight = 720; const uint32_t kPixelWidth = 1; const uint32_t kPixelHeight = 1; +const uint8_t kColorPrimaries = 0; +const uint8_t kMatrixCoefficients = 0; const uint8_t kTransferCharacteristics = 0; const uint16_t kTrickPlayFactor = 1; const uint8_t kNaluLengthSize = 1; @@ -125,8 +127,8 @@ std::shared_ptr CreateVideoStreamInfo(Codec codec) { kTrackId, kTimeScale, kDuration, codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kVideoExtraData, std::size(kVideoExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); return stream_info; } @@ -358,8 +360,8 @@ TEST_F(PesPacketGeneratorTest, TimeStampScaling) { kTrackId, kTestTimescale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kVideoExtraData, std::size(kVideoExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(generator_.Initialize(*stream_info)); EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); diff --git a/packager/media/formats/mp2t/ts_audio_type.h b/packager/media/formats/mp2t/ts_audio_type.h new file mode 100644 index 00000000000..a8347dcad05 --- /dev/null +++ b/packager/media/formats/mp2t/ts_audio_type.h @@ -0,0 +1,30 @@ +// Copyright 2023 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef PACKAGER_MEDIA_FORMATS_MP2T_TS_AUDIO_TYPE_H +#define PACKAGER_MEDIA_FORMATS_MP2T_TS_AUDIO_TYPE_H + +#include + +namespace shaka { +namespace media { +namespace mp2t { + +enum class TsAudioType : uint8_t { + // ISO-13818.1 / ITU H.222 Table 2-60 "Audio type values" + kUndefined = 0x00, + kCleanEffects = 0x01, + kHearingImpaired = 0x02, + kVisualyImpairedCommentary = 0x03, + // 0x04-0x7F - user private + // 0x80-0xFF - reserved +}; + +} // namespace mp2t +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_TS_AUDIO_TYPE_H diff --git a/packager/media/formats/mp2t/ts_muxer.cc b/packager/media/formats/mp2t/ts_muxer.cc index f9de46b6346..636a1200f7d 100644 --- a/packager/media/formats/mp2t/ts_muxer.cc +++ b/packager/media/formats/mp2t/ts_muxer.cc @@ -8,6 +8,9 @@ #include +#include +#include + namespace shaka { namespace media { namespace mp2t { @@ -23,6 +26,16 @@ Status TsMuxer::InitializeMuxer() { if (streams().size() > 1u) return Status(error::MUXER_FAILURE, "Cannot handle more than one streams."); + if (options().segment_template.empty()) { + const std::string& file_name = options().output_file_name; + DCHECK(!file_name.empty()); + output_file_.reset(File::Open(file_name.c_str(), "w")); + if (!output_file_) { + return Status(error::FILE_FAILURE, + "Cannot open file for write " + file_name); + } + } + segmenter_.reset(new TsSegmenter(options(), muxer_listener())); Status status = segmenter_->Initialize(*streams()[0]); FireOnMediaStartEvent(); @@ -36,6 +49,9 @@ Status TsMuxer::Finalize() { Status TsMuxer::AddMediaSample(size_t stream_id, const MediaSample& sample) { DCHECK_EQ(stream_id, 0u); + + // The duration of the first sample may have been adjusted, so use + // the duration of the second sample instead. if (num_samples_ < 2) { sample_durations_[num_samples_] = sample.duration() * kTsTimescale / streams().front()->time_scale(); @@ -49,10 +65,82 @@ Status TsMuxer::AddMediaSample(size_t stream_id, const MediaSample& sample) { Status TsMuxer::FinalizeSegment(size_t stream_id, const SegmentInfo& segment_info) { DCHECK_EQ(stream_id, 0u); - return segment_info.is_subsegment - ? Status::OK - : segmenter_->FinalizeSegment(segment_info.start_timestamp, - segment_info.duration); + + if (segment_info.is_subsegment) + return Status::OK; + + Status s = segmenter_->FinalizeSegment(segment_info.start_timestamp, + segment_info.duration); + if (!s.ok()) + return s; + if (!segmenter_->segment_started()) + return Status::OK; + + int64_t segment_start_timestamp = segmenter_->segment_start_timestamp(); + + std::string segment_path = + options().segment_template.empty() + ? options().output_file_name + : GetSegmentName(options().segment_template, segment_start_timestamp, + segment_info.segment_number, options().bandwidth); + + const int64_t file_size = segmenter_->segment_buffer()->Size(); + + RETURN_IF_ERROR(WriteSegment(segment_path, segmenter_->segment_buffer())); + + total_duration_ += segment_info.duration; + + if (muxer_listener()) { + muxer_listener()->OnNewSegment( + segment_path, + segment_info.start_timestamp * segmenter_->timescale() + + segmenter_->transport_stream_timestamp_offset(), + segment_info.duration * segmenter_->timescale(), file_size, + segment_info.segment_number); + } + + segmenter_->set_segment_started(false); + + return Status::OK; +} + +Status TsMuxer::WriteSegment(const std::string& segment_path, + BufferWriter* segment_buffer) { + std::unique_ptr file; + + if (output_file_) { + // This is in single segment mode. + Range range; + range.start = media_ranges_.subsegment_ranges.empty() + ? 0 + : (media_ranges_.subsegment_ranges.back().end + 1); + range.end = range.start + segment_buffer->Size() - 1; + media_ranges_.subsegment_ranges.push_back(range); + } else { + file.reset(File::Open(segment_path.c_str(), "w")); + if (!file) { + return Status(error::FILE_FAILURE, + "Cannot open file for write " + segment_path); + } + } + + RETURN_IF_ERROR(segment_buffer->WriteToFile(output_file_ ? output_file_.get() + : file.get())); + + if (file) + RETURN_IF_ERROR(CloseFile(std::move(file))); + return Status::OK; +} + +Status TsMuxer::CloseFile(std::unique_ptr file) { + std::string file_name = file->file_name(); + if (!file.release()->Close()) { + return Status( + error::FILE_FAILURE, + "Cannot close file " + file_name + + ", possibly file permission issue or running out of disk space."); + } + return Status::OK; } void TsMuxer::FireOnMediaStartEvent() { @@ -66,10 +154,7 @@ void TsMuxer::FireOnMediaEndEvent() { if (!muxer_listener()) return; - // For now, there is no single file TS segmenter. So all the values passed - // here are left empty. - MuxerListener::MediaRanges range; - muxer_listener()->OnMediaEnd(range, 0); + muxer_listener()->OnMediaEnd(media_ranges_, total_duration_); } } // namespace mp2t diff --git a/packager/media/formats/mp2t/ts_muxer.h b/packager/media/formats/mp2t/ts_muxer.h index 9f55c2f0e4c..3a04efdbce8 100644 --- a/packager/media/formats/mp2t/ts_muxer.h +++ b/packager/media/formats/mp2t/ts_muxer.h @@ -30,12 +30,24 @@ class TsMuxer : public Muxer { Status FinalizeSegment(size_t stream_id, const SegmentInfo& sample) override; + Status WriteSegment(const std::string& segment_path, + BufferWriter* segment_buffer); + Status CloseFile(std::unique_ptr file); + void FireOnMediaStartEvent(); void FireOnMediaEndEvent(); std::unique_ptr segmenter_; - int64_t sample_durations_[2]; - int64_t num_samples_ = 0; + int64_t sample_durations_[2] = {0, 0}; + size_t num_samples_ = 0; + + // Used in single segment mode. + std::unique_ptr output_file_; + + // Keeps track of segment ranges in single segment mode. + MuxerListener::MediaRanges media_ranges_; + + uint64_t total_duration_ = 0; DISALLOW_COPY_AND_ASSIGN(TsMuxer); }; diff --git a/packager/media/formats/mp2t/ts_section_pmt.cc b/packager/media/formats/mp2t/ts_section_pmt.cc index 07aef734138..6c3b8895c2c 100644 --- a/packager/media/formats/mp2t/ts_section_pmt.cc +++ b/packager/media/formats/mp2t/ts_section_pmt.cc @@ -10,12 +10,22 @@ #include #include +#include #include namespace shaka { namespace media { namespace mp2t { +namespace { + +const int kISO639LanguageDescriptor = 0x0A; +const int kMaximumBitrateDescriptor = 0x0E; +const int kTeletextDescriptor = 0x56; +const int kSubtitlingDescriptor = 0x59; + +} // namespace + TsSectionPmt::TsSectionPmt(const RegisterPesCb& register_pes_cb) : register_pes_cb_(register_pes_cb) { } @@ -82,6 +92,9 @@ bool TsSectionPmt::ParsePsiSection(BitReader* bit_reader) { TsStreamType stream_type; const uint8_t* descriptor; size_t descriptor_length; + std::string lang; + uint32_t max_bitrate; + TsAudioType audio_type; }; std::vector pid_info; while (static_cast(bit_reader->bits_available()) > @@ -99,22 +112,67 @@ bool TsSectionPmt::ParsePsiSection(BitReader* bit_reader) { // Do not register the PID right away. // Wait for the end of the section to be fully parsed // to make sure there is no error. - pid_info.push_back({pid_es, stream_type, descriptor, es_info_length}); + pid_info.push_back({pid_es, stream_type, descriptor, es_info_length, "", 0, + TsAudioType::kUndefined}); // Read the ES info descriptors. // Defined in section 2.6 of ISO-13818. - if (es_info_length > 0) { - uint8_t descriptor_tag; + uint8_t descriptor_tag; + uint8_t descriptor_length; + + while (es_info_length) { RCHECK(bit_reader->ReadBits(8, &descriptor_tag)); - es_info_length--; + RCHECK(bit_reader->ReadBits(8, &descriptor_length)); + es_info_length -= 2; // See ETSI EN 300 468 Section 6.1 - if (stream_type == TsStreamType::kPesPrivateData && - descriptor_tag == 0x59) { // subtitling_descriptor - pid_info.back().stream_type = TsStreamType::kDvbSubtitles; + if (stream_type == TsStreamType::kPesPrivateData) { + switch (descriptor_tag) { + case kTeletextDescriptor: + pid_info.back().stream_type = TsStreamType::kTeletextSubtitles; + break; + case kSubtitlingDescriptor: + pid_info.back().stream_type = TsStreamType::kDvbSubtitles; + break; + default: + break; + } + } else if (descriptor_tag == kISO639LanguageDescriptor && + descriptor_length >= 4) { + // See section 2.6.19 of ISO-13818 + // Descriptor can contain 0..N language defintions, + // we process only the first one + RCHECK(es_info_length >= 4); + + char lang[3]; + RCHECK(bit_reader->ReadBits(8, &lang[0])); // ISO_639_language_code + RCHECK(bit_reader->ReadBits(8, &lang[1])); + RCHECK(bit_reader->ReadBits(8, &lang[2])); + RCHECK(bit_reader->ReadBits(8, &pid_info.back().audio_type)); + pid_info.back().lang = std::string(lang, 3); + + es_info_length -= 4; + descriptor_length -= 4; + } else if (descriptor_tag == kMaximumBitrateDescriptor && + descriptor_length >= 3) { + // See section 2.6.25 of ISO-13818 + RCHECK(es_info_length >= 3); + + uint32_t max_bitrate; + RCHECK(bit_reader->SkipBits(2)); // reserved + RCHECK(bit_reader->ReadBits(22, &max_bitrate)); + // maximum bitrate is stored in units of 50 bytes per second + pid_info.back().max_bitrate = 50 * 8 * max_bitrate; + + es_info_length -= 3; + descriptor_length -= 3; } + + RCHECK(bit_reader->SkipBits(8 * descriptor_length)); + es_info_length -= descriptor_length; } - RCHECK(bit_reader->SkipBits(8 * es_info_length)); + + RCHECK(bit_reader->SkipBytes(es_info_length)); } // Read the CRC. @@ -123,8 +181,8 @@ bool TsSectionPmt::ParsePsiSection(BitReader* bit_reader) { // Once the PMT has been proved to be correct, register the PIDs. for (auto& info : pid_info) { - register_pes_cb_(info.pid_es, info.stream_type, info.descriptor, - info.descriptor_length); + register_pes_cb_(info.pid_es, info.stream_type, info.max_bitrate, info.lang, + info.audio_type, info.descriptor, info.descriptor_length); } return true; diff --git a/packager/media/formats/mp2t/ts_section_pmt.h b/packager/media/formats/mp2t/ts_section_pmt.h index 96075281bb0..fd3eddeebbf 100644 --- a/packager/media/formats/mp2t/ts_section_pmt.h +++ b/packager/media/formats/mp2t/ts_section_pmt.h @@ -6,8 +6,10 @@ #define PACKAGER_MEDIA_FORMATS_MP2T_TS_SECTION_PMT_H_ #include +#include #include +#include #include #include @@ -17,10 +19,20 @@ namespace mp2t { class TsSectionPmt : public TsSectionPsi { public: - // RegisterPesCb::Run(int pes_pid, int stream_type); + // RegisterPesCb::Run(int pes_pid, int stream_type, uint32_t max_bitrate, + // const string& lang, TsAudioType audio_type, uint8_t* descriptor, + // size_t desriptor_size); // Stream type is defined in // "Table 2-34 – Stream type assignments" in H.222 - typedef std::function + // Audio type is defined in + // "Table 2-60 - Audio type values" in H.222 + typedef std::function RegisterPesCb; explicit TsSectionPmt(const RegisterPesCb& register_pes_cb); diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index a2525bca3a8..e4baa851a83 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -48,8 +48,6 @@ TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener) TsSegmenter::~TsSegmenter() {} Status TsSegmenter::Initialize(const StreamInfo& stream_info) { - if (muxer_options_.segment_template.empty()) - return Status(error::MUXER_FAILURE, "Segment template not specified."); if (!pes_packet_generator_->Initialize(stream_info)) { return Status(error::MUXER_FAILURE, "Failed to initialize PesPacketGenerator."); @@ -193,39 +191,6 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) { } } - // This method may be called from Finalize() so segment_started_ could - // be false. - if (!segment_started_) - return Status::OK; - std::string segment_path = - GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_, - segment_number_++, muxer_options_.bandwidth); - - const int64_t file_size = segment_buffer_.Size(); - std::unique_ptr segment_file; - segment_file.reset(File::Open(segment_path.c_str(), "w")); - if (!segment_file) { - return Status(error::FILE_FAILURE, - "Cannot open file for write " + segment_path); - } - - RETURN_IF_ERROR(segment_buffer_.WriteToFile(segment_file.get())); - - if (!segment_file.release()->Close()) { - return Status( - error::FILE_FAILURE, - "Cannot close file " + segment_path + - ", possibly file permission issue or running out of disk space."); - } - - if (listener_) { - listener_->OnNewSegment(segment_path, - start_timestamp * timescale_scale_ + - transport_stream_timestamp_offset_, - duration * timescale_scale_, file_size); - } - segment_started_ = false; - return Status::OK; } diff --git a/packager/media/formats/mp2t/ts_segmenter.h b/packager/media/formats/mp2t/ts_segmenter.h index 7c765a60297..073cf1d3070 100644 --- a/packager/media/formats/mp2t/ts_segmenter.h +++ b/packager/media/formats/mp2t/ts_segmenter.h @@ -24,9 +24,6 @@ class MuxerListener; namespace mp2t { -// TODO(rkuroiwa): For now, this implements multifile segmenter. Like other -// make this an abstract super class and implement multifile and single file -// segmenters. class TsSegmenter { public: // TODO(rkuroiwa): Add progress listener? @@ -57,6 +54,7 @@ class TsSegmenter { /// stream's time scale. /// @param duration is the segment's duration in the input stream's time /// scale. + /// @param segment_number is the segment number. // TODO(kqyang): Remove the usage of segment start timestamp and duration in // xx_segmenter, which could cause confusions on which is the source of truth // as the segment start timestamp and duration could be tracked locally. @@ -72,6 +70,16 @@ class TsSegmenter { /// Only for testing. void SetSegmentStartedForTesting(bool value); + int64_t segment_start_timestamp() const { return segment_start_timestamp_; } + BufferWriter* segment_buffer() { return &segment_buffer_; } + void set_segment_started(bool value) { segment_started_ = value; } + bool segment_started() const { return segment_started_; } + + double timescale() const { return timescale_scale_; } + uint32_t transport_stream_timestamp_offset() const { + return transport_stream_timestamp_offset_; + } + private: Status StartSegmentIfNeeded(int64_t next_pts); @@ -87,18 +95,16 @@ class TsSegmenter { const int32_t transport_stream_timestamp_offset_ = 0; // Scale used to scale the input stream to TS's timesccale (which is 90000). + // Used for calculating the duration in seconds fo the current segment. double timescale_scale_ = 1.0; - // Used for segment template. - uint64_t segment_number_ = 0; - std::unique_ptr ts_writer_; BufferWriter segment_buffer_; // Set to true if segment_buffer_ is initialized, set to false after - // FinalizeSegment() succeeds. + // FinalizeSegment() succeeds in ts_muxer. bool segment_started_ = false; std::unique_ptr pes_packet_generator_; diff --git a/packager/media/formats/mp2t/ts_segmenter_unittest.cc b/packager/media/formats/mp2t/ts_segmenter_unittest.cc index 30fa07abf42..3f34856e226 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -45,6 +45,8 @@ const uint32_t kWidth = 1280; const uint32_t kHeight = 720; const uint32_t kPixelWidth = 1; const uint32_t kPixelHeight = 1; +const uint8_t kColorPrimaries = 0; +const uint8_t kMatrixCoefficients = 0; const uint8_t kTransferCharacteristics = 0; const uint16_t kTrickPlayFactor = 1; const uint8_t kNaluLengthSize = 1; @@ -84,6 +86,7 @@ class MockTsWriter : public TsWriter { MOCK_METHOD1(NewSegment, bool(BufferWriter* buffer_writer)); MOCK_METHOD0(SignalEncrypted, void()); + MOCK_METHOD0(FinalizeSegment, bool()); // Similar to the hack above but takes a std::unique_ptr. MOCK_METHOD2(AddPesPacketMock, bool(PesPacket* pes_packet, @@ -114,8 +117,8 @@ TEST_F(TsSegmenterTest, Initialize) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -134,8 +137,8 @@ TEST_F(TsSegmenterTest, AddSample) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -186,8 +189,8 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { kTrackId, kInputTimescale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "memory://file$Number$.ts"; @@ -207,11 +210,6 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { // Doesn't really matter how long this is. sample2->set_duration(kInputTimescale * 7); - EXPECT_CALL(mock_listener, - OnNewSegment("memory://file1.ts", - kFirstPts * kTimeScale / kInputTimescale, - kTimeScale * 11, _)); - Sequence writer_sequence; EXPECT_CALL(*mock_ts_writer_, NewSegment(_)) .InSequence(writer_sequence) @@ -237,7 +235,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) .InSequence(ready_pes_sequence) .WillOnce(Return(1u)); - + EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) .InSequence(ready_pes_sequence) .WillOnce(Return(0u)); @@ -245,10 +243,6 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { EXPECT_CALL(*mock_pes_packet_generator_, Flush()) .WillOnce(Return(true)); - EXPECT_CALL(*mock_ts_writer_, NewSegment(_)) - .InSequence(writer_sequence) - .WillOnce(Return(true)); - EXPECT_CALL(*mock_ts_writer_, AddPesPacketMock(_, _)) .Times(2) .WillRepeatedly(Return(true)); @@ -277,8 +271,8 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -305,8 +299,8 @@ TEST_F(TsSegmenterTest, FinalizeSegment) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -332,8 +326,8 @@ TEST_F(TsSegmenterTest, EncryptedSample) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "memory://file$Number$.ts"; @@ -391,8 +385,6 @@ TEST_F(TsSegmenterTest, EncryptedSample) { .InSequence(pes_packet_sequence) .WillOnce(Return(new PesPacket())); - EXPECT_CALL(mock_listener, OnNewSegment("memory://file1.ts", _, _, _)); - MockTsWriter* mock_ts_writer_raw = mock_ts_writer_.get(); segmenter.InjectPesPacketGeneratorForTesting( diff --git a/packager/media/formats/mp2t/ts_stream_type.h b/packager/media/formats/mp2t/ts_stream_type.h index 459311da0d1..27abbb9e85e 100644 --- a/packager/media/formats/mp2t/ts_stream_type.h +++ b/packager/media/formats/mp2t/ts_stream_type.h @@ -45,6 +45,7 @@ enum class TsStreamType { // Below are internal values used to select other stream types based on other // info in headers. kDvbSubtitles = 0x100, + kTeletextSubtitles = 0x101, }; } // namespace mp2t diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index bd724566fd5..1553de3ed21 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -15,7 +15,6 @@ #include #include #include -#include namespace shaka { namespace media { diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index e200593c659..299b30abccb 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -1639,6 +1639,16 @@ bool VideoSampleEntry::ReadWriteInternal(BoxBuffer* buffer) { extra_codec_configs.push_back(std::move(dv_box)); } } + const bool is_av1 = actual_format == FOURCC_av01; + if (is_av1) { + for (FourCC fourcc : {FOURCC_dvvC}) { + CodecConfiguration dv_box; + dv_box.box_type = fourcc; + RCHECK(buffer->TryReadWriteChild(&dv_box)); + if (!dv_box.data.empty()) + extra_codec_configs.push_back(std::move(dv_box)); + } + } } else { for (CodecConfiguration& extra_codec_config : extra_codec_configs) RCHECK(buffer->ReadWriteChild(&extra_codec_config)); @@ -1817,6 +1827,27 @@ size_t DTSSpecific::ComputeSizeInternal() { sizeof(kDdtsExtraData); } +UDTSSpecific::UDTSSpecific() = default; +UDTSSpecific::~UDTSSpecific() = default; + +FourCC UDTSSpecific::BoxType() const { + return FOURCC_udts; +} + +bool UDTSSpecific::ReadWriteInternal(BoxBuffer* buffer) { + RCHECK(ReadWriteHeaderInternal(buffer) && + buffer->ReadWriteVector( + &data, buffer->Reading() ? buffer->BytesLeft() : data.size())); + return true; +} + +size_t UDTSSpecific::ComputeSizeInternal() { + // This box is optional. Skip it if not initialized. + if (data.empty()) + return 0; + return HeaderSize() + data.size(); +} + AC3Specific::AC3Specific() = default; AC3Specific::~AC3Specific() = default; @@ -1954,6 +1985,27 @@ size_t FlacSpecific::ComputeSizeInternal() { return HeaderSize() + data.size(); } +ALACSpecific::ALACSpecific() = default; +ALACSpecific::~ALACSpecific() = default; + +FourCC ALACSpecific::BoxType() const { + return FOURCC_alac; +} + +bool ALACSpecific::ReadWriteInternal(BoxBuffer* buffer) { + RCHECK(ReadWriteHeaderInternal(buffer)); + size_t size = buffer->Reading() ? buffer->BytesLeft() : data.size(); + RCHECK(buffer->ReadWriteVector(&data, size)); + return true; +} + +size_t ALACSpecific::ComputeSizeInternal() { + // This box is optional. Skip it if not initialized. + if (data.empty()) + return 0; + return HeaderSize() + data.size(); +} + AudioSampleEntry::AudioSampleEntry() = default; AudioSampleEntry::~AudioSampleEntry() = default; @@ -1989,12 +2041,14 @@ bool AudioSampleEntry::ReadWriteInternal(BoxBuffer* buffer) { RCHECK(buffer->TryReadWriteChild(&esds)); RCHECK(buffer->TryReadWriteChild(&ddts)); + RCHECK(buffer->TryReadWriteChild(&udts)); RCHECK(buffer->TryReadWriteChild(&dac3)); RCHECK(buffer->TryReadWriteChild(&dec3)); RCHECK(buffer->TryReadWriteChild(&dac4)); RCHECK(buffer->TryReadWriteChild(&dops)); RCHECK(buffer->TryReadWriteChild(&dfla)); RCHECK(buffer->TryReadWriteChild(&mhac)); + RCHECK(buffer->TryReadWriteChild(&alac)); // Somehow Edge does not support having sinf box before codec_configuration, // box, so just do it in the end of AudioSampleEntry. See @@ -2020,7 +2074,8 @@ size_t AudioSampleEntry::ComputeSizeInternal() { sizeof(samplesize) + sizeof(samplerate) + sinf.ComputeSize() + esds.ComputeSize() + ddts.ComputeSize() + dac3.ComputeSize() + dec3.ComputeSize() + dops.ComputeSize() + dfla.ComputeSize() + - dac4.ComputeSize() + mhac.ComputeSize() + + dac4.ComputeSize() + mhac.ComputeSize() + udts.ComputeSize() + + alac.ComputeSize() + // Reserved and predefined bytes. 6 + 8 + // 6 + 8 bytes reserved. 4; // 4 bytes predefined. diff --git a/packager/media/formats/mp4/box_definitions.h b/packager/media/formats/mp4/box_definitions.h index 98dec4caeff..68836671a53 100644 --- a/packager/media/formats/mp4/box_definitions.h +++ b/packager/media/formats/mp4/box_definitions.h @@ -349,6 +349,12 @@ struct DTSSpecific : Box { std::vector extra_data; }; +struct UDTSSpecific : Box { + DECLARE_BOX_METHODS(UDTSSpecific); + + std::vector data; +}; + struct AC3Specific : Box { DECLARE_BOX_METHODS(AC3Specific); @@ -391,6 +397,15 @@ struct FlacSpecific : FullBox { std::vector data; }; +// ALAC specific decoder configuration box: +// https://wiki.multimedia.cx/index.php/Apple_Lossless_Audio_Coding +// We do not care about the actual data inside, which is simply copied over. +struct ALACSpecific : FullBox { + DECLARE_BOX_METHODS(ALACSpecific); + + std::vector data; +}; + struct AudioSampleEntry : Box { DECLARE_BOX_METHODS(AudioSampleEntry); @@ -411,12 +426,14 @@ struct AudioSampleEntry : Box { ElementaryStreamDescriptor esds; DTSSpecific ddts; + UDTSSpecific udts; AC3Specific dac3; EC3Specific dec3; AC4Specific dac4; OpusSpecific dops; FlacSpecific dfla; MHAConfiguration mhac; + ALACSpecific alac; }; struct WebVTTConfigurationBox : Box { diff --git a/packager/media/formats/mp4/box_definitions_unittest.cc b/packager/media/formats/mp4/box_definitions_unittest.cc index f7d68f14f60..2515951c1e5 100644 --- a/packager/media/formats/mp4/box_definitions_unittest.cc +++ b/packager/media/formats/mp4/box_definitions_unittest.cc @@ -417,6 +417,16 @@ class BoxDefinitionsTestGeneral : public testing::Test { ddts->pcm_sample_depth = 24; } + void Fill(UDTSSpecific* udts) { + const uint8_t kUdtsData[] = {0x01, 0x20, 0x00, 0x00, 0x0, 0x3F, 0x80, 0x00}; + udts->data.assign(kUdtsData, kUdtsData + std::size(kUdtsData)); + } + + void Modify(UDTSSpecific* udts) { + const uint8_t kUdtsData[] = {0x01, 0x20, 0x01, 0x80, 0xA, 0x3F, 0x80, 0x00}; + udts->data.assign(kUdtsData, kUdtsData + std::size(kUdtsData)); + } + void Fill(AC3Specific* dac3) { const uint8_t kAc3Data[] = {0x50, 0x11, 0x60}; dac3->data.assign(kAc3Data, kAc3Data + std::size(kAc3Data)); @@ -1217,6 +1227,20 @@ TEST_F(BoxDefinitionsTest, DTSSampleEntry) { ASSERT_EQ(entry, entry_readback); } +TEST_F(BoxDefinitionsTest, UDTSSampleEntry) { + AudioSampleEntry entry; + entry.format = FOURCC_dtsx; + entry.data_reference_index = 2; + entry.channelcount = 6; + entry.samplesize = 16; + entry.samplerate = 48000; + Fill(&entry.udts); + entry.Write(this->buffer_.get()); + AudioSampleEntry entry_readback; + ASSERT_TRUE(ReadBack(&entry_readback)); + ASSERT_EQ(entry, entry_readback); +} + TEST_F(BoxDefinitionsTest, AC3SampleEntry) { AudioSampleEntry entry; entry.format = FOURCC_ac_3; diff --git a/packager/media/formats/mp4/low_latency_segment_segmenter.cc b/packager/media/formats/mp4/low_latency_segment_segmenter.cc index cbb1449acc3..22476461dc6 100644 --- a/packager/media/formats/mp4/low_latency_segment_segmenter.cc +++ b/packager/media/formats/mp4/low_latency_segment_segmenter.cc @@ -71,13 +71,13 @@ Status LowLatencySegmentSegmenter::DoFinalize() { return Status::OK; } -Status LowLatencySegmentSegmenter::DoFinalizeSegment() { +Status LowLatencySegmentSegmenter::DoFinalizeSegment(int64_t segment_number) { return FinalizeSegment(); } -Status LowLatencySegmentSegmenter::DoFinalizeChunk() { +Status LowLatencySegmentSegmenter::DoFinalizeChunk(int64_t segment_number) { if (is_initial_chunk_in_seg_) { - return WriteInitialChunk(); + return WriteInitialChunk(segment_number); } return WriteChunk(); } @@ -98,7 +98,7 @@ Status LowLatencySegmentSegmenter::WriteInitSegment() { return buffer->WriteToFile(file.get()); } -Status LowLatencySegmentSegmenter::WriteInitialChunk() { +Status LowLatencySegmentSegmenter::WriteInitialChunk(int64_t segment_number) { DCHECK(sidx()); DCHECK(fragment_buffer()); DCHECK(styp_); @@ -161,9 +161,9 @@ Status LowLatencySegmentSegmenter::WriteInitialChunk() { } // Add the current segment in the manifest. // Following chunks will be appended to the open segment file. - muxer_listener()->OnNewSegment(file_name_, - sidx()->earliest_presentation_time, - segment_duration, segment_size_); + muxer_listener()->OnNewSegment( + file_name_, sidx()->earliest_presentation_time, segment_duration, + segment_size_, segment_number); is_initial_chunk_in_seg_ = false; } diff --git a/packager/media/formats/mp4/low_latency_segment_segmenter.h b/packager/media/formats/mp4/low_latency_segment_segmenter.h index 0db73df8b03..5f2e577ddad 100644 --- a/packager/media/formats/mp4/low_latency_segment_segmenter.h +++ b/packager/media/formats/mp4/low_latency_segment_segmenter.h @@ -44,13 +44,13 @@ class LowLatencySegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; - Status DoFinalizeChunk() override; + Status DoFinalizeSegment(int64_t segment_number) override; + Status DoFinalizeChunk(int64_t segment_number) override; // Write segment to file. Status WriteInitSegment(); Status WriteChunk(); - Status WriteInitialChunk(); + Status WriteInitialChunk(int64_t segment_number); Status FinalizeSegment(); uint64_t GetSegmentDuration(); diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index af8a0eea732..71d569dd1c1 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -37,6 +37,13 @@ #include #include +ABSL_FLAG(bool, + use_dovi_supplemental_codecs, + false, + "Set to true to signal DolbyVision using the modern supplemental " + "codecs approach instead of the legacy " + "duplicate representations approach"); + namespace shaka { namespace media { namespace mp4 { @@ -90,6 +97,8 @@ Codec FourCCToCodec(FourCC fourcc) { return kCodecDTSL; case FOURCC_dtse: return kCodecDTSE; + case FOURCC_dtsx: + return kCodecDTSX; case FOURCC_dtsp: return kCodecDTSP; case FOURCC_dtsm: @@ -100,6 +109,8 @@ Codec FourCCToCodec(FourCC fourcc) { return kCodecEAC3; case FOURCC_ac_4: return kCodecAC4; + case FOURCC_alac: + return kCodecALAC; case FOURCC_fLaC: return kCodecFlac; case FOURCC_mha1: @@ -152,6 +163,7 @@ bool UpdateCodecStringForDolbyVision( switch (actual_format) { case FOURCC_dvh1: case FOURCC_dvhe: + case FOURCC_dav1: // Non-Backward compatibility mode. Replace the code string with // Dolby Vision only. *codec_string = dovi_config.GetCodecString(actual_format); @@ -165,6 +177,9 @@ bool UpdateCodecStringForDolbyVision( // See above. *codec_string += ";" + dovi_config.GetCodecString(FOURCC_dvh1); break; + case FOURCC_av01: + *codec_string += ";" + dovi_config.GetCodecString(FOURCC_dav1); + break; default: LOG(ERROR) << "Unsupported format with extra codec " << FourCCToString(actual_format); @@ -173,6 +188,48 @@ bool UpdateCodecStringForDolbyVision( return true; } +bool UpdateDolbyVisionInfo(FourCC actual_format, + const std::vector& configs, + uint8_t transfer_characteristics, + std::string* codec_string, + std::string* dovi_supplemental_codec_string, + FourCC* dovi_compatible_brand) { + DOVIDecoderConfigurationRecord dovi_config; + if (!dovi_config.Parse(GetDOVIDecoderConfig(configs))) { + LOG(ERROR) << "Failed to parse Dolby Vision decoder " + "configuration record."; + return false; + } + switch (actual_format) { + case FOURCC_dvh1: + case FOURCC_dvhe: + case FOURCC_dav1: + // Non-Backward compatibility mode. Replace the code string with + // Dolby Vision only. + *codec_string = dovi_config.GetCodecString(actual_format); + break; + case FOURCC_hev1: + // Backward compatibility mode. Use supplemental codec indicating Dolby + // Dolby Vision content. + *dovi_supplemental_codec_string = dovi_config.GetCodecString(FOURCC_dvhe); + break; + case FOURCC_hvc1: + // See above. + *dovi_supplemental_codec_string = dovi_config.GetCodecString(FOURCC_dvh1); + break; + case FOURCC_av01: + *dovi_supplemental_codec_string = dovi_config.GetCodecString(FOURCC_dav1); + break; + default: + LOG(ERROR) << "Unsupported format with extra codec " + << FourCCToString(actual_format); + return false; + } + *dovi_compatible_brand = + dovi_config.GetDoViCompatibleBrand(transfer_characteristics); + return true; +} + const uint64_t kNanosecondsPerSecond = 1000000000ull; } // namespace @@ -404,6 +461,9 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { std::vector> streams; + bool use_dovi_supplemental = + absl::GetFlag(FLAGS_use_dovi_supplemental_codecs); + for (std::vector::const_iterator track = moov_->tracks.begin(); track != moov_->tracks.end(); ++track) { const int32_t timescale = track->media.header.timescale; @@ -511,6 +571,9 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { max_bitrate = entry.ddts.max_bitrate; avg_bitrate = entry.ddts.avg_bitrate; break; + case FOURCC_dtsx: + codec_config = entry.udts.data; + break; case FOURCC_ac_3: codec_config = entry.dac3.data; num_channels = static_cast(GetAc3NumChannels(codec_config)); @@ -529,6 +592,9 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { return false; } break; + case FOURCC_alac: + codec_config = entry.alac.data; + break; case FOURCC_fLaC: codec_config = entry.dfla.data; break; @@ -628,8 +694,12 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { &pixel_height); } std::string codec_string; + std::string dovi_supplemental_codec_string(""); + FourCC dovi_compatible_brand = FOURCC_NULL; uint8_t nalu_length_size = 0; uint8_t transfer_characteristics = 0; + uint8_t color_primaries = 0; + uint8_t matrix_coefficients = 0; const FourCC actual_format = entry.GetActualFormat(); const Codec video_codec = FourCCToCodec(actual_format); @@ -642,13 +712,34 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { } // Generate the full codec string if the colr atom is present. if (entry.colr.color_parameter_type != FOURCC_NULL) { + transfer_characteristics = entry.colr.transfer_characteristics; + color_primaries = entry.colr.color_primaries; + matrix_coefficients = entry.colr.matrix_coefficients; codec_string = av1_config.GetCodecString( - entry.colr.color_primaries, entry.colr.transfer_characteristics, - entry.colr.matrix_coefficients, + color_primaries, transfer_characteristics, matrix_coefficients, entry.colr.video_full_range_flag); } else { codec_string = av1_config.GetCodecString(); } + + if (!entry.extra_codec_configs.empty()) { + // |extra_codec_configs| is present only for Dolby Vision. + if (use_dovi_supplemental) { + if (!UpdateDolbyVisionInfo( + actual_format, entry.extra_codec_configs, + transfer_characteristics, &codec_string, + &dovi_supplemental_codec_string, + &dovi_compatible_brand)) { + return false; + } + } else { + if (!UpdateCodecStringForDolbyVision(actual_format, + entry.extra_codec_configs, + &codec_string)) { + return false; + } + } + } break; } case FOURCC_avc1: @@ -661,6 +752,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { codec_string = avc_config.GetCodecString(actual_format); nalu_length_size = avc_config.nalu_length_size(); transfer_characteristics = avc_config.transfer_characteristics(); + color_primaries = avc_config.color_primaries(); + matrix_coefficients = avc_config.matrix_coefficients(); // Use configurations from |avc_config| if it is valid. if (avc_config.coded_width() != 0) { @@ -709,12 +802,25 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { codec_string = hevc_config.GetCodecString(actual_format); nalu_length_size = hevc_config.nalu_length_size(); transfer_characteristics = hevc_config.transfer_characteristics(); + color_primaries = hevc_config.color_primaries(); + matrix_coefficients = hevc_config.matrix_coefficients(); if (!entry.extra_codec_configs.empty()) { // |extra_codec_configs| is present only for Dolby Vision. - if (!UpdateCodecStringForDolbyVision( - actual_format, entry.extra_codec_configs, &codec_string)) { - return false; + if (use_dovi_supplemental) { + if (!UpdateDolbyVisionInfo( + actual_format, entry.extra_codec_configs, + transfer_characteristics, &codec_string, + &dovi_supplemental_codec_string, + &dovi_compatible_brand)) { + return false; + } + } else { + if (!UpdateCodecStringForDolbyVision(actual_format, + entry.extra_codec_configs, + &codec_string)) { + return false; + } } } break; @@ -757,10 +863,16 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { track->header.track_id, timescale, duration, video_codec, GetH26xStreamFormat(actual_format), codec_string, codec_configuration_data.data(), codec_configuration_data.size(), - coded_width, coded_height, pixel_width, pixel_height, - transfer_characteristics, + coded_width, coded_height, pixel_width, pixel_height, color_primaries, + matrix_coefficients, transfer_characteristics, 0, // trick_play_factor nalu_length_size, track->media.header.language.code, is_encrypted)); + + if (use_dovi_supplemental) { + video_stream_info->set_supplemental_codec( + dovi_supplemental_codec_string); + video_stream_info->set_compatible_brand(dovi_compatible_brand); + } video_stream_info->set_extra_config(entry.ExtraCodecConfigsAsVector()); video_stream_info->set_colr_data((entry.colr.raw_box).data(), (entry.colr.raw_box).size()); diff --git a/packager/media/formats/mp4/mp4_media_parser.h b/packager/media/formats/mp4/mp4_media_parser.h index 817ef208505..3d8e7d37d6f 100644 --- a/packager/media/formats/mp4/mp4_media_parser.h +++ b/packager/media/formats/mp4/mp4_media_parser.h @@ -12,12 +12,17 @@ #include #include +#include +#include + #include #include #include #include #include +ABSL_DECLARE_FLAG(bool, use_dovi_supplemental_codecs); + namespace shaka { namespace media { namespace mp4 { diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 9411a459d69..085eb257e76 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -76,6 +76,8 @@ FourCC CodecToFourCC(Codec codec, H26xStreamFormat h26x_stream_format) { return FOURCC_mp4a; case kCodecAC3: return FOURCC_ac_3; + case kCodecALAC: + return FOURCC_alac; case kCodecDTSC: return FOURCC_dtsc; case kCodecDTSH: @@ -86,6 +88,8 @@ FourCC CodecToFourCC(Codec codec, H26xStreamFormat h26x_stream_format) { return FOURCC_dtse; case kCodecDTSM: return FOURCC_dtsm; + case kCodecDTSX: + return FOURCC_dtsx; case kCodecEAC3: return FOURCC_ec_3; case kCodecAC4: @@ -210,7 +214,8 @@ Status MP4Muxer::FinalizeSegment(size_t stream_id, DCHECK(segmenter_); VLOG(3) << "Finalizing " << (segment_info.is_subsegment ? "sub" : "") << "segment " << segment_info.start_timestamp << " duration " - << segment_info.duration; + << segment_info.duration << " segment number " + << segment_info.segment_number; return segmenter_->FinalizeSegment(stream_id, segment_info); } @@ -237,8 +242,22 @@ Status MP4Muxer::DelayInitializeMuxer() { ftyp->compatible_brands.push_back(codec_fourcc); // https://professional.dolby.com/siteassets/content-creation/dolby-vision-for-content-creators/dolby_vision_bitstreams_within_the_iso_base_media_file_format_dec2017.pdf - if (streams()[0].get()->codec_string().find("dvh") != std::string::npos) + std::string codec_string = + static_cast(streams()[0].get()) + ->codec_string(); + std::string supplemental_codec_string = + static_cast(streams()[0].get()) + ->supplemental_codec(); + if (codec_string.find("dvh") != std::string::npos || + supplemental_codec_string.find("dvh") != std::string::npos || + codec_string.find("dav1") != std::string::npos || + supplemental_codec_string.find("dav1") != std::string::npos) ftyp->compatible_brands.push_back(FOURCC_dby1); + FourCC extra_brand = + static_cast(streams()[0].get()) + ->compatible_brand(); + if (extra_brand != FOURCC_NULL) + ftyp->compatible_brands.push_back(extra_brand); } // CMAF allows only one track/stream per file. @@ -518,6 +537,9 @@ bool MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info, audio.ddts.sampling_frequency = audio_info->sampling_frequency(); audio.ddts.pcm_sample_depth = audio_info->sample_bits(); break; + case kCodecDTSX: + audio.udts.data = audio_info->codec_config(); + break; case kCodecAC3: audio.dac3.data = audio_info->codec_config(); break; @@ -527,6 +549,9 @@ bool MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info, case kCodecAC4: audio.dac4.data = audio_info->codec_config(); break; + case kCodecALAC: + audio.alac.data = audio_info->codec_config(); + break; case kCodecFlac: audio.dfla.data = audio_info->codec_config(); break; @@ -712,10 +737,7 @@ uint64_t MP4Muxer::IsoTimeNow() { const uint64_t kIsomTimeOffset = 2082844800l; // Get the current system time since January 1, 1970, in seconds. - std::chrono::system_clock::duration duration = - std::chrono::system_clock::now().time_since_epoch(); - std::int64_t secondsSince1970 = - std::chrono::duration_cast(duration).count(); + std::int64_t secondsSince1970 = Now(); // Add the offset of seconds between January 1, 1970, and January 1, 1904. return secondsSince1970 + kIsomTimeOffset; diff --git a/packager/media/formats/mp4/multi_segment_segmenter.cc b/packager/media/formats/mp4/multi_segment_segmenter.cc index 2211ad50810..2b95549503b 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.cc +++ b/packager/media/formats/mp4/multi_segment_segmenter.cc @@ -32,8 +32,7 @@ MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options, std::unique_ptr ftyp, std::unique_ptr moov) : Segmenter(options, std::move(ftyp), std::move(moov)), - styp_(new SegmentType), - num_segments_(0) { + styp_(new SegmentType) { // Use the same brands for styp as ftyp. styp_->major_brand = Segmenter::ftyp()->major_brand; styp_->compatible_brands = Segmenter::ftyp()->compatible_brands; @@ -71,8 +70,8 @@ Status MultiSegmentSegmenter::DoFinalize() { return Status::OK; } -Status MultiSegmentSegmenter::DoFinalizeSegment() { - return WriteSegment(); +Status MultiSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) { + return WriteSegment(segment_number); } Status MultiSegmentSegmenter::WriteInitSegment() { @@ -91,7 +90,7 @@ Status MultiSegmentSegmenter::WriteInitSegment() { return buffer->WriteToFile(file.get()); } -Status MultiSegmentSegmenter::WriteSegment() { +Status MultiSegmentSegmenter::WriteSegment(int64_t segment_number) { DCHECK(sidx()); DCHECK(fragment_buffer()); DCHECK(styp_); @@ -116,7 +115,7 @@ Status MultiSegmentSegmenter::WriteSegment() { } else { file_name = GetSegmentName(options().segment_template, sidx()->earliest_presentation_time, - num_segments_++, options().bandwidth); + segment_number, options().bandwidth); file.reset(File::Open(file_name.c_str(), "w")); if (!file) { return Status(error::FILE_FAILURE, @@ -165,9 +164,9 @@ Status MultiSegmentSegmenter::WriteSegment() { UpdateProgress(segment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment(file_name, - sidx()->earliest_presentation_time, - segment_duration, segment_size); + muxer_listener()->OnNewSegment( + file_name, sidx()->earliest_presentation_time, segment_duration, + segment_size, segment_number); } return Status::OK; diff --git a/packager/media/formats/mp4/multi_segment_segmenter.h b/packager/media/formats/mp4/multi_segment_segmenter.h index c82ac5ce9d7..c36eed65bb5 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.h +++ b/packager/media/formats/mp4/multi_segment_segmenter.h @@ -43,14 +43,13 @@ class MultiSegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; + Status DoFinalizeSegment(int64_t segment_number) override; // Write segment to file. Status WriteInitSegment(); - Status WriteSegment(); + Status WriteSegment(int64_t segment_number); std::unique_ptr styp_; - uint32_t num_segments_; std::shared_ptr emsg_handler_; DISALLOW_COPY_AND_ASSIGN(MultiSegmentSegmenter); diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index da65d6b23fd..8cd17027239 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -143,8 +143,12 @@ Status Segmenter::AddSample(size_t stream_id, const MediaSample& sample) { if (!status.ok()) return status; - if (sample_duration_ == 0) - sample_duration_ = sample.duration(); + // The duration of the first sample may have been adjusted, so use + // the duration of the second sample instead. + if (num_samples_ < 2) { + sample_durations_[num_samples_] = sample.duration(); + num_samples_++; + } stream_durations_[stream_id] += sample.duration(); return Status::OK; } @@ -232,14 +236,15 @@ Status Segmenter::FinalizeSegment(size_t stream_id, if (segment_info.is_chunk) { // Finalize the completed chunk for the LL-DASH case. - status = DoFinalizeChunk(); + status = DoFinalizeChunk(segment_info.segment_number); if (!status.ok()) return status; } if (!segment_info.is_subsegment || segment_info.is_final_chunk_in_seg) { // Finalize the segment. - status = DoFinalizeSegment(); + status = DoFinalizeSegment(segment_info.segment_number); + // Reset segment information to initial state. sidx_->references.clear(); key_frame_infos_.clear(); diff --git a/packager/media/formats/mp4/segmenter.h b/packager/media/formats/mp4/segmenter.h index 5333ffe35c3..51e659b35cb 100644 --- a/packager/media/formats/mp4/segmenter.h +++ b/packager/media/formats/mp4/segmenter.h @@ -99,7 +99,9 @@ class Segmenter { /// @return The sample duration in the timescale of the media. /// Returns 0 if no samples are added yet. - int32_t sample_duration() const { return sample_duration_; } + int64_t sample_duration() const { + return sample_durations_[num_samples_ < 2 ? 0 : 1]; + } protected: /// Update segmentation progress using ProgressListener. @@ -125,9 +127,8 @@ class Segmenter { private: virtual Status DoInitialize() = 0; virtual Status DoFinalize() = 0; - virtual Status DoFinalizeSegment() = 0; - - virtual Status DoFinalizeChunk() { return Status::OK; } + virtual Status DoFinalizeSegment(int64_t segment_number) = 0; + virtual Status DoFinalizeChunk(int64_t segment_number) { return Status::OK; } uint32_t GetReferenceStreamId(); @@ -147,7 +148,8 @@ class Segmenter { ProgressListener* progress_listener_ = nullptr; uint64_t progress_target_ = 0u; uint64_t accumulated_progress_ = 0u; - int32_t sample_duration_ = 0; + int64_t sample_durations_[2] = {0, 0}; + size_t num_samples_ = 0; std::vector stream_durations_; std::vector key_frame_infos_; diff --git a/packager/media/formats/mp4/single_segment_segmenter.cc b/packager/media/formats/mp4/single_segment_segmenter.cc index 13527ce446d..afedf4ac7a1 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.cc +++ b/packager/media/formats/mp4/single_segment_segmenter.cc @@ -166,7 +166,7 @@ Status SingleSegmentSegmenter::DoFinalize() { return Status::OK; } -Status SingleSegmentSegmenter::DoFinalizeSegment() { +Status SingleSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) { DCHECK(sidx()); DCHECK(fragment_buffer()); // sidx() contains pre-generated segment references with one reference per @@ -224,9 +224,9 @@ Status SingleSegmentSegmenter::DoFinalizeSegment() { UpdateProgress(vod_ref.subsegment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment(options().output_file_name, - vod_ref.earliest_presentation_time, - vod_ref.subsegment_duration, segment_size); + muxer_listener()->OnNewSegment( + options().output_file_name, vod_ref.earliest_presentation_time, + vod_ref.subsegment_duration, segment_size, segment_number); } return Status::OK; } diff --git a/packager/media/formats/mp4/single_segment_segmenter.h b/packager/media/formats/mp4/single_segment_segmenter.h index edfe618f8eb..02fe763088b 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.h +++ b/packager/media/formats/mp4/single_segment_segmenter.h @@ -44,7 +44,7 @@ class SingleSegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; + Status DoFinalizeSegment(int64_t segment_number) override; std::unique_ptr vod_sidx_; std::string temp_file_name_; diff --git a/packager/media/formats/packed_audio/packed_audio_writer.cc b/packager/media/formats/packed_audio/packed_audio_writer.cc index f73e09faa0c..14a3d2febb1 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer.cc @@ -81,7 +81,7 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id, options().segment_template.empty() ? options().output_file_name : GetSegmentName(options().segment_template, segment_timestamp, - segment_number_++, options().bandwidth); + segment_info.segment_number, options().bandwidth); // Save |segment_size| as it will be cleared after writing. const size_t segment_size = segmenter_->segment_buffer()->Size(); @@ -92,7 +92,8 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id, if (muxer_listener()) { muxer_listener()->OnNewSegment( segment_path, segment_timestamp + transport_stream_timestamp_offset_, - segment_info.duration * segmenter_->TimescaleScale(), segment_size); + segment_info.duration * segmenter_->TimescaleScale(), segment_size, + segment_info.segment_number); } return Status::OK; } diff --git a/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc b/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc index 910e8fa3a06..637b4983827 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc @@ -44,6 +44,8 @@ const char kOutputFile[] = "memory://test.aac"; const char kSegmentTemplate[] = "memory://test_$Number$.aac"; const char kSegment1Name[] = "memory://test_1.aac"; const char kSegment2Name[] = "memory://test_2.aac"; +const int64_t kSegmentNumber1 = 1; +const int64_t kSegmentNumber2 = 2; class MockPackedAudioSegmenter : public PackedAudioSegmenter { public: @@ -122,9 +124,10 @@ TEST_P(PackedAudioWriterTest, SubsegmentIgnored) { const int64_t kDuration = 100; const bool kSubsegment = true; auto subsegment_stream_data = StreamData::FromSegmentInfo( - kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, kSubsegment)); + kStreamIndex, + GetSegmentInfo(kTimestamp, kDuration, kSubsegment, kSegmentNumber1)); - EXPECT_CALL(*mock_muxer_listener_ptr_, OnNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*mock_muxer_listener_ptr_, OnNewSegment(_, _, _, _, _)).Times(0); EXPECT_CALL(*mock_segmenter_ptr_, FinalizeSegment()).Times(0); ASSERT_OK(Input(kInput)->Dispatch(std::move(subsegment_stream_data))); } @@ -137,7 +140,8 @@ TEST_P(PackedAudioWriterTest, OneSegment) { const int64_t kDuration = 100; const bool kSubsegment = true; auto segment_stream_data = StreamData::FromSegmentInfo( - kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, !kSubsegment)); + kStreamIndex, + GetSegmentInfo(kTimestamp, kDuration, !kSubsegment, kSegmentNumber1)); const double kMockTimescaleScale = 10; const char kMockSegmentData[] = "hello segment 1"; @@ -147,7 +151,8 @@ TEST_P(PackedAudioWriterTest, OneSegment) { *mock_muxer_listener_ptr_, OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, kTimestamp * kMockTimescaleScale, - kDuration * kMockTimescaleScale, kSegmentDataSize)); + kDuration * kMockTimescaleScale, kSegmentDataSize, + kSegmentNumber1)); EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) .WillRepeatedly(Return(kMockTimescaleScale)); @@ -190,10 +195,11 @@ TEST_P(PackedAudioWriterTest, TwoSegments) { const int64_t kDuration = 100; const bool kSubsegment = true; auto segment1_stream_data = StreamData::FromSegmentInfo( - kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, !kSubsegment)); - auto segment2_stream_data = StreamData::FromSegmentInfo( kStreamIndex, - GetSegmentInfo(kTimestamp + kDuration, kDuration, !kSubsegment)); + GetSegmentInfo(kTimestamp, kDuration, !kSubsegment, kSegmentNumber1)); + auto segment2_stream_data = StreamData::FromSegmentInfo( + kStreamIndex, GetSegmentInfo(kTimestamp + kDuration, kDuration, + !kSubsegment, kSegmentNumber2)); const double kMockTimescaleScale = 10; const char kMockSegment1Data[] = "hello segment 1"; @@ -206,12 +212,13 @@ TEST_P(PackedAudioWriterTest, TwoSegments) { OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, kTimestamp * kMockTimescaleScale, kDuration * kMockTimescaleScale, - sizeof(kMockSegment1Data) - 1)); + sizeof(kMockSegment1Data) - 1, kSegmentNumber1)); EXPECT_CALL( *mock_muxer_listener_ptr_, OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment2Name, (kTimestamp + kDuration) * kMockTimescaleScale, - kDuration * kMockTimescaleScale, kSegment2DataSize)); + kDuration * kMockTimescaleScale, kSegment2DataSize, + kSegmentNumber2)); EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) .WillRepeatedly(Return(kMockTimescaleScale)); diff --git a/packager/media/formats/ttml/ttml_generator.cc b/packager/media/formats/ttml/ttml_generator.cc index e7fc596fc3a..230582cb4fa 100644 --- a/packager/media/formats/ttml/ttml_generator.cc +++ b/packager/media/formats/ttml/ttml_generator.cc @@ -18,6 +18,7 @@ namespace ttml { namespace { constexpr const char* kRegionIdPrefix = "_shaka_region_"; +constexpr const char* kRegionTeletextPrefix = "ttx_"; std::string ToTtmlTime(int64_t time, int32_t timescale) { int64_t remaining = time * 1000 / timescale; @@ -54,6 +55,18 @@ void TtmlGenerator::Initialize(const std::map& regions, regions_ = regions; language_ = language; time_scale_ = time_scale; + // Add ebu_tt_d_regions + float step = 74.1f / 11; + for (int i = 0; i < 12; i++) { + TextRegion region; + float verPos = 10.0 + int(float(step) * float(i)); + region.width = TextNumber(80, TextUnitType::kPercent); + region.height = TextNumber(15, TextUnitType::kPercent); + region.window_anchor_x = TextNumber(10, TextUnitType::kPercent); + region.window_anchor_y = TextNumber(verPos, TextUnitType::kPercent); + const std::string id = kRegionTeletextPrefix + std::to_string(i); + regions_.emplace(id, region); + } } void TtmlGenerator::AddSample(const TextSample& sample) { @@ -66,72 +79,92 @@ void TtmlGenerator::Reset() { bool TtmlGenerator::Dump(std::string* result) const { xml::XmlNode root("tt"); + bool ebuTTDFormat = isEbuTTTD(); RCHECK(root.SetStringAttribute("xmlns", kTtNamespace)); RCHECK(root.SetStringAttribute("xmlns:tts", "http://www.w3.org/ns/ttml#styling")); - - bool did_log = false; - xml::XmlNode head("head"); + RCHECK(root.SetStringAttribute("xmlns:tts", + "http://www.w3.org/ns/ttml#styling")); RCHECK(root.SetStringAttribute("xml:lang", language_)); - for (const auto& pair : regions_) { - if (!did_log && (pair.second.region_anchor_x.value != 0 && - pair.second.region_anchor_y.value != 0)) { - LOG(WARNING) << "TTML doesn't support non-0 region anchor"; - did_log = true; - } - xml::XmlNode region("region"); - const auto origin = - ToTtmlSize(pair.second.window_anchor_x, pair.second.window_anchor_y); - const auto extent = ToTtmlSize(pair.second.width, pair.second.height); - RCHECK(region.SetStringAttribute("xml:id", pair.first)); - RCHECK(region.SetStringAttribute("tts:origin", origin)); - RCHECK(region.SetStringAttribute("tts:extent", extent)); - RCHECK(head.AddChild(std::move(region))); + if (ebuTTDFormat) { + RCHECK(root.SetStringAttribute("xmlns:ttp", + "http://www.w3.org/ns/ttml#parameter")); + RCHECK(root.SetStringAttribute("xmlns:ttm", + "http://www.w3.org/ns/ttml#metadata")); + RCHECK(root.SetStringAttribute("xmlns:ebuttm", "urn:ebu:tt:metadata")); + RCHECK(root.SetStringAttribute("xmlns:ebutts", "urn:ebu:tt:style")); + RCHECK(root.SetStringAttribute("xml:space", "default")); + RCHECK(root.SetStringAttribute("ttp:timeBase", "media")); + RCHECK(root.SetStringAttribute("ttp:cellResolution", "32 15")); } - RCHECK(root.AddChild(std::move(head))); - size_t image_count = 0; + xml::XmlNode head("head"); + xml::XmlNode styling("styling"); xml::XmlNode metadata("metadata"); + xml::XmlNode layout("layout"); + RCHECK(addRegions(layout)); + xml::XmlNode body("body"); + if (ebuTTDFormat) { + RCHECK(body.SetStringAttribute("style", "default")); + } + size_t image_count = 0; + std::unordered_set fragmentStyles; xml::XmlNode div("div"); for (const auto& sample : samples_) { - RCHECK(AddSampleToXml(sample, &div, &metadata, &image_count)); + RCHECK( + AddSampleToXml(sample, &div, &metadata, fragmentStyles, &image_count)); } - RCHECK(body.AddChild(std::move(div))); if (image_count > 0) { RCHECK(root.SetStringAttribute( "xmlns:smpte", "http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt")); - RCHECK(root.AddChild(std::move(metadata))); } + RCHECK(body.AddChild(std::move(div))); + RCHECK(head.AddChild(std::move(metadata))); + RCHECK(addStyling(styling, fragmentStyles)); + RCHECK(head.AddChild(std::move(styling))); + RCHECK(head.AddChild(std::move(layout))); + RCHECK(root.AddChild(std::move(head))); + RCHECK(root.AddChild(std::move(body))); *result = root.ToString(/* comment= */ ""); return true; } -bool TtmlGenerator::AddSampleToXml(const TextSample& sample, - xml::XmlNode* body, - xml::XmlNode* metadata, - size_t* image_count) const { +bool TtmlGenerator::AddSampleToXml( + const TextSample& sample, + xml::XmlNode* body, + xml::XmlNode* metadata, + std::unordered_set& fragmentStyles, + size_t* image_count) const { xml::XmlNode p("p"); - RCHECK(p.SetStringAttribute("xml:space", "preserve")); + if (!isEbuTTTD()) { + RCHECK(p.SetStringAttribute("xml:space", "preserve")); + } RCHECK(p.SetStringAttribute("begin", ToTtmlTime(sample.start_time(), time_scale_))); RCHECK( p.SetStringAttribute("end", ToTtmlTime(sample.EndTime(), time_scale_))); - RCHECK(ConvertFragmentToXml(sample.body(), &p, metadata, image_count)); + RCHECK(ConvertFragmentToXml(sample.body(), &p, metadata, fragmentStyles, + image_count)); if (!sample.id().empty()) RCHECK(p.SetStringAttribute("xml:id", sample.id())); const auto& settings = sample.settings(); - if (settings.line || settings.position || settings.width || settings.height) { - // TTML positioning needs to be from a region. - if (!settings.region.empty()) { - LOG(WARNING) - << "Using both text regions and positioning isn't supported in TTML"; + bool regionFound = false; + if (!settings.region.empty()) { + auto reg = regions_.find(settings.region); + if (reg != regions_.end()) { + regionFound = true; + RCHECK(p.SetStringAttribute("region", settings.region)); } + } + if (!regionFound && (settings.line || settings.position || settings.width || + settings.height)) { + // TTML positioning needs to be from a region. const auto origin = ToTtmlSize( settings.position.value_or(TextNumber(0, TextUnitType::kPixels)), settings.line.value_or(TextNumber(0, TextUnitType::kPixels))); @@ -146,8 +179,6 @@ bool TtmlGenerator::AddSampleToXml(const TextSample& sample, RCHECK(region.SetStringAttribute("tts:extent", extent)); RCHECK(p.SetStringAttribute("region", id)); RCHECK(body->AddChild(std::move(region))); - } else if (!settings.region.empty()) { - RCHECK(p.SetStringAttribute("region", settings.region)); } if (settings.writing_direction != WritingDirection::kHorizontal) { @@ -179,19 +210,22 @@ bool TtmlGenerator::AddSampleToXml(const TextSample& sample, return true; } -bool TtmlGenerator::ConvertFragmentToXml(const TextFragment& body, - xml::XmlNode* parent, - xml::XmlNode* metadata, - size_t* image_count) const { +bool TtmlGenerator::ConvertFragmentToXml( + const TextFragment& body, + xml::XmlNode* parent, + xml::XmlNode* metadata, + std::unordered_set& fragmentStyles, + size_t* image_count) const { if (body.newline) { xml::XmlNode br("br"); return parent->AddChild(std::move(br)); } - - // If we have new styles, add a new . xml::XmlNode span("span"); xml::XmlNode* node = parent; - if (body.style.bold || body.style.italic || body.style.underline) { + bool useSpan = + (body.style.bold || body.style.italic || body.style.underline || + !body.style.color.empty() || !body.style.backgroundColor.empty()); + if (useSpan) { node = &span; if (body.style.bold) { RCHECK(span.SetStringAttribute("tts:fontWeight", @@ -206,6 +240,20 @@ bool TtmlGenerator::ConvertFragmentToXml(const TextFragment& body, "tts:textDecoration", *body.style.underline ? "underline" : "noUnderline")); } + std::string color = "white"; + std::string backgroundColor = "black"; + + if (!body.style.color.empty()) { + color = body.style.color; + } + + if (!body.style.backgroundColor.empty()) { + backgroundColor = body.style.backgroundColor; + } + + const std::string fragStyle = color + "_" + backgroundColor; + fragmentStyles.insert(fragStyle); + RCHECK(span.SetStringAttribute("style", fragStyle)); } if (!body.body.empty()) { @@ -226,16 +274,91 @@ bool TtmlGenerator::ConvertFragmentToXml(const TextFragment& body, RCHECK(node->SetStringAttribute("smpte:backgroundImage", "#" + id)); } else { for (const auto& frag : body.sub_fragments) { - if (!ConvertFragmentToXml(frag, node, metadata, image_count)) + if (!ConvertFragmentToXml(frag, node, metadata, fragmentStyles, + image_count)) return false; } } - if (body.style.bold || body.style.italic || body.style.underline) + if (useSpan) RCHECK(parent->AddChild(std::move(span))); return true; } +std::vector TtmlGenerator::usedRegions() const { + std::vector uRegions; + for (const auto& sample : samples_) { + if (!sample.settings().region.empty()) { + uRegions.push_back(sample.settings().region); + } + } + return uRegions; +} + +bool TtmlGenerator::addRegions(xml::XmlNode& layout) const { + auto regNames = usedRegions(); + for (const auto& r : regions_) { + bool used = false; + for (const auto& name : regNames) { + if (r.first == name) { + used = true; + } + } + if (used) { + xml::XmlNode region("region"); + const auto origin = + ToTtmlSize(r.second.window_anchor_x, r.second.window_anchor_y); + const auto extent = ToTtmlSize(r.second.width, r.second.height); + RCHECK(region.SetStringAttribute("xml:id", r.first)); + RCHECK(region.SetStringAttribute("tts:origin", origin)); + RCHECK(region.SetStringAttribute("tts:extent", extent)); + RCHECK(region.SetStringAttribute("tts:overflow", "visible")); + RCHECK(layout.AddChild(std::move(region))); + } + } + return true; +} + +bool TtmlGenerator::addStyling( + xml::XmlNode& styling, + const std::unordered_set& fragmentStyles) const { + if (fragmentStyles.empty()) { + return true; + } + // Add default style + xml::XmlNode defaultStyle("style"); + RCHECK(defaultStyle.SetStringAttribute("xml:id", "default")); + RCHECK(defaultStyle.SetStringAttribute("tts:fontStyle", "normal")); + RCHECK(defaultStyle.SetStringAttribute("tts:fontFamily", "sansSerif")); + RCHECK(defaultStyle.SetStringAttribute("tts:fontSize", "100%")); + RCHECK(defaultStyle.SetStringAttribute("tts:lineHeight", "normal")); + RCHECK(defaultStyle.SetStringAttribute("tts:textAlign", "center")); + RCHECK(defaultStyle.SetStringAttribute("ebutts:linePadding", "0.5c")); + RCHECK(styling.AddChild(std::move(defaultStyle))); + + for (const auto& name : fragmentStyles) { + auto pos = name.find('_'); + auto color = name.substr(0, pos); + auto backgroundColor = name.substr(pos + 1, name.size()); + xml::XmlNode fragStyle("style"); + RCHECK(fragStyle.SetStringAttribute("xml:id", name)); + RCHECK( + fragStyle.SetStringAttribute("tts:backgroundColor", backgroundColor)); + RCHECK(fragStyle.SetStringAttribute("tts:color", color)); + RCHECK(styling.AddChild(std::move(fragStyle))); + } + return true; +} + +bool TtmlGenerator::isEbuTTTD() const { + for (const auto& sample : samples_) { + if (sample.settings().region.rfind(kRegionTeletextPrefix, 0) == 0) { + return true; + } + } + return false; +} + } // namespace ttml } // namespace media } // namespace shaka diff --git a/packager/media/formats/ttml/ttml_generator.h b/packager/media/formats/ttml/ttml_generator.h index 303a3e4b33d..9c1afbdfaf4 100644 --- a/packager/media/formats/ttml/ttml_generator.h +++ b/packager/media/formats/ttml/ttml_generator.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -38,12 +39,20 @@ class TtmlGenerator { bool AddSampleToXml(const TextSample& sample, xml::XmlNode* body, xml::XmlNode* metadata, + std::unordered_set& fragmentStyles, size_t* image_count) const; bool ConvertFragmentToXml(const TextFragment& fragment, xml::XmlNode* parent, xml::XmlNode* metadata, + std::unordered_set& fragmentStyles, size_t* image_count) const; + bool addStyling(xml::XmlNode& styling, + const std::unordered_set& fragmentStyles) const; + bool addRegions(xml::XmlNode& layout) const; + std::vector usedRegions() const; + bool isEbuTTTD() const; + std::list samples_; std::map regions_; std::string language_; diff --git a/packager/media/formats/ttml/ttml_generator_unittest.cc b/packager/media/formats/ttml/ttml_generator_unittest.cc index 23728d223f7..0f0d04dcbd9 100644 --- a/packager/media/formats/ttml/ttml_generator_unittest.cc +++ b/packager/media/formats/ttml/ttml_generator_unittest.cc @@ -64,7 +64,11 @@ TEST_F(TtmlMuxerTest, WithOneSegmentAndWithOneSample) { "\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "
\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" " \n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" " \n" "

\n" @@ -166,7 +182,11 @@ TEST_F(TtmlMuxerTest, HandlesLanguage) { "\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "
\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" " \n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "
\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" " \n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" " \n" "

\n" @@ -287,7 +323,11 @@ TEST_F(TtmlMuxerTest, HandlesReset) { "\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "
\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" - " \n" - " \n" - " " + " \n" + " \n" + " " "AQID\n" - " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" + "\n" + " \n" + " \n" + " \n" + "