When the publisher or source of a stream is offline, the server can be configured to fill gaps in the stream with a video that is played on repeat until a publisher comes back online. This allows readers to stay connected regardless of the state of the stream. The offline video and any future online stream are concatenated without decoding or re-encoding packets, using the original codec.
393 lines
10 KiB
Go
393 lines
10 KiB
Go
package stream
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpac3"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpav1"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph264"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph265"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpklv"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmjpeg"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1audio"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1video"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpsimpleaudio"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp8"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp9"
|
|
mcopus "github.com/bluenviron/mediacommon/v2/pkg/codecs/opus"
|
|
"github.com/bluenviron/mediamtx/internal/unit"
|
|
"github.com/pion/rtp"
|
|
)
|
|
|
|
func ptrOf[T any](v T) *T {
|
|
return &v
|
|
}
|
|
|
|
type rtpEncoderNotAvailableError struct {
|
|
format format.Format
|
|
}
|
|
|
|
func (e rtpEncoderNotAvailableError) Error() string {
|
|
return fmt.Sprintf("RTP encoder not available for format %T", e.format)
|
|
}
|
|
|
|
type rtpEncoder interface {
|
|
encode(unit.Payload) ([]*rtp.Packet, error)
|
|
}
|
|
|
|
type rtpEncoderH265 rtph265.Encoder
|
|
|
|
func (e *rtpEncoderH265) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtph265.Encoder)(e).Encode(payload.(unit.PayloadH265))
|
|
}
|
|
|
|
type rtpEncoderH264 rtph264.Encoder
|
|
|
|
func (e *rtpEncoderH264) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtph264.Encoder)(e).Encode(payload.(unit.PayloadH264))
|
|
}
|
|
|
|
type rtpEncoderAV1 rtpav1.Encoder
|
|
|
|
func (e *rtpEncoderAV1) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpav1.Encoder)(e).Encode(payload.(unit.PayloadAV1))
|
|
}
|
|
|
|
type rtpEncoderVP9 rtpvp9.Encoder
|
|
|
|
func (e *rtpEncoderVP9) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpvp9.Encoder)(e).Encode(payload.(unit.PayloadVP9))
|
|
}
|
|
|
|
type rtpEncoderVP8 rtpvp8.Encoder
|
|
|
|
func (e *rtpEncoderVP8) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpvp8.Encoder)(e).Encode(payload.(unit.PayloadVP8))
|
|
}
|
|
|
|
type rtpEncoderMPEG4Video rtpfragmented.Encoder
|
|
|
|
func (e *rtpEncoderMPEG4Video) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpfragmented.Encoder)(e).Encode(payload.(unit.PayloadMPEG4Video))
|
|
}
|
|
|
|
type rtpEncoderMPEG1Video rtpmpeg1video.Encoder
|
|
|
|
func (e *rtpEncoderMPEG1Video) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpmpeg1video.Encoder)(e).Encode(payload.(unit.PayloadMPEG1Video))
|
|
}
|
|
|
|
type rtpEncoderMJPEG rtpmjpeg.Encoder
|
|
|
|
func (e *rtpEncoderMJPEG) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpmjpeg.Encoder)(e).Encode(payload.(unit.PayloadMJPEG))
|
|
}
|
|
|
|
type rtpEncoderOpus rtpsimpleaudio.Encoder
|
|
|
|
func (e *rtpEncoderOpus) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
pts := int64(0)
|
|
packets := make([]*rtp.Packet, len(payload.(unit.PayloadOpus)))
|
|
|
|
for i, packet := range payload.(unit.PayloadOpus) {
|
|
pkt, err := (*rtpsimpleaudio.Encoder)(e).Encode(packet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pkt.Timestamp += uint32(pts)
|
|
pts += mcopus.PacketDuration2(packet)
|
|
packets[i] = pkt
|
|
}
|
|
|
|
return packets, nil
|
|
}
|
|
|
|
type rtpEncoderMPEG4Audio rtpmpeg4audio.Encoder
|
|
|
|
func (e *rtpEncoderMPEG4Audio) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpmpeg4audio.Encoder)(e).Encode(payload.(unit.PayloadMPEG4Audio))
|
|
}
|
|
|
|
type rtpEncoderMPEG4AudioLATM rtpfragmented.Encoder
|
|
|
|
func (e *rtpEncoderMPEG4AudioLATM) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpfragmented.Encoder)(e).Encode(payload.(unit.PayloadMPEG4AudioLATM))
|
|
}
|
|
|
|
type rtpEncoderMPEG1Audio rtpmpeg1audio.Encoder
|
|
|
|
func (e *rtpEncoderMPEG1Audio) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpmpeg1audio.Encoder)(e).Encode(payload.(unit.PayloadMPEG1Audio))
|
|
}
|
|
|
|
type rtpEncoderAC3 rtpac3.Encoder
|
|
|
|
func (e *rtpEncoderAC3) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpac3.Encoder)(e).Encode(payload.(unit.PayloadAC3))
|
|
}
|
|
|
|
type rtpEncoderG711 rtplpcm.Encoder
|
|
|
|
func (e *rtpEncoderG711) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtplpcm.Encoder)(e).Encode(payload.(unit.PayloadG711))
|
|
}
|
|
|
|
type rtpEncoderLPCM rtplpcm.Encoder
|
|
|
|
func (e *rtpEncoderLPCM) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtplpcm.Encoder)(e).Encode(payload.(unit.PayloadLPCM))
|
|
}
|
|
|
|
type rtpEncoderKLV rtpklv.Encoder
|
|
|
|
func (e *rtpEncoderKLV) encode(payload unit.Payload) ([]*rtp.Packet, error) {
|
|
return (*rtpklv.Encoder)(e).Encode(payload.(unit.PayloadKLV))
|
|
}
|
|
|
|
func newRTPEncoder(
|
|
forma format.Format,
|
|
rtpMaxPayloadSize int,
|
|
ssrc *uint32,
|
|
initialSequenceNumber *uint16,
|
|
) (rtpEncoder, error) {
|
|
switch forma := forma.(type) {
|
|
case *format.H265:
|
|
wrapped := &rtph265.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
MaxDONDiff: forma.MaxDONDiff,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderH265)(wrapped), nil
|
|
|
|
case *format.H264:
|
|
wrapped := &rtph264.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
PacketizationMode: forma.PacketizationMode,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderH264)(wrapped), nil
|
|
|
|
case *format.AV1:
|
|
wrapped := &rtpav1.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderAV1)(wrapped), nil
|
|
|
|
case *format.VP9:
|
|
wrapped := &rtpvp9.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
InitialPictureID: ptrOf(uint16(0x35af)),
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderVP9)(wrapped), nil
|
|
|
|
case *format.VP8:
|
|
wrapped := &rtpvp8.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderVP8)(wrapped), nil
|
|
|
|
case *format.MPEG4Video:
|
|
wrapped := &rtpfragmented.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderMPEG4Video)(wrapped), nil
|
|
|
|
case *format.MPEG1Video:
|
|
wrapped := &rtpmpeg1video.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderMPEG1Video)(wrapped), nil
|
|
|
|
case *format.MJPEG:
|
|
wrapped := &rtpmjpeg.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderMJPEG)(wrapped), nil
|
|
|
|
case *format.Opus:
|
|
wrapped := &rtpsimpleaudio.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderOpus)(wrapped), nil
|
|
|
|
case *format.MPEG4Audio:
|
|
wrapped := &rtpmpeg4audio.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
SizeLength: forma.SizeLength,
|
|
IndexLength: forma.IndexLength,
|
|
IndexDeltaLength: forma.IndexDeltaLength,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderMPEG4Audio)(wrapped), nil
|
|
|
|
case *format.MPEG4AudioLATM:
|
|
wrapped := &rtpfragmented.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderMPEG4AudioLATM)(wrapped), nil
|
|
|
|
case *format.MPEG1Audio:
|
|
wrapped := &rtpmpeg1audio.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderMPEG1Audio)(wrapped), nil
|
|
|
|
case *format.AC3:
|
|
wrapped := &rtpac3.Encoder{
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderAC3)(wrapped), nil
|
|
|
|
case *format.G711:
|
|
wrapped := &rtplpcm.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadType(),
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
BitDepth: 8,
|
|
ChannelCount: forma.ChannelCount,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderG711)(wrapped), nil
|
|
|
|
case *format.LPCM:
|
|
wrapped := &rtplpcm.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
BitDepth: forma.BitDepth,
|
|
ChannelCount: forma.ChannelCount,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderLPCM)(wrapped), nil
|
|
|
|
case *format.KLV:
|
|
wrapped := &rtpklv.Encoder{
|
|
PayloadMaxSize: rtpMaxPayloadSize,
|
|
PayloadType: forma.PayloadTyp,
|
|
SSRC: ssrc,
|
|
InitialSequenceNumber: initialSequenceNumber,
|
|
}
|
|
err := wrapped.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*rtpEncoderKLV)(wrapped), nil
|
|
|
|
default:
|
|
return nil, rtpEncoderNotAvailableError{forma}
|
|
}
|
|
}
|