@@ -973,8 +973,13 @@ components:
|
||||
nullable: true
|
||||
tracks:
|
||||
type: array
|
||||
deprecated: true
|
||||
items:
|
||||
$ref: '#/components/schemas/PathTrackCodec'
|
||||
tracks2:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PathTrack'
|
||||
inboundBytes:
|
||||
type: integer
|
||||
format: uint64
|
||||
@@ -1027,6 +1032,134 @@ components:
|
||||
id:
|
||||
type: string
|
||||
|
||||
PathTrack:
|
||||
type: object
|
||||
properties:
|
||||
codec:
|
||||
$ref: '#/components/schemas/PathTrackCodec'
|
||||
codecProps:
|
||||
type: object
|
||||
nullable: true
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PathTrackCodecProps'
|
||||
|
||||
PathTrackCodecProps:
|
||||
oneOf:
|
||||
- $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'
|
||||
|
||||
PathTrackCodecPropsAV1:
|
||||
type: object
|
||||
properties:
|
||||
width:
|
||||
type: integer
|
||||
format: int64
|
||||
height:
|
||||
type: integer
|
||||
format: int64
|
||||
profile:
|
||||
type: integer
|
||||
format: int64
|
||||
level:
|
||||
type: integer
|
||||
format: int64
|
||||
tier:
|
||||
type: integer
|
||||
format: int64
|
||||
PathTrackCodecPropsVP9:
|
||||
type: object
|
||||
properties:
|
||||
profile:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
PathTrackCodecPropsH265:
|
||||
type: object
|
||||
properties:
|
||||
width:
|
||||
type: integer
|
||||
format: int64
|
||||
height:
|
||||
type: integer
|
||||
format: int64
|
||||
profile:
|
||||
type: string
|
||||
level:
|
||||
type: string
|
||||
|
||||
PathTrackCodecPropsH264:
|
||||
type: object
|
||||
properties:
|
||||
width:
|
||||
type: integer
|
||||
format: int64
|
||||
height:
|
||||
type: integer
|
||||
format: int64
|
||||
profile:
|
||||
type: string
|
||||
level:
|
||||
type: string
|
||||
|
||||
PathTrackCodecPropsOpus:
|
||||
type: object
|
||||
properties:
|
||||
channelCount:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
PathTrackCodecPropsMPEG4Audio:
|
||||
type: object
|
||||
properties:
|
||||
sampleRate:
|
||||
type: integer
|
||||
format: int64
|
||||
channelCount:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
PathTrackCodecPropsAC3:
|
||||
type: object
|
||||
properties:
|
||||
sampleRate:
|
||||
type: integer
|
||||
format: int64
|
||||
channelCount:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
PathTrackCodecPropsG711:
|
||||
type: object
|
||||
properties:
|
||||
muLaw:
|
||||
type: boolean
|
||||
sampleRate:
|
||||
type: integer
|
||||
format: int64
|
||||
channelCount:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
PathTrackCodecPropsLPCM:
|
||||
type: object
|
||||
properties:
|
||||
bitDepth:
|
||||
type: integer
|
||||
format: int64
|
||||
sampleRate:
|
||||
type: integer
|
||||
format: int64
|
||||
channelCount:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
HLSMuxer:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -10,10 +10,10 @@ require (
|
||||
github.com/abema/go-mp4 v1.5.0
|
||||
github.com/alecthomas/kong v1.14.0
|
||||
github.com/asticode/go-astits v1.15.0
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.8
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.9
|
||||
github.com/bluenviron/gortmplib v0.3.0
|
||||
github.com/bluenviron/gortsplib/v5 v5.4.1-0.20260316202619-e63876e33be9
|
||||
github.com/bluenviron/mediacommon/v2 v2.8.2
|
||||
github.com/bluenviron/gortsplib/v5 v5.5.0
|
||||
github.com/bluenviron/mediacommon/v2 v2.8.3
|
||||
github.com/datarhei/gosrt v0.10.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gin-contrib/pprof v1.5.3
|
||||
|
||||
@@ -33,14 +33,14 @@ github.com/asticode/go-astits v1.15.0 h1:yRyCiUc8Jj4F7clt2GDxHghMpWuFL5rkaLuGUd2
|
||||
github.com/asticode/go-astits v1.15.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.8 h1:+KueZeydRDciIq2t3fwT4pshqN+Vreu4c2IMPLVx2Kk=
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.8/go.mod h1:c4b9vKhyksO6fHhWdCpuuczxUNFeZtBahlFiwrvW2oA=
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.9 h1:ahRbuBzw+FvdVDwli731RgJsErunyBDWhghpS5fT2qQ=
|
||||
github.com/bluenviron/gohlslib/v2 v2.2.9/go.mod h1:kA0hTg96hmTZjeZ/Vxwpu/Njy0emAoidEoDGQ/KlMH0=
|
||||
github.com/bluenviron/gortmplib v0.3.0 h1:jBNl7bYtXSq+USx70gQ4kzRsffM1h0XRjGwlHQHZVq8=
|
||||
github.com/bluenviron/gortmplib v0.3.0/go.mod h1:3tUsWceMOrs8Ylt5UyQEHNHCVl52V1gi9tz+IaLTafI=
|
||||
github.com/bluenviron/gortsplib/v5 v5.4.1-0.20260316202619-e63876e33be9 h1:BSz3Mt+1AqqFtB0zaScPCf3lrHr9L8aQ4XtBFWgrcxU=
|
||||
github.com/bluenviron/gortsplib/v5 v5.4.1-0.20260316202619-e63876e33be9/go.mod h1:4K3mXlLQ3vN4AyQKtxQE8T5I0Fr1KTNanBCzxDBJKug=
|
||||
github.com/bluenviron/mediacommon/v2 v2.8.2 h1:ZHuxthfO8o4/fhlG6JvoK5lT27tMWw4k5efpJlpu0yE=
|
||||
github.com/bluenviron/mediacommon/v2 v2.8.2/go.mod h1:4AsE74EnTxkHeUs1VMER31fivU0jufZUAepaKFRV1lM=
|
||||
github.com/bluenviron/gortsplib/v5 v5.5.0 h1:2wlUKhlSw1ptKEVnkEuZMpQuN8Xt311Fdd/SB124JPA=
|
||||
github.com/bluenviron/gortsplib/v5 v5.5.0/go.mod h1:otcPqR836QZej/EYx7njn8vl4TLj8Ya3QAf+GBwh2cQ=
|
||||
github.com/bluenviron/mediacommon/v2 v2.8.3 h1:T6xb7ZK3eBixi/HynzhtGRCEIrazwcmGIeu0WDTVISY=
|
||||
github.com/bluenviron/mediacommon/v2 v2.8.3/go.mod h1:CsYjGgzIz8RbloQf4BHR4uReogZsB4PEKWfePVIzJv8=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
|
||||
@@ -661,6 +661,12 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) {
|
||||
}
|
||||
return defs.MediasToCodecs(pa.stream.Desc.Medias)
|
||||
}(),
|
||||
Tracks2: func() []defs.APIPathTrack {
|
||||
if !pa.isAvailable() {
|
||||
return []defs.APIPathTrack{}
|
||||
}
|
||||
return defs.MediasToTracks(pa.stream.Desc.Medias)
|
||||
}(),
|
||||
InboundBytes: func() uint64 {
|
||||
if !pa.isAvailable() {
|
||||
return 0
|
||||
|
||||
@@ -72,7 +72,8 @@ type APIPath struct {
|
||||
Online bool `json:"online"`
|
||||
OnlineTime *time.Time `json:"onlineTime"`
|
||||
Source *APIPathSource `json:"source"`
|
||||
Tracks []APIPathTrackCodec `json:"tracks"`
|
||||
Tracks []APIPathTrackCodec `json:"tracks" deprecated:"true"`
|
||||
Tracks2 []APIPathTrack `json:"tracks2"`
|
||||
Readers []APIPathReader `json:"readers"`
|
||||
InboundBytes uint64 `json:"inboundBytes"`
|
||||
OutboundBytes uint64 `json:"outboundBytes"`
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package defs
|
||||
|
||||
// APIPathTrack is a track.
|
||||
type APIPathTrack struct {
|
||||
Codec APIPathTrackCodec `json:"codec"`
|
||||
CodecProps APIPathTrackCodecProps `json:"codecProps"`
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package defs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"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.
|
||||
@@ -37,65 +42,239 @@ const (
|
||||
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 {
|
||||
switch forma.(type) {
|
||||
// video
|
||||
case *format.AV1:
|
||||
ret[i] = APIPathTrackCodecAV1
|
||||
case *format.VP9:
|
||||
ret[i] = APIPathTrackCodecVP9
|
||||
case *format.VP8:
|
||||
ret[i] = APIPathTrackCodecVP8
|
||||
case *format.H265:
|
||||
ret[i] = APIPathTrackCodecH265
|
||||
case *format.H264:
|
||||
ret[i] = APIPathTrackCodecH264
|
||||
case *format.MPEG4Video:
|
||||
ret[i] = APIPathTrackCodecMPEG4Video
|
||||
case *format.MPEG1Video:
|
||||
ret[i] = APIPathTrackCodecMPEG1Video
|
||||
case *format.MJPEG:
|
||||
ret[i] = APIPathTrackCodecMJPEG
|
||||
// audio
|
||||
case *format.Opus:
|
||||
ret[i] = APIPathTrackCodecOpus
|
||||
case *format.Vorbis:
|
||||
ret[i] = APIPathTrackCodecVorbis
|
||||
case *format.MPEG4Audio:
|
||||
ret[i] = APIPathTrackCodecMPEG4Audio
|
||||
case *format.MPEG4AudioLATM:
|
||||
ret[i] = APIPathTrackCodecMPEG4AudioLATM
|
||||
case *format.MPEG1Audio:
|
||||
ret[i] = APIPathTrackCodecMPEG1Audio
|
||||
case *format.AC3:
|
||||
ret[i] = APIPathTrackCodecAC3
|
||||
case *format.Speex:
|
||||
ret[i] = APIPathTrackCodecSpeex
|
||||
case *format.G726:
|
||||
ret[i] = APIPathTrackCodecG726
|
||||
case *format.G722:
|
||||
ret[i] = APIPathTrackCodecG722
|
||||
case *format.G711:
|
||||
ret[i] = APIPathTrackCodecG711
|
||||
case *format.LPCM:
|
||||
ret[i] = APIPathTrackCodecLPCM
|
||||
// other
|
||||
case *format.MPEGTS:
|
||||
ret[i] = APIPathTrackCodecMPEGTS
|
||||
case *format.KLV:
|
||||
ret[i] = APIPathTrackCodecKLV
|
||||
default:
|
||||
ret[i] = APIPathTrackCodecGeneric
|
||||
}
|
||||
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 {
|
||||
@@ -120,3 +299,19 @@ func gatherFormats(medias []*description.Media) []format.Format {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package defs
|
||||
|
||||
// APIPathTrackCodecProps contains codec-specific properties.
|
||||
type APIPathTrackCodecProps interface {
|
||||
apiPathTrackCodecProps()
|
||||
}
|
||||
|
||||
// APIPathTrackCodecPropsAV1 contains codec-specific properties of AV1.
|
||||
type APIPathTrackCodecPropsAV1 struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Profile int `json:"profile"`
|
||||
Level int `json:"level"`
|
||||
Tier int `json:"tier"`
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsAV1) apiPathTrackCodecProps() {}
|
||||
|
||||
// APIPathTrackCodecPropsVP9 contains codec-specific properties of VP9.
|
||||
type APIPathTrackCodecPropsVP9 struct {
|
||||
Profile int `json:"profile"`
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsVP9) apiPathTrackCodecProps() {}
|
||||
|
||||
// APIPathTrackCodecPropsH265 contains codec-specific properties of H265.
|
||||
type APIPathTrackCodecPropsH265 struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Profile string `json:"profile"`
|
||||
Level string `json:"level"`
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsH265) apiPathTrackCodecProps() {}
|
||||
|
||||
// APIPathTrackCodecPropsH264 contains codec-specific properties of H264.
|
||||
type APIPathTrackCodecPropsH264 struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Profile string `json:"profile"`
|
||||
Level string `json:"level"`
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsH264) apiPathTrackCodecProps() {}
|
||||
|
||||
// APIPathTrackCodecPropsOpus contains codec-specific properties of Opus.
|
||||
type APIPathTrackCodecPropsOpus struct {
|
||||
ChannelCount int `json:"channelCount"`
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsOpus) apiPathTrackCodecProps() {}
|
||||
|
||||
// APIPathTrackCodecPropsMPEG4Audio contains codec-specific properties of MPEG4Audio.
|
||||
type APIPathTrackCodecPropsMPEG4Audio struct {
|
||||
SampleRate int `json:"sampleRate"`
|
||||
ChannelCount int `json:"channelCount"`
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsMPEG4Audio) apiPathTrackCodecProps() {}
|
||||
|
||||
// APIPathTrackCodecPropsAC3 contains codec-specific properties of AC3.
|
||||
type APIPathTrackCodecPropsAC3 struct {
|
||||
SampleRate int `json:"sampleRate"`
|
||||
ChannelCount int `json:"channelCount"`
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsAC3) apiPathTrackCodecProps() {}
|
||||
|
||||
// APIPathTrackCodecPropsG711 contains codec-specific properties of G711.
|
||||
type APIPathTrackCodecPropsG711 struct {
|
||||
MULaw bool `json:"muLaw"`
|
||||
SampleRate int `json:"sampleRate"`
|
||||
ChannelCount int `json:"channelCount"`
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsG711) apiPathTrackCodecProps() {}
|
||||
|
||||
// APIPathTrackCodecPropsLPCM contains codec-specific properties of LPCM.
|
||||
type APIPathTrackCodecPropsLPCM struct {
|
||||
BitDepth int `json:"bitDepth"`
|
||||
SampleRate int `json:"sampleRate"`
|
||||
ChannelCount int `json:"channelCount"`
|
||||
}
|
||||
|
||||
func (*APIPathTrackCodecPropsLPCM) apiPathTrackCodecProps() {}
|
||||
@@ -3,10 +3,68 @@ 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{},
|
||||
@@ -58,3 +116,91 @@ func TestFormatsToCodecs(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -77,6 +77,14 @@ func goStructToApi(t *testing.T, rt reflect.Type) openAPIProperty {
|
||||
return openAPIProperty{Ref: "#/components/schemas/" + schemaName(rt)}
|
||||
}
|
||||
|
||||
if rt == reflect.TypeOf((*defs.APIPathTrackCodecProps)(nil)).Elem() {
|
||||
return openAPIProperty{
|
||||
Type: "object",
|
||||
AllOf: []openAPIProperty{{Ref: "#/components/schemas/" + schemaName(rt)}},
|
||||
Nullable: true,
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case rt == reflect.TypeOf(""):
|
||||
return openAPIProperty{Type: "string"}
|
||||
@@ -367,6 +375,10 @@ func TestGo2API(t *testing.T) {
|
||||
"PathSource",
|
||||
defs.APIPathSource{},
|
||||
},
|
||||
{
|
||||
"PathTrack",
|
||||
defs.APIPathTrack{},
|
||||
},
|
||||
{
|
||||
"Recording",
|
||||
defs.APIRecording{},
|
||||
|
||||
Reference in New Issue
Block a user