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

libavcodec: v4l2m2m: include SPS/PPS headers in keyframes #10

Open
wants to merge 1 commit into
base: pios/bookworm
Choose a base branch
from

Conversation

marcusb
Copy link

@marcusb marcusb commented Jul 30, 2024

This adds an option "-repeat_seq_header", which tells the encoder to insert sequence headers repeatedly in the stream. This is required for clients to be able to join mid-stream in a H.264 stream from the Raspberry Pi.

Without this option (the previous and default behavior), PPS/SPS headers are only added to the first IDR frame. When repeat_seq_header is enabled, PPS/SPS is added to every IDR frame, allowing clients to decode the stream from that point.

This adds an option "-repeat_seq_header", which tells the encoder to
insert sequence headers repeatedly in the stream. This is required for
clients to be able to join mid-stream in a H.264 stream from the
Raspberry Pi.

Without this option (the previous and default behavior), PPS/SPS
headers are only added to the first IDR frame. When
-repeat_seq_header is enabled, PPS/SPS is added to every IDR frame,
allowing clients to decode the stream from that point.

Signed-off-by: Marcus Better <[email protected]>
@jc-kynesim
Copy link

Fair comment. I'll see if it is sane to make that the default behaviour for contexts that don't ask for SPS/PPS in avctx (with an option to turn it off) as the overhead is low and the advantages noticeable.

@jc-kynesim
Copy link

I've had a look at the code again and the encoder should prepend a SPS/PPS pair to every key frame if the AV_CODEC_FLAG_GLOBAL_HEADER (extract SPS/PPS and put it in the context) is not set. Trivial testing i.e. "ffmpeg -i test.mkv -c:v h264_v4l2m2m t.h264" shows that it works. Can you give me a failing ffmpeg command line please?

@marcusb
Copy link
Author

marcusb commented Aug 13, 2024

I see what you mean, I can confirm it works with that command.

In my case, I am pulling a H.265 stream from a camera, decoding it with the hardware HEVC decoder, and encoding into a H.264 RTSP stream, all in one ffmpeg command (in this case using go2rtc):

ffmpeg -hide_banner -v error -allowed_media_types video -hwaccel drm -fflags nobuffer -flags low_delay -timeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_flags prefer_tcp -i rtsp://user:pass@camera?stream=0 -filter:v format=yuv420p -g 24 -c:v h264_v4l2m2m -repeat_seq_header 1 -an -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp rtsp://127.0.0.1:8554/d05a43d9b90722c3088dcc663614a835

In this setting, I can reproduce it 100% reliably. I verify with

ffprobe -rtsp_transport tcp  -select_streams v -show_frames -show_entries frame=pict_type -debug mb_type -of csv 'rtsp://host::8554/cam'

and it prints out the SPS/PPS headers. Without -repeat_seq_header, they do not appear.

I don't know what exactly the difference is between the scenarios, and haven't been able to reproduce it in a self-contained way.

@jc-kynesim
Copy link

I think the problem is that the rtsp encoder has AVFMT_GLOBALHEADER in its flags (end of rtspenc.c) which, I believe, requests SPS/PPS (or other similar global data) to be sent in extradata rather than inline in the stream. The v4l2m2m encoder respects this flag and doesn't send them inline. Removing the flag causes inline SPS/PPS to occur again and your problem goes away. My problem is that I don't understand the ffmpeg rtsp module very well & I can't work out whether removing this flag is a bad thing in other circumstances.

@marcusb
Copy link
Author

marcusb commented Aug 19, 2024

Interesting that it doesn't seem to have that effect with libx264.

At a minimum it should be possible to override that behavior with flags on the command line, but I wasn't able to make it work. Tried some combinations of -bsf:v extract_extradata and -bsf:v dump_extra but no dice.

@jc-kynesim
Copy link

libx264 doesn't appear to support the offending flag and that appears to be fine :-( I've kicked a query out onto ff-devel - maybe I'll get a response. This is something that the user shouldn't have to add flags for and I think that rtspenc is currently wrong so I'd prefer not to "fix" my code unless it becomes clear that is the only way forward.

@jc-kynesim
Copy link

OK I now have a better understanding of how this is meant to work. Having got a reply on ff-devel it seems that the SPS/PPS packets aren't meant to turn up in the stream they are just meant to be part of SDP that is returned on stream join and my testing shows that they indeed turn up there. As they are constant there is no need to repeat - and this isn't a broadcast medium where joining a stream is just snooping on the data, the client has to actively connect and will get the SDP as part of that process. Snip from my ffprobe run showing this, using mediamtx as my RTSP server:

[tcp @ 0x61bb6a192540] No default whitelist set
[tcp @ 0x61bb6a192540] Original list of addresses:
[tcp @ 0x61bb6a192540] Address 10.44.0.81 port 8554
[tcp @ 0x61bb6a192540] Interleaved list of addresses:
[tcp @ 0x61bb6a192540] Address 10.44.0.81 port 8554
[tcp @ 0x61bb6a192540] Starting connection attempt to 10.44.0.81 port 8554
[tcp @ 0x61bb6a192540] Successfully connected to 10.44.0.81 port 8554
[rtsp @ 0x61bb6a1ced00] SDP:
v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
c=IN IP4 0.0.0.0
t=0 0
m=video 0 RTP/AVP 96
a=control:rtsp://pi4:8554/mystream/trackID=0
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; profile-level-id=640028; sprop-parameter-sets=J2
QAKKwrQDwBE/LCAAADAAIAAAMAecFABhqAAL683vcA8SJq,KO4BNyw=

[rtsp @ 0x61bb6a1ced00] video codec set to: h264
[rtsp @ 0x61bb6a1ced00] RTP Packetization Mode: 1
[rtsp @ 0x61bb6a1ced00] RTP Profile IDC: 64 Profile IOP: 0 Level: 28
[rtsp @ 0x61bb6a1ced00] Extradata set to 0x61bb6a1d2520 (size: 49)
[rtsp @ 0x61bb6a1ced00] setting jitter buffer size to 0
[rtsp @ 0x61bb6a1ced00] hello state=0
[h264 @ 0x61bb6a1d5200] nal_unit_type: 7(SPS), nal_ref_idc: 1
[h264 @ 0x61bb6a1d5200] nal_unit_type: 8(PPS), nal_ref_idc: 1
[h264 @ 0x61bb6a1d5200] Decoding VUI
[h264 @ 0x61bb6a1d5200] nal_unit_type: 7(SPS), nal_ref_idc: 1
[h264 @ 0x61bb6a1d5200] nal_unit_type: 8(PPS), nal_ref_idc: 1
[h264 @ 0x61bb6a1d5200] Decoding VUI
[h264 @ 0x61bb6a1d5200] nal_unit_type: 1(Coded slice of a non-IDR picture), nal_
ref_idc: 1
[h264 @ 0x61bb6a1d5200] Format yuv420p chosen by get_format().
[h264 @ 0x61bb6a1d5200] Reinit context to 1920x1088, pix_fmt: yuv420p
[h264 @ 0x61bb6a1d5200] Frame num gap 8 6
[h264 @ 0x61bb6a1d5200] nal_unit_type: 1(Coded slice of a non-IDR picture), nal_
ref_idc: 1
    Last message repeated 14 times
[h264 @ 0x61bb6a1d5200] nal_unit_type: 5(IDR), nal_ref_idc: 1
[h264 @ 0x61bb6a1d5200] New frame, type: I

`
The SPS/PPS is passed in the sprop-parameter-sets element above. So I think it is working as expected. My test isn't identical to your case - I start with a file that I transcode, but I don't think that invalidates the result.

@marcusb
Copy link
Author

marcusb commented Aug 23, 2024

Hmm I'm using go2rtc as RTSP server and I'm not seeing the sprop-parameter-sets. This is my SDP:

Aug 22 21:26:33 pi4 go2rtc[657755]: 21:26:33.913 TRC [rtsp] server response:
Aug 22 21:26:33 pi4 go2rtc[657755]: RTSP/1.0 200 OK                                                                                                                         
Aug 22 21:26:33 pi4 go2rtc[657755]: Content-Type: application/sdp            
Aug 22 21:26:33 pi4 go2rtc[657755]: Cseq: 2          
Aug 22 21:26:33 pi4 go2rtc[657755]: Content-Length: 169
Aug 22 21:26:33 pi4 go2rtc[657755]: 
Aug 22 21:26:33 pi4 go2rtc[657755]: v=0                                               
Aug 22 21:26:33 pi4 go2rtc[657755]: o=- 1 1 IN IP4 0.0.0.0
Aug 22 21:26:33 pi4 go2rtc[657755]: s=go2rtc/1.9.4
Aug 22 21:26:33 pi4 go2rtc[657755]: c=IN IP4 0.0.0.0 
Aug 22 21:26:33 pi4 go2rtc[657755]: t=0 0
Aug 22 21:26:33 pi4 go2rtc[657755]: m=video 0 RTP/AVP 96                   
Aug 22 21:26:33 pi4 go2rtc[657755]: a=rtpmap:96 H264/90000                                                                                                                  
Aug 22 21:26:33 pi4 go2rtc[657755]: a=fmtp:96 packetization-mode=1
Aug 22 21:26:33 pi4 go2rtc[657755]: a=control:trackID=0
Aug 22 21:26:33 pi4 go2rtc[657755]: 21:26:33.914 TRC [rtsp] server request:  
Aug 22 21:26:33 pi4 go2rtc[657755]: SETUP rtsp://pi4.example.org:8554/cam3_test/trackID=0 RTSP/1.0
Aug 22 21:26:33 pi4 go2rtc[657755]: Transport: RTP/AVP/TCP;unicast;interleaved=0-1
Aug 22 21:26:33 pi4 go2rtc[657755]: Cseq: 3        
Aug 22 21:26:33 pi4 go2rtc[657755]: User-Agent: Lavf61.1.100
Aug 22 21:26:33 pi4 go2rtc[657755]:                                                   

Wonder if the problem is in go2rtc then?

@jc-kynesim
Copy link

Might be - for testing purposes maybe try mediamtx? It is extraordinarily easy to set up for this test - just download a copy from
https://github.com/bluenviron/mediamtx/releases, unpack and run ./mediamtx (that was the entirety of my setup). It is possible that the block gets lost in some part of your command line that I simplified. N.B. I am not promoting mediamtx for any purpose, I have no experience of it beyond this test - its just the first one I found & it comes with a prebuilt binary that just works on a Pi4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants