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:
- are consumer’s options wrong?
- how to know if consumer discard the data from producer?