From f13aa21d0f83204f23050981f9d390523c356977 Mon Sep 17 00:00:00 2001 From: seydx Date: Sun, 3 Nov 2024 16:33:08 +0100 Subject: [PATCH 1/9] Add backchannel support for rtsp server --- internal/rtsp/rtsp.go | 18 ++++++++++++++++++ pkg/core/media.go | 4 ++++ pkg/rtsp/producer.go | 44 +++++++++++++++++++++++++++++-------------- pkg/rtsp/server.go | 29 +++++++++++++++++++++++----- 4 files changed, 76 insertions(+), 19 deletions(-) diff --git a/internal/rtsp/rtsp.go b/internal/rtsp/rtsp.go index 230bdece..377061e5 100644 --- a/internal/rtsp/rtsp.go +++ b/internal/rtsp/rtsp.go @@ -8,6 +8,7 @@ import ( "github.com/AlexxIT/go2rtc/internal/app" "github.com/AlexxIT/go2rtc/internal/streams" + "github.com/AlexxIT/go2rtc/pkg/aac" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/rtsp" "github.com/AlexxIT/go2rtc/pkg/tcp" @@ -184,6 +185,23 @@ func tcpHandler(conn *rtsp.Conn) { } } + if query.Get("backchannel") == "1" { + conn.Medias = append(conn.Medias, &core.Media{ + Kind: core.KindAudio, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{ + {Name: core.CodecOpus, ClockRate: 48000, Channels: 2}, + {Name: core.CodecPCM, ClockRate: 16000}, + {Name: core.CodecPCMA, ClockRate: 16000}, + {Name: core.CodecPCMU, ClockRate: 16000}, + {Name: core.CodecPCM, ClockRate: 8000}, + {Name: core.CodecPCMA, ClockRate: 8000}, + {Name: core.CodecPCMU, ClockRate: 8000}, + {Name: core.CodecAAC, ClockRate: 16000, FmtpLine: aac.FMTP + "1408"}, + }, + }) + } + if s := query.Get("pkt_size"); s != "" { conn.PacketSize = uint16(core.Atoi(s)) } diff --git a/pkg/core/media.go b/pkg/core/media.go index 72ab58c6..a700bb62 100644 --- a/pkg/core/media.go +++ b/pkg/core/media.go @@ -141,6 +141,10 @@ func MarshalSDP(name string, medias []*Media) ([]byte, error) { } md.WithCodec(codec.PayloadType, name, codec.ClockRate, codec.Channels, codec.FmtpLine) + if media.Direction != "" { + md.WithPropertyAttribute(media.Direction) + } + if media.ID != "" { md.WithValueAttribute("control", media.ID) } diff --git a/pkg/rtsp/producer.go b/pkg/rtsp/producer.go index de115808..323d9197 100644 --- a/pkg/rtsp/producer.go +++ b/pkg/rtsp/producer.go @@ -16,27 +16,43 @@ func (c *Conn) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, e } } - c.stateMu.Lock() - defer c.stateMu.Unlock() + switch c.mode { + case core.ModeActiveProducer: + c.stateMu.Lock() + defer c.stateMu.Unlock() - if c.state == StatePlay { - if err := c.Reconnect(); err != nil { + if c.state == StatePlay { + if err := c.Reconnect(); err != nil { + return nil, err + } + } + + channel, err := c.SetupMedia(media) + if err != nil { return nil, err } - } - channel, err := c.SetupMedia(media) - if err != nil { - return nil, err - } + c.state = StateSetup - c.state = StateSetup + track := core.NewReceiver(media, codec) + track.ID = channel + c.Receivers = append(c.Receivers, track) - track := core.NewReceiver(media, codec) - track.ID = channel - c.Receivers = append(c.Receivers, track) + return track, nil + case core.ModePassiveConsumer: + // Backchannel + c.stateMu.Lock() + defer c.stateMu.Unlock() - return track, nil + channel := byte(len(c.Senders)) * 2 + track := core.NewReceiver(media, codec) + track.ID = channel + c.Receivers = append(c.Receivers, track) + + return track, nil + default: + return nil, errors.New("rtsp: wrong mode for GetTrack") + } } func (c *Conn) Start() (err error) { diff --git a/pkg/rtsp/server.go b/pkg/rtsp/server.go index 7953b0dc..1cddbec5 100644 --- a/pkg/rtsp/server.go +++ b/pkg/rtsp/server.go @@ -129,6 +129,16 @@ func (c *Conn) Accept() error { medias = append(medias, media) } + for i, track := range c.Receivers { + media := &core.Media{ + Kind: core.GetKind(track.Codec.Name), + Direction: core.DirectionSendonly, + Codecs: []*core.Codec{track.Codec}, + ID: "trackID=" + strconv.Itoa(i+len(c.Senders)), + } + medias = append(medias, media) + } + res.Body, err = core.MarshalSDP(c.SessionName, medias) if err != nil { return err @@ -154,11 +164,20 @@ func (c *Conn) Accept() error { c.state = StateSetup if c.mode == core.ModePassiveConsumer { - if i := reqTrackID(req); i >= 0 && i < len(c.Senders) { - // mark sender as SETUP - c.Senders[i].Media.ID = MethodSetup - tr = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", i*2, i*2+1) - res.Header.Set("Transport", tr) + trackID := reqTrackID(req) + + if trackID >= 0 { + if trackID < len(c.Senders) { + c.Senders[trackID].Media.ID = MethodSetup + tr = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", trackID*2, trackID*2+1) + res.Header.Set("Transport", tr) + } else if trackID >= len(c.Senders) && trackID < len(c.Senders)+len(c.Receivers) { + c.Receivers[trackID-len(c.Senders)].Media.ID = MethodSetup + tr = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", trackID*2, trackID*2+1) + res.Header.Set("Transport", tr) + } else { + res.Status = "400 Bad Request" + } } else { res.Status = "400 Bad Request" } From a8edaedc8b7518507eb1a5720d9963639a056b13 Mon Sep 17 00:00:00 2001 From: Alex X Date: Thu, 14 Nov 2024 19:39:26 +0300 Subject: [PATCH 2/9] Fix broken incoming sources after v1.9.7 #1458 --- internal/streams/play.go | 2 +- internal/streams/stream.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/streams/play.go b/internal/streams/play.go index 7ada66e6..9bec7258 100644 --- a/internal/streams/play.go +++ b/internal/streams/play.go @@ -103,7 +103,7 @@ func (s *Stream) Play(source string) error { } func (s *Stream) AddInternalProducer(conn core.Producer) { - producer := &Producer{conn: conn, state: stateInternal} + producer := &Producer{conn: conn, state: stateInternal, url: "internal"} s.mu.Lock() s.producers = append(s.producers, producer) s.mu.Unlock() diff --git a/internal/streams/stream.go b/internal/streams/stream.go index e194e0ac..569e63ee 100644 --- a/internal/streams/stream.go +++ b/internal/streams/stream.go @@ -76,7 +76,7 @@ func (s *Stream) RemoveConsumer(cons core.Consumer) { } func (s *Stream) AddProducer(prod core.Producer) { - producer := &Producer{conn: prod, state: stateExternal} + producer := &Producer{conn: prod, state: stateExternal, url: "external"} s.mu.Lock() s.producers = append(s.producers, producer) s.mu.Unlock() From ece49a158e0f326decbafc1a1172d38ad844ed96 Mon Sep 17 00:00:00 2001 From: Alex X Date: Sun, 26 Jan 2025 16:09:50 +0300 Subject: [PATCH 3/9] Add support H264, H265, NV12 for V4L2 source #1546 --- pkg/h264/annexb/annexb_test.go | 12 ++++++++++++ pkg/v4l2/device/device.go | 26 +++++++++++++++++--------- pkg/v4l2/device/formats.go | 26 ++++++++++++++++++++++++-- pkg/v4l2/producer.go | 33 +++++++++++++++++++++++++++------ 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/pkg/h264/annexb/annexb_test.go b/pkg/h264/annexb/annexb_test.go index 7220f570..cbc382fe 100644 --- a/pkg/h264/annexb/annexb_test.go +++ b/pkg/h264/annexb/annexb_test.go @@ -83,3 +83,15 @@ func TestDahua(t *testing.T) { n := naluTypes(b) require.Equal(t, []byte{0x40, 0x42, 0x44, 0x26}, n) } + +func TestUSB(t *testing.T) { + s := "00 00 00 01 67 4D 00 1F 8D 8D 40 28 02 DD 37 01 01 01 40 00 01 C2 00 00 57 E4 01 00 00 00 01 68 EE 3C 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 65 88 80 00" + b := EncodeToAVCC(decode(s)) + n := naluTypes(b) + require.Equal(t, []byte{0x67, 0x68, 0x65}, n) + + s = "00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 41 9A 00 4C" + b = EncodeToAVCC(decode(s)) + n = naluTypes(b) + require.Equal(t, []byte{0x41}, n) +} diff --git a/pkg/v4l2/device/device.go b/pkg/v4l2/device/device.go index 7f16fd23..c77d60f5 100644 --- a/pkg/v4l2/device/device.go +++ b/pkg/v4l2/device/device.go @@ -11,8 +11,9 @@ import ( ) type Device struct { - fd int - bufs [][]byte + fd int + bufs [][]byte + pixFmt uint32 } func Open(path string) (*Device, error) { @@ -119,6 +120,8 @@ func (d *Device) ListFrameRates(pixFmt, width, height uint32) ([]uint32, error) } func (d *Device) SetFormat(width, height, pixFmt uint32) error { + d.pixFmt = pixFmt + f := v4l2_format{ typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, pix: v4l2_pix_format{ @@ -196,7 +199,7 @@ func (d *Device) StreamOff() (err error) { return ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb)) } -func (d *Device) Capture(planarYUV bool) ([]byte, error) { +func (d *Device) Capture() ([]byte, error) { dec := v4l2_buffer{ typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, memory: V4L2_MEMORY_MMAP, @@ -205,11 +208,16 @@ func (d *Device) Capture(planarYUV bool) ([]byte, error) { return nil, err } - buf := make([]byte, dec.bytesused) - if planarYUV { - YUYV2YUV(buf, d.bufs[dec.index][:dec.bytesused]) - } else { - copy(buf, d.bufs[dec.index][:dec.bytesused]) + src := d.bufs[dec.index][:dec.bytesused] + dst := make([]byte, dec.bytesused) + + switch d.pixFmt { + case V4L2_PIX_FMT_YUYV: + YUYVtoYUV(dst, src) + case V4L2_PIX_FMT_NV12: + NV12toYUV(dst, src) + default: + copy(dst, d.bufs[dec.index][:dec.bytesused]) } enc := v4l2_buffer{ @@ -221,7 +229,7 @@ func (d *Device) Capture(planarYUV bool) ([]byte, error) { return nil, err } - return buf, nil + return dst, nil } func (d *Device) Close() error { diff --git a/pkg/v4l2/device/formats.go b/pkg/v4l2/device/formats.go index fb54bbd1..a0b41082 100644 --- a/pkg/v4l2/device/formats.go +++ b/pkg/v4l2/device/formats.go @@ -2,7 +2,10 @@ package device const ( V4L2_PIX_FMT_YUYV = 'Y' | 'U'<<8 | 'Y'<<16 | 'V'<<24 + V4L2_PIX_FMT_NV12 = 'N' | 'V'<<8 | '1'<<16 | '2'<<24 V4L2_PIX_FMT_MJPEG = 'M' | 'J'<<8 | 'P'<<16 | 'G'<<24 + V4L2_PIX_FMT_H264 = 'H' | '2'<<8 | '6'<<16 | '4'<<24 + V4L2_PIX_FMT_HEVC = 'H' | 'E'<<8 | 'V'<<16 | 'C'<<24 ) type Format struct { @@ -13,11 +16,13 @@ type Format struct { var Formats = []Format{ {V4L2_PIX_FMT_YUYV, "YUV 4:2:2", "yuyv422"}, + {V4L2_PIX_FMT_NV12, "Y/UV 4:2:0", "nv12"}, {V4L2_PIX_FMT_MJPEG, "Motion-JPEG", "mjpeg"}, + {V4L2_PIX_FMT_H264, "H.264", "h264"}, + {V4L2_PIX_FMT_HEVC, "HEVC", "hevc"}, } -// YUYV2YUV convert packed YUV to planar YUV -func YUYV2YUV(dst, src []byte) { +func YUYVtoYUV(dst, src []byte) { n := len(src) i0 := 0 iy := 0 @@ -38,3 +43,20 @@ func YUYV2YUV(dst, src []byte) { iv++ } } + +func NV12toYUV(dst, src []byte) { + n := len(src) + k := n / 6 + i0 := k * 4 + iu := i0 + iv := i0 + k + copy(dst, src[:i0]) // copy Y + for i0 < n { + dst[iu] = src[i0] + i0++ + iu++ + dst[iv] = src[i0] + i0++ + iv++ + } +} diff --git a/pkg/v4l2/producer.go b/pkg/v4l2/producer.go index 87199762..663d0a9e 100644 --- a/pkg/v4l2/producer.go +++ b/pkg/v4l2/producer.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/AlexxIT/go2rtc/pkg/v4l2/device" "github.com/pion/rtp" ) @@ -46,17 +47,29 @@ func Open(rawURL string) (*Producer, error) { } switch query.Get("input_format") { - case "mjpeg": - codec.Name = core.CodecJPEG - pixFmt = device.V4L2_PIX_FMT_MJPEG case "yuyv422": if codec.FmtpLine == "" { return nil, errors.New("v4l2: invalid video_size") } - codec.Name = core.CodecRAW codec.FmtpLine += ";colorspace=422" pixFmt = device.V4L2_PIX_FMT_YUYV + case "nv12": + if codec.FmtpLine == "" { + return nil, errors.New("v4l2: invalid video_size") + } + codec.Name = core.CodecRAW + codec.FmtpLine += ";colorspace=420mpeg2" // maybe 420jpeg + pixFmt = device.V4L2_PIX_FMT_NV12 + case "mjpeg": + codec.Name = core.CodecJPEG + pixFmt = device.V4L2_PIX_FMT_MJPEG + case "h264": + codec.Name = core.CodecH264 + pixFmt = device.V4L2_PIX_FMT_H264 + case "hevc": + codec.Name = core.CodecH265 + pixFmt = device.V4L2_PIX_FMT_HEVC default: return nil, errors.New("v4l2: invalid input_format") } @@ -93,10 +106,14 @@ func (c *Producer) Start() error { return err } - planarYUV := c.Medias[0].Codecs[0].Name == core.CodecRAW + var bitstream bool + switch c.Medias[0].Codecs[0].Name { + case core.CodecH264, core.CodecH265: + bitstream = true + } for { - buf, err := c.dev.Capture(planarYUV) + buf, err := c.dev.Capture() if err != nil { return err } @@ -107,6 +124,10 @@ func (c *Producer) Start() error { continue } + if bitstream { + buf = annexb.EncodeToAVCC(buf) + } + pkt := &rtp.Packet{ Header: rtp.Header{Timestamp: core.Now90000()}, Payload: buf, From 645c11f0bd8dc8308be566d0e5ab0af8a520b609 Mon Sep 17 00:00:00 2001 From: Alex X Date: Sun, 2 Feb 2025 11:01:44 +0300 Subject: [PATCH 4/9] Ignore unknown NAL unit types for RTP/H264 #1570 --- pkg/h264/rtp.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/h264/rtp.go b/pkg/h264/rtp.go index b4a9dafb..d093254f 100644 --- a/pkg/h264/rtp.go +++ b/pkg/h264/rtp.go @@ -22,7 +22,7 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { buf := make([]byte, 0, 512*1024) // 512K return func(packet *rtp.Packet) { - //log.Printf("[RTP] codec: %s, nalu: %2d, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d, %v", track.Codec.Name, packet.Payload[0]&0x1F, len(packet.Payload), packet.Timestamp, packet.PayloadType, packet.SSRC, packet.SequenceNumber, packet.Marker) + //log.Printf("[RTP] codec: %s, nalu: %2d, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d, %v", codec.Name, packet.Payload[0]&0x1F, len(packet.Payload), packet.Timestamp, packet.PayloadType, packet.SSRC, packet.SequenceNumber, packet.Marker) payload, err := depack.Unmarshal(packet.Payload) if len(payload) == 0 || err != nil { @@ -68,6 +68,9 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { payload = payload[i:] continue + case NALUTypePFrame, NALUTypeSPS, NALUTypePPS: // pass + default: + return // skip any unknown NAL unit type } break } From f9a8c1969c76aeb3840c4dbf643475df2081198b Mon Sep 17 00:00:00 2001 From: Alex X Date: Sun, 2 Feb 2025 14:46:37 +0300 Subject: [PATCH 5/9] Improve delay for MSE player --- www/video-rtc.js | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/www/video-rtc.js b/www/video-rtc.js index 52fb5dda..fb872b45 100644 --- a/www/video-rtc.js +++ b/www/video-rtc.js @@ -439,24 +439,30 @@ export class VideoRTC extends HTMLElement { const sb = ms.addSourceBuffer(msg.value); sb.mode = 'segments'; // segments or sequence sb.addEventListener('updateend', () => { - if (sb.updating) return; - - try { - if (bufLen > 0) { + if (!sb.updating && bufLen > 0) { + try { const data = buf.slice(0, bufLen); - bufLen = 0; sb.appendBuffer(data); - } else if (sb.buffered && sb.buffered.length) { - const end = sb.buffered.end(sb.buffered.length - 1) - 15; - const start = sb.buffered.start(0); - if (end > start) { - sb.remove(start, end); - ms.setLiveSeekableRange(end, end + 15); - } - // console.debug("VideoRTC.buffered", start, end); + bufLen = 0; + } catch (e) { + // console.debug(e); } - } catch (e) { - // console.debug(e); + } + + if (!sb.updating && sb.buffered && sb.buffered.length) { + const end = sb.buffered.end(sb.buffered.length - 1); + const start = end - 5; + const start0 = sb.buffered.start(0); + if (start > start0) { + sb.remove(start0, start); + ms.setLiveSeekableRange(start, end); + } + if (this.video.currentTime < start) { + this.video.currentTime = start; + } + const gap = end - this.video.currentTime; + this.video.playbackRate = gap > 0.1 ? gap : 0.1; + // console.debug('VideoRTC.buffered', gap, this.video.playbackRate, this.video.readyState); } }); @@ -468,7 +474,7 @@ export class VideoRTC extends HTMLElement { const b = new Uint8Array(data); buf.set(b, bufLen); bufLen += b.byteLength; - // console.debug("VideoRTC.buffer", b.byteLength, bufLen); + // console.debug('VideoRTC.buffer', b.byteLength, bufLen); } else { try { sb.appendBuffer(data); From 1b0db3c8b00449920c81d304f7de5b55745688ca Mon Sep 17 00:00:00 2001 From: Alex X Date: Sun, 2 Feb 2025 15:41:30 +0300 Subject: [PATCH 6/9] Add readme for V4L2 module --- internal/v4l2/README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 internal/v4l2/README.md diff --git a/internal/v4l2/README.md b/internal/v4l2/README.md new file mode 100644 index 00000000..1c5dd390 --- /dev/null +++ b/internal/v4l2/README.md @@ -0,0 +1,39 @@ +# V4L2 + +What you should to know about [V4L2](https://en.wikipedia.org/wiki/Video4Linux): + +- V4L2 (Video for Linux API version 2) works only in Linux +- supports USB cameras and other similar devices +- one device can only be connected to one software simultaneously +- cameras support a fixed list of formats, resolutions and frame rates +- basic cameras supports only RAW (non-compressed) pixel formats +- regular cameras supports MJPEG format (series of JPEG frames) +- advances cameras support H264 format (MSE/MP4, WebRTC compatible) +- using MJPEG and H264 formats (if the camera supports them) won't cost you the CPU usage +- transcoding RAW format to MJPEG or H264 - will cost you a significant CPU usage +- H265 (HEVC) format is also supported (if the camera supports it) + +Tests show that the basic Keenetic router with MIPS processor can broadcast three MJPEG cameras in the following resolutions: 1600х1200 + 640х480 + 640х480. The USB bus bandwidth is no more enough for larger resolutions. CPU consumption is no more than 5%. + +Supported formats for your camera can be found here: **Go2rtc > WebUI > Add > V4L2**. + +## RAW format + +Example: + +```yaml +streams: + camera1: v4l2:device?video=/dev/video0&input_format=yuyv422&video_size=1280x720&framerate=10 +``` + +Go2rtc supports built-in transcoding of RAW to MJPEG format. This does not need to be additionally configured. + +``` +ffplay http://localhost:1984/api/stream.mjpeg?src=camera1 +``` + +**Important.** You don't have to transcode the RAW format to transmit it over the network. You can stream it in `y4m` format, which is perfectly supported by ffmpeg. It won't cost you a CPU usage. But will require high network bandwidth. + +``` +ffplay http://localhost:1984/api/stream.y4m?src=camera1 +``` From ad8c025393e47454b59a398d7877360b9ed447ff Mon Sep 17 00:00:00 2001 From: seydx Date: Sun, 3 Nov 2024 16:33:08 +0100 Subject: [PATCH 7/9] Add backchannel support for rtsp server --- pkg/rtsp/server.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/rtsp/server.go b/pkg/rtsp/server.go index df2ebdb5..b7e65dac 100644 --- a/pkg/rtsp/server.go +++ b/pkg/rtsp/server.go @@ -139,6 +139,16 @@ func (c *Conn) Accept() error { medias = append(medias, media) } + for i, track := range c.Receivers { + media := &core.Media{ + Kind: core.GetKind(track.Codec.Name), + Direction: core.DirectionSendonly, + Codecs: []*core.Codec{track.Codec}, + ID: "trackID=" + strconv.Itoa(i+len(c.Senders)), + } + medias = append(medias, media) + } + res.Body, err = core.MarshalSDP(c.SessionName, medias) if err != nil { return err From b34d970076de676b0cc69b7c8716b8b2df4a00aa Mon Sep 17 00:00:00 2001 From: seydx Date: Tue, 18 Feb 2025 11:52:57 +0100 Subject: [PATCH 8/9] remove duplicated code --- pkg/rtsp/server.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/rtsp/server.go b/pkg/rtsp/server.go index b7e65dac..df2ebdb5 100644 --- a/pkg/rtsp/server.go +++ b/pkg/rtsp/server.go @@ -139,16 +139,6 @@ func (c *Conn) Accept() error { medias = append(medias, media) } - for i, track := range c.Receivers { - media := &core.Media{ - Kind: core.GetKind(track.Codec.Name), - Direction: core.DirectionSendonly, - Codecs: []*core.Codec{track.Codec}, - ID: "trackID=" + strconv.Itoa(i+len(c.Senders)), - } - medias = append(medias, media) - } - res.Body, err = core.MarshalSDP(c.SessionName, medias) if err != nil { return err From 0a773c82aff1bc8a5cc2c058f845b14b7ca34f3e Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 18 Feb 2025 16:59:00 +0300 Subject: [PATCH 9/9] Code refactoring for RTSP backchannel --- internal/rtsp/rtsp.go | 18 ++++++++---------- pkg/rtsp/producer.go | 41 ++++++++++++++++++----------------------- pkg/rtsp/server.go | 18 ++++++------------ 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/internal/rtsp/rtsp.go b/internal/rtsp/rtsp.go index 5c023b71..4c9ca162 100644 --- a/internal/rtsp/rtsp.go +++ b/internal/rtsp/rtsp.go @@ -8,7 +8,6 @@ import ( "github.com/AlexxIT/go2rtc/internal/app" "github.com/AlexxIT/go2rtc/internal/streams" - "github.com/AlexxIT/go2rtc/pkg/aac" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/rtsp" "github.com/AlexxIT/go2rtc/pkg/tcp" @@ -186,11 +185,11 @@ func tcpHandler(conn *rtsp.Conn) { } } - if query.Get("backchannel") == "1" { - conn.Medias = append(conn.Medias, &core.Media{ - Kind: core.KindAudio, - Direction: core.DirectionRecvonly, - Codecs: []*core.Codec{ + if query.Get("backchannel") == "1" { + conn.Medias = append(conn.Medias, &core.Media{ + Kind: core.KindAudio, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{ {Name: core.CodecOpus, ClockRate: 48000, Channels: 2}, {Name: core.CodecPCM, ClockRate: 16000}, {Name: core.CodecPCMA, ClockRate: 16000}, @@ -198,10 +197,9 @@ func tcpHandler(conn *rtsp.Conn) { {Name: core.CodecPCM, ClockRate: 8000}, {Name: core.CodecPCMA, ClockRate: 8000}, {Name: core.CodecPCMU, ClockRate: 8000}, - {Name: core.CodecAAC, ClockRate: 16000, FmtpLine: aac.FMTP + "1408"}, - }, - }) - } + }, + }) + } if s := query.Get("pkt_size"); s != "" { conn.PacketSize = uint16(core.Atoi(s)) diff --git a/pkg/rtsp/producer.go b/pkg/rtsp/producer.go index 323d9197..3d818b62 100644 --- a/pkg/rtsp/producer.go +++ b/pkg/rtsp/producer.go @@ -16,43 +16,38 @@ func (c *Conn) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, e } } - switch c.mode { - case core.ModeActiveProducer: - c.stateMu.Lock() - defer c.stateMu.Unlock() + c.stateMu.Lock() + defer c.stateMu.Unlock() + var channel byte + + switch c.mode { + case core.ModeActiveProducer: if c.state == StatePlay { if err := c.Reconnect(); err != nil { return nil, err } } - channel, err := c.SetupMedia(media) + var err error + channel, err = c.SetupMedia(media) if err != nil { return nil, err } c.state = StateSetup + case core.ModePassiveConsumer: + // Backchannel + channel = byte(len(c.Senders)) * 2 + default: + return nil, errors.New("rtsp: wrong mode for GetTrack") + } - track := core.NewReceiver(media, codec) - track.ID = channel - c.Receivers = append(c.Receivers, track) + track := core.NewReceiver(media, codec) + track.ID = channel + c.Receivers = append(c.Receivers, track) - return track, nil - case core.ModePassiveConsumer: - // Backchannel - c.stateMu.Lock() - defer c.stateMu.Unlock() - - channel := byte(len(c.Senders)) * 2 - track := core.NewReceiver(media, codec) - track.ID = channel - c.Receivers = append(c.Receivers, track) - - return track, nil - default: - return nil, errors.New("rtsp: wrong mode for GetTrack") - } + return track, nil } func (c *Conn) Start() (err error) { diff --git a/pkg/rtsp/server.go b/pkg/rtsp/server.go index df2ebdb5..f4aea614 100644 --- a/pkg/rtsp/server.go +++ b/pkg/rtsp/server.go @@ -164,20 +164,14 @@ func (c *Conn) Accept() error { c.state = StateSetup if c.mode == core.ModePassiveConsumer { - trackID := reqTrackID(req) - - if trackID >= 0 { - if trackID < len(c.Senders) { - c.Senders[trackID].Media.ID = MethodSetup - tr = fmt.Sprintf("%d-%d", trackID*2, trackID*2+1) - res.Header.Set("Transport", transport+tr) - } else if trackID >= len(c.Senders) && trackID < len(c.Senders)+len(c.Receivers) { - c.Receivers[trackID-len(c.Senders)].Media.ID = MethodSetup - tr = fmt.Sprintf("%d-%d", trackID*2, trackID*2+1) - res.Header.Set("Transport", transport+tr) + if i := reqTrackID(req); i >= 0 && i < len(c.Senders)+len(c.Receivers) { + if i < len(c.Senders) { + c.Senders[i].Media.ID = MethodSetup } else { - res.Status = "400 Bad Request" + c.Receivers[i-len(c.Senders)].Media.ID = MethodSetup } + tr = fmt.Sprintf("%d-%d", i*2, i*2+1) + res.Header.Set("Transport", transport+tr) } else { res.Status = "400 Bad Request" }