PlainTransport Consumer not receive any data from PlainTransport Producer

I implement a simple demo to test PlainTransport’s Consumer and Producer.
2 PlainTransport is created on the same router. The first PlainTransport’s Producer is used to receive rtp packets which is generated by FFMPEG with port 5004. The second’s Consumer is used to consume the Producer’s data and send packets to localhost:5005. From the stats log i can know that packets are received by Producer but no packets are send from Consumer.

Environment

  • Rust crate: mediasoup = “0.18.2”
  • Ubuntu 24.04

Code:

   // create worker
    let work_manager = mediasoup::worker_manager::WorkerManager::new();
    let worker = work_manager
        .create_worker(WorkerSettings::default())
        .await
        .expect("Fail to create worker");
    // create codec params
    let mut codec_params_params = RtpCodecParametersParameters::default();
    codec_params_params.insert(
        "packetization-mode".to_string(),
        RtpCodecParametersParametersValue::Number(1),
    );
    codec_params_params.insert("profile-level-id".to_string(), "42c028".to_string());
    let codec_capacity = RtpCodecCapability::Video {
        mime_type: MimeTypeVideo::H264,
        clock_rate: NonZeroU32::new(90000).unwrap(),
        parameters: codec_params_params.clone(),
        preferred_payload_type: Some(96),
        rtcp_feedback: vec![],
    };
    // create router
    let router = worker
        .create_router(RouterOptions::new(vec![codec_capacity.clone()]))
        .await
        .expect("Fail to create router");
    let codec_params = RtpCodecParameters::Video {
        mime_type: MimeTypeVideo::H264,
        payload_type: 96,
        clock_rate: NonZeroU32::new(90000).unwrap(),
        parameters: codec_params_params,
        rtcp_feedback: vec![],
    };
    let mut rtp_parameters = RtpParameters::default();
    rtp_parameters.codecs.push(codec_params);
    rtp_parameters
        .encodings
        .push(mediasoup::rtp_parameters::RtpEncodingParameters {
            ssrc: Some(0xed6f6d90),
            codec_payload_type: Some(96),
            max_bitrate: None,
            ..Default::default()
        });
    let rtp_capacities = RtpCapabilities {
        codecs: vec![codec_capacity.clone()],
        header_extensions: vec![],
    };

    // create plain transport to receive rtp
    let mut plain_transport_in_options = PlainTransportOptions::new(ListenInfo {
        protocol: Protocol::Udp,
        ip: std::net::IpAddr::V4(Ipv4Addr::UNSPECIFIED),
        announced_address: None,
        port: Some(5004),
        port_range: None,
        flags: None,
        send_buffer_size: None,
        recv_buffer_size: None,
    });
    plain_transport_in_options.comedia = true;
    let plain_transport_in = router
        .create_plain_transport(plain_transport_in_options)
        .await
        .expect("Failed to create plain transport for receiving");
    let plain_producer = plain_transport_in
        .produce(ProducerOptions::new(MediaKind::Video, rtp_parameters))
        .await
        .expect("Failed to create producer for receiving");
    // create plain transport to send rtp
    let plain_transport_out = router
        .create_plain_transport(PlainTransportOptions::new(ListenInfo {
            protocol: Protocol::Udp,
            ip: std::net::IpAddr::V4(Ipv4Addr::UNSPECIFIED),
            announced_address: None,
            port: None,
            port_range: None,
            flags: None,
            send_buffer_size: None,
            recv_buffer_size: None,
        }))
        .await
        .expect("Failed to create plain transport");
    plain_transport_out
        .connect(PlainTransportRemoteParameters {
            ip: Some(std::net::IpAddr::V4(Ipv4Addr::LOCALHOST)),
            port: Some(5005),
            srtp_parameters: None,
            rtcp_port: None,
        })
        .await
        .expect("Failed to connect plain transport");
    let plain_consumer_options = ConsumerOptions::new(plain_producer.id(), rtp_capacities.clone());
    let plain_consumer = plain_transport_out
        .consume(plain_consumer_options)
        .await
        .expect("Failed to consume on plain transport");
    // print stats
    loop {
        tracing::info!("plain_producer paused: {}", plain_producer.paused());
        tracing::info!("plain_producer closed: {}", plain_producer.closed());
        tracing::info!("plain_consumer paused: {}", plain_consumer.paused());
        tracing::info!("plain_consumer closed: {}", plain_consumer.closed());
        let stat = plain_transport_in
            .get_stats()
            .await
            .expect("Failed to get plain_transport_in stats");
        tracing::info!("plain_transport_in stats: {:?}", stat);
        let stat = plain_transport_out
            .get_stats()
            .await
            .expect("Failed to get plain_transport_out stats");
        tracing::info!("plain_transport_out stats: {:?}", stat);
        let producer_stats = plain_producer
            .get_stats()
            .await
            .expect("Failed to get plain_producer stats");
        tracing::info!("plain_producer stats: {:?}", producer_stats);
        let consumer_stats = plain_consumer
            .get_stats()
            .await
            .expect("Failed to get plain_consumer stats");
        tracing::info!("plain_consumer stats: {:?}", consumer_stats);
        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
    }

Stats in log:

2025-07-11T08:58:08.537102Z  INFO demo: 118: plain_producer paused: false
2025-07-11T08:58:08.537190Z  INFO demo: 119: plain_producer closed: false
2025-07-11T08:58:08.537205Z  INFO demo: 120: plain_consumer paused: false
2025-07-11T08:58:08.537211Z  INFO demo: 121: plain_consumer closed: false
2025-07-11T08:58:08.537685Z  INFO demo: 126: plain_transport_in stats: [PlainTransportStat { transport_id: TransportId(e6332f58-c217-429d-9981-2c35c6a05cea), timestamp: 20664290, sctp_state: None, bytes_received: 7494917, recv_bitrate: 5001104, bytes_sent: 624, send_bitrate: 416, rtp_bytes_received: 7600457, rtp_recv_bitrate: 5091834, rtp_bytes_sent: 0, rtp_send_bitrate: 0, rtx_bytes_received: 0, rtx_recv_bitrate: 0, rtx_bytes_sent: 0, rtx_send_bitrate: 0, probation_bytes_sent: 0, probation_send_bitrate: 0, available_outgoing_bitrate: None, available_incoming_bitrate: None, max_incoming_bitrate: None, max_outgoing_bitrate: None, min_outgoing_bitrate: None, rtp_packet_loss_received: None, rtp_packet_loss_sent: None, rtcp_mux: true, comedia: true, tuple: WithRemote { local_address: "0.0.0.0", local_port: 5004, remote_ip: 127.0.0.1, remote_port: 63273, protocol: Udp }, rtcp_tuple: None }]
2025-07-11T08:58:08.538090Z  INFO demo: 131: plain_transport_out stats: [PlainTransportStat { transport_id: TransportId(3b043e2a-6383-4524-ad94-c4882c77c945), timestamp: 20664290, sctp_state: None, bytes_received: 0, recv_bitrate: 0, bytes_sent: 0, send_bitrate: 0, rtp_bytes_received: 0, rtp_recv_bitrate: 0, rtp_bytes_sent: 0, rtp_send_bitrate: 0, rtx_bytes_received: 0, rtx_recv_bitrate: 0, rtx_bytes_sent: 0, rtx_send_bitrate: 0, probation_bytes_sent: 0, probation_send_bitrate: 0, available_outgoing_bitrate: None, available_incoming_bitrate: None, max_incoming_bitrate: None, max_outgoing_bitrate: None, min_outgoing_bitrate: None, rtp_packet_loss_received: None, rtp_packet_loss_sent: None, rtcp_mux: true, comedia: false, tuple: WithRemote { local_address: "0.0.0.0", local_port: 35683, remote_ip: 127.0.0.1, remote_port: 5005, protocol: Udp }, rtcp_tuple: None }]
2025-07-11T08:58:08.538425Z  INFO demo: 136: plain_producer stats: [ProducerStat { timestamp: 20664291, ssrc: 3983502736, rtx_ssrc: None, rid: None, kind: Video, mime_type: Video(H264), packets_lost: 0, fraction_lost: 0, packets_discarded: 0, packets_retransmitted: 0, packets_repaired: 0, nack_count: 0, nack_packet_count: 0, pli_count: 0, fir_count: 0, score: 10, packet_count: 5277, byte_count: 7494917, bitrate: 5021818, round_trip_time: Some(0.0), rtx_packets_discarded: Some(0), jitter: 19, bitrate_by_layer: [] }]
2025-07-11T08:58:08.538766Z  INFO demo: 141: plain_consumer stats: WithProducer((ConsumerStat { timestamp: 20664291, ssrc: 854211733, rtx_ssrc: None, kind: Video, mime_type: Video(H264), packets_lost: 0, fraction_lost: 0, packets_discarded: 0, packets_retransmitted: 0, packets_repaired: 0, nack_count: 0, nack_packet_count: 0, pli_count: 0, fir_count: 0, score: 10, packet_count: 0, byte_count: 0, bitrate: 0, round_trip_time: Some(0.0) }, ProducerStat { timestamp: 20664291, ssrc: 3983502736, rtx_ssrc: None, rid: None, kind: Video, mime_type: Video(H264), packets_lost: 0, fraction_lost: 0, packets_discarded: 0, packets_retransmitted: 0, packets_repaired: 0, nack_count: 0, nack_packet_count: 0, pli_count: 0, fir_count: 0, score: 10, packet_count: 5277, byte_count: 7494917, bitrate: 5021818, round_trip_time: Some(0.0), rtx_packets_discarded: Some(0), jitter: 19, bitrate_by_layer: [] }))

SDP info from FFMPEG

v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
c=IN IP4 127.0.0.1
t=0 0
a=tool:libavformat 59.34.101
m=video 5004 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z0LAKNoB4Ag7ARAAAAMAEAAAAwMA8YMq,aM48gA==; profile-level-id=42C028

What I’ve already tried

  • Check if the producer’s ssrc match the stream generated by FFMPEG – Ok
  • Check if the producer receive packets from log – Ok
  • Check if the consumer paused from log – Not paused
  • Check if the consumer send packets by tcpdump-- No any packet

My Questions:

  1. are consumer’s options wrong?
  2. how to know if consumer discard the data from producer?