diff --git a/src/lib/stream/pipeline/fake_pipeline.rs b/src/lib/stream/pipeline/fake_pipeline.rs index 36d4683a..a11e738f 100644 --- a/src/lib/stream/pipeline/fake_pipeline.rs +++ b/src/lib/stream/pipeline/fake_pipeline.rs @@ -120,10 +120,11 @@ impl FakePipeline { "videotestsrc pattern={pattern} is-live=true do-timestamp=true", " ! timeoverlay", " ! video/x-raw,format=I420", - " ! jpegenc quality=85 idct-method=1", - " ! capsfilter name={filter_name} caps=image/jpeg,width={width},height={height},framerate={interval_denominator}/{interval_numerator}", + " ! jpegenc quality=85 idct-method=fast", + " ! capsfilter name={filter_name} caps = \"image/jpeg, sof-marker=(int)0, width=(int){width}, height=(int){height}, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction){interval_numerator}/{interval_denominator}, interlace-mode=(string)progressive, colorimetry=(string)bt601, chroma-site=(string)jpeg\"", " ! tee name={video_tee_name} allow-not-linked=true", - " ! rtpjpegpay pt=96", + " ! rtpjpegpay pt=26", + " ! capsfilter caps = \"application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)JPEG, a-framerate=(string){framerate}, payload=(int)26\"", " ! tee name={rtp_tee_name} allow-not-linked=true", ), pattern = pattern, @@ -134,6 +135,7 @@ impl FakePipeline { filter_name = filter_name, video_tee_name = video_tee_name, rtp_tee_name = rtp_tee_name, + framerate = format!("{:.6}", configuration.frame_interval.numerator as f32 / configuration.frame_interval.denominator as f32), ) } unsupported => { diff --git a/src/lib/stream/pipeline/v4l_pipeline.rs b/src/lib/stream/pipeline/v4l_pipeline.rs index 6c199e99..afe85233 100644 --- a/src/lib/stream/pipeline/v4l_pipeline.rs +++ b/src/lib/stream/pipeline/v4l_pipeline.rs @@ -73,14 +73,30 @@ impl V4lPipeline { rtp_tee_name = rtp_tee_name, ) } + /* + clear; GST_DEBUG="*:3" gst-launch-1.0 -v \ + v4l2src do-timestamp=true device=/dev/video6 \ + ! videoconvert \ + ! capsfilter caps="video/x-raw, width=(int)320, height=(int)320, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive, format=(string)I420, colorimetry=(string)bt601" \ + ! tee name=TeeVideo allow-not-linked=true \ + TeeVideo. \ + ! queue leaky=downstream silent=true flush-on-eos=true max-size-buffers=0 \ + ! rtpvrawpay pt=96 \ + ! capsfilter caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)RAW, sampling=(string)YCbCr-4:2:0, depth=(string)8, width=(string)320, height=(string)320, colorimetry=(string)BT601-5, payload=(int)96, a-framerate=(string)30" \ + ! tee name=TeeRTP allow-not-linked=true \ + TeeRTP. \ + ! queue leaky=downstream silent=true flush-on-eos=true max-size-buffers=0 \ + ! multiudpsink clients=127.0.0.1:5600 sync=false + */ VideoEncodeType::Yuyv => { format!( concat!( "v4l2src device={device} do-timestamp=true", " ! videoconvert", - " ! capsfilter name={filter_name} caps=video/x-raw,format=I420,width={width},height={height},framerate={interval_denominator}/{interval_numerator},colorimetry=bt601", + " ! capsfilter name={filter_name} caps=video/x-raw,format=I420,width={width},height={height},framerate={interval_denominator}/{interval_numerator},colorimetry=bt601,pixel-aspect-ratio=1/1,interlace-mode=progressive", " ! tee name={video_tee_name} allow-not-linked=true", " ! rtpvrawpay pt=96", + " ! capsfilter caps=\"application/x-rtp, clock-rate=(int)90000, encoding-name=(string)RAW, sampling=(string)YCbCr-4:2:0, depth=(string)8, width=(string){width}, height=(string){height}, colorimetry=(string)BT601-5, payload=(int)96, a-framerate=(string){framerate}\"", " ! tee name={rtp_tee_name} allow-not-linked=true" ), device = device, @@ -91,6 +107,7 @@ impl V4lPipeline { filter_name = filter_name, video_tee_name = video_tee_name, rtp_tee_name = rtp_tee_name, + framerate = (configuration.frame_interval.denominator as f32 / configuration.frame_interval.numerator as f32) as u32, ) } VideoEncodeType::Mjpg => { @@ -100,7 +117,7 @@ impl V4lPipeline { // We don't need a jpegparse, as it leads to incompatible caps, spoiling the negotiation. " ! capsfilter name={filter_name} caps=image/jpeg,width={width},height={height},framerate={interval_denominator}/{interval_numerator}", " ! tee name={video_tee_name} allow-not-linked=true", - " ! rtpjpegpay pt=96", + " ! rtpjpegpay pt=26", " ! tee name={rtp_tee_name} allow-not-linked=true" ), device = device, diff --git a/src/lib/stream/rtsp/rtsp_server.rs b/src/lib/stream/rtsp/rtsp_server.rs index 8a604971..9afa5396 100644 --- a/src/lib/stream/rtsp/rtsp_server.rs +++ b/src/lib/stream/rtsp/rtsp_server.rs @@ -195,7 +195,7 @@ impl RTSPServer { " ! queue leaky=downstream flush-on-eos=true silent=true max-size-buffers=10", " ! capsfilter caps={rtp_caps:?}", " ! rtpjpegdepay", - " ! rtpjpegpay name=pay0 pt=96", + " ! rtpjpegpay name=pay0 pt=26", ), socket_path = socket_path, rtp_caps = rtp_caps, diff --git a/tests/v4l2_latency_and_jitter.rs b/tests/v4l2_latency_and_jitter.rs index 97e9a760..153984fe 100644 --- a/tests/v4l2_latency_and_jitter.rs +++ b/tests/v4l2_latency_and_jitter.rs @@ -69,11 +69,12 @@ impl V4l2LoopBack { VideoEncodeType::H264 => format!( concat!( "qrtimestampsrc do-timestamp=true", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)RGB\"", " ! videoconvert", - " ! video/x-raw,width={width},height={height},framerate={framerate_num}/{framerate_den},format=I420", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)I420\"", " ! x264enc tune=zerolatency speed-preset=ultrafast bitrate=5000", " ! h264parse", - " ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream,alignment=au", + " ! capsfilter caps=\"video/x-h264, profile=(string)constrained-baseline, stream-format=(string)byte-stream, alignment=(string)au\"", " ! v4l2sink device={device_path} sync=false", ), height = configuration.height, @@ -85,10 +86,12 @@ impl V4l2LoopBack { VideoEncodeType::Mjpg => format!( concat!( "qrtimestampsrc do-timestamp=true", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)RGB\"", " ! videoconvert", - " ! video/x-raw,width={width},height={height},framerate={framerate_num}/{framerate_den},format=I420", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)I420\"", " ! jpegenc quality=85 idct-method=ifast", " ! jpegparse", + " ! capsfilter caps=\"image/jpeg, colorspace=(string)sYUV, sampling=(string)YCbCr-4:2:0, colorimetry=(string)bt601\"", " ! v4l2sink device={device_path} sync=false", ), height = configuration.height, @@ -100,9 +103,11 @@ impl V4l2LoopBack { VideoEncodeType::Yuyv => format!( concat!( "qrtimestampsrc do-timestamp=true", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)RGB\"", " ! videoconvert", - " ! video/x-raw,width={width},height={height},framerate={framerate_num}/{framerate_den},format=YUY2", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)YUY2\"", " ! rawvideoparse use-sink-caps=true", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive, format=(string)YUY2, colorimetry=(string)bt601\"", " ! v4l2sink device={device_path} sync=false", ), height = configuration.height, @@ -178,45 +183,38 @@ impl QrTimeStampSink { VideoEncodeType::H264 => format!( concat!( "{source_description}", - " ! application/x-rtp,payload=96", + " ! capsfilter caps=\"application/x-rtp, clock-rate=(int)90000, encoding-name=(string)H264, a-framerate=(string){framerate}, payload=(int)96\"", " ! rtph264depay", " ! h264parse", - " ! video/x-h264,width={width},height={height}", " ! avdec_h264 discard-corrupted-frames=true", " ! videoconvert", " ! identity check-imperfect-timestamp=true check-imperfect-offset=true", " ! qrtimestampsink name=qrsink sync=false", ), source_description = source_description, - width = configuration.width, - height = configuration.height, + framerate = format!("{:.6}", configuration.frame_interval.denominator as f32 / configuration.frame_interval.numerator as f32), ), VideoEncodeType::Mjpg => format!( concat!( "{source_description}", - " ! application/x-rtp,payload=96", + " ! capsfilter caps=\"application/x-rtp, clock-rate=(int)90000, encoding-name=(string)JPEG, a-framerate=(string){framerate}, payload=(int)26\"", " ! rtpjpegdepay", - " ! image/jpeg,width={width},height={height}", + " ! jpegparse", " ! jpegdec", " ! videoconvert", " ! identity check-imperfect-timestamp=true check-imperfect-offset=true", " ! qrtimestampsink name=qrsink sync=false", ), source_description = source_description, - width = configuration.width, - height = configuration.height, + framerate = format!("{:.6}", configuration.frame_interval.denominator as f32 / configuration.frame_interval.numerator as f32), ), VideoEncodeType::Yuyv => format!( concat!( "{source_description}", - " ! capsfilter caps=\"application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)RAW, sampling=(string)YCbCr-4:2:0, depth=(string)8, width=(string)320, height=(string)320, colorimetry=(string)BT601-5, payload=(int)96, a-framerate=(string)30\"", + " ! capsfilter caps=\"application/x-rtp, clock-rate=(int)90000, encoding-name=(string)RAW, sampling=(string)YCbCr-4:2:0, depth=(string)8, width=(string){width}, height=(string){height}, colorimetry=(string)BT601-5, payload=(int)96, a-framerate=(string){framerate}\"", " ! rtpvrawdepay", - " ! videorate skip-to-first=true silent=true", - " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive, format=(string)I420, colorimetry=(string)bt601\"", - // " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive, format=(string)I420, colorimetry=(string)bt601\"", - " ! rawvideoparse use-sink-caps=true ", + " ! rawvideoparse width={width} height={height} framerate={framerate_num}/{framerate_den} format=i420 colorimetry=bt601 pixel-aspect-ratio=1/1 interlaced=false", " ! videoconvert", - " ! identity check-imperfect-timestamp=true check-imperfect-offset=true", " ! qrtimestampsink name=qrsink sync=false", ), source_description = source_description, @@ -224,6 +222,7 @@ impl QrTimeStampSink { height = configuration.height, framerate_num = configuration.frame_interval.denominator, framerate_den = configuration.frame_interval.numerator, + framerate = format!("{:.6}", configuration.frame_interval.denominator as f32 / configuration.frame_interval.numerator as f32), ), _ => unimplemented!(), }; @@ -308,14 +307,15 @@ impl Baseline { let pipeline_description = match configuration.encode { VideoEncodeType::H264 => format!( concat!( + // source "qrtimestampsrc do-timestamp=true", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)RGB\"", " ! videoconvert", - " ! video/x-raw,width={width},height={height},framerate={framerate_num}/{framerate_den},format=I420", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)I420\"", " ! x264enc tune=zerolatency speed-preset=ultrafast bitrate=5000", " ! h264parse", - " ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream,alignment=au", - " ! h264parse", - " ! video/x-h264,width={width},height={height},framerate={framerate_num}/{framerate_den}", + " ! capsfilter caps=\"video/x-h264, profile=(string)constrained-baseline, stream-format=(string)byte-stream, alignment=(string)au\"", + // sink " ! avdec_h264 discard-corrupted-frames=true", " ! videoconvert", " ! identity check-imperfect-timestamp=true check-imperfect-offset=true", @@ -328,11 +328,14 @@ impl Baseline { ), VideoEncodeType::Mjpg => format!( concat!( + // source "qrtimestampsrc do-timestamp=true", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)RGB\"", " ! videoconvert", - " ! video/x-raw,width={width},height={height},framerate={framerate_num}/{framerate_den},format=I420", + " ! capsfilter caps = \"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)I420\"", " ! jpegenc quality=85 idct-method=ifast", - " ! image/jpeg,width={width},height={height},framerate={framerate_num}/{framerate_den}", + " ! capsfilter caps = \"image/jpeg, interlace-mode=(string)progressive, colorimetry=(string)bt601, chroma-site=(string)jpeg\"", + // sink " ! jpegdec", " ! videoconvert", " ! identity check-imperfect-timestamp=true check-imperfect-offset=true", @@ -345,14 +348,16 @@ impl Baseline { ), VideoEncodeType::Yuyv => format!( concat!( + // source "qrtimestampsrc do-timestamp=true", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)RGB\"", " ! videoconvert", - " ! video/x-raw,width={width},height={height},framerate={framerate_num}/{framerate_den},format=YUY2", - " ! rawvideoparse use-sink-caps=true ", - " ! videorate skip-to-first=true silent=true", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, format=(string)YUY2\"", + " ! rawvideoparse use-sink-caps=true disable-passthrough=true", + " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive, format=(string)YUY2, colorimetry=(string)bt601\"", + // sink + " ! rawvideoparse use-sink-caps=true disable-passthrough=true", " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, framerate=(fraction){framerate_num}/{framerate_den}, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive, format=(string)YUY2, colorimetry=(string)bt601\"", - // " ! capsfilter caps=\"video/x-raw, width=(int){width}, height=(int){height}, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive, format=(string)YUY2, colorimetry=(string)bt601\"", - " ! rawvideoparse use-sink-caps=true ", " ! videoconvert", " ! identity check-imperfect-timestamp=true check-imperfect-offset=true", " ! qrtimestampsink name=qrsink sync=false", @@ -563,23 +568,23 @@ async fn main() { let buffers = 100; let test_cases = [ - (VideoEncodeType::H264, format!("udp://{address}:5600"), 5.), - (VideoEncodeType::Mjpg, format!("udp://{address}:5600"), 5.), - (VideoEncodeType::Yuyv, format!("udp://{address}:5600"), 8.), + (VideoEncodeType::H264, format!("udp://{address}:5600"), 30.), + (VideoEncodeType::Mjpg, format!("udp://{address}:5600"), 30.), + (VideoEncodeType::Yuyv, format!("udp://{address}:5600"), 30.), ( VideoEncodeType::H264, format!("rtsp://{address}:8554/test"), - 5., + 30., ), ( VideoEncodeType::Mjpg, format!("rtsp://{address}:8554/test"), - 5., + 30., ), ( VideoEncodeType::Yuyv, format!("rtsp://{address}:8554/test"), - 8., + 30., ), ];