2e34bf927e
As described in #47 and #58: revamp this type to keep the full raw packet, and provide accessors rather than raw fields. It's also smaller now, which is nice.
138 lines
5.0 KiB
Rust
138 lines
5.0 KiB
Rust
// Copyright (C) 2021 Scott Lamb <slamb@slamb.org>
|
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
//! Tests client performance against a mock server.
|
|
|
|
use std::{io::ErrorKind, net::SocketAddr, num::NonZeroU32};
|
|
|
|
use bytes::Bytes;
|
|
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
|
|
use futures::StreamExt;
|
|
use retina::{
|
|
client::{PlayOptions, SetupOptions},
|
|
codec::CodecItem,
|
|
};
|
|
use std::convert::TryFrom;
|
|
use tokio::io::AsyncWriteExt;
|
|
use url::Url;
|
|
|
|
/// Mock server. Serves `data` on every connection.
|
|
async fn serve(data: Bytes) -> SocketAddr {
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
tokio::task::spawn(async move {
|
|
loop {
|
|
let conn = match listener.accept().await {
|
|
Err(_) => return,
|
|
Ok((conn, _addr)) => conn,
|
|
};
|
|
tokio::task::spawn(serve_connection(conn, data.clone()));
|
|
}
|
|
});
|
|
addr
|
|
}
|
|
|
|
/// Mock server connection handler.
|
|
///
|
|
/// Tries to send the canned data. Doesn't even try to read any data from the
|
|
/// client until the end. This assumes the total data written from client fits
|
|
/// within the socket buffer; it might cause deadlock otherwise.
|
|
async fn serve_connection(mut stream: tokio::net::TcpStream, data: Bytes) {
|
|
// Send.
|
|
if stream.write_all(&data[..]).await.is_err() {
|
|
return;
|
|
}
|
|
|
|
// Wait until the client has read it all and closes the connection.
|
|
// This reads and discards whatever the client sent earlier.
|
|
if stream.shutdown().await.is_err() {
|
|
return;
|
|
}
|
|
loop {
|
|
if stream.readable().await.is_err() {
|
|
return;
|
|
}
|
|
let mut buf = [0u8; 1024];
|
|
match stream.try_read(&mut buf) {
|
|
Err(e) if e.kind() == ErrorKind::WouldBlock => {}
|
|
Err(_) | Ok(0) => return,
|
|
Ok(_) => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn make_test_data(max_payload_size: u16) -> Bytes {
|
|
let mut data = Vec::new();
|
|
data.extend_from_slice(include_bytes!(
|
|
"../src/client/testdata/hikvision_describe.txt"
|
|
));
|
|
data.extend_from_slice(include_bytes!("../src/client/testdata/hikvision_setup.txt"));
|
|
data.extend_from_slice(include_bytes!("../src/client/testdata/hikvision_play.txt"));
|
|
|
|
// These are the actual sizes of the slice NALs in one (two-second, 60-frame) Group of Pictures
|
|
// from a Dahua IP camera. This is about 3.5 Mbps.
|
|
let frame_sizes: [u32; 60] = [
|
|
667339, 7960, 2296, 3153, 3687, 3246, 3621, 2300, 2603, 2956, 2888, 3187, 3439, 3299, 3252,
|
|
3372, 3052, 2988, 2921, 2859, 2806, 3400, 2811, 3143, 2972, 4097, 2906, 3307, 3628, 3750,
|
|
3575, 3144, 3431, 3317, 3154, 3387, 3630, 3232, 3574, 3254, 4198, 4235, 4898, 4890, 4854,
|
|
4854, 4863, 4652, 4227, 4101, 3867, 3870, 3716, 3074, 3253, 3267, 3192, 3995, 3904, 3781,
|
|
];
|
|
let mut dummy_frame = vec![0; 1048576];
|
|
dummy_frame[4] = h264_reader::nal::UnitType::SliceLayerWithoutPartitioningIdr.id();
|
|
let mut p =
|
|
retina::codec::h264::Packetizer::new(max_payload_size, 0, 24104, 96, 0x4cacc3d1).unwrap();
|
|
let mut timestamp = retina::Timestamp::new(0, NonZeroU32::new(90_000).unwrap(), 0).unwrap();
|
|
for _ in 0..30 {
|
|
for &f in &frame_sizes {
|
|
dummy_frame[0..4].copy_from_slice(&f.to_be_bytes()[..]);
|
|
let frame = Bytes::copy_from_slice(&dummy_frame[..(usize::try_from(f).unwrap() + 4)]);
|
|
p.push(timestamp, frame).unwrap();
|
|
while let Some(pkt) = p.pull().unwrap() {
|
|
let pkt = pkt.raw();
|
|
data.push(b'$'); // interleaved data
|
|
data.push(0); // channel 0
|
|
data.extend_from_slice(&u16::try_from(pkt.len()).unwrap().to_be_bytes()[..]);
|
|
data.extend_from_slice(&pkt);
|
|
}
|
|
timestamp = timestamp.try_add(3000).unwrap();
|
|
}
|
|
}
|
|
Bytes::from(data)
|
|
}
|
|
|
|
async fn read_to_eof(addr: SocketAddr) {
|
|
let url = Url::parse(&format!("rtsp://{}/", &addr)).unwrap();
|
|
let mut session =
|
|
retina::client::Session::describe(url, retina::client::SessionOptions::default())
|
|
.await
|
|
.unwrap();
|
|
session.setup(0, SetupOptions::default()).await.unwrap();
|
|
let session = session
|
|
.play(PlayOptions::default())
|
|
.await
|
|
.unwrap()
|
|
.demuxed()
|
|
.unwrap();
|
|
tokio::pin!(session);
|
|
let mut i = 0;
|
|
while let Some(item) = session.next().await {
|
|
match item {
|
|
Ok(CodecItem::VideoFrame(_)) => i += 1,
|
|
o => panic!("bad item: {:#?}", o),
|
|
}
|
|
}
|
|
assert_eq!(i, 30 * 60);
|
|
}
|
|
|
|
fn criterion_benchmark(c: &mut Criterion) {
|
|
let mut g = c.benchmark_group("client");
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
let data = make_test_data(1440);
|
|
g.throughput(Throughput::Bytes(u64::try_from(data.len()).unwrap()));
|
|
let addr = rt.block_on(serve(data));
|
|
g.bench_function("h264", |b| b.to_async(&rt).iter(|| read_to_eof(addr)));
|
|
}
|
|
|
|
criterion_group!(benches, criterion_benchmark);
|
|
criterion_main!(benches);
|