@@ -112,6 +112,11 @@ func TestConfigPathsAdd(t *testing.T) {
|
||||
"sourceOnDemand": true,
|
||||
"disablePublisherOverride": true, // test setting a deprecated parameter
|
||||
"rpiCameraVFlip": true,
|
||||
"rpiCameraCodec": "hardwareH264",
|
||||
"rpiCameraIDRPeriod": 120,
|
||||
"rpiCameraBitrate": 3000000,
|
||||
"rpiCameraH264Profile": "main",
|
||||
"rpiCameraH264Level": "4.2",
|
||||
}, nil)
|
||||
|
||||
var out map[string]any
|
||||
@@ -120,6 +125,11 @@ func TestConfigPathsAdd(t *testing.T) {
|
||||
require.Equal(t, true, out["sourceOnDemand"])
|
||||
require.Equal(t, true, out["disablePublisherOverride"])
|
||||
require.Equal(t, true, out["rpiCameraVFlip"])
|
||||
require.Equal(t, "hardwareH264", out["rpiCameraCodec"])
|
||||
require.Equal(t, float64(120), out["rpiCameraIDRPeriod"])
|
||||
require.Equal(t, float64(3000000), out["rpiCameraBitrate"])
|
||||
require.Equal(t, "main", out["rpiCameraH264Profile"])
|
||||
require.Equal(t, "4.2", out["rpiCameraH264Level"])
|
||||
}
|
||||
|
||||
func TestConfigPathsAddUnknownField(t *testing.T) { //nolint:dupl
|
||||
@@ -185,12 +195,20 @@ func TestConfigPathsPatch(t *testing.T) { //nolint:dupl
|
||||
"sourceOnDemand": true,
|
||||
"disablePublisherOverride": true, // test setting a deprecated parameter
|
||||
"rpiCameraVFlip": true,
|
||||
"rpiCameraCodec": "hardwareH264",
|
||||
"rpiCameraIDRPeriod": 120,
|
||||
"rpiCameraBitrate": 3000000,
|
||||
"rpiCameraH264Profile": "main",
|
||||
"rpiCameraH264Level": "4.2",
|
||||
}, nil)
|
||||
|
||||
httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/paths/patch/my/path",
|
||||
map[string]any{
|
||||
"source": "rtsp://127.0.0.1:9998/mypath",
|
||||
"sourceOnDemand": true,
|
||||
"source": "rtsp://127.0.0.1:9998/mypath",
|
||||
"sourceOnDemand": true,
|
||||
"rpiCameraCodec": "softwareH264",
|
||||
"rpiCameraBitrate": 4000000,
|
||||
"rpiCameraH264Level": "4.0",
|
||||
}, nil)
|
||||
|
||||
var out map[string]any
|
||||
@@ -199,6 +217,11 @@ func TestConfigPathsPatch(t *testing.T) { //nolint:dupl
|
||||
require.Equal(t, true, out["sourceOnDemand"])
|
||||
require.Equal(t, true, out["disablePublisherOverride"])
|
||||
require.Equal(t, true, out["rpiCameraVFlip"])
|
||||
require.Equal(t, "softwareH264", out["rpiCameraCodec"])
|
||||
require.Equal(t, float64(120), out["rpiCameraIDRPeriod"])
|
||||
require.Equal(t, float64(4000000), out["rpiCameraBitrate"])
|
||||
require.Equal(t, "main", out["rpiCameraH264Profile"])
|
||||
require.Equal(t, "4.0", out["rpiCameraH264Level"])
|
||||
}
|
||||
|
||||
func TestConfigPathsReplace(t *testing.T) { //nolint:dupl
|
||||
@@ -226,6 +249,11 @@ func TestConfigPathsReplace(t *testing.T) { //nolint:dupl
|
||||
"sourceOnDemand": true,
|
||||
"disablePublisherOverride": true, // test setting a deprecated parameter
|
||||
"rpiCameraVFlip": true,
|
||||
"rpiCameraCodec": "hardwareH264",
|
||||
"rpiCameraIDRPeriod": 120,
|
||||
"rpiCameraBitrate": 3000000,
|
||||
"rpiCameraH264Profile": "main",
|
||||
"rpiCameraH264Level": "4.2",
|
||||
}, nil)
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/replace/my/path",
|
||||
@@ -240,6 +268,11 @@ func TestConfigPathsReplace(t *testing.T) { //nolint:dupl
|
||||
require.Equal(t, true, out["sourceOnDemand"])
|
||||
require.Equal(t, nil, out["disablePublisherOverride"])
|
||||
require.Equal(t, false, out["rpiCameraVFlip"])
|
||||
require.Equal(t, "auto", out["rpiCameraCodec"])
|
||||
require.Equal(t, float64(60), out["rpiCameraIDRPeriod"])
|
||||
require.Equal(t, float64(5000000), out["rpiCameraBitrate"])
|
||||
require.Equal(t, "auto", out["rpiCameraH264Profile"])
|
||||
require.Equal(t, "4.1", out["rpiCameraH264Level"])
|
||||
}
|
||||
|
||||
func TestConfigPathsReplaceNonExisting(t *testing.T) { //nolint:dupl
|
||||
|
||||
+16
-12
@@ -323,9 +323,14 @@ type Path struct {
|
||||
RPICameraJPEGQuality *uint `json:"rpiCameraJPEGQuality,omitempty" deprecated:"true"`
|
||||
RPICameraMJPEGQuality uint `json:"rpiCameraMJPEGQuality"`
|
||||
RPICameraPrimaryName string `json:"-"` // filled by Validate()
|
||||
RPICameraSecondaryCodec string `json:"-"` // filled by Validate()
|
||||
RPICameraSecondaryWidth uint `json:"-"` // filled by Validate()
|
||||
RPICameraSecondaryHeight uint `json:"-"` // filled by Validate()
|
||||
RPICameraSecondaryFPS float64 `json:"-"` // filled by Validate()
|
||||
RPICameraSecondaryIDRPeriod uint `json:"-"` // filled by Validate()
|
||||
RPICameraSecondaryBitrate uint `json:"-"` // filled by Validate()
|
||||
RPICameraSecondaryH264Profile string `json:"-"` // filled by Validate()
|
||||
RPICameraSecondaryH264Level string `json:"-"` // filled by Validate()
|
||||
RPICameraSecondaryMJPEGQuality uint `json:"-"` // filled by Validate()
|
||||
|
||||
// Hooks
|
||||
@@ -699,13 +704,13 @@ func (pconf *Path) validate(
|
||||
pconf.RPICameraMJPEGQuality = *pconf.RPICameraJPEGQuality
|
||||
}
|
||||
|
||||
if !pconf.RPICameraSecondary {
|
||||
switch pconf.RPICameraCodec {
|
||||
case "auto", "hardwareH264", "softwareH264", "mjpeg":
|
||||
default:
|
||||
return fmt.Errorf("supported codecs for a primary RPI Camera stream are auto, hardwareH264, softwareH264, mjpeg")
|
||||
}
|
||||
switch pconf.RPICameraCodec {
|
||||
case "auto", "hardwareH264", "softwareH264", "mjpeg":
|
||||
default:
|
||||
return fmt.Errorf("supported codecs for a RPI Camera stream are auto, hardwareH264, softwareH264, mjpeg")
|
||||
}
|
||||
|
||||
if !pconf.RPICameraSecondary {
|
||||
for otherName, otherPath := range conf.Paths {
|
||||
if otherPath != pconf &&
|
||||
otherPath != nil &&
|
||||
@@ -717,12 +722,6 @@ func (pconf *Path) validate(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch pconf.RPICameraCodec {
|
||||
case "auto", "mjpeg":
|
||||
default:
|
||||
return fmt.Errorf("supported codecs for a secondary RPI Camera stream are auto, mjpeg")
|
||||
}
|
||||
|
||||
var primaryName string
|
||||
var primary *Path
|
||||
|
||||
@@ -751,6 +750,11 @@ func (pconf *Path) validate(
|
||||
primary.RPICameraSecondaryHeight = pconf.RPICameraHeight
|
||||
primary.RPICameraSecondaryFPS = pconf.RPICameraFPS
|
||||
primary.RPICameraSecondaryMJPEGQuality = pconf.RPICameraMJPEGQuality
|
||||
primary.RPICameraSecondaryCodec = pconf.RPICameraCodec
|
||||
primary.RPICameraSecondaryIDRPeriod = pconf.RPICameraIDRPeriod
|
||||
primary.RPICameraSecondaryBitrate = pconf.RPICameraBitrate
|
||||
primary.RPICameraSecondaryH264Profile = pconf.RPICameraH264Profile
|
||||
primary.RPICameraSecondaryH264Level = pconf.RPICameraH264Level
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -52,9 +52,14 @@ type cameraParams struct {
|
||||
H264Level string
|
||||
MJPEGQuality uint32
|
||||
|
||||
SecondaryCodec string
|
||||
SecondaryWidth uint32
|
||||
SecondaryHeight uint32
|
||||
SecondaryFPS float32
|
||||
SecondaryIDRPeriod uint32
|
||||
SecondaryBitrate uint32
|
||||
SecondaryH264Profile string
|
||||
SecondaryH264Level string
|
||||
SecondaryMJPEGQuality uint32
|
||||
}
|
||||
|
||||
@@ -149,9 +154,31 @@ func (p *cameraParams) fromConf(logLevel conf.LogLevel, cnf *conf.Path) {
|
||||
|
||||
p.MJPEGQuality = uint32(cnf.RPICameraMJPEGQuality)
|
||||
|
||||
p.SecondaryCodec = func() string {
|
||||
if cnf.RPICameraSecondaryCodec == "auto" {
|
||||
return "mjpeg"
|
||||
}
|
||||
return cnf.RPICameraSecondaryCodec
|
||||
}()
|
||||
|
||||
p.SecondaryWidth = uint32(cnf.RPICameraSecondaryWidth)
|
||||
p.SecondaryHeight = uint32(cnf.RPICameraSecondaryHeight)
|
||||
p.SecondaryFPS = float32(cnf.RPICameraSecondaryFPS)
|
||||
p.SecondaryIDRPeriod = uint32(cnf.RPICameraSecondaryIDRPeriod)
|
||||
p.SecondaryBitrate = uint32(cnf.RPICameraSecondaryBitrate)
|
||||
|
||||
p.SecondaryH264Profile = func() string {
|
||||
if cnf.RPICameraSecondaryH264Profile == "auto" {
|
||||
if p.SecondaryCodec == "hardwareH264" {
|
||||
return "main"
|
||||
}
|
||||
return "baseline"
|
||||
}
|
||||
|
||||
return cnf.RPICameraSecondaryH264Profile
|
||||
}()
|
||||
|
||||
p.SecondaryH264Level = cnf.RPICameraSecondaryH264Level
|
||||
p.SecondaryMJPEGQuality = uint32(cnf.RPICameraSecondaryMJPEGQuality)
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
22aa13d2506cbfab4369114aca23643a2b5f0b2df681ef4407d4cd358973a2c7
|
||||
1ef561be0f00c745871b936bf86f6c6ca973a752ee335a2ec33f3ae05187adb3
|
||||
|
||||
@@ -1 +1 @@
|
||||
6c58145ec21aae6a362e233e17e2fd93aa778848b5462848861128310270b3aa
|
||||
890f0eee1889f279e904ea669b67d2c54d9872f0cb68a17f1c4fa574a5b61a82
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.7.0
|
||||
v2.8.0
|
||||
|
||||
@@ -51,9 +51,12 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||
}
|
||||
|
||||
func (s *Source) runPrimary(params defs.StaticSourceRunParams) error {
|
||||
var p cameraParams
|
||||
p.fromConf(s.LogLevel, params.Conf)
|
||||
|
||||
var forma format.Format
|
||||
|
||||
if params.Conf.RPICameraCodec == "auto" || params.Conf.RPICameraCodec == "hardwareH264" || params.Conf.RPICameraCodec == "softwareH264" {
|
||||
if p.Codec == "hardwareH264" || p.Codec == "softwareH264" {
|
||||
forma = &format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
@@ -86,7 +89,7 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error {
|
||||
|
||||
var encode func(au []byte) ([]*rtp.Packet, error)
|
||||
|
||||
if params.Conf.RPICameraCodec == "auto" || params.Conf.RPICameraCodec == "hardwareH264" || params.Conf.RPICameraCodec == "softwareH264" {
|
||||
if p.Codec == "hardwareH264" || p.Codec == "softwareH264" {
|
||||
encH264 := &rtph264.Encoder{
|
||||
PayloadType: 96,
|
||||
PayloadMaxSize: s.RTPMaxPayloadSize,
|
||||
@@ -159,18 +162,46 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error {
|
||||
var onDataSecondary func(pts int64, ntp time.Time, au []byte)
|
||||
|
||||
if params.Conf.RPICameraSecondaryWidth != 0 {
|
||||
secondaryEncMJPEG := &rtpmjpeg.Encoder{
|
||||
PayloadMaxSize: s.RTPMaxPayloadSize,
|
||||
}
|
||||
err := secondaryEncMJPEG.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
var encodeSecondary func(au []byte) ([]*rtp.Packet, error)
|
||||
|
||||
if p.SecondaryCodec == "hardwareH264" || p.SecondaryCodec == "softwareH264" {
|
||||
secondaryEncH264 := &rtph264.Encoder{
|
||||
PayloadType: 96,
|
||||
PayloadMaxSize: s.RTPMaxPayloadSize,
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
err := secondaryEncH264.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
encodeSecondary = func(au []byte) ([]*rtp.Packet, error) {
|
||||
var nalus h264.AnnexB
|
||||
err = nalus.Unmarshal(au)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secondaryEncH264.Encode(nalus)
|
||||
}
|
||||
} else {
|
||||
secondaryEncMJPEG := &rtpmjpeg.Encoder{
|
||||
PayloadMaxSize: s.RTPMaxPayloadSize,
|
||||
}
|
||||
err := secondaryEncMJPEG.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
encodeSecondary = func(au []byte) ([]*rtp.Packet, error) {
|
||||
return secondaryEncMJPEG.Encode(au)
|
||||
}
|
||||
}
|
||||
|
||||
onDataSecondary = func(pts int64, ntp time.Time, au []byte) {
|
||||
initializeSubStream()
|
||||
|
||||
pkts, err2 := secondaryEncMJPEG.Encode(au)
|
||||
pkts, err2 := encodeSecondary(au)
|
||||
if err2 != nil {
|
||||
s.Log(logger.Error, err2.Error())
|
||||
return
|
||||
@@ -178,7 +209,6 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error {
|
||||
|
||||
for _, pkt := range pkts {
|
||||
pkt.Timestamp = uint32(pts)
|
||||
pkt.PayloadType = 96
|
||||
subStream.WriteUnit(mediaSecondary, mediaSecondary.Formats[0], &unit.Unit{
|
||||
PTS: pts,
|
||||
NTP: ntp,
|
||||
@@ -194,9 +224,6 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error {
|
||||
}
|
||||
}()
|
||||
|
||||
var p cameraParams
|
||||
p.fromConf(s.LogLevel, params.Conf)
|
||||
|
||||
cam := &camera{
|
||||
params: p,
|
||||
onData: onData,
|
||||
@@ -230,6 +257,9 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error {
|
||||
}
|
||||
|
||||
func (s *Source) runSecondary(params defs.StaticSourceRunParams) error {
|
||||
var p cameraParams
|
||||
p.fromConf(s.LogLevel, params.Conf)
|
||||
|
||||
r := &secondaryReader{}
|
||||
r.ctx, r.ctxCancel = context.WithCancel(context.Background())
|
||||
defer r.ctxCancel()
|
||||
@@ -241,9 +271,19 @@ func (s *Source) runSecondary(params defs.StaticSourceRunParams) error {
|
||||
|
||||
defer path.RemoveReader(defs.PathRemoveReaderReq{Author: r})
|
||||
|
||||
var forma format.Format
|
||||
if p.Codec == "hardwareH264" || p.Codec == "softwareH264" {
|
||||
forma = &format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
} else {
|
||||
forma = &format.MJPEG{}
|
||||
}
|
||||
|
||||
media := &description.Media{
|
||||
Type: description.MediaTypeVideo,
|
||||
Formats: []format.Format{&format.MJPEG{}},
|
||||
Formats: []format.Format{forma},
|
||||
}
|
||||
|
||||
res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
|
||||
@@ -260,18 +300,15 @@ func (s *Source) runSecondary(params defs.StaticSourceRunParams) error {
|
||||
primaryStream.OrigDesc.Medias[1],
|
||||
primaryStream.OrigDesc.Medias[1].Formats[0],
|
||||
func(u *unit.Unit) error {
|
||||
pkt := u.RTPPackets[0]
|
||||
|
||||
newPkt := &rtp.Packet{
|
||||
Header: pkt.Header,
|
||||
Payload: pkt.Payload,
|
||||
clone := *u.RTPPackets[0]
|
||||
if p.Codec != "hardwareH264" && p.Codec != "softwareH264" {
|
||||
clone.PayloadType = 26
|
||||
}
|
||||
newPkt.PayloadType = 26
|
||||
|
||||
res.SubStream.WriteUnit(media, media.Formats[0], &unit.Unit{
|
||||
PTS: u.PTS,
|
||||
NTP: u.NTP,
|
||||
RTPPackets: []*rtp.Packet{newPkt},
|
||||
RTPPackets: []*rtp.Packet{&clone},
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user