Merge pull request #32 from ZLMediaKit/master
[pull] master from ZLMediaKit:master
|
|
@ -23,3 +23,37 @@ jobs:
|
|||
|
||||
- name: 编译
|
||||
run: cd Android && ./gradlew build
|
||||
|
||||
- name: 设置环境变量
|
||||
run: |
|
||||
echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/} | tr -s "/\?%*:|\"<>" "_")" >> $GITHUB_ENV
|
||||
echo "BRANCH2=$(echo ${GITHUB_REF#refs/heads/} )" >> $GITHUB_ENV
|
||||
echo "DATE=$(date +%Y-%m-%d)" >> $GITHUB_ENV
|
||||
|
||||
- name: 打包二进制
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.workflow }}_${{ env.BRANCH }}_${{ env.DATE }}
|
||||
path: Android/app/build/outputs/apk/debug/*
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
|
||||
- name: issue评论
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/feature/test'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: 483,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '- 下载地址: [${{ github.workflow }}_${{ env.BRANCH }}_${{ env.DATE }}](${{ steps.upload.outputs.artifact-url }})\n'
|
||||
+ '- 分支: ${{ env.BRANCH2 }}\n'
|
||||
+ '- git hash: ${{ github.sha }} \n'
|
||||
+ '- 编译日期: ${{ env.DATE }}\n'
|
||||
+ '- 编译记录: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n'
|
||||
+ '- 开启特性: 未开启openssl/webrtc/datachannel等功能\n'
|
||||
+ '- 打包ci名: ${{ github.workflow }}\n'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,12 +7,6 @@ on:
|
|||
- "feature/*"
|
||||
- "release/*"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
- "feature/*"
|
||||
- "release/*"
|
||||
|
||||
env:
|
||||
# Use docker.io for Docker Hub if empty
|
||||
REGISTRY: docker.io
|
||||
|
|
@ -21,7 +15,7 @@ env:
|
|||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
|
@ -39,7 +33,6 @@ jobs:
|
|||
# Install the cosign tool except on PR
|
||||
# https://github.com/sigstore/cosign-installer
|
||||
- name: Install cosign
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@d572c9c13673d2e0a26fabf90b5748f36886883f
|
||||
|
||||
- name: Set up QEMU
|
||||
|
|
@ -53,7 +46,6 @@ jobs:
|
|||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
|
|
@ -71,6 +63,7 @@ jobs:
|
|||
# Build and push Docker image with Buildx (don't push on PR)
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/feature/test'
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -13,9 +13,6 @@ jobs:
|
|||
- name: 下载submodule源码
|
||||
run: mv -f .gitmodules_github .gitmodules && git submodule sync && git submodule update --init
|
||||
|
||||
- name: apt-get安装依赖库(非必选)
|
||||
run: sudo apt-get update && sudo apt-get install -y cmake libssl-dev libsdl-dev libavcodec-dev libavutil-dev libswscale-dev libresample-dev libusrsctp-dev
|
||||
|
||||
- name: 下载 SRTP
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
|
|
@ -24,13 +21,115 @@ jobs:
|
|||
ref: v2.3.0
|
||||
path: 3rdpart/libsrtp
|
||||
|
||||
- name: 编译 SRTP
|
||||
run: cd 3rdpart/libsrtp && ./configure --enable-openssl && make -j4 && sudo make install
|
||||
- name: 下载 openssl
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: openssl/openssl
|
||||
fetch-depth: 1
|
||||
ref: OpenSSL_1_1_1
|
||||
path: 3rdpart/openssl
|
||||
|
||||
- name: 编译
|
||||
run: mkdir -p linux_build && cd linux_build && cmake .. -DENABLE_WEBRTC=true -DENABLE_FFMPEG=true && make -j $(nproc)
|
||||
|
||||
- name: 运行MediaServer
|
||||
run: pwd && cd release/linux/Debug && sudo ./MediaServer -d &
|
||||
- name: 下载 usrsctp
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: sctplab/usrsctp
|
||||
fetch-depth: 1
|
||||
ref: 0.9.5.0
|
||||
path: 3rdpart/usrsctp
|
||||
|
||||
- name: 启动 Docker 容器, 在Docker 容器中执行脚本
|
||||
run: |
|
||||
docker pull centos:7
|
||||
docker run -v $(pwd):/root -w /root --rm centos:7 sh -c "
|
||||
#!/bin/bash
|
||||
set -x
|
||||
|
||||
# Backup original CentOS-Base.repo file
|
||||
cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
|
||||
|
||||
# Define new repository configuration
|
||||
cat <<EOF > /etc/yum.repos.d/CentOS-Base.repo
|
||||
[base]
|
||||
name=CentOS-7 - Base - mirrors.aliyun.com
|
||||
baseurl=http://mirrors.aliyun.com/centos/7/os/x86_64/
|
||||
gpgcheck=1
|
||||
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
|
||||
|
||||
[updates]
|
||||
name=CentOS-7 - Updates - mirrors.aliyun.com
|
||||
baseurl=http://mirrors.aliyun.com/centos/7/updates/x86_64/
|
||||
gpgcheck=1
|
||||
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
|
||||
EOF
|
||||
|
||||
# Clean yum cache and recreate it
|
||||
yum clean all
|
||||
yum makecache
|
||||
|
||||
echo \"CentOS 7 软件源已成功切换\"
|
||||
yum install -y git wget gcc gcc-c++ make
|
||||
|
||||
mkdir -p /root/install
|
||||
|
||||
cd 3rdpart/openssl
|
||||
./config no-shared --prefix=/root/install
|
||||
make -j $(nproc)
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
wget https://github.com/Kitware/CMake/releases/download/v3.29.5/cmake-3.29.5.tar.gz
|
||||
tar -xf cmake-3.29.5.tar.gz
|
||||
cd cmake-3.29.5
|
||||
OPENSSL_ROOT_DIR=/root/install ./configure
|
||||
make -j $(nproc)
|
||||
make install
|
||||
cd ..
|
||||
|
||||
cd 3rdpart/usrsctp
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON ..
|
||||
make -j $(nproc)
|
||||
make install
|
||||
cd ../../../
|
||||
|
||||
cd 3rdpart/libsrtp && ./configure --enable-openssl --with-openssl-dir=/root/install && make -j $(nproc) && make install
|
||||
cd ../../
|
||||
|
||||
mkdir -p linux_build && cd linux_build && cmake .. -DOPENSSL_ROOT_DIR=/root/install -DCMAKE_BUILD_TYPE=Release && make -j $(nproc)
|
||||
"
|
||||
|
||||
- name: 设置环境变量
|
||||
run: |
|
||||
echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/} | tr -s "/\?%*:|\"<>" "_")" >> $GITHUB_ENV
|
||||
echo "BRANCH2=$(echo ${GITHUB_REF#refs/heads/} )" >> $GITHUB_ENV
|
||||
echo "DATE=$(date +%Y-%m-%d)" >> $GITHUB_ENV
|
||||
|
||||
- name: 打包二进制
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.workflow }}_${{ env.BRANCH }}_${{ env.DATE }}
|
||||
path: release/*
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
|
||||
- name: issue评论
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/feature/test'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: 483,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '- 下载地址: [${{ github.workflow }}_${{ env.BRANCH }}_${{ env.DATE }}](${{ steps.upload.outputs.artifact-url }})\n'
|
||||
+ '- 分支: ${{ env.BRANCH2 }}\n'
|
||||
+ '- git hash: ${{ github.sha }} \n'
|
||||
+ '- 编译日期: ${{ env.DATE }}\n'
|
||||
+ '- 编译记录: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n'
|
||||
+ '- 打包ci名: ${{ github.workflow }}\n'
|
||||
+ '- 开启特性: openssl/webrtc/datachannel\n'
|
||||
+ '- 说明: 本二进制在centos7(x64)上编译,请确保您的机器系统不低于此版本\n'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,27 +13,54 @@ jobs:
|
|||
- name: 下载submodule源码
|
||||
run: mv -f .gitmodules_github .gitmodules && git submodule sync && git submodule update --init
|
||||
|
||||
# - name: 安装brew
|
||||
# run: ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
||||
#
|
||||
# - name: brew安装依赖库(非必选)
|
||||
# run: brew update && brew install cmake openssl sdl2 ffmpeg
|
||||
|
||||
# - name: 下载 SRTP
|
||||
# uses: actions/checkout@v2
|
||||
# with:
|
||||
# repository: cisco/libsrtp
|
||||
# fetch-depth: 1
|
||||
# ref: v2.3.0
|
||||
# path: 3rdpart/libsrtp
|
||||
#
|
||||
# - name: 编译 SRTP
|
||||
# run: cd 3rdpart/libsrtp && ./configure --enable-openssl && make -j4 && sudo make install
|
||||
- name: 配置 vcpkg
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
vcpkgDirectory: '${{github.workspace}}/vcpkg'
|
||||
vcpkgTriplet: arm64-osx
|
||||
# 2024.06.01
|
||||
vcpkgGitCommitId: '47364fbc300756f64f7876b549d9422d5f3ec0d3'
|
||||
vcpkgArguments: 'openssl libsrtp[openssl] usrsctp'
|
||||
|
||||
- name: 编译
|
||||
run: mkdir -p build && cd build && cmake .. && make -j $(nproc)
|
||||
uses: lukka/run-cmake@v3
|
||||
with:
|
||||
useVcpkgToolchainFile: true
|
||||
buildDirectory: '${{github.workspace}}/build'
|
||||
cmakeAppendedArgs: ''
|
||||
cmakeBuildType: 'Release'
|
||||
|
||||
- name: 运行MediaServer
|
||||
run: pwd && cd release/darwin/Debug && sudo ./MediaServer -d &
|
||||
- name: 设置环境变量
|
||||
run: |
|
||||
echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/} | tr -s "/\?%*:|\"<>" "_")" >> $GITHUB_ENV
|
||||
echo "BRANCH2=$(echo ${GITHUB_REF#refs/heads/} )" >> $GITHUB_ENV
|
||||
echo "DATE=$(date +%Y-%m-%d)" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: 打包二进制
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.workflow }}_${{ env.BRANCH }}_${{ env.DATE }}
|
||||
path: release/*
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
|
||||
- name: issue评论
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/feature/test'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: 483,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '- 下载地址: [${{ github.workflow }}_${{ env.BRANCH }}_${{ env.DATE }}](${{ steps.upload.outputs.artifact-url }})\n'
|
||||
+ '- 分支: ${{ env.BRANCH2 }}\n'
|
||||
+ '- git hash: ${{ github.sha }} \n'
|
||||
+ '- 编译日期: ${{ env.DATE }}\n'
|
||||
+ '- 编译记录: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n'
|
||||
+ '- 打包ci名: ${{ github.workflow }}\n'
|
||||
+ '- 开启特性: openssl/webrtc/datachannel\n'
|
||||
+ '- 说明: 此二进制为arm64版本\n'
|
||||
})
|
||||
|
|
@ -4,7 +4,7 @@ on: [pull_request]
|
|||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -17,14 +17,52 @@ jobs:
|
|||
with:
|
||||
vcpkgDirectory: '${{github.workspace}}/vcpkg'
|
||||
vcpkgTriplet: x64-windows-static
|
||||
# 2021.05.12
|
||||
vcpkgGitCommitId: '5568f110b509a9fd90711978a7cb76bae75bb092'
|
||||
vcpkgArguments: 'openssl libsrtp'
|
||||
# 2024.06.01
|
||||
vcpkgGitCommitId: '47364fbc300756f64f7876b549d9422d5f3ec0d3'
|
||||
vcpkgArguments: 'openssl libsrtp[openssl] usrsctp'
|
||||
|
||||
- name: 编译
|
||||
uses: lukka/run-cmake@v3
|
||||
with:
|
||||
useVcpkgToolchainFile: true
|
||||
buildDirectory: '${{github.workspace}}/build'
|
||||
cmakeAppendedArgs: '-DCMAKE_ENABLE_WEBRTC:BOOL=ON'
|
||||
cmakeBuildType: 'RelWithDebInfo'
|
||||
cmakeAppendedArgs: ''
|
||||
cmakeBuildType: 'Release'
|
||||
|
||||
- name: 设置环境变量
|
||||
run: |
|
||||
$dateString = Get-Date -Format "yyyy-MM-dd"
|
||||
$branch = $env:GITHUB_REF -replace "refs/heads/", "" -replace "[\\/\\\?\%\*:\|\x22<>]", "_"
|
||||
$branch2 = $env:GITHUB_REF -replace "refs/heads/", ""
|
||||
echo "BRANCH=$branch" >> $env:GITHUB_ENV
|
||||
echo "BRANCH2=$branch2" >> $env:GITHUB_ENV
|
||||
echo "DATE=$dateString" >> $env:GITHUB_ENV
|
||||
|
||||
- name: 打包二进制
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.workflow }}_${{ env.BRANCH }}_${{ env.DATE }}
|
||||
path: release/*
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
|
||||
- name: issue评论
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/feature/test'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: 483,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '- 下载地址: [${{ github.workflow }}_${{ env.BRANCH }}_${{ env.DATE }}](${{ steps.upload.outputs.artifact-url }})\n'
|
||||
+ '- 分支: ${{ env.BRANCH2 }}\n'
|
||||
+ '- git hash: ${{ github.sha }} \n'
|
||||
+ '- 编译日期: ${{ env.DATE }}\n'
|
||||
+ '- 编译记录: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n'
|
||||
+ '- 打包ci名: ${{ github.workflow }}\n'
|
||||
+ '- 开启特性: openssl/webrtc/datachannel\n'
|
||||
+ '- 说明: 此二进制为x64版本\n'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 1e1a990783c6c09452419c0aaa6d72ce02d0202b
|
||||
Subproject commit fb695d203421d906c473018022a736fa4a7a47e4
|
||||
|
|
@ -13,22 +13,18 @@
|
|||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#ifdef assert
|
||||
#undef assert
|
||||
#endif//assert
|
||||
#ifdef assert
|
||||
#undef assert
|
||||
#endif//assert
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
extern void Assert_Throw(int failed, const char *exp, const char *func, const char *file, int line, const char *str);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
extern void Assert_Throw(int failed, const char *exp, const char *func, const char *file, int line, const char *str);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#define assert(exp) Assert_Throw(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, NULL)
|
||||
#else
|
||||
#define assert(e) ((void)0)
|
||||
#endif//NDEBUG
|
||||
#define assert(exp) Assert_Throw(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, NULL)
|
||||
|
||||
#endif //ZLMEDIAKIT_ASSERT_H
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 527c0f5117b489fda78fcd123d446370ddd9ec9a
|
||||
Subproject commit cf83ebc62e65ae6f3b73bc5ebd06cb0b2da49fa5
|
||||
|
|
@ -448,10 +448,21 @@ endif()
|
|||
|
||||
if(WIN32)
|
||||
update_cached_list(MK_LINK_LIBRARIES WS2_32 Iphlpapi shlwapi)
|
||||
elseif(ANDROID)
|
||||
update_cached_list(MK_LINK_LIBRARIES log)
|
||||
elseif(NOT ANDROID OR IOS)
|
||||
update_cached_list(MK_LINK_LIBRARIES pthread)
|
||||
endif()
|
||||
|
||||
if(ENABLE_VIDEOSTACK)
|
||||
if(ENABLE_FFMPEG AND ENABLE_X264)
|
||||
message(STATUS "ENABLE_VIDEOSTACK defined")
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_VIDEOSTACK)
|
||||
else()
|
||||
message(WARNING "ENABLE_VIDEOSTACK requires ENABLE_FFMPEG and ENABLE_X264")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Solution folders:
|
||||
# ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -159,12 +159,15 @@
|
|||
- 2、作为独立的流媒体服务器使用,不想做c/c++开发的,可以参考 [restful api](https://github.com/ZLMediaKit/ZLMediaKit/wiki/MediaServer支持的HTTP-API) 和 [web hook](https://github.com/ZLMediaKit/ZLMediaKit/wiki/MediaServer支持的HTTP-HOOK-API ).
|
||||
- 3、如果想做c/c++开发,添加业务逻辑增加功能,可以参考这里的[测试程序](https://github.com/ZLMediaKit/ZLMediaKit/tree/master/tests).
|
||||
|
||||
## 二进制文件下载
|
||||
zlmediakit采用 github action 持续集成自动编译打包上传编译产出包,请在[issue列表](https://github.com/ZLMediaKit/ZLMediaKit/issues/483)下载最新sdk库文件以及可执行文件。
|
||||
|
||||
## Docker 镜像
|
||||
|
||||
你可以从Docker Hub下载已经编译好的镜像并启动它:
|
||||
|
||||
```bash
|
||||
#此镜像为github持续集成自动编译推送,跟代码(master分支)保持最新状态
|
||||
#此镜像为github action 持续集成自动编译推送,跟代码(master分支)保持最新状态
|
||||
docker run -id -p 1935:1935 -p 8080:80 -p 8443:443 -p 8554:554 -p 10000:10000 -p 10000:10000/udp -p 8000:8000/udp -p 9000:9000/udp zlmediakit/zlmediakit:master
|
||||
```
|
||||
|
||||
|
|
@ -188,6 +191,7 @@ bash build_docker_images.sh
|
|||
- [jessibuca](https://github.com/langhuihui/jessibuca) 基于wasm支持H265的播放器
|
||||
- [wsPlayer](https://github.com/v354412101/wsPlayer) 基于MSE的websocket-fmp4播放器
|
||||
- [BXC_gb28181Player](https://github.com/any12345com/BXC_gb28181Player) C++开发的支持国标GB28181协议的视频流播放器
|
||||
- [RTCPlayer](https://github.com/leo94666/RTCPlayer) 一个基于Android客户端的的RTC播放器
|
||||
|
||||
- WEB管理网站
|
||||
- [zlm_webassist](https://github.com/1002victor/zlm_webassist) 本项目配套的前后端分离web管理项目
|
||||
|
|
@ -363,6 +367,7 @@ bash build_docker_images.sh
|
|||
[jamesZHANG500](https://github.com/jamesZHANG500)
|
||||
[weidelong](https://github.com/wdl1697454803)
|
||||
[小强先生](https://github.com/linshangqiang)
|
||||
[李之阳](https://github.com/leo94666)
|
||||
|
||||
同时感谢JetBrains对开源项目的支持,本项目使用CLion开发与调试:
|
||||
|
||||
|
|
|
|||
|
|
@ -324,6 +324,10 @@ git submodule update --init
|
|||
});
|
||||
|
||||
```
|
||||
|
||||
## Binary file download
|
||||
zlmediakit uses github action to continuously integrate automatic compilation package and upload the compilation output package. Please download the latest sdk library file and executable file at [issue list] (https://github.com/ZLMediaKit/ZLMediaKit/issues/483).
|
||||
|
||||
## Docker Image
|
||||
|
||||
You can download the pre-compiled image from Docker Hub and start it:
|
||||
|
|
@ -369,6 +373,8 @@ bash build_docker_images.sh
|
|||
- [WebSocket-fmp4 player based on MSE](https://github.com/v354412101/wsPlayer)
|
||||
- [Domestic webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC)
|
||||
- [GB28181 player implemented in C++](https://github.com/any12345com/BXC_gb28181Player)
|
||||
- [Android RTCPlayer](https://github.com/leo94666/RTCPlayer)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
|
|
@ -521,6 +527,7 @@ Thanks to all those who have supported this project in various ways, including b
|
|||
[jamesZHANG500](https://github.com/jamesZHANG500)
|
||||
[weidelong](https://github.com/wdl1697454803)
|
||||
[小强先生](https://github.com/linshangqiang)
|
||||
[李之阳](https://github.com/leo94666)
|
||||
|
||||
Also thank to JetBrains for their support for open source project, we developed and debugged zlmediakit with CLion:
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ API_EXPORT const char *API_CALL mk_record_info_get_stream(const mk_record_info c
|
|||
///////////////////////////////////////////Parser/////////////////////////////////////////////
|
||||
//Parser对象的C映射
|
||||
typedef struct mk_parser_t *mk_parser;
|
||||
//Parser对象中Headers foreach回调
|
||||
typedef void(API_CALL *on_mk_parser_header_cb)(void *user_data, const char *key, const char *val);
|
||||
//Parser::Method(),获取命令字,譬如GET/POST
|
||||
API_EXPORT const char* API_CALL mk_parser_get_method(const mk_parser ctx);
|
||||
//Parser::Url(),获取HTTP的访问url(不包括?后面的参数)
|
||||
|
|
@ -72,6 +74,8 @@ API_EXPORT const char* API_CALL mk_parser_get_tail(const mk_parser ctx);
|
|||
API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key);
|
||||
//Parser::Content(),获取HTTP body
|
||||
API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_t *length);
|
||||
//循环获取所有header
|
||||
API_EXPORT void API_CALL mk_parser_headers_for_each(const mk_parser ctx, on_mk_parser_header_cb cb, void *user_data);
|
||||
|
||||
///////////////////////////////////////////MediaInfo/////////////////////////////////////////////
|
||||
//MediaInfo对象的C映射
|
||||
|
|
@ -114,19 +118,24 @@ API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_so
|
|||
API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx);
|
||||
// copy track reference by index from MediaSource, please use mk_track_unref to release it
|
||||
API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index);
|
||||
// MediaSource::Track:loss
|
||||
API_EXPORT float API_CALL mk_media_source_get_track_loss(const mk_media_source ctx, const mk_track track);
|
||||
// MediaSource::broadcastMessage
|
||||
API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len);
|
||||
// MediaSource::getOriginUrl()
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_origin_url(const mk_media_source ctx);
|
||||
// MediaSource::getOriginType()
|
||||
API_EXPORT int API_CALL mk_media_source_get_origin_type(const mk_media_source ctx);
|
||||
// MediaSource::getOriginTypeStr(), 使用后请用mk_free释放返回值
|
||||
API_EXPORT const char *API_CALL mk_media_source_get_origin_type_str(const mk_media_source ctx);
|
||||
// MediaSource::getCreateStamp()
|
||||
API_EXPORT uint64_t API_CALL mk_media_source_get_create_stamp(const mk_media_source ctx);
|
||||
// MediaSource::isRecording() 0:hls,1:MP4
|
||||
API_EXPORT int API_CALL mk_media_source_is_recording(const mk_media_source ctx, int type);
|
||||
|
||||
|
||||
|
||||
// MediaSource::getBytesSpeed()
|
||||
API_EXPORT int API_CALL mk_media_source_get_bytes_speed(const mk_media_source ctx);
|
||||
// MediaSource::getAliveSecond()
|
||||
API_EXPORT uint64_t API_CALL mk_media_source_get_alive_second(const mk_media_source ctx);
|
||||
/**
|
||||
* 直播源在ZLMediaKit中被称作为MediaSource,
|
||||
* 目前支持3种,分别是RtmpMediaSource、RtspMediaSource、HlsMediaSource
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ API_EXPORT void API_CALL mk_proxy_player_release(mk_proxy_player ctx);
|
|||
/**
|
||||
* 设置代理播放器配置选项
|
||||
* @param ctx 代理播放器指针
|
||||
* @param key 配置项键,支持 net_adapter/rtp_type/rtsp_user/rtsp_pwd/protocol_timeout_ms/media_timeout_ms/beat_interval_ms
|
||||
* @param key 配置项键,支持 net_adapter/rtp_type/rtsp_user/rtsp_pwd/protocol_timeout_ms/media_timeout_ms/beat_interval_ms/rtsp_speed
|
||||
* @param val 配置项值,如果是整形,需要转换成统一转换成string
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_proxy_player_set_option(mk_proxy_player ctx, const char *key, const char *val);
|
||||
|
|
@ -98,7 +98,9 @@ API_EXPORT void API_CALL mk_proxy_player_play(mk_proxy_player ctx, const char *u
|
|||
* 如果你不调用mk_proxy_player_release函数,那么MediaSource.close()操作将无效
|
||||
* @param user_data 用户数据指针,通过mk_proxy_player_set_on_close函数设置
|
||||
*/
|
||||
typedef void(API_CALL *on_mk_proxy_player_close)(void *user_data, int err, const char *what, int sys_err);
|
||||
typedef void(API_CALL *on_mk_proxy_player_cb)(void *user_data, int err, const char *what, int sys_err);
|
||||
// 保持兼容
|
||||
#define on_mk_proxy_player_close on_mk_proxy_player_cb
|
||||
|
||||
/**
|
||||
* 监听MediaSource.close()事件
|
||||
|
|
@ -108,8 +110,17 @@ typedef void(API_CALL *on_mk_proxy_player_close)(void *user_data, int err, const
|
|||
* @param cb 回调指针
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_proxy_player_set_on_close(mk_proxy_player ctx, on_mk_proxy_player_close cb, void *user_data);
|
||||
API_EXPORT void API_CALL mk_proxy_player_set_on_close2(mk_proxy_player ctx, on_mk_proxy_player_close cb, void *user_data, on_user_data_free user_data_free);
|
||||
API_EXPORT void API_CALL mk_proxy_player_set_on_close(mk_proxy_player ctx, on_mk_proxy_player_cb cb, void *user_data);
|
||||
API_EXPORT void API_CALL mk_proxy_player_set_on_close2(mk_proxy_player ctx, on_mk_proxy_player_cb cb, void *user_data, on_user_data_free user_data_free);
|
||||
|
||||
/**
|
||||
* 设置代理第一次播放结果回调,如果第一次播放失败,可以认作是启动失败
|
||||
* @param ctx 对象指针
|
||||
* @param cb 回调指针
|
||||
* @param user_data 用户数据指针
|
||||
* @param user_data_free 用户数据释放回调
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_proxy_player_set_on_play_result(mk_proxy_player ctx, on_mk_proxy_player_cb cb, void *user_data, on_user_data_free user_data_free);
|
||||
|
||||
/**
|
||||
* 获取总的观看人数
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ typedef struct mk_rtp_server_t *mk_rtp_server;
|
|||
* @return
|
||||
*/
|
||||
API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int tcp_mode, const char *stream_id);
|
||||
API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create2(uint16_t port, int tcp_mode, const char *vhost, const char *app, const char *stream_id);
|
||||
|
||||
/**
|
||||
* TCP 主动模式时连接到服务器是否成功的回调
|
||||
|
|
|
|||
|
|
@ -73,6 +73,21 @@ API_EXPORT const char* API_CALL mk_track_codec_name(mk_track track);
|
|||
*/
|
||||
API_EXPORT int API_CALL mk_track_bit_rate(mk_track track);
|
||||
|
||||
/**
|
||||
* 获取轨道是否已就绪,1: 已就绪,0:未就绪
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_track_ready(mk_track track);
|
||||
|
||||
/**
|
||||
* 获取累计帧数
|
||||
*/
|
||||
API_EXPORT uint64_t API_CALL mk_track_frames(mk_track track);
|
||||
|
||||
/**
|
||||
* 获取时间,单位毫秒
|
||||
*/
|
||||
API_EXPORT uint64_t API_CALL mk_track_duration(mk_track track);
|
||||
|
||||
/**
|
||||
* 监听frame输出事件
|
||||
* @param track track对象
|
||||
|
|
@ -114,6 +129,21 @@ API_EXPORT int API_CALL mk_track_video_height(mk_track track);
|
|||
*/
|
||||
API_EXPORT int API_CALL mk_track_video_fps(mk_track track);
|
||||
|
||||
/**
|
||||
* 获取视频累计关键帧数
|
||||
*/
|
||||
API_EXPORT uint64_t API_CALL mk_track_video_key_frames(mk_track track);
|
||||
|
||||
/**
|
||||
* 获取视频GOP关键帧间隔
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_track_video_gop_size(mk_track track);
|
||||
|
||||
/**
|
||||
* 获取视频累计关键帧间隔(毫秒)
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_track_video_gop_interval_ms(mk_track track);
|
||||
|
||||
/**
|
||||
* 获取音频采样率
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -125,6 +125,13 @@ API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_
|
|||
}
|
||||
return parser->content().c_str();
|
||||
}
|
||||
API_EXPORT void API_CALL mk_parser_headers_for_each(const mk_parser ctx, on_mk_parser_header_cb cb, void *user_data){
|
||||
assert(ctx && cb);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
for (auto it = parser->getHeader().begin(); it != parser->getHeader().end(); ++it) {
|
||||
cb(user_data, it->first.c_str(), it->second.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////MediaInfo/////////////////////////////////////////////
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_params(const mk_media_info ctx){
|
||||
|
|
@ -218,6 +225,13 @@ API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx
|
|||
return (mk_track) new Track::Ptr(std::move(tracks[index]));
|
||||
}
|
||||
|
||||
API_EXPORT float API_CALL mk_media_source_get_track_loss(const mk_media_source ctx, const mk_track track) {
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
// rtp推流只有一个统计器,但是可能有多个track,如果短时间多次获取间隔丢包率,第二次会获取为-1
|
||||
return src->getLossRate((*((Track::Ptr *)track))->getTrackType());
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len) {
|
||||
assert(ctx && msg && len);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
|
|
@ -240,6 +254,12 @@ API_EXPORT int API_CALL mk_media_source_get_origin_type(const mk_media_source c
|
|||
return static_cast<int>(src->getOriginType());
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_origin_type_str(const mk_media_source ctx) {
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return _strdup(getOriginTypeString(src->getOriginType()).c_str());
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_media_source_get_create_stamp(const mk_media_source ctx) {
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
|
|
@ -252,6 +272,19 @@ API_EXPORT int API_CALL mk_media_source_is_recording(const mk_media_source ctx,i
|
|||
return src->isRecording((Recorder::type)type);
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_media_source_get_bytes_speed(const mk_media_source ctx) {
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getBytesSpeed();
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_media_source_get_alive_second(const mk_media_source ctx) {
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getAliveSecond();
|
||||
}
|
||||
|
||||
|
||||
API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,20 @@ API_EXPORT void API_CALL mk_proxy_player_set_on_close2(mk_proxy_player ctx, on_m
|
|||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_proxy_player_set_on_play_result(mk_proxy_player ctx, on_mk_proxy_player_close cb, void *user_data, on_user_data_free user_data_free) {
|
||||
assert(ctx);
|
||||
PlayerProxy::Ptr &obj = *((PlayerProxy::Ptr *)ctx);
|
||||
std::shared_ptr<void> ptr(user_data, user_data_free ? user_data_free : [](void *) {});
|
||||
obj->getPoller()->async([obj, cb, ptr]() {
|
||||
// 切换线程再操作
|
||||
obj->setPlayCallbackOnce([cb, ptr](const SockException &ex) {
|
||||
if (cb) {
|
||||
cb(ptr.get(), ex.getErrCode(), ex.what(), ex.getCustomCode());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_proxy_player_total_reader_count(mk_proxy_player ctx){
|
||||
assert(ctx);
|
||||
PlayerProxy::Ptr &obj = *((PlayerProxy::Ptr *) ctx);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,13 @@ using namespace mediakit;
|
|||
|
||||
API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int tcp_mode, const char *stream_id) {
|
||||
RtpServer::Ptr *server = new RtpServer::Ptr(new RtpServer);
|
||||
(*server)->start(port, stream_id, (RtpServer::TcpMode)tcp_mode);
|
||||
(*server)->start(port, MediaTuple { DEFAULT_VHOST, kRtpAppName, stream_id, "" }, (RtpServer::TcpMode)tcp_mode);
|
||||
return (mk_rtp_server)server;
|
||||
}
|
||||
|
||||
API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create2(uint16_t port, int tcp_mode, const char *vhost, const char *app, const char *stream_id) {
|
||||
RtpServer::Ptr *server = new RtpServer::Ptr(new RtpServer);
|
||||
(*server)->start(port, MediaTuple { vhost, app, stream_id, "" }, (RtpServer::TcpMode)tcp_mode);
|
||||
return (mk_rtp_server)server;
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +62,7 @@ API_EXPORT void API_CALL mk_rtp_server_set_on_detach2(mk_rtp_server ctx, on_mk_r
|
|||
RtpServer::Ptr *server = (RtpServer::Ptr *) ctx;
|
||||
if (cb) {
|
||||
std::shared_ptr<void> ptr(user_data, user_data_free ? user_data_free : [](void *) {});
|
||||
(*server)->setOnDetach([cb, ptr]() {
|
||||
(*server)->setOnDetach([cb, ptr](const SockException &ex) {
|
||||
cb(ptr.get());
|
||||
});
|
||||
} else {
|
||||
|
|
@ -71,6 +77,11 @@ API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int enable
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create2(uint16_t port, int tcp_mode, const char *vhost, const char *app, const char *stream_id) {
|
||||
WarnL << "请打开ENABLE_RTPPROXY后再编译";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_rtp_server_release(mk_rtp_server ctx) {
|
||||
WarnL << "请打开ENABLE_RTPPROXY后再编译";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,21 @@ API_EXPORT int API_CALL mk_track_bit_rate(mk_track track) {
|
|||
return (*((Track::Ptr *) track))->getBitRate();
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_track_ready(mk_track track) {
|
||||
assert(track);
|
||||
return (*((Track::Ptr *)track))->ready();
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_track_frames(mk_track track) {
|
||||
assert(track);
|
||||
return (*((Track::Ptr *)track))->getFrames();
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_track_duration(mk_track track) {
|
||||
assert(track);
|
||||
return (*((Track::Ptr *)track))->getDuration();
|
||||
}
|
||||
|
||||
API_EXPORT void *API_CALL mk_track_add_delegate(mk_track track, on_mk_frame_out cb, void *user_data) {
|
||||
return mk_track_add_delegate2(track, cb, user_data, nullptr);
|
||||
}
|
||||
|
|
@ -167,6 +182,36 @@ API_EXPORT int API_CALL mk_track_video_fps(mk_track track) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_track_video_key_frames(mk_track track) {
|
||||
assert(track);
|
||||
auto video = dynamic_pointer_cast<VideoTrack>((*((Track::Ptr *)track)));
|
||||
if (video) {
|
||||
return video->getVideoFps();
|
||||
}
|
||||
WarnL << "not video track";
|
||||
return 0;
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_track_video_gop_size(mk_track track) {
|
||||
assert(track);
|
||||
auto video = dynamic_pointer_cast<VideoTrack>((*((Track::Ptr *)track)));
|
||||
if (video) {
|
||||
return video->getVideoGopSize();
|
||||
}
|
||||
WarnL << "not video track";
|
||||
return 0;
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_track_video_gop_interval_ms(mk_track track) {
|
||||
assert(track);
|
||||
auto video = dynamic_pointer_cast<VideoTrack>((*((Track::Ptr *)track)));
|
||||
if (video) {
|
||||
return video->getVideoGopInterval();
|
||||
}
|
||||
WarnL << "not video track";
|
||||
return 0;
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_track_audio_sample_rate(mk_track track) {
|
||||
assert(track);
|
||||
auto audio = dynamic_pointer_cast<AudioTrack>((*((Track::Ptr *) track)));
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ WORKDIR /opt/media/ZLMediaKit
|
|||
|
||||
# 3rdpart init
|
||||
WORKDIR /opt/media/ZLMediaKit/3rdpart
|
||||
RUN wget https://mirror.ghproxy.com/https://github.com/cisco/libsrtp/archive/v2.3.0.tar.gz -O libsrtp-2.3.0.tar.gz && \
|
||||
RUN wget https://github.com/cisco/libsrtp/archive/v2.3.0.tar.gz -O libsrtp-2.3.0.tar.gz && \
|
||||
tar xfv libsrtp-2.3.0.tar.gz && \
|
||||
mv libsrtp-2.3.0 libsrtp && \
|
||||
cd libsrtp && ./configure --enable-openssl && make -j $(nproc) && make install
|
||||
|
|
|
|||
|
|
@ -55,62 +55,61 @@ void AACRtpDecoder::obtainFrame() {
|
|||
bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) {
|
||||
auto payload_size = rtp->getPayloadSize();
|
||||
if (payload_size <= 0) {
|
||||
//无实际负载
|
||||
// 无实际负载
|
||||
return false;
|
||||
}
|
||||
|
||||
auto stamp = rtp->getStampMS();
|
||||
//rtp数据开始部分
|
||||
// rtp数据开始部分
|
||||
auto ptr = rtp->getPayload();
|
||||
//rtp数据末尾
|
||||
// rtp数据末尾
|
||||
auto end = ptr + payload_size;
|
||||
//首2字节表示Au-Header的个数,单位bit,所以除以16得到Au-Header个数
|
||||
// 首2字节表示Au-Header的个数,单位bit,所以除以16得到Au-Header个数
|
||||
auto au_header_count = ((ptr[0] << 8) | ptr[1]) >> 4;
|
||||
if (!au_header_count) {
|
||||
//问题issue: https://github.com/ZLMediaKit/ZLMediaKit/issues/1869
|
||||
// 问题issue: https://github.com/ZLMediaKit/ZLMediaKit/issues/1869
|
||||
WarnL << "invalid aac rtp au_header_count";
|
||||
return false;
|
||||
}
|
||||
//记录au_header起始指针
|
||||
// 记录au_header起始指针
|
||||
auto au_header_ptr = ptr + 2;
|
||||
ptr = au_header_ptr + au_header_count * 2;
|
||||
ptr = au_header_ptr + au_header_count * 2;
|
||||
|
||||
if (end < ptr) {
|
||||
//数据不够
|
||||
// 数据不够
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_last_dts) {
|
||||
//记录第一个时间戳
|
||||
// 记录第一个时间戳
|
||||
_last_dts = stamp;
|
||||
}
|
||||
|
||||
//每个audio unit时间戳增量
|
||||
// 每个audio unit时间戳增量
|
||||
auto dts_inc = (stamp - _last_dts) / au_header_count;
|
||||
if (dts_inc < 0 && dts_inc > 100) {
|
||||
//时间戳增量异常,忽略
|
||||
if (dts_inc < 0 || dts_inc > 100) {
|
||||
// 时间戳增量异常,忽略
|
||||
dts_inc = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < au_header_count; ++i) {
|
||||
for (auto i = 0u; i < (size_t)au_header_count; ++i) {
|
||||
// 之后的2字节是AU_HEADER,其中高13位表示一帧AAC负载的字节长度,低3位无用
|
||||
uint16_t size = ((au_header_ptr[0] << 8) | au_header_ptr[1]) >> 3;
|
||||
if (ptr + size > end) {
|
||||
//数据不够
|
||||
auto size = ((au_header_ptr[0] << 8) | au_header_ptr[1]) >> 3;
|
||||
auto len = std::min<int>(size, end - ptr);
|
||||
if (len <= 0) {
|
||||
break;
|
||||
}
|
||||
_frame->_buffer.append((char *)ptr, len);
|
||||
ptr += len;
|
||||
au_header_ptr += 2;
|
||||
|
||||
if (size) {
|
||||
//设置aac数据
|
||||
_frame->_buffer.assign((char *) ptr, size);
|
||||
//设置当前audio unit时间戳
|
||||
if (_frame->size() >= (size_t)size) {
|
||||
// 设置当前audio unit时间戳
|
||||
_frame->_dts = _last_dts + i * dts_inc;
|
||||
ptr += size;
|
||||
au_header_ptr += 2;
|
||||
flushData();
|
||||
}
|
||||
}
|
||||
//记录上次时间戳
|
||||
// 记录上次时间戳
|
||||
_last_dts = stamp;
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ bool G711RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
|||
auto remain_size = len;
|
||||
size_t max_size = 160 * _channels * _pkt_dur_ms / 20; // 20 ms per 160 byte
|
||||
size_t n = 0;
|
||||
bool mark = true;
|
||||
bool mark = false;
|
||||
while (remain_size >= max_size) {
|
||||
assert(remain_size >= max_size);
|
||||
const size_t rtp_size = max_size;
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ size_t prefixSize(const char *ptr, size_t len) {
|
|||
H264Track::H264Track(const string &sps, const string &pps, int sps_prefix_len, int pps_prefix_len) {
|
||||
_sps = sps.substr(sps_prefix_len);
|
||||
_pps = pps.substr(pps_prefix_len);
|
||||
update();
|
||||
H264Track::update();
|
||||
}
|
||||
|
||||
CodecId H264Track::getCodecId() const {
|
||||
|
|
@ -238,6 +238,14 @@ bool H264Track::update() {
|
|||
return getAVCInfo(_sps, _width, _height, _fps);
|
||||
}
|
||||
|
||||
std::vector<Frame::Ptr> H264Track::getConfigFrames() const {
|
||||
if (!ready()) {
|
||||
return {};
|
||||
}
|
||||
return { createConfigFrame<H264Frame>(_sps, 0, getIndex()),
|
||||
createConfigFrame<H264Frame>(_pps, 0, getIndex()) };
|
||||
}
|
||||
|
||||
Track::Ptr H264Track::clone() const {
|
||||
return std::make_shared<H264Track>(*this);
|
||||
}
|
||||
|
|
@ -284,23 +292,11 @@ bool H264Track::inputFrame_l(const Frame::Ptr &frame) {
|
|||
|
||||
void H264Track::insertConfigFrame(const Frame::Ptr &frame) {
|
||||
if (!_sps.empty()) {
|
||||
auto spsFrame = FrameImp::create<H264Frame>();
|
||||
spsFrame->_prefix_size = 4;
|
||||
spsFrame->_buffer.assign("\x00\x00\x00\x01", 4);
|
||||
spsFrame->_buffer.append(_sps);
|
||||
spsFrame->_dts = frame->dts();
|
||||
spsFrame->setIndex(frame->getIndex());
|
||||
VideoTrack::inputFrame(spsFrame);
|
||||
VideoTrack::inputFrame(createConfigFrame<H264Frame>(_sps, frame->dts(), frame->getIndex()));
|
||||
}
|
||||
|
||||
if (!_pps.empty()) {
|
||||
auto ppsFrame = FrameImp::create<H264Frame>();
|
||||
ppsFrame->_prefix_size = 4;
|
||||
ppsFrame->_buffer.assign("\x00\x00\x00\x01", 4);
|
||||
ppsFrame->_buffer.append(_pps);
|
||||
ppsFrame->_dts = frame->dts();
|
||||
ppsFrame->setIndex(frame->getIndex());
|
||||
VideoTrack::inputFrame(ppsFrame);
|
||||
VideoTrack::inputFrame(createConfigFrame<H264Frame>(_pps, frame->dts(), frame->getIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ public:
|
|||
toolkit::Buffer::Ptr getExtraData() const override;
|
||||
void setExtraData(const uint8_t *data, size_t size) override;
|
||||
bool update() override;
|
||||
std::vector<Frame::Ptr> getConfigFrames() const override;
|
||||
|
||||
private:
|
||||
Sdp::Ptr getSdp(uint8_t payload_type) const override;
|
||||
|
|
@ -131,5 +132,17 @@ private:
|
|||
std::string _pps;
|
||||
};
|
||||
|
||||
template <typename FrameType>
|
||||
Frame::Ptr createConfigFrame(const std::string &data, uint64_t dts, int index) {
|
||||
auto frame = FrameImp::create<FrameType>();
|
||||
frame->_prefix_size = 4;
|
||||
frame->_buffer.assign("\x00\x00\x00\x01", 4);
|
||||
frame->_buffer.append(data);
|
||||
frame->_dts = dts;
|
||||
frame->setIndex(index);
|
||||
return frame;
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_H264_H
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ H265Track::H265Track(const string &vps,const string &sps, const string &pps,int
|
|||
_vps = vps.substr(vps_prefix_len);
|
||||
_sps = sps.substr(sps_prefix_len);
|
||||
_pps = pps.substr(pps_prefix_len);
|
||||
update();
|
||||
H265Track::update();
|
||||
}
|
||||
|
||||
CodecId H265Track::getCodecId() const {
|
||||
|
|
@ -185,6 +185,15 @@ bool H265Track::update() {
|
|||
return getHEVCInfo(_vps, _sps, _width, _height, _fps);
|
||||
}
|
||||
|
||||
std::vector<Frame::Ptr> H265Track::getConfigFrames() const {
|
||||
if (!ready()) {
|
||||
return {};
|
||||
}
|
||||
return { createConfigFrame<H265Frame>(_vps, 0, getIndex()),
|
||||
createConfigFrame<H265Frame>(_sps, 0, getIndex()),
|
||||
createConfigFrame<H265Frame>(_pps, 0, getIndex()) };
|
||||
}
|
||||
|
||||
Track::Ptr H265Track::clone() const {
|
||||
return std::make_shared<H265Track>(*this);
|
||||
}
|
||||
|
|
@ -194,32 +203,13 @@ void H265Track::insertConfigFrame(const Frame::Ptr &frame) {
|
|||
return;
|
||||
}
|
||||
if (!_vps.empty()) {
|
||||
auto vpsFrame = FrameImp::create<H265Frame>();
|
||||
vpsFrame->_prefix_size = 4;
|
||||
vpsFrame->_buffer.assign("\x00\x00\x00\x01", 4);
|
||||
vpsFrame->_buffer.append(_vps);
|
||||
vpsFrame->_dts = frame->dts();
|
||||
vpsFrame->setIndex(frame->getIndex());
|
||||
VideoTrack::inputFrame(vpsFrame);
|
||||
VideoTrack::inputFrame(createConfigFrame<H265Frame>(_vps, frame->dts(), frame->getIndex()));
|
||||
}
|
||||
if (!_sps.empty()) {
|
||||
auto spsFrame = FrameImp::create<H265Frame>();
|
||||
spsFrame->_prefix_size = 4;
|
||||
spsFrame->_buffer.assign("\x00\x00\x00\x01", 4);
|
||||
spsFrame->_buffer.append(_sps);
|
||||
spsFrame->_dts = frame->dts();
|
||||
spsFrame->setIndex(frame->getIndex());
|
||||
VideoTrack::inputFrame(spsFrame);
|
||||
VideoTrack::inputFrame(createConfigFrame<H265Frame>(_sps, frame->dts(), frame->getIndex()));
|
||||
}
|
||||
|
||||
if (!_pps.empty()) {
|
||||
auto ppsFrame = FrameImp::create<H265Frame>();
|
||||
ppsFrame->_prefix_size = 4;
|
||||
ppsFrame->_buffer.assign("\x00\x00\x00\x01", 4);
|
||||
ppsFrame->_buffer.append(_pps);
|
||||
ppsFrame->_dts = frame->dts();
|
||||
ppsFrame->setIndex(frame->getIndex());
|
||||
VideoTrack::inputFrame(ppsFrame);
|
||||
VideoTrack::inputFrame(createConfigFrame<H265Frame>(_pps, frame->dts(), frame->getIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ public:
|
|||
toolkit::Buffer::Ptr getExtraData() const override;
|
||||
void setExtraData(const uint8_t *data, size_t size) override;
|
||||
bool update() override;
|
||||
std::vector<Frame::Ptr> getConfigFrames() const override;
|
||||
|
||||
private:
|
||||
Sdp::Ptr getSdp(uint8_t payload_type) const override;
|
||||
|
|
|
|||
|
|
@ -306,13 +306,12 @@ static int getBits(void *pvHandle, int iN)
|
|||
uint8_t u8Nbyte;
|
||||
uint8_t u8Shift;
|
||||
uint32_t u32Result = 0;
|
||||
int iRet = 0;
|
||||
uint32_t iRet = 0;
|
||||
int iResoLen = 0;
|
||||
|
||||
if(NULL == ptPtr)
|
||||
{
|
||||
RPT(RPT_ERR, "NULL pointer");
|
||||
iRet = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
|
@ -324,7 +323,6 @@ static int getBits(void *pvHandle, int iN)
|
|||
iResoLen = getBitsLeft(ptPtr);
|
||||
if(iResoLen < iN)
|
||||
{
|
||||
iRet = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1476,6 +1476,16 @@
|
|||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "rtp",
|
||||
"description": "应用名,例如 rtp"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
"value": "test",
|
||||
|
|
@ -1517,6 +1527,16 @@
|
|||
"value": "1",
|
||||
"description": "tcp模式,0时为不启用tcp监听,1时为启用tcp监听,2时为tcp主动连接模式"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "rtp",
|
||||
"description": "应用名,例如 rtp"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
"value": "test",
|
||||
|
|
@ -1582,6 +1602,16 @@
|
|||
"value": "1",
|
||||
"description": "tcp模式,0时为不启用tcp监听,1时为启用tcp监听"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "rtp",
|
||||
"description": "应用名,例如 rtp"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
"value": "test",
|
||||
|
|
@ -1635,6 +1665,16 @@
|
|||
"value": "1",
|
||||
"description": "tcp主动模式时服务端端口"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "rtp",
|
||||
"description": "应用名,例如 rtp"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
"value": "test",
|
||||
|
|
@ -1666,6 +1706,16 @@
|
|||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "rtp",
|
||||
"description": "应用名,例如 rtp"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
"value": "test",
|
||||
|
|
@ -1697,6 +1747,16 @@
|
|||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "rtp",
|
||||
"description": "应用名,例如 rtp"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
"value": "test",
|
||||
|
|
@ -1733,6 +1793,16 @@
|
|||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "rtp",
|
||||
"description": "应用名,例如 rtp"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
"value": "test",
|
||||
|
|
@ -1764,6 +1834,16 @@
|
|||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "rtp",
|
||||
"description": "应用名,例如 rtp"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
"value": "test",
|
||||
|
|
|
|||
|
|
@ -18,23 +18,15 @@
|
|||
|
||||
// ITU-R BT.709
|
||||
#define RGB_TO_Y(R, G, B) (((47 * (R) + 157 * (G) + 16 * (B) + 128) >> 8) + 16)
|
||||
#define RGB_TO_U(R, G, B) (((-26 * (R)-87 * (G) + 112 * (B) + 128) >> 8) + 128)
|
||||
#define RGB_TO_V(R, G, B) (((112 * (R)-102 * (G)-10 * (B) + 128) >> 8) + 128)
|
||||
#define RGB_TO_U(R, G, B) (((-26 * (R) - 87 * (G) + 112 * (B) + 128) >> 8) + 128)
|
||||
#define RGB_TO_V(R, G, B) (((112 * (R) - 102 * (G) - 10 * (B) + 128) >> 8) + 128)
|
||||
|
||||
INSTANCE_IMP(VideoStackManager)
|
||||
|
||||
Param::~Param()
|
||||
{
|
||||
VideoStackManager::Instance().unrefChannel(
|
||||
id, width, height, pixfmt);
|
||||
}
|
||||
Param::~Param() { VideoStackManager::Instance().unrefChannel(id, width, height, pixfmt); }
|
||||
|
||||
Channel::Channel(const std::string& id, int width, int height, AVPixelFormat pixfmt)
|
||||
: _id(id)
|
||||
, _width(width)
|
||||
, _height(height)
|
||||
, _pixfmt(pixfmt)
|
||||
{
|
||||
: _id(id), _width(width), _height(height), _pixfmt(pixfmt) {
|
||||
_tmp = std::make_shared<mediakit::FFmpegFrame>();
|
||||
|
||||
_tmp->get()->width = _width;
|
||||
|
|
@ -53,88 +45,72 @@ Channel::Channel(const std::string& id, int width, int height, AVPixelFormat pix
|
|||
_tmp = _sws->inputFrame(frame);
|
||||
}
|
||||
|
||||
void Channel::addParam(const std::weak_ptr<Param>& p)
|
||||
{
|
||||
void Channel::addParam(const std::weak_ptr<Param>& p) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
_params.push_back(p);
|
||||
}
|
||||
|
||||
void Channel::onFrame(const mediakit::FFmpegFrame::Ptr& frame)
|
||||
{
|
||||
void Channel::onFrame(const mediakit::FFmpegFrame::Ptr& frame) {
|
||||
std::weak_ptr<Channel> weakSelf = shared_from_this();
|
||||
_poller = _poller ? _poller : toolkit::WorkThreadPool::Instance().getPoller();
|
||||
_poller->async([weakSelf, frame]() {
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
if (!self) { return; }
|
||||
self->_tmp = self->_sws->inputFrame(frame);
|
||||
|
||||
self->forEachParam([self](const Param::Ptr& p) { self->fillBuffer(p); });
|
||||
});
|
||||
}
|
||||
|
||||
void Channel::forEachParam(const std::function<void(const Param::Ptr&)>& func)
|
||||
{
|
||||
void Channel::forEachParam(const std::function<void(const Param::Ptr&)>& func) {
|
||||
for (auto& wp : _params) {
|
||||
if (auto sp = wp.lock()) {
|
||||
func(sp);
|
||||
}
|
||||
if (auto sp = wp.lock()) { func(sp); }
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::fillBuffer(const Param::Ptr& p)
|
||||
{
|
||||
if (auto buf = p->weak_buf.lock()) {
|
||||
copyData(buf, p);
|
||||
}
|
||||
void Channel::fillBuffer(const Param::Ptr& p) {
|
||||
if (auto buf = p->weak_buf.lock()) { copyData(buf, p); }
|
||||
}
|
||||
|
||||
void Channel::copyData(const mediakit::FFmpegFrame::Ptr& buf, const Param::Ptr& p)
|
||||
{
|
||||
void Channel::copyData(const mediakit::FFmpegFrame::Ptr& buf, const Param::Ptr& p) {
|
||||
|
||||
switch (p->pixfmt) {
|
||||
case AV_PIX_FMT_YUV420P: {
|
||||
for (int i = 0; i < p->height; i++) {
|
||||
memcpy(buf->get()->data[0] + buf->get()->linesize[0] * (i + p->posY) + p->posX,
|
||||
_tmp->get()->data[0] + _tmp->get()->linesize[0] * i,
|
||||
_tmp->get()->width);
|
||||
}
|
||||
//确保height为奇数时,也能正确的复制到最后一行uv数据
|
||||
for (int i = 0; i < (p->height + 1) / 2; i++) {
|
||||
// U平面
|
||||
memcpy(buf->get()->data[1] + buf->get()->linesize[1] * (i + p->posY / 2) + p->posX / 2,
|
||||
_tmp->get()->data[1] + _tmp->get()->linesize[1] * i,
|
||||
_tmp->get()->width / 2);
|
||||
case AV_PIX_FMT_YUV420P: {
|
||||
for (int i = 0; i < p->height; i++) {
|
||||
memcpy(buf->get()->data[0] + buf->get()->linesize[0] * (i + p->posY) + p->posX,
|
||||
_tmp->get()->data[0] + _tmp->get()->linesize[0] * i, _tmp->get()->width);
|
||||
}
|
||||
// 确保height为奇数时,也能正确的复制到最后一行uv数据
|
||||
for (int i = 0; i < (p->height + 1) / 2; i++) {
|
||||
// U平面
|
||||
memcpy(buf->get()->data[1] + buf->get()->linesize[1] * (i + p->posY / 2) +
|
||||
p->posX / 2,
|
||||
_tmp->get()->data[1] + _tmp->get()->linesize[1] * i, _tmp->get()->width / 2);
|
||||
|
||||
// V平面
|
||||
memcpy(buf->get()->data[2] + buf->get()->linesize[2] * (i + p->posY / 2) + p->posX / 2,
|
||||
_tmp->get()->data[2] + _tmp->get()->linesize[2] * i,
|
||||
_tmp->get()->width / 2);
|
||||
// V平面
|
||||
memcpy(buf->get()->data[2] + buf->get()->linesize[2] * (i + p->posY / 2) +
|
||||
p->posX / 2,
|
||||
_tmp->get()->data[2] + _tmp->get()->linesize[2] * i, _tmp->get()->width / 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_NV12: {
|
||||
// TODO: 待实现
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_NV12: {
|
||||
//TODO: 待实现
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
WarnL << "No support pixformat: " << av_get_pix_fmt_name(p->pixfmt);
|
||||
break;
|
||||
default: WarnL << "No support pixformat: " << av_get_pix_fmt_name(p->pixfmt); break;
|
||||
}
|
||||
}
|
||||
void StackPlayer::addChannel(const std::weak_ptr<Channel>& chn)
|
||||
{
|
||||
void StackPlayer::addChannel(const std::weak_ptr<Channel>& chn) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
_channels.push_back(chn);
|
||||
}
|
||||
|
||||
void StackPlayer::play()
|
||||
{
|
||||
void StackPlayer::play() {
|
||||
|
||||
auto url = _url;
|
||||
//创建拉流 解码对象
|
||||
// 创建拉流 解码对象
|
||||
_player = std::make_shared<mediakit::MediaPlayer>();
|
||||
std::weak_ptr<mediakit::MediaPlayer> weakPlayer = _player;
|
||||
|
||||
|
|
@ -146,13 +122,9 @@ void StackPlayer::play()
|
|||
_player->setOnPlayResult([weakPlayer, weakSelf, url](const toolkit::SockException& ex) mutable {
|
||||
TraceL << "StackPlayer: " << url << " OnPlayResult: " << ex.what();
|
||||
auto strongPlayer = weakPlayer.lock();
|
||||
if (!strongPlayer) {
|
||||
return;
|
||||
}
|
||||
if (!strongPlayer) { return; }
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
if (!self) { return; }
|
||||
|
||||
if (!ex) {
|
||||
// 取消定时器
|
||||
|
|
@ -164,19 +136,18 @@ void StackPlayer::play()
|
|||
self->rePlay(url);
|
||||
}
|
||||
|
||||
auto videoTrack = std::dynamic_pointer_cast<mediakit::VideoTrack>(strongPlayer->getTrack(mediakit::TrackVideo, false));
|
||||
//auto audioTrack = std::dynamic_pointer_cast<mediakit::AudioTrack>(strongPlayer->getTrack(mediakit::TrackAudio, false));
|
||||
auto videoTrack = std::dynamic_pointer_cast<mediakit::VideoTrack>(
|
||||
strongPlayer->getTrack(mediakit::TrackVideo, false));
|
||||
// auto audioTrack = std::dynamic_pointer_cast<mediakit::AudioTrack>(strongPlayer->getTrack(mediakit::TrackAudio, false));
|
||||
|
||||
if (videoTrack) {
|
||||
//TODO:添加使用显卡还是cpu解码的判断逻辑
|
||||
//auto decoder = std::make_shared<FFmpegDecoder>(videoTrack, 1, std::vector<std::string>{ "hevc_cuvid", "h264_cuvid"});
|
||||
auto decoder = std::make_shared<mediakit::FFmpegDecoder>(videoTrack, 0, std::vector<std::string> { "h264", "hevc" });
|
||||
// TODO:添加使用显卡还是cpu解码的判断逻辑
|
||||
auto decoder = std::make_shared<mediakit::FFmpegDecoder>(
|
||||
videoTrack, 0, std::vector<std::string>{"h264", "hevc"});
|
||||
|
||||
decoder->setOnDecode([weakSelf](const mediakit::FFmpegFrame::Ptr& frame) mutable {
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
if (!self) { return; }
|
||||
|
||||
self->onFrame(frame);
|
||||
});
|
||||
|
|
@ -190,14 +161,10 @@ void StackPlayer::play()
|
|||
_player->setOnShutdown([weakPlayer, url, weakSelf](const toolkit::SockException& ex) {
|
||||
TraceL << "StackPlayer: " << url << " OnShutdown: " << ex.what();
|
||||
auto strongPlayer = weakPlayer.lock();
|
||||
if (!strongPlayer) {
|
||||
return;
|
||||
}
|
||||
if (!strongPlayer) { return; }
|
||||
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
if (!self) { return; }
|
||||
|
||||
self->onDisconnect();
|
||||
|
||||
|
|
@ -207,18 +174,14 @@ void StackPlayer::play()
|
|||
_player->play(url);
|
||||
}
|
||||
|
||||
void StackPlayer::onFrame(const mediakit::FFmpegFrame::Ptr& frame)
|
||||
{
|
||||
void StackPlayer::onFrame(const mediakit::FFmpegFrame::Ptr& frame) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
for (auto& weak_chn : _channels) {
|
||||
if (auto chn = weak_chn.lock()) {
|
||||
chn->onFrame(frame);
|
||||
}
|
||||
if (auto chn = weak_chn.lock()) { chn->onFrame(frame); }
|
||||
}
|
||||
}
|
||||
|
||||
void StackPlayer::onDisconnect()
|
||||
{
|
||||
void StackPlayer::onDisconnect() {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
for (auto& weak_chn : _channels) {
|
||||
if (auto chn = weak_chn.lock()) {
|
||||
|
|
@ -228,31 +191,22 @@ void StackPlayer::onDisconnect()
|
|||
}
|
||||
}
|
||||
|
||||
void StackPlayer::rePlay(const std::string& url)
|
||||
{
|
||||
void StackPlayer::rePlay(const std::string& url) {
|
||||
_failedCount++;
|
||||
auto delay = MAX(2 * 1000, MIN(_failedCount * 3 * 1000, 60 * 1000)); //步进延迟 重试间隔
|
||||
auto delay = MAX(2 * 1000, MIN(_failedCount * 3 * 1000, 60 * 1000));// 步进延迟 重试间隔
|
||||
std::weak_ptr<StackPlayer> weakSelf = shared_from_this();
|
||||
_timer = std::make_shared<toolkit::Timer>(
|
||||
delay / 1000.0f, [weakSelf, url]() {
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
}
|
||||
WarnL << "replay [" << self->_failedCount << "]:" << url;
|
||||
self->_player->play(url);
|
||||
return false;
|
||||
},
|
||||
nullptr);
|
||||
_timer = std::make_shared<toolkit::Timer>(delay / 1000.0f, [weakSelf, url]() {
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {}
|
||||
WarnL << "replay [" << self->_failedCount << "]:" << url;
|
||||
self->_player->play(url);
|
||||
return false;
|
||||
}, nullptr);
|
||||
}
|
||||
|
||||
VideoStack::VideoStack(const std::string& id, int width, int height, AVPixelFormat pixfmt, float fps, int bitRate)
|
||||
: _id(id)
|
||||
, _width(width)
|
||||
, _height(height)
|
||||
, _pixfmt(pixfmt)
|
||||
, _fps(fps)
|
||||
, _bitRate(bitRate)
|
||||
{
|
||||
VideoStack::VideoStack(const std::string& id, int width, int height, AVPixelFormat pixfmt,
|
||||
float fps, int bitRate)
|
||||
: _id(id), _width(width), _height(height), _pixfmt(pixfmt), _fps(fps), _bitRate(bitRate) {
|
||||
|
||||
_buffer = std::make_shared<mediakit::FFmpegFrame>();
|
||||
|
||||
|
|
@ -262,7 +216,8 @@ VideoStack::VideoStack(const std::string& id, int width, int height, AVPixelForm
|
|||
|
||||
av_frame_get_buffer(_buffer->get(), 32);
|
||||
|
||||
_dev = std::make_shared<mediakit::DevChannel>(mediakit::MediaTuple { DEFAULT_VHOST, "live", _id });
|
||||
_dev = std::make_shared<mediakit::DevChannel>(
|
||||
mediakit::MediaTuple{DEFAULT_VHOST, "live", _id, ""});
|
||||
|
||||
mediakit::VideoInfo info;
|
||||
info.codecId = mediakit::CodecH264;
|
||||
|
|
@ -272,34 +227,28 @@ VideoStack::VideoStack(const std::string& id, int width, int height, AVPixelForm
|
|||
info.iBitRate = _bitRate;
|
||||
|
||||
_dev->initVideo(info);
|
||||
//dev->initAudio(); //TODO:音频
|
||||
// dev->initAudio(); //TODO:音频
|
||||
_dev->addTrackCompleted();
|
||||
|
||||
_isExit = false;
|
||||
}
|
||||
|
||||
VideoStack::~VideoStack()
|
||||
{
|
||||
VideoStack::~VideoStack() {
|
||||
_isExit = true;
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
}
|
||||
if (_thread.joinable()) { _thread.join(); }
|
||||
}
|
||||
|
||||
void VideoStack::setParam(const Params& params)
|
||||
{
|
||||
void VideoStack::setParam(const Params& params) {
|
||||
if (_params) {
|
||||
for (auto& p : (*_params)) {
|
||||
if (!p)
|
||||
continue;
|
||||
if (!p) continue;
|
||||
p->weak_buf.reset();
|
||||
}
|
||||
}
|
||||
|
||||
initBgColor();
|
||||
for (auto& p : (*params)) {
|
||||
if (!p)
|
||||
continue;
|
||||
if (!p) continue;
|
||||
p->weak_buf = _buffer;
|
||||
if (auto chn = p->weak_chn.lock()) {
|
||||
chn->addParam(p);
|
||||
|
|
@ -309,14 +258,14 @@ void VideoStack::setParam(const Params& params)
|
|||
_params = params;
|
||||
}
|
||||
|
||||
void VideoStack::start()
|
||||
{
|
||||
void VideoStack::start() {
|
||||
_thread = std::thread([&]() {
|
||||
uint64_t pts = 0;
|
||||
int frameInterval = 1000 / _fps;
|
||||
auto lastEncTP = std::chrono::steady_clock::now();
|
||||
while (!_isExit) {
|
||||
if (std::chrono::steady_clock::now() - lastEncTP > std::chrono::milliseconds(frameInterval)) {
|
||||
if (std::chrono::steady_clock::now() - lastEncTP >
|
||||
std::chrono::milliseconds(frameInterval)) {
|
||||
lastEncTP = std::chrono::steady_clock::now();
|
||||
|
||||
_dev->inputYUV((char**)_buffer->get()->data, _buffer->get()->linesize, pts);
|
||||
|
|
@ -326,9 +275,8 @@ void VideoStack::start()
|
|||
});
|
||||
}
|
||||
|
||||
void VideoStack::initBgColor()
|
||||
{
|
||||
//填充底色
|
||||
void VideoStack::initBgColor() {
|
||||
// 填充底色
|
||||
auto R = 20;
|
||||
auto G = 20;
|
||||
auto B = 20;
|
||||
|
|
@ -342,27 +290,19 @@ void VideoStack::initBgColor()
|
|||
memset(_buffer->get()->data[2], V, _buffer->get()->linesize[2] * _height / 2);
|
||||
}
|
||||
|
||||
Channel::Ptr VideoStackManager::getChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt)
|
||||
{
|
||||
Channel::Ptr VideoStackManager::getChannel(const std::string& id, int width, int height,
|
||||
AVPixelFormat pixfmt) {
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto key = id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt);
|
||||
auto it = _channelMap.find(key);
|
||||
if (it != _channelMap.end()) {
|
||||
return it->second->acquire();
|
||||
}
|
||||
if (it != _channelMap.end()) { return it->second->acquire(); }
|
||||
|
||||
return createChannel(id, width, height, pixfmt);
|
||||
}
|
||||
|
||||
void VideoStackManager::unrefChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt)
|
||||
{
|
||||
void VideoStackManager::unrefChannel(const std::string& id, int width, int height,
|
||||
AVPixelFormat pixfmt) {
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto key = id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt);
|
||||
|
|
@ -377,8 +317,7 @@ void VideoStackManager::unrefChannel(const std::string& id,
|
|||
}
|
||||
}
|
||||
|
||||
int VideoStackManager::startVideoStack(const Json::Value& json)
|
||||
{
|
||||
int VideoStackManager::startVideoStack(const Json::Value& json) {
|
||||
|
||||
std::string id;
|
||||
int width, height;
|
||||
|
|
@ -392,8 +331,7 @@ int VideoStackManager::startVideoStack(const Json::Value& json)
|
|||
auto stack = std::make_shared<VideoStack>(id, width, height);
|
||||
|
||||
for (auto& p : (*params)) {
|
||||
if (!p)
|
||||
continue;
|
||||
if (!p) continue;
|
||||
p->weak_chn = getChannel(p->id, p->width, p->height, p->pixfmt);
|
||||
}
|
||||
|
||||
|
|
@ -405,13 +343,13 @@ int VideoStackManager::startVideoStack(const Json::Value& json)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int VideoStackManager::resetVideoStack(const Json::Value& json)
|
||||
{
|
||||
int VideoStackManager::resetVideoStack(const Json::Value& json) {
|
||||
std::string id;
|
||||
int width, height;
|
||||
auto params = parseParams(json, id, width, height);
|
||||
|
||||
if (!params) {
|
||||
ErrorL << "Videostack parse params failed!";
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -419,15 +357,12 @@ int VideoStackManager::resetVideoStack(const Json::Value& json)
|
|||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto it = _stackMap.find(id);
|
||||
if (it == _stackMap.end()) {
|
||||
return -2;
|
||||
}
|
||||
if (it == _stackMap.end()) { return -2; }
|
||||
stack = it->second;
|
||||
}
|
||||
|
||||
for (auto& p : (*params)) {
|
||||
if (!p)
|
||||
continue;
|
||||
if (!p) continue;
|
||||
p->weak_chn = getChannel(p->id, p->width, p->height, p->pixfmt);
|
||||
}
|
||||
|
||||
|
|
@ -435,8 +370,7 @@ int VideoStackManager::resetVideoStack(const Json::Value& json)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int VideoStackManager::stopVideoStack(const std::string& id)
|
||||
{
|
||||
int VideoStackManager::stopVideoStack(const std::string& id) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto it = _stackMap.find(id);
|
||||
if (it != _stackMap.end()) {
|
||||
|
|
@ -447,93 +381,90 @@ int VideoStackManager::stopVideoStack(const std::string& id)
|
|||
return -1;
|
||||
}
|
||||
|
||||
mediakit::FFmpegFrame::Ptr VideoStackManager::getBgImg()
|
||||
{
|
||||
return _bgImg;
|
||||
}
|
||||
mediakit::FFmpegFrame::Ptr VideoStackManager::getBgImg() { return _bgImg; }
|
||||
|
||||
Params VideoStackManager::parseParams(const Json::Value& json,
|
||||
std::string& id,
|
||||
int& width,
|
||||
int& height)
|
||||
{
|
||||
try {
|
||||
id = json["id"].asString();
|
||||
|
||||
width = json["width"].asInt();
|
||||
height = json["height"].asInt();
|
||||
|
||||
int rows = json["row"].asInt(); //堆叠行数
|
||||
int cols = json["col"].asInt(); //堆叠列数
|
||||
float gapv = json["gapv"].asFloat(); //垂直间距
|
||||
float gaph = json["gaph"].asFloat(); //水平间距
|
||||
|
||||
//单个间距
|
||||
int gaphPix = static_cast<int>(round(width * gaph));
|
||||
int gapvPix = static_cast<int>(round(height * gapv));
|
||||
|
||||
// 根据间距计算格子宽高
|
||||
int gridWidth = cols > 1 ? (width - gaphPix * (cols - 1)) / cols : width;
|
||||
int gridHeight = rows > 1 ? (height - gapvPix * (rows - 1)) / rows : height;
|
||||
|
||||
auto params = std::make_shared<std::vector<Param::Ptr>>(rows * cols);
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int col = 0; col < cols; col++) {
|
||||
std::string url = json["url"][row][col].asString();
|
||||
|
||||
auto param = std::make_shared<Param>();
|
||||
param->posX = gridWidth * col + col * gaphPix;
|
||||
param->posY = gridHeight * row + row * gapvPix;
|
||||
param->width = gridWidth;
|
||||
param->height = gridHeight;
|
||||
param->id = url;
|
||||
|
||||
(*params)[row * cols + col] = param;
|
||||
}
|
||||
}
|
||||
|
||||
//判断是否需要合并格子 (焦点屏)
|
||||
if (!json["span"].empty() && json.isMember("span")) {
|
||||
for (const auto& subArray : json["span"]) {
|
||||
if (!subArray.isArray() || subArray.size() != 2) {
|
||||
throw Json::LogicError("Incorrect 'span' sub-array format in JSON");
|
||||
}
|
||||
std::array<int, 4> mergePos;
|
||||
int index = 0;
|
||||
|
||||
for (const auto& innerArray : subArray) {
|
||||
if (!innerArray.isArray() || innerArray.size() != 2) {
|
||||
throw Json::LogicError("Incorrect 'span' inner-array format in JSON");
|
||||
}
|
||||
for (const auto& number : innerArray) {
|
||||
if (index < mergePos.size()) {
|
||||
mergePos[index++] = number.asInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = mergePos[0]; i <= mergePos[2]; i++) {
|
||||
for (int j = mergePos[1]; j <= mergePos[3]; j++) {
|
||||
if (i == mergePos[0] && j == mergePos[1]) {
|
||||
(*params)[i * cols + j]->width = (mergePos[3] - mergePos[1] + 1) * gridWidth + (mergePos[3] - mergePos[1]) * gapvPix;
|
||||
(*params)[i * cols + j]->height = (mergePos[2] - mergePos[0] + 1) * gridHeight + (mergePos[2] - mergePos[0]) * gaphPix;
|
||||
} else {
|
||||
(*params)[i * cols + j] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
} catch (const std::exception& e) {
|
||||
ErrorL << "Videostack parse params failed! " << e.what();
|
||||
return nullptr;
|
||||
template<typename T> T getJsonValue(const Json::Value& json, const std::string& key) {
|
||||
if (!json.isMember(key)) {
|
||||
throw Json::LogicError("VideoStack parseParams missing required field: " + key);
|
||||
}
|
||||
return json[key].as<T>();
|
||||
}
|
||||
|
||||
bool VideoStackManager::loadBgImg(const std::string& path)
|
||||
{
|
||||
Params VideoStackManager::parseParams(const Json::Value& json, std::string& id, int& width,
|
||||
int& height) {
|
||||
|
||||
id = getJsonValue<std::string>(json, "id");
|
||||
width = getJsonValue<int>(json, "width");
|
||||
height = getJsonValue<int>(json, "height");
|
||||
int rows = getJsonValue<int>(json, "row");// 行数
|
||||
int cols = getJsonValue<int>(json, "col");// 列数
|
||||
|
||||
float gapv = json["gapv"].asFloat();// 垂直间距
|
||||
float gaph = json["gaph"].asFloat();// 水平间距
|
||||
|
||||
// 单个间距
|
||||
int gaphPix = static_cast<int>(round(width * gaph));
|
||||
int gapvPix = static_cast<int>(round(height * gapv));
|
||||
|
||||
// 根据间距计算格子宽高
|
||||
int gridWidth = cols > 1 ? (width - gaphPix * (cols - 1)) / cols : width;
|
||||
int gridHeight = rows > 1 ? (height - gapvPix * (rows - 1)) / rows : height;
|
||||
|
||||
auto params = std::make_shared<std::vector<Param::Ptr>>(rows * cols);
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int col = 0; col < cols; col++) {
|
||||
std::string url = json["url"][row][col].asString();
|
||||
|
||||
auto param = std::make_shared<Param>();
|
||||
param->posX = gridWidth * col + col * gaphPix;
|
||||
param->posY = gridHeight * row + row * gapvPix;
|
||||
param->width = gridWidth;
|
||||
param->height = gridHeight;
|
||||
param->id = url;
|
||||
|
||||
(*params)[row * cols + col] = param;
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否需要合并格子 (焦点屏)
|
||||
if (json.isMember("span") && json["span"].isArray() && json["span"].size() > 0) {
|
||||
for (const auto& subArray : json["span"]) {
|
||||
if (!subArray.isArray() || subArray.size() != 2) {
|
||||
throw Json::LogicError("Incorrect 'span' sub-array format in JSON");
|
||||
}
|
||||
std::array<int, 4> mergePos;
|
||||
unsigned int index = 0;
|
||||
|
||||
for (const auto& innerArray : subArray) {
|
||||
if (!innerArray.isArray() || innerArray.size() != 2) {
|
||||
throw Json::LogicError("Incorrect 'span' inner-array format in JSON");
|
||||
}
|
||||
for (const auto& number : innerArray) {
|
||||
if (index < mergePos.size()) { mergePos[index++] = number.asInt(); }
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = mergePos[0]; i <= mergePos[2]; i++) {
|
||||
for (int j = mergePos[1]; j <= mergePos[3]; j++) {
|
||||
if (i == mergePos[0] && j == mergePos[1]) {
|
||||
(*params)[i * cols + j]->width =
|
||||
(mergePos[3] - mergePos[1] + 1) * gridWidth +
|
||||
(mergePos[3] - mergePos[1]) * gapvPix;
|
||||
(*params)[i * cols + j]->height =
|
||||
(mergePos[2] - mergePos[0] + 1) * gridHeight +
|
||||
(mergePos[2] - mergePos[0]) * gaphPix;
|
||||
} else {
|
||||
(*params)[i * cols + j] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
bool VideoStackManager::loadBgImg(const std::string& path) {
|
||||
_bgImg = std::make_shared<mediakit::FFmpegFrame>();
|
||||
|
||||
_bgImg->get()->width = 1280;
|
||||
|
|
@ -543,21 +474,21 @@ bool VideoStackManager::loadBgImg(const std::string& path)
|
|||
av_frame_get_buffer(_bgImg->get(), 32);
|
||||
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
if (!file.is_open()) { return false; }
|
||||
|
||||
file.read((char*)_bgImg->get()->data[0], _bgImg->get()->linesize[0] * _bgImg->get()->height); // Y
|
||||
file.read((char*)_bgImg->get()->data[1], _bgImg->get()->linesize[1] * _bgImg->get()->height / 2); // U
|
||||
file.read((char*)_bgImg->get()->data[2], _bgImg->get()->linesize[2] * _bgImg->get()->height / 2); // V
|
||||
file.read((char*)_bgImg->get()->data[0],
|
||||
_bgImg->get()->linesize[0] * _bgImg->get()->height);// Y
|
||||
file.read((char*)_bgImg->get()->data[1],
|
||||
_bgImg->get()->linesize[1] * _bgImg->get()->height / 2);// U
|
||||
file.read((char*)_bgImg->get()->data[2],
|
||||
_bgImg->get()->linesize[2] * _bgImg->get()->height / 2);// V
|
||||
return true;
|
||||
}
|
||||
|
||||
Channel::Ptr VideoStackManager::createChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt)
|
||||
{
|
||||
void VideoStackManager::clear() { _stackMap.clear(); }
|
||||
|
||||
Channel::Ptr VideoStackManager::createChannel(const std::string& id, int width, int height,
|
||||
AVPixelFormat pixfmt) {
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
StackPlayer::Ptr player;
|
||||
|
|
@ -568,24 +499,24 @@ Channel::Ptr VideoStackManager::createChannel(const std::string& id,
|
|||
player = createPlayer(id);
|
||||
}
|
||||
|
||||
auto refChn = std::make_shared<RefWrapper<Channel::Ptr>>(std::make_shared<Channel>(id, width, height, pixfmt));
|
||||
auto refChn = std::make_shared<RefWrapper<Channel::Ptr>>(
|
||||
std::make_shared<Channel>(id, width, height, pixfmt));
|
||||
auto chn = refChn->acquire();
|
||||
player->addChannel(chn);
|
||||
|
||||
_channelMap[id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt)] = refChn;
|
||||
_channelMap[id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt)] =
|
||||
refChn;
|
||||
return chn;
|
||||
}
|
||||
|
||||
StackPlayer::Ptr VideoStackManager::createPlayer(const std::string& id)
|
||||
{
|
||||
StackPlayer::Ptr VideoStackManager::createPlayer(const std::string& id) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto refPlayer = std::make_shared<RefWrapper<StackPlayer::Ptr>>(std::make_shared<StackPlayer>(id));
|
||||
auto refPlayer =
|
||||
std::make_shared<RefWrapper<StackPlayer::Ptr>>(std::make_shared<StackPlayer>(id));
|
||||
_playerMap[id] = refPlayer;
|
||||
|
||||
auto player = refPlayer->acquire();
|
||||
if (!id.empty()) {
|
||||
player->play();
|
||||
}
|
||||
if (!id.empty()) { player->play(); }
|
||||
|
||||
return player;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,29 +5,23 @@
|
|||
#include "Player/MediaPlayer.h"
|
||||
#include "json/json.h"
|
||||
#include <mutex>
|
||||
template <typename T>
|
||||
class RefWrapper {
|
||||
public:
|
||||
template<typename T> class RefWrapper {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<RefWrapper<T>>;
|
||||
|
||||
template <typename... Args>
|
||||
explicit RefWrapper(Args&&... args)
|
||||
: _rc(0)
|
||||
, _entity(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
template<typename... Args>
|
||||
explicit RefWrapper(Args&&... args) : _rc(0), _entity(std::forward<Args>(args)...) {}
|
||||
|
||||
T acquire()
|
||||
{
|
||||
T acquire() {
|
||||
++_rc;
|
||||
return _entity;
|
||||
}
|
||||
|
||||
bool dispose() { return --_rc <= 0; }
|
||||
|
||||
private:
|
||||
T _entity;
|
||||
private:
|
||||
std::atomic<int> _rc;
|
||||
T _entity;
|
||||
};
|
||||
|
||||
class Channel;
|
||||
|
|
@ -40,7 +34,7 @@ struct Param {
|
|||
int width = 0;
|
||||
int height = 0;
|
||||
AVPixelFormat pixfmt = AV_PIX_FMT_YUV420P;
|
||||
std::string id {};
|
||||
std::string id{};
|
||||
|
||||
// runtime
|
||||
std::weak_ptr<Channel> weak_chn;
|
||||
|
|
@ -52,7 +46,7 @@ struct Param {
|
|||
using Params = std::shared_ptr<std::vector<Param::Ptr>>;
|
||||
|
||||
class Channel : public std::enable_shared_from_this<Channel> {
|
||||
public:
|
||||
public:
|
||||
using Ptr = std::shared_ptr<Channel>;
|
||||
|
||||
Channel(const std::string& id, int width, int height, AVPixelFormat pixfmt);
|
||||
|
|
@ -63,12 +57,12 @@ class Channel : public std::enable_shared_from_this<Channel> {
|
|||
|
||||
void fillBuffer(const Param::Ptr& p);
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void forEachParam(const std::function<void(const Param::Ptr&)>& func);
|
||||
|
||||
void copyData(const mediakit::FFmpegFrame::Ptr& buf, const Param::Ptr& p);
|
||||
|
||||
private:
|
||||
private:
|
||||
std::string _id;
|
||||
int _width;
|
||||
int _height;
|
||||
|
|
@ -84,13 +78,10 @@ class Channel : public std::enable_shared_from_this<Channel> {
|
|||
};
|
||||
|
||||
class StackPlayer : public std::enable_shared_from_this<StackPlayer> {
|
||||
public:
|
||||
public:
|
||||
using Ptr = std::shared_ptr<StackPlayer>;
|
||||
|
||||
StackPlayer(const std::string& url)
|
||||
: _url(url)
|
||||
{
|
||||
}
|
||||
StackPlayer(const std::string& url) : _url(url) {}
|
||||
|
||||
void addChannel(const std::weak_ptr<Channel>& chn);
|
||||
|
||||
|
|
@ -100,14 +91,14 @@ class StackPlayer : public std::enable_shared_from_this<StackPlayer> {
|
|||
|
||||
void onDisconnect();
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void rePlay(const std::string& url);
|
||||
|
||||
private:
|
||||
private:
|
||||
std::string _url;
|
||||
mediakit::MediaPlayer::Ptr _player;
|
||||
|
||||
//用于断线重连
|
||||
// 用于断线重连
|
||||
toolkit::Timer::Ptr _timer;
|
||||
int _failedCount = 0;
|
||||
|
||||
|
|
@ -116,15 +107,12 @@ class StackPlayer : public std::enable_shared_from_this<StackPlayer> {
|
|||
};
|
||||
|
||||
class VideoStack {
|
||||
public:
|
||||
public:
|
||||
using Ptr = std::shared_ptr<VideoStack>;
|
||||
|
||||
VideoStack(const std::string& url,
|
||||
int width = 1920,
|
||||
int height = 1080,
|
||||
AVPixelFormat pixfmt = AV_PIX_FMT_YUV420P,
|
||||
float fps = 25.0,
|
||||
int bitRate = 2 * 1024 * 1024);
|
||||
VideoStack(const std::string& url, int width = 1920, int height = 1080,
|
||||
AVPixelFormat pixfmt = AV_PIX_FMT_YUV420P, float fps = 25.0,
|
||||
int bitRate = 2 * 1024 * 1024);
|
||||
|
||||
~VideoStack();
|
||||
|
||||
|
|
@ -132,15 +120,15 @@ class VideoStack {
|
|||
|
||||
void start();
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void initBgColor();
|
||||
|
||||
public:
|
||||
public:
|
||||
Params _params;
|
||||
|
||||
mediakit::FFmpegFrame::Ptr _buffer;
|
||||
|
||||
private:
|
||||
private:
|
||||
std::string _id;
|
||||
int _width;
|
||||
int _height;
|
||||
|
|
@ -156,53 +144,47 @@ class VideoStack {
|
|||
};
|
||||
|
||||
class VideoStackManager {
|
||||
public:
|
||||
static VideoStackManager& Instance();
|
||||
|
||||
Channel::Ptr getChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt);
|
||||
|
||||
void unrefChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt);
|
||||
|
||||
public:
|
||||
// 创建拼接流
|
||||
int startVideoStack(const Json::Value& json);
|
||||
|
||||
// 停止拼接流
|
||||
int stopVideoStack(const std::string& id);
|
||||
|
||||
// 可以在不断流的情况下,修改拼接流的配置(实现切换拼接屏内容)
|
||||
int resetVideoStack(const Json::Value& json);
|
||||
|
||||
int stopVideoStack(const std::string& id);
|
||||
public:
|
||||
static VideoStackManager& Instance();
|
||||
|
||||
Channel::Ptr getChannel(const std::string& id, int width, int height, AVPixelFormat pixfmt);
|
||||
|
||||
void unrefChannel(const std::string& id, int width, int height, AVPixelFormat pixfmt);
|
||||
|
||||
bool loadBgImg(const std::string& path);
|
||||
|
||||
void clear();
|
||||
|
||||
mediakit::FFmpegFrame::Ptr getBgImg();
|
||||
|
||||
protected:
|
||||
Params parseParams(const Json::Value& json,
|
||||
std::string& id,
|
||||
int& width,
|
||||
int& height);
|
||||
protected:
|
||||
Params parseParams(const Json::Value& json, std::string& id, int& width, int& height);
|
||||
|
||||
protected:
|
||||
Channel::Ptr createChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt);
|
||||
protected:
|
||||
Channel::Ptr createChannel(const std::string& id, int width, int height, AVPixelFormat pixfmt);
|
||||
|
||||
StackPlayer::Ptr createPlayer(const std::string& id);
|
||||
|
||||
private:
|
||||
private:
|
||||
mediakit::FFmpegFrame::Ptr _bgImg;
|
||||
|
||||
private:
|
||||
private:
|
||||
std::recursive_mutex _mx;
|
||||
|
||||
std::unordered_map<std::string, VideoStack::Ptr> _stackMap;
|
||||
|
||||
std::unordered_map<std::string, RefWrapper<Channel::Ptr>::Ptr> _channelMap;
|
||||
|
||||
|
||||
std::unordered_map<std::string, RefWrapper<StackPlayer::Ptr>::Ptr> _playerMap;
|
||||
};
|
||||
#endif
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <exception>
|
||||
#include <sys/stat.h>
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
|
|
@ -45,7 +46,7 @@
|
|||
#include "Http/HttpRequester.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
#include "Pusher/PusherProxy.h"
|
||||
#include "Rtp/RtpSelector.h"
|
||||
#include "Rtp/RtpProcess.h"
|
||||
#include "Record/MP4Reader.h"
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
|
|
@ -476,18 +477,19 @@ Value makeMediaSourceJson(MediaSource &media){
|
|||
}
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
uint16_t openRtpServer(uint16_t local_port, const string &stream_id, int tcp_mode, const string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex) {
|
||||
if (s_rtp_server.find(stream_id)) {
|
||||
//为了防止RtpProcess所有权限混乱的问题,不允许重复添加相同的stream_id
|
||||
uint16_t openRtpServer(uint16_t local_port, const mediakit::MediaTuple &tuple, int tcp_mode, const string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex) {
|
||||
auto key = tuple.shortUrl();
|
||||
if (s_rtp_server.find(key)) {
|
||||
//为了防止RtpProcess所有权限混乱的问题,不允许重复添加相同的key
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto server = s_rtp_server.makeWithAction(stream_id, [&](RtpServer::Ptr server) {
|
||||
server->start(local_port, stream_id, (RtpServer::TcpMode)tcp_mode, local_ip.c_str(), re_use_port, ssrc, only_track, multiplex);
|
||||
auto server = s_rtp_server.makeWithAction(key, [&](RtpServer::Ptr server) {
|
||||
server->start(local_port, tuple, (RtpServer::TcpMode)tcp_mode, local_ip.c_str(), re_use_port, ssrc, only_track, multiplex);
|
||||
});
|
||||
server->setOnDetach([stream_id]() {
|
||||
server->setOnDetach([key](const SockException &ex) {
|
||||
//设置rtp超时移除事件
|
||||
s_rtp_server.erase(stream_id);
|
||||
s_rtp_server.erase(key);
|
||||
});
|
||||
|
||||
//回复json
|
||||
|
|
@ -1198,8 +1200,16 @@ void installWebApi() {
|
|||
api_regist("/index/api/getRtpInfo",[](API_ARGS_MAP){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("stream_id");
|
||||
|
||||
auto process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
|
||||
std::string vhost = DEFAULT_VHOST;
|
||||
if (!allArgs["vhost"].empty()) {
|
||||
vhost = allArgs["vhost"];
|
||||
}
|
||||
std::string app = kRtpAppName;
|
||||
if (!allArgs["app"].empty()) {
|
||||
app = allArgs["app"];
|
||||
}
|
||||
auto src = MediaSource::find(vhost, app, allArgs["stream_id"]);
|
||||
auto process = src ? src->getRtpProcess() : nullptr;
|
||||
if (!process) {
|
||||
val["exist"] = false;
|
||||
return;
|
||||
|
|
@ -1211,7 +1221,16 @@ void installWebApi() {
|
|||
api_regist("/index/api/openRtpServer",[](API_ARGS_MAP){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("port", "stream_id");
|
||||
std::string vhost = DEFAULT_VHOST;
|
||||
if (!allArgs["vhost"].empty()) {
|
||||
vhost = allArgs["vhost"];
|
||||
}
|
||||
std::string app = kRtpAppName;
|
||||
if (!allArgs["app"].empty()) {
|
||||
app = allArgs["app"];
|
||||
}
|
||||
auto stream_id = allArgs["stream_id"];
|
||||
auto tuple = MediaTuple { vhost, app, stream_id, "" };
|
||||
auto tcp_mode = allArgs["tcp_mode"].as<int>();
|
||||
if (allArgs["enable_tcp"].as<int>() && !tcp_mode) {
|
||||
//兼容老版本请求,新版本去除enable_tcp参数并新增tcp_mode参数
|
||||
|
|
@ -1226,40 +1245,50 @@ void installWebApi() {
|
|||
if (!allArgs["local_ip"].empty()) {
|
||||
local_ip = allArgs["local_ip"];
|
||||
}
|
||||
auto port = openRtpServer(allArgs["port"], stream_id, tcp_mode, local_ip, allArgs["re_use_port"].as<bool>(),
|
||||
auto port = openRtpServer(allArgs["port"], tuple, tcp_mode, local_ip, allArgs["re_use_port"].as<bool>(),
|
||||
allArgs["ssrc"].as<uint32_t>(), only_track);
|
||||
if (port == 0) {
|
||||
throw InvalidArgsException("该stream_id已存在");
|
||||
throw InvalidArgsException("This stream already exists");
|
||||
}
|
||||
//回复json
|
||||
val["port"] = port;
|
||||
});
|
||||
|
||||
api_regist("/index/api/openRtpServerMultiplex", [](API_ARGS_MAP) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("port", "stream_id");
|
||||
auto stream_id = allArgs["stream_id"];
|
||||
auto tcp_mode = allArgs["tcp_mode"].as<int>();
|
||||
if (allArgs["enable_tcp"].as<int>() && !tcp_mode) {
|
||||
// 兼容老版本请求,新版本去除enable_tcp参数并新增tcp_mode参数
|
||||
tcp_mode = 1;
|
||||
}
|
||||
auto only_track = allArgs["only_track"].as<int>();
|
||||
if (allArgs["only_audio"].as<bool>()) {
|
||||
// 兼容老版本请求,新版本去除only_audio参数并新增only_track参数
|
||||
only_track = 1;
|
||||
}
|
||||
std::string local_ip = "::";
|
||||
if (!allArgs["local_ip"].empty()) {
|
||||
local_ip = allArgs["local_ip"];
|
||||
}
|
||||
auto port = openRtpServer(allArgs["port"], stream_id, tcp_mode, local_ip, true, 0, only_track,true);
|
||||
if (port == 0) {
|
||||
throw InvalidArgsException("该stream_id已存在");
|
||||
}
|
||||
// 回复json
|
||||
val["port"] = port;
|
||||
});
|
||||
api_regist("/index/api/openRtpServerMultiplex", [](API_ARGS_MAP) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("port", "stream_id");
|
||||
std::string vhost = DEFAULT_VHOST;
|
||||
if (!allArgs["vhost"].empty()) {
|
||||
vhost = allArgs["vhost"];
|
||||
}
|
||||
std::string app = kRtpAppName;
|
||||
if (!allArgs["app"].empty()) {
|
||||
app = allArgs["app"];
|
||||
}
|
||||
auto stream_id = allArgs["stream_id"];
|
||||
auto tuple = MediaTuple { vhost, app, stream_id, "" };
|
||||
auto tcp_mode = allArgs["tcp_mode"].as<int>();
|
||||
if (allArgs["enable_tcp"].as<int>() && !tcp_mode) {
|
||||
// 兼容老版本请求,新版本去除enable_tcp参数并新增tcp_mode参数
|
||||
tcp_mode = 1;
|
||||
}
|
||||
auto only_track = allArgs["only_track"].as<int>();
|
||||
if (allArgs["only_audio"].as<bool>()) {
|
||||
// 兼容老版本请求,新版本去除only_audio参数并新增only_track参数
|
||||
only_track = 1;
|
||||
}
|
||||
std::string local_ip = "::";
|
||||
if (!allArgs["local_ip"].empty()) {
|
||||
local_ip = allArgs["local_ip"];
|
||||
}
|
||||
|
||||
auto port = openRtpServer(allArgs["port"], tuple, tcp_mode, local_ip, true, 0, only_track, true);
|
||||
if (port == 0) {
|
||||
throw InvalidArgsException("This stream already exists");
|
||||
}
|
||||
// 回复json
|
||||
val["port"] = port;
|
||||
});
|
||||
|
||||
api_regist("/index/api/connectRtpServer", [](API_ARGS_MAP_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
|
|
@ -1272,9 +1301,19 @@ void installWebApi() {
|
|||
invoker(200, headerOut, val.toStyledString());
|
||||
};
|
||||
|
||||
auto server = s_rtp_server.find(allArgs["stream_id"]);
|
||||
std::string vhost = DEFAULT_VHOST;
|
||||
if (!allArgs["vhost"].empty()) {
|
||||
vhost = allArgs["vhost"];
|
||||
}
|
||||
std::string app = kRtpAppName;
|
||||
if (!allArgs["app"].empty()) {
|
||||
app = allArgs["app"];
|
||||
}
|
||||
auto stream_id = allArgs["stream_id"];
|
||||
auto tuple = MediaTuple { vhost, app, stream_id, "" };
|
||||
auto server = s_rtp_server.find(tuple.shortUrl());
|
||||
if (!server) {
|
||||
cb(SockException(Err_other, "未找到rtp服务"));
|
||||
cb(SockException(Err_other, "can not find the stream"));
|
||||
return;
|
||||
}
|
||||
server->connectToServer(allArgs["dst_url"], allArgs["dst_port"], cb);
|
||||
|
|
@ -1284,7 +1323,17 @@ void installWebApi() {
|
|||
CHECK_SECRET();
|
||||
CHECK_ARGS("stream_id");
|
||||
|
||||
if(s_rtp_server.erase(allArgs["stream_id"]) == 0){
|
||||
std::string vhost = DEFAULT_VHOST;
|
||||
if (!allArgs["vhost"].empty()) {
|
||||
vhost = allArgs["vhost"];
|
||||
}
|
||||
std::string app = kRtpAppName;
|
||||
if (!allArgs["app"].empty()) {
|
||||
app = allArgs["app"];
|
||||
}
|
||||
auto stream_id = allArgs["stream_id"];
|
||||
auto tuple = MediaTuple { vhost, app, stream_id, "" };
|
||||
if (s_rtp_server.erase(tuple.shortUrl()) == 0) {
|
||||
val["hit"] = 0;
|
||||
return;
|
||||
}
|
||||
|
|
@ -1295,7 +1344,17 @@ void installWebApi() {
|
|||
CHECK_SECRET();
|
||||
CHECK_ARGS("stream_id", "ssrc");
|
||||
|
||||
auto server = s_rtp_server.find(allArgs["stream_id"]);
|
||||
std::string vhost = DEFAULT_VHOST;
|
||||
if (!allArgs["vhost"].empty()) {
|
||||
vhost = allArgs["vhost"];
|
||||
}
|
||||
std::string app = kRtpAppName;
|
||||
if (!allArgs["app"].empty()) {
|
||||
app = allArgs["app"];
|
||||
}
|
||||
auto stream_id = allArgs["stream_id"];
|
||||
auto tuple = MediaTuple { vhost, app, stream_id, "" };
|
||||
auto server = s_rtp_server.find(tuple.shortUrl());
|
||||
if (!server) {
|
||||
throw ApiRetException("RtpServer not found by stream_id", API::NotFound);
|
||||
}
|
||||
|
|
@ -1307,8 +1366,11 @@ void installWebApi() {
|
|||
|
||||
std::lock_guard<std::recursive_mutex> lck(s_rtp_server._mtx);
|
||||
for (auto &pr : s_rtp_server._map) {
|
||||
auto vec = split(pr.first, "/");
|
||||
Value obj;
|
||||
obj["stream_id"] = pr.first;
|
||||
obj["vhost"] = vec[0];
|
||||
obj["app"] = vec[1];
|
||||
obj["stream_id"] = vec[2];
|
||||
obj["port"] = pr.second->getPort();
|
||||
val["data"].append(obj);
|
||||
}
|
||||
|
|
@ -1437,10 +1499,19 @@ void installWebApi() {
|
|||
api_regist("/index/api/pauseRtpCheck", [](API_ARGS_MAP) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("stream_id");
|
||||
std::string vhost = DEFAULT_VHOST;
|
||||
if (!allArgs["vhost"].empty()) {
|
||||
vhost = allArgs["vhost"];
|
||||
}
|
||||
std::string app = kRtpAppName;
|
||||
if (!allArgs["app"].empty()) {
|
||||
app = allArgs["app"];
|
||||
}
|
||||
//只是暂停流的检查,流媒体服务器做为流负载服务,收流就转发,RTSP/RTMP有自己暂停协议
|
||||
auto rtp_process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
|
||||
if (rtp_process) {
|
||||
rtp_process->setStopCheckRtp(true);
|
||||
auto src = MediaSource::find(vhost, app, allArgs["stream_id"]);
|
||||
auto process = src ? src->getRtpProcess() : nullptr;
|
||||
if (process) {
|
||||
process->setStopCheckRtp(true);
|
||||
} else {
|
||||
val["code"] = API::NotFound;
|
||||
}
|
||||
|
|
@ -1449,9 +1520,18 @@ void installWebApi() {
|
|||
api_regist("/index/api/resumeRtpCheck", [](API_ARGS_MAP) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("stream_id");
|
||||
auto rtp_process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
|
||||
if (rtp_process) {
|
||||
rtp_process->setStopCheckRtp(false);
|
||||
std::string vhost = DEFAULT_VHOST;
|
||||
if (!allArgs["vhost"].empty()) {
|
||||
vhost = allArgs["vhost"];
|
||||
}
|
||||
std::string app = kRtpAppName;
|
||||
if (!allArgs["app"].empty()) {
|
||||
app = allArgs["app"];
|
||||
}
|
||||
auto src = MediaSource::find(vhost, app, allArgs["stream_id"]);
|
||||
auto process = src ? src->getRtpProcess() : nullptr;
|
||||
if (process) {
|
||||
process->setStopCheckRtp(false);
|
||||
} else {
|
||||
val["code"] = API::NotFound;
|
||||
}
|
||||
|
|
@ -1869,6 +1949,7 @@ void installWebApi() {
|
|||
});
|
||||
#endif
|
||||
|
||||
#if ENABLE_MP4
|
||||
api_regist("/index/api/loadMP4File", [](API_ARGS_MAP) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost", "app", "stream", "file_path");
|
||||
|
|
@ -1887,6 +1968,7 @@ void installWebApi() {
|
|||
// sample_ms设置为0,从配置文件加载;file_repeat可以指定,如果配置文件也指定循环解复用,那么强制开启
|
||||
reader->startReadMP4(0, true, allArgs["file_repeat"]);
|
||||
});
|
||||
#endif
|
||||
|
||||
GET_CONFIG_FUNC(std::set<std::string>, download_roots, API::kDownloadRoot, [](const string &str) -> std::set<std::string> {
|
||||
std::set<std::string> ret;
|
||||
|
|
@ -1948,9 +2030,29 @@ void installWebApi() {
|
|||
|
||||
api_regist("/index/api/stack/start", [](API_ARGS_JSON_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
auto ret = VideoStackManager::Instance().startVideoStack(allArgs.args);
|
||||
val["code"] = ret;
|
||||
val["msg"] = ret ? "failed" : "success";
|
||||
int ret = 0;
|
||||
try {
|
||||
ret = VideoStackManager::Instance().startVideoStack(allArgs.args);
|
||||
val["code"] = ret;
|
||||
val["msg"] = ret ? "failed" : "success";
|
||||
} catch (const std::exception &e) {
|
||||
val["code"] = -1;
|
||||
val["msg"] = e.what();
|
||||
}
|
||||
invoker(200, headerOut, val.toStyledString());
|
||||
});
|
||||
|
||||
api_regist("/index/api/stack/reset", [](API_ARGS_JSON_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
int ret = 0;
|
||||
try {
|
||||
auto ret = VideoStackManager::Instance().resetVideoStack(allArgs.args);
|
||||
val["code"] = ret;
|
||||
val["msg"] = ret ? "failed" : "success";
|
||||
} catch (const std::exception &e) {
|
||||
val["code"] = -1;
|
||||
val["msg"] = e.what();
|
||||
}
|
||||
invoker(200, headerOut, val.toStyledString());
|
||||
});
|
||||
|
||||
|
|
@ -1972,6 +2074,9 @@ void unInstallWebApi(){
|
|||
#if defined(ENABLE_RTPPROXY)
|
||||
s_rtp_server.clear();
|
||||
#endif
|
||||
#if defined(ENABLE_VIDEOSTACK) && defined(ENABLE_FFMPEG) && defined(ENABLE_X264)
|
||||
VideoStackManager::Instance().clear();
|
||||
#endif
|
||||
|
||||
NoticeCenter::Instance().delListener(&web_api_tag);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ void installWebApi();
|
|||
void unInstallWebApi();
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
uint16_t openRtpServer(uint16_t local_port, const std::string &stream_id, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex=false);
|
||||
uint16_t openRtpServer(uint16_t local_port, const mediakit::MediaTuple &tuple, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex=false);
|
||||
#endif
|
||||
|
||||
Json::Value makeMediaSourceJson(mediakit::MediaSource &media);
|
||||
|
|
|
|||
|
|
@ -682,7 +682,9 @@ void installWebHook() {
|
|||
|
||||
ArgsType body;
|
||||
body["local_port"] = local_port;
|
||||
body["stream_id"] = stream_id;
|
||||
body[VHOST_KEY] = tuple.vhost;
|
||||
body["app"] = tuple.app;
|
||||
body["stream_id"] = tuple.stream;
|
||||
body["tcp_mode"] = tcp_mode;
|
||||
body["re_use_port"] = re_use_port;
|
||||
body["ssrc"] = ssrc;
|
||||
|
|
|
|||
|
|
@ -321,6 +321,8 @@ int H264Encoder::inputData(char *yuv[3], int linesize[3], int64_t cts, H264Frame
|
|||
_aFrames[i].iType = pNal.i_type;
|
||||
_aFrames[i].iLength = pNal.i_payload;
|
||||
_aFrames[i].pucData = pNal.p_payload;
|
||||
_aFrames[i].dts = _pPicOut->i_dts;
|
||||
_aFrames[i].pts = _pPicOut->i_pts;
|
||||
}
|
||||
*out_frame = _aFrames;
|
||||
return iNal;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ public:
|
|||
int iType;
|
||||
int iLength;
|
||||
uint8_t *pucData;
|
||||
|
||||
int64_t dts;
|
||||
int64_t pts;
|
||||
} H264Frame;
|
||||
|
||||
H264Encoder();
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ bool DevChannel::inputYUV(char *yuv[3], int linesize[3], uint64_t cts) {
|
|||
int frames = _pH264Enc->inputData(yuv, linesize, cts, &out_frames);
|
||||
bool ret = false;
|
||||
for (int i = 0; i < frames; i++) {
|
||||
ret = inputH264((char *) out_frames[i].pucData, out_frames[i].iLength, cts) ? true : ret;
|
||||
ret = inputH264((char *) out_frames[i].pucData, out_frames[i].iLength, out_frames[i].dts, out_frames[i].pts) ? true : ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,59 +55,13 @@ string getOriginTypeString(MediaOriginType type){
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ProtocolOption::ProtocolOption() {
|
||||
GET_CONFIG(int, s_modify_stamp, Protocol::kModifyStamp);
|
||||
GET_CONFIG(bool, s_enabel_audio, Protocol::kEnableAudio);
|
||||
GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio);
|
||||
GET_CONFIG(bool, s_auto_close, Protocol::kAutoClose);
|
||||
GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS);
|
||||
GET_CONFIG(uint32_t, s_paced_sender_ms, Protocol::kPacedSenderMS);
|
||||
|
||||
GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls);
|
||||
GET_CONFIG(bool, s_enable_hls_fmp4, Protocol::kEnableHlsFmp4);
|
||||
GET_CONFIG(bool, s_enable_mp4, Protocol::kEnableMP4);
|
||||
GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp);
|
||||
GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp);
|
||||
GET_CONFIG(bool, s_enable_ts, Protocol::kEnableTS);
|
||||
GET_CONFIG(bool, s_enable_fmp4, Protocol::kEnableFMP4);
|
||||
|
||||
GET_CONFIG(bool, s_hls_demand, Protocol::kHlsDemand);
|
||||
GET_CONFIG(bool, s_rtsp_demand, Protocol::kRtspDemand);
|
||||
GET_CONFIG(bool, s_rtmp_demand, Protocol::kRtmpDemand);
|
||||
GET_CONFIG(bool, s_ts_demand, Protocol::kTSDemand);
|
||||
GET_CONFIG(bool, s_fmp4_demand, Protocol::kFMP4Demand);
|
||||
|
||||
GET_CONFIG(bool, s_mp4_as_player, Protocol::kMP4AsPlayer);
|
||||
GET_CONFIG(uint32_t, s_mp4_max_second, Protocol::kMP4MaxSecond);
|
||||
GET_CONFIG(string, s_mp4_save_path, Protocol::kMP4SavePath);
|
||||
|
||||
GET_CONFIG(string, s_hls_save_path, Protocol::kHlsSavePath);
|
||||
|
||||
modify_stamp = s_modify_stamp;
|
||||
enable_audio = s_enabel_audio;
|
||||
add_mute_audio = s_add_mute_audio;
|
||||
auto_close = s_auto_close;
|
||||
continue_push_ms = s_continue_push_ms;
|
||||
paced_sender_ms = s_paced_sender_ms;
|
||||
|
||||
enable_hls = s_enable_hls;
|
||||
enable_hls_fmp4 = s_enable_hls_fmp4;
|
||||
enable_mp4 = s_enable_mp4;
|
||||
enable_rtsp = s_enable_rtsp;
|
||||
enable_rtmp = s_enable_rtmp;
|
||||
enable_ts = s_enable_ts;
|
||||
enable_fmp4 = s_enable_fmp4;
|
||||
|
||||
hls_demand = s_hls_demand;
|
||||
rtsp_demand = s_rtsp_demand;
|
||||
rtmp_demand = s_rtmp_demand;
|
||||
ts_demand = s_ts_demand;
|
||||
fmp4_demand = s_fmp4_demand;
|
||||
|
||||
mp4_as_player = s_mp4_as_player;
|
||||
mp4_max_second = s_mp4_max_second;
|
||||
mp4_save_path = s_mp4_save_path;
|
||||
|
||||
hls_save_path = s_hls_save_path;
|
||||
mINI ini;
|
||||
auto &config = mINI::Instance();
|
||||
static auto sz = strlen(Protocol::kFieldName);
|
||||
for (auto it = config.lower_bound(Protocol::kFieldName); it != config.end() && start_with(it->first, Protocol::kFieldName); ++it) {
|
||||
ini.emplace(it->first.substr(sz), it->second);
|
||||
}
|
||||
load(ini);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -271,9 +225,14 @@ toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() {
|
|||
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed: " + getUrl());
|
||||
}
|
||||
|
||||
std::shared_ptr<MultiMediaSourceMuxer> MediaSource::getMuxer() {
|
||||
std::shared_ptr<MultiMediaSourceMuxer> MediaSource::getMuxer() const {
|
||||
auto listener = _listener.lock();
|
||||
return listener ? listener->getMuxer(*this) : nullptr;
|
||||
return listener ? listener->getMuxer(const_cast<MediaSource&>(*this)) : nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<RtpProcess> MediaSource::getRtpProcess() const {
|
||||
auto listener = _listener.lock();
|
||||
return listener ? listener->getRtpProcess(const_cast<MediaSource&>(*this)) : nullptr;
|
||||
}
|
||||
|
||||
void MediaSource::onReaderChanged(int size) {
|
||||
|
|
@ -706,7 +665,7 @@ string MediaSourceEvent::getOriginUrl(MediaSource &sender) const {
|
|||
MediaOriginType MediaSourceEventInterceptor::getOriginType(MediaSource &sender) const {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return MediaOriginType::unknown;
|
||||
return MediaSourceEvent::getOriginType(sender);
|
||||
}
|
||||
return listener->getOriginType(sender);
|
||||
}
|
||||
|
|
@ -726,7 +685,7 @@ string MediaSourceEventInterceptor::getOriginUrl(MediaSource &sender) const {
|
|||
std::shared_ptr<SockInfo> MediaSourceEventInterceptor::getOriginSock(MediaSource &sender) const {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return nullptr;
|
||||
return MediaSourceEvent::getOriginSock(sender);
|
||||
}
|
||||
return listener->getOriginSock(sender);
|
||||
}
|
||||
|
|
@ -734,7 +693,7 @@ std::shared_ptr<SockInfo> MediaSourceEventInterceptor::getOriginSock(MediaSource
|
|||
bool MediaSourceEventInterceptor::seekTo(MediaSource &sender, uint32_t stamp) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
return MediaSourceEvent::seekTo(sender, stamp);
|
||||
}
|
||||
return listener->seekTo(sender, stamp);
|
||||
}
|
||||
|
|
@ -742,7 +701,7 @@ bool MediaSourceEventInterceptor::seekTo(MediaSource &sender, uint32_t stamp) {
|
|||
bool MediaSourceEventInterceptor::pause(MediaSource &sender, bool pause) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
return MediaSourceEvent::pause(sender, pause);
|
||||
}
|
||||
return listener->pause(sender, pause);
|
||||
}
|
||||
|
|
@ -750,7 +709,7 @@ bool MediaSourceEventInterceptor::pause(MediaSource &sender, bool pause) {
|
|||
bool MediaSourceEventInterceptor::speed(MediaSource &sender, float speed) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
return MediaSourceEvent::speed(sender, speed);
|
||||
}
|
||||
return listener->speed(sender, speed);
|
||||
}
|
||||
|
|
@ -758,7 +717,7 @@ bool MediaSourceEventInterceptor::speed(MediaSource &sender, float speed) {
|
|||
bool MediaSourceEventInterceptor::close(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
return MediaSourceEvent::close(sender);
|
||||
}
|
||||
return listener->close(sender);
|
||||
}
|
||||
|
|
@ -766,7 +725,7 @@ bool MediaSourceEventInterceptor::close(MediaSource &sender) {
|
|||
int MediaSourceEventInterceptor::totalReaderCount(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return sender.readerCount();
|
||||
return MediaSourceEvent::totalReaderCount(sender);
|
||||
}
|
||||
return listener->totalReaderCount(sender);
|
||||
}
|
||||
|
|
@ -774,44 +733,55 @@ int MediaSourceEventInterceptor::totalReaderCount(MediaSource &sender) {
|
|||
void MediaSourceEventInterceptor::onReaderChanged(MediaSource &sender, int size) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
MediaSourceEvent::onReaderChanged(sender, size);
|
||||
} else {
|
||||
listener->onReaderChanged(sender, size);
|
||||
return MediaSourceEvent::onReaderChanged(sender, size);
|
||||
}
|
||||
listener->onReaderChanged(sender, size);
|
||||
}
|
||||
|
||||
void MediaSourceEventInterceptor::onRegist(MediaSource &sender, bool regist) {
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->onRegist(sender, regist);
|
||||
if (!listener) {
|
||||
return MediaSourceEvent::onRegist(sender, regist);
|
||||
}
|
||||
listener->onRegist(sender, regist);
|
||||
}
|
||||
|
||||
float MediaSourceEventInterceptor::getLossRate(MediaSource &sender, TrackType type){
|
||||
float MediaSourceEventInterceptor::getLossRate(MediaSource &sender, TrackType type) {
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
return listener->getLossRate(sender, type);
|
||||
if (!listener) {
|
||||
return MediaSourceEvent::getLossRate(sender, type);
|
||||
}
|
||||
return -1; //异常返回-1
|
||||
return listener->getLossRate(sender, type);
|
||||
}
|
||||
|
||||
toolkit::EventPoller::Ptr MediaSourceEventInterceptor::getOwnerPoller(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
return listener->getOwnerPoller(sender);
|
||||
if (!listener) {
|
||||
return MediaSourceEvent::getOwnerPoller(sender);
|
||||
}
|
||||
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed");
|
||||
return listener->getOwnerPoller(sender);
|
||||
}
|
||||
|
||||
std::shared_ptr<MultiMediaSourceMuxer> MediaSourceEventInterceptor::getMuxer(MediaSource &sender) {
|
||||
std::shared_ptr<MultiMediaSourceMuxer> MediaSourceEventInterceptor::getMuxer(MediaSource &sender) const {
|
||||
auto listener = _listener.lock();
|
||||
return listener ? listener->getMuxer(sender) : nullptr;
|
||||
if (!listener) {
|
||||
return MediaSourceEvent::getMuxer(sender);
|
||||
}
|
||||
return listener->getMuxer(sender);
|
||||
}
|
||||
|
||||
std::shared_ptr<RtpProcess> MediaSourceEventInterceptor::getRtpProcess(MediaSource &sender) const {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return MediaSourceEvent::getRtpProcess(sender);
|
||||
}
|
||||
return listener->getRtpProcess(sender);
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
return MediaSourceEvent::setupRecord(sender, type, start, custom_path, max_second);
|
||||
}
|
||||
return listener->setupRecord(sender, type, start, custom_path, max_second);
|
||||
}
|
||||
|
|
@ -819,7 +789,7 @@ bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::typ
|
|||
bool MediaSourceEventInterceptor::isRecording(MediaSource &sender, Recorder::type type) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
return MediaSourceEvent::isRecording(sender, type);
|
||||
}
|
||||
return listener->isRecording(sender, type);
|
||||
}
|
||||
|
|
@ -827,26 +797,25 @@ bool MediaSourceEventInterceptor::isRecording(MediaSource &sender, Recorder::typ
|
|||
vector<Track::Ptr> MediaSourceEventInterceptor::getMediaTracks(MediaSource &sender, bool trackReady) const {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return vector<Track::Ptr>();
|
||||
return MediaSourceEvent::getMediaTracks(sender, trackReady);
|
||||
}
|
||||
return listener->getMediaTracks(sender, trackReady);
|
||||
}
|
||||
|
||||
void MediaSourceEventInterceptor::startSendRtp(MediaSource &sender, const MediaSourceEvent::SendRtpArgs &args, const std::function<void(uint16_t, const toolkit::SockException &)> cb) {
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->startSendRtp(sender, args, cb);
|
||||
} else {
|
||||
MediaSourceEvent::startSendRtp(sender, args, cb);
|
||||
if (!listener) {
|
||||
return MediaSourceEvent::startSendRtp(sender, args, cb);
|
||||
}
|
||||
listener->startSendRtp(sender, args, cb);
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::stopSendRtp(MediaSource &sender, const string &ssrc){
|
||||
bool MediaSourceEventInterceptor::stopSendRtp(MediaSource &sender, const string &ssrc) {
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
return listener->stopSendRtp(sender, ssrc);
|
||||
if (!listener) {
|
||||
return MediaSourceEvent::stopSendRtp(sender, ssrc);
|
||||
}
|
||||
return false;
|
||||
return listener->stopSendRtp(sender, ssrc);
|
||||
}
|
||||
|
||||
void MediaSourceEventInterceptor::setDelegate(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
|
|
@ -856,7 +825,7 @@ void MediaSourceEventInterceptor::setDelegate(const std::weak_ptr<MediaSourceEve
|
|||
_listener = listener;
|
||||
}
|
||||
|
||||
std::shared_ptr<MediaSourceEvent> MediaSourceEventInterceptor::getDelegate() const{
|
||||
std::shared_ptr<MediaSourceEvent> MediaSourceEventInterceptor::getDelegate() const {
|
||||
return _listener.lock();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include "Util/mini.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "Extension/Track.h"
|
||||
#include "Record/Recorder.h"
|
||||
|
|
@ -41,6 +42,7 @@ enum class MediaOriginType : uint8_t {
|
|||
std::string getOriginTypeString(MediaOriginType type);
|
||||
|
||||
class MediaSource;
|
||||
class RtpProcess;
|
||||
class MultiMediaSourceMuxer;
|
||||
class MediaSourceEvent {
|
||||
public:
|
||||
|
|
@ -88,7 +90,9 @@ public:
|
|||
// 获取所有track相关信息
|
||||
virtual std::vector<Track::Ptr> getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector<Track::Ptr>(); };
|
||||
// 获取MultiMediaSourceMuxer对象
|
||||
virtual std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) { return nullptr; }
|
||||
virtual std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) const { return nullptr; }
|
||||
// 获取RtpProcess对象
|
||||
virtual std::shared_ptr<RtpProcess> getRtpProcess(MediaSource &sender) const { return nullptr; }
|
||||
|
||||
class SendRtpArgs {
|
||||
public:
|
||||
|
|
@ -145,6 +149,14 @@ static void getArgsValue(const MAP &allArgs, const KEY &key, TYPE &value) {
|
|||
}
|
||||
}
|
||||
|
||||
template <typename KEY, typename TYPE>
|
||||
static void getArgsValue(const toolkit::mINI &allArgs, const KEY &key, TYPE &value) {
|
||||
auto it = allArgs.find(key);
|
||||
if (it != allArgs.end()) {
|
||||
value = (TYPE)it->second;
|
||||
}
|
||||
}
|
||||
|
||||
class ProtocolOption {
|
||||
public:
|
||||
ProtocolOption();
|
||||
|
|
@ -278,7 +290,8 @@ public:
|
|||
bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override;
|
||||
float getLossRate(MediaSource &sender, TrackType type) override;
|
||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
|
||||
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) const override;
|
||||
std::shared_ptr<RtpProcess> getRtpProcess(MediaSource &sender) const override;
|
||||
|
||||
private:
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
|
|
@ -395,7 +408,9 @@ public:
|
|||
// 获取所在线程
|
||||
toolkit::EventPoller::Ptr getOwnerPoller();
|
||||
// 获取MultiMediaSourceMuxer对象
|
||||
std::shared_ptr<MultiMediaSourceMuxer> getMuxer();
|
||||
std::shared_ptr<MultiMediaSourceMuxer> getMuxer() const;
|
||||
// 获取RtpProcess对象
|
||||
std::shared_ptr<RtpProcess> getRtpProcess() const;
|
||||
|
||||
////////////////static方法,查找或生成MediaSource////////////////
|
||||
|
||||
|
|
|
|||
|
|
@ -466,8 +466,8 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) {
|
|||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<MultiMediaSourceMuxer> MultiMediaSourceMuxer::getMuxer(MediaSource &sender) {
|
||||
return shared_from_this();
|
||||
std::shared_ptr<MultiMediaSourceMuxer> MultiMediaSourceMuxer::getMuxer(MediaSource &sender) const {
|
||||
return const_cast<MultiMediaSourceMuxer*>(this)->shared_from_this();
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ public:
|
|||
/**
|
||||
* 获取本对象
|
||||
*/
|
||||
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
|
||||
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) const override;
|
||||
|
||||
const ProtocolOption &getOption() const;
|
||||
const MediaTuple &getMediaTuple() const;
|
||||
|
|
|
|||
|
|
@ -104,33 +104,32 @@ static onceToken token([]() {
|
|||
} // namespace General
|
||||
|
||||
namespace Protocol {
|
||||
#define PROTOCOL_FIELD "protocol."
|
||||
const string kModifyStamp = PROTOCOL_FIELD "modify_stamp";
|
||||
const string kEnableAudio = PROTOCOL_FIELD "enable_audio";
|
||||
const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio";
|
||||
const string kAutoClose = PROTOCOL_FIELD "auto_close";
|
||||
const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms";
|
||||
const string kPacedSenderMS = PROTOCOL_FIELD "paced_sender_ms";
|
||||
const string kModifyStamp = string(kFieldName) + "modify_stamp";
|
||||
const string kEnableAudio = string(kFieldName) + "enable_audio";
|
||||
const string kAddMuteAudio = string(kFieldName) + "add_mute_audio";
|
||||
const string kAutoClose = string(kFieldName) + "auto_close";
|
||||
const string kContinuePushMS = string(kFieldName) + "continue_push_ms";
|
||||
const string kPacedSenderMS = string(kFieldName) + "paced_sender_ms";
|
||||
|
||||
const string kEnableHls = PROTOCOL_FIELD "enable_hls";
|
||||
const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4";
|
||||
const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4";
|
||||
const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp";
|
||||
const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp";
|
||||
const string kEnableTS = PROTOCOL_FIELD "enable_ts";
|
||||
const string kEnableFMP4 = PROTOCOL_FIELD "enable_fmp4";
|
||||
const string kEnableHls = string(kFieldName) + "enable_hls";
|
||||
const string kEnableHlsFmp4 = string(kFieldName) + "enable_hls_fmp4";
|
||||
const string kEnableMP4 = string(kFieldName) + "enable_mp4";
|
||||
const string kEnableRtsp = string(kFieldName) + "enable_rtsp";
|
||||
const string kEnableRtmp = string(kFieldName) + "enable_rtmp";
|
||||
const string kEnableTS = string(kFieldName) + "enable_ts";
|
||||
const string kEnableFMP4 = string(kFieldName) + "enable_fmp4";
|
||||
|
||||
const string kMP4AsPlayer = PROTOCOL_FIELD "mp4_as_player";
|
||||
const string kMP4MaxSecond = PROTOCOL_FIELD "mp4_max_second";
|
||||
const string kMP4SavePath = PROTOCOL_FIELD "mp4_save_path";
|
||||
const string kMP4AsPlayer = string(kFieldName) + "mp4_as_player";
|
||||
const string kMP4MaxSecond = string(kFieldName) + "mp4_max_second";
|
||||
const string kMP4SavePath = string(kFieldName) + "mp4_save_path";
|
||||
|
||||
const string kHlsSavePath = PROTOCOL_FIELD "hls_save_path";
|
||||
const string kHlsSavePath = string(kFieldName) + "hls_save_path";
|
||||
|
||||
const string kHlsDemand = PROTOCOL_FIELD "hls_demand";
|
||||
const string kRtspDemand = PROTOCOL_FIELD "rtsp_demand";
|
||||
const string kRtmpDemand = PROTOCOL_FIELD "rtmp_demand";
|
||||
const string kTSDemand = PROTOCOL_FIELD "ts_demand";
|
||||
const string kFMP4Demand = PROTOCOL_FIELD "fmp4_demand";
|
||||
const string kHlsDemand = string(kFieldName) + "hls_demand";
|
||||
const string kRtspDemand = string(kFieldName) + "rtsp_demand";
|
||||
const string kRtmpDemand = string(kFieldName) + "rtmp_demand";
|
||||
const string kTSDemand = string(kFieldName) + "ts_demand";
|
||||
const string kFMP4Demand = string(kFieldName) + "fmp4_demand";
|
||||
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kModifyStamp] = (int)ProtocolOption::kModifyStampRelative;
|
||||
|
|
@ -375,6 +374,7 @@ const string kBenchmarkMode = "benchmark_mode";
|
|||
const string kWaitTrackReady = "wait_track_ready";
|
||||
const string kPlayTrack = "play_track";
|
||||
const string kProxyUrl = "proxy_url";
|
||||
const string kRtspSpeed = "rtsp_speed";
|
||||
} // namespace Client
|
||||
|
||||
} // namespace mediakit
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ extern const std::string kBroadcastReloadConfig;
|
|||
|
||||
// rtp server 超时
|
||||
extern const std::string kBroadcastRtpServerTimeout;
|
||||
#define BroadcastRtpServerTimeoutArgs uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc
|
||||
#define BroadcastRtpServerTimeoutArgs uint16_t &local_port, const MediaTuple &tuple, int &tcp_mode, bool &re_use_port, uint32_t &ssrc
|
||||
|
||||
// rtc transport sctp 连接状态
|
||||
extern const std::string kBroadcastRtcSctpConnecting;
|
||||
|
|
@ -205,6 +205,7 @@ extern const std::string kBroadcastPlayerCountChanged;
|
|||
} // namespace General
|
||||
|
||||
namespace Protocol {
|
||||
static constexpr char kFieldName[] = "protocol.";
|
||||
//时间戳修复这一路流标志位
|
||||
extern const std::string kModifyStamp;
|
||||
//转协议是否开启音频
|
||||
|
|
@ -447,6 +448,8 @@ extern const std::string kWaitTrackReady;
|
|||
extern const std::string kPlayTrack;
|
||||
//设置代理url,目前只支持http协议
|
||||
extern const std::string kProxyUrl;
|
||||
//设置开始rtsp倍速播放
|
||||
extern const std::string kRtspSpeed;
|
||||
} // namespace Client
|
||||
} // namespace mediakit
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,11 @@ public:
|
|||
* 返回视频fps
|
||||
*/
|
||||
virtual float getVideoFps() const { return 0; }
|
||||
|
||||
/**
|
||||
* 返回相关 sps/pps 等
|
||||
*/
|
||||
virtual std::vector<Frame::Ptr> getConfigFrames() const { return std::vector<Frame::Ptr>{}; }
|
||||
};
|
||||
|
||||
class VideoTrackImp : public VideoTrack {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,13 @@ public:
|
|||
FMP4MediaSource(const MediaTuple& tuple,
|
||||
int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, tuple), _ring_size(ring_size) {}
|
||||
|
||||
~FMP4MediaSource() override { flush(); }
|
||||
~FMP4MediaSource() override {
|
||||
try {
|
||||
flush();
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
|
|
|
|||
|
|
@ -26,7 +26,13 @@ public:
|
|||
_media_src = std::make_shared<FMP4MediaSource>(tuple);
|
||||
}
|
||||
|
||||
~FMP4MediaSourceMuxer() override { MP4MuxerMemory::flush(); };
|
||||
~FMP4MediaSourceMuxer() override {
|
||||
try {
|
||||
MP4MuxerMemory::flush();
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
setDelegate(listener);
|
||||
|
|
|
|||
|
|
@ -141,7 +141,9 @@ static std::shared_ptr<char> getSharedMmap(const string &file_path, int64_t &fil
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
file_size = ::GetFileSize(hfile, NULL);
|
||||
LARGE_INTEGER FileSize;
|
||||
GetFileSizeEx(hfile, &FileSize); //GetFileSize函数的拓展,可用于获取大于4G的文件大小
|
||||
file_size = FileSize.QuadPart;
|
||||
|
||||
auto hmapping = ::CreateFileMapping(hfile, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace mediakit {
|
|||
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, const string &url_in) {
|
||||
auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller();
|
||||
std::weak_ptr<EventPoller> weak_poller = poller;
|
||||
static auto release_func = [weak_poller](PlayerBase *ptr) {
|
||||
auto release_func = [weak_poller](PlayerBase *ptr) {
|
||||
if (auto poller = weak_poller.lock()) {
|
||||
poller->async([ptr]() {
|
||||
onceToken token(nullptr, [&]() { delete ptr; });
|
||||
|
|
|
|||
|
|
@ -84,7 +84,13 @@ public:
|
|||
using Ptr = std::shared_ptr<HlsRecorder>;
|
||||
template <typename ...ARGS>
|
||||
HlsRecorder(ARGS && ...args) : HlsRecorderBase<MpegMuxer>(false, std::forward<ARGS>(args)...) {}
|
||||
~HlsRecorder() override { this->flush(); }
|
||||
~HlsRecorder() override {
|
||||
try {
|
||||
this->flush();
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
|
||||
|
|
@ -102,7 +108,13 @@ public:
|
|||
using Ptr = std::shared_ptr<HlsFMP4Recorder>;
|
||||
template <typename ...ARGS>
|
||||
HlsFMP4Recorder(ARGS && ...args) : HlsRecorderBase<MP4MuxerMemory>(true, std::forward<ARGS>(args)...) {}
|
||||
~HlsFMP4Recorder() override { this->flush(); }
|
||||
~HlsFMP4Recorder() override {
|
||||
try {
|
||||
this->flush();
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
void addTrackCompleted() override {
|
||||
HlsRecorderBase<MP4MuxerMemory>::addTrackCompleted();
|
||||
|
|
|
|||
|
|
@ -48,7 +48,13 @@ public:
|
|||
*/
|
||||
RtmpMediaSource(const MediaTuple& tuple, int ring_size = RTMP_GOP_SIZE): MediaSource(RTMP_SCHEMA, tuple), _ring_size(ring_size) {}
|
||||
|
||||
~RtmpMediaSource() override { flush(); }
|
||||
~RtmpMediaSource() override {
|
||||
try {
|
||||
flush();
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
|
|
|
|||
|
|
@ -87,10 +87,9 @@ DecoderImp::DecoderImp(const Decoder::Ptr &decoder, MediaSinkInterface *sink){
|
|||
void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t bytes, int finish) {
|
||||
// G711传统只支持 8000/1/16的规格,FFmpeg貌似做了扩展,但是这里不管它了
|
||||
auto track = Factory::getTrackByCodecId(getCodecByMpegId(codecid), 8000, 1, 16);
|
||||
if (!track) {
|
||||
return;
|
||||
if (track) {
|
||||
onTrack(stream, std::move(track));
|
||||
}
|
||||
onTrack(stream, std::move(track));
|
||||
// 防止未获取视频track提前complete导致忽略后续视频的问题,用于兼容一些不太规范的ps流
|
||||
if (finish && _have_video) {
|
||||
_sink->addTrackCompleted();
|
||||
|
|
|
|||
|
|
@ -11,26 +11,26 @@
|
|||
#if defined(ENABLE_RTPPROXY)
|
||||
#include "GB28181Process.h"
|
||||
#include "RtpProcess.h"
|
||||
#include "RtpSelector.h"
|
||||
#include "Http/HttpTSPlayer.h"
|
||||
#include "Util/File.h"
|
||||
#include "Common/config.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
static constexpr char kRtpAppName[] = "rtp";
|
||||
//在创建_muxer对象前(也就是推流鉴权成功前),需要先缓存frame,这样可以防止丢包,提高体验
|
||||
//但是同时需要控制缓冲长度,防止内存溢出。200帧数据,大概有10秒数据,应该足矣等待鉴权hook返回
|
||||
static constexpr size_t kMaxCachedFrame = 200;
|
||||
//但是同时需要控制缓冲长度,防止内存溢出。最多缓存10秒数据,应该足矣等待鉴权hook返回
|
||||
static constexpr size_t kMaxCachedFrameMS = 10 * 1000;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
RtpProcess::RtpProcess(const string &stream_id) {
|
||||
_media_info.schema = kRtpAppName;
|
||||
_media_info.vhost = DEFAULT_VHOST;
|
||||
_media_info.app = kRtpAppName;
|
||||
_media_info.stream = stream_id;
|
||||
RtpProcess::Ptr RtpProcess::createProcess(const MediaTuple &tuple) {
|
||||
RtpProcess::Ptr ret(new RtpProcess(tuple));
|
||||
ret->createTimer();
|
||||
return ret;
|
||||
}
|
||||
|
||||
RtpProcess::RtpProcess(const MediaTuple &tuple) {
|
||||
static_cast<MediaTuple &>(_media_info) = tuple;
|
||||
|
||||
GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir);
|
||||
{
|
||||
|
|
@ -75,6 +75,25 @@ RtpProcess::~RtpProcess() {
|
|||
}
|
||||
}
|
||||
|
||||
void RtpProcess::onManager() {
|
||||
if (!alive()) {
|
||||
onDetach(SockException(Err_timeout, "RtpProcess timeout"));
|
||||
}
|
||||
}
|
||||
|
||||
void RtpProcess::createTimer() {
|
||||
//创建超时管理定时器
|
||||
weak_ptr<RtpProcess> weakSelf = shared_from_this();
|
||||
_timer = std::make_shared<Timer>(3.0f, [weakSelf] {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
strongSelf->onManager();
|
||||
return true;
|
||||
}, EventPollerPool::Instance().getPoller());
|
||||
}
|
||||
|
||||
bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data, size_t len, const struct sockaddr *addr, uint64_t *dts_out) {
|
||||
if (!isRtp(data, len)) {
|
||||
WarnP(this) << "Not rtp packet";
|
||||
|
|
@ -90,6 +109,7 @@ bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data
|
|||
_addr.reset(new sockaddr_storage(*((sockaddr_storage *)addr)));
|
||||
if (first) {
|
||||
emitOnPublish();
|
||||
_cache_ticker.resetTime();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,8 +150,8 @@ bool RtpProcess::inputFrame(const Frame::Ptr &frame) {
|
|||
_last_frame_time.resetTime();
|
||||
return _muxer->inputFrame(frame);
|
||||
}
|
||||
if (_cached_func.size() > kMaxCachedFrame) {
|
||||
WarnL << "cached frame of track(" << frame->getCodecName() << ") is too much, now dropped, please check your on_publish hook url in config.ini file";
|
||||
if (_cache_ticker.elapsedTime() > kMaxCachedFrameMS) {
|
||||
WarnL << "Cached frame of stream(" << _media_info.stream << ") is too much, your on_publish hook responded too late!";
|
||||
return false;
|
||||
}
|
||||
auto frame_cached = Frame::getCacheAbleFrame(frame);
|
||||
|
|
@ -203,13 +223,14 @@ void RtpProcess::setOnlyTrack(OnlyTrack only_track) {
|
|||
_only_track = only_track;
|
||||
}
|
||||
|
||||
void RtpProcess::onDetach() {
|
||||
void RtpProcess::onDetach(const SockException &ex) {
|
||||
if (_on_detach) {
|
||||
_on_detach();
|
||||
WarnL << ex << ", stream_id: " << getIdentifier();
|
||||
_on_detach(ex);
|
||||
}
|
||||
}
|
||||
|
||||
void RtpProcess::setOnDetach(function<void()> cb) {
|
||||
void RtpProcess::setOnDetach(onDetachCB cb) {
|
||||
_on_detach = std::move(cb);
|
||||
}
|
||||
|
||||
|
|
@ -256,9 +277,6 @@ void RtpProcess::emitOnPublish() {
|
|||
}
|
||||
if (err.empty()) {
|
||||
strong_self->_muxer = std::make_shared<MultiMediaSourceMuxer>(strong_self->_media_info, 0.0f, option);
|
||||
if (!option.stream_replace.empty()) {
|
||||
RtpSelector::Instance().addStreamReplace(strong_self->_media_info.stream, option.stream_replace);
|
||||
}
|
||||
switch (strong_self->_only_track) {
|
||||
case kOnlyAudio: strong_self->_muxer->setOnlyAudio(); break;
|
||||
case kOnlyVideo: strong_self->_muxer->enableAudio(false); break;
|
||||
|
|
@ -294,6 +312,15 @@ std::shared_ptr<SockInfo> RtpProcess::getOriginSock(MediaSource &sender) const {
|
|||
return const_cast<RtpProcess *>(this)->shared_from_this();
|
||||
}
|
||||
|
||||
RtpProcess::Ptr RtpProcess::getRtpProcess(mediakit::MediaSource &sender) const {
|
||||
return const_cast<RtpProcess *>(this)->shared_from_this();
|
||||
}
|
||||
|
||||
bool RtpProcess::close(mediakit::MediaSource &sender) {
|
||||
onDetach(SockException(Err_shutdown, "close media"));
|
||||
return true;
|
||||
}
|
||||
|
||||
toolkit::EventPoller::Ptr RtpProcess::getOwnerPoller(MediaSource &sender) {
|
||||
if (_sock) {
|
||||
return _sock->getPoller();
|
||||
|
|
|
|||
|
|
@ -18,11 +18,14 @@
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
class RtpProcess final : public RtcpContextForRecv, public toolkit::SockInfo, public MediaSinkInterface, public MediaSourceEventInterceptor, public std::enable_shared_from_this<RtpProcess>{
|
||||
static constexpr char kRtpAppName[] = "rtp";
|
||||
|
||||
class RtpProcess final : public RtcpContextForRecv, public toolkit::SockInfo, public MediaSinkInterface, public MediaSourceEvent, public std::enable_shared_from_this<RtpProcess>{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<RtpProcess>;
|
||||
friend class RtpProcessHelper;
|
||||
RtpProcess(const std::string &stream_id);
|
||||
using onDetachCB = std::function<void(const toolkit::SockException &ex)>;
|
||||
|
||||
static Ptr createProcess(const MediaTuple &tuple);
|
||||
~RtpProcess();
|
||||
enum OnlyTrack { kAll = 0, kOnlyAudio = 1, kOnlyVideo = 2 };
|
||||
|
||||
|
|
@ -38,20 +41,16 @@ public:
|
|||
*/
|
||||
bool inputRtp(bool is_udp, const toolkit::Socket::Ptr &sock, const char *data, size_t len, const struct sockaddr *addr , uint64_t *dts_out = nullptr);
|
||||
|
||||
/**
|
||||
* 是否超时,用于超时移除对象
|
||||
*/
|
||||
bool alive();
|
||||
|
||||
/**
|
||||
* 超时时被RtpSelector移除时触发
|
||||
*/
|
||||
void onDetach();
|
||||
void onDetach(const toolkit::SockException &ex);
|
||||
|
||||
/**
|
||||
* 设置onDetach事件回调
|
||||
*/
|
||||
void setOnDetach(std::function<void()> cb);
|
||||
void setOnDetach(onDetachCB cb);
|
||||
|
||||
/**
|
||||
* 设置onDetach事件回调,false检查RTP超时,true停止
|
||||
|
|
@ -88,10 +87,17 @@ protected:
|
|||
std::shared_ptr<SockInfo> getOriginSock(MediaSource &sender) const override;
|
||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||
float getLossRate(MediaSource &sender, TrackType type) override;
|
||||
Ptr getRtpProcess(mediakit::MediaSource &sender) const override;
|
||||
bool close(mediakit::MediaSource &sender) override;
|
||||
|
||||
private:
|
||||
RtpProcess(const MediaTuple &tuple);
|
||||
|
||||
void emitOnPublish();
|
||||
void doCachedFunc();
|
||||
bool alive();
|
||||
void onManager();
|
||||
void createTimer();
|
||||
|
||||
private:
|
||||
OnlyTrack _only_track = kAll;
|
||||
|
|
@ -102,14 +108,16 @@ private:
|
|||
toolkit::Socket::Ptr _sock;
|
||||
MediaInfo _media_info;
|
||||
toolkit::Ticker _last_frame_time;
|
||||
std::function<void()> _on_detach;
|
||||
onDetachCB _on_detach;
|
||||
std::shared_ptr<FILE> _save_file_rtp;
|
||||
std::shared_ptr<FILE> _save_file_video;
|
||||
ProcessInterface::Ptr _process;
|
||||
MultiMediaSourceMuxer::Ptr _muxer;
|
||||
std::atomic_bool _stop_rtp_check{false};
|
||||
toolkit::Timer::Ptr _timer;
|
||||
toolkit::Ticker _last_check_alive;
|
||||
std::recursive_mutex _func_mtx;
|
||||
toolkit::Ticker _cache_ticker;
|
||||
std::deque<std::function<void()> > _cached_func;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,168 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
#include <stddef.h>
|
||||
#include "RtpSelector.h"
|
||||
#include "RtpSplitter.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
INSTANCE_IMP(RtpSelector);
|
||||
|
||||
void RtpSelector::clear(){
|
||||
lock_guard<decltype(_mtx_map)> lck(_mtx_map);
|
||||
_map_rtp_process.clear();
|
||||
_map_stream_replace.clear();
|
||||
}
|
||||
|
||||
bool RtpSelector::getSSRC(const char *data, size_t data_len, uint32_t &ssrc){
|
||||
if (data_len < 12) {
|
||||
return false;
|
||||
}
|
||||
uint32_t *ssrc_ptr = (uint32_t *) (data + 8);
|
||||
ssrc = ntohl(*ssrc_ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
RtpProcess::Ptr RtpSelector::getProcess(const string &stream_id,bool makeNew) {
|
||||
lock_guard<decltype(_mtx_map)> lck(_mtx_map);
|
||||
string stream_id_origin = stream_id;
|
||||
auto it_replace = _map_stream_replace.find(stream_id);
|
||||
if (it_replace != _map_stream_replace.end()) {
|
||||
stream_id_origin = it_replace->second;
|
||||
}
|
||||
|
||||
auto it = _map_rtp_process.find(stream_id_origin);
|
||||
if (it == _map_rtp_process.end() && !makeNew) {
|
||||
return nullptr;
|
||||
}
|
||||
if (it != _map_rtp_process.end() && makeNew) {
|
||||
//已经被其他线程持有了,不得再被持有,否则会存在线程安全的问题
|
||||
throw ProcessExisted(StrPrinter << "RtpProcess(" << stream_id_origin << ") already existed");
|
||||
}
|
||||
RtpProcessHelper::Ptr &ref = _map_rtp_process[stream_id_origin];
|
||||
if (!ref) {
|
||||
ref = std::make_shared<RtpProcessHelper>(stream_id_origin, shared_from_this());
|
||||
ref->attachEvent();
|
||||
createTimer();
|
||||
}
|
||||
return ref->getProcess();
|
||||
}
|
||||
|
||||
void RtpSelector::createTimer() {
|
||||
if (!_timer) {
|
||||
//创建超时管理定时器
|
||||
weak_ptr<RtpSelector> weakSelf = shared_from_this();
|
||||
_timer = std::make_shared<Timer>(3.0f, [weakSelf] {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
strongSelf->onManager();
|
||||
return true;
|
||||
}, EventPollerPool::Instance().getPoller());
|
||||
}
|
||||
}
|
||||
|
||||
void RtpSelector::delProcess(const string &stream_id,const RtpProcess *ptr) {
|
||||
RtpProcess::Ptr process;
|
||||
{
|
||||
lock_guard<decltype(_mtx_map)> lck(_mtx_map);
|
||||
auto it = _map_rtp_process.find(stream_id);
|
||||
if (it == _map_rtp_process.end()) {
|
||||
return;
|
||||
}
|
||||
if (it->second->getProcess().get() != ptr) {
|
||||
return;
|
||||
}
|
||||
process = it->second->getProcess();
|
||||
_map_rtp_process.erase(it);
|
||||
delStreamReplace(stream_id);
|
||||
}
|
||||
process->onDetach();
|
||||
}
|
||||
|
||||
void RtpSelector::addStreamReplace(const string &stream_id, const std::string &stream_replace) {
|
||||
lock_guard<decltype(_mtx_map)> lck(_mtx_map);
|
||||
_map_stream_replace[stream_replace] = stream_id;
|
||||
}
|
||||
|
||||
void RtpSelector::delStreamReplace(const string &stream_id) {
|
||||
for (auto it = _map_stream_replace.begin(); it != _map_stream_replace.end(); ++it) {
|
||||
if (it->second == stream_id) {
|
||||
_map_stream_replace.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RtpSelector::onManager() {
|
||||
List<RtpProcess::Ptr> clear_list;
|
||||
{
|
||||
lock_guard<decltype(_mtx_map)> lck(_mtx_map);
|
||||
for (auto it = _map_rtp_process.begin(); it != _map_rtp_process.end();) {
|
||||
if (it->second->getProcess()->alive()) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
WarnL << "RtpProcess timeout:" << it->first;
|
||||
clear_list.emplace_back(it->second->getProcess());
|
||||
delStreamReplace(it->first);
|
||||
it = _map_rtp_process.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
clear_list.for_each([](const RtpProcess::Ptr &process) {
|
||||
process->onDetach();
|
||||
});
|
||||
}
|
||||
|
||||
RtpProcessHelper::RtpProcessHelper(const string &stream_id, const weak_ptr<RtpSelector> &parent) {
|
||||
_stream_id = stream_id;
|
||||
_parent = parent;
|
||||
_process = std::make_shared<RtpProcess>(stream_id);
|
||||
}
|
||||
|
||||
RtpProcessHelper::~RtpProcessHelper() {
|
||||
auto process = std::move(_process);
|
||||
try {
|
||||
// flush时,确保线程安全
|
||||
process->getOwnerPoller(MediaSource::NullMediaSource())->async([process]() { process->flush(); });
|
||||
} catch (...) {
|
||||
// 忽略getOwnerPoller可能抛出的异常
|
||||
}
|
||||
}
|
||||
|
||||
void RtpProcessHelper::attachEvent() {
|
||||
//主要目的是close回调触发时能把对象从RtpSelector中删除
|
||||
_process->setDelegate(shared_from_this());
|
||||
}
|
||||
|
||||
bool RtpProcessHelper::close(MediaSource &sender) {
|
||||
//此回调在其他线程触发
|
||||
auto parent = _parent.lock();
|
||||
if (!parent) {
|
||||
return false;
|
||||
}
|
||||
parent->delProcess(_stream_id, _process.get());
|
||||
WarnL << "close media: " << sender.getUrl();
|
||||
return true;
|
||||
}
|
||||
|
||||
RtpProcess::Ptr &RtpProcessHelper::getProcess() {
|
||||
return _process;
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_RTPSELECTOR_H
|
||||
#define ZLMEDIAKIT_RTPSELECTOR_H
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
#include <stdint.h>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include "RtpProcess.h"
|
||||
#include "Common/MediaSource.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
class RtpSelector;
|
||||
class RtpProcessHelper : public MediaSourceEvent , public std::enable_shared_from_this<RtpProcessHelper> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<RtpProcessHelper>;
|
||||
RtpProcessHelper(const std::string &stream_id, const std::weak_ptr<RtpSelector > &parent);
|
||||
~RtpProcessHelper();
|
||||
void attachEvent();
|
||||
RtpProcess::Ptr & getProcess();
|
||||
|
||||
protected:
|
||||
// 通知其停止推流
|
||||
bool close(MediaSource &sender) override;
|
||||
|
||||
private:
|
||||
std::string _stream_id;
|
||||
RtpProcess::Ptr _process;
|
||||
std::weak_ptr<RtpSelector> _parent;
|
||||
};
|
||||
|
||||
class RtpSelector : public std::enable_shared_from_this<RtpSelector>{
|
||||
public:
|
||||
class ProcessExisted : public std::runtime_error {
|
||||
public:
|
||||
template<typename ...T>
|
||||
ProcessExisted(T && ...args) : std::runtime_error(std::forward<T>(args)...) {}
|
||||
};
|
||||
|
||||
static bool getSSRC(const char *data,size_t data_len, uint32_t &ssrc);
|
||||
static RtpSelector &Instance();
|
||||
|
||||
/**
|
||||
* 清空所有对象
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* 获取一个rtp处理器
|
||||
* @param stream_id 流id
|
||||
* @param makeNew 不存在时是否新建, 该参数为true时,必须确保之前未创建同名对象
|
||||
* @return rtp处理器
|
||||
*/
|
||||
RtpProcess::Ptr getProcess(const std::string &stream_id, bool makeNew);
|
||||
|
||||
/**
|
||||
* 删除rtp处理器
|
||||
* @param stream_id 流id
|
||||
* @param ptr rtp处理器指针
|
||||
*/
|
||||
void delProcess(const std::string &stream_id, const RtpProcess *ptr);
|
||||
|
||||
void addStreamReplace(const std::string &stream_id, const std::string &stream_replace);
|
||||
|
||||
private:
|
||||
void onManager();
|
||||
void createTimer();
|
||||
void delStreamReplace(const std::string &stream_id);
|
||||
|
||||
private:
|
||||
toolkit::Timer::Ptr _timer;
|
||||
std::recursive_mutex _mtx_map;
|
||||
std::unordered_map<std::string,RtpProcessHelper::Ptr> _map_rtp_process;
|
||||
std::unordered_map<std::string,std::string> _map_stream_replace;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
#endif //ZLMEDIAKIT_RTPSELECTOR_H
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
#if defined(ENABLE_RTPPROXY)
|
||||
#include "Util/uv_errno.h"
|
||||
#include "RtpServer.h"
|
||||
#include "RtpSelector.h"
|
||||
#include "RtpProcess.h"
|
||||
#include "Rtcp/RtcpContext.h"
|
||||
#include "Common/config.h"
|
||||
|
||||
|
|
@ -30,43 +30,39 @@ class RtcpHelper: public std::enable_shared_from_this<RtcpHelper> {
|
|||
public:
|
||||
using Ptr = std::shared_ptr<RtcpHelper>;
|
||||
|
||||
RtcpHelper(Socket::Ptr rtcp_sock, std::string stream_id) {
|
||||
RtcpHelper(Socket::Ptr rtcp_sock, MediaTuple tuple) {
|
||||
_rtcp_sock = std::move(rtcp_sock);
|
||||
_stream_id = std::move(stream_id);
|
||||
}
|
||||
|
||||
~RtcpHelper() {
|
||||
if (_process) {
|
||||
// 删除rtp处理器
|
||||
RtpSelector::Instance().delProcess(_stream_id, _process.get());
|
||||
}
|
||||
_tuple = std::move(tuple);
|
||||
}
|
||||
|
||||
void setRtpServerInfo(uint16_t local_port, RtpServer::TcpMode mode, bool re_use_port, uint32_t ssrc, int only_track) {
|
||||
_local_port = local_port;
|
||||
_tcp_mode = mode;
|
||||
_re_use_port = re_use_port;
|
||||
_ssrc = ssrc;
|
||||
_only_track = only_track;
|
||||
_process = RtpProcess::createProcess(_tuple);
|
||||
_process->setOnlyTrack((RtpProcess::OnlyTrack)only_track);
|
||||
|
||||
_timeout_cb = [=]() mutable {
|
||||
NOTICE_EMIT(BroadcastRtpServerTimeoutArgs, Broadcast::kBroadcastRtpServerTimeout, local_port, _tuple, (int)mode, re_use_port, ssrc);
|
||||
};
|
||||
|
||||
weak_ptr<RtcpHelper> weak_self = shared_from_this();
|
||||
_process->setOnDetach([weak_self](const SockException &ex) {
|
||||
if (auto strong_self = weak_self.lock()) {
|
||||
if (strong_self->_on_detach) {
|
||||
strong_self->_on_detach(ex);
|
||||
}
|
||||
if (ex.getErrCode() == Err_timeout) {
|
||||
strong_self->_timeout_cb();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setOnDetach(function<void()> cb) {
|
||||
if (_process) {
|
||||
_process->setOnDetach(std::move(cb));
|
||||
} else {
|
||||
_on_detach = std::move(cb);
|
||||
}
|
||||
}
|
||||
void setOnDetach(RtpProcess::onDetachCB cb) { _on_detach = std::move(cb); }
|
||||
|
||||
RtpProcess::Ptr getProcess() const { return _process; }
|
||||
|
||||
void onRecvRtp(const Socket::Ptr &sock, const Buffer::Ptr &buf, struct sockaddr *addr) {
|
||||
if (!_process) {
|
||||
_process = RtpSelector::Instance().getProcess(_stream_id, true);
|
||||
_process->setOnlyTrack((RtpProcess::OnlyTrack)_only_track);
|
||||
_process->setOnDetach(std::move(_on_detach));
|
||||
cancelDelayTask();
|
||||
}
|
||||
_process->inputRtp(true, sock, buf->data(), buf->size(), addr);
|
||||
|
||||
// 统计rtp接受情况,用于发送rr包
|
||||
auto header = (RtpHeader *)buf->data();
|
||||
sendRtcp(ntohl(header->ssrc), addr);
|
||||
|
|
@ -92,37 +88,12 @@ public:
|
|||
// 收到sr rtcp后驱动返回rr rtcp
|
||||
strong_self->sendRtcp(strong_self->_ssrc, (struct sockaddr *)(strong_self->_rtcp_addr.get()));
|
||||
});
|
||||
|
||||
GET_CONFIG(uint64_t, timeoutSec, RtpProxy::kTimeoutSec);
|
||||
_delay_task = _rtcp_sock->getPoller()->doDelayTask(timeoutSec * 1000, [weak_self]() {
|
||||
if (auto strong_self = weak_self.lock()) {
|
||||
auto process = RtpSelector::Instance().getProcess(strong_self->_stream_id, false);
|
||||
if (!process && strong_self->_on_detach) {
|
||||
strong_self->_on_detach();
|
||||
}
|
||||
if(process && strong_self->_on_detach){// tcp 链接防止断开不删除rtpServer
|
||||
process->setOnDetach(std::move(strong_self->_on_detach));
|
||||
}
|
||||
if (!process) { // process 未创建,触发rtp server 超时事件
|
||||
NOTICE_EMIT(BroadcastRtpServerTimeoutArgs, Broadcast::kBroadcastRtpServerTimeout, strong_self->_local_port, strong_self->_stream_id,
|
||||
(int)strong_self->_tcp_mode, strong_self->_re_use_port, strong_self->_ssrc);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
void cancelDelayTask() {
|
||||
if (_delay_task) {
|
||||
_delay_task->cancel();
|
||||
_delay_task = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void sendRtcp(uint32_t rtp_ssrc, struct sockaddr *addr) {
|
||||
// 每5秒发送一次rtcp
|
||||
if (_ticker.elapsedTime() < 5000 || !_process) {
|
||||
if (_ticker.elapsedTime() < 5000) {
|
||||
return;
|
||||
}
|
||||
_ticker.resetTime();
|
||||
|
|
@ -141,25 +112,21 @@ private:
|
|||
}
|
||||
|
||||
private:
|
||||
bool _re_use_port = false;
|
||||
int _only_track = 0;
|
||||
uint16_t _local_port = 0;
|
||||
uint32_t _ssrc = 0;
|
||||
RtpServer::TcpMode _tcp_mode = RtpServer::NONE;
|
||||
|
||||
std::function<void()> _timeout_cb;
|
||||
Ticker _ticker;
|
||||
Socket::Ptr _rtcp_sock;
|
||||
RtpProcess::Ptr _process;
|
||||
std::string _stream_id;
|
||||
function<void()> _on_detach;
|
||||
MediaTuple _tuple;
|
||||
RtpProcess::onDetachCB _on_detach;
|
||||
std::shared_ptr<struct sockaddr_storage> _rtcp_addr;
|
||||
EventPoller::DelayTask::Ptr _delay_task;
|
||||
};
|
||||
|
||||
void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_mode, const char *local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex) {
|
||||
void RtpServer::start(uint16_t local_port, const MediaTuple &tuple, TcpMode tcp_mode, const char *local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex) {
|
||||
//创建udp服务器
|
||||
Socket::Ptr rtp_socket = Socket::createSocket(nullptr, true);
|
||||
Socket::Ptr rtcp_socket = Socket::createSocket(nullptr, true);
|
||||
auto poller = EventPollerPool::Instance().getPoller();
|
||||
Socket::Ptr rtp_socket = Socket::createSocket(poller, true);
|
||||
Socket::Ptr rtcp_socket = Socket::createSocket(poller, true);
|
||||
if (local_port == 0) {
|
||||
//随机端口,rtp端口采用偶数
|
||||
auto pair = std::make_pair(rtp_socket, rtcp_socket);
|
||||
|
|
@ -177,29 +144,13 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_
|
|||
GET_CONFIG(int, udpRecvSocketBuffer, RtpProxy::kUdpRecvSocketBuffer);
|
||||
SockUtil::setRecvBuf(rtp_socket->rawFD(), udpRecvSocketBuffer);
|
||||
|
||||
TcpServer::Ptr tcp_server;
|
||||
_tcp_mode = tcp_mode;
|
||||
if (tcp_mode == PASSIVE || tcp_mode == ACTIVE) {
|
||||
//创建tcp服务器
|
||||
tcp_server = std::make_shared<TcpServer>(rtp_socket->getPoller());
|
||||
(*tcp_server)[RtpSession::kStreamID] = stream_id;
|
||||
(*tcp_server)[RtpSession::kSSRC] = ssrc;
|
||||
(*tcp_server)[RtpSession::kOnlyTrack] = only_track;
|
||||
if (tcp_mode == PASSIVE) {
|
||||
tcp_server->start<RtpSession>(local_port, local_ip);
|
||||
} else if (stream_id.empty()) {
|
||||
// tcp主动模式时只能一个端口一个流,必须指定流id; 创建TcpServer对象也仅用于传参
|
||||
throw std::runtime_error(StrPrinter << "tcp主动模式时必需指定流id");
|
||||
}
|
||||
}
|
||||
|
||||
//创建udp服务器
|
||||
UdpServer::Ptr udp_server;
|
||||
RtcpHelper::Ptr helper;
|
||||
//增加了多路复用判断,如果多路复用为true,就走else逻辑,同时保留了原来stream_id为空走else逻辑
|
||||
if (!stream_id.empty() && !multiplex) {
|
||||
if (!tuple.stream.empty() && !multiplex) {
|
||||
//指定了流id,那么一个端口一个流(不管是否包含多个ssrc的多个流,绑定rtp源后,会筛选掉ip端口不匹配的流)
|
||||
helper = std::make_shared<RtcpHelper>(std::move(rtcp_socket), stream_id);
|
||||
helper = std::make_shared<RtcpHelper>(std::move(rtcp_socket), tuple);
|
||||
helper->startRtcp();
|
||||
helper->setRtpServerInfo(local_port, tcp_mode, re_use_port, ssrc, only_track);
|
||||
bool bind_peer_addr = false;
|
||||
|
|
@ -222,14 +173,35 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_
|
|||
});
|
||||
} else {
|
||||
//单端口多线程接收多个流,根据ssrc区分流
|
||||
udp_server = std::make_shared<UdpServer>(rtp_socket->getPoller());
|
||||
udp_server = std::make_shared<UdpServer>();
|
||||
(*udp_server)[RtpSession::kOnlyTrack] = only_track;
|
||||
(*udp_server)[RtpSession::kUdpRecvBuffer] = udpRecvSocketBuffer;
|
||||
udp_server->start<RtpSession>(local_port, local_ip);
|
||||
rtp_socket = nullptr;
|
||||
}
|
||||
|
||||
_on_cleanup = [rtp_socket, stream_id]() {
|
||||
TcpServer::Ptr tcp_server;
|
||||
if (tcp_mode == PASSIVE || tcp_mode == ACTIVE) {
|
||||
auto processor = helper ? helper->getProcess() : nullptr;
|
||||
// 如果共享同一个processor对象,那么tcp server深圳为单线程模式确保线程安全
|
||||
tcp_server = std::make_shared<TcpServer>(processor ? poller : nullptr);
|
||||
(*tcp_server)[RtpSession::kVhost] = tuple.vhost;
|
||||
(*tcp_server)[RtpSession::kApp] = tuple.app;
|
||||
(*tcp_server)[RtpSession::kStreamID] = tuple.stream;
|
||||
(*tcp_server)[RtpSession::kSSRC] = ssrc;
|
||||
(*tcp_server)[RtpSession::kOnlyTrack] = only_track;
|
||||
if (tcp_mode == PASSIVE) {
|
||||
weak_ptr<RtpServer> weak_self = shared_from_this();
|
||||
tcp_server->start<RtpSession>(local_port, local_ip, 1024, [weak_self, processor](std::shared_ptr<RtpSession> &session) {
|
||||
session->setRtpProcess(processor);
|
||||
});
|
||||
} else if (tuple.stream.empty()) {
|
||||
// tcp主动模式时只能一个端口一个流,必须指定流id; 创建TcpServer对象也仅用于传参
|
||||
throw std::runtime_error(StrPrinter << "tcp主动模式时必需指定流id");
|
||||
}
|
||||
}
|
||||
|
||||
_on_cleanup = [rtp_socket]() {
|
||||
if (rtp_socket) {
|
||||
//去除循环引用
|
||||
rtp_socket->setOnRead(nullptr);
|
||||
|
|
@ -240,9 +212,10 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_
|
|||
_udp_server = udp_server;
|
||||
_rtp_socket = rtp_socket;
|
||||
_rtcp_helper = helper;
|
||||
_tcp_mode = tcp_mode;
|
||||
}
|
||||
|
||||
void RtpServer::setOnDetach(function<void()> cb) {
|
||||
void RtpServer::setOnDetach(RtpProcess::onDetachCB cb) {
|
||||
if (_rtcp_helper) {
|
||||
_rtcp_helper->setOnDetach(std::move(cb));
|
||||
}
|
||||
|
|
@ -277,6 +250,7 @@ void RtpServer::connectToServer(const std::string &url, uint16_t port, const fun
|
|||
|
||||
void RtpServer::onConnect() {
|
||||
auto rtp_session = std::make_shared<RtpSession>(_rtp_socket);
|
||||
rtp_session->setRtpProcess(_rtcp_helper->getProcess());
|
||||
rtp_session->attachServer(*_tcp_server);
|
||||
_rtp_socket->setOnRead([rtp_session](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) {
|
||||
rtp_session->onRecv(buf);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public:
|
|||
* @param ssrc 指定的ssrc
|
||||
* @param multiplex 多路复用
|
||||
*/
|
||||
void start(uint16_t local_port, const std::string &stream_id = "", TcpMode tcp_mode = PASSIVE,
|
||||
void start(uint16_t local_port, const MediaTuple &tuple = MediaTuple{DEFAULT_VHOST, kRtpAppName, "", ""}, TcpMode tcp_mode = PASSIVE,
|
||||
const char *local_ip = "::", bool re_use_port = true, uint32_t ssrc = 0, int only_track = 0, bool multiplex = false);
|
||||
|
||||
/**
|
||||
|
|
@ -62,7 +62,7 @@ public:
|
|||
/**
|
||||
* 设置RtpProcess onDetach事件回调
|
||||
*/
|
||||
void setOnDetach(std::function<void()> cb);
|
||||
void setOnDetach(RtpProcess::onDetachCB cb);
|
||||
|
||||
/**
|
||||
* 更新ssrc
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
#include "RtpSession.h"
|
||||
#include "RtpSelector.h"
|
||||
#include "RtpProcess.h"
|
||||
#include "Network/TcpServer.h"
|
||||
#include "Rtsp/Rtsp.h"
|
||||
#include "Rtsp/RtpReceiver.h"
|
||||
|
|
@ -21,6 +21,8 @@ using namespace toolkit;
|
|||
|
||||
namespace mediakit{
|
||||
|
||||
const string RtpSession::kVhost = "vhost";
|
||||
const string RtpSession::kApp = "app";
|
||||
const string RtpSession::kStreamID = "stream_id";
|
||||
const string RtpSession::kSSRC = "ssrc";
|
||||
const string RtpSession::kOnlyTrack = "only_track";
|
||||
|
|
@ -31,7 +33,9 @@ void RtpSession::attachServer(const Server &server) {
|
|||
}
|
||||
|
||||
void RtpSession::setParams(mINI &ini) {
|
||||
_stream_id = ini[kStreamID];
|
||||
_tuple.vhost = ini[kVhost];
|
||||
_tuple.app = ini[kApp];
|
||||
_tuple.stream = ini[kStreamID];
|
||||
_ssrc = ini[kSSRC];
|
||||
_only_track = ini[kOnlyTrack];
|
||||
int udp_socket_buffer = ini[kUdpRecvBuffer];
|
||||
|
|
@ -60,28 +64,24 @@ void RtpSession::onRecv(const Buffer::Ptr &data) {
|
|||
}
|
||||
|
||||
void RtpSession::onError(const SockException &err) {
|
||||
WarnP(this) << _stream_id << " " << err;
|
||||
if (_process) {
|
||||
RtpSelector::Instance().delProcess(_stream_id, _process.get());
|
||||
_process = nullptr;
|
||||
if (_emit_detach) {
|
||||
_process->onDetach(err);
|
||||
}
|
||||
WarnP(this) << _tuple.shortUrl() << " " << err;
|
||||
}
|
||||
|
||||
void RtpSession::onManager() {
|
||||
if (_process && !_process->alive()) {
|
||||
shutdown(SockException(Err_timeout, "receive rtp timeout"));
|
||||
}
|
||||
|
||||
if (!_process && _ticker.createdTime() > 10 * 1000) {
|
||||
shutdown(SockException(Err_timeout, "illegal connection"));
|
||||
}
|
||||
}
|
||||
|
||||
void RtpSession::setRtpProcess(RtpProcess::Ptr process) {
|
||||
_emit_detach = (bool)process;
|
||||
_process = std::move(process);
|
||||
}
|
||||
|
||||
void RtpSession::onRtpPacket(const char *data, size_t len) {
|
||||
if (_delay_close) {
|
||||
// 正在延时关闭中,忽略所有数据
|
||||
return;
|
||||
}
|
||||
if (!isRtp(data, len)) {
|
||||
// 忽略非rtp数据
|
||||
WarnP(this) << "Not rtp packet";
|
||||
|
|
@ -104,33 +104,31 @@ void RtpSession::onRtpPacket(const char *data, size_t len) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 未设置ssrc时,尝试获取ssrc
|
||||
if (!_ssrc && !getSSRC(data, len, _ssrc)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 未指定流id就使用ssrc为流id
|
||||
if (_tuple.stream.empty()) {
|
||||
_tuple.stream = printSSRC(_ssrc);
|
||||
}
|
||||
|
||||
if (!_process) {
|
||||
//未设置ssrc时,尝试获取ssrc
|
||||
if (!_ssrc && !RtpSelector::getSSRC(data, len, _ssrc)) {
|
||||
return;
|
||||
}
|
||||
if (_stream_id.empty()) {
|
||||
//未指定流id就使用ssrc为流id
|
||||
_stream_id = printSSRC(_ssrc);
|
||||
}
|
||||
try {
|
||||
_process = RtpSelector::Instance().getProcess(_stream_id, true);
|
||||
} catch (RtpSelector::ProcessExisted &ex) {
|
||||
if (!_is_udp) {
|
||||
// tcp情况下立即断开连接
|
||||
throw;
|
||||
}
|
||||
// udp情况下延时断开连接(等待超时自动关闭),防止频繁创建销毁RtpSession对象
|
||||
WarnP(this) << ex.what();
|
||||
_delay_close = true;
|
||||
return;
|
||||
}
|
||||
_process = RtpProcess::createProcess(_tuple);
|
||||
_process->setOnlyTrack((RtpProcess::OnlyTrack)_only_track);
|
||||
_process->setDelegate(static_pointer_cast<RtpSession>(shared_from_this()));
|
||||
weak_ptr<RtpSession> weak_self = static_pointer_cast<RtpSession>(shared_from_this());
|
||||
_process->setOnDetach([weak_self](const SockException &ex) {
|
||||
if (auto strong_self = weak_self.lock()) {
|
||||
strong_self->_process = nullptr;
|
||||
strong_self->shutdown(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
uint32_t rtp_ssrc = 0;
|
||||
RtpSelector::getSSRC(data, len, rtp_ssrc);
|
||||
getSSRC(data, len, rtp_ssrc);
|
||||
if (rtp_ssrc != _ssrc) {
|
||||
WarnP(this) << "ssrc mismatched, rtp dropped: " << rtp_ssrc << " != " << _ssrc;
|
||||
return;
|
||||
|
|
@ -143,26 +141,10 @@ void RtpSession::onRtpPacket(const char *data, size_t len) {
|
|||
} else {
|
||||
throw;
|
||||
}
|
||||
} catch (std::exception &ex) {
|
||||
if (!_is_udp) {
|
||||
// tcp情况下立即断开连接
|
||||
throw;
|
||||
}
|
||||
// udp情况下延时断开连接(等待超时自动关闭),防止频繁创建销毁RtpSession对象
|
||||
WarnP(this) << ex.what();
|
||||
_delay_close = true;
|
||||
return;
|
||||
}
|
||||
_ticker.resetTime();
|
||||
}
|
||||
|
||||
bool RtpSession::close(MediaSource &sender) {
|
||||
//此回调在其他线程触发
|
||||
string err = StrPrinter << "close media: " << sender.getUrl();
|
||||
safeShutdown(SockException(Err_shutdown, err));
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *findSSRC(const char *data, ssize_t len, uint32_t ssrc) {
|
||||
// rtp前面必须预留两个字节的长度字段
|
||||
for (ssize_t i = 2; i <= len - 4; ++i) {
|
||||
|
|
@ -268,7 +250,7 @@ const char *RtpSession::searchByPsHeaderFlag(const char *data, size_t len) {
|
|||
|
||||
// TODO or Not ? 更新设置ssrc
|
||||
uint32_t rtp_ssrc = 0;
|
||||
RtpSelector::getSSRC(rtp_ptr + 2, len, rtp_ssrc);
|
||||
getSSRC(rtp_ptr + 2, len, rtp_ssrc);
|
||||
_ssrc = rtp_ssrc;
|
||||
InfoL << "设置_ssrc为:" << _ssrc;
|
||||
// RtpServer::updateSSRC(uint32_t ssrc)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@
|
|||
|
||||
namespace mediakit{
|
||||
|
||||
class RtpSession : public toolkit::Session, public RtpSplitter, public MediaSourceEvent {
|
||||
class RtpSession : public toolkit::Session, public RtpSplitter {
|
||||
public:
|
||||
static const std::string kVhost;
|
||||
static const std::string kApp;
|
||||
static const std::string kStreamID;
|
||||
static const std::string kSSRC;
|
||||
static const std::string kOnlyTrack;
|
||||
|
|
@ -34,10 +36,9 @@ public:
|
|||
void onManager() override;
|
||||
void setParams(toolkit::mINI &ini);
|
||||
void attachServer(const toolkit::Server &server) override;
|
||||
void setRtpProcess(RtpProcess::Ptr process);
|
||||
|
||||
protected:
|
||||
// 通知其停止推流
|
||||
bool close(MediaSource &sender) override;
|
||||
// 收到rtp回调
|
||||
void onRtpPacket(const char *data, size_t len) override;
|
||||
// RtpSplitter override
|
||||
|
|
@ -48,14 +49,14 @@ protected:
|
|||
const char *searchByPsHeaderFlag(const char *data, size_t len);
|
||||
|
||||
private:
|
||||
bool _delay_close = false;
|
||||
bool _is_udp = false;
|
||||
bool _search_rtp = false;
|
||||
bool _search_rtp_finished = false;
|
||||
bool _emit_detach = false;
|
||||
int _only_track = 0;
|
||||
uint32_t _ssrc = 0;
|
||||
toolkit::Ticker _ticker;
|
||||
std::string _stream_id;
|
||||
MediaTuple _tuple;
|
||||
struct sockaddr_storage _addr;
|
||||
RtpProcess::Ptr _process;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -144,7 +144,16 @@ RtpMultiCaster::RtpMultiCaster(SocketHelper &helper, const string &local_ip, con
|
|||
});
|
||||
});
|
||||
|
||||
_rtp_reader->setDetachCB([this]() {
|
||||
string strKey = StrPrinter << local_ip << " " << vhost << " " << app << " " << stream << endl;
|
||||
_rtp_reader->setDetachCB([this, strKey]() {
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(g_mtx);
|
||||
auto it = g_multi_caster_map.find(strKey);
|
||||
if (it != g_multi_caster_map.end()) {
|
||||
g_multi_caster_map.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
unordered_map<void *, onDetach> _detach_map_copy;
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(_mtx);
|
||||
|
|
|
|||
|
|
@ -470,6 +470,15 @@ string printSSRC(uint32_t ui32Ssrc) {
|
|||
return tmp;
|
||||
}
|
||||
|
||||
bool getSSRC(const char *data, size_t data_len, uint32_t &ssrc) {
|
||||
if (data_len < 12) {
|
||||
return false;
|
||||
}
|
||||
uint32_t *ssrc_ptr = (uint32_t *)(data + 8);
|
||||
ssrc = ntohl(*ssrc_ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isRtp(const char *buf, size_t size) {
|
||||
if (size < 2) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@ toolkit::Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved);
|
|||
void makeSockPair(std::pair<toolkit::Socket::Ptr, toolkit::Socket::Ptr> &pair, const std::string &local_ip, bool re_use_port = false, bool is_udp = true);
|
||||
// 十六进制方式打印ssrc
|
||||
std::string printSSRC(uint32_t ui32Ssrc);
|
||||
bool getSSRC(const char *data, size_t data_len, uint32_t &ssrc);
|
||||
|
||||
bool isRtp(const char *buf, size_t size);
|
||||
bool isRtcp(const char *buf, size_t size);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,13 @@ public:
|
|||
*/
|
||||
RtspMediaSource(const MediaTuple& tuple, int ring_size = RTP_GOP_SIZE): MediaSource(RTSP_SCHEMA, tuple), _ring_size(ring_size) {}
|
||||
|
||||
~RtspMediaSource() override { flush(); }
|
||||
~RtspMediaSource() override {
|
||||
try {
|
||||
flush();
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
|
|
|
|||
|
|
@ -29,7 +29,13 @@ public:
|
|||
getRtpRing()->setDelegate(_media_src);
|
||||
}
|
||||
|
||||
~RtspMediaSourceMuxer() override { RtspMuxer::flush(); }
|
||||
~RtspMediaSourceMuxer() override {
|
||||
try {
|
||||
RtspMuxer::flush();
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
setDelegate(listener);
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ void RtspPlayer::play(const string &strUrl) {
|
|||
_rtp_type = (Rtsp::eRtpType)(int)(*this)[Client::kRtpType];
|
||||
_beat_type = (*this)[Client::kRtspBeatType].as<int>();
|
||||
_beat_interval_ms = (*this)[Client::kBeatIntervalMS].as<int>();
|
||||
_speed = (*this)[Client::kRtspSpeed].as<float>();
|
||||
DebugL << url._url << " " << (url._user.size() ? url._user : "null") << " " << (url._passwd.size() ? url._passwd : "null") << " " << _rtp_type;
|
||||
|
||||
weak_ptr<RtspPlayer> weakSelf = static_pointer_cast<RtspPlayer>(shared_from_this());
|
||||
|
|
@ -256,17 +257,19 @@ void RtspPlayer::sendSetup(unsigned int track_idx) {
|
|||
switch (_rtp_type) {
|
||||
case Rtsp::RTP_TCP: {
|
||||
sendRtspRequest(
|
||||
"SETUP", control_url, { "Transport", StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 << "-" << track->_type * 2 + 1 });
|
||||
"SETUP", control_url,
|
||||
{ "Transport", StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 << "-" << track->_type * 2 + 1 << ";mode=play" });
|
||||
} break;
|
||||
case Rtsp::RTP_MULTICAST: {
|
||||
sendRtspRequest("SETUP", control_url, { "Transport", "RTP/AVP;multicast" });
|
||||
sendRtspRequest("SETUP", control_url, { "Transport", "RTP/AVP;multicast;mode=play" });
|
||||
} break;
|
||||
case Rtsp::RTP_UDP: {
|
||||
createUdpSockIfNecessary(track_idx);
|
||||
sendRtspRequest(
|
||||
"SETUP", control_url,
|
||||
{ "Transport",
|
||||
StrPrinter << "RTP/AVP;unicast;client_port=" << _rtp_sock[track_idx]->get_local_port() << "-" << _rtcp_sock[track_idx]->get_local_port() });
|
||||
StrPrinter << "RTP/AVP;unicast;client_port=" << _rtp_sock[track_idx]->get_local_port() << "-" << _rtcp_sock[track_idx]->get_local_port()
|
||||
<< ";mode=play" });
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
|
|
@ -387,7 +390,12 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) {
|
|||
}
|
||||
// 所有setup命令发送完毕
|
||||
// 发送play命令
|
||||
sendPause(type_play, 0);
|
||||
if (_speed==0.0f) {
|
||||
sendPause(type_play, 0);
|
||||
} else {
|
||||
sendPause(type_speed, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RtspPlayer::sendDescribe() {
|
||||
|
|
@ -436,6 +444,9 @@ void RtspPlayer::sendPause(int type, uint32_t seekMS) {
|
|||
case type_seek:
|
||||
sendRtspRequest("PLAY", _control_url, { "Range", StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-" });
|
||||
break;
|
||||
case type_speed:
|
||||
speed(_speed);
|
||||
break;
|
||||
default:
|
||||
WarnL << "unknown type : " << type;
|
||||
_on_response = nullptr;
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ private:
|
|||
uint32_t _beat_interval_ms = 0;
|
||||
|
||||
std::string _play_url;
|
||||
// rtsp开始倍速
|
||||
float _speed= 0.0f;
|
||||
std::vector<SdpTrack::Ptr> _sdp_track;
|
||||
std::function<void(const Parser&)> _on_response;
|
||||
//RTP端口,trackid idx 为数组下标
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "Util/util.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Common/macros.h"
|
||||
#include "Rtsp/RtpReceiver.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
|
@ -59,7 +60,11 @@ const char *RtspSplitter::onSearchPacketTail_l(const char *data, size_t len) {
|
|||
|
||||
ssize_t RtspSplitter::onRecvHeader(const char *data, size_t len) {
|
||||
if (_isRtpPacket) {
|
||||
onRtpPacket(data, len);
|
||||
try {
|
||||
onRtpPacket(data, len);
|
||||
} catch (RtpTrack::BadRtpException &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (len == 4 && !memcmp(data, "\r\n\r\n", 4)) {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,13 @@ public:
|
|||
|
||||
TSMediaSource(const MediaTuple& tuple, int ring_size = TS_GOP_SIZE): MediaSource(TS_SCHEMA, tuple), _ring_size(ring_size) {}
|
||||
|
||||
~TSMediaSource() override { flush(); }
|
||||
~TSMediaSource() override {
|
||||
try {
|
||||
flush();
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include "Thread/WorkThreadPool.h"
|
||||
#include "Pusher/MediaPusher.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
|
||||
#include "Record/MP4Reader.h"
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
|
@ -52,7 +52,7 @@ public:
|
|||
Option::ArgRequired,/*该选项后面必须跟值*/
|
||||
nullptr,/*该选项默认值*/
|
||||
true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
|
||||
"拉流url,支持rtsp/rtmp/hls",/*该选项说明文字*/
|
||||
"拉流url,支持rtsp/rtmp/hls/mp4文件",/*该选项说明文字*/
|
||||
nullptr);
|
||||
|
||||
(*_parser) << Option('o',/*该选项简称,如果是\x00则说明无简称*/
|
||||
|
|
@ -92,18 +92,16 @@ public:
|
|||
Option::ArgRequired,/*该选项后面必须跟值*/
|
||||
to_string((int) (Rtsp::RTP_TCP)).data(),/*该选项默认值*/
|
||||
true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
|
||||
"rtsp拉流和推流方式,支持tcp/udp:0/1",/*该选项说明文字*/
|
||||
nullptr);
|
||||
"rtsp拉流和推流方式,支持tcp/udp:0/1", /*该选项说明文字*/
|
||||
nullptr);
|
||||
}
|
||||
|
||||
~CMD_main() override {}
|
||||
|
||||
const char *description() const override {
|
||||
return "主程序命令参数";
|
||||
}
|
||||
const char *description() const override { return "主程序命令参数"; }
|
||||
};
|
||||
|
||||
//此程序用于推流性能测试
|
||||
// 此程序用于推流性能测试
|
||||
int main(int argc, char *argv[]) {
|
||||
CMD_main cmd_main;
|
||||
try {
|
||||
|
|
@ -116,7 +114,7 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
int threads = cmd_main["threads"];
|
||||
LogLevel logLevel = (LogLevel) cmd_main["level"].as<int>();
|
||||
LogLevel logLevel = (LogLevel)cmd_main["level"].as<int>();
|
||||
logLevel = MIN(MAX(logLevel, LTrace), LError);
|
||||
auto in_url = cmd_main["in"];
|
||||
auto out_url = cmd_main["out"];
|
||||
|
|
@ -129,6 +127,8 @@ int main(int argc, char *argv[]) {
|
|||
cout << "推流协议只支持rtsp或rtmp!" << endl;
|
||||
return -1;
|
||||
}
|
||||
const std::string app = "app";
|
||||
const std::string stream = "test";
|
||||
|
||||
//设置日志
|
||||
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel", logLevel));
|
||||
|
|
@ -145,22 +145,39 @@ int main(int argc, char *argv[]) {
|
|||
ProtocolOption option;
|
||||
option.enable_hls = false;
|
||||
option.enable_mp4 = false;
|
||||
MediaSource::Ptr src = nullptr;
|
||||
PlayerProxy::Ptr proxy = nullptr;;
|
||||
|
||||
//添加拉流代理
|
||||
auto proxy = std::make_shared<PlayerProxy>(DEFAULT_VHOST, "app", "test", option);
|
||||
//rtsp拉流代理方式
|
||||
(*proxy)[Client::kRtpType] = rtp_type;
|
||||
//开始拉流代理
|
||||
proxy->play(in_url);
|
||||
if (end_with(in_url, ".mp4")) {
|
||||
// create MediaSource from mp4file
|
||||
auto reader = std::make_shared<MP4Reader>(DEFAULT_VHOST, app, stream, in_url);
|
||||
//mp4 repeat
|
||||
reader->startReadMP4(0, true, true);
|
||||
src = MediaSource::find(schema, DEFAULT_VHOST, app, stream, false);
|
||||
if (!src) {
|
||||
// mp4文件不存在
|
||||
WarnL << "no such file or directory: " << in_url;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
//添加拉流代理
|
||||
proxy = std::make_shared<PlayerProxy>(DEFAULT_VHOST, app, stream, option);
|
||||
//rtsp拉流代理方式
|
||||
(*proxy)[Client::kRtpType] = rtp_type;
|
||||
//开始拉流代理
|
||||
proxy->play(in_url);
|
||||
|
||||
auto get_src = [schema]() {
|
||||
return MediaSource::find(schema, DEFAULT_VHOST, "app", "test", false);
|
||||
}
|
||||
|
||||
auto get_src = [schema,app,stream]() {
|
||||
return MediaSource::find(schema, DEFAULT_VHOST, app, stream, false);
|
||||
};
|
||||
|
||||
//推流器map
|
||||
recursive_mutex mtx;
|
||||
unordered_map<void *, MediaPusher::Ptr> pusher_map;
|
||||
|
||||
|
||||
auto add_pusher = [&](const MediaSource::Ptr &src, const string &rand_str, size_t index) {
|
||||
auto pusher = std::make_shared<MediaPusher>(src);
|
||||
auto tag = pusher.get();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
#include "Rtsp/RtspSession.h"
|
||||
#include "Rtmp/RtmpSession.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Rtp/RtpSelector.h"
|
||||
#include "Rtp/RtpProcess.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
|
@ -42,7 +42,7 @@ static bool loadFile(const char *path, const EventPoller::Ptr &poller) {
|
|||
memset(&addr, 0, sizeof(addr));
|
||||
addr.ss_family = AF_INET;
|
||||
auto sock = Socket::createSocket(poller);
|
||||
auto process = RtpSelector::Instance().getProcess("test", true);
|
||||
auto process = RtpProcess::createProcess(MediaTuple { DEFAULT_VHOST, kRtpAppName, "test", "" });
|
||||
|
||||
uint64_t stamp_last = 0;
|
||||
auto total_size = std::make_shared<size_t>(0);
|
||||
|
|
@ -89,7 +89,6 @@ static bool loadFile(const char *path, const EventPoller::Ptr &poller) {
|
|||
auto ret = do_read();
|
||||
if (!ret) {
|
||||
WarnL << *total_size / 1024 << "KB";
|
||||
RtpSelector::Instance().delProcess("test", process.get());
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@ namespace RTC
|
|||
});
|
||||
// Set ciphers.
|
||||
ret = SSL_CTX_set_cipher_list(
|
||||
sslCtx, "DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK");
|
||||
sslCtx, "DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK:!RC4");
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,6 +20,28 @@
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
// RTC配置项目
|
||||
namespace Rtc {
|
||||
|
||||
//~ nack接收端(rtp发送端)
|
||||
// Nack缓存包最早时间间隔
|
||||
extern const std::string kMaxNackMS;
|
||||
// Nack包检查间隔(包数量)
|
||||
extern const std::string kRtpCacheCheckInterval;
|
||||
|
||||
//~ nack发送端(rtp接收端)
|
||||
// 最大保留的rtp丢包状态个数
|
||||
extern const std::string kNackMaxSize;
|
||||
// rtp丢包状态最长保留时间
|
||||
extern const std::string kNackMaxMS;
|
||||
// nack最多请求重传次数
|
||||
extern const std::string kNackMaxCount;
|
||||
// nack重传频率,rtt的倍数
|
||||
extern const std::string kNackIntervalRatio;
|
||||
// nack包中rtp个数,减小此值可以让nack包响应更灵敏
|
||||
extern const std::string kNackRtpSize;
|
||||
} // namespace Rtc
|
||||
|
||||
class NackList {
|
||||
public:
|
||||
void pushBack(RtpPacket::Ptr rtp);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@
|
|||
*/
|
||||
|
||||
#include "WebRtcPlayer.h"
|
||||
|
||||
#include "Common/config.h"
|
||||
#include "Extension/Factory.h"
|
||||
#include "Util/base64.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
|
@ -32,6 +35,9 @@ WebRtcPlayer::WebRtcPlayer(const EventPoller::Ptr &poller,
|
|||
_media_info = info;
|
||||
_play_src = src;
|
||||
CHECK(src);
|
||||
|
||||
GET_CONFIG(bool, direct_proxy, Rtsp::kDirectProxy);
|
||||
_send_config_frames_once = direct_proxy;
|
||||
}
|
||||
|
||||
void WebRtcPlayer::onStartWebRTC() {
|
||||
|
|
@ -56,6 +62,13 @@ void WebRtcPlayer::onStartWebRTC() {
|
|||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strong_self->_send_config_frames_once && !pkt->empty()) {
|
||||
const auto &first_rtp = pkt->front();
|
||||
strong_self->sendConfigFrames(first_rtp->getSeq(), first_rtp->sample_rate, first_rtp->getStamp(), first_rtp->ntp_stamp);
|
||||
strong_self->_send_config_frames_once = false;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
pkt->for_each([&](const RtpPacket::Ptr &rtp) {
|
||||
//TraceL<<"send track type:"<<rtp->type<<" ts:"<<rtp->getStamp()<<" ntp:"<<rtp->ntp_stamp<<" size:"<<rtp->getPayloadSize()<<" i:"<<i;
|
||||
|
|
@ -111,4 +124,41 @@ void WebRtcPlayer::onRtcConfigure(RtcConfigure &configure) const {
|
|||
configure.setPlayRtspInfo(playSrc->getSdp());
|
||||
}
|
||||
|
||||
void WebRtcPlayer::sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp) {
|
||||
auto play_src = _play_src.lock();
|
||||
if (!play_src) {
|
||||
return;
|
||||
}
|
||||
SdpParser parser(play_src->getSdp());
|
||||
auto video_sdp = parser.getTrack(TrackVideo);
|
||||
if (!video_sdp) {
|
||||
return;
|
||||
}
|
||||
auto video_track = dynamic_pointer_cast<VideoTrack>(Factory::getTrackBySdp(video_sdp));
|
||||
if (!video_track) {
|
||||
return;
|
||||
}
|
||||
auto frames = video_track->getConfigFrames();
|
||||
if (frames.empty()) {
|
||||
return;
|
||||
}
|
||||
auto encoder = mediakit::Factory::getRtpEncoderByCodecId(video_track->getCodecId(), 0);
|
||||
if (!encoder) {
|
||||
return;
|
||||
}
|
||||
|
||||
GET_CONFIG(uint32_t, video_mtu, Rtp::kVideoMtuSize);
|
||||
encoder->setRtpInfo(0, video_mtu, sample_rate, 0, 0, 0);
|
||||
|
||||
auto seq = before_seq - frames.size();
|
||||
for (const auto &frame : frames) {
|
||||
auto rtp = encoder->getRtpInfo().makeRtp(TrackVideo, frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), false, 0);
|
||||
auto header = rtp->getHeader();
|
||||
header->seq = htons(seq++);
|
||||
header->stamp = htonl(timestamp);
|
||||
rtp->ntp_stamp = ntp_timestamp;
|
||||
onSendRtp(rtp, false);
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace mediakit
|
||||
|
|
@ -31,11 +31,17 @@ protected:
|
|||
private:
|
||||
WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info);
|
||||
|
||||
void sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp);
|
||||
|
||||
private:
|
||||
//媒体相关元数据
|
||||
MediaInfo _media_info;
|
||||
//播放的rtsp源
|
||||
std::weak_ptr<RtspMediaSource> _play_src;
|
||||
|
||||
// rtp 直接转发情况下通常会缺少 sps/pps, 在转发 rtp 前, 先发送一次相关帧信息, 部分情况下是可以播放的
|
||||
bool _send_config_frames_once { false };
|
||||
|
||||
//播放rtsp源的reader对象
|
||||
RtspMediaSource::RingType::RingReader::Ptr _reader;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "Util/base64.h"
|
||||
#include "Network/sockutil.h"
|
||||
#include "Common/config.h"
|
||||
#include "Nack.h"
|
||||
#include "RtpExt.h"
|
||||
#include "Rtcp/Rtcp.h"
|
||||
#include "Rtcp/RtcpFCI.h"
|
||||
|
|
@ -57,9 +58,6 @@ const string kMinBitrate = RTC_FIELD "min_bitrate";
|
|||
// 数据通道设置
|
||||
const string kDataChannelEcho = RTC_FIELD "datachannel_echo";
|
||||
|
||||
// rtp丢包状态最长保留时间
|
||||
const string kNackMaxMS = RTC_FIELD "nackMaxMS";
|
||||
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kTimeOutSec] = 15;
|
||||
mINI::Instance()[kExternIP] = "";
|
||||
|
|
@ -72,8 +70,6 @@ static onceToken token([]() {
|
|||
mINI::Instance()[kMinBitrate] = 0;
|
||||
|
||||
mINI::Instance()[kDataChannelEcho] = true;
|
||||
|
||||
mINI::Instance()[kNackMaxMS] = 3 * 1000;
|
||||
});
|
||||
|
||||
} // namespace RTC
|
||||
|
|
@ -806,7 +802,8 @@ public:
|
|||
setOnSorted(std::move(cb));
|
||||
//设置jitter buffer参数
|
||||
GET_CONFIG(uint32_t, nack_maxms, Rtc::kNackMaxMS);
|
||||
RtpTrackImp::setParams(1024, nack_maxms, 512);
|
||||
GET_CONFIG(uint32_t, nack_max_rtp, Rtc::kNackMaxSize);
|
||||
RtpTrackImp::setParams(nack_max_rtp, nack_maxms, nack_max_rtp / 2);
|
||||
_nack_ctx.setOnNack([this](const FCI_NACK &nack) { onNack(nack); });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
/.idea/
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
/build
|
||||
.cxx
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-android-extensions'
|
||||
id 'kotlin-kapt'
|
||||
|
||||
}
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.zlmediakit.webrtc"
|
||||
minSdk 21
|
||||
targetSdk 32
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
|
||||
implementation("com.squareup.okhttp3:okhttp:4.10.0")
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation 'org.webrtc:google-webrtc:1.0.32006'
|
||||
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.zlmediakit.webrtc
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.zlmediakit.webrtc", appContext.packageName)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.zlmediakit.webrtc">
|
||||
|
||||
<uses-feature android:name="android.hardware.camera"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus"/>
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true"/>
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AndroidWebRTC"
|
||||
android:usesCleartextTraffic="true"
|
||||
|
||||
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
package com.zlmediakit.webrtc
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.activity_main.view.*
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private var isSpeaker = true
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
lifecycle.addObserver(web_rtc_sv)
|
||||
|
||||
//http://124.223.98.45/index/api/webrtc?app=live&stream=test&type=play
|
||||
url.setText("http://124.223.98.45/index/api/webrtc?app=live&stream=test&type=play")
|
||||
|
||||
//http://192.168.1.17/index/api/webrtc?app=live&stream=test&type=play
|
||||
btn_play.setOnClickListener {
|
||||
web_rtc_sv?.setVideoPath(url.text.toString())
|
||||
web_rtc_sv.start()
|
||||
}
|
||||
|
||||
web_rtc_sv.setOnErrorListener { errorCode, errorMsg ->
|
||||
runOnUiThread {
|
||||
Toast.makeText(this, "errorCode:$errorCode,errorMsg:$errorMsg", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
btn_pause.setOnClickListener {
|
||||
web_rtc_sv?.pause()
|
||||
}
|
||||
|
||||
btn_resume.setOnClickListener {
|
||||
web_rtc_sv?.resume()
|
||||
}
|
||||
|
||||
btn_screenshot.setOnClickListener {
|
||||
web_rtc_sv?.screenshot {
|
||||
runOnUiThread {
|
||||
iv_screen.setImageDrawable(BitmapDrawable(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btn_mute.setOnClickListener {
|
||||
web_rtc_sv.mute(true)
|
||||
}
|
||||
|
||||
|
||||
selectAudio()
|
||||
btn_speaker.setOnClickListener {
|
||||
selectAudio()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun selectAudio(){
|
||||
if (isSpeaker){
|
||||
btn_speaker.setText("扬声器")
|
||||
web_rtc_sv.setSpeakerphoneOn(isSpeaker)
|
||||
}else{
|
||||
btn_speaker.setText("话筒")
|
||||
web_rtc_sv.setSpeakerphoneOn(isSpeaker)
|
||||
}
|
||||
isSpeaker=!isSpeaker
|
||||
}
|
||||
}
|
||||
|
|
@ -1,439 +0,0 @@
|
|||
package com.zlmediakit.webrtc
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.media.AudioManager
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.gson.Gson
|
||||
import okhttp3.*
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import org.webrtc.*
|
||||
import org.webrtc.RendererCommon.ScalingType
|
||||
import org.webrtc.audio.AudioDeviceModule
|
||||
import org.webrtc.audio.JavaAudioDeviceModule
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
public class WebRTCSurfaceView(context: Context, attrs: AttributeSet?) :
|
||||
RelativeLayout(context, attrs), DefaultLifecycleObserver, RendererCommon.RendererEvents {
|
||||
|
||||
|
||||
private data class sdp(var sdp: String, var username: String, var password: String)
|
||||
|
||||
private data class SdpResponse(var code: Int, var id: String, var sdp: String, var type: String)
|
||||
|
||||
private enum class ErrorCode(val errorCode: Int) {
|
||||
SUCCESS(0x00),
|
||||
GET_REMOTE_SDP_ERROR(0x01);
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private val TAG = "WebRTCSurfaceView"
|
||||
|
||||
}
|
||||
|
||||
private var mContext: Context = context
|
||||
|
||||
private val eglBase: EglBase = EglBase.create()
|
||||
private var mEGLBaseContext: EglBase.Context = eglBase.eglBaseContext
|
||||
|
||||
private lateinit var videoUrl: String;
|
||||
|
||||
private var mPeerConnectionFactory: PeerConnectionFactory? = null
|
||||
|
||||
private var mLocalMediaStream: MediaStream? = null
|
||||
private var mLocalAudioTrack: AudioTrack? = null
|
||||
private var mAudioSource: AudioSource? = null
|
||||
|
||||
private var mLocalSessionDescription: SessionDescription? = null
|
||||
private var mRemoteSessionDescription: SessionDescription? = null
|
||||
|
||||
private var mLocalPeer: Peer? = null
|
||||
|
||||
private var mSurfaceViewRenderer: SurfaceViewRenderer
|
||||
|
||||
private lateinit var OnErrorListener: (errorCode: Int, errorMsg: String) -> Unit?
|
||||
|
||||
fun setOnErrorListener(listener: (errorCode: Int, errorMsg: String) -> Unit) {
|
||||
this.OnErrorListener = listener
|
||||
}
|
||||
|
||||
private lateinit var OnPreparedListener: () -> Unit?
|
||||
|
||||
fun setOnPreparedListener(listener: () -> Unit) {
|
||||
this.OnPreparedListener = listener
|
||||
}
|
||||
|
||||
private val audioManager: AudioManager
|
||||
|
||||
|
||||
init {
|
||||
|
||||
val view = LayoutInflater.from(mContext).inflate(R.layout.layout_videoview, this)
|
||||
|
||||
mPeerConnectionFactory = createConnectionFactory()
|
||||
|
||||
mSurfaceViewRenderer = view.findViewById(R.id.surface_view_renderer)
|
||||
|
||||
mSurfaceViewRenderer.init(mEGLBaseContext, this)
|
||||
mSurfaceViewRenderer.setScalingType(ScalingType.SCALE_ASPECT_FILL)
|
||||
mSurfaceViewRenderer.setEnableHardwareScaler(true)
|
||||
|
||||
|
||||
//创建媒体流
|
||||
mLocalMediaStream = mPeerConnectionFactory?.createLocalMediaStream("ARDAMS")
|
||||
//采集音频
|
||||
mAudioSource = mPeerConnectionFactory?.createAudioSource(createAudioConstraints())
|
||||
mLocalAudioTrack = mPeerConnectionFactory?.createAudioTrack("ARDAMSa0", mAudioSource)
|
||||
|
||||
//添加Tracks
|
||||
mLocalMediaStream?.addTrack(mLocalAudioTrack)
|
||||
|
||||
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
audioManager.isSpeakerphoneOn = false
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun set(width: Int, height: Int) {
|
||||
layoutParams.width = width
|
||||
layoutParams.height = height
|
||||
}
|
||||
|
||||
private fun createConnectionFactory(): PeerConnectionFactory? {
|
||||
|
||||
val options = PeerConnectionFactory.InitializationOptions.builder(mContext)
|
||||
.setEnableInternalTracer(false)
|
||||
.createInitializationOptions()
|
||||
|
||||
PeerConnectionFactory.initialize(options)
|
||||
|
||||
val videoEncoderFactory = DefaultVideoEncoderFactory(
|
||||
mEGLBaseContext,
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
val videoDecoderFactory = DefaultVideoDecoderFactory(mEGLBaseContext)
|
||||
|
||||
|
||||
val audioDevice = createJavaAudioDevice()
|
||||
val peerConnectionFactory = PeerConnectionFactory.builder()
|
||||
.setAudioDeviceModule(audioDevice)
|
||||
.setVideoEncoderFactory(videoEncoderFactory)
|
||||
.setVideoDecoderFactory(videoDecoderFactory)
|
||||
.createPeerConnectionFactory()
|
||||
audioDevice.release()
|
||||
|
||||
return peerConnectionFactory
|
||||
|
||||
}
|
||||
|
||||
private fun createAudioConstraints(): MediaConstraints {
|
||||
val audioConstraints = MediaConstraints()
|
||||
audioConstraints.mandatory.add(
|
||||
MediaConstraints.KeyValuePair(
|
||||
"googEchoCancellation",
|
||||
"true"
|
||||
)
|
||||
)
|
||||
audioConstraints.mandatory.add(
|
||||
MediaConstraints.KeyValuePair(
|
||||
"googAutoGainControl",
|
||||
"false"
|
||||
)
|
||||
)
|
||||
audioConstraints.mandatory.add(
|
||||
MediaConstraints.KeyValuePair(
|
||||
"googHighpassFilter",
|
||||
"true"
|
||||
)
|
||||
)
|
||||
audioConstraints.mandatory.add(
|
||||
MediaConstraints.KeyValuePair(
|
||||
"googNoiseSuppression",
|
||||
"true"
|
||||
)
|
||||
)
|
||||
return audioConstraints
|
||||
}
|
||||
|
||||
private fun offerOrAnswerConstraint(): MediaConstraints {
|
||||
val mediaConstraints = MediaConstraints()
|
||||
val keyValuePairs = java.util.ArrayList<MediaConstraints.KeyValuePair>()
|
||||
keyValuePairs.add(MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"))
|
||||
keyValuePairs.add(MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"))
|
||||
mediaConstraints.mandatory.addAll(keyValuePairs)
|
||||
return mediaConstraints
|
||||
}
|
||||
|
||||
private fun createJavaAudioDevice(): AudioDeviceModule {
|
||||
val audioTrackErrorCallback: JavaAudioDeviceModule.AudioTrackErrorCallback = object :
|
||||
JavaAudioDeviceModule.AudioTrackErrorCallback {
|
||||
override fun onWebRtcAudioTrackInitError(errorMessage: String) {
|
||||
Log.i(TAG, "onWebRtcAudioTrackInitError ============> $errorMessage")
|
||||
|
||||
}
|
||||
|
||||
override fun onWebRtcAudioTrackStartError(
|
||||
errorCode: JavaAudioDeviceModule.AudioTrackStartErrorCode, errorMessage: String
|
||||
) {
|
||||
Log.i(TAG, "onWebRtcAudioTrackStartError ============> $errorCode:$errorMessage")
|
||||
|
||||
}
|
||||
|
||||
override fun onWebRtcAudioTrackError(errorMessage: String) {
|
||||
Log.i(TAG, "onWebRtcAudioTrackError ============> $errorMessage")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set audio track state callbacks.
|
||||
val audioTrackStateCallback: JavaAudioDeviceModule.AudioTrackStateCallback = object :
|
||||
JavaAudioDeviceModule.AudioTrackStateCallback {
|
||||
override fun onWebRtcAudioTrackStart() {
|
||||
Log.i(TAG, "onWebRtcAudioTrackStart ============>")
|
||||
|
||||
}
|
||||
|
||||
override fun onWebRtcAudioTrackStop() {
|
||||
Log.i(TAG, "onWebRtcAudioTrackStop ============>")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return JavaAudioDeviceModule.builder(mContext)
|
||||
.setUseHardwareAcousticEchoCanceler(true)
|
||||
.setUseHardwareNoiseSuppressor(true)
|
||||
.setAudioTrackErrorCallback(audioTrackErrorCallback)
|
||||
.setAudioTrackStateCallback(audioTrackStateCallback)
|
||||
.setUseStereoOutput(true) //立体声
|
||||
.createAudioDeviceModule()
|
||||
}
|
||||
|
||||
fun setVideoPath(url: String) {
|
||||
videoUrl = url
|
||||
}
|
||||
|
||||
fun start() {
|
||||
|
||||
mLocalPeer = Peer {
|
||||
val okHttpClient = OkHttpClient.Builder().build()
|
||||
|
||||
|
||||
val body = RequestBody.create("text/plain; charset=utf-8".toMediaType(), it!!)
|
||||
|
||||
|
||||
val request: Request = Request.Builder()
|
||||
.url(videoUrl)
|
||||
.post(body)
|
||||
.build()
|
||||
|
||||
val call: Call = okHttpClient.newCall(request)
|
||||
|
||||
call.enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
Log.i(TAG, "onFailure")
|
||||
OnErrorListener?.invoke(
|
||||
ErrorCode.GET_REMOTE_SDP_ERROR.errorCode,
|
||||
e.message.toString()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
val body = response.body?.string()
|
||||
val sdpResponse = Gson().fromJson(body, SdpResponse::class.java)
|
||||
|
||||
try {
|
||||
mRemoteSessionDescription = SessionDescription(
|
||||
SessionDescription.Type.fromCanonicalForm("answer"),
|
||||
sdpResponse.sdp
|
||||
)
|
||||
Log.i(
|
||||
TAG,
|
||||
"RemoteSdpObserver onCreateSuccess:[SessionDescription[type=${mRemoteSessionDescription?.type?.name},description=${mRemoteSessionDescription?.description}]]"
|
||||
)
|
||||
mLocalPeer?.setRemoteDescription(mRemoteSessionDescription!!)
|
||||
} catch (e: Exception) {
|
||||
Log.i(TAG, e.toString())
|
||||
OnErrorListener.invoke(
|
||||
ErrorCode.GET_REMOTE_SDP_ERROR.errorCode,
|
||||
e.localizedMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
mSurfaceViewRenderer.pauseVideo()
|
||||
//mSurfaceViewRenderer.disableFpsReduction()
|
||||
}
|
||||
|
||||
fun resume() {
|
||||
mSurfaceViewRenderer.setFpsReduction(15f)
|
||||
}
|
||||
|
||||
fun screenshot(listener: (bitmap: Bitmap) -> Unit) {
|
||||
mSurfaceViewRenderer.addFrameListener({
|
||||
listener.invoke(it)
|
||||
}, 1f)
|
||||
}
|
||||
|
||||
fun setSpeakerphoneOn(on: Boolean) {
|
||||
audioManager.isSpeakerphoneOn = on
|
||||
}
|
||||
|
||||
fun mute(on:Boolean) {
|
||||
audioManager.isMicrophoneMute=on
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
super.onDestroy(owner)
|
||||
mSurfaceViewRenderer.release()
|
||||
mLocalPeer?.mPeerConnection?.dispose()
|
||||
mAudioSource?.dispose()
|
||||
mPeerConnectionFactory?.dispose()
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
}
|
||||
|
||||
inner class Peer(var sdp: (String?) -> Unit = {}) : PeerConnection.Observer, SdpObserver {
|
||||
|
||||
var mPeerConnection: PeerConnection? = null
|
||||
|
||||
init {
|
||||
mPeerConnection = createPeerConnection()
|
||||
mPeerConnection?.createOffer(this, offerOrAnswerConstraint())
|
||||
}
|
||||
|
||||
//初始化 RTCPeerConnection 连接管道
|
||||
private fun createPeerConnection(): PeerConnection? {
|
||||
if (mPeerConnectionFactory == null) {
|
||||
mPeerConnectionFactory = createConnectionFactory()
|
||||
}
|
||||
// 管道连接抽象类实现方法
|
||||
val ICEServers = LinkedList<PeerConnection.IceServer>()
|
||||
val rtcConfig = PeerConnection.RTCConfiguration(ICEServers)
|
||||
//修改模式 PlanB无法使用仅接收音视频的配置
|
||||
//rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.PLAN_B
|
||||
return mPeerConnectionFactory?.createPeerConnection(rtcConfig, this)
|
||||
}
|
||||
|
||||
fun setRemoteDescription(sdp: SessionDescription) {
|
||||
mPeerConnection?.setRemoteDescription(this, sdp)
|
||||
}
|
||||
|
||||
override fun onCreateSuccess(sessionDescription: SessionDescription?) {
|
||||
mPeerConnection?.setLocalDescription(this, sessionDescription)
|
||||
mPeerConnection?.addStream(mLocalMediaStream)
|
||||
sdp.invoke(sessionDescription?.description)
|
||||
}
|
||||
|
||||
override fun onSetSuccess() {
|
||||
|
||||
}
|
||||
|
||||
override fun onCreateFailure(p0: String?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onSetFailure(p0: String?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onSignalingChange(signalingState: PeerConnection.SignalingState?) {
|
||||
Log.i(TAG, "onSignalingChange ============> " + signalingState.toString())
|
||||
}
|
||||
|
||||
override fun onIceConnectionChange(iceConnectionState: PeerConnection.IceConnectionState?) {
|
||||
Log.i(TAG, "onIceConnectionChange ============> " + iceConnectionState.toString())
|
||||
|
||||
}
|
||||
|
||||
override fun onIceConnectionReceivingChange(p0: Boolean) {
|
||||
Log.i(TAG, "onIceConnectionReceivingChange ============> $p0")
|
||||
|
||||
}
|
||||
|
||||
override fun onIceGatheringChange(iceGatheringState: PeerConnection.IceGatheringState?) {
|
||||
Log.i(TAG, "onIceGatheringChange ============> ${iceGatheringState.toString()}")
|
||||
}
|
||||
|
||||
override fun onIceCandidate(iceCandidate: IceCandidate?) {
|
||||
Log.i(TAG, "onIceCandidate ============> ${iceCandidate.toString()}")
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onIceCandidatesRemoved(p0: Array<out IceCandidate>?) {
|
||||
Log.i(TAG, "onIceCandidatesRemoved ============> ${p0.toString()}")
|
||||
}
|
||||
|
||||
override fun onAddStream(mediaStream: MediaStream?) {
|
||||
Log.i(TAG, "onAddStream ============> ${mediaStream?.toString()}")
|
||||
|
||||
if (mediaStream?.videoTracks?.isEmpty() != true) {
|
||||
val remoteVideoTrack = mediaStream?.videoTracks?.get(0)
|
||||
remoteVideoTrack?.setEnabled(true)
|
||||
remoteVideoTrack?.addSink(mSurfaceViewRenderer)
|
||||
}
|
||||
|
||||
if (mediaStream?.audioTracks?.isEmpty() != true) {
|
||||
val remoteAudioTrack = mediaStream?.audioTracks?.get(0)
|
||||
remoteAudioTrack?.setEnabled(true)
|
||||
remoteAudioTrack?.setVolume(1.0)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onRemoveStream(mediaStream: MediaStream?) {
|
||||
Log.i(TAG, "onRemoveStream ============> ${mediaStream.toString()}")
|
||||
|
||||
}
|
||||
|
||||
override fun onDataChannel(dataChannel: DataChannel?) {
|
||||
Log.i(TAG, "onDataChannel ============> ${dataChannel.toString()}")
|
||||
|
||||
}
|
||||
|
||||
override fun onRenegotiationNeeded() {
|
||||
Log.i(TAG, "onRenegotiationNeeded ============>")
|
||||
|
||||
}
|
||||
|
||||
override fun onAddTrack(rtpReceiver: RtpReceiver?, p1: Array<out MediaStream>?) {
|
||||
Log.i(TAG, "onAddTrack ============>" + rtpReceiver?.track())
|
||||
Log.i(TAG, "onAddTrack ============>" + p1?.size)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFirstFrameRendered() {
|
||||
Log.i(TAG, "onFirstFrameRendered ============>")
|
||||
|
||||
}
|
||||
|
||||
override fun onFrameResolutionChanged(frameWidth: Int, frameHeight: Int, rotation: Int) {
|
||||
Log.i(TAG, "onFrameResolutionChanged ============> $frameWidth:$frameHeight:$rotation")
|
||||
//set(frameWidth,frameHeight)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<com.zlmediakit.webrtc.WebRTCSurfaceView
|
||||
android:id="@+id/web_rtc_sv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@+id/web_rtc_sv"
|
||||
android:text=""/>
|
||||
<LinearLayout
|
||||
android:id="@+id/ll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="30dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/url">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btn_play"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="播放" />
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btn_pause"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="暂停" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btn_resume"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="恢复" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btn_speaker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="扬声器" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btn_mute"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="静音" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="10dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ll">
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btn_screenshot"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="截图" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btn_screen_record"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="录制" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/iv_screen"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<org.webrtc.SurfaceViewRenderer
|
||||
android:id="@+id/surface_view_renderer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |