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

V380 Pro: bad sps #108

Closed
Curid opened this issue Nov 18, 2024 · 5 comments
Closed

V380 Pro: bad sps #108

Curid opened this issue Nov 18, 2024 · 5 comments

Comments

@Curid
Copy link
Contributor

Curid commented Nov 18, 2024

bad sps
  conn: 172.27.0.2:44598(me)->[local IP]:554@2024-11-18T16:57:48
  stream: TCP, interleaved channel ids 0-1
  ssrc: 00000000
  seq: 19
  pkt: 26318@2024-11-18T16:57:48

wireshark_dumps.zip

@Curid
Copy link
Contributor Author

Curid commented Nov 19, 2024

The camera uses the macro-media rtsp server #52.

I made a test case based on the second set of packets in the Wireshark dump: https://github.com/Curid/retina/tree/issue108

@Curid
Copy link
Contributor Author

Curid commented Nov 21, 2024

gortsplib behavior:

  • seq1 is a unmarked FU-A fragment with the start bit set
  • seq2-17 are unmarked FU-A fragments
  • seq18 is a marked FU-A fragment with the stop bit set
  • the stop bit causes a 25290 byte NAL to be finalized and annexb encoding to be checked
  • decoding the annexb results in 3 access units:
  • one 23 byte SPS, one 5 byte PPS, and one 25254 byte IDR

retina behavior:

  • seq1 is detected as a unmarked SPS FU-A fragment with the start bit set
  • seq2-17 are detected as unmarked SPS FU-A fragments
  • seq18 is detected as a SPS FU-A fragment with the stop bit set
  • seq18 is marked but this code delays finalization
  • seq19 is a marked and unfragmented SliceLayerWithoutPartitioningNonIdr
  • seq19 triggers finalization of the 25290 byte SPS NAL
  • the PPS and IDR are discarded

@Curid
Copy link
Contributor Author

Curid commented Nov 22, 2024

The SPS is valid after annexb decoding:

h264_reader
use h264_reader::{
    nal::sps::SeqParameterSet,
    rbsp::{decode_nal, BitReader},
};

fn main() {
    // hex: 274de0288d680a03da1000000300100000030280f1422a
    let sps_rbsp = decode_nal(&[
        39, 77, 224, 40, 141, 104, 10, 3, 218, 16, 0, 0, 3, 0, 16, 0, 0, 3, 2, 128, 241, 66, 42,
    ])
    .unwrap();
    let sps = SeqParameterSet::from_bits(BitReader::new(&*sps_rbsp)).unwrap();
    println!("sps: {sps:#?}");
    /*
        sps: SeqParameterSet {
            profile_idc: ProfileIdc(
                77,
            ),
            constraint_flags: ConstraintFlags {
                flag0: true,
                flag1: true,
                flag2: true,
                flag3: false,
                flag4: false,
                flag5: false,
                reserved_zero_two_bits: 0,
            },
            level_idc: 40,
            seq_parameter_set_id: ParamSetId(
                0,
            ),
            chroma_info: ChromaInfo {
                chroma_format: YUV420,
                separate_colour_plane_flag: false,
                bit_depth_luma_minus8: 0,
                bit_depth_chroma_minus8: 0,
                qpprime_y_zero_transform_bypass_flag: false,
                scaling_matrix: SeqScalingMatrix,
            },
            log2_max_frame_num_minus4: 12,
            pic_order_cnt: TypeTwo,
            max_num_ref_frames: 1,
            gaps_in_frame_num_value_allowed_flag: false,
            pic_width_in_mbs_minus1: 39,
            pic_height_in_map_units_minus1: 29,
            frame_mbs_flags: Frames,
            direct_8x8_inference_flag: true,
            frame_cropping: None,
            vui_parameters: Some(
                VuiParameters {
                    aspect_ratio_info: None,
                    overscan_appropriate: Unspecified,
                    video_signal_type: None,
                    chroma_loc_info: None,
                    timing_info: Some(
                        TimingInfo {
                            num_units_in_tick: 1,
                            time_scale: 40,
                            fixed_frame_rate_flag: false,
                        },
                    ),
                    nal_hrd_parameters: None,
                    vcl_hrd_parameters: None,
                    low_delay_hrd_flag: None,
                    pic_struct_present_flag: false,
                    bitstream_restrictions: Some(
                        BitstreamRestrictions {
                            motion_vectors_over_pic_boundaries_flag: true,
                            max_bytes_per_pic_denom: 0,
                            max_bits_per_mb_denom: 0,
                            log2_max_mv_length_horizontal: 9,
                            log2_max_mv_length_vertical: 7,
                            max_num_reorder_frames: 0,
                            max_dec_frame_buffering: 1,
                        },
                    ),
                },
            ),
        }
    */
}
bluenviron
package main

import (
	"fmt"
	"log"

	"github.com/bluenviron/mediacommon/pkg/codecs/h264"
)

func main() {
	var sps h264.SPS
	err := sps.Unmarshal([]byte{39, 77, 224, 40, 141, 104, 10, 3, 218, 16, 0, 0, 3, 0, 16, 0, 0, 3, 2, 128, 241, 66, 42})
	if err != nil {
		log.Fatalf("sps wouldn't parse: %v", err)
	}
	fmt.Printf("sps: %+v\n", sps)
	fmt.Printf("vui: %+v\n", sps.VUI)
	/*
		sps: {
			ProfileIdc:77
			ConstraintSet0Flag:true
			ConstraintSet1Flag:true
			ConstraintSet2Flag:true
			ConstraintSet3Flag:false
			ConstraintSet4Flag:false
			ConstraintSet5Flag:false
			LevelIdc:40
			ID:0
			ChromaFormatIdc:1
			SeparateColourPlaneFlag:false
			BitDepthLumaMinus8:0
			BitDepthChromaMinus8:0
			QpprimeYZeroTransformBypassFlag:false
			ScalingList4x4:[]
			UseDefaultScalingMatrix4x4Flag:[]
			ScalingList8x8:[]
			UseDefaultScalingMatrix8x8Flag:[]
			Log2MaxFrameNumMinus4:12
			PicOrderCntType:2
			Log2MaxPicOrderCntLsbMinus4:0
			DeltaPicOrderAlwaysZeroFlag:false
			OffsetForNonRefPic:0
			OffsetForTopToBottomField:0
			OffsetForRefFrames:[]
			MaxNumRefFrames:1
			GapsInFrameNumValueAllowedFlag:false
			PicWidthInMbsMinus1:39
			PicHeightInMapUnitsMinus1:29
			FrameMbsOnlyFlag:true
			MbAdaptiveFrameFieldFlag:false
			Direct8x8InferenceFlag:true
			FrameCropping:<nil>
			VUI: &{
				AspectRatioInfoPresentFlag:false
				AspectRatioIdc:0
				SarWidth:0
				SarHeight:0
				OverscanInfoPresentFlag:false
				OverscanAppropriateFlag:false
				VideoSignalTypePresentFlag:false
				VideoFormat:0
				VideoFullRangeFlag:false
				ColourDescriptionPresentFlag:false
				ColourPrimaries:0
				TransferCharacteristics:0
				MatrixCoefficients:0
				ChromaLocInfoPresentFlag:false
				ChromaSampleLocTypeTopField:0
				ChromaSampleLocTypeBottomField:0
				TimingInfo:0xc000012220
				NalHRD:<nil>
				VclHRD:<nil>
				LowDelayHrdFlag:false
				PicStructPresentFlag:false
				BitstreamRestriction:0xc00001c200
			}
		}
	*/
}
vdk
package main

import (
	"fmt"
	"log"

	"github.com/deepch/vdk/codec/h264parser"
)

func main() {
	sps, err := h264parser.ParseSPS([]byte{39, 77, 224, 40, 141, 104, 10, 3, 218, 16, 0, 0, 3, 0, 16, 0, 0, 3, 2, 128, 241, 66, 42})
	if err != nil {
		log.Fatalf("sps wouldn't parse: %v", err)
	}
	fmt.Printf("sps: %+v\n", sps)
	/*
		sps: {
			Id:0 ProfileIdc:77
			LevelIdc:40
			ConstraintSetFlag:56
			MbWidth:40
			MbHeight:30
			CropLeft:0
			CropRight:0
			CropTop:0
			CropBottom:0
			Width:640
			Height:480
			FPS:20
		}
	*/
}

scottlamb added a commit that referenced this issue Jan 15, 2025
Servers are not supposed to use Annex B sequences in RTP payloads, but
V380 cameras do. Retina promises that it "works around brokenness in
cheap closed-source cameras", so let's try to make this work.

As noted by @Curid in #108, there's prior art: gortsp does Annex B
decoding. ffmpeg also appears to support this, perhaps by accident: the
output from its RTP layer is in Annex B format, and it doesn't error on
Annex B sequences in RTP, so callers implicitly get the same result with
proper or improper framing.

Along the way, notice NALs that illegally contain the sequences
`00 00 00` or `00 00 02` and ones that illegally end with `00`. We'll
see if this happens in practice, but I'd prefer to explicitly consider
how to work around it if so, rather than passing along bad data
unknowingly.

Throughput on my laptop falls from 3.4 GiB/s to 2.9 GiB/s on the
`depacketize/h264_aac_discard` test, which uses realistic data.
I think that's acceptable. The code avoids unnecessary copies (or
refcounts even) and uses `memchr::memchr` to optimize the case of
looking for the next zero byte. From skimming a profile, most of the
added time is in `memchr`, so I don't think we're going to do much
better than that.

Fixes #68
Fixes #108
@scottlamb
Copy link
Owner

Can you give this a spin with the multi-nal branch? It adds Annex B decoding of RTP payloads.

@Curid
Copy link
Contributor Author

Curid commented Jan 22, 2025

User reports the following error with this commit:

I20250122 15:02:04.271 main client::mp4] Using h264 video stream
I20250122 15:02:04.292 main client::mp4] No suitable audio stream found
I20250122 15:02:04.502 main client::mp4] writing MP4 failed; details will be logged with Fatal: after RTSP session teardown
E20250122 15:02:04.555 main client] Fatal: forbidden sequence 00 00 00 in NAL

conn: 192.168.1.81:50938(me)->192.168.1.68:554@2025-01-22T15:02:04
stream: TCP, interleaved channel ids 0-1
ssrc: 00000000
seq: 1
pkt: 566@2025-01-22T15:02:04

The latest commit outputs a working mp4 file.

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

No branches or pull requests

2 participants