Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exec does not kill previous command when reconnecting #1084

Open
slyoldfox opened this issue May 3, 2024 · 6 comments
Open

exec does not kill previous command when reconnecting #1084

slyoldfox opened this issue May 3, 2024 · 6 comments
Assignees
Labels
bug Something isn't working

Comments

@slyoldfox
Copy link

slyoldfox commented May 3, 2024

My doorbell publishes an RTP UDP stream which disconnects every minute.

When the stream disconnects, the (previous) underlying exec process is not killed before the reconnect occurs.

Basically I am using a TCP socket which provides SDP to ffmpeg:

streams:
  doorbell3:
    - "ffmpeg:tcp://192.168.0.20:50000#video=copy#audio=pcma"

This writes the following SDP to the socket:

v=0
m=audio 5000 RTP/AVP 110
c=IN IP4 127.0.0.1
a=rtpmap:110 speex/8000/1
m=video 5002 RTP/AVP 96
c=IN IP4 127.0.0.1
a=rtpmap:96 H264/90000

I can see the stream working as expected on the streams page. After a while the servers stops sending packets to ports 5000 and 5002 and a reconnect will occur which will lead to a bind failed error.

This reconnects happens here:
https://github.com/AlexxIT/go2rtc/blob/master/internal/streams/producer.go#L174

The error is quite logical because it will try to bind() to UDP ports 5000 and 5002 using the following underlying command ffmpeg -hide_banner -i tcp://192.168.0.20:50000 -c:v copy -c:a pcm_alaw -ar:a 8000 -ac:a 1 -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp rtsp://127.0.0.1:8554/61351096f0e2c54e7b8c0c1725be6ebb.

Locally I hacked around this by doing the following (but I am sure a better solution is at hand), which seems to fix the issue:

	if preciousCmd != nil && preciousCmd.Process != nil {
		preciousCmd.Process.Kill()
		preciousCmd = nil
	}
	cmd := exec.Command(args[0], args[1:]...)
	if log.Debug().Enabled() {
		cmd.Stderr = os.Stderr
	}
	preciousCmd = cmd

This is happens here:
https://github.com/AlexxIT/go2rtc/blob/master/internal/exec/exec.go#L71

This is on 1.9.0 on Mac Intel

@AlexxIT AlexxIT added the question Further information is requested label May 4, 2024
@AlexxIT
Copy link
Owner

AlexxIT commented May 4, 2024

There's a lot of confusion in your post:

  • tcp://192.168.0.5:50000 (port and IP)
  • where SDP came from?
  • m=audio 5000 (port) m=video 5002 (port)
  • speex/8000/1 - not supported codec
  • tcp://192.168.0.20:50000 (port and IP)

@slyoldfox
Copy link
Author

slyoldfox commented May 4, 2024

  • tcp://192.168.0.5:50000 and tcp://192.168.0.20:50000 were both the same. It's a simple TCP socket that writes a (fixed) SDP. I succesfully use it with the command ffmpeg -f sdp -i tcp://192.168.0.5:5000 ... and transcode the speex codec there to PCMA. The usage of the SDP is mandatory for ffmpeg because you can't use ffmpeg -f rtp -i videoinput -f rtp -i audioinput form the command line.
  • This intercom sends RTP packets on port 5000 for the audio and 5002 for video - the codecs and ports are fixed on the intercom side
  • I understand that speex is not supported, but the #audio=pcma option works as expected so it is not really relevant to the issue

The only issue I'm seeing is that the reconnect from the gortc worker doesn't seem to close in time, which causes the ffmpeg process to still be running when the intercom stops sending packets and the go2rtc reconnects.
Since ffmpeg will bind() to the UDP ports 5000 and 5002 taken from the SDP - it will fail because the previous ffmpeg is still bound to these ports.

I was looking if pkg/magic/producer.go could support SDP detection from the TCP socket natively, I suppose it might fit in at some point by checking if the first 4 bytes are "v=0", but that's another story :-)

@AlexxIT
Copy link
Owner

AlexxIT commented May 4, 2024

It didn't get any better.

  • 192.168.0.5 and 192.168.0.20 - different IPs, they're definitely not same
  • 50000 and 5000 - different ports

I still don't understand the whole data flow.

PS. SDP over TCP it's really another story. It doesn't meet any standards.

@slyoldfox
Copy link
Author

slyoldfox commented May 4, 2024

I am sorry if the flow is confusing, I admit that this bticino intercom is quite exotic in its setup.

192.168.0.20 is the IP address of the intercom and exposes the SDP via a normal socket call on TCP port 50000.
192.168.0.5 is my development computer, where I also have a TCP socket running on port 50000 for debugging, ignore this IP, I have changed the initial comment).

Everything starts out with a receiver calling TCP port 50000 on the intercom IP (192.168.0.20). The intercom will return the SDP above in the reply on the socket. Internally, the intercom will setup a SIP call between itself and the outdoor camera unit, it is not really relevant to you, but the TCP socket on port 50000 triggers the SIP call internally.

Once the SIP call is established internally, it will (re)stream the RTP UDP packets on port 5000 (for audio) and RDP UDP packets on port 5002 (for video) - as provided in the SDP by the TCP socket on port 50000 - towards the receiver.

The receiver binds on UDP ports 5000 and 5002 to receive these packets and process them (ffmpeg does this internally after having parsed the SDP).

So to summarize - hopefully a little more clear:

  1. receiver starts ffmpeg with -f sdp -i tcp://192.168.0.20:50000
  2. intercom is listening on TCP port 50000 and returns the SDP which specifies that audio and video will be sent on UDP ports 5000 and 5002 respectively, the intercom starts sending these packets to the IP address to the receiver. These packets are RTP packets over UDP.
  3. ffmpeg on the receiver parses this SDP and binds to UDP ports 5000 and 5002 to process the RTP packets.

@AlexxIT
Copy link
Owner

AlexxIT commented May 4, 2024

  1. Where does intercom get the IP-address to send UDP? Will this be the same address where the TCP request to port 50000 came from or is this configurable separately?
  2. What software is this 50000 port even made for? Does anything other than ffmpeg know how to receive SDP over TCP?
  3. It doesn't make sense for magic/producer.go to support SDP because go2rtc doesn't manage to open random ports to receive UDP data. Also because of your problem - we can't specify the ports ourselves and they may be busy.

OK. I'll try to reproduce your situation about reconnecting issue.

@AlexxIT AlexxIT self-assigned this May 4, 2024
@AlexxIT AlexxIT added bug Something isn't working and removed question Further information is requested labels May 4, 2024
@slyoldfox
Copy link
Author

Thank you! If it's any help, this is the command I use to simulate packets sent from the intercom to the receiver:

ffmpeg -re -f lavfi -i "color=red:size=688x480:rate=15" -f lavfi -i "sine=frequency=1000:b=4" -profile:v baseline -preset ultrafast -g 15 -vcodec libx264 -an -tune zerolatency -f rtp "rtp://GO2RTCIP:5002" -vn -ar 8000 -acodec speex -f rtp -payload_type 110 "rtp://GO2RTCIP:5000"

Replace GO2RTCIP with the IP of your local receiver of course.

You will see that ffmpeg spits out an SDP which matches the SDP that gets returned by the socket server on port 50000.

In theory ffmpeg doesn't care if the SDP is provided from a local file (with -i /local/test.sdp) or over a socket (with -i tcp://IP:50000). In some way it is smart enough to detect that the format is SDP and thus providing -f sdp is not really required on the receiver side.

The following snippet runs the socket server on nodejs to simulate the SDP serving on port 50000.

const net = require("net");
const server = new net.createServer(); 

const sdp = `v=0
m=audio 5000 RTP/AVP 110
c=IN IP4 127.0.0.1
a=rtpmap:110 speex/8000/1
m=video 5002 RTP/AVP 96
c=IN IP4 127.0.0.1
a=rtpmap:96 H264/90000`
server.on("connection", client => {
    client.on('close', () => {
        console.log("Socket DISCONNECTED");
    })

    client.on("data", function (data) {
        console.log("Server received data: " + data);
    });
    console.log("Wrote sdp: " + sdp)
    client.write(sdp)
    client.destroy()

});
server.on("listening", () => {
    console.log(`SDP server listening on ${server.address().address}:${server.address().port}`)
})

server.listen(50000, "0.0.0.0");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants