1
0
Fork 0
forked from External/mediamtx

webrtc: support publishing H265 tracks (#3435) (#3492)

IMPORTANT NOTE: this doesn't allow to read H265 tracks with WebRTC,
just to publish them. The inability to read H265 tracks with WebRTC is
not in any way related to the server but depends on browsers and on the
fact that they are not legally entitled to embed a H265 decoder inside
them.
This commit is contained in:
Alessandro Ros 2024-06-19 21:02:08 +02:00 committed by GitHub
parent 65d90f7cc6
commit a1dc9f45f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 58 additions and 28 deletions

View file

@ -22,8 +22,8 @@ Live streams can be published to the server with:
|--------|--------|------------|------------| |--------|--------|------------|------------|
|[SRT clients](#srt-clients)||H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3| |[SRT clients](#srt-clients)||H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3|
|[SRT cameras and servers](#srt-cameras-and-servers)||H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3| |[SRT cameras and servers](#srt-cameras-and-servers)||H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3|
|[WebRTC clients](#webrtc-clients)|Browser-based, WHIP|AV1, VP9, VP8, H264|Opus, G722, G711 (PCMA, PCMU)| |[WebRTC clients](#webrtc-clients)|Browser-based, WHIP|AV1, VP9, VP8, H265, H264|Opus, G722, G711 (PCMA, PCMU)|
|[WebRTC servers](#webrtc-servers)|WHEP|AV1, VP9, VP8, H264|Opus, G722, G711 (PCMA, PCMU)| |[WebRTC servers](#webrtc-servers)|WHEP|AV1, VP9, VP8, H265, H264|Opus, G722, G711 (PCMA, PCMU)|
|[RTSP clients](#rtsp-clients)|UDP, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM and any RTP-compatible codec| |[RTSP clients](#rtsp-clients)|UDP, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM and any RTP-compatible codec|
|[RTSP cameras and servers](#rtsp-cameras-and-servers)|UDP, UDP-Multicast, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM and any RTP-compatible codec| |[RTSP cameras and servers](#rtsp-cameras-and-servers)|UDP, UDP-Multicast, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM and any RTP-compatible codec|
|[RTMP clients](#rtmp-clients)|RTMP, RTMPS, Enhanced RTMP|AV1, VP9, H265, H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G711 (PCMA, PCMU), LPCM| |[RTMP clients](#rtmp-clients)|RTMP, RTMPS, Enhanced RTMP|AV1, VP9, H265, H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G711 (PCMA, PCMU), LPCM|

View file

@ -5,6 +5,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/liberrors" "github.com/bluenviron/gortsplib/v4/pkg/liberrors"
"github.com/bluenviron/gortsplib/v4/pkg/rtpreorderer" "github.com/bluenviron/gortsplib/v4/pkg/rtpreorderer"
@ -81,9 +82,8 @@ var incomingVideoCodecs = []webrtc.RTPCodecParameters{
}, },
{ {
RTPCodecCapability: webrtc.RTPCodecCapability{ RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH264, MimeType: webrtc.MimeTypeH265,
ClockRate: 90000, ClockRate: 90000,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
}, },
PayloadType: 103, PayloadType: 103,
}, },
@ -91,10 +91,18 @@ var incomingVideoCodecs = []webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{ RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH264, MimeType: webrtc.MimeTypeH264,
ClockRate: 90000, ClockRate: 90000,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
}, },
PayloadType: 104, PayloadType: 104,
}, },
{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH264,
ClockRate: 90000,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
},
PayloadType: 105,
},
} }
var incomingAudioCodecs = []webrtc.RTPCodecParameters{ var incomingAudioCodecs = []webrtc.RTPCodecParameters{
@ -229,6 +237,7 @@ type IncomingTrack struct {
track *webrtc.TrackRemote track *webrtc.TrackRemote
log logger.Writer log logger.Writer
typ description.MediaType
format format.Format format format.Format
reorderer *rtpreorderer.Reorderer reorderer *rtpreorderer.Reorderer
pkts []*rtp.Packet pkts []*rtp.Packet
@ -246,41 +255,47 @@ func newIncomingTrack(
reorderer: rtpreorderer.New(), reorderer: rtpreorderer.New(),
} }
isVideo := false
switch strings.ToLower(track.Codec().MimeType) { switch strings.ToLower(track.Codec().MimeType) {
case strings.ToLower(webrtc.MimeTypeAV1): case strings.ToLower(webrtc.MimeTypeAV1):
isVideo = true t.typ = description.MediaTypeVideo
t.format = &format.AV1{ t.format = &format.AV1{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
} }
case strings.ToLower(webrtc.MimeTypeVP9): case strings.ToLower(webrtc.MimeTypeVP9):
isVideo = true t.typ = description.MediaTypeVideo
t.format = &format.VP9{ t.format = &format.VP9{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
} }
case strings.ToLower(webrtc.MimeTypeVP8): case strings.ToLower(webrtc.MimeTypeVP8):
isVideo = true t.typ = description.MediaTypeVideo
t.format = &format.VP8{ t.format = &format.VP8{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
} }
case strings.ToLower(webrtc.MimeTypeH265):
t.typ = description.MediaTypeVideo
t.format = &format.H265{
PayloadTyp: uint8(track.PayloadType()),
}
case strings.ToLower(webrtc.MimeTypeH264): case strings.ToLower(webrtc.MimeTypeH264):
isVideo = true t.typ = description.MediaTypeVideo
t.format = &format.H264{ t.format = &format.H264{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
PacketizationMode: 1, PacketizationMode: 1,
} }
case strings.ToLower(mimeTypeMultiopus): case strings.ToLower(mimeTypeMultiopus):
t.typ = description.MediaTypeAudio
t.format = &format.Opus{ t.format = &format.Opus{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
ChannelCount: int(track.Codec().Channels), ChannelCount: int(track.Codec().Channels),
} }
case strings.ToLower(webrtc.MimeTypeOpus): case strings.ToLower(webrtc.MimeTypeOpus):
t.typ = description.MediaTypeAudio
t.format = &format.Opus{ t.format = &format.Opus{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
ChannelCount: func() int { ChannelCount: func() int {
@ -292,9 +307,12 @@ func newIncomingTrack(
} }
case strings.ToLower(webrtc.MimeTypeG722): case strings.ToLower(webrtc.MimeTypeG722):
t.typ = description.MediaTypeAudio
t.format = &format.G722{} t.format = &format.G722{}
case strings.ToLower(webrtc.MimeTypePCMU): case strings.ToLower(webrtc.MimeTypePCMU):
t.typ = description.MediaTypeAudio
channels := track.Codec().Channels channels := track.Codec().Channels
if channels == 0 { if channels == 0 {
channels = 1 channels = 1
@ -313,6 +331,8 @@ func newIncomingTrack(
} }
case strings.ToLower(webrtc.MimeTypePCMA): case strings.ToLower(webrtc.MimeTypePCMA):
t.typ = description.MediaTypeAudio
channels := track.Codec().Channels channels := track.Codec().Channels
if channels == 0 { if channels == 0 {
channels = 1 channels = 1
@ -331,6 +351,7 @@ func newIncomingTrack(
} }
case strings.ToLower(mimeTypeL16): case strings.ToLower(mimeTypeL16):
t.typ = description.MediaTypeAudio
t.format = &format.LPCM{ t.format = &format.LPCM{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
BitDepth: 16, BitDepth: 16,
@ -339,7 +360,7 @@ func newIncomingTrack(
} }
default: default:
return nil, fmt.Errorf("unsupported codec: %+v", track.Codec()) return nil, fmt.Errorf("unsupported codec: %+v", track.Codec().RTPCodecCapability)
} }
// read incoming RTCP packets to make interceptors work // read incoming RTCP packets to make interceptors work
@ -354,7 +375,7 @@ func newIncomingTrack(
}() }()
// send period key frame requests // send period key frame requests
if isVideo { if t.typ == description.MediaTypeVideo {
go func() { go func() {
keyframeTicker := time.NewTicker(keyFrameInterval) keyframeTicker := time.NewTicker(keyFrameInterval)
defer keyframeTicker.Stop() defer keyframeTicker.Stop()

View file

@ -54,6 +54,15 @@ func (t *OutgoingTrack) codecParameters() (webrtc.RTPCodecParameters, error) {
PayloadType: 96, PayloadType: 96,
}, nil }, nil
case *format.H265:
return webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH265,
ClockRate: 90000,
},
PayloadType: 96,
}, nil
case *format.H264: case *format.H264:
return webrtc.RTPCodecParameters{ return webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{ RTPCodecCapability: webrtc.RTPCodecCapability{
@ -205,6 +214,7 @@ func (t *OutgoingTrack) isVideo() bool {
case *format.AV1, case *format.AV1,
*format.VP9, *format.VP9,
*format.VP8, *format.VP8,
*format.H265,
*format.H264: *format.H264:
return true return true
} }

View file

@ -83,6 +83,17 @@ func TestPeerConnectionPublishRead(t *testing.T) {
PayloadTyp: 96, PayloadTyp: 96,
}, },
}, },
{
"h265",
test.FormatH265,
webrtc.RTPCodecCapability{
MimeType: "video/H265",
ClockRate: 90000,
},
&format.H265{
PayloadTyp: 96,
},
},
{ {
"h264", "h264",
test.FormatH264, test.FormatH264,

View file

@ -10,21 +10,9 @@ func TracksToMedias(tracks []*IncomingTrack) []*description.Media {
ret := make([]*description.Media, len(tracks)) ret := make([]*description.Media, len(tracks))
for i, track := range tracks { for i, track := range tracks {
forma := track.Format()
var mediaType description.MediaType
switch forma.(type) {
case *format.AV1, *format.VP9, *format.VP8, *format.H264:
mediaType = description.MediaTypeVideo
default:
mediaType = description.MediaTypeAudio
}
ret[i] = &description.Media{ ret[i] = &description.Media{
Type: mediaType, Type: track.typ,
Formats: []format.Format{forma}, Formats: []format.Format{track.format},
} }
} }