Merge branch 'main' into feature/sourceudpclientportrange

This commit is contained in:
Bjørn Steen Saethre 2026-01-21 14:17:31 +01:00
commit 7ca890315f
166 changed files with 4953 additions and 5290 deletions

View file

@ -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

View file

@ -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

View file

@ -14,7 +14,7 @@ jobs:
- run: make binaries
- uses: actions/upload-artifact@v5
- uses: actions/upload-artifact@v6
with:
name: binaries
path: binaries

View file

@ -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

View file

@ -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)

View file

@ -7,8 +7,8 @@
<br>
[![Website](https://img.shields.io/badge/website-mediamtx.org-1c94b5)](https://mediamtx.org)
[![Test](https://github.com/bluenviron/mediamtx/actions/workflows/code_test.yml/badge.svg)](https://github.com/bluenviron/mediamtx/actions/workflows/code_test.yml)
[![Lint](https://github.com/bluenviron/mediamtx/actions/workflows/code_lint.yml/badge.svg)](https://github.com/bluenviron/mediamtx/actions/workflows/code_lint.yml)
[![Test](https://github.com/bluenviron/mediamtx/actions/workflows/code_test.yml/badge.svg?branch=main)](https://github.com/bluenviron/mediamtx/actions/workflows/code_test.yml?query=branch%3Amain)
[![Lint](https://github.com/bluenviron/mediamtx/actions/workflows/code_lint.yml/badge.svg?branch=main)](https://github.com/bluenviron/mediamtx/actions/workflows/code_lint.yml?query=branch%3Amain)
[![CodeCov](https://codecov.io/gh/bluenviron/mediamtx/branch/main/graph/badge.svg)](https://app.codecov.io/gh/bluenviron/mediamtx/tree/main)
[![Release](https://img.shields.io/github/v/release/bluenviron/mediamtx)](https://github.com/bluenviron/mediamtx/releases)
[![Docker Hub](https://img.shields.io/badge/docker-bluenviron/mediamtx-blue)](https://hub.docker.com/r/bluenviron/mediamtx)

View file

@ -73,6 +73,8 @@ components:
type: array
items:
type: string
logStructured:
type: boolean
logFile:
type: string
sysLogPrefix:

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 \

View file

@ -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
```

View file

@ -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).

View file

@ -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
```

View file

@ -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`.

View file

@ -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.

View 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
...
```

View file

@ -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
...
```

View file

@ -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.

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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.

View file

@ -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.

View file

@ -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).

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
})
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
})
}

View file

@ -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
}

View file

@ -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)
})
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
})
}
}

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("800")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("8\x00\x00")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("80")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("a00")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("a0\x00\x00")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("0")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("a000")

View file

@ -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
}

View file

@ -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
}

View file

@ -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)

View file

@ -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)

View file

@ -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 ||

View file

@ -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)

View file

@ -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
}

View file

@ -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" +

View file

@ -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 {

View file

@ -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)

View file

@ -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")
},

View file

@ -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().

View file

@ -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))
}

View 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)
}
}
}
}

View 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)
}

View file

@ -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
}

View file

@ -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() {

View file

@ -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
}

View 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")
}

View file

@ -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...)
}
}

View 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