mirror of
https://github.com/bluenviron/mediamtx.git
synced 2026-01-26 21:39:16 -08:00
Merge branch 'main' into feature/sourceudpclientportrange
This commit is contained in:
commit
7ca890315f
166 changed files with 4953 additions and 5290 deletions
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
|
@ -63,8 +63,8 @@ body:
|
|||
label: MediaMTX configuration
|
||||
description: |
|
||||
MediaMTX configuration is often required to replicate the issue.
|
||||
placeholder: Paste or drag the configuration file here
|
||||
# render: shell
|
||||
placeholder: Paste the configuration file here
|
||||
render: yaml
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
|
|
@ -73,7 +73,6 @@ body:
|
|||
description: |
|
||||
MediaMTX logs are often useful to identify the issue. If you think this is the case, set the parameter 'logLevel' to 'debug' and attach logs.
|
||||
placeholder: Paste or drag the log file here
|
||||
# render: shell
|
||||
|
||||
- type: textarea
|
||||
id: network
|
||||
|
|
|
|||
2
.github/workflows/code_lint.yml
vendored
2
.github/workflows/code_lint.yml
vendored
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
|
||||
- uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.6.1
|
||||
version: v2.8.0
|
||||
|
||||
go_mod:
|
||||
runs-on: ubuntu-22.04
|
||||
|
|
|
|||
2
.github/workflows/nightly_binaries.yml
vendored
2
.github/workflows/nightly_binaries.yml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
|
||||
- run: make binaries
|
||||
|
||||
- uses: actions/upload-artifact@v5
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: binaries
|
||||
path: binaries
|
||||
|
|
|
|||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
with:
|
||||
subject-path: '${{ github.workspace }}/binaries/*'
|
||||
|
||||
- uses: actions/upload-artifact@v5
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: binaries
|
||||
path: binaries
|
||||
|
|
@ -38,7 +38,7 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: binaries
|
||||
path: binaries
|
||||
|
|
@ -62,7 +62,7 @@ jobs:
|
|||
+ `\n`
|
||||
+ `## Security\n`
|
||||
+ `\n`
|
||||
+ `Binaries are compiled from source through the [Release workflow](https://github.com/${owner}/${repo}/actions/workflows/release.yml) without human intervention,`
|
||||
+ `Binaries are compiled from source through the [Release workflow](https://github.com/${owner}/${repo}/actions/workflows/release.yml?query=branch%3Amain) without human intervention,`
|
||||
+ ` preventing any external interference.\n`
|
||||
+ `\n`
|
||||
+ 'You can verify that binaries have been produced by the workflow by using [GitHub Attestations](https://docs.github.com/en/actions/concepts/security/artifact-attestations):\n'
|
||||
|
|
@ -151,7 +151,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: binaries
|
||||
path: binaries
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -1,5 +1,5 @@
|
|||
BASE_IMAGE = golang:1.25-alpine3.22
|
||||
GOLANGCI_LINT_IMAGE = golangci/golangci-lint:v2.7.1
|
||||
GOLANGCI_LINT_IMAGE = golangci/golangci-lint:v2.8.0
|
||||
NODE_IMAGE = node:20-alpine3.22
|
||||
|
||||
.PHONY: $(shell ls)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
<br>
|
||||
|
||||
[](https://mediamtx.org)
|
||||
[](https://github.com/bluenviron/mediamtx/actions/workflows/code_test.yml)
|
||||
[](https://github.com/bluenviron/mediamtx/actions/workflows/code_lint.yml)
|
||||
[](https://github.com/bluenviron/mediamtx/actions/workflows/code_test.yml?query=branch%3Amain)
|
||||
[](https://github.com/bluenviron/mediamtx/actions/workflows/code_lint.yml?query=branch%3Amain)
|
||||
[](https://app.codecov.io/gh/bluenviron/mediamtx/tree/main)
|
||||
[](https://github.com/bluenviron/mediamtx/releases)
|
||||
[](https://hub.docker.com/r/bluenviron/mediamtx)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ components:
|
|||
type: array
|
||||
items:
|
||||
type: string
|
||||
logStructured:
|
||||
type: boolean
|
||||
logFile:
|
||||
type: string
|
||||
sysLogPrefix:
|
||||
|
|
|
|||
|
|
@ -6,15 +6,37 @@ ADD binaries/mediamtx_*_linux_armv7.tar.gz /linux/arm/v7
|
|||
ADD binaries/mediamtx_*_linux_arm64.tar.gz /linux/arm64
|
||||
|
||||
#################################################################
|
||||
FROM --platform=linux/arm/v7 debian:bullseye-slim AS base-arm-v7
|
||||
|
||||
FROM --platform=linux/arm/v6 balenalib/raspberry-pi:bullseye-run-20240508 AS base-arm-v6
|
||||
FROM --platform=linux/arm/v7 balenalib/raspberry-pi:bullseye-run-20240508 AS base-arm-v7
|
||||
FROM --platform=linux/arm64 balenalib/raspberrypi3-64:bullseye-run-20240429 AS base-arm64
|
||||
# even though the base image is arm v7,
|
||||
# Raspbian libraries and compilers provide arm v6 compatibility.
|
||||
|
||||
RUN apt update \
|
||||
&& apt install -y wget gpg \
|
||||
&& echo "deb http://archive.raspbian.org/raspbian bullseye main rpi firmware" > /etc/apt/sources.list \
|
||||
&& echo "deb http://archive.raspberrypi.org/debian bullseye main" > /etc/apt/sources.list.d/raspi.list \
|
||||
&& wget -O- https://archive.raspbian.org/raspbian.public.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/raspbian.gpg \
|
||||
&& wget -O- https://archive.raspberrypi.org/debian/raspberrypi.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/raspberrypi.gpg \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt update && apt install --reinstall -y \
|
||||
libc6 \
|
||||
libstdc++6 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#################################################################
|
||||
FROM --platform=linux/arm64 debian:bullseye-slim AS base-arm64
|
||||
|
||||
RUN apt update \
|
||||
&& apt install -y wget gpg \
|
||||
&& echo "deb http://archive.raspberrypi.org/debian bullseye main" > /etc/apt/sources.list.d/raspi.list \
|
||||
&& wget -O- https://archive.raspberrypi.org/debian/raspberrypi.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/raspberrypi.gpg \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#################################################################
|
||||
FROM --platform=linux/amd64 scratch AS base
|
||||
|
||||
COPY --from=base-arm-v6 / /linux/arm/v6
|
||||
COPY --from=base-arm-v7 / /linux/arm/v6
|
||||
COPY --from=base-arm-v7 / /linux/arm/v7
|
||||
COPY --from=base-arm64 / /linux/arm64
|
||||
|
||||
|
|
|
|||
|
|
@ -6,15 +6,37 @@ ADD binaries/mediamtx_*_linux_armv7.tar.gz /linux/arm/v7
|
|||
ADD binaries/mediamtx_*_linux_arm64.tar.gz /linux/arm64
|
||||
|
||||
#################################################################
|
||||
FROM --platform=linux/arm/v7 debian:bullseye-slim AS base-arm-v7
|
||||
|
||||
FROM --platform=linux/arm/v6 balenalib/raspberry-pi:bullseye-run-20240508 AS base-arm-v6
|
||||
FROM --platform=linux/arm/v7 balenalib/raspberry-pi:bullseye-run-20240508 AS base-arm-v7
|
||||
FROM --platform=linux/arm64 balenalib/raspberrypi3-64:bullseye-run-20240429 AS base-arm64
|
||||
# even though the base image is arm v7,
|
||||
# Raspbian libraries and compilers provide arm v6 compatibility.
|
||||
|
||||
RUN apt update \
|
||||
&& apt install -y wget gpg \
|
||||
&& echo "deb http://archive.raspbian.org/raspbian bullseye main rpi firmware" > /etc/apt/sources.list \
|
||||
&& echo "deb http://archive.raspberrypi.org/debian bullseye main" > /etc/apt/sources.list.d/raspi.list \
|
||||
&& wget -O- https://archive.raspbian.org/raspbian.public.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/raspbian.gpg \
|
||||
&& wget -O- https://archive.raspberrypi.org/debian/raspberrypi.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/raspberrypi.gpg \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt update && apt install --reinstall -y \
|
||||
libc6 \
|
||||
libstdc++6 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#################################################################
|
||||
FROM --platform=linux/arm64 debian:bullseye-slim AS base-arm64
|
||||
|
||||
RUN apt update \
|
||||
&& apt install -y wget gpg \
|
||||
&& echo "deb http://archive.raspberrypi.org/debian bullseye main" > /etc/apt/sources.list.d/raspi.list \
|
||||
&& wget -O- https://archive.raspberrypi.org/debian/raspberrypi.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/raspberrypi.gpg \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#################################################################
|
||||
FROM --platform=linux/amd64 scratch AS base
|
||||
|
||||
COPY --from=base-arm-v6 / /linux/arm/v6
|
||||
COPY --from=base-arm-v7 / /linux/arm/v6
|
||||
COPY --from=base-arm-v7 / /linux/arm/v7
|
||||
COPY --from=base-arm64 / /linux/arm64
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Welcome to the MediaMTX documentation!
|
||||
|
||||
MediaMTX is a ready-to-use and zero-dependency live media server and media proxy. It has been conceived as a "media router" that routes media streams from one end to the other.
|
||||
_MediaMTX_ is a ready-to-use and zero-dependency live media server and media proxy. It has been conceived as a "media router" that routes media streams from one end to the other.
|
||||
|
||||
Main features:
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ There are several installation methods available: standalone binary, Docker imag
|
|||
|
||||
1. Visit the [Releases page](https://github.com/bluenviron/mediamtx/releases) on GitHub, download and extract a standalone binary that corresponds to your operating system and architecture (example: `mediamtx_{version_tag}_linux_amd64.tar.gz`).
|
||||
|
||||
2. Start the server:
|
||||
2. Start the server by double clicking on `mediamtx` (`mediamtx.exe` on Windows) or writing in the terminal:
|
||||
|
||||
```sh
|
||||
./mediamtx
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Upgrade
|
||||
|
||||
If you have an existing MediaMTX installation, you can upgrade it to the latest version. The procedure depends on how MediaMTX was installed.
|
||||
If you have an existing _MediaMTX_ installation, you can upgrade it to the latest version. The procedure depends on how _MediaMTX_ was installed.
|
||||
|
||||
## Standalone binary
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ The standalone binary comes with a upgrade utility that can be launched with:
|
|||
./mediamtx --upgrade
|
||||
```
|
||||
|
||||
This will replace the MediaMTX executable with its latest version. Privileges to write to the executable location are required.
|
||||
This will replace the _MediaMTX_ executable with its latest version. Privileges to write to the executable location are required.
|
||||
|
||||
## Docker image
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
! qtdemux name=d d.video_0 ! queue ! s.sink_0 d.audio_0 ! queue ! s.sink_1
|
||||
```
|
||||
|
||||
2. Open the stream. For instance, you can open the stream with _VLC_:
|
||||
2. Read the stream. For instance, you can read the stream with _VLC_:
|
||||
|
||||
```sh
|
||||
vlc --network-caching=50 rtsp://localhost:8554/mystream
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ paths:
|
|||
source: wheps://host:port/path
|
||||
```
|
||||
|
||||
If the remote server is a MediaMTX instance, remember to add a `/whep` suffix after the stream name, since in MediaMTX [it's part of the WHEP URL](read#webrtc):
|
||||
If the remote server is a _MediaMTX_ instance, remember to add a `/whep` suffix after the stream name, since in _MediaMTX_ [it's part of the WHEP URL](read#webrtc):
|
||||
|
||||
```yml
|
||||
paths:
|
||||
|
|
@ -287,6 +287,7 @@ paths:
|
|||
_MediaMTX_ natively supports most of the Raspberry Pi Camera models, enabling high-quality and low-latency video streaming from the camera to any user, for any purpose. There are some additional requirements:
|
||||
|
||||
1. The server must run on a Raspberry Pi, with one of the following operating systems:
|
||||
- Raspberry Pi OS Trixie
|
||||
- Raspberry Pi OS Bookworm
|
||||
- Raspberry Pi OS Bullseye
|
||||
|
||||
|
|
@ -452,7 +453,7 @@ ffmpeg -re -stream_loop -1 -i file.ts -c copy -f flv rtmp://localhost:1935/mystr
|
|||
|
||||
#### FFmpeg and MPEG-TS over UDP
|
||||
|
||||
In MediaMTX configuration, add a path with `source: udp+mpegts://238.0.0.1:1234`. Then:
|
||||
In _MediaMTX_ configuration, add a path with `source: udp+mpegts://238.0.0.1:1234`. Then:
|
||||
|
||||
```sh
|
||||
ffmpeg -re -stream_loop -1 -i file.ts -c copy -f mpegts 'udp://127.0.0.1:3356?pkt_size=1316'
|
||||
|
|
@ -468,7 +469,7 @@ ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=30 \
|
|||
|
||||
#### FFmpeg and RTP over UDP
|
||||
|
||||
In MediaMTX configuration, add a path with `source: udp+rtp://238.0.0.1:1234` and a valid `rtpSDP` (see [RTP](#rtp)). Then:
|
||||
In _MediaMTX_ configuration, add a path with `source: udp+rtp://238.0.0.1:1234` and a valid `rtpSDP` (see [RTP](#rtp)). Then:
|
||||
|
||||
```sh
|
||||
ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=30 \
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
All the configuration parameters are listed and commented in the [configuration file](/docs/references/configuration-file) (`mediamtx.yml`).
|
||||
|
||||
## Change the configuration
|
||||
|
||||
There are several ways to change the configuration:
|
||||
|
||||
1. By editing the configuration file, that is
|
||||
|
|
@ -46,3 +48,13 @@ There are several ways to change the configuration:
|
|||
```
|
||||
|
||||
3. By using the [Control API](control-api).
|
||||
|
||||
## Encrypt the configuration
|
||||
|
||||
The configuration file can be entirely encrypted for security purposes by using the `crypto_secretbox` function of the NaCL function. An online tool for performing this operation is [available here](https://play.golang.org/p/rX29jwObNe4).
|
||||
|
||||
After performing the encryption, put the base64-encoded result into the configuration file, and launch the server with the `MTX_CONFKEY` variable:
|
||||
|
||||
```
|
||||
MTX_CONFKEY=mykey ./mediamtx
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
MediaMTX can be configured to ask clients for credentials, either in the form of username/password or a string-based token. These credentials are then validated through a chosen method.
|
||||
_MediaMTX_ can be configured to ask clients for credentials, either in the form of username/password or a string-based token. These credentials are then validated through a chosen method.
|
||||
|
||||
## Credential validation
|
||||
|
||||
|
|
@ -306,6 +306,6 @@ Authorization: Bearer MY_JWT
|
|||
|
||||
In OBS Studio, this is the "Bearer Token" field.
|
||||
|
||||
If the `Authorization: Bearer` token cannot be directly provided (for instance, with web browsers that directly access MediaMTX and show a credential dialog), you can pass the token as password, using an arbitrary user.
|
||||
If the `Authorization: Bearer` token cannot be directly provided (for instance, with web browsers that directly access _MediaMTX_ and show a credential dialog), you can pass the token as password, using an arbitrary user.
|
||||
|
||||
In web browsers, if you need to automatically fill credentials from a parent web page, see [Embed streams in a website](embed-streams-in-a-website).
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
# Encrypt the configuration
|
||||
|
||||
The configuration file can be entirely encrypted for security purposes by using the `crypto_secretbox` function of the NaCL function. An online tool for performing this operation is [available here](https://play.golang.org/p/rX29jwObNe4).
|
||||
|
||||
After performing the encryption, put the base64-encoded result into the configuration file, and launch the server with the `MTX_CONFKEY` variable:
|
||||
|
||||
```
|
||||
MTX_CONFKEY=mykey ./mediamtx
|
||||
```
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Expose the server in a subfolder
|
||||
|
||||
HTTP-based services (WebRTC, HLS, Control API, Playback Server, Metrics, pprof) can be exposed in a subfolder of an existing HTTP server or reverse proxy. The reverse proxy must be able to intercept HTTP requests addressed to MediaMTX and corresponding responses, and perform the following changes:
|
||||
HTTP-based services (WebRTC, HLS, Control API, Playback Server, Metrics, pprof) can be exposed in a subfolder of an existing HTTP server or reverse proxy. The reverse proxy must be able to intercept HTTP requests addressed to _MediaMTX_ and corresponding responses, and perform the following changes:
|
||||
|
||||
- The subfolder path must be stripped from request paths. For instance, if the server is exposed behind `/subpath` and the reverse proxy receives a request with path `/subpath/mystream/index.m3u8`, this has to be changed into `/mystream/index.m3u8`.
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ The iframe can be controlled by adding query parameters to the URL (example: `ht
|
|||
|
||||
The iframe method is fit for most use cases, but it has some limitations:
|
||||
|
||||
- it doesn't allow to pass credentials (username, password or token) from the website to MediaMTX; credentials are asked directly to users.
|
||||
- it doesn't allow to pass credentials (username, password or token) from the website to _MediaMTX_; credentials are asked directly to users.
|
||||
- it doesn't allow to directly access the video tag, to extract data from it, or to perform dynamic actions.
|
||||
|
||||
In order to overcome these limitations, it is possible to load the stream directly inside a `<video>` tag in the web page, through a JavaScript library.
|
||||
|
|
@ -94,7 +94,7 @@ The iframe can be controlled by adding query parameters to the URL (example: `ht
|
|||
|
||||
The iframe method is fit for most use cases, but it has some limitations:
|
||||
|
||||
- it doesn't allow to pass credentials (username, password or token) from the website to MediaMTX; credentials are asked directly to users.
|
||||
- it doesn't allow to pass credentials (username, password or token) from the website to _MediaMTX_; credentials are asked directly to users.
|
||||
- it doesn't allow to directly access the video tag, to extract data from it, or to perform dynamic actions.
|
||||
|
||||
In order to overcome these limitations, it is possible to load the stream directly inside a `<video>` tag in the web page, through the _hls.js_ library.
|
||||
86
docs/2-usage/17-logging.md
Normal file
86
docs/2-usage/17-logging.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Logging
|
||||
|
||||
## Log verbosity
|
||||
|
||||
Log verbosity can be set with the `logLevel` parameter:
|
||||
|
||||
```yml
|
||||
# Verbosity of the program; available values are "error", "warn", "info", "debug".
|
||||
logLevel: info
|
||||
```
|
||||
|
||||
## Log destinations
|
||||
|
||||
Log entries can be sent to multiple destinations. By default, they are printed on the console (stdout).
|
||||
|
||||
It is possible to write logs to a file by using these parameters:
|
||||
|
||||
```yml
|
||||
# Destinations of log messages; available values are "stdout", "file" and "syslog".
|
||||
logDestinations: [file]
|
||||
# If "file" is in logDestinations, this is the file which will receive the logs.
|
||||
logFile: mediamtx.log
|
||||
```
|
||||
|
||||
It is possible to write logs to the system logging server (syslog) by using these parameters:
|
||||
|
||||
```yml
|
||||
# Destinations of log messages; available values are "stdout", "file" and "syslog".
|
||||
logDestinations: [syslog]
|
||||
# If "syslog" is in logDestinations, use prefix for logs.
|
||||
sysLogPrefix: mediamtx
|
||||
```
|
||||
|
||||
Log entries can be queried by using:
|
||||
|
||||
```sh
|
||||
journalctl SYSLOG_IDENTIFIER=mediamtx
|
||||
```
|
||||
|
||||
If _MediaMTX_ is also running as a [system service](start-on-boot), log entries can be queried by using:
|
||||
|
||||
```sh
|
||||
journalctl -u mediamtx
|
||||
```
|
||||
|
||||
## Structured logging
|
||||
|
||||
Log collectors (like Loki, Logstash, CloudWatch and fluentd) parse logs in a more reliable way if they are fed with entries in structured format (JSONL). This can be enabled with the `logStructured` parameter:
|
||||
|
||||
```yml
|
||||
# When destination is "stdout" or "file", emit logs in structured format (JSON).
|
||||
logStructured: true
|
||||
```
|
||||
|
||||
Obtaining:
|
||||
|
||||
```
|
||||
{"timestamp":"2003-05-01T20:34:14+01:00","level":"INF","message":"[RTSP] listener opened on :8554 (TCP), :8000 (UDP/RTP), :8001 (UDP/RTCP)"}
|
||||
```
|
||||
|
||||
## Log file rotation
|
||||
|
||||
The log file can be periodically rotated or truncated by using an external utility.
|
||||
|
||||
On most Linux distributions, the `logrotate` utility is in charge of managing log files. It can be configured to handle the _MediaMTX_ log file too by creating a configuration file, placed in `/etc/logrotate.d/mediamtx`, with this content:
|
||||
|
||||
```
|
||||
/my/mediamtx/path/mediamtx.log {
|
||||
daily
|
||||
copytruncate
|
||||
rotate 7
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
}
|
||||
```
|
||||
|
||||
This file will rotate the log file every day, adding a `.NUMBER` suffix to older copies:
|
||||
|
||||
```
|
||||
mediamtx.log.1
|
||||
mediamtx.log.2
|
||||
mediamtx.log.3
|
||||
...
|
||||
```
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# Log management
|
||||
|
||||
By default, log entries are printed on the console (stdout). It is possible to write logs to a file by using the `logDestinations` and `logFile` settings:
|
||||
|
||||
```yml
|
||||
# Destinations of log messages; available values are "stdout", "file" and "syslog".
|
||||
logDestinations: [file]
|
||||
# If "file" is in logDestinations, this is the file which will receive the logs.
|
||||
logFile: mediamtx.log
|
||||
```
|
||||
|
||||
The log file can be periodically rotated or truncated by using an external utility.
|
||||
|
||||
On most Linux distributions, the `logrotate` utility is in charge of managing log files. It can be configured to handle the _MediaMTX_ log file too by creating a configuration file, placed in `/etc/logrotate.d/mediamtx`, with this content:
|
||||
|
||||
```
|
||||
/my/mediamtx/path/mediamtx.log {
|
||||
daily
|
||||
copytruncate
|
||||
rotate 7
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
}
|
||||
```
|
||||
|
||||
This file will rotate the log file every day, adding a `.NUMBER` suffix to older copies:
|
||||
|
||||
```
|
||||
mediamtx.log.1
|
||||
mediamtx.log.2
|
||||
mediamtx.log.3
|
||||
...
|
||||
```
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Extract metrics
|
||||
|
||||
MediaMTX provides several metrics through a dedicated HTTP server, in a format compatible with [Prometheus](https://prometheus.io/).
|
||||
_MediaMTX_ provides several metrics through a dedicated HTTP server, in a format compatible with [Prometheus](https://prometheus.io/).
|
||||
|
||||
This server can be enabled by setting `metrics: yes` in the configuration.
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ webrtcICEServers2:
|
|||
password: secret
|
||||
```
|
||||
|
||||
where secret is the secret of the TURN server. MediaMTX will generate a set of credentials by using the secret, and credentials will be sent to clients before the WebRTC/ICE connection is established.
|
||||
where secret is the secret of the TURN server. _MediaMTX_ will generate a set of credentials by using the secret, and credentials will be sent to clients before the WebRTC/ICE connection is established.
|
||||
|
||||
In some cases you may want the browser to connect using TURN servers but have mediamtx not using TURN (for example if the TURN server is on the same network as mediamtx). To allow this you can configure the TURN server to be client only:
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ Streams can be published and read with the rtmps scheme and the 1937 port:
|
|||
rtmps://localhost:1937/...
|
||||
```
|
||||
|
||||
Be aware that RTMPS is currently unsupported by all major players. However, you can use a local _MediaMTX_ instance to decrypt streams before reading them, or alternatively a proxy like [stunnel](https://www.stunnel.org) or [nginx](https://nginx.org/). For instance, you can launch a local MediaMTX instance with this configuration:
|
||||
Be aware that RTMPS is currently unsupported by all major players. However, you can use a local _MediaMTX_ instance to decrypt streams before reading them, or alternatively a proxy like [stunnel](https://www.stunnel.org) or [nginx](https://nginx.org/). For instance, you can launch a local _MediaMTX_ instance with this configuration:
|
||||
|
||||
```yml
|
||||
paths:
|
||||
|
|
@ -3,11 +3,11 @@
|
|||
MediaMTX is meant for routing live streams, and makes use of a series of protocols and techniques which try to preserve the real-time aspect of streams and minimize latency at cost of losing packets in transmit, in particular:
|
||||
|
||||
- most protocols are built on UDP, which is an "unreliable transport", specifically picked because it allows to drop late packets in case of network congestions.
|
||||
- there's a circular buffer that stores outgoing packets and starts dropping packets when full.
|
||||
- there's a circular buffer that stores outgoing packets and drops packets if full.
|
||||
|
||||
Packet losses are usually detected and printed in MediaMTX logs.
|
||||
Packet losses are usually detected and printed in _MediaMTX_ logs.
|
||||
|
||||
If you need to improve the stream reliability and decrease packet losses, the first thing to do is to check whether the network between the MediaMTX instance and the intended publishers and readers has sufficient bandwidth for transmitting the media stream. Most of the times, packet losses are caused by a network which is not fit for this scope. This limitation can be overcome by either recompressing the stream with a lower bitrate, or by upgrading the network physical infrastructure (routers, cables, Wi-Fi, firewalls, topology, etc).
|
||||
If you need to improve the stream reliability and decrease packet losses, the first thing to do is to check whether the physical network between the _MediaMTX_ instance and the intended publishers and readers has sufficient bandwidth for transmitting the media stream. Most of the times, packet losses are caused by a network which is not fit for this scope. This limitation can be overcome by either recompressing the stream with a lower bitrate, or by upgrading the network infrastructure (routers, cables, Wi-Fi, firewalls, topology, etc).
|
||||
|
||||
There are however some parameters that can be tuned to improve the situation, at cost of increasing RAM consumption:
|
||||
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
# Configuration file reference
|
||||
|
||||
This is a copy of the configuration file (`mediamtx.yml`) of the latest MediaMTX release ({version_tag}), that contains all available parameters. Check the [Configuration usage page](/docs/usage/configuration) for instructions on how to change it.
|
||||
This is a copy of the configuration file (`mediamtx.yml`) of the latest _MediaMTX_ release ({version_tag}), that contains all available parameters. Check the [Configuration usage page](/docs/usage/configuration) for instructions on how to change it.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# Control API reference
|
||||
|
||||
This is the reference of the Control API of the latest MediaMTX release ({version_tag}), generated automatically from the [OpenAPI / Swagger file](https://github.com/bluenviron/mediamtx/blob/{version_tag}/api/openapi.yaml) available in the repository. Check the [Control API usage page](/docs/usage/control-api) for instructions on how to use the API.
|
||||
This is the reference of the Control API of the latest _MediaMTX_ release ({version_tag}), generated automatically from the [OpenAPI / Swagger file](https://github.com/bluenviron/mediamtx/blob/{version_tag}/api/openapi.yaml) available in the repository. Check the [Control API usage page](/docs/usage/control-api) for instructions on how to use the API.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# License
|
||||
|
||||
All the code in the main MediaMTX repository is released under the [MIT License](https://github.com/bluenviron/mediamtx/blob/{version_tag}/LICENSE). Compiled binaries include some third-party dependencies:
|
||||
All the code in the main _MediaMTX_ repository is released under the [MIT License](https://github.com/bluenviron/mediamtx/blob/{version_tag}/LICENSE). Compiled binaries include some third-party dependencies:
|
||||
|
||||
- all the Golang dependencies listed into the [go.mod file](https://github.com/bluenviron/mediamtx/blob/{version_tag}/go.mod), which are all released under either the MIT license, BSD 3-Clause license or Apache License 2.0.
|
||||
- hls.js, released under the [Apache License 2.0](https://github.com/video-dev/hls.js/blob/master/LICENSE).
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
Binaries published in the [Releases](https://github.com/bluenviron/mediamtx/releases) section of GitHub are the output of a process which has been designed with a security-first approach. Every step from source code to the intended final destination of binaries is fully visible, immune from external interferences and independently verifiable. This is the process:
|
||||
|
||||
1. During every release, the [Release workflow](https://github.com/bluenviron/mediamtx/actions/workflows/release.yml) is triggered on GitHub.
|
||||
1. During every release, the [Release workflow](https://github.com/bluenviron/mediamtx/actions/workflows/release.yml?query=branch%3Amain) is triggered on GitHub.
|
||||
|
||||
2. The release workflow pulls the source code and builds binaries.
|
||||
|
||||
|
|
|
|||
58
go.mod
58
go.mod
|
|
@ -3,17 +3,17 @@ module github.com/bluenviron/mediamtx
|
|||
go 1.25.0
|
||||
|
||||
require (
|
||||
code.cloudfoundry.org/bytefmt v0.58.0
|
||||
code.cloudfoundry.org/bytefmt v0.62.0
|
||||
github.com/Masterminds/semver/v3 v3.4.0
|
||||
github.com/MicahParks/jwkset v0.11.0
|
||||
github.com/MicahParks/keyfunc/v3 v3.7.0
|
||||
github.com/abema/go-mp4 v1.4.1
|
||||
github.com/alecthomas/kong v1.13.0
|
||||
github.com/asticode/go-astits v1.14.0
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.4
|
||||
github.com/bluenviron/gortmplib v0.1.2
|
||||
github.com/bluenviron/gortsplib/v5 v5.2.1
|
||||
github.com/bluenviron/mediacommon/v2 v2.5.3
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.5-0.20260117214804-b8c1ff42629d
|
||||
github.com/bluenviron/gortmplib v0.2.0
|
||||
github.com/bluenviron/gortsplib/v5 v5.2.3
|
||||
github.com/bluenviron/mediacommon/v2 v2.6.0
|
||||
github.com/datarhei/gosrt v0.9.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gin-contrib/pprof v1.5.3
|
||||
|
|
@ -25,20 +25,20 @@ require (
|
|||
github.com/gookit/color v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/matthewhartstonge/argon2 v1.4.3
|
||||
github.com/matthewhartstonge/argon2 v1.4.4
|
||||
github.com/minio/selfupdate v0.6.0
|
||||
github.com/pion/ice/v4 v4.0.13
|
||||
github.com/pion/interceptor v0.1.42
|
||||
github.com/pion/ice/v4 v4.2.0
|
||||
github.com/pion/interceptor v0.1.43
|
||||
github.com/pion/logging v0.2.4
|
||||
github.com/pion/rtcp v1.2.16
|
||||
github.com/pion/rtp v1.8.26
|
||||
github.com/pion/sdp/v3 v3.0.16
|
||||
github.com/pion/transport/v3 v3.1.1
|
||||
github.com/pion/webrtc/v4 v4.1.7
|
||||
github.com/pion/rtp v1.10.0
|
||||
github.com/pion/sdp/v3 v3.0.17
|
||||
github.com/pion/transport/v4 v4.0.1
|
||||
github.com/pion/webrtc/v4 v4.2.3
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/crypto v0.45.0
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/term v0.37.0
|
||||
golang.org/x/crypto v0.47.0
|
||||
golang.org/x/sys v0.40.0
|
||||
golang.org/x/term v0.39.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
|
|
@ -74,18 +74,18 @@ require (
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pion/datachannel v1.5.10 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.8 // indirect
|
||||
github.com/pion/datachannel v1.6.0 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.10 // indirect
|
||||
github.com/pion/mdns/v2 v2.1.0 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/sctp v1.8.41 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.9 // indirect
|
||||
github.com/pion/stun/v3 v3.0.1 // indirect
|
||||
github.com/pion/turn/v4 v4.1.3 // indirect
|
||||
github.com/pion/sctp v1.9.2 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.10 // indirect
|
||||
github.com/pion/stun/v3 v3.1.1 // indirect
|
||||
github.com/pion/turn/v4 v4.1.4 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.1 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.57.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
|
|
@ -93,15 +93,13 @@ require (
|
|||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/datarhei/gosrt => github.com/aler9/gosrt v0.9.1-0.20251222193740-d7fe54d226e5
|
||||
|
|
|
|||
126
go.sum
126
go.sum
|
|
@ -1,7 +1,7 @@
|
|||
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
|
||||
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
|
||||
code.cloudfoundry.org/bytefmt v0.58.0 h1:pj/1gobEDZh6PIlNnDmOJMAGnv9hb2NHbw7/PSUY4wY=
|
||||
code.cloudfoundry.org/bytefmt v0.58.0/go.mod h1:koEpk4JCe4CXNcULPrdoTcMbkmG87+ZIT+vHqoNMyL0=
|
||||
code.cloudfoundry.org/bytefmt v0.62.0 h1:MeIv8SfspydaNWVmZZZGFcE2Bs1PKVfjmA0x2/WQtMU=
|
||||
code.cloudfoundry.org/bytefmt v0.62.0/go.mod h1:cV5MEjJxcDcu4oGNGwb3bJK/H3yx0rXhji++YR2rPBY=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
|
|
@ -23,6 +23,8 @@ github.com/alecthomas/kong v1.13.0 h1:5e/7XC3ugvhP1DQBmTS+WuHtCbcv44hsohMgcvVxSr
|
|||
github.com/alecthomas/kong v1.13.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/aler9/gosrt v0.9.1-0.20251222193740-d7fe54d226e5 h1:RJzCu1Srlz876mijSN8gqvVISh7vVpplje/zAT8o6Pk=
|
||||
github.com/aler9/gosrt v0.9.1-0.20251222193740-d7fe54d226e5/go.mod h1:SuUdLtWsdOAdcDMMmhT20i5XJ5ErwmX1hDeLCIb+xBk=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
|
|
@ -33,12 +35,12 @@ github.com/asticode/go-astits v1.14.0 h1:zkgnZzipx2XX5mWycqsSBeEyDH58+i4HtyF4j2R
|
|||
github.com/asticode/go-astits v1.14.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
|
||||
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYhJeJ2aZxADI2tGADS15AzIF8MQ8XAhT4=
|
||||
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI=
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.4 h1:5F/Ud2VuJrrLYCDV0Ham947UIIrd801/GyoCsPYMqiw=
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.4/go.mod h1:5J8Ry4xraLs6W0B2gVS0IqhCzcq+CDDJIisfkpoHyxM=
|
||||
github.com/bluenviron/gortmplib v0.1.2 h1:bJ+5pUxKnHjEoKPbNs7lDLyYy5R9Z1lISJ1n54DsjWE=
|
||||
github.com/bluenviron/gortmplib v0.1.2/go.mod h1:oVOWgfs7wsZNoKYLttyqLar7a71ZTohO1JOVXkNxtHg=
|
||||
github.com/bluenviron/mediacommon/v2 v2.5.3 h1:espW+MsV1IYH6WE3QGa1UgPnIp13JuJG4VqGvoqseBU=
|
||||
github.com/bluenviron/mediacommon/v2 v2.5.3/go.mod h1:5V15TiOfeaNVmZPVuOqAwqQSWyvMV86/dijDKu5q9Zs=
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.5-0.20260117214804-b8c1ff42629d h1:Mm56CDoDBNgVRI4vQHt6+JMQ+yiEX8Fljlh/o2NskaQ=
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.5-0.20260117214804-b8c1ff42629d/go.mod h1:iatp6XDIncpGlua8YU2wga01fU7pzp42nWG684GZhbM=
|
||||
github.com/bluenviron/gortmplib v0.2.0 h1:j15eeHrgVh6Avg9oAx+r4w0HugTqrIqLBsYnhs3D1dE=
|
||||
github.com/bluenviron/gortmplib v0.2.0/go.mod h1:yzobxBF8zusF2nKbEOF69zIIL429j0kaCWc/euNdvO4=
|
||||
github.com/bluenviron/mediacommon/v2 v2.6.0 h1:wZAPXwv7V78Cx2x7cToYIHOLToHl6APcvHbdQT+gOkg=
|
||||
github.com/bluenviron/mediacommon/v2 v2.6.0/go.mod h1:5V15TiOfeaNVmZPVuOqAwqQSWyvMV86/dijDKu5q9Zs=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
|
|
@ -50,8 +52,6 @@ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gE
|
|||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/datarhei/gosrt v0.9.0 h1:FW8A+F8tBiv7eIa57EBHjtTJKFX+OjvLogF/tFXoOiA=
|
||||
github.com/datarhei/gosrt v0.9.0/go.mod h1:rqTRK8sDZdN2YBgp1EEICSV4297mQk0oglwvpXhaWdk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
|
@ -102,8 +102,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUv
|
|||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE=
|
||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
|
@ -135,8 +135,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/matthewhartstonge/argon2 v1.4.3 h1:rqbAR3lzzIjQdwQ5V8AyLKRB6F5tk479EiPSsrwH428=
|
||||
github.com/matthewhartstonge/argon2 v1.4.3/go.mod h1:yV9Hi7hkRTdHMBUtpaJepCxc0szFRF7g1kE2i1QEy9s=
|
||||
github.com/matthewhartstonge/argon2 v1.4.4 h1:IxMnW8Y5/As75hr3zIUAGmHlzqwhYb/cc0wI9QO29JM=
|
||||
github.com/matthewhartstonge/argon2 v1.4.4/go.mod h1:3o39C3PfERZZ3sm5hFcYNTaLDz4sz6PVfeJo8yITGf4=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
||||
|
|
@ -147,22 +147,22 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
|||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE=
|
||||
github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
|
||||
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
|
||||
github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw=
|
||||
github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
||||
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
||||
github.com/pion/dtls/v3 v3.0.8 h1:ZrPUrvPVDaTJDM8Vu1veatzXebLlsIWeT7Vaate/zwM=
|
||||
github.com/pion/dtls/v3 v3.0.8/go.mod h1:abApPjgadS/ra1wvUzHLc3o2HvoxppAh+NZkyApL4Os=
|
||||
github.com/pion/ice/v4 v4.0.13 h1:1cdmd80gmLdnVTM2bXzw2CBebvXvkGNEaWi/CuDK9WQ=
|
||||
github.com/pion/ice/v4 v4.0.13/go.mod h1:Xo5f5DBbEjQac+6pR7i83AGuwoGxnxwXkOOvHFVnfnM=
|
||||
github.com/pion/interceptor v0.1.42 h1:0/4tvNtruXflBxLfApMVoMubUMik57VZ+94U0J7cmkQ=
|
||||
github.com/pion/interceptor v0.1.42/go.mod h1:g6XYTChs9XyolIQFhRHOOUS+bGVGLRfgTCUzH29EfVU=
|
||||
github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0=
|
||||
github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk=
|
||||
github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg=
|
||||
github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8=
|
||||
github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw=
|
||||
github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4=
|
||||
github.com/pion/interceptor v0.1.43 h1:6hmRfnmjogSs300xfkR0JxYFZ9k5blTEvCD7wxEDuNQ=
|
||||
github.com/pion/interceptor v0.1.43/go.mod h1:BSiC1qKIJt1XVr3l3xQ2GEmCFStk9tx8fwtCZxxgR7M=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||
github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY=
|
||||
|
|
@ -171,22 +171,24 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
|||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
|
||||
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
|
||||
github.com/pion/rtp v1.8.26 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc=
|
||||
github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||
github.com/pion/sctp v1.8.41 h1:20R4OHAno4Vky3/iE4xccInAScAa83X6nWUfyc65MIs=
|
||||
github.com/pion/sctp v1.8.41/go.mod h1:2wO6HBycUH7iCssuGyc2e9+0giXVW0pyCv3ZuL8LiyY=
|
||||
github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=
|
||||
github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
|
||||
github.com/pion/srtp/v3 v3.0.9 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY=
|
||||
github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8=
|
||||
github.com/pion/stun/v3 v3.0.1 h1:jx1uUq6BdPihF0yF33Jj2mh+C9p0atY94IkdnW174kA=
|
||||
github.com/pion/stun/v3 v3.0.1/go.mod h1:RHnvlKFg+qHgoKIqtQWMOJF52wsImCAf/Jh5GjX+4Tw=
|
||||
github.com/pion/rtp v1.10.0 h1:XN/xca4ho6ZEcijpdF2VGFbwuHUfiIMf3ew8eAAE43w=
|
||||
github.com/pion/rtp v1.10.0/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||
github.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo=
|
||||
github.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8=
|
||||
github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo=
|
||||
github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
|
||||
github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
|
||||
github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
|
||||
github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw=
|
||||
github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM=
|
||||
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
|
||||
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
||||
github.com/pion/turn/v4 v4.1.3 h1:jVNW0iR05AS94ysEtvzsrk3gKs9Zqxf6HmnsLfRvlzA=
|
||||
github.com/pion/turn/v4 v4.1.3/go.mod h1:TD/eiBUf5f5LwXbCJa35T7dPtTpCHRJ9oJWmyPLVT3A=
|
||||
github.com/pion/webrtc/v4 v4.1.7 h1:sl3vFuVHa1u/7DcFbud7e1zk3sG3RjBS5GI2ckltROg=
|
||||
github.com/pion/webrtc/v4 v4.1.7/go.mod h1:y3mRk8wpmOVkTTEGYB/eXAg0DPEfTEdC/Y021zRiOiM=
|
||||
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ=
|
||||
github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ=
|
||||
github.com/pion/webrtc/v4 v4.2.3 h1:RtdWDnkenNQGxUrZqWa5gSkTm5ncsLg5d+zu0M4cXt4=
|
||||
github.com/pion/webrtc/v4 v4.2.3/go.mod h1:7vsyFzRzaKP5IELUnj8zLcglPyIT6wWwqTppBZ1k6Kc=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
|
@ -194,10 +196,10 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg=
|
||||
github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
|
||||
github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
|
|
@ -227,8 +229,8 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
|
|||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
|
|
@ -237,16 +239,16 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
@ -259,22 +261,22 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpac3"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type ac3 struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.AC3
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpac3.Encoder
|
||||
decoder *rtpac3.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *ac3) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ac3) createEncoder() error {
|
||||
t.encoder = &rtpac3.Encoder{
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *ac3) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadAC3))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ac3) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
frames, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpac3.ErrNonStartingPacketAndNoPrevious) ||
|
||||
errors.Is(err, rtpac3.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadAC3(frames)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
package codecprocessor //nolint:dupl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpav1"
|
||||
mcav1 "github.com/bluenviron/mediacommon/v2/pkg/codecs/av1"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type av1 struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.AV1
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpav1.Encoder
|
||||
decoder *rtpav1.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *av1) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *av1) createEncoder() error {
|
||||
t.encoder = &rtpav1.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *av1) remuxTemporalUnit(tu unit.PayloadAV1) unit.PayloadAV1 {
|
||||
n := 0
|
||||
|
||||
for _, obu := range tu {
|
||||
typ := mcav1.OBUType((obu[0] >> 3) & 0b1111)
|
||||
|
||||
if typ == mcav1.OBUTypeTemporalDelimiter {
|
||||
continue
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
filteredTU := make([][]byte, n)
|
||||
i := 0
|
||||
|
||||
for _, obu := range tu {
|
||||
typ := mcav1.OBUType((obu[0] >> 3) & 0b1111)
|
||||
|
||||
if typ == mcav1.OBUTypeTemporalDelimiter {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredTU[i] = obu
|
||||
i++
|
||||
}
|
||||
|
||||
return filteredTU
|
||||
}
|
||||
|
||||
func (t *av1) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
u.Payload = t.remuxTemporalUnit(u.Payload.(unit.PayloadAV1))
|
||||
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadAV1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *av1) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tu, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpav1.ErrNonStartingPacketAndNoPrevious) ||
|
||||
errors.Is(err, rtpav1.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = t.remuxTemporalUnit(tu)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
mcav1 "github.com/bluenviron/mediacommon/v2/pkg/codecs/av1"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAV1RemoveTUD(t *testing.T) {
|
||||
forma := &format.AV1{}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
u := &unit.Unit{
|
||||
PTS: 30000,
|
||||
Payload: unit.PayloadAV1{
|
||||
{byte(mcav1.OBUTypeTemporalDelimiter) << 3},
|
||||
{5},
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadAV1{
|
||||
{5},
|
||||
}, u.Payload)
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
package codecprocessor //nolint:dupl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type g711 struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.G711
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtplpcm.Encoder
|
||||
decoder *rtplpcm.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *g711) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *g711) createEncoder() error {
|
||||
t.encoder = &rtplpcm.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadType(),
|
||||
BitDepth: 8,
|
||||
ChannelCount: t.Format.ChannelCount,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *g711) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadG711))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *g711) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
samples, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadG711(samples)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestG711ProcessUnit(t *testing.T) {
|
||||
t.Run("alaw", func(t *testing.T) {
|
||||
forma := &format.G711{
|
||||
PayloadTyp: 8,
|
||||
MULaw: false,
|
||||
SampleRate: 8000,
|
||||
ChannelCount: 1,
|
||||
}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
unit := &unit.Unit{
|
||||
Payload: unit.PayloadG711{1, 2, 3, 4},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(unit)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []*rtp.Packet{{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
PayloadType: 8,
|
||||
SequenceNumber: unit.RTPPackets[0].SequenceNumber,
|
||||
Timestamp: unit.RTPPackets[0].Timestamp,
|
||||
SSRC: unit.RTPPackets[0].SSRC,
|
||||
},
|
||||
Payload: []byte{1, 2, 3, 4},
|
||||
}}, unit.RTPPackets)
|
||||
})
|
||||
|
||||
t.Run("mulaw", func(t *testing.T) {
|
||||
forma := &format.G711{
|
||||
PayloadTyp: 0,
|
||||
MULaw: true,
|
||||
SampleRate: 8000,
|
||||
ChannelCount: 1,
|
||||
}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
unit := &unit.Unit{
|
||||
Payload: unit.PayloadG711{1, 2, 3, 4},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(unit)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []*rtp.Packet{{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
PayloadType: 0,
|
||||
SequenceNumber: unit.RTPPackets[0].SequenceNumber,
|
||||
Timestamp: unit.RTPPackets[0].Timestamp,
|
||||
SSRC: unit.RTPPackets[0].SSRC,
|
||||
},
|
||||
Payload: []byte{1, 2, 3, 4},
|
||||
}}, unit.RTPPackets)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type generic struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format format.Format
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
}
|
||||
|
||||
func (t *generic) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
return fmt.Errorf("we don't know how to generate RTP packets of format %T", t.Format)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *generic) ProcessUnit(_ *unit.Unit) error {
|
||||
return fmt.Errorf("using a generic unit without RTP is not supported")
|
||||
}
|
||||
|
||||
func (t *generic) ProcessRTPPacket(
|
||||
u *unit.Unit,
|
||||
_ bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGenericProcessRTPPacket(t *testing.T) {
|
||||
forma := &format.Generic{
|
||||
PayloadTyp: 96,
|
||||
RTPMa: "private/90000",
|
||||
}
|
||||
err := forma.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err := New(1450, forma, false, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkt := &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
Padding: true,
|
||||
},
|
||||
Payload: []byte{1, 2, 3, 4},
|
||||
PaddingSize: 20,
|
||||
}
|
||||
|
||||
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// check that padding has been removed
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: []byte{1, 2, 3, 4},
|
||||
}, pkt)
|
||||
}
|
||||
|
|
@ -1,316 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph264"
|
||||
mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
// H264-related parameters
|
||||
var (
|
||||
H264DefaultSPS = []byte{ // 1920x1080 baseline
|
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
|
||||
0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20,
|
||||
}
|
||||
|
||||
H264DefaultPPS = []byte{0x08, 0x06, 0x07, 0x08}
|
||||
)
|
||||
|
||||
// extract SPS and PPS without decoding RTP packets
|
||||
func rtpH264ExtractParams(payload []byte) ([]byte, []byte) {
|
||||
if len(payload) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
typ := mch264.NALUType(payload[0] & 0x1F)
|
||||
|
||||
switch typ {
|
||||
case mch264.NALUTypeSPS:
|
||||
return payload, nil
|
||||
|
||||
case mch264.NALUTypePPS:
|
||||
return nil, payload
|
||||
|
||||
case mch264.NALUTypeSTAPA:
|
||||
payload = payload[1:]
|
||||
var sps []byte
|
||||
var pps []byte
|
||||
|
||||
for len(payload) > 0 {
|
||||
if len(payload) < 2 {
|
||||
break
|
||||
}
|
||||
|
||||
size := uint16(payload[0])<<8 | uint16(payload[1])
|
||||
payload = payload[2:]
|
||||
|
||||
if size == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if int(size) > len(payload) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nalu := payload[:size]
|
||||
payload = payload[size:]
|
||||
|
||||
typ = mch264.NALUType(nalu[0] & 0x1F)
|
||||
|
||||
switch typ {
|
||||
case mch264.NALUTypeSPS:
|
||||
sps = nalu
|
||||
|
||||
case mch264.NALUTypePPS:
|
||||
pps = nalu
|
||||
}
|
||||
}
|
||||
|
||||
return sps, pps
|
||||
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
type h264 struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.H264
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtph264.Encoder
|
||||
decoder *rtph264.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *h264) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder(nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *h264) createEncoder(
|
||||
ssrc *uint32,
|
||||
initialSequenceNumber *uint16,
|
||||
) error {
|
||||
t.encoder = &rtph264.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
SSRC: ssrc,
|
||||
InitialSequenceNumber: initialSequenceNumber,
|
||||
PacketizationMode: t.Format.PacketizationMode,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *h264) updateTrackParametersFromRTPPacket(payload []byte) {
|
||||
sps, pps := rtpH264ExtractParams(payload)
|
||||
|
||||
if (sps != nil && !bytes.Equal(sps, t.Format.SPS)) ||
|
||||
(pps != nil && !bytes.Equal(pps, t.Format.PPS)) {
|
||||
if sps == nil {
|
||||
sps = t.Format.SPS
|
||||
}
|
||||
if pps == nil {
|
||||
pps = t.Format.PPS
|
||||
}
|
||||
t.Format.SafeSetParams(sps, pps)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *h264) updateTrackParametersFromAU(au unit.PayloadH264) {
|
||||
sps := t.Format.SPS
|
||||
pps := t.Format.PPS
|
||||
update := false
|
||||
|
||||
for _, nalu := range au {
|
||||
typ := mch264.NALUType(nalu[0] & 0x1F)
|
||||
|
||||
switch typ {
|
||||
case mch264.NALUTypeSPS:
|
||||
if !bytes.Equal(nalu, sps) {
|
||||
sps = nalu
|
||||
update = true
|
||||
}
|
||||
|
||||
case mch264.NALUTypePPS:
|
||||
if !bytes.Equal(nalu, pps) {
|
||||
pps = nalu
|
||||
update = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if update {
|
||||
t.Format.SafeSetParams(sps, pps)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *h264) remuxAccessUnit(au unit.PayloadH264) unit.PayloadH264 {
|
||||
isKeyFrame := false
|
||||
n := 0
|
||||
|
||||
for _, nalu := range au {
|
||||
typ := mch264.NALUType(nalu[0] & 0x1F)
|
||||
|
||||
switch typ {
|
||||
case mch264.NALUTypeSPS, mch264.NALUTypePPS:
|
||||
continue
|
||||
|
||||
case mch264.NALUTypeAccessUnitDelimiter:
|
||||
continue
|
||||
|
||||
case mch264.NALUTypeIDR:
|
||||
if !isKeyFrame {
|
||||
isKeyFrame = true
|
||||
|
||||
// prepend parameters
|
||||
if t.Format.SPS != nil && t.Format.PPS != nil {
|
||||
n += 2
|
||||
}
|
||||
}
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
filteredAU := make([][]byte, n)
|
||||
i := 0
|
||||
|
||||
if isKeyFrame && t.Format.SPS != nil && t.Format.PPS != nil {
|
||||
filteredAU[0] = t.Format.SPS
|
||||
filteredAU[1] = t.Format.PPS
|
||||
i = 2
|
||||
}
|
||||
|
||||
for _, nalu := range au {
|
||||
typ := mch264.NALUType(nalu[0] & 0x1F)
|
||||
|
||||
switch typ {
|
||||
case mch264.NALUTypeSPS, mch264.NALUTypePPS:
|
||||
continue
|
||||
|
||||
case mch264.NALUTypeAccessUnitDelimiter:
|
||||
continue
|
||||
}
|
||||
|
||||
filteredAU[i] = nalu
|
||||
i++
|
||||
}
|
||||
|
||||
return filteredAU
|
||||
}
|
||||
|
||||
func (t *h264) ProcessUnit(u *unit.Unit) error {
|
||||
t.updateTrackParametersFromAU(u.Payload.(unit.PayloadH264))
|
||||
u.Payload = t.remuxAccessUnit(u.Payload.(unit.PayloadH264))
|
||||
|
||||
if !u.NilPayload() {
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH264))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *h264) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
t.updateTrackParametersFromRTPPacket(pkt.Payload)
|
||||
|
||||
if t.encoder == nil {
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
// RTP packets exceed maximum size: start re-encoding them
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
t.Parent.Log(logger.Info, "RTP packets are too big, remuxing them into smaller ones")
|
||||
|
||||
v1 := pkt.SSRC
|
||||
v2 := pkt.SequenceNumber
|
||||
err := t.createEncoder(&v1, &v2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
au, err := t.decoder.Decode(pkt)
|
||||
|
||||
if t.encoder != nil {
|
||||
u.RTPPackets = nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, rtph264.ErrNonStartingPacketAndNoPrevious) ||
|
||||
errors.Is(err, rtph264.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = t.remuxAccessUnit(au)
|
||||
}
|
||||
|
||||
// route packet as is
|
||||
if t.encoder == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// encode into RTP
|
||||
if !u.NilPayload() {
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH264))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, newPKT := range u.RTPPackets {
|
||||
newPKT.Timestamp = pkt.Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,307 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type testLogger struct {
|
||||
cb func(level logger.Level, format string, args ...any)
|
||||
}
|
||||
|
||||
func (l *testLogger) Log(level logger.Level, format string, args ...any) {
|
||||
l.cb(level, format, args...)
|
||||
}
|
||||
|
||||
// Logger returns a dummy logger.
|
||||
func Logger(cb func(logger.Level, string, ...any)) logger.Writer {
|
||||
return &testLogger{cb: cb}
|
||||
}
|
||||
|
||||
func TestH264RemoveAUD(t *testing.T) {
|
||||
forma := &format.H264{}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
u := &unit.Unit{
|
||||
PTS: 30000,
|
||||
Payload: unit.PayloadH264{
|
||||
{9, 24}, // AUD
|
||||
{5, 1}, // IDR
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadH264{
|
||||
{5, 1}, // IDR
|
||||
}, u.Payload)
|
||||
}
|
||||
|
||||
func TestH264AddParams(t *testing.T) {
|
||||
forma := &format.H264{}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
u1 := &unit.Unit{
|
||||
PTS: 30000,
|
||||
Payload: unit.PayloadH264{
|
||||
{7, 4, 5, 6}, // SPS
|
||||
{8, 1}, // PPS
|
||||
{5, 1}, // IDR
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(u1)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadH264{
|
||||
{7, 4, 5, 6}, // SPS
|
||||
{8, 1}, // PPS
|
||||
{5, 1}, // IDR
|
||||
}, u1.Payload)
|
||||
|
||||
u2 := &unit.Unit{
|
||||
PTS: 30000 * 2,
|
||||
Payload: unit.PayloadH264{
|
||||
{5, 2}, // IDR
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(u2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test that params have been added to the SDP
|
||||
require.Equal(t, []byte{7, 4, 5, 6}, forma.SPS)
|
||||
require.Equal(t, []byte{8, 1}, forma.PPS)
|
||||
|
||||
// test that params have been added to the frame
|
||||
require.Equal(t, unit.PayloadH264{
|
||||
{7, 4, 5, 6}, // SPS
|
||||
{8, 1}, // PPS
|
||||
{5, 2}, // IDR
|
||||
}, u2.Payload)
|
||||
|
||||
// test that timestamp has increased
|
||||
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
|
||||
}
|
||||
|
||||
func TestH264ProcessEmptyUnit(t *testing.T) {
|
||||
forma := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
unit := &unit.Unit{
|
||||
Payload: unit.PayloadH264{
|
||||
{0x07, 0x01, 0x02, 0x03}, // SPS
|
||||
{0x08, 0x01, 0x02}, // PPS
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(unit)
|
||||
require.NoError(t, err)
|
||||
|
||||
// if all NALUs have been removed, no RTP packets shall be generated.
|
||||
require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets)
|
||||
}
|
||||
|
||||
func TestH264RTPExtractParams(t *testing.T) {
|
||||
for _, ca := range []string{"standard", "aggregated"} {
|
||||
t.Run(ca, func(t *testing.T) {
|
||||
forma := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
p, err := New(1450, forma, false, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
enc, err := forma.CreateEncoder()
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err := enc.Encode([][]byte{{byte(mch264.NALUTypeIDR)}})
|
||||
require.NoError(t, err)
|
||||
|
||||
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadH264{
|
||||
{byte(mch264.NALUTypeIDR)},
|
||||
}, u.Payload)
|
||||
|
||||
if ca == "standard" {
|
||||
pkts, err = enc.Encode([][]byte{{7, 4, 5, 6}}) // SPS
|
||||
require.NoError(t, err)
|
||||
|
||||
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err = enc.Encode([][]byte{{8, 1}}) // PPS
|
||||
require.NoError(t, err)
|
||||
|
||||
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
pkts, err = enc.Encode([][]byte{
|
||||
{7, 4, 5, 6}, // SPS
|
||||
{8, 1}, // PPS
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.Equal(t, []byte{7, 4, 5, 6}, forma.SPS)
|
||||
require.Equal(t, []byte{8, 1}, forma.PPS)
|
||||
|
||||
pkts, err = enc.Encode([][]byte{{byte(mch264.NALUTypeIDR)}})
|
||||
require.NoError(t, err)
|
||||
|
||||
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadH264{
|
||||
{0x07, 4, 5, 6},
|
||||
{0x08, 1},
|
||||
{byte(mch264.NALUTypeIDR)},
|
||||
}, u.Payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestH264RTPOversized(t *testing.T) {
|
||||
forma := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
logged := false
|
||||
|
||||
p, err := New(1460, forma, false,
|
||||
Logger(func(_ logger.Level, s string, i ...any) {
|
||||
require.Equal(t, "RTP packets are too big, remuxing them into smaller ones", fmt.Sprintf(s, i...))
|
||||
logged = true
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
var out []*rtp.Packet
|
||||
|
||||
for _, pkt := range []*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
Padding: true,
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 124,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
Padding: true,
|
||||
},
|
||||
Payload: append([]byte{0x1c, 0b10000000}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 2000/4)...),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 125,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
Padding: true,
|
||||
},
|
||||
Payload: []byte{0x1c, 0b01000000, 0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
} {
|
||||
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
out = append(out, u.RTPPackets...)
|
||||
}
|
||||
|
||||
require.Equal(t, []*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 124,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: append(
|
||||
append([]byte{0x1c, 0x80}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 364)...),
|
||||
[]byte{0x01, 0x02}...,
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 125,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: append(
|
||||
[]byte{0x1c, 0x40, 0x03, 0x04},
|
||||
bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 136)...,
|
||||
),
|
||||
},
|
||||
}, out)
|
||||
|
||||
require.True(t, logged)
|
||||
}
|
||||
|
||||
func FuzzRTPH264ExtractParams(f *testing.F) {
|
||||
f.Fuzz(func(_ *testing.T, b []byte) {
|
||||
rtpH264ExtractParams(b)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,348 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph265"
|
||||
mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
// H265-related parameters
|
||||
var (
|
||||
H265DefaultVPS = []byte{
|
||||
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20,
|
||||
0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03,
|
||||
0x00, 0x00, 0x03, 0x00, 0x7b, 0x18, 0xb0, 0x24,
|
||||
}
|
||||
|
||||
H265DefaultSPS = []byte{
|
||||
0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03,
|
||||
0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
|
||||
0x00, 0x7b, 0xa0, 0x07, 0x82, 0x00, 0x88, 0x7d,
|
||||
0xb6, 0x71, 0x8b, 0x92, 0x44, 0x80, 0x53, 0x88,
|
||||
0x88, 0x92, 0xcf, 0x24, 0xa6, 0x92, 0x72, 0xc9,
|
||||
0x12, 0x49, 0x22, 0xdc, 0x91, 0xaa, 0x48, 0xfc,
|
||||
0xa2, 0x23, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a,
|
||||
0x02, 0x02, 0x02, 0x01,
|
||||
}
|
||||
|
||||
H265DefaultPPS = []byte{
|
||||
0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40,
|
||||
}
|
||||
)
|
||||
|
||||
// extract VPS, SPS and PPS without decoding RTP packets
|
||||
func rtpH265ExtractParams(payload []byte) ([]byte, []byte, []byte) {
|
||||
if len(payload) < 2 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
typ := mch265.NALUType((payload[0] >> 1) & 0b111111)
|
||||
|
||||
switch typ {
|
||||
case mch265.NALUType_VPS_NUT:
|
||||
return payload, nil, nil
|
||||
|
||||
case mch265.NALUType_SPS_NUT:
|
||||
return nil, payload, nil
|
||||
|
||||
case mch265.NALUType_PPS_NUT:
|
||||
return nil, nil, payload
|
||||
|
||||
case mch265.NALUType_AggregationUnit:
|
||||
payload = payload[2:]
|
||||
var vps []byte
|
||||
var sps []byte
|
||||
var pps []byte
|
||||
|
||||
for len(payload) > 0 {
|
||||
if len(payload) < 2 {
|
||||
break
|
||||
}
|
||||
|
||||
size := uint16(payload[0])<<8 | uint16(payload[1])
|
||||
payload = payload[2:]
|
||||
|
||||
if size == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if int(size) > len(payload) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
nalu := payload[:size]
|
||||
payload = payload[size:]
|
||||
|
||||
typ = mch265.NALUType((nalu[0] >> 1) & 0b111111)
|
||||
|
||||
switch typ {
|
||||
case mch265.NALUType_VPS_NUT:
|
||||
vps = nalu
|
||||
|
||||
case mch265.NALUType_SPS_NUT:
|
||||
sps = nalu
|
||||
|
||||
case mch265.NALUType_PPS_NUT:
|
||||
pps = nalu
|
||||
}
|
||||
}
|
||||
|
||||
return vps, sps, pps
|
||||
|
||||
default:
|
||||
return nil, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
type h265 struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.H265
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtph265.Encoder
|
||||
decoder *rtph265.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *h265) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder(nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *h265) createEncoder(
|
||||
ssrc *uint32,
|
||||
initialSequenceNumber *uint16,
|
||||
) error {
|
||||
t.encoder = &rtph265.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
SSRC: ssrc,
|
||||
InitialSequenceNumber: initialSequenceNumber,
|
||||
MaxDONDiff: t.Format.MaxDONDiff,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *h265) updateTrackParametersFromRTPPacket(payload []byte) {
|
||||
vps, sps, pps := rtpH265ExtractParams(payload)
|
||||
|
||||
if (vps != nil && !bytes.Equal(vps, t.Format.VPS)) ||
|
||||
(sps != nil && !bytes.Equal(sps, t.Format.SPS)) ||
|
||||
(pps != nil && !bytes.Equal(pps, t.Format.PPS)) {
|
||||
if vps == nil {
|
||||
vps = t.Format.VPS
|
||||
}
|
||||
if sps == nil {
|
||||
sps = t.Format.SPS
|
||||
}
|
||||
if pps == nil {
|
||||
pps = t.Format.PPS
|
||||
}
|
||||
t.Format.SafeSetParams(vps, sps, pps)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *h265) updateTrackParametersFromAU(au unit.PayloadH265) {
|
||||
vps := t.Format.VPS
|
||||
sps := t.Format.SPS
|
||||
pps := t.Format.PPS
|
||||
update := false
|
||||
|
||||
for _, nalu := range au {
|
||||
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
|
||||
|
||||
switch typ {
|
||||
case mch265.NALUType_VPS_NUT:
|
||||
if !bytes.Equal(nalu, t.Format.VPS) {
|
||||
vps = nalu
|
||||
update = true
|
||||
}
|
||||
|
||||
case mch265.NALUType_SPS_NUT:
|
||||
if !bytes.Equal(nalu, t.Format.SPS) {
|
||||
sps = nalu
|
||||
update = true
|
||||
}
|
||||
|
||||
case mch265.NALUType_PPS_NUT:
|
||||
if !bytes.Equal(nalu, t.Format.PPS) {
|
||||
pps = nalu
|
||||
update = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if update {
|
||||
t.Format.SafeSetParams(vps, sps, pps)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *h265) remuxAccessUnit(au unit.PayloadH265) unit.PayloadH265 {
|
||||
isKeyFrame := false
|
||||
n := 0
|
||||
|
||||
for _, nalu := range au {
|
||||
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
|
||||
|
||||
switch typ {
|
||||
case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT:
|
||||
continue
|
||||
|
||||
case mch265.NALUType_AUD_NUT:
|
||||
continue
|
||||
|
||||
case mch265.NALUType_IDR_W_RADL, mch265.NALUType_IDR_N_LP, mch265.NALUType_CRA_NUT:
|
||||
if !isKeyFrame {
|
||||
isKeyFrame = true
|
||||
|
||||
// prepend parameters
|
||||
if t.Format.VPS != nil && t.Format.SPS != nil && t.Format.PPS != nil {
|
||||
n += 3
|
||||
}
|
||||
}
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
filteredAU := make([][]byte, n)
|
||||
i := 0
|
||||
|
||||
if isKeyFrame && t.Format.VPS != nil && t.Format.SPS != nil && t.Format.PPS != nil {
|
||||
filteredAU[0] = t.Format.VPS
|
||||
filteredAU[1] = t.Format.SPS
|
||||
filteredAU[2] = t.Format.PPS
|
||||
i = 3
|
||||
}
|
||||
|
||||
for _, nalu := range au {
|
||||
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
|
||||
|
||||
switch typ {
|
||||
case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT:
|
||||
continue
|
||||
|
||||
case mch265.NALUType_AUD_NUT:
|
||||
continue
|
||||
}
|
||||
|
||||
filteredAU[i] = nalu
|
||||
i++
|
||||
}
|
||||
|
||||
return filteredAU
|
||||
}
|
||||
|
||||
func (t *h265) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
t.updateTrackParametersFromAU(u.Payload.(unit.PayloadH265))
|
||||
u.Payload = t.remuxAccessUnit(u.Payload.(unit.PayloadH265))
|
||||
|
||||
if !u.NilPayload() {
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH265))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *h265) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
t.updateTrackParametersFromRTPPacket(pkt.Payload)
|
||||
|
||||
if t.encoder == nil {
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
// RTP packets exceed maximum size: start re-encoding them
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
t.Parent.Log(logger.Info, "RTP packets are too big, remuxing them into smaller ones")
|
||||
|
||||
v1 := pkt.SSRC
|
||||
v2 := pkt.SequenceNumber
|
||||
err := t.createEncoder(&v1, &v2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
au, err := t.decoder.Decode(pkt)
|
||||
|
||||
if t.encoder != nil {
|
||||
u.RTPPackets = nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, rtph265.ErrNonStartingPacketAndNoPrevious) ||
|
||||
errors.Is(err, rtph265.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = t.remuxAccessUnit(au)
|
||||
}
|
||||
|
||||
// route packet as is
|
||||
if t.encoder == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// encode into RTP
|
||||
if !u.NilPayload() {
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH265))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, newPKT := range u.RTPPackets {
|
||||
newPKT.Timestamp = pkt.Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,295 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
func TestH265RemoveAUD(t *testing.T) {
|
||||
forma := &format.H265{}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
u := &unit.Unit{
|
||||
PTS: 30000,
|
||||
Payload: unit.PayloadH265{
|
||||
{byte(mch265.NALUType_AUD_NUT) << 1, 0},
|
||||
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadH265{
|
||||
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
|
||||
}, u.Payload)
|
||||
}
|
||||
|
||||
func TestH265AddParams(t *testing.T) {
|
||||
forma := &format.H265{}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
u1 := &unit.Unit{
|
||||
PTS: 30000,
|
||||
Payload: unit.PayloadH265{
|
||||
{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3},
|
||||
{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6},
|
||||
{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9},
|
||||
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(u1)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadH265{
|
||||
{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3},
|
||||
{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6},
|
||||
{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9},
|
||||
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
|
||||
}, u1.Payload)
|
||||
|
||||
u2 := &unit.Unit{
|
||||
PTS: 30000 * 2,
|
||||
Payload: unit.PayloadH265{
|
||||
{byte(mch265.NALUType_CRA_NUT) << 1, 1},
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(u2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test that params have been added to the SDP
|
||||
require.Equal(t, []byte{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, forma.VPS)
|
||||
require.Equal(t, []byte{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, forma.SPS)
|
||||
require.Equal(t, []byte{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, forma.PPS)
|
||||
|
||||
// test that params have been added to the frame
|
||||
require.Equal(t, unit.PayloadH265{
|
||||
{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3},
|
||||
{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6},
|
||||
{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9},
|
||||
{byte(mch265.NALUType_CRA_NUT) << 1, 1},
|
||||
}, u2.Payload)
|
||||
|
||||
// test that timestamp has increased
|
||||
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
|
||||
}
|
||||
|
||||
func TestH265ProcessEmptyUnit(t *testing.T) {
|
||||
forma := &format.H265{
|
||||
PayloadTyp: 96,
|
||||
}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
unit := &unit.Unit{
|
||||
Payload: unit.PayloadH265{
|
||||
{byte(mch265.NALUType_VPS_NUT) << 1, 10, 11, 12}, // VPS
|
||||
{byte(mch265.NALUType_SPS_NUT) << 1, 13, 14, 15}, // SPS
|
||||
{byte(mch265.NALUType_PPS_NUT) << 1, 16, 17, 18}, // PPS
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(unit)
|
||||
require.NoError(t, err)
|
||||
|
||||
// if all NALUs have been removed, no RTP packets shall be generated.
|
||||
require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets)
|
||||
}
|
||||
|
||||
func TestH265RTPExtractParams(t *testing.T) {
|
||||
for _, ca := range []string{"standard", "aggregated"} {
|
||||
t.Run(ca, func(t *testing.T) {
|
||||
forma := &format.H265{
|
||||
PayloadTyp: 96,
|
||||
}
|
||||
|
||||
p, err := New(1450, forma, false, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
enc, err := forma.CreateEncoder()
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err := enc.Encode([][]byte{{byte(mch265.NALUType_CRA_NUT) << 1, 0}})
|
||||
require.NoError(t, err)
|
||||
|
||||
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadH265{
|
||||
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
|
||||
}, u.Payload)
|
||||
|
||||
if ca == "standard" {
|
||||
pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}})
|
||||
require.NoError(t, err)
|
||||
|
||||
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}})
|
||||
require.NoError(t, err)
|
||||
|
||||
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}})
|
||||
require.NoError(t, err)
|
||||
|
||||
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
pkts, err = enc.Encode([][]byte{
|
||||
{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3},
|
||||
{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6},
|
||||
{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.Equal(t, []byte{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, forma.VPS)
|
||||
require.Equal(t, []byte{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, forma.SPS)
|
||||
require.Equal(t, []byte{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, forma.PPS)
|
||||
|
||||
pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_CRA_NUT) << 1, 0}})
|
||||
require.NoError(t, err)
|
||||
|
||||
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
|
||||
err = p.ProcessRTPPacket(u, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadH265{
|
||||
{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3},
|
||||
{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6},
|
||||
{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9},
|
||||
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
|
||||
}, u.Payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestH265RTPOversized(t *testing.T) {
|
||||
forma := &format.H265{
|
||||
PayloadTyp: 96,
|
||||
VPS: []byte{byte(mch265.NALUType_VPS_NUT) << 1, 10, 11, 12},
|
||||
SPS: []byte{byte(mch265.NALUType_SPS_NUT) << 1, 13, 14, 15},
|
||||
PPS: []byte{byte(mch265.NALUType_PPS_NUT) << 1, 16, 17, 18},
|
||||
}
|
||||
|
||||
logged := false
|
||||
|
||||
p, err := New(1460, forma, false,
|
||||
Logger(func(_ logger.Level, s string, i ...any) {
|
||||
require.Equal(t, "RTP packets are too big, remuxing them into smaller ones", fmt.Sprintf(s, i...))
|
||||
logged = true
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
var out []*rtp.Packet
|
||||
|
||||
for _, pkt := range []*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
Padding: true,
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 124,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
Padding: true,
|
||||
},
|
||||
Payload: bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 2000/4),
|
||||
},
|
||||
} {
|
||||
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
out = append(out, u.RTPPackets...)
|
||||
}
|
||||
|
||||
require.Equal(t, []*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 124,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: append(
|
||||
append([]byte{0x63, 0x02, 0x80, 0x03, 0x04}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 363)...),
|
||||
[]byte{0x01, 0x02, 0x03}...,
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 125,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: append(
|
||||
[]byte{0x63, 0x02, 0x40, 0x04},
|
||||
bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 135)...,
|
||||
),
|
||||
},
|
||||
}, out)
|
||||
|
||||
require.True(t, logged)
|
||||
}
|
||||
|
||||
func FuzzRTPH265ExtractParams(f *testing.F) {
|
||||
f.Fuzz(func(_ *testing.T, b []byte) {
|
||||
rtpH265ExtractParams(b)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpklv"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type klv struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.KLV
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpklv.Encoder
|
||||
decoder *rtpklv.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *klv) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *klv) createEncoder() error {
|
||||
t.encoder = &rtpklv.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *klv) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
if t.encoder == nil {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadKLV))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *klv) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
un, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadKLV(un)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestKlvCreateEncoder(t *testing.T) {
|
||||
forma := &format.KLV{
|
||||
PayloadTyp: 96,
|
||||
}
|
||||
p, err := New(1472, forma, false, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
klvProc := p.(*klv)
|
||||
err = klvProc.createEncoder()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestKlvProcessUnit(t *testing.T) {
|
||||
forma := &format.KLV{
|
||||
PayloadTyp: 96,
|
||||
}
|
||||
p, err := New(1472, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create test Unit
|
||||
when := int64(5000000000) // 5 seconds in nanoseconds
|
||||
u := unit.Unit{
|
||||
RTPPackets: nil,
|
||||
PTS: when,
|
||||
Payload: unit.PayloadKLV{1, 2, 3, 4},
|
||||
}
|
||||
uu := &u
|
||||
|
||||
// process the unit
|
||||
err = p.ProcessUnit(uu)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestKlvProcessRTPPacket(t *testing.T) {
|
||||
forma := &format.KLV{
|
||||
PayloadTyp: 96,
|
||||
}
|
||||
p, err := New(1472, forma, false, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkt := &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 3446,
|
||||
Timestamp: 175349,
|
||||
SSRC: 563423,
|
||||
Padding: true,
|
||||
},
|
||||
Payload: []byte{1, 2, 3, 4},
|
||||
PaddingSize: 20,
|
||||
}
|
||||
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}}
|
||||
err = p.ProcessRTPPacket(u, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 3446,
|
||||
Timestamp: 175349,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: []byte{1, 2, 3, 4},
|
||||
}, pkt)
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
package codecprocessor //nolint:dupl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type lpcm struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.LPCM
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtplpcm.Encoder
|
||||
decoder *rtplpcm.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *lpcm) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *lpcm) createEncoder() error {
|
||||
t.encoder = &rtplpcm.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
BitDepth: t.Format.BitDepth,
|
||||
ChannelCount: t.Format.ChannelCount,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *lpcm) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadLPCM))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *lpcm) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
samples, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadLPCM(samples)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLPCMProcessUnit(t *testing.T) {
|
||||
forma := &format.LPCM{
|
||||
PayloadTyp: 96,
|
||||
BitDepth: 16,
|
||||
SampleRate: 44100,
|
||||
ChannelCount: 2,
|
||||
}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
unit := &unit.Unit{
|
||||
Payload: unit.PayloadLPCM{1, 2, 3, 4},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(unit)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []*rtp.Packet{{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: unit.RTPPackets[0].SequenceNumber,
|
||||
Timestamp: unit.RTPPackets[0].Timestamp,
|
||||
SSRC: unit.RTPPackets[0].SSRC,
|
||||
},
|
||||
Payload: []byte{1, 2, 3, 4},
|
||||
}}, unit.RTPPackets)
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
package codecprocessor //nolint:dupl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmjpeg"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type mjpeg struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.MJPEG
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpmjpeg.Encoder
|
||||
decoder *rtpmjpeg.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *mjpeg) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mjpeg) createEncoder() error {
|
||||
t.encoder = &rtpmjpeg.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *mjpeg) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
// encode into RTP
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMJPEG))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mjpeg) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
frame, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpmjpeg.ErrNonStartingPacketAndNoPrevious) ||
|
||||
errors.Is(err, rtpmjpeg.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadMJPEG(frame)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
package codecprocessor //nolint:dupl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1audio"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type mpeg1Audio struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.MPEG1Audio
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpmpeg1audio.Encoder
|
||||
decoder *rtpmpeg1audio.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *mpeg1Audio) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg1Audio) createEncoder() error {
|
||||
t.encoder = &rtpmpeg1audio.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *mpeg1Audio) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG1Audio))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg1Audio) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
frames, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious) ||
|
||||
errors.Is(err, rtpmpeg1audio.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadMPEG1Audio(frames)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package codecprocessor //nolint:dupl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1video"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
// MPEG-1 video related parameters
|
||||
var (
|
||||
MPEG1VideoDefaultConfig = []byte{
|
||||
0x00, 0x00, 0x01, 0xb3, 0x78, 0x04, 0x38, 0x35,
|
||||
0xff, 0xff, 0xe0, 0x18, 0x00, 0x00, 0x01, 0xb5,
|
||||
0x14, 0x4a, 0x00, 0x01, 0x00, 0x00,
|
||||
}
|
||||
)
|
||||
|
||||
type mpeg1Video struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.MPEG1Video
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpmpeg1video.Encoder
|
||||
decoder *rtpmpeg1video.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *mpeg1Video) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg1Video) createEncoder() error {
|
||||
t.encoder = &rtpmpeg1video.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *mpeg1Video) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
// encode into RTP
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG1Video))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg1Video) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
frame, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpmpeg1video.ErrNonStartingPacketAndNoPrevious) ||
|
||||
errors.Is(err, rtpmpeg1video.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadMPEG1Video(frame)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type mpeg4Audio struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.MPEG4Audio
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpmpeg4audio.Encoder
|
||||
decoder *rtpmpeg4audio.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *mpeg4Audio) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg4Audio) createEncoder() error {
|
||||
t.encoder = &rtpmpeg4audio.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
SizeLength: t.Format.SizeLength,
|
||||
IndexLength: t.Format.IndexLength,
|
||||
IndexDeltaLength: t.Format.IndexDeltaLength,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *mpeg4Audio) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG4Audio))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg4Audio) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
aus, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpmpeg4audio.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadMPEG4Audio(aus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type mpeg4AudioLATM struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.MPEG4AudioLATM
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpfragmented.Encoder
|
||||
decoder *rtpfragmented.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *mpeg4AudioLATM) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg4AudioLATM) createEncoder() error {
|
||||
t.encoder = &rtpfragmented.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *mpeg4AudioLATM) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG4AudioLATM))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg4AudioLATM) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
el, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpmpeg4audio.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadMPEG4AudioLATM(el)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
package codecprocessor //nolint:dupl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
// MPEG-4 video related parameters
|
||||
var (
|
||||
MPEG4VideoDefaultConfig = []byte{
|
||||
0x00, 0x00, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x01,
|
||||
0xb5, 0x89, 0x13, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x01, 0x20, 0x00, 0xc4, 0x8d, 0x88, 0x00,
|
||||
0xf5, 0x3c, 0x04, 0x87, 0x14, 0x63, 0x00, 0x00,
|
||||
0x01, 0xb2, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x38,
|
||||
0x2e, 0x31, 0x33, 0x34, 0x2e, 0x31, 0x30, 0x30,
|
||||
}
|
||||
)
|
||||
|
||||
type mpeg4Video struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.MPEG4Video
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpfragmented.Encoder
|
||||
decoder *rtpfragmented.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *mpeg4Video) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg4Video) createEncoder() error {
|
||||
t.encoder = &rtpfragmented.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *mpeg4Video) updateTrackParameters(frame unit.PayloadMPEG4Video) {
|
||||
if bytes.HasPrefix(frame, []byte{0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode)}) {
|
||||
end := bytes.Index(frame[4:], []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)})
|
||||
if end < 0 {
|
||||
return
|
||||
}
|
||||
conf := frame[:end+4]
|
||||
|
||||
if !bytes.Equal(conf, t.Format.Config) {
|
||||
t.Format.SafeSetParams(conf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *mpeg4Video) remuxFrame(frame unit.PayloadMPEG4Video) unit.PayloadMPEG4Video {
|
||||
// remove config
|
||||
if bytes.HasPrefix(frame, []byte{0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode)}) {
|
||||
end := bytes.Index(frame[4:], []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)})
|
||||
if end >= 0 {
|
||||
frame = frame[end+4:]
|
||||
}
|
||||
}
|
||||
|
||||
// add config
|
||||
if bytes.Contains(frame, []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) {
|
||||
f := make([]byte, len(t.Format.Config)+len(frame))
|
||||
n := copy(f, t.Format.Config)
|
||||
copy(f[n:], frame)
|
||||
frame = f
|
||||
}
|
||||
|
||||
if len(frame) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
func (t *mpeg4Video) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
t.updateTrackParameters(u.Payload.(unit.PayloadMPEG4Video))
|
||||
u.Payload = t.remuxFrame(u.Payload.(unit.PayloadMPEG4Video))
|
||||
|
||||
if !u.NilPayload() {
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG4Video))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mpeg4Video) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
t.updateTrackParameters(pkt.Payload)
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
frame, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpfragmented.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = t.remuxFrame(frame)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMPEG4VideoProcessUnit(t *testing.T) {
|
||||
forma := &format.MPEG4Video{
|
||||
PayloadTyp: 96,
|
||||
}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
u1 := &unit.Unit{
|
||||
PTS: 30000,
|
||||
Payload: unit.PayloadMPEG4Video{
|
||||
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
|
||||
0, 0, 1, 0xFF,
|
||||
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
|
||||
0, 0, 1, 0xF0,
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(u1)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, unit.PayloadMPEG4Video{
|
||||
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
|
||||
0, 0, 1, 0xFF,
|
||||
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
|
||||
0, 0, 1, 0xF0,
|
||||
}, u1.Payload)
|
||||
|
||||
u2 := &unit.Unit{
|
||||
PTS: 30000 * 2,
|
||||
Payload: unit.PayloadMPEG4Video{
|
||||
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
|
||||
0, 0, 1, 0xF1,
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(u2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test that params have been added to the SDP
|
||||
require.Equal(t, []byte{
|
||||
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
|
||||
0, 0, 1, 0xFF,
|
||||
}, forma.Config)
|
||||
|
||||
// test that params have been added to the frame
|
||||
require.Equal(t, unit.PayloadMPEG4Video{
|
||||
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
|
||||
0, 0, 1, 0xFF,
|
||||
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
|
||||
0, 0, 1, 0xF1,
|
||||
}, u2.Payload)
|
||||
|
||||
// test that timestamp has increased
|
||||
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpsimpleaudio"
|
||||
mcopus "github.com/bluenviron/mediacommon/v2/pkg/codecs/opus"
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type opus struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.Opus
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpsimpleaudio.Encoder
|
||||
decoder *rtpsimpleaudio.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *opus) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *opus) createEncoder() error {
|
||||
t.encoder = &rtpsimpleaudio.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *opus) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
var rtpPackets []*rtp.Packet //nolint:prealloc
|
||||
pts := u.PTS
|
||||
|
||||
for _, packet := range u.Payload.(unit.PayloadOpus) {
|
||||
pkt, err := t.encoder.Encode(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkt.Timestamp += t.randomStart + uint32(pts)
|
||||
|
||||
rtpPackets = append(rtpPackets, pkt)
|
||||
pts += mcopus.PacketDuration2(packet)
|
||||
}
|
||||
|
||||
u.RTPPackets = rtpPackets
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *opus) ProcessRTPPacket(
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
packet, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadOpus{packet}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOpusProcessUnit(t *testing.T) {
|
||||
forma := &format.Opus{
|
||||
PayloadTyp: 96,
|
||||
ChannelCount: 2,
|
||||
}
|
||||
|
||||
p, err := New(1450, forma, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
unit := &unit.Unit{
|
||||
Payload: unit.PayloadOpus{
|
||||
{
|
||||
0xfc, 0x1e, 0x61, 0x96, 0xfc, 0xf7, 0x9b, 0x23,
|
||||
0x5b, 0xc9, 0x56, 0xad, 0x05, 0x12, 0x2f, 0x6c,
|
||||
0xc0, 0x0c, 0x2c, 0x17, 0x7b, 0x1a, 0xde, 0x1b,
|
||||
0x37, 0x89, 0xc5, 0xbb, 0x34, 0xbb, 0x1c, 0x74,
|
||||
0x7c, 0x18, 0x0a, 0xde, 0xa1, 0x2b, 0x86, 0x1d,
|
||||
0x60, 0xa2, 0xb6, 0xce, 0xe7, 0x0e, 0x17, 0x1b,
|
||||
0xc7, 0xd4, 0xd1, 0x2a, 0x68, 0x1f, 0x05, 0x2b,
|
||||
0x22, 0x80, 0x68, 0x12, 0x0c, 0x45, 0xbc, 0x3a,
|
||||
0xd2, 0x1b, 0xf2, 0x8a, 0x77, 0x5f, 0x2b, 0x34,
|
||||
0x97, 0x34, 0x09, 0x6d, 0x05, 0x5f, 0x48, 0x0c,
|
||||
0x45, 0xb5, 0xae, 0x2a, 0x90, 0x21, 0xda, 0xfb,
|
||||
0x5b, 0x10,
|
||||
},
|
||||
{
|
||||
0xfc, 0x1d, 0x61, 0x96, 0xfa, 0x7a, 0x90, 0x59,
|
||||
0xb7, 0x10, 0xd7, 0x03, 0x84, 0x27, 0x3f, 0x52,
|
||||
0x9f, 0xd7, 0x38, 0x9f, 0xbc, 0xff, 0x7d, 0x62,
|
||||
0xe8, 0x60, 0x64, 0x54, 0x9d, 0x1a, 0xd1, 0x7c,
|
||||
0x1a, 0x0a, 0x76, 0xd2, 0x30, 0x9b, 0xbe, 0xc7,
|
||||
0x5d, 0x37, 0x42, 0xe7, 0xdd, 0xfc, 0xc7, 0x03,
|
||||
0xab, 0x90, 0xd9, 0x9b, 0xad, 0xf4, 0x88, 0xd3,
|
||||
0x81, 0xbf, 0xd0, 0x68, 0x10, 0x0c, 0x46, 0xa4,
|
||||
0xe8, 0x83, 0xd9, 0x6b, 0x7a, 0x25, 0xed, 0x81,
|
||||
0xf6, 0x92, 0x14, 0x70, 0x6c, 0x48, 0x0c, 0x45,
|
||||
0xb4, 0xf2, 0x61, 0x9b, 0xd7, 0x62, 0x58, 0x87,
|
||||
},
|
||||
{
|
||||
0xfc, 0x1e, 0x61, 0x96, 0xfa, 0x7f, 0x61, 0x51,
|
||||
0x2f, 0x10, 0x81, 0x22, 0x01, 0x81, 0x46, 0x5e,
|
||||
0xbd, 0xb0, 0x87, 0xcb, 0x0a, 0xa6, 0xd6, 0xd3,
|
||||
0xec, 0x7c, 0x9e, 0xf5, 0x07, 0x5a, 0x07, 0x1b,
|
||||
0x7c, 0x19, 0x0a, 0xde, 0xa1, 0x38, 0xe4, 0x51,
|
||||
0x7f, 0x54, 0x6f, 0x91, 0x9f, 0xda, 0x2b, 0x40,
|
||||
0x80, 0x36, 0xeb, 0xe3, 0xc2, 0x58, 0x12, 0x55,
|
||||
0x80, 0x65, 0x14, 0x68, 0x12, 0x0c, 0x46, 0xa4,
|
||||
0xdc, 0x6e, 0x62, 0x79, 0xf9, 0x09, 0x28, 0x11,
|
||||
0xab, 0xab, 0xd3, 0x84, 0x7d, 0x93, 0x34, 0x48,
|
||||
0x0c, 0x46, 0x9a, 0x0c, 0xb0, 0xfe, 0x7a, 0x5b,
|
||||
0x22, 0x85, 0x4a, 0x73, 0x0d, 0xd0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = p.ProcessUnit(unit)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: unit.RTPPackets[0].SequenceNumber,
|
||||
Timestamp: unit.RTPPackets[0].Timestamp,
|
||||
SSRC: unit.RTPPackets[0].SSRC,
|
||||
},
|
||||
Payload: []byte{
|
||||
0xfc, 0x1e, 0x61, 0x96, 0xfc, 0xf7, 0x9b, 0x23,
|
||||
0x5b, 0xc9, 0x56, 0xad, 0x05, 0x12, 0x2f, 0x6c,
|
||||
0xc0, 0x0c, 0x2c, 0x17, 0x7b, 0x1a, 0xde, 0x1b,
|
||||
0x37, 0x89, 0xc5, 0xbb, 0x34, 0xbb, 0x1c, 0x74,
|
||||
0x7c, 0x18, 0x0a, 0xde, 0xa1, 0x2b, 0x86, 0x1d,
|
||||
0x60, 0xa2, 0xb6, 0xce, 0xe7, 0x0e, 0x17, 0x1b,
|
||||
0xc7, 0xd4, 0xd1, 0x2a, 0x68, 0x1f, 0x05, 0x2b,
|
||||
0x22, 0x80, 0x68, 0x12, 0x0c, 0x45, 0xbc, 0x3a,
|
||||
0xd2, 0x1b, 0xf2, 0x8a, 0x77, 0x5f, 0x2b, 0x34,
|
||||
0x97, 0x34, 0x09, 0x6d, 0x05, 0x5f, 0x48, 0x0c,
|
||||
0x45, 0xb5, 0xae, 0x2a, 0x90, 0x21, 0xda, 0xfb,
|
||||
0x5b, 0x10,
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: unit.RTPPackets[0].SequenceNumber + 1,
|
||||
Timestamp: unit.RTPPackets[0].Timestamp + 960,
|
||||
SSRC: unit.RTPPackets[0].SSRC,
|
||||
},
|
||||
Payload: []byte{
|
||||
0xfc, 0x1d, 0x61, 0x96, 0xfa, 0x7a, 0x90, 0x59,
|
||||
0xb7, 0x10, 0xd7, 0x03, 0x84, 0x27, 0x3f, 0x52,
|
||||
0x9f, 0xd7, 0x38, 0x9f, 0xbc, 0xff, 0x7d, 0x62,
|
||||
0xe8, 0x60, 0x64, 0x54, 0x9d, 0x1a, 0xd1, 0x7c,
|
||||
0x1a, 0x0a, 0x76, 0xd2, 0x30, 0x9b, 0xbe, 0xc7,
|
||||
0x5d, 0x37, 0x42, 0xe7, 0xdd, 0xfc, 0xc7, 0x03,
|
||||
0xab, 0x90, 0xd9, 0x9b, 0xad, 0xf4, 0x88, 0xd3,
|
||||
0x81, 0xbf, 0xd0, 0x68, 0x10, 0x0c, 0x46, 0xa4,
|
||||
0xe8, 0x83, 0xd9, 0x6b, 0x7a, 0x25, 0xed, 0x81,
|
||||
0xf6, 0x92, 0x14, 0x70, 0x6c, 0x48, 0x0c, 0x45,
|
||||
0xb4, 0xf2, 0x61, 0x9b, 0xd7, 0x62, 0x58, 0x87,
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: unit.RTPPackets[0].SequenceNumber + 2,
|
||||
Timestamp: unit.RTPPackets[0].Timestamp + 960*2,
|
||||
SSRC: unit.RTPPackets[0].SSRC,
|
||||
},
|
||||
Payload: []byte{
|
||||
0xfc, 0x1e, 0x61, 0x96, 0xfa, 0x7f, 0x61, 0x51,
|
||||
0x2f, 0x10, 0x81, 0x22, 0x01, 0x81, 0x46, 0x5e,
|
||||
0xbd, 0xb0, 0x87, 0xcb, 0x0a, 0xa6, 0xd6, 0xd3,
|
||||
0xec, 0x7c, 0x9e, 0xf5, 0x07, 0x5a, 0x07, 0x1b,
|
||||
0x7c, 0x19, 0x0a, 0xde, 0xa1, 0x38, 0xe4, 0x51,
|
||||
0x7f, 0x54, 0x6f, 0x91, 0x9f, 0xda, 0x2b, 0x40,
|
||||
0x80, 0x36, 0xeb, 0xe3, 0xc2, 0x58, 0x12, 0x55,
|
||||
0x80, 0x65, 0x14, 0x68, 0x12, 0x0c, 0x46, 0xa4,
|
||||
0xdc, 0x6e, 0x62, 0x79, 0xf9, 0x09, 0x28, 0x11,
|
||||
0xab, 0xab, 0xd3, 0x84, 0x7d, 0x93, 0x34, 0x48,
|
||||
0x0c, 0x46, 0x9a, 0x0c, 0xb0, 0xfe, 0x7a, 0x5b,
|
||||
0x22, 0x85, 0x4a, 0x73, 0x0d, 0xd0,
|
||||
},
|
||||
},
|
||||
}, unit.RTPPackets)
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
// Package codecprocessor contains codec-specific processing.
|
||||
package codecprocessor
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
func randUint32() (uint32, error) {
|
||||
var b [4]byte
|
||||
_, err := rand.Read(b[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil
|
||||
}
|
||||
|
||||
// Processor is the codec-specific part of the processing that happens inside stream.Stream.
|
||||
type Processor interface {
|
||||
// process a Unit.
|
||||
ProcessUnit(*unit.Unit) error
|
||||
|
||||
// process a RTP packet.
|
||||
ProcessRTPPacket(
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error
|
||||
|
||||
initialize() error
|
||||
}
|
||||
|
||||
// New allocates a Processor.
|
||||
func New(
|
||||
rtpMaxPayloadSize int,
|
||||
forma format.Format,
|
||||
generateRTPPackets bool,
|
||||
parent logger.Writer,
|
||||
) (Processor, error) {
|
||||
var proc Processor
|
||||
|
||||
switch forma := forma.(type) {
|
||||
case *format.AV1:
|
||||
proc = &av1{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.VP9:
|
||||
proc = &vp9{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.VP8:
|
||||
proc = &vp8{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.H265:
|
||||
proc = &h265{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.H264:
|
||||
proc = &h264{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.MPEG4Video:
|
||||
proc = &mpeg4Video{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.MPEG1Video:
|
||||
proc = &mpeg1Video{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.MJPEG:
|
||||
proc = &mjpeg{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.Opus:
|
||||
proc = &opus{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.KLV:
|
||||
proc = &klv{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.MPEG4Audio:
|
||||
proc = &mpeg4Audio{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.MPEG4AudioLATM:
|
||||
proc = &mpeg4AudioLATM{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.MPEG1Audio:
|
||||
proc = &mpeg1Audio{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.AC3:
|
||||
proc = &ac3{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.G711:
|
||||
proc = &g711{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
case *format.LPCM:
|
||||
proc = &lpcm{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
default:
|
||||
proc = &generic{
|
||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||
Format: forma,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
Parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
err := proc.initialize()
|
||||
return proc, err
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
package codecprocessor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
in format.Format
|
||||
out Processor
|
||||
}{
|
||||
{
|
||||
"av1",
|
||||
&format.AV1{},
|
||||
&av1{},
|
||||
},
|
||||
{
|
||||
"vp9",
|
||||
&format.VP9{},
|
||||
&vp9{},
|
||||
},
|
||||
{
|
||||
"vp8",
|
||||
&format.VP8{},
|
||||
&vp8{},
|
||||
},
|
||||
{
|
||||
"h265",
|
||||
&format.H265{},
|
||||
&h265{},
|
||||
},
|
||||
{
|
||||
"h264",
|
||||
&format.H264{},
|
||||
&h264{},
|
||||
},
|
||||
{
|
||||
"mpeg4 video",
|
||||
&format.MPEG4Video{},
|
||||
&mpeg4Video{},
|
||||
},
|
||||
{
|
||||
"mpeg1 video",
|
||||
&format.MPEG1Video{},
|
||||
&mpeg1Video{},
|
||||
},
|
||||
{
|
||||
"mpeg1 mjpeg",
|
||||
&format.MPEG1Audio{},
|
||||
&mpeg1Audio{},
|
||||
},
|
||||
{
|
||||
"opus",
|
||||
&format.Opus{},
|
||||
&opus{},
|
||||
},
|
||||
{
|
||||
"mpeg4 audio",
|
||||
&format.MPEG4Audio{},
|
||||
&mpeg4Audio{},
|
||||
},
|
||||
{
|
||||
"mpeg1 audio",
|
||||
&format.MPEG1Audio{},
|
||||
&mpeg1Audio{},
|
||||
},
|
||||
{
|
||||
"ac3",
|
||||
&format.AC3{},
|
||||
&ac3{},
|
||||
},
|
||||
{
|
||||
"g711",
|
||||
&format.G711{},
|
||||
&g711{},
|
||||
},
|
||||
{
|
||||
"lpcm",
|
||||
&format.LPCM{},
|
||||
&lpcm{},
|
||||
},
|
||||
{
|
||||
"klv",
|
||||
&format.KLV{
|
||||
PayloadTyp: 96,
|
||||
},
|
||||
&klv{},
|
||||
},
|
||||
{
|
||||
"generic",
|
||||
&format.Generic{},
|
||||
&generic{},
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
p, err := New(1450, ca.in, false, nil)
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, ca.out, p)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("800")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("8\x00\x00")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("80")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("a00")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("a0\x00\x00")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("0")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("a000")
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
package codecprocessor //nolint:dupl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp8"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type vp8 struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.VP8
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpvp8.Encoder
|
||||
decoder *rtpvp8.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *vp8) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *vp8) createEncoder() error {
|
||||
t.encoder = &rtpvp8.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *vp8) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadVP8))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *vp8) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
frame, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpvp8.ErrNonStartingPacketAndNoPrevious) ||
|
||||
errors.Is(err, rtpvp8.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadVP8(frame)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
package codecprocessor //nolint:dupl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp9"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
type vp9 struct {
|
||||
RTPMaxPayloadSize int
|
||||
Format *format.VP9
|
||||
GenerateRTPPackets bool
|
||||
Parent logger.Writer
|
||||
|
||||
encoder *rtpvp9.Encoder
|
||||
decoder *rtpvp9.Decoder
|
||||
randomStart uint32
|
||||
}
|
||||
|
||||
func (t *vp9) initialize() error {
|
||||
if t.GenerateRTPPackets {
|
||||
err := t.createEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.randomStart, err = randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *vp9) createEncoder() error {
|
||||
t.encoder = &rtpvp9.Encoder{
|
||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||
PayloadType: t.Format.PayloadTyp,
|
||||
}
|
||||
return t.encoder.Init()
|
||||
}
|
||||
|
||||
func (t *vp9) ProcessUnit(u *unit.Unit) error { //nolint:dupl
|
||||
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadVP9))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.RTPPackets = pkts
|
||||
|
||||
for _, pkt := range u.RTPPackets {
|
||||
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *vp9) ProcessRTPPacket( //nolint:dupl
|
||||
u *unit.Unit,
|
||||
hasNonRTSPReaders bool,
|
||||
) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||
}
|
||||
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.decoder != nil {
|
||||
if t.decoder == nil {
|
||||
var err error
|
||||
t.decoder, err = t.Format.CreateDecoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
frame, err := t.decoder.Decode(pkt)
|
||||
if err != nil {
|
||||
if errors.Is(err, rtpvp9.ErrNonStartingPacketAndNoPrevious) ||
|
||||
errors.Is(err, rtpvp9.ErrMorePacketsNeeded) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
u.Payload = unit.PayloadVP9(frame)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -152,6 +152,7 @@ type Conf struct {
|
|||
// General
|
||||
LogLevel LogLevel `json:"logLevel"`
|
||||
LogDestinations LogDestinations `json:"logDestinations"`
|
||||
LogStructured bool `json:"logStructured"`
|
||||
LogFile string `json:"logFile"`
|
||||
SysLogPrefix string `json:"sysLogPrefix"`
|
||||
ReadTimeout Duration `json:"readTimeout"`
|
||||
|
|
@ -321,6 +322,7 @@ func (conf *Conf) setDefaults() {
|
|||
// General
|
||||
conf.LogLevel = LogLevel(logger.Info)
|
||||
conf.LogDestinations = LogDestinations{logger.DestinationStdout}
|
||||
conf.LogStructured = false
|
||||
conf.LogFile = "mediamtx.log"
|
||||
conf.SysLogPrefix = "mediamtx"
|
||||
conf.ReadTimeout = 10 * Duration(time.Second)
|
||||
|
|
|
|||
|
|
@ -15,10 +15,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bluenviron/gortmplib"
|
||||
rtmpcodecs "github.com/bluenviron/gortmplib/pkg/codecs"
|
||||
"github.com/bluenviron/gortsplib/v5"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
||||
tscodecs "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/codecs"
|
||||
srt "github.com/datarhei/gosrt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pion/rtp"
|
||||
|
|
@ -440,14 +441,21 @@ func TestAPIProtocolListGet(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
track := &gortmplib.Track{
|
||||
Codec: &rtmpcodecs.H264{
|
||||
SPS: test.FormatH264.SPS,
|
||||
PPS: test.FormatH264.PPS,
|
||||
},
|
||||
}
|
||||
|
||||
w := &gortmplib.Writer{
|
||||
Conn: conn,
|
||||
Tracks: []format.Format{test.FormatH264},
|
||||
Tracks: []*gortmplib.Track{track},
|
||||
}
|
||||
err = w.Initialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = w.WriteH264(test.FormatH264, 2*time.Second, 2*time.Second, [][]byte{{5, 2, 3, 4}})
|
||||
err = w.WriteH264(track, 2*time.Second, 2*time.Second, [][]byte{{5, 2, 3, 4}})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
|
@ -557,7 +565,7 @@ func TestAPIProtocolListGet(t *testing.T) {
|
|||
defer conn.Close()
|
||||
|
||||
track := &mpegts.Track{
|
||||
Codec: &mpegts.CodecH264{},
|
||||
Codec: &tscodecs.H264{},
|
||||
}
|
||||
|
||||
bw := bufio.NewWriter(conn)
|
||||
|
|
@ -1037,14 +1045,21 @@ func TestAPIProtocolKick(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
track := &gortmplib.Track{
|
||||
Codec: &rtmpcodecs.H264{
|
||||
SPS: test.FormatH264.SPS,
|
||||
PPS: test.FormatH264.PPS,
|
||||
},
|
||||
}
|
||||
|
||||
w := &gortmplib.Writer{
|
||||
Conn: conn,
|
||||
Tracks: []format.Format{test.FormatH264},
|
||||
Tracks: []*gortmplib.Track{track},
|
||||
}
|
||||
err = w.Initialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = w.WriteH264(test.FormatH264, 2*time.Second, 2*time.Second, [][]byte{{5, 2, 3, 4}})
|
||||
err = w.WriteH264(track, 2*time.Second, 2*time.Second, [][]byte{{5, 2, 3, 4}})
|
||||
require.NoError(t, err)
|
||||
|
||||
case "webrtc":
|
||||
|
|
@ -1084,7 +1099,7 @@ func TestAPIProtocolKick(t *testing.T) {
|
|||
defer conn.Close()
|
||||
|
||||
track := &mpegts.Track{
|
||||
Codec: &mpegts.CodecH264{},
|
||||
Codec: &tscodecs.H264{},
|
||||
}
|
||||
|
||||
bw := bufio.NewWriter(conn)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
|
@ -55,10 +56,24 @@ var defaultConfPathsNotWin = []string{
|
|||
"/etc/mediamtx/mediamtx.yml",
|
||||
}
|
||||
|
||||
var cli struct {
|
||||
Confpath string `arg:"" default:""`
|
||||
Version bool `help:"print version"`
|
||||
Upgrade bool `help:"upgrade executable to the latest version"`
|
||||
func goArm() string {
|
||||
bi, _ := debug.ReadBuildInfo()
|
||||
for _, bs := range bi.Settings {
|
||||
if bs.Key == "GOARM" {
|
||||
return bs.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getArch() string {
|
||||
var arch string
|
||||
if runtime.GOARCH == "arm" {
|
||||
arch = "armv" + goArm()
|
||||
} else {
|
||||
arch = runtime.GOARCH
|
||||
}
|
||||
return arch
|
||||
}
|
||||
|
||||
func atLeastOneRecordDeleteAfter(pathConfs map[string]*conf.Path) bool {
|
||||
|
|
@ -82,6 +97,12 @@ func getRTPMaxPayloadSize(udpMaxPayloadSize int, rtspEncryption conf.Encryption)
|
|||
return v
|
||||
}
|
||||
|
||||
var cli struct {
|
||||
Confpath string `arg:"" default:""`
|
||||
Version bool `help:"print version"`
|
||||
Upgrade bool `help:"upgrade executable to the latest version"`
|
||||
}
|
||||
|
||||
// Core is an instance of MediaMTX.
|
||||
type Core struct {
|
||||
ctx context.Context
|
||||
|
|
@ -116,7 +137,7 @@ type Core struct {
|
|||
// New allocates a Core.
|
||||
func New(args []string) (*Core, bool) {
|
||||
parser, err := kong.New(&cli,
|
||||
kong.Description("MediaMTX "+string(version)),
|
||||
kong.Description("MediaMTX "+string(version)+", "+runtime.GOOS+", "+getArch()),
|
||||
kong.UsageOnError(),
|
||||
kong.ValueFormatter(func(value *kong.Value) string {
|
||||
switch value.Name {
|
||||
|
|
@ -157,7 +178,14 @@ func New(args []string) (*Core, bool) {
|
|||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
tempLogger, _ := logger.New(logger.Warn, []logger.Destination{logger.DestinationStdout}, "", "")
|
||||
tempLogger := &logger.Logger{
|
||||
Level: logger.Warn,
|
||||
Destinations: []logger.Destination{logger.DestinationStdout},
|
||||
Structured: false,
|
||||
File: "",
|
||||
SysLogPrefix: "",
|
||||
}
|
||||
tempLogger.Initialize() //nolint:errcheck
|
||||
|
||||
confPaths := append([]string(nil), defaultConfPaths...)
|
||||
if runtime.GOOS != "windows" {
|
||||
|
|
@ -263,19 +291,22 @@ func (p *Core) createResources(initial bool) error {
|
|||
var err error
|
||||
|
||||
if p.logger == nil {
|
||||
p.logger, err = logger.New(
|
||||
logger.Level(p.conf.LogLevel),
|
||||
p.conf.LogDestinations,
|
||||
p.conf.LogFile,
|
||||
p.conf.SysLogPrefix,
|
||||
)
|
||||
i := &logger.Logger{
|
||||
Level: logger.Level(p.conf.LogLevel),
|
||||
Destinations: p.conf.LogDestinations,
|
||||
Structured: p.conf.LogStructured,
|
||||
File: p.conf.LogFile,
|
||||
SysLogPrefix: p.conf.SysLogPrefix,
|
||||
}
|
||||
err = i.Initialize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.logger = i
|
||||
}
|
||||
|
||||
if initial {
|
||||
p.Log(logger.Info, "MediaMTX %s", version)
|
||||
p.Log(logger.Info, "MediaMTX "+string(version)+", "+runtime.GOOS+", "+getArch())
|
||||
|
||||
if p.confPath != "" {
|
||||
a, _ := filepath.Abs(p.confPath)
|
||||
|
|
@ -692,7 +723,8 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
|
|||
newConf.LogLevel != p.conf.LogLevel ||
|
||||
!reflect.DeepEqual(newConf.LogDestinations, p.conf.LogDestinations) ||
|
||||
newConf.LogFile != p.conf.LogFile ||
|
||||
newConf.SysLogPrefix != p.conf.SysLogPrefix
|
||||
newConf.SysLogPrefix != p.conf.SysLogPrefix ||
|
||||
newConf.LogStructured != p.conf.LogStructured
|
||||
|
||||
closeAuthManager := newConf == nil ||
|
||||
newConf.AuthMethod != p.conf.AuthMethod ||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bluenviron/gortmplib"
|
||||
rtmpcodecs "github.com/bluenviron/gortmplib/pkg/codecs"
|
||||
"github.com/bluenviron/gortsplib/v5"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
||||
tscodecs "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/codecs"
|
||||
srt "github.com/datarhei/gosrt"
|
||||
"github.com/pion/rtp"
|
||||
pwebrtc "github.com/pion/webrtc/v4"
|
||||
|
|
@ -217,14 +218,21 @@ webrtc_sessions_rtcp_packets_sent 0
|
|||
require.NoError(t, err2)
|
||||
defer conn.Close()
|
||||
|
||||
track := &gortmplib.Track{
|
||||
Codec: &rtmpcodecs.H264{
|
||||
SPS: test.FormatH264.SPS,
|
||||
PPS: test.FormatH264.PPS,
|
||||
},
|
||||
}
|
||||
|
||||
w := &gortmplib.Writer{
|
||||
Conn: conn,
|
||||
Tracks: []format.Format{test.FormatH264},
|
||||
Tracks: []*gortmplib.Track{track},
|
||||
}
|
||||
err2 = w.Initialize()
|
||||
require.NoError(t, err2)
|
||||
|
||||
err2 = w.WriteH264(test.FormatH264, 2*time.Second, 2*time.Second, [][]byte{{5, 2, 3, 4}})
|
||||
err2 = w.WriteH264(track, 2*time.Second, 2*time.Second, [][]byte{{5, 2, 3, 4}})
|
||||
require.NoError(t, err2)
|
||||
|
||||
<-terminate
|
||||
|
|
@ -245,14 +253,21 @@ webrtc_sessions_rtcp_packets_sent 0
|
|||
require.NoError(t, err2)
|
||||
defer conn.Close()
|
||||
|
||||
track := &gortmplib.Track{
|
||||
Codec: &rtmpcodecs.H264{
|
||||
SPS: test.FormatH264.SPS,
|
||||
PPS: test.FormatH264.PPS,
|
||||
},
|
||||
}
|
||||
|
||||
w := &gortmplib.Writer{
|
||||
Conn: conn,
|
||||
Tracks: []format.Format{test.FormatH264},
|
||||
Tracks: []*gortmplib.Track{track},
|
||||
}
|
||||
err2 = w.Initialize()
|
||||
require.NoError(t, err2)
|
||||
|
||||
err2 = w.WriteH264(test.FormatH264, 2*time.Second, 2*time.Second, [][]byte{{5, 2, 3, 4}})
|
||||
err2 = w.WriteH264(track, 2*time.Second, 2*time.Second, [][]byte{{5, 2, 3, 4}})
|
||||
require.NoError(t, err2)
|
||||
|
||||
<-terminate
|
||||
|
|
@ -318,7 +333,7 @@ webrtc_sessions_rtcp_packets_sent 0
|
|||
defer publisher.Close()
|
||||
|
||||
track := &mpegts.Track{
|
||||
Codec: &mpegts.CodecH264{},
|
||||
Codec: &tscodecs.H264{},
|
||||
}
|
||||
|
||||
bw := bufio.NewWriter(publisher)
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ func (pa *path) doReloadConf(newConf *conf.Path) {
|
|||
}
|
||||
|
||||
func (pa *path) doSourceStaticSetReady(req defs.PathSourceStaticSetReadyReq) {
|
||||
err := pa.setReady(req.Desc, req.GenerateRTPPackets, req.FillNTP)
|
||||
err := pa.setReady(req.Desc, req.UseRTPPackets, req.ReplaceNTP)
|
||||
if err != nil {
|
||||
req.Res <- defs.PathSourceStaticSetReadyRes{Err: err}
|
||||
return
|
||||
|
|
@ -478,7 +478,7 @@ func (pa *path) doAddPublisher(req defs.PathAddPublisherReq) {
|
|||
pa.source = req.Author
|
||||
pa.publisherQuery = req.AccessRequest.Query
|
||||
|
||||
err := pa.setReady(req.Desc, req.GenerateRTPPackets, req.FillNTP)
|
||||
err := pa.setReady(req.Desc, req.UseRTPPackets, req.ReplaceNTP)
|
||||
if err != nil {
|
||||
pa.source = nil
|
||||
req.Res <- defs.PathAddPublisherRes{Err: err}
|
||||
|
|
@ -586,9 +586,11 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) {
|
|||
return pa.stream.BytesSent()
|
||||
}(),
|
||||
Readers: func() []defs.APIPathSourceOrReader {
|
||||
ret := []defs.APIPathSourceOrReader{}
|
||||
ret := make([]defs.APIPathSourceOrReader, len(pa.readers))
|
||||
i := 0
|
||||
for r := range pa.readers {
|
||||
ret = append(ret, r.APIReaderDescribe())
|
||||
ret[i] = r.APIReaderDescribe()
|
||||
i++
|
||||
}
|
||||
return ret
|
||||
}(),
|
||||
|
|
@ -688,14 +690,14 @@ func (pa *path) onDemandPublisherStop(reason string) {
|
|||
pa.onDemandPublisherState = pathOnDemandStateInitial
|
||||
}
|
||||
|
||||
func (pa *path) setReady(desc *description.Session, generateRTPPackets bool, fillNTP bool) error {
|
||||
func (pa *path) setReady(desc *description.Session, useRTPPackets bool, replaceNTP bool) error {
|
||||
pa.stream = &stream.Stream{
|
||||
WriteQueueSize: pa.writeQueueSize,
|
||||
RTPMaxPayloadSize: pa.rtpMaxPayloadSize,
|
||||
Desc: desc,
|
||||
GenerateRTPPackets: generateRTPPackets,
|
||||
FillNTP: fillNTP,
|
||||
Parent: pa.source,
|
||||
Desc: desc,
|
||||
UseRTPPackets: useRTPPackets,
|
||||
WriteQueueSize: pa.writeQueueSize,
|
||||
RTPMaxPayloadSize: pa.rtpMaxPayloadSize,
|
||||
ReplaceNTP: replaceNTP,
|
||||
Parent: pa.source,
|
||||
}
|
||||
err := pa.stream.Initialize()
|
||||
if err != nil {
|
||||
|
|
@ -805,10 +807,7 @@ func (pa *path) executeRemoveReader(r defs.Reader) {
|
|||
}
|
||||
|
||||
func (pa *path) executeRemovePublisher() {
|
||||
if pa.stream != nil {
|
||||
pa.setNotReady()
|
||||
}
|
||||
|
||||
pa.setNotReady()
|
||||
pa.source = nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -842,7 +842,7 @@ func TestPathFallback(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPathResolveSource(t *testing.T) {
|
||||
var stream *gortsplib.ServerStream
|
||||
var strm *gortsplib.ServerStream
|
||||
|
||||
s := gortsplib.Server{
|
||||
Handler: &testServer{
|
||||
|
|
@ -852,12 +852,12 @@ func TestPathResolveSource(t *testing.T) {
|
|||
require.Equal(t, "/a", ctx.Path)
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}, stream, nil
|
||||
}, strm, nil
|
||||
},
|
||||
onSetup: func(_ *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}, stream, nil
|
||||
}, strm, nil
|
||||
},
|
||||
onPlay: func(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||
return &base.Response{
|
||||
|
|
@ -872,13 +872,13 @@ func TestPathResolveSource(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
|
||||
stream = &gortsplib.ServerStream{
|
||||
strm = &gortsplib.ServerStream{
|
||||
Server: &s,
|
||||
Desc: &description.Session{Medias: []*description.Media{test.MediaH264}},
|
||||
}
|
||||
err = stream.Initialize()
|
||||
err = strm.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer stream.Close()
|
||||
defer strm.Close()
|
||||
|
||||
p, ok := newInstance(
|
||||
"paths:\n" +
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"net/http"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
|
|
@ -62,16 +61,6 @@ func latestRemoteVersion() (*semver.Version, error) {
|
|||
return versions[0], nil
|
||||
}
|
||||
|
||||
func goArm() string {
|
||||
bi, _ := debug.ReadBuildInfo()
|
||||
for _, bs := range bi.Settings {
|
||||
if bs.Key == "GOARM" {
|
||||
return bs.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractExecutable(r io.Reader) ([]byte, error) {
|
||||
gzReader, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
|
|
@ -152,13 +141,6 @@ func upgrade() error {
|
|||
|
||||
fmt.Printf("downloading version %v...\n", "v"+latest.String())
|
||||
|
||||
var arch string
|
||||
if runtime.GOARCH == "arm" {
|
||||
arch = "armv" + goArm()
|
||||
} else {
|
||||
arch = runtime.GOARCH
|
||||
}
|
||||
|
||||
var extension string
|
||||
if runtime.GOOS == "windows" {
|
||||
extension = "zip"
|
||||
|
|
@ -166,7 +148,7 @@ func upgrade() error {
|
|||
extension = "tar.gz"
|
||||
}
|
||||
|
||||
ur := fmt.Sprintf(downloadURL, "v"+latest.String(), "v"+latest.String(), runtime.GOOS, arch, extension)
|
||||
ur := fmt.Sprintf(downloadURL, "v"+latest.String(), "v"+latest.String(), runtime.GOOS, getArch(), extension)
|
||||
|
||||
res, err := http.Get(ur)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ const (
|
|||
callbackPeriod = 1 * time.Second
|
||||
)
|
||||
|
||||
// CounterDumper is a counter that periodically invokes a callback if the counter is not zero.
|
||||
type CounterDumper struct {
|
||||
// Dumper is a counter that periodically invokes a callback if the counter is not zero.
|
||||
type Dumper struct {
|
||||
OnReport func(v uint64)
|
||||
|
||||
counter *uint64
|
||||
|
|
@ -21,7 +21,7 @@ type CounterDumper struct {
|
|||
}
|
||||
|
||||
// Start starts the counter.
|
||||
func (c *CounterDumper) Start() {
|
||||
func (c *Dumper) Start() {
|
||||
c.counter = new(uint64)
|
||||
c.terminate = make(chan struct{})
|
||||
c.done = make(chan struct{})
|
||||
|
|
@ -30,22 +30,22 @@ func (c *CounterDumper) Start() {
|
|||
}
|
||||
|
||||
// Stop stops the counter.
|
||||
func (c *CounterDumper) Stop() {
|
||||
func (c *Dumper) Stop() {
|
||||
close(c.terminate)
|
||||
<-c.done
|
||||
}
|
||||
|
||||
// Increase increases the counter value by 1.
|
||||
func (c *CounterDumper) Increase() {
|
||||
func (c *Dumper) Increase() {
|
||||
atomic.AddUint64(c.counter, 1)
|
||||
}
|
||||
|
||||
// Add adds value to the counter.
|
||||
func (c *CounterDumper) Add(v uint64) {
|
||||
func (c *Dumper) Add(v uint64) {
|
||||
atomic.AddUint64(c.counter, v)
|
||||
}
|
||||
|
||||
func (c *CounterDumper) run() {
|
||||
func (c *Dumper) run() {
|
||||
defer close(c.done)
|
||||
|
||||
t := time.NewTicker(callbackPeriod)
|
||||
|
|
@ -7,10 +7,10 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCounterDumperReport(t *testing.T) {
|
||||
func TestDumperReport(t *testing.T) {
|
||||
done := make(chan struct{})
|
||||
|
||||
c := &CounterDumper{
|
||||
c := &Dumper{
|
||||
OnReport: func(v uint64) {
|
||||
require.Equal(t, uint64(3), v)
|
||||
close(done)
|
||||
|
|
@ -29,8 +29,8 @@ func TestCounterDumperReport(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCounterDumperDoNotReport(t *testing.T) {
|
||||
c := &CounterDumper{
|
||||
func TestDumperDoNotReport(t *testing.T) {
|
||||
c := &Dumper{
|
||||
OnReport: func(_ uint64) {
|
||||
t.Errorf("should not happen")
|
||||
},
|
||||
|
|
@ -64,13 +64,13 @@ type PathAddPublisherRes struct {
|
|||
|
||||
// PathAddPublisherReq contains arguments of AddPublisher().
|
||||
type PathAddPublisherReq struct {
|
||||
Author Publisher
|
||||
Desc *description.Session
|
||||
GenerateRTPPackets bool
|
||||
FillNTP bool
|
||||
ConfToCompare *conf.Path
|
||||
AccessRequest PathAccessRequest
|
||||
Res chan PathAddPublisherRes
|
||||
Author Publisher
|
||||
Desc *description.Session
|
||||
UseRTPPackets bool
|
||||
ReplaceNTP bool
|
||||
ConfToCompare *conf.Path
|
||||
AccessRequest PathAccessRequest
|
||||
Res chan PathAddPublisherRes
|
||||
}
|
||||
|
||||
// PathRemovePublisherReq contains arguments of RemovePublisher().
|
||||
|
|
@ -107,10 +107,10 @@ type PathSourceStaticSetReadyRes struct {
|
|||
|
||||
// PathSourceStaticSetReadyReq contains arguments of SetReady().
|
||||
type PathSourceStaticSetReadyReq struct {
|
||||
Desc *description.Session
|
||||
GenerateRTPPackets bool
|
||||
FillNTP bool
|
||||
Res chan PathSourceStaticSetReadyRes
|
||||
Desc *description.Session
|
||||
UseRTPPackets bool
|
||||
ReplaceNTP bool
|
||||
Res chan PathSourceStaticSetReadyRes
|
||||
}
|
||||
|
||||
// PathSourceStaticSetNotReadyReq contains arguments of SetNotReady().
|
||||
|
|
|
|||
|
|
@ -42,22 +42,32 @@ func FormatsInfo(formats []format.Format) string {
|
|||
strings.Join(FormatsToCodecs(formats), ", "))
|
||||
}
|
||||
|
||||
// MediasToCodecs returns the name of codecs of given formats.
|
||||
func MediasToCodecs(medias []*description.Media) []string {
|
||||
var formats []format.Format
|
||||
func gatherFormats(medias []*description.Media) []format.Format {
|
||||
n := 0
|
||||
for _, media := range medias {
|
||||
formats = append(formats, media.Formats...)
|
||||
n += len(media.Formats)
|
||||
}
|
||||
|
||||
return FormatsToCodecs(formats)
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
formats := make([]format.Format, n)
|
||||
n = 0
|
||||
|
||||
for _, media := range medias {
|
||||
n += copy(formats[n:], media.Formats)
|
||||
}
|
||||
|
||||
return formats
|
||||
}
|
||||
|
||||
// MediasToCodecs returns the name of codecs of given formats.
|
||||
func MediasToCodecs(medias []*description.Media) []string {
|
||||
return FormatsToCodecs(gatherFormats(medias))
|
||||
}
|
||||
|
||||
// MediasInfo returns a description of medias.
|
||||
func MediasInfo(medias []*description.Media) string {
|
||||
var formats []format.Format
|
||||
for _, media := range medias {
|
||||
formats = append(formats, media.Formats...)
|
||||
}
|
||||
|
||||
return FormatsInfo(formats)
|
||||
return FormatsInfo(gatherFormats(medias))
|
||||
}
|
||||
|
|
|
|||
70
internal/errordumper/dumper.go
Normal file
70
internal/errordumper/dumper.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Package errordumper contains a counter that that periodically invokes a callback if the counter is not zero.
|
||||
package errordumper
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
callbackPeriod = 1 * time.Second
|
||||
)
|
||||
|
||||
// Dumper is a counter that periodically invokes a callback if errors were added.
|
||||
type Dumper struct {
|
||||
OnReport func(v uint64, last error)
|
||||
|
||||
mutex sync.Mutex
|
||||
counter uint64
|
||||
last error
|
||||
|
||||
terminate chan struct{}
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// Start starts the counter.
|
||||
func (c *Dumper) Start() {
|
||||
c.terminate = make(chan struct{})
|
||||
c.done = make(chan struct{})
|
||||
|
||||
go c.run()
|
||||
}
|
||||
|
||||
// Stop stops the counter.
|
||||
func (c *Dumper) Stop() {
|
||||
close(c.terminate)
|
||||
<-c.done
|
||||
}
|
||||
|
||||
// Add adds an error to the counter.
|
||||
func (c *Dumper) Add(err error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.counter++
|
||||
c.last = err
|
||||
}
|
||||
|
||||
func (c *Dumper) run() {
|
||||
defer close(c.done)
|
||||
|
||||
t := time.NewTicker(callbackPeriod)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.terminate:
|
||||
return
|
||||
|
||||
case <-t.C:
|
||||
c.mutex.Lock()
|
||||
var counter uint64
|
||||
counter, c.counter = c.counter, 0
|
||||
last := c.last
|
||||
c.mutex.Unlock()
|
||||
|
||||
if counter != 0 {
|
||||
c.OnReport(counter, last)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
internal/errordumper/dumper_test.go
Normal file
43
internal/errordumper/dumper_test.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package errordumper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDumperReport(t *testing.T) {
|
||||
done := make(chan struct{})
|
||||
|
||||
c := &Dumper{
|
||||
OnReport: func(v uint64, last error) {
|
||||
require.Equal(t, uint64(1), v)
|
||||
require.EqualError(t, last, "test error")
|
||||
close(done)
|
||||
},
|
||||
}
|
||||
c.Start()
|
||||
defer c.Stop()
|
||||
|
||||
c.Add(fmt.Errorf("test error"))
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Errorf("should not happen")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumperDoNotReport(t *testing.T) {
|
||||
c := &Dumper{
|
||||
OnReport: func(_ uint64, _ error) {
|
||||
t.Errorf("should not happen")
|
||||
},
|
||||
}
|
||||
c.Start()
|
||||
defer c.Stop()
|
||||
|
||||
<-time.After(2 * time.Second)
|
||||
}
|
||||
|
|
@ -2,31 +2,50 @@ package logger
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type destinationFile struct {
|
||||
file *os.File
|
||||
buf bytes.Buffer
|
||||
structured bool
|
||||
file *os.File
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func newDestinationFile(filePath string) (destination, error) {
|
||||
func newDestinationFile(structured bool, filePath string) (destination, error) {
|
||||
f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &destinationFile{
|
||||
file: f,
|
||||
structured: structured,
|
||||
file: f,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *destinationFile) log(t time.Time, level Level, format string, args ...any) {
|
||||
d.buf.Reset()
|
||||
writeTime(&d.buf, t, false)
|
||||
writeLevel(&d.buf, level, false)
|
||||
writeContent(&d.buf, format, args)
|
||||
|
||||
if d.structured {
|
||||
d.buf.WriteString(`{"timestamp":"`)
|
||||
d.buf.WriteString(t.Format(time.RFC3339))
|
||||
d.buf.WriteString(`","level":"`)
|
||||
writeLevel(&d.buf, level, false)
|
||||
d.buf.WriteString(`","message":`)
|
||||
d.buf.WriteString(strconv.Quote(fmt.Sprintf(format, args...)))
|
||||
d.buf.WriteString(`}`)
|
||||
d.buf.WriteByte('\n')
|
||||
} else {
|
||||
writePlainTime(&d.buf, t, false)
|
||||
writeLevel(&d.buf, level, false)
|
||||
d.buf.WriteByte(' ')
|
||||
fmt.Fprintf(&d.buf, format, args...)
|
||||
d.buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
d.file.Write(d.buf.Bytes()) //nolint:errcheck
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,30 +2,51 @@ package logger
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type destinationStdout struct {
|
||||
useColor bool
|
||||
|
||||
buf bytes.Buffer
|
||||
structured bool
|
||||
stdout io.Writer
|
||||
useColor bool
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func newDestionationStdout() destination {
|
||||
func newDestionationStdout(structured bool, stdout io.Writer) destination {
|
||||
return &destinationStdout{
|
||||
useColor: term.IsTerminal(int(os.Stdout.Fd())),
|
||||
structured: structured,
|
||||
stdout: stdout,
|
||||
useColor: term.IsTerminal(int(os.Stdout.Fd())),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *destinationStdout) log(t time.Time, level Level, format string, args ...any) {
|
||||
d.buf.Reset()
|
||||
writeTime(&d.buf, t, d.useColor)
|
||||
writeLevel(&d.buf, level, d.useColor)
|
||||
writeContent(&d.buf, format, args)
|
||||
os.Stdout.Write(d.buf.Bytes()) //nolint:errcheck
|
||||
|
||||
if d.structured {
|
||||
d.buf.WriteString(`{"timestamp":"`)
|
||||
d.buf.WriteString(t.Format(time.RFC3339))
|
||||
d.buf.WriteString(`","level":"`)
|
||||
writeLevel(&d.buf, level, false)
|
||||
d.buf.WriteString(`","message":`)
|
||||
d.buf.WriteString(strconv.Quote(fmt.Sprintf(format, args...)))
|
||||
d.buf.WriteString(`}`)
|
||||
d.buf.WriteByte('\n')
|
||||
} else {
|
||||
writePlainTime(&d.buf, t, d.useColor)
|
||||
writeLevel(&d.buf, level, d.useColor)
|
||||
d.buf.WriteByte(' ')
|
||||
fmt.Fprintf(&d.buf, format, args...)
|
||||
d.buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
d.stdout.Write(d.buf.Bytes()) //nolint:errcheck
|
||||
}
|
||||
|
||||
func (d *destinationStdout) close() {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
//go:build !darwin && !windows
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
"time"
|
||||
)
|
||||
|
||||
type destinationSysLog struct {
|
||||
syslog io.WriteCloser
|
||||
syslog *syslog.Writer
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func newDestinationSyslog(prefix string) (destination, error) {
|
||||
syslog, err := newSysLog(prefix)
|
||||
syslog, err := syslog.New(syslog.LOG_DAEMON, prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -22,14 +25,23 @@ func newDestinationSyslog(prefix string) (destination, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (d *destinationSysLog) log(t time.Time, level Level, format string, args ...any) {
|
||||
func (d *destinationSysLog) log(_ time.Time, level Level, format string, args ...any) {
|
||||
d.buf.Reset()
|
||||
writeTime(&d.buf, t, false)
|
||||
writeLevel(&d.buf, level, false)
|
||||
writeContent(&d.buf, format, args)
|
||||
d.syslog.Write(d.buf.Bytes()) //nolint:errcheck
|
||||
|
||||
fmt.Fprintf(&d.buf, format, args...)
|
||||
|
||||
switch level {
|
||||
case Debug:
|
||||
d.syslog.Debug(d.buf.String()) //nolint:errcheck
|
||||
case Info:
|
||||
d.syslog.Info(d.buf.String()) //nolint:errcheck
|
||||
case Warn:
|
||||
d.syslog.Warning(d.buf.String()) //nolint:errcheck
|
||||
case Error:
|
||||
d.syslog.Err(d.buf.String()) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
func (d *destinationSysLog) close() {
|
||||
d.syslog.Close()
|
||||
d.syslog.Close() //nolint:errcheck
|
||||
}
|
||||
|
|
|
|||
9
internal/logger/destination_syslog_disabled.go
Normal file
9
internal/logger/destination_syslog_disabled.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build darwin || windows
|
||||
|
||||
package logger
|
||||
|
||||
import "fmt"
|
||||
|
||||
func newDestinationSyslog(_ string) (destination, error) {
|
||||
return nil, fmt.Errorf("syslog is not available on macOS and Windows")
|
||||
}
|
||||
|
|
@ -3,7 +3,8 @@ package logger
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -12,47 +13,56 @@ import (
|
|||
|
||||
// Logger is a log handler.
|
||||
type Logger struct {
|
||||
level Level
|
||||
Level Level
|
||||
Destinations []Destination
|
||||
Structured bool
|
||||
File string
|
||||
SysLogPrefix string
|
||||
|
||||
timeNow func() time.Time
|
||||
stdout io.Writer
|
||||
destinations []destination
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// New allocates a log handler.
|
||||
func New(level Level, destinations []Destination, filePath string, sysLogPrefix string) (*Logger, error) {
|
||||
lh := &Logger{
|
||||
level: level,
|
||||
// Initialize initializes Logger.
|
||||
func (l *Logger) Initialize() error {
|
||||
if l.timeNow == nil {
|
||||
l.timeNow = time.Now
|
||||
}
|
||||
if l.stdout == nil {
|
||||
l.stdout = os.Stdout
|
||||
}
|
||||
|
||||
for _, destType := range destinations {
|
||||
for _, destType := range l.Destinations {
|
||||
switch destType {
|
||||
case DestinationStdout:
|
||||
lh.destinations = append(lh.destinations, newDestionationStdout())
|
||||
l.destinations = append(l.destinations, newDestionationStdout(l.Structured, l.stdout))
|
||||
|
||||
case DestinationFile:
|
||||
dest, err := newDestinationFile(filePath)
|
||||
dest, err := newDestinationFile(l.Structured, l.File)
|
||||
if err != nil {
|
||||
lh.Close()
|
||||
return nil, err
|
||||
l.Close()
|
||||
return err
|
||||
}
|
||||
lh.destinations = append(lh.destinations, dest)
|
||||
l.destinations = append(l.destinations, dest)
|
||||
|
||||
case DestinationSyslog:
|
||||
dest, err := newDestinationSyslog(sysLogPrefix)
|
||||
dest, err := newDestinationSyslog(l.SysLogPrefix)
|
||||
if err != nil {
|
||||
lh.Close()
|
||||
return nil, err
|
||||
l.Close()
|
||||
return err
|
||||
}
|
||||
lh.destinations = append(lh.destinations, dest)
|
||||
l.destinations = append(l.destinations, dest)
|
||||
}
|
||||
}
|
||||
|
||||
return lh, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes a log handler.
|
||||
func (lh *Logger) Close() {
|
||||
for _, dest := range lh.destinations {
|
||||
func (l *Logger) Close() {
|
||||
for _, dest := range l.destinations {
|
||||
dest.close()
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +84,7 @@ func itoa(i int, wid int) []byte {
|
|||
return b[bp:]
|
||||
}
|
||||
|
||||
func writeTime(buf *bytes.Buffer, t time.Time, useColor bool) {
|
||||
func writePlainTime(buf *bytes.Buffer, t time.Time, useColor bool) {
|
||||
var intbuf bytes.Buffer
|
||||
|
||||
// date
|
||||
|
|
@ -132,26 +142,20 @@ func writeLevel(buf *bytes.Buffer, level Level, useColor bool) {
|
|||
buf.WriteString("ERR")
|
||||
}
|
||||
}
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
|
||||
func writeContent(buf *bytes.Buffer, format string, args []any) {
|
||||
fmt.Fprintf(buf, format, args...)
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
// Log writes a log entry.
|
||||
func (lh *Logger) Log(level Level, format string, args ...any) {
|
||||
if level < lh.level {
|
||||
func (l *Logger) Log(level Level, format string, args ...any) {
|
||||
if level < l.Level {
|
||||
return
|
||||
}
|
||||
|
||||
lh.mutex.Lock()
|
||||
defer lh.mutex.Unlock()
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
t := time.Now()
|
||||
t := l.timeNow()
|
||||
|
||||
for _, dest := range lh.destinations {
|
||||
for _, dest := range l.destinations {
|
||||
dest.log(t, level, format, args...)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
77
internal/logger/logger_test.go
Normal file
77
internal/logger/logger_test.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoggerToStdout(t *testing.T) {
|
||||
for _, ca := range []string{
|
||||
"plain",
|
||||
"structured",
|
||||
} {
|
||||
t.Run(ca, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
l := &Logger{
|
||||
Destinations: []Destination{DestinationStdout},
|
||||
Structured: (ca == "structured"),
|
||||
timeNow: func() time.Time { return time.Date(2003, 11, 4, 23, 15, 8, 0, time.UTC) },
|
||||
stdout: &buf,
|
||||
}
|
||||
err := l.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer l.Close()
|
||||
|
||||
l.Log(Info, "test format %d", 123)
|
||||
|
||||
if ca == "plain" {
|
||||
require.Equal(t, "2003/11/04 23:15:08 INF test format 123\n", buf.String())
|
||||
} else {
|
||||
require.Equal(t, `{"timestamp":"2003-11-04T23:15:08Z",`+
|
||||
`"level":"INF","message":"test format 123"}`+"\n", buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerToFile(t *testing.T) {
|
||||
for _, ca := range []string{
|
||||
"plain",
|
||||
"structured",
|
||||
} {
|
||||
t.Run(ca, func(t *testing.T) {
|
||||
tempFile, err := os.CreateTemp(os.TempDir(), "mtx-logger-")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer tempFile.Close()
|
||||
|
||||
l := &Logger{
|
||||
Level: Debug,
|
||||
Destinations: []Destination{DestinationFile},
|
||||
Structured: ca == "structured",
|
||||
File: tempFile.Name(),
|
||||
timeNow: func() time.Time { return time.Date(2003, 11, 4, 23, 15, 8, 0, time.UTC) },
|
||||
}
|
||||
err = l.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer l.Close()
|
||||
|
||||
l.Log(Info, "test format %d", 123)
|
||||
|
||||
buf, err := os.ReadFile(tempFile.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
if ca == "plain" {
|
||||
require.Equal(t, "2003/11/04 23:15:08 INF test format 123\n", string(buf))
|
||||
} else {
|
||||
require.Equal(t, `{"timestamp":"2003-11-04T23:15:08Z",`+
|
||||
`"level":"INF","message":"test format 123"}`+"\n", string(buf))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue