solve codec labels internally (#5603)
stop using gortsplib format.Format.Codec() for getting codec names.
This commit is contained in:
+3
-3
@@ -65,13 +65,13 @@ components:
|
||||
- H265
|
||||
- H264
|
||||
- MPEG-4 Video
|
||||
- MPEG-1 Video
|
||||
- MJPEG
|
||||
- MPEG-1/2 Video
|
||||
- M-JPEG
|
||||
- Opus
|
||||
- Vorbis
|
||||
- MPEG-4 Audio
|
||||
- MPEG-4 Audio LATM
|
||||
- MPEG-1 Audio
|
||||
- MPEG-1/2 Audio
|
||||
- AC3
|
||||
- Speex
|
||||
- G726
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/conf"
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -41,7 +42,7 @@ func TestPathsList(t *testing.T) {
|
||||
Source: &defs.APIPathSource{Type: defs.APIPathSourceTypeRTSPSession, ID: "pub1"},
|
||||
Ready: true,
|
||||
ReadyTime: &now,
|
||||
Tracks: []defs.APIPathTrackCodec{defs.APIPathTrackCodecH264, defs.APIPathTrackCodecOpus},
|
||||
Tracks: []defs.APIPathTrackCodec{formatlabel.H264, formatlabel.Opus},
|
||||
InboundBytes: 1000,
|
||||
OutboundBytes: 2000,
|
||||
InboundFramesInError: 3,
|
||||
@@ -100,7 +101,7 @@ func TestPathsGet(t *testing.T) {
|
||||
Source: &defs.APIPathSource{Type: defs.APIPathSourceTypeRTSPSession, ID: "session123"},
|
||||
Ready: true,
|
||||
ReadyTime: &now,
|
||||
Tracks: []defs.APIPathTrackCodec{defs.APIPathTrackCodecH264, defs.APIPathTrackCodecOpus},
|
||||
Tracks: []defs.APIPathTrackCodec{formatlabel.H264, formatlabel.Opus},
|
||||
InboundBytes: 123456,
|
||||
OutboundBytes: 789012,
|
||||
InboundFramesInError: 12,
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/protocols/webrtc"
|
||||
"github.com/bluenviron/mediamtx/internal/protocols/whip"
|
||||
"github.com/bluenviron/mediamtx/internal/test"
|
||||
@@ -140,7 +141,7 @@ func TestAPIPathsList(t *testing.T) {
|
||||
Type: "rtspSession",
|
||||
},
|
||||
Ready: true,
|
||||
Tracks: []defs.APIPathTrackCodec{defs.APIPathTrackCodecH264, defs.APIPathTrackCodecMPEG4Audio},
|
||||
Tracks: []defs.APIPathTrackCodec{formatlabel.H264, formatlabel.MPEG4Audio},
|
||||
InboundBytes: 17,
|
||||
InboundFramesInError: 0,
|
||||
BytesReceived: 17,
|
||||
@@ -190,7 +191,7 @@ func TestAPIPathsList(t *testing.T) {
|
||||
Type: "rtspsSession",
|
||||
},
|
||||
Ready: true,
|
||||
Tracks: []defs.APIPathTrackCodec{defs.APIPathTrackCodecH264, defs.APIPathTrackCodecMPEG4Audio},
|
||||
Tracks: []defs.APIPathTrackCodec{formatlabel.H264, formatlabel.MPEG4Audio},
|
||||
}},
|
||||
}, out)
|
||||
})
|
||||
@@ -338,7 +339,7 @@ func TestAPIPathsGet(t *testing.T) {
|
||||
Type: "rtspSession",
|
||||
},
|
||||
Ready: true,
|
||||
Tracks: []defs.APIPathTrackCodec{defs.APIPathTrackCodecH264},
|
||||
Tracks: []defs.APIPathTrackCodec{formatlabel.H264},
|
||||
}, out)
|
||||
} else {
|
||||
res, err := hc.Get("http://localhost:9997/v3/paths/get/" + pathName)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/bluenviron/mediamtx/internal/conf"
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/hooks"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/recorder"
|
||||
@@ -659,7 +660,7 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) {
|
||||
if !pa.isAvailable() {
|
||||
return []defs.APIPathTrackCodec{}
|
||||
}
|
||||
return defs.MediasToCodecs(pa.stream.Desc.Medias)
|
||||
return formatlabel.MediasToLabels(pa.stream.Desc.Medias)
|
||||
}(),
|
||||
Tracks2: func() []defs.APIPathTrack {
|
||||
if !pa.isAvailable() {
|
||||
|
||||
@@ -1,7 +1,56 @@
|
||||
package defs
|
||||
|
||||
import (
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
)
|
||||
|
||||
// APIPathTrack is a track.
|
||||
type APIPathTrack struct {
|
||||
Codec APIPathTrackCodec `json:"codec"`
|
||||
CodecProps APIPathTrackCodecProps `json:"codecProps"`
|
||||
}
|
||||
|
||||
func formatToTrack(forma format.Format) APIPathTrack {
|
||||
return APIPathTrack{
|
||||
Codec: formatlabel.FormatToLabel(forma),
|
||||
CodecProps: formatToTrackCodecProps(forma),
|
||||
}
|
||||
}
|
||||
|
||||
func gatherFormats(medias []*description.Media) []format.Format {
|
||||
n := 0
|
||||
for _, media := range medias {
|
||||
n += len(media.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
|
||||
}
|
||||
|
||||
// FormatsToTracks returns tracks of given formats.
|
||||
func FormatsToTracks(formats []format.Format) []APIPathTrack {
|
||||
ret := make([]APIPathTrack, len(formats))
|
||||
|
||||
for i, forma := range formats {
|
||||
ret[i] = formatToTrack(forma)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// MediasToTracks returns tracks of given medias.
|
||||
func MediasToTracks(medias []*description.Media) []APIPathTrack {
|
||||
return FormatsToTracks(gatherFormats(medias))
|
||||
}
|
||||
|
||||
@@ -1,317 +1,6 @@
|
||||
package defs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
import "github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
codecsh264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
|
||||
codecsh265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
|
||||
)
|
||||
|
||||
// APIPathTrackCodec is a path track codec.
|
||||
type APIPathTrackCodec string
|
||||
|
||||
// path track codecs.
|
||||
const (
|
||||
// video
|
||||
APIPathTrackCodecAV1 APIPathTrackCodec = "AV1"
|
||||
APIPathTrackCodecVP9 APIPathTrackCodec = "VP9"
|
||||
APIPathTrackCodecVP8 APIPathTrackCodec = "VP8"
|
||||
APIPathTrackCodecH265 APIPathTrackCodec = "H265"
|
||||
APIPathTrackCodecH264 APIPathTrackCodec = "H264"
|
||||
APIPathTrackCodecMPEG4Video APIPathTrackCodec = "MPEG-4 Video"
|
||||
APIPathTrackCodecMPEG1Video APIPathTrackCodec = "MPEG-1 Video"
|
||||
APIPathTrackCodecMJPEG APIPathTrackCodec = "MJPEG"
|
||||
// audio
|
||||
APIPathTrackCodecOpus APIPathTrackCodec = "Opus"
|
||||
APIPathTrackCodecVorbis APIPathTrackCodec = "Vorbis"
|
||||
APIPathTrackCodecMPEG4Audio APIPathTrackCodec = "MPEG-4 Audio"
|
||||
APIPathTrackCodecMPEG4AudioLATM APIPathTrackCodec = "MPEG-4 Audio LATM"
|
||||
APIPathTrackCodecMPEG1Audio APIPathTrackCodec = "MPEG-1 Audio"
|
||||
APIPathTrackCodecAC3 APIPathTrackCodec = "AC3"
|
||||
APIPathTrackCodecSpeex APIPathTrackCodec = "Speex"
|
||||
APIPathTrackCodecG726 APIPathTrackCodec = "G726"
|
||||
APIPathTrackCodecG722 APIPathTrackCodec = "G722"
|
||||
APIPathTrackCodecG711 APIPathTrackCodec = "G711"
|
||||
APIPathTrackCodecLPCM APIPathTrackCodec = "LPCM"
|
||||
// other
|
||||
APIPathTrackCodecMPEGTS APIPathTrackCodec = "MPEG-TS"
|
||||
APIPathTrackCodecKLV APIPathTrackCodec = "KLV"
|
||||
APIPathTrackCodecGeneric APIPathTrackCodec = "Generic"
|
||||
)
|
||||
|
||||
func formatToCodec(forma format.Format) APIPathTrackCodec {
|
||||
switch forma.(type) {
|
||||
// video
|
||||
case *format.AV1:
|
||||
return APIPathTrackCodecAV1
|
||||
case *format.VP9:
|
||||
return APIPathTrackCodecVP9
|
||||
case *format.VP8:
|
||||
return APIPathTrackCodecVP8
|
||||
case *format.H265:
|
||||
return APIPathTrackCodecH265
|
||||
case *format.H264:
|
||||
return APIPathTrackCodecH264
|
||||
case *format.MPEG4Video:
|
||||
return APIPathTrackCodecMPEG4Video
|
||||
case *format.MPEG1Video:
|
||||
return APIPathTrackCodecMPEG1Video
|
||||
case *format.MJPEG:
|
||||
return APIPathTrackCodecMJPEG
|
||||
// audio
|
||||
case *format.Opus:
|
||||
return APIPathTrackCodecOpus
|
||||
case *format.Vorbis:
|
||||
return APIPathTrackCodecVorbis
|
||||
case *format.MPEG4Audio:
|
||||
return APIPathTrackCodecMPEG4Audio
|
||||
case *format.MPEG4AudioLATM:
|
||||
return APIPathTrackCodecMPEG4AudioLATM
|
||||
case *format.MPEG1Audio:
|
||||
return APIPathTrackCodecMPEG1Audio
|
||||
case *format.AC3:
|
||||
return APIPathTrackCodecAC3
|
||||
case *format.Speex:
|
||||
return APIPathTrackCodecSpeex
|
||||
case *format.G726:
|
||||
return APIPathTrackCodecG726
|
||||
case *format.G722:
|
||||
return APIPathTrackCodecG722
|
||||
case *format.G711:
|
||||
return APIPathTrackCodecG711
|
||||
case *format.LPCM:
|
||||
return APIPathTrackCodecLPCM
|
||||
// other
|
||||
case *format.MPEGTS:
|
||||
return APIPathTrackCodecMPEGTS
|
||||
case *format.KLV:
|
||||
return APIPathTrackCodecKLV
|
||||
default:
|
||||
return APIPathTrackCodecGeneric
|
||||
}
|
||||
}
|
||||
|
||||
func h264ProfileString(profileIdc uint8) string {
|
||||
switch profileIdc {
|
||||
case 66:
|
||||
return "Baseline"
|
||||
case 77:
|
||||
return "Main"
|
||||
case 88:
|
||||
return "Extended"
|
||||
case 100:
|
||||
return "High"
|
||||
case 110:
|
||||
return "High 10"
|
||||
case 122:
|
||||
return "High 4:2:2"
|
||||
case 244:
|
||||
return "High 4:4:4 Predictive"
|
||||
default:
|
||||
return strconv.Itoa(int(profileIdc))
|
||||
}
|
||||
}
|
||||
|
||||
func h264LevelString(levelIdc uint8) string {
|
||||
major := levelIdc / 10
|
||||
minor := levelIdc % 10
|
||||
if minor == 0 {
|
||||
return fmt.Sprintf("%d", major)
|
||||
}
|
||||
return fmt.Sprintf("%d.%d", major, minor)
|
||||
}
|
||||
|
||||
func h265ProfileString(profileIdc uint8) string {
|
||||
switch profileIdc {
|
||||
case 1:
|
||||
return "Main"
|
||||
case 2:
|
||||
return "Main 10"
|
||||
case 3:
|
||||
return "Main Still Picture"
|
||||
case 4:
|
||||
return "Range Extensions"
|
||||
case 5:
|
||||
return "High Throughput"
|
||||
case 6:
|
||||
return "Multiview Main"
|
||||
case 7:
|
||||
return "Scalable Main"
|
||||
case 8:
|
||||
return "3D Main"
|
||||
case 9:
|
||||
return "Screen Content Coding Extensions"
|
||||
case 10:
|
||||
return "Scalable Range Extensions"
|
||||
default:
|
||||
return strconv.Itoa(int(profileIdc))
|
||||
}
|
||||
}
|
||||
|
||||
func h265LevelString(levelIdc uint8) string {
|
||||
// H.265 level_idc = 30 * level, so 3.0 = 90, 4.0 = 120, 4.1 = 123
|
||||
level := float64(levelIdc) / 30.0
|
||||
// Check if it's a clean level like 3.0, 4.0
|
||||
if levelIdc%30 == 0 {
|
||||
return fmt.Sprintf("%.0f", level)
|
||||
}
|
||||
return fmt.Sprintf("%.1f", level)
|
||||
}
|
||||
|
||||
// FormatsToCodecs returns codecs of given formats.
|
||||
func FormatsToCodecs(formats []format.Format) []APIPathTrackCodec {
|
||||
ret := make([]APIPathTrackCodec, len(formats))
|
||||
for i, forma := range formats {
|
||||
ret[i] = formatToCodec(forma)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func formatToTrack(forma format.Format) APIPathTrack {
|
||||
return APIPathTrack{
|
||||
Codec: formatToCodec(forma),
|
||||
CodecProps: formatToTrackCodecProps(forma),
|
||||
}
|
||||
}
|
||||
|
||||
func formatToTrackCodecProps(forma format.Format) APIPathTrackCodecProps {
|
||||
switch forma := forma.(type) {
|
||||
case *format.AV1:
|
||||
props := &APIPathTrackCodecPropsAV1{}
|
||||
|
||||
if forma.Profile != nil {
|
||||
props.Profile = *forma.Profile
|
||||
}
|
||||
if forma.LevelIdx != nil {
|
||||
props.Level = *forma.LevelIdx
|
||||
}
|
||||
if forma.Tier != nil {
|
||||
props.Tier = *forma.Tier
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.VP9:
|
||||
props := &APIPathTrackCodecPropsVP9{}
|
||||
|
||||
if forma.ProfileID != nil {
|
||||
props.Profile = *forma.ProfileID
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.H265:
|
||||
props := &APIPathTrackCodecPropsH265{}
|
||||
|
||||
_, sps, _ := forma.SafeParams()
|
||||
if sps != nil {
|
||||
var s codecsh265.SPS
|
||||
if err := s.Unmarshal(sps); err == nil {
|
||||
props.Width = s.Width()
|
||||
props.Height = s.Height()
|
||||
props.Profile = h265ProfileString(s.ProfileTierLevel.GeneralProfileIdc)
|
||||
props.Level = h265LevelString(s.ProfileTierLevel.GeneralLevelIdc)
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.H264:
|
||||
props := &APIPathTrackCodecPropsH264{}
|
||||
|
||||
sps, _ := forma.SafeParams()
|
||||
if sps != nil {
|
||||
var s codecsh264.SPS
|
||||
if err := s.Unmarshal(sps); err == nil {
|
||||
props.Width = s.Width()
|
||||
props.Height = s.Height()
|
||||
props.Profile = h264ProfileString(s.ProfileIdc)
|
||||
props.Level = h264LevelString(s.LevelIdc)
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.Opus:
|
||||
return &APIPathTrackCodecPropsOpus{
|
||||
ChannelCount: forma.ChannelCount,
|
||||
}
|
||||
|
||||
case *format.MPEG4Audio:
|
||||
props := &APIPathTrackCodecPropsMPEG4Audio{}
|
||||
|
||||
if forma.Config != nil {
|
||||
props.SampleRate = forma.Config.SampleRate
|
||||
props.ChannelCount = int(forma.Config.ChannelConfig)
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.AC3:
|
||||
return &APIPathTrackCodecPropsAC3{
|
||||
SampleRate: forma.SampleRate,
|
||||
ChannelCount: forma.ChannelCount,
|
||||
}
|
||||
|
||||
case *format.G711:
|
||||
return &APIPathTrackCodecPropsG711{
|
||||
MULaw: forma.MULaw,
|
||||
SampleRate: forma.SampleRate,
|
||||
ChannelCount: forma.ChannelCount,
|
||||
}
|
||||
|
||||
case *format.LPCM:
|
||||
return &APIPathTrackCodecPropsLPCM{
|
||||
BitDepth: forma.BitDepth,
|
||||
SampleRate: forma.SampleRate,
|
||||
ChannelCount: forma.ChannelCount,
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func gatherFormats(medias []*description.Media) []format.Format {
|
||||
n := 0
|
||||
for _, media := range medias {
|
||||
n += len(media.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 codecs of given medias.
|
||||
func MediasToCodecs(medias []*description.Media) []APIPathTrackCodec {
|
||||
return FormatsToCodecs(gatherFormats(medias))
|
||||
}
|
||||
|
||||
// FormatsToTracks returns tracks of given formats.
|
||||
func FormatsToTracks(formats []format.Format) []APIPathTrack {
|
||||
ret := make([]APIPathTrack, len(formats))
|
||||
|
||||
for i, forma := range formats {
|
||||
ret[i] = formatToTrack(forma)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// MediasToTracks returns tracks of given medias.
|
||||
func MediasToTracks(medias []*description.Media) []APIPathTrack {
|
||||
return FormatsToTracks(gatherFormats(medias))
|
||||
}
|
||||
// APIPathTrackCodec is a track codec.
|
||||
type APIPathTrackCodec = formatlabel.Label
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
package defs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
codecsh264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
|
||||
codecsh265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
|
||||
)
|
||||
|
||||
// APIPathTrackCodecProps contains codec-specific properties.
|
||||
type APIPathTrackCodecProps interface {
|
||||
apiPathTrackCodecProps()
|
||||
@@ -83,3 +92,168 @@ type APIPathTrackCodecPropsLPCM struct {
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsLPCM) apiPathTrackCodecProps() {}
|
||||
|
||||
func h264ProfileString(profileIdc uint8) string {
|
||||
switch profileIdc {
|
||||
case 66:
|
||||
return "Baseline"
|
||||
case 77:
|
||||
return "Main"
|
||||
case 88:
|
||||
return "Extended"
|
||||
case 100:
|
||||
return "High"
|
||||
case 110:
|
||||
return "High 10"
|
||||
case 122:
|
||||
return "High 4:2:2"
|
||||
case 244:
|
||||
return "High 4:4:4 Predictive"
|
||||
default:
|
||||
return strconv.Itoa(int(profileIdc))
|
||||
}
|
||||
}
|
||||
|
||||
func h264LevelString(levelIdc uint8) string {
|
||||
major := levelIdc / 10
|
||||
minor := levelIdc % 10
|
||||
if minor == 0 {
|
||||
return fmt.Sprintf("%d", major)
|
||||
}
|
||||
return fmt.Sprintf("%d.%d", major, minor)
|
||||
}
|
||||
|
||||
func h265ProfileString(profileIdc uint8) string {
|
||||
switch profileIdc {
|
||||
case 1:
|
||||
return "Main"
|
||||
case 2:
|
||||
return "Main 10"
|
||||
case 3:
|
||||
return "Main Still Picture"
|
||||
case 4:
|
||||
return "Range Extensions"
|
||||
case 5:
|
||||
return "High Throughput"
|
||||
case 6:
|
||||
return "Multiview Main"
|
||||
case 7:
|
||||
return "Scalable Main"
|
||||
case 8:
|
||||
return "3D Main"
|
||||
case 9:
|
||||
return "Screen Content Coding Extensions"
|
||||
case 10:
|
||||
return "Scalable Range Extensions"
|
||||
default:
|
||||
return strconv.Itoa(int(profileIdc))
|
||||
}
|
||||
}
|
||||
|
||||
func h265LevelString(levelIdc uint8) string {
|
||||
// H.265 level_idc = 30 * level, so 3.0 = 90, 4.0 = 120, 4.1 = 123
|
||||
level := float64(levelIdc) / 30.0
|
||||
// Check if it's a clean level like 3.0, 4.0
|
||||
if levelIdc%30 == 0 {
|
||||
return fmt.Sprintf("%.0f", level)
|
||||
}
|
||||
return fmt.Sprintf("%.1f", level)
|
||||
}
|
||||
|
||||
func formatToTrackCodecProps(forma format.Format) APIPathTrackCodecProps {
|
||||
switch forma := forma.(type) {
|
||||
case *format.AV1:
|
||||
props := &APIPathTrackCodecPropsAV1{}
|
||||
|
||||
if forma.Profile != nil {
|
||||
props.Profile = *forma.Profile
|
||||
}
|
||||
if forma.LevelIdx != nil {
|
||||
props.Level = *forma.LevelIdx
|
||||
}
|
||||
if forma.Tier != nil {
|
||||
props.Tier = *forma.Tier
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.VP9:
|
||||
props := &APIPathTrackCodecPropsVP9{}
|
||||
|
||||
if forma.ProfileID != nil {
|
||||
props.Profile = *forma.ProfileID
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.H265:
|
||||
props := &APIPathTrackCodecPropsH265{}
|
||||
|
||||
_, sps, _ := forma.SafeParams()
|
||||
if sps != nil {
|
||||
var s codecsh265.SPS
|
||||
if err := s.Unmarshal(sps); err == nil {
|
||||
props.Width = s.Width()
|
||||
props.Height = s.Height()
|
||||
props.Profile = h265ProfileString(s.ProfileTierLevel.GeneralProfileIdc)
|
||||
props.Level = h265LevelString(s.ProfileTierLevel.GeneralLevelIdc)
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.H264:
|
||||
props := &APIPathTrackCodecPropsH264{}
|
||||
|
||||
sps, _ := forma.SafeParams()
|
||||
if sps != nil {
|
||||
var s codecsh264.SPS
|
||||
if err := s.Unmarshal(sps); err == nil {
|
||||
props.Width = s.Width()
|
||||
props.Height = s.Height()
|
||||
props.Profile = h264ProfileString(s.ProfileIdc)
|
||||
props.Level = h264LevelString(s.LevelIdc)
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.Opus:
|
||||
return &APIPathTrackCodecPropsOpus{
|
||||
ChannelCount: forma.ChannelCount,
|
||||
}
|
||||
|
||||
case *format.MPEG4Audio:
|
||||
props := &APIPathTrackCodecPropsMPEG4Audio{}
|
||||
|
||||
if forma.Config != nil {
|
||||
props.SampleRate = forma.Config.SampleRate
|
||||
props.ChannelCount = int(forma.Config.ChannelConfig)
|
||||
}
|
||||
|
||||
return props
|
||||
|
||||
case *format.AC3:
|
||||
return &APIPathTrackCodecPropsAC3{
|
||||
SampleRate: forma.SampleRate,
|
||||
ChannelCount: forma.ChannelCount,
|
||||
}
|
||||
|
||||
case *format.G711:
|
||||
return &APIPathTrackCodecPropsG711{
|
||||
MULaw: forma.MULaw,
|
||||
SampleRate: forma.SampleRate,
|
||||
ChannelCount: forma.ChannelCount,
|
||||
}
|
||||
|
||||
case *format.LPCM:
|
||||
return &APIPathTrackCodecPropsLPCM{
|
||||
BitDepth: forma.BitDepth,
|
||||
SampleRate: forma.SampleRate,
|
||||
ChannelCount: forma.ChannelCount,
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
package defs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func ptr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
func testFormatH264() *format.H264 {
|
||||
return &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{
|
||||
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,
|
||||
},
|
||||
PPS: []byte{0x08, 0x06, 0x07, 0x08},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func testFormatMPEG4Audio() *format.MPEG4Audio {
|
||||
return &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
ChannelCount: 2,
|
||||
ChannelConfig: 2,
|
||||
},
|
||||
SizeLength: 13,
|
||||
IndexLength: 3,
|
||||
IndexDeltaLength: 3,
|
||||
}
|
||||
}
|
||||
|
||||
func testFormatH265() *format.H265 {
|
||||
return &format.H265{
|
||||
PayloadTyp: 96,
|
||||
VPS: []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,
|
||||
},
|
||||
SPS: []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,
|
||||
},
|
||||
PPS: []byte{
|
||||
0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatsToCodecs(t *testing.T) {
|
||||
codecs := FormatsToCodecs([]format.Format{
|
||||
&format.AV1{},
|
||||
&format.VP9{},
|
||||
&format.VP8{},
|
||||
&format.H265{},
|
||||
&format.H264{},
|
||||
&format.MPEG4Video{},
|
||||
&format.MPEG1Video{},
|
||||
&format.MJPEG{},
|
||||
&format.Opus{},
|
||||
&format.Vorbis{},
|
||||
&format.MPEG4Audio{},
|
||||
&format.MPEG4AudioLATM{},
|
||||
&format.MPEG1Audio{},
|
||||
&format.AC3{},
|
||||
&format.Speex{},
|
||||
&format.G726{},
|
||||
&format.G722{},
|
||||
&format.G711{},
|
||||
&format.LPCM{},
|
||||
&format.MPEGTS{},
|
||||
&format.KLV{},
|
||||
&format.Generic{},
|
||||
})
|
||||
|
||||
require.Equal(t, []APIPathTrackCodec{
|
||||
APIPathTrackCodecAV1,
|
||||
APIPathTrackCodecVP9,
|
||||
APIPathTrackCodecVP8,
|
||||
APIPathTrackCodecH265,
|
||||
APIPathTrackCodecH264,
|
||||
APIPathTrackCodecMPEG4Video,
|
||||
APIPathTrackCodecMPEG1Video,
|
||||
APIPathTrackCodecMJPEG,
|
||||
APIPathTrackCodecOpus,
|
||||
APIPathTrackCodecVorbis,
|
||||
APIPathTrackCodecMPEG4Audio,
|
||||
APIPathTrackCodecMPEG4AudioLATM,
|
||||
APIPathTrackCodecMPEG1Audio,
|
||||
APIPathTrackCodecAC3,
|
||||
APIPathTrackCodecSpeex,
|
||||
APIPathTrackCodecG726,
|
||||
APIPathTrackCodecG722,
|
||||
APIPathTrackCodecG711,
|
||||
APIPathTrackCodecLPCM,
|
||||
APIPathTrackCodecMPEGTS,
|
||||
APIPathTrackCodecKLV,
|
||||
APIPathTrackCodecGeneric,
|
||||
}, codecs)
|
||||
}
|
||||
|
||||
func TestMediasToTracks(t *testing.T) {
|
||||
tracks := MediasToTracks([]*description.Media{
|
||||
{Formats: []format.Format{testFormatH264()}},
|
||||
{Formats: []format.Format{testFormatH265()}},
|
||||
{
|
||||
Formats: []format.Format{
|
||||
&format.AV1{Profile: ptr(1), LevelIdx: ptr(9), Tier: ptr(0)},
|
||||
&format.VP9{ProfileID: ptr(2)},
|
||||
&format.MJPEG{},
|
||||
&format.Opus{ChannelCount: 2},
|
||||
&format.G711{MULaw: true, SampleRate: 8000, ChannelCount: 1},
|
||||
&format.LPCM{BitDepth: 16, SampleRate: 48000, ChannelCount: 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
Formats: []format.Format{testFormatMPEG4Audio()},
|
||||
},
|
||||
})
|
||||
|
||||
require.Equal(t, []APIPathTrack{
|
||||
{
|
||||
Codec: APIPathTrackCodecH264,
|
||||
CodecProps: &APIPathTrackCodecPropsH264{
|
||||
Width: 1920,
|
||||
Height: 1080,
|
||||
Profile: "Baseline",
|
||||
Level: "4",
|
||||
},
|
||||
},
|
||||
{
|
||||
Codec: APIPathTrackCodecH265,
|
||||
CodecProps: &APIPathTrackCodecPropsH265{
|
||||
Width: 960,
|
||||
Height: 540,
|
||||
Profile: "Main 10",
|
||||
Level: "4.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Codec: APIPathTrackCodecAV1,
|
||||
CodecProps: &APIPathTrackCodecPropsAV1{
|
||||
Profile: 1,
|
||||
Level: 9,
|
||||
Tier: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
Codec: APIPathTrackCodecVP9,
|
||||
CodecProps: &APIPathTrackCodecPropsVP9{
|
||||
Profile: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Codec: APIPathTrackCodecMJPEG,
|
||||
CodecProps: nil,
|
||||
},
|
||||
{
|
||||
Codec: APIPathTrackCodecOpus,
|
||||
CodecProps: &APIPathTrackCodecPropsOpus{
|
||||
ChannelCount: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Codec: APIPathTrackCodecG711,
|
||||
CodecProps: &APIPathTrackCodecPropsG711{
|
||||
MULaw: true,
|
||||
SampleRate: 8000,
|
||||
ChannelCount: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Codec: APIPathTrackCodecLPCM,
|
||||
CodecProps: &APIPathTrackCodecPropsLPCM{
|
||||
BitDepth: 16,
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Codec: APIPathTrackCodecMPEG4Audio,
|
||||
CodecProps: &APIPathTrackCodecPropsMPEG4Audio{
|
||||
SampleRate: 44100,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
},
|
||||
}, tracks)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
)
|
||||
|
||||
@@ -22,10 +23,10 @@ type Source interface {
|
||||
|
||||
// FormatsInfo returns a description of formats.
|
||||
func FormatsInfo(formats []format.Format) string {
|
||||
codecs := FormatsToCodecs(formats)
|
||||
codecNames := make([]string, len(codecs))
|
||||
for i, codec := range codecs {
|
||||
codecNames[i] = string(codec)
|
||||
labels := formatlabel.FormatsToLabels(formats)
|
||||
codecNames := make([]string, len(labels))
|
||||
for i, label := range labels {
|
||||
codecNames[i] = string(label)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d %s (%s)",
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// Package formatlabel contains format label definitions.
|
||||
package formatlabel
|
||||
|
||||
import (
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
)
|
||||
|
||||
// Label is a format label.
|
||||
type Label string
|
||||
|
||||
// labels.
|
||||
const (
|
||||
// video
|
||||
AV1 Label = "AV1"
|
||||
VP9 Label = "VP9"
|
||||
VP8 Label = "VP8"
|
||||
H265 Label = "H265"
|
||||
H264 Label = "H264"
|
||||
MPEG4Video Label = "MPEG-4 Video"
|
||||
MPEG1Video Label = "MPEG-1/2 Video"
|
||||
MJPEG Label = "M-JPEG"
|
||||
// audio
|
||||
Opus Label = "Opus"
|
||||
Vorbis Label = "Vorbis"
|
||||
MPEG4Audio Label = "MPEG-4 Audio"
|
||||
MPEG4AudioLATM Label = "MPEG-4 Audio LATM"
|
||||
MPEG1Audio Label = "MPEG-1/2 Audio"
|
||||
AC3 Label = "AC3"
|
||||
Speex Label = "Speex"
|
||||
G726 Label = "G726"
|
||||
G722 Label = "G722"
|
||||
G711 Label = "G711"
|
||||
LPCM Label = "LPCM"
|
||||
// other
|
||||
MPEGTS Label = "MPEG-TS"
|
||||
KLV Label = "KLV"
|
||||
Generic Label = "Generic"
|
||||
)
|
||||
|
||||
// FormatToLabel returns the label associated with a format.
|
||||
func FormatToLabel(forma format.Format) Label {
|
||||
switch forma.(type) {
|
||||
// video
|
||||
case *format.AV1:
|
||||
return AV1
|
||||
case *format.VP9:
|
||||
return VP9
|
||||
case *format.VP8:
|
||||
return VP8
|
||||
case *format.H265:
|
||||
return H265
|
||||
case *format.H264:
|
||||
return H264
|
||||
case *format.MPEG4Video:
|
||||
return MPEG4Video
|
||||
case *format.MPEG1Video:
|
||||
return MPEG1Video
|
||||
case *format.MJPEG:
|
||||
return MJPEG
|
||||
// audio
|
||||
case *format.Opus:
|
||||
return Opus
|
||||
case *format.Vorbis:
|
||||
return Vorbis
|
||||
case *format.MPEG4Audio:
|
||||
return MPEG4Audio
|
||||
case *format.MPEG4AudioLATM:
|
||||
return MPEG4AudioLATM
|
||||
case *format.MPEG1Audio:
|
||||
return MPEG1Audio
|
||||
case *format.AC3:
|
||||
return AC3
|
||||
case *format.Speex:
|
||||
return Speex
|
||||
case *format.G726:
|
||||
return G726
|
||||
case *format.G722:
|
||||
return G722
|
||||
case *format.G711:
|
||||
return G711
|
||||
case *format.LPCM:
|
||||
return LPCM
|
||||
// other
|
||||
case *format.MPEGTS:
|
||||
return MPEGTS
|
||||
case *format.KLV:
|
||||
return KLV
|
||||
default:
|
||||
return Generic
|
||||
}
|
||||
}
|
||||
|
||||
func gatherFormats(medias []*description.Media) []format.Format {
|
||||
n := 0
|
||||
for _, media := range medias {
|
||||
n += len(media.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
|
||||
}
|
||||
|
||||
// FormatsToLabels returns labels of given formats.
|
||||
func FormatsToLabels(formats []format.Format) []Label {
|
||||
ret := make([]Label, len(formats))
|
||||
for i, forma := range formats {
|
||||
ret[i] = FormatToLabel(forma)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// MediasToLabels returns labels of given medias.
|
||||
func MediasToLabels(medias []*description.Media) []Label {
|
||||
return FormatsToLabels(gatherFormats(medias))
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package formatlabel
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFormatToLabel(t *testing.T) {
|
||||
codecs := []Label{
|
||||
FormatToLabel(&format.AV1{}),
|
||||
FormatToLabel(&format.VP9{}),
|
||||
FormatToLabel(&format.VP8{}),
|
||||
FormatToLabel(&format.H265{}),
|
||||
FormatToLabel(&format.H264{}),
|
||||
FormatToLabel(&format.MPEG4Video{}),
|
||||
FormatToLabel(&format.MPEG1Video{}),
|
||||
FormatToLabel(&format.MJPEG{}),
|
||||
FormatToLabel(&format.Opus{}),
|
||||
FormatToLabel(&format.Vorbis{}),
|
||||
FormatToLabel(&format.MPEG4Audio{}),
|
||||
FormatToLabel(&format.MPEG4AudioLATM{}),
|
||||
FormatToLabel(&format.MPEG1Audio{}),
|
||||
FormatToLabel(&format.AC3{}),
|
||||
FormatToLabel(&format.Speex{}),
|
||||
FormatToLabel(&format.G726{}),
|
||||
FormatToLabel(&format.G722{}),
|
||||
FormatToLabel(&format.G711{}),
|
||||
FormatToLabel(&format.LPCM{}),
|
||||
FormatToLabel(&format.MPEGTS{}),
|
||||
FormatToLabel(&format.KLV{}),
|
||||
FormatToLabel(&format.Generic{}),
|
||||
}
|
||||
|
||||
require.Equal(t, []Label{
|
||||
AV1,
|
||||
VP9,
|
||||
VP8,
|
||||
H265,
|
||||
H264,
|
||||
MPEG4Video,
|
||||
MPEG1Video,
|
||||
MJPEG,
|
||||
Opus,
|
||||
Vorbis,
|
||||
MPEG4Audio,
|
||||
MPEG4AudioLATM,
|
||||
MPEG1Audio,
|
||||
AC3,
|
||||
Speex,
|
||||
G726,
|
||||
G722,
|
||||
G711,
|
||||
LPCM,
|
||||
MPEGTS,
|
||||
KLV,
|
||||
Generic,
|
||||
}, codecs)
|
||||
}
|
||||
|
||||
func TestFormatsToLabels(t *testing.T) {
|
||||
require.Equal(t, []Label{
|
||||
H264,
|
||||
Opus,
|
||||
KLV,
|
||||
Generic,
|
||||
}, FormatsToLabels([]format.Format{
|
||||
&format.H264{},
|
||||
&format.Opus{},
|
||||
&format.KLV{},
|
||||
&format.Generic{},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestMediasToLabels(t *testing.T) {
|
||||
require.Equal(t, []Label{
|
||||
H264,
|
||||
Opus,
|
||||
G711,
|
||||
KLV,
|
||||
}, MediasToLabels([]*description.Media{
|
||||
{Formats: []format.Format{&format.H264{}}},
|
||||
{Formats: []format.Format{&format.Opus{}, &format.G711{}}},
|
||||
{Formats: []format.Format{&format.KLV{}}},
|
||||
}))
|
||||
}
|
||||
@@ -46,6 +46,7 @@ func wrapRef(rt reflect.Type, p openAPIProperty) openAPIProperty {
|
||||
type openAPISchema struct {
|
||||
Type string `yaml:"type"`
|
||||
Enum []string `yaml:"enum"`
|
||||
OneOf []openAPIProperty `yaml:"oneOf"`
|
||||
Properties map[string]openAPIProperty `yaml:"properties"`
|
||||
}
|
||||
|
||||
@@ -62,6 +63,10 @@ func schemaName(rt reflect.Type) string {
|
||||
return "PathConf"
|
||||
}
|
||||
|
||||
if rt == reflect.TypeOf(defs.APIPathTrackCodec("")) {
|
||||
return "PathTrackCodec"
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
@@ -190,13 +195,13 @@ func goEnumToApi(rt reflect.Type) (openAPISchema, bool) {
|
||||
"H265",
|
||||
"H264",
|
||||
"MPEG-4 Video",
|
||||
"MPEG-1 Video",
|
||||
"MJPEG",
|
||||
"MPEG-1/2 Video",
|
||||
"M-JPEG",
|
||||
"Opus",
|
||||
"Vorbis",
|
||||
"MPEG-4 Audio",
|
||||
"MPEG-4 Audio LATM",
|
||||
"MPEG-1 Audio",
|
||||
"MPEG-1/2 Audio",
|
||||
"AC3",
|
||||
"Speex",
|
||||
"G726",
|
||||
@@ -379,6 +384,42 @@ func TestGo2API(t *testing.T) {
|
||||
"PathTrack",
|
||||
defs.APIPathTrack{},
|
||||
},
|
||||
{
|
||||
"PathTrackCodecPropsAV1",
|
||||
defs.APIPathTrackCodecPropsAV1{},
|
||||
},
|
||||
{
|
||||
"PathTrackCodecPropsVP9",
|
||||
defs.APIPathTrackCodecPropsVP9{},
|
||||
},
|
||||
{
|
||||
"PathTrackCodecPropsH265",
|
||||
defs.APIPathTrackCodecPropsH265{},
|
||||
},
|
||||
{
|
||||
"PathTrackCodecPropsH264",
|
||||
defs.APIPathTrackCodecPropsH264{},
|
||||
},
|
||||
{
|
||||
"PathTrackCodecPropsOpus",
|
||||
defs.APIPathTrackCodecPropsOpus{},
|
||||
},
|
||||
{
|
||||
"PathTrackCodecPropsMPEG4Audio",
|
||||
defs.APIPathTrackCodecPropsMPEG4Audio{},
|
||||
},
|
||||
{
|
||||
"PathTrackCodecPropsAC3",
|
||||
defs.APIPathTrackCodecPropsAC3{},
|
||||
},
|
||||
{
|
||||
"PathTrackCodecPropsG711",
|
||||
defs.APIPathTrackCodecPropsG711{},
|
||||
},
|
||||
{
|
||||
"PathTrackCodecPropsLPCM",
|
||||
defs.APIPathTrackCodecPropsLPCM{},
|
||||
},
|
||||
{
|
||||
"Recording",
|
||||
defs.APIRecording{},
|
||||
@@ -464,6 +505,20 @@ func TestGo2API(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("oneOfs", func(t *testing.T) {
|
||||
require.Equal(t, openAPISchema{OneOf: []openAPIProperty{
|
||||
{Ref: "#/components/schemas/PathTrackCodecPropsAV1"},
|
||||
{Ref: "#/components/schemas/PathTrackCodecPropsVP9"},
|
||||
{Ref: "#/components/schemas/PathTrackCodecPropsH265"},
|
||||
{Ref: "#/components/schemas/PathTrackCodecPropsH264"},
|
||||
{Ref: "#/components/schemas/PathTrackCodecPropsOpus"},
|
||||
{Ref: "#/components/schemas/PathTrackCodecPropsMPEG4Audio"},
|
||||
{Ref: "#/components/schemas/PathTrackCodecPropsAC3"},
|
||||
{Ref: "#/components/schemas/PathTrackCodecPropsG711"},
|
||||
{Ref: "#/components/schemas/PathTrackCodecPropsLPCM"},
|
||||
}}, doc.Components.Schemas["PathTrackCodecProps"])
|
||||
})
|
||||
|
||||
t.Run("enums", func(t *testing.T) {
|
||||
for _, rt := range []reflect.Type{
|
||||
reflect.TypeOf(defs.APIOKStatus("")),
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/bluenviron/mediamtx/internal/auth"
|
||||
"github.com/bluenviron/mediamtx/internal/conf"
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/test"
|
||||
"github.com/google/uuid"
|
||||
@@ -50,7 +51,7 @@ func (dummyPathManager) APIPathsList() (*defs.APIPathList, error) {
|
||||
},
|
||||
Ready: true,
|
||||
ReadyTime: ptrOf(time.Date(2003, 11, 4, 23, 15, 7, 0, time.UTC)),
|
||||
Tracks: []defs.APIPathTrackCodec{defs.APIPathTrackCodecH264, defs.APIPathTrackCodecH265},
|
||||
Tracks: []defs.APIPathTrackCodec{formatlabel.H264, formatlabel.H265},
|
||||
InboundBytes: 123,
|
||||
OutboundBytes: 456,
|
||||
InboundFramesInError: 7,
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/stream"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
@@ -314,7 +315,7 @@ func FromStream(
|
||||
for _, media := range desc.Medias {
|
||||
for _, forma := range media.Formats {
|
||||
if !slices.Contains(setuppedFormats, forma) {
|
||||
r.Parent.Log(logger.Warn, "skipping track %d (%s)", n, forma.Codec())
|
||||
r.Parent.Log(logger.Warn, "skipping track %d (%s)", n, formatlabel.FormatToLabel(forma))
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/substructs"
|
||||
srt "github.com/datarhei/gosrt"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/stream"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
@@ -185,7 +186,7 @@ func FromStream(
|
||||
if !firstReceived {
|
||||
firstReceived = true
|
||||
} else if u.PTS < lastPTS {
|
||||
return fmt.Errorf("MPEG-1 Video streams with B-frames are not supported (yet)")
|
||||
return fmt.Errorf("MPEG-1/2 Video streams with B-frames are not supported (yet)")
|
||||
}
|
||||
lastPTS = u.PTS
|
||||
|
||||
@@ -402,7 +403,7 @@ func FromStream(
|
||||
for _, medi := range desc.Medias {
|
||||
for _, forma := range medi.Formats {
|
||||
if !slices.Contains(setuppedFormats, forma) {
|
||||
r.Parent.Log(logger.Warn, "skipping track %d (%s)", n, forma.Codec())
|
||||
r.Parent.Log(logger.Warn, "skipping track %d (%s)", n, formatlabel.FormatToLabel(forma))
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
var errNoSupportedCodecs = errors.New(
|
||||
"the stream doesn't contain any supported codec, which are currently " +
|
||||
"H265, H264, MPEG-4 Video, MPEG-1/2 Video, Opus, MPEG-4 Audio, MPEG-1 Audio, AC-3")
|
||||
"H265, H264, MPEG-4 Video, MPEG-1/2 Video, Opus, MPEG-4 Audio, MPEG-1/2 Audio, AC-3")
|
||||
|
||||
// ToStream maps a MPEG-TS stream to a MediaMTX stream.
|
||||
func ToStream(
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/opus"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/stream"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
@@ -475,7 +476,7 @@ func FromStream(
|
||||
for _, media := range desc.Medias {
|
||||
for _, forma := range media.Formats {
|
||||
if !slices.Contains(setuppedFormats, forma) {
|
||||
r.Parent.Log(logger.Warn, "skipping track %d (%s)", n, forma.Codec())
|
||||
r.Parent.Log(logger.Warn, "skipping track %d (%s)", n, formatlabel.FormatToLabel(forma))
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp9"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/g711"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/opus"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/stream"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
@@ -711,7 +712,7 @@ func FromStream(
|
||||
for _, media := range desc.Medias {
|
||||
for _, forma := range media.Formats {
|
||||
if !slices.Contains(setuppedFormats, forma) {
|
||||
r.Parent.Log(logger.Warn, "skipping track %d (%s)", n, forma.Codec())
|
||||
r.Parent.Log(logger.Warn, "skipping track %d (%s)", n, formatlabel.FormatToLabel(forma))
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
mcodecs "github.com/bluenviron/mediacommon/v2/pkg/formats/mp4/codecs"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
@@ -576,7 +577,7 @@ func (f *formatFMP4) initialize() bool {
|
||||
}
|
||||
firstReceived = true
|
||||
} else if u.PTS < lastPTS {
|
||||
return fmt.Errorf("MPEG-1 Video streams with B-frames are not supported (yet)")
|
||||
return fmt.Errorf("MPEG-1/2 Video streams with B-frames are not supported (yet)")
|
||||
}
|
||||
lastPTS = u.PTS
|
||||
|
||||
@@ -921,7 +922,7 @@ func (f *formatFMP4) initialize() bool {
|
||||
for _, medi := range f.ri.stream.Desc.Medias {
|
||||
for _, forma := range medi.Formats {
|
||||
if !slices.Contains(setuppedFormats, forma) {
|
||||
f.ri.Log(logger.Warn, "skipping track %d (%s)", n, forma.Codec())
|
||||
f.ri.Log(logger.Warn, "skipping track %d (%s)", n, formatlabel.FormatToLabel(forma))
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
tscodecs "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/codecs"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
@@ -221,7 +222,7 @@ func (f *formatMPEGTS) initialize() bool {
|
||||
if !firstReceived {
|
||||
firstReceived = true
|
||||
} else if u.PTS < lastPTS {
|
||||
return fmt.Errorf("MPEG-1 Video streams with B-frames are not supported (yet)")
|
||||
return fmt.Errorf("MPEG-1/2 Video streams with B-frames are not supported (yet)")
|
||||
}
|
||||
lastPTS = u.PTS
|
||||
|
||||
@@ -424,7 +425,7 @@ func (f *formatMPEGTS) initialize() bool {
|
||||
for _, medi := range f.ri.stream.Desc.Medias {
|
||||
for _, forma := range medi.Formats {
|
||||
if !slices.Contains(setuppedFormats, forma) {
|
||||
f.ri.Log(logger.Warn, "skipping track %d (%s)", n, forma.Codec())
|
||||
f.ri.Log(logger.Warn, "skipping track %d (%s)", n, formatlabel.FormatToLabel(forma))
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
@@ -7,43 +7,11 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/bluenviron/mediamtx/internal/formatlabel"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
// FormatsToCodecs returns the name of codecs of given formats.
|
||||
func FormatsToCodecs(formats []format.Format) []string {
|
||||
ret := make([]string, len(formats))
|
||||
for i, forma := range formats {
|
||||
ret[i] = forma.Codec()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func gatherFormats(medias []*description.Media) []format.Format {
|
||||
n := 0
|
||||
for _, media := range medias {
|
||||
n += len(media.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
|
||||
}
|
||||
|
||||
func mediasToCodecs(medias []*description.Media) []string {
|
||||
return FormatsToCodecs(gatherFormats(medias))
|
||||
}
|
||||
|
||||
func formatMPEG4AudioConfig(asc *mpeg4audio.AudioSpecificConfig) string {
|
||||
return fmt.Sprintf("type=%d, sampleRate=%d, channelCount=%d",
|
||||
asc.Type, asc.SampleRate, asc.ChannelConfig)
|
||||
@@ -62,19 +30,19 @@ func formatLPCMConfig(f *format.LPCM) string {
|
||||
func mediasAreCompatible(medias1 []*description.Media, medias2 []*description.Media) error {
|
||||
if len(medias1) != len(medias2) {
|
||||
return fmt.Errorf("wants to publish %v, but stream expects %v",
|
||||
mediasToCodecs(medias2), mediasToCodecs(medias1))
|
||||
formatlabel.MediasToLabels(medias2), formatlabel.MediasToLabels(medias1))
|
||||
}
|
||||
|
||||
for i := range medias1 {
|
||||
if len(medias1[i].Formats) != len(medias2[i].Formats) {
|
||||
return fmt.Errorf("wants to publish %v, but stream expects %v",
|
||||
mediasToCodecs(medias2), mediasToCodecs(medias1))
|
||||
formatlabel.MediasToLabels(medias2), formatlabel.MediasToLabels(medias1))
|
||||
}
|
||||
|
||||
for j := range medias1[i].Formats {
|
||||
if reflect.TypeOf(medias1[i].Formats[j]) != reflect.TypeOf(medias2[i].Formats[j]) {
|
||||
return fmt.Errorf("wants to publish %v, but stream expects %v",
|
||||
mediasToCodecs(medias2), mediasToCodecs(medias1))
|
||||
formatlabel.MediasToLabels(medias2), formatlabel.MediasToLabels(medias1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package unit
|
||||
|
||||
// PayloadMPEG1Audio is the payload of a MPEG-1 Audio track.
|
||||
// PayloadMPEG1Audio is the payload of a MPEG-1/2 Audio track.
|
||||
type PayloadMPEG1Audio [][]byte
|
||||
|
||||
func (PayloadMPEG1Audio) isPayload() {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package unit
|
||||
|
||||
// PayloadMPEG1Video is the payload of a MPEG-1 Video track.
|
||||
// PayloadMPEG1Video is the payload of a MPEG-1/2 Video track.
|
||||
type PayloadMPEG1Video []byte
|
||||
|
||||
func (PayloadMPEG1Video) isPayload() {}
|
||||
|
||||
Reference in New Issue
Block a user