Compare commits

..

No commits in common. "2ec4c231c209528de3759eeb8ece0984fe8dcbdf" and "f0ee6a0444f4729d8bfa436c5c42c9492423431b" have entirely different histories.

13 changed files with 301 additions and 215 deletions

View File

@ -1,24 +1,14 @@
use core::{fmt::Display, str}; pub struct AtemPacket {
length: u16,
// TODO: we don't need itertools once https://github.com/rust-lang/rust/issues/79524 lands flags: u8,
use itertools::Itertools; session_id: u16,
remote_packet_id: u16,
/// The "hello" packet to start communication with the ATEM body: Vec<u8>,
pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
pub struct AtemPacket<T: AsRef<[u8]>> {
buf: T,
} }
#[derive(Debug)]
pub enum AtemPacketErr { pub enum AtemPacketErr {
/// The packet was too short TooShort(String),
TooShort { got: usize }, LengthDiffers(String),
/// The packet's stated and actual lengths were different
LengthDiffers { expected: u16, got: usize },
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@ -42,122 +32,64 @@ impl From<PacketFlag> for u8 {
} }
} }
impl<T: AsRef<[u8]>> AtemPacket<T> { impl AtemPacket {
pub fn new_checked(buf: T) -> Result<Self, AtemPacketErr> {
let len = buf.as_ref().len();
if len < 12 {
return Err(AtemPacketErr::TooShort {
got: buf.as_ref().len(),
});
}
let p = Self { buf };
if p.length() as usize != len {
return Err(AtemPacketErr::LengthDiffers {
expected: p.length(),
got: len,
});
}
Ok(p)
}
pub fn length(&self) -> u16 { pub fn length(&self) -> u16 {
u16::from_be_bytes(self.buf.as_ref()[0..=1].try_into().unwrap()) & 0x07ff self.length
} }
pub fn flags(&self) -> u8 { pub fn flags(&self) -> u8 {
self.buf.as_ref()[0] >> 3 self.flags
} }
pub fn session_id(&self) -> u16 { pub fn session_id(&self) -> u16 {
u16::from_be_bytes(self.buf.as_ref()[2..=3].try_into().unwrap()) self.session_id
}
pub fn ack_number(&self) -> u16 {
u16::from_be_bytes(self.buf.as_ref()[4..=5].try_into().unwrap())
}
pub fn remote_sequence_number(&self) -> u16 {
u16::from_be_bytes(self.buf.as_ref()[9..=10].try_into().unwrap())
} }
pub fn remote_packet_id(&self) -> u16 { pub fn remote_packet_id(&self) -> u16 {
u16::from_be_bytes(self.buf.as_ref()[10..=11].try_into().unwrap()) self.remote_packet_id
}
pub fn body(&self) -> Vec<u8> {
self.body.clone()
} }
/// Return true if this packet has the given [`PacketFlag`]
pub fn has_flag(&self, flag: PacketFlag) -> bool { pub fn has_flag(&self, flag: PacketFlag) -> bool {
self.flags() & u8::from(flag) > 0 self.flags & u8::from(flag) > 0
}
/// Get an iterator over the `Field`s in this packet.
///
/// Returns None if this is a packet without fields.
pub fn fields(&self) -> Option<Fields> {
// TODO: do we only ever get newsessionid during the handshake (i.e. not in a packet with fields)?
if self.has_flag(PacketFlag::NewSessionId) {
None
} else {
Some(Fields {
data: self.body(),
offset: 0,
})
}
}
pub fn body(&self) -> &[u8] {
&self.buf.as_ref()[12..]
} }
} }
#[cfg(feature = "std")] impl TryFrom<&[u8]> for AtemPacket {
impl<T: AsRef<[u8]>> Display for AtemPacket<T> { type Error = AtemPacketErr;
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"sid: {}, len: {}, fields: ({})",
self.session_id(),
self.length(),
self.fields()
.and_then(|f| Some(
f.map(|f| str::from_utf8(f.r#type).unwrap())
.intersperse(", ")
.collect::<String>()
))
.unwrap_or("none".into())
)
}
}
/// An ATEM protocol field - a 4-ascii character type plus variable length data fn try_from(buffer: &[u8]) -> Result<Self, Self::Error> {
pub struct Field<'a> { if buffer.len() < 12 {
r#type: &'a [u8; 4], return Err(AtemPacketErr::TooShort(format!(
data: &'a [u8], "Invalid packet from ATEM {:x?}",
} buffer
)));
/// Created by [`AtemPacket::fields`]
pub struct Fields<'a> {
data: &'a [u8],
offset: usize,
}
impl<'a> Iterator for Fields<'a> {
type Item = Field<'a>;
fn next(&mut self) -> Option<Self::Item> {
let remain = self.data.len() - self.offset;
if remain == 0 {
return None;
} else if remain < 8 {
// TODO: is 8 indeed the minimum size for something here? (i.e. no field data)
panic!("Oh no");
} }
let length = let length = u16::from_be_bytes(buffer[0..2].try_into().unwrap()) & 0x07ff;
u16::from_be_bytes(self.data[self.offset..=self.offset + 1].try_into().unwrap()); if length as usize != buffer.len() {
// TODO: sanity check length return Err(AtemPacketErr::LengthDiffers(format!(
let r#type: &[u8; 4] = self.data[self.offset + 4..=self.offset + 7] "Length of message differs, expected {} got {}",
.try_into() length,
.unwrap(); buffer.len()
let data = &self.data[self.offset + 8..self.offset + (length as usize)]; )));
self.offset += (length as usize); }
Some(Field { r#type, data })
let flags = buffer[0] >> 3;
let session_id = u16::from_be_bytes(buffer[2..4].try_into().unwrap());
let remote_packet_id = u16::from_be_bytes(buffer[10..12].try_into().unwrap());
let body = buffer[12..].to_vec();
Ok(AtemPacket {
length,
flags,
session_id,
remote_packet_id,
body,
})
} }
} }

View File

@ -1,4 +1,3 @@
use core::net::SocketAddr;
use std::{io, sync::Arc, thread::yield_now}; use std::{io, sync::Arc, thread::yield_now};
use tokio::{sync::RwLock, task::JoinHandle}; use tokio::{sync::RwLock, task::JoinHandle};
@ -18,8 +17,8 @@ pub enum AtemSocketConnectionError {
} }
impl AtemSocket { impl AtemSocket {
pub fn new<A: Into<SocketAddr>>(addr: A) -> Self { pub fn new() -> Self {
let socket = AtemSocketInner::new(addr.into()); let socket = AtemSocketInner::new();
let socket = Arc::new(RwLock::new(socket)); let socket = Arc::new(RwLock::new(socket));
let socket_clone = Arc::clone(&socket); let socket_clone = Arc::clone(&socket);
@ -38,8 +37,12 @@ impl AtemSocket {
} }
} }
pub async fn connect(&mut self) -> Result<(), AtemSocketConnectionError> { pub async fn connect(
self.socket.write().await.connect().await?; &mut self,
address: String,
port: u16,
) -> Result<(), AtemSocketConnectionError> {
self.socket.write().await.connect(address, port).await?;
Ok(()) Ok(())
} }
@ -57,3 +60,9 @@ impl AtemSocket {
.await; .await;
} }
} }
impl Default for AtemSocket {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,13 +1,13 @@
use core::net::SocketAddr;
use std::{ use std::{
io, io,
net::SocketAddr,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
use log::{debug, trace}; use log::debug;
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use crate::atem_lib::atem_packet::{AtemPacket, PacketFlag, COMMAND_CONNECT_HELLO}; use crate::atem_lib::atem_util;
const IN_FLIGHT_TIMEOUT: u64 = 60; const IN_FLIGHT_TIMEOUT: u64 = 60;
const CONNECTION_TIMEOUT: u64 = 5000; const CONNECTION_TIMEOUT: u64 = 5000;
@ -39,6 +39,27 @@ impl Into<u8> for ConnectionState {
} }
} }
#[derive(PartialEq)]
enum PacketFlag {
AckRequest,
NewSessionId,
IsRetransmit,
RetransmitRequest,
AckReply,
}
impl From<PacketFlag> for u8 {
fn from(flag: PacketFlag) -> Self {
match flag {
PacketFlag::AckRequest => 0x01,
PacketFlag::NewSessionId => 0x02,
PacketFlag::IsRetransmit => 0x04,
PacketFlag::RetransmitRequest => 0x08,
PacketFlag::AckReply => 0x10,
}
}
}
#[derive(Clone)] #[derive(Clone)]
struct InFlightPacket { struct InFlightPacket {
packet_id: u16, packet_id: u16,
@ -68,7 +89,8 @@ pub struct AtemSocketInner {
session_id: u16, session_id: u16,
socket: Option<UdpSocket>, socket: Option<UdpSocket>,
address: SocketAddr, address: String,
port: u16,
last_received_at: SystemTime, last_received_at: SystemTime,
last_received_packed_id: u16, last_received_packed_id: u16,
@ -91,7 +113,7 @@ enum AtemSocketWriteError {
} }
impl AtemSocketInner { impl AtemSocketInner {
pub fn new(addr: SocketAddr) -> Self { pub fn new() -> Self {
AtemSocketInner { AtemSocketInner {
connection_state: ConnectionState::Closed, connection_state: ConnectionState::Closed,
reconnect_timer: None, reconnect_timer: None,
@ -101,7 +123,8 @@ impl AtemSocketInner {
session_id: 0, session_id: 0,
socket: None, socket: None,
address: addr, address: "0.0.0.0".to_string(),
port: 0,
last_received_at: SystemTime::now(), last_received_at: SystemTime::now(),
last_received_packed_id: 0, last_received_packed_id: 0,
@ -111,9 +134,15 @@ impl AtemSocketInner {
} }
} }
pub async fn connect(&mut self) -> Result<(), io::Error> { pub async fn connect(&mut self, address: String, port: u16) -> Result<(), io::Error> {
self.address = address.clone();
self.port = port;
let socket = UdpSocket::bind("0.0.0.0:0").await?; let socket = UdpSocket::bind("0.0.0.0:0").await?;
socket.connect(self.address).await?; let remote_addr = format!("{}:{}", address, port)
.parse::<SocketAddr>()
.unwrap();
socket.connect(remote_addr).await?;
self.socket = Some(socket); self.socket = Some(socket);
self.start_timers(); self.start_timers();
@ -123,7 +152,7 @@ impl AtemSocketInner {
self.in_flight = vec![]; self.in_flight = vec![];
debug!("Reconnect"); debug!("Reconnect");
self.send_packet(&COMMAND_CONNECT_HELLO).await; self.send_packet(&atem_util::COMMAND_CONNECT_HELLO).await;
self.connection_state = ConnectionState::SynSent; self.connection_state = ConnectionState::SynSent;
Ok(()) Ok(())
@ -189,7 +218,7 @@ impl AtemSocketInner {
async fn restart_connection(&mut self) { async fn restart_connection(&mut self) {
self.disconnect(); self.disconnect();
self.connect().await.ok(); self.connect(self.address.clone(), self.port).await.ok();
} }
pub async fn tick(&mut self) { pub async fn tick(&mut self) {
@ -248,23 +277,30 @@ impl AtemSocketInner {
} }
async fn recieved_packet(&mut self, packet: &[u8]) { async fn recieved_packet(&mut self, packet: &[u8]) {
trace!("RX Raw: {:x?}", packet); debug!("RECV {:x?}", packet);
let checked = match AtemPacket::new_checked(packet) { if packet.len() < 12 {
Ok(p) => p, debug!("Invalid packet from ATEM {:x?}", packet);
Err(e) => { return;
debug!("Invalid packet ({:?}): {:x?}", e, packet); }
return;
}
};
debug!("RX: {}", checked);
self.last_received_at = SystemTime::now(); self.last_received_at = SystemTime::now();
let length = u16::from_be_bytes(packet[0..2].try_into().unwrap()) & 0x07ff;
self.session_id = checked.session_id(); if length as usize != packet.len() {
let remote_packet_id = checked.remote_packet_id(); debug!(
"Length of message differs, expected {} got {}",
length,
packet.len()
);
return;
}
if checked.flags() & u8::from(PacketFlag::NewSessionId) > 0 { let flags = packet[0] >> 3;
self.session_id = u16::from_be_bytes(packet[2..4].try_into().unwrap());
let remote_packet_id = u16::from_be_bytes(packet[10..12].try_into().unwrap());
if flags & u8::from(PacketFlag::NewSessionId) > 0 {
debug!("New session"); debug!("New session");
self.connection_state = ConnectionState::Established; self.connection_state = ConnectionState::Established;
self.last_received_packed_id = remote_packet_id; self.last_received_packed_id = remote_packet_id;
@ -273,20 +309,20 @@ impl AtemSocketInner {
} }
if self.connection_state == ConnectionState::Established { if self.connection_state == ConnectionState::Established {
if checked.has_flag(PacketFlag::RetransmitRequest) { if flags & u8::from(PacketFlag::RetransmitRequest) > 0 {
let from_packet_id = u16::from_be_bytes(packet[6..8].try_into().unwrap()); let from_packet_id = u16::from_be_bytes(packet[6..8].try_into().unwrap());
debug!("Retransmit request: {:x?}", from_packet_id); debug!("Retransmit request: {:x?}", from_packet_id);
self.retransmit_from(from_packet_id).await; self.retransmit_from(from_packet_id).await;
} }
if checked.has_flag(PacketFlag::AckRequest) { if flags & u8::from(PacketFlag::AckRequest) > 0 {
if remote_packet_id == (self.last_received_packed_id + 1) % MAX_PACKET_ID { if remote_packet_id == (self.last_received_packed_id + 1) % MAX_PACKET_ID {
self.last_received_packed_id = remote_packet_id; self.last_received_packed_id = remote_packet_id;
self.send_or_queue_ack().await; self.send_or_queue_ack().await;
if checked.length() > 12 { if length > 12 {
self.on_command_received(checked.body(), remote_packet_id); self.on_command_received(&packet[12..], remote_packet_id);
} }
} else if self } else if self
.is_packet_covered_by_ack(self.last_received_packed_id, remote_packet_id) .is_packet_covered_by_ack(self.last_received_packed_id, remote_packet_id)
@ -295,12 +331,12 @@ impl AtemSocketInner {
} }
} }
if checked.has_flag(PacketFlag::IsRetransmit) { if flags & u8::from(PacketFlag::IsRetransmit) > 0 {
debug!("ATEM retransmitted packet {:x?}", remote_packet_id); debug!("ATEM retransmitted packet {:x?}", remote_packet_id);
} }
if checked.has_flag(PacketFlag::AckReply) { if flags & u8::from(PacketFlag::AckReply) > 0 {
let ack_packet_id = checked.ack_number(); let ack_packet_id = u16::from_be_bytes(packet[4..6].try_into().unwrap());
let mut acked_commands: Vec<AckedPacket> = vec![]; let mut acked_commands: Vec<AckedPacket> = vec![];
self.in_flight = self self.in_flight = self

View File

@ -0,0 +1,4 @@
pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];

View File

@ -1,3 +1,4 @@
pub mod atem_packet; mod atem_packet;
pub mod atem_socket; pub mod atem_socket;
mod atem_socket_inner; mod atem_socket_inner;
pub mod atem_util;

View File

@ -2,5 +2,5 @@
pub struct ColorGeneratorState { pub struct ColorGeneratorState {
pub hue: u64, pub hue: u64,
pub saturation: u64, pub saturation: u64,
pub luma: u64, pub luma: u64
} }

View File

@ -1,17 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::enums::{ use crate::enums::{ExternalPortType, FairlightAnalogInputLevel, FairlightAudioMixOption, FairlightAudioSourceType, FairlightInputConfiguration, FairlightInputType};
ExternalPortType, FairlightAnalogInputLevel, FairlightAudioMixOption, FairlightAudioSourceType,
FairlightInputConfiguration, FairlightInputType,
};
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioDynamicsState { pub struct FairlightAudioDynamicsState {
pub make_up_gain: Option<u64>, pub make_up_gain: Option<u64>,
pub limiter: Option<FairlightAudioLimiterState>, pub limiter: Option<FairlightAudioLimiterState>,
pub compressor: Option<FairlightAudioCompressorState>, pub compressor: Option<FairlightAudioCompressorState>,
pub expander: Option<FairlightAudioExpanderState>, pub expander: Option<FairlightAudioExpanderState>
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -20,7 +17,7 @@ pub struct FairlightAudioLimiterState {
pub threshold: u64, pub threshold: u64,
pub attack: u64, pub attack: u64,
pub hold: u64, pub hold: u64,
pub release: u64, pub release: u64
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -30,7 +27,7 @@ pub struct FairlightAudioCompressorState {
pub ratio: u64, pub ratio: u64,
pub attack: u64, pub attack: u64,
pub hold: u64, pub hold: u64,
pub release: u64, pub release: u64
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -42,7 +39,7 @@ pub struct FairlightAudioExpanderState {
pub ratio: u64, pub ratio: u64,
pub attack: u64, pub attack: u64,
pub hold: u64, pub hold: u64,
pub release: u64, pub release: u64
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -50,22 +47,22 @@ pub struct FairlightAudioEqualizerBandState {
pub band_enabled: bool, pub band_enabled: bool,
supported_shapes: Vec<u64>, // TODO supported_shapes: Vec<u64>, // TODO
pub shape: u64, // TODO pub shape: u64, // TODO
supported_frequency_ranges: Vec<u64>, // TODO supported_frequency_ranges: Vec<u64>, // TODO
pub frequency_ranges: u64, // TODO pub frequency_ranges: u64, // TODO
pub frequency: u64, pub frequency: u64,
pub gain: u64, pub gain: u64,
pub q_factor: u64, pub q_factor: u64
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioMasterChannelPropertiesState { pub struct FairlightAudioMasterChannelPropertiesState {
// Gain in decibel, -Infinity to +6dB // Gain in decibel, -Infinity to +6dB
pub fader_gain: u64, pub fader_gain: u64,
pub follow_fade_to_black: bool, pub follow_fade_to_black: bool
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -81,21 +78,21 @@ pub struct FairlightAudioMonitorChannel {
pub gain: u64, pub gain: u64,
pub input_master_gain: u64, pub input_master_gain: u64,
pub input_talkback_gain: u64, pub input_talkback_gain: u64,
pub input_sidetone_gain: u64, pub input_sidetone_gain: u64
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioSource { pub struct FairlightAudioSource {
pub properties: Option<FairlightAudioSourcePropertiesState>, pub properties: Option<FairlightAudioSourcePropertiesState>,
pub equalizer: Option<FairlightAudioEqualizerState>, pub equalizer: Option<FairlightAudioEqualizerState>,
pub dynamics: Option<FairlightAudioDynamicsState>, pub dynamics: Option<FairlightAudioDynamicsState>
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioEqualizerState { pub struct FairlightAudioEqualizerState {
pub enabled: bool, pub enabled: bool,
pub gain: u64, pub gain: u64,
bands: Vec<FairlightAudioEqualizerBandState>, bands: Vec<FairlightAudioEqualizerBandState>
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -113,13 +110,13 @@ pub struct FairlightAudioSourcePropertiesState {
pub fader_gain: u64, pub fader_gain: u64,
supported_mix_options: Vec<FairlightAudioMixOption>, supported_mix_options: Vec<FairlightAudioMixOption>,
pub mix_option: FairlightAudioMixOption, pub mix_option: FairlightAudioMixOption
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioInput { pub struct FairlightAudioInput {
pub properties: Option<FairlightAudioInputProperties>, pub properties: Option<FairlightAudioInputProperties>,
pub sources: HashMap<String, FairlightAudioSource>, pub sources: HashMap<String, FairlightAudioSource>
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -131,7 +128,7 @@ pub struct FairlightAudioInputProperties {
pub active_configuration: FairlightInputConfiguration, pub active_configuration: FairlightInputConfiguration,
supported_input_levels: Vec<FairlightAnalogInputLevel>, supported_input_levels: Vec<FairlightAnalogInputLevel>,
pub activeInputLevel: FairlightAnalogInputLevel, pub activeInputLevel: FairlightAnalogInputLevel
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -140,5 +137,5 @@ pub struct AtemFairlightAudioState {
pub master: Option<FairlightAudioMasterChannel>, pub master: Option<FairlightAudioMasterChannel>,
pub monitor: Option<FairlightAudioMonitorChannel>, pub monitor: Option<FairlightAudioMonitorChannel>,
pub audio_follow_video_crossfade_transition_enabled: Option<bool>, pub audio_follow_video_crossfade_transition_enabled: Option<bool>
} }

View File

@ -10,5 +10,5 @@ pub struct InputChannel {
pub external_port_type: ExternalPortType, pub external_port_type: ExternalPortType,
internal_port_type: InternalPortType, internal_port_type: InternalPortType,
source_availability: SourceAvailability, source_availability: SourceAvailability,
me_availability: MeAvailability, me_availability: MeAvailability
} }

View File

@ -11,7 +11,7 @@ pub struct RecordingState {
pub duration: Option<Timecode>, pub duration: Option<Timecode>,
pub disks: HashMap<u64, RecordingDiskProperties>, pub disks: HashMap<u64, RecordingDiskProperties>
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -19,7 +19,7 @@ pub struct RecordingDiskProperties {
pub disk_id: u64, pub disk_id: u64,
pub volume_name: String, pub volume_name: String,
pub recording_time_available: u64, pub recording_time_available: u64,
pub status: RecordingDiskStatus, pub status: RecordingDiskStatus
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -27,7 +27,7 @@ pub struct RecordingStateStatus {
pub state: RecordingStatus, pub state: RecordingStatus,
pub error: RecordingError, pub error: RecordingError,
pub recording_time_available: u64, pub recording_time_available: u64
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -37,5 +37,5 @@ pub struct RecordingStateProperties {
pub working_set_1_disk_id: u64, pub working_set_1_disk_id: u64,
pub working_set_2_disk_id: u64, pub working_set_2_disk_id: u64,
pub record_in_all_cameras: bool, pub record_in_all_cameras: bool
} }

View File

@ -8,19 +8,19 @@ pub struct StreamingState {
pub stats: Option<StreamingStateStats>, pub stats: Option<StreamingStateStats>,
pub service: StreamingServiceProperties, pub service: StreamingServiceProperties,
pub duration: Option<Timecode>, pub duration: Option<Timecode>
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct StreamingStateStatus { pub struct StreamingStateStatus {
state: StreamingStatus, state: StreamingStatus,
error: StreamingError, error: StreamingError
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct StreamingStateStats { pub struct StreamingStateStats {
cache_used: u64, cache_used: u64,
encoding_bitrate: u64, encoding_bitrate: u64
} }
pub struct StreamingServiceProperties { pub struct StreamingServiceProperties {
@ -28,5 +28,5 @@ pub struct StreamingServiceProperties {
pub url: String, pub url: String,
pub key: String, pub key: String,
bitrates: (u64, u64), bitrates: (u64, u64)
} }

View File

@ -1,4 +1,4 @@
use std::{env::args, net::Ipv4Addr, time::Duration}; use std::time::Duration;
use atem_connection_rs::{ use atem_connection_rs::{
atem_lib::atem_socket::AtemSocket, atem_lib::atem_socket::AtemSocket,
@ -18,11 +18,8 @@ async fn main() {
let switch_to_source_1 = ProgramInput::new(0, 1); let switch_to_source_1 = ProgramInput::new(0, 1);
let switch_to_source_2 = ProgramInput::new(0, 2); let switch_to_source_2 = ProgramInput::new(0, 2);
let mut atem = AtemSocket::new(( let mut atem = AtemSocket::new();
args().skip(1).next().unwrap().parse::<Ipv4Addr>().unwrap(), atem.connect("127.0.0.1".to_string(), 9910).await.ok();
9910,
));
atem.connect().await.ok();
let mut tracking_id = 0; let mut tracking_id = 0;
loop { loop {

View File

@ -1,15 +1,70 @@
{ {
"nodes": { "nodes": {
"naersk": { "devshell": {
"inputs": { "inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1745925850, "lastModified": 1705332421,
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=", "narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=",
"owner": "numtide",
"repo": "devshell",
"rev": "83cb93d6d063ad290beee669f4badf9914cc16ec",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1698420672,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f", "rev": "aeb58d5e8faead8980a807c840232697982d47b9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -20,11 +75,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1749401433, "lastModified": 1704161960,
"narHash": "sha256-HXIQzULIG/MEUW2Q/Ss47oE3QrjxvpUX7gUl4Xp6lnc=", "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "08fcb0dcb59df0344652b38ea6326a2d8271baff", "rev": "63143ac2c9186be6d9da6035fa22620018c85932",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -36,11 +91,12 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1733412085, "lastModified": 1705883077,
"narHash": "sha256-FillH0qdWDt/nlO6ED7h4cmN+G9uXwGjwmCnHs0QVYM=", "narHash": "sha256-ByzHHX3KxpU1+V0erFy8jpujTufimh6KaS/Iv3AciHk=",
"path": "/nix/store/5wvrlpfrwlmg24ywkybvidyzcvki6bwx-source", "owner": "NixOS",
"rev": "4dc2fc4e62dbf62b84132fe526356fbac7b03541", "repo": "nixpkgs",
"type": "path" "rev": "5f5210aa20e343b7e35f40c033000db0ef80d7b9",
"type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
@ -49,11 +105,25 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1744536153, "lastModified": 1705883077,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", "narHash": "sha256-ByzHHX3KxpU1+V0erFy8jpujTufimh6KaS/Iv3AciHk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", "rev": "5f5210aa20e343b7e35f40c033000db0ef80d7b9",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1681358109,
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -65,22 +135,24 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"devshell": "devshell",
"naersk": "naersk", "naersk": "naersk",
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_3",
"rust-overlay": "rust-overlay", "rust-overlay": "rust-overlay",
"utils": "utils" "utils": "utils"
} }
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_3" "flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_4"
}, },
"locked": { "locked": {
"lastModified": 1749436897, "lastModified": 1705976279,
"narHash": "sha256-OkDtaCGQQVwVFz5HWfbmrMJR99sFIMXHCHEYXzUJEJY=", "narHash": "sha256-Zx97bJ3+O8IP70uJPD//rRsr8bcxICISMTZUT/L9eFk=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "e7876c387e35dc834838aff254d8e74cf5bd4f19", "rev": "f889dc31ef97835834bdc3662394ebdb3c96b974",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -104,16 +176,46 @@
"type": "github" "type": "github"
} }
}, },
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": { "utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems_3"
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1705309234,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -3,6 +3,7 @@
inputs = { inputs = {
utils.url = "github:numtide/flake-utils"; utils.url = "github:numtide/flake-utils";
devshell.url = "github:numtide/devshell";
naersk.url = "github:nix-community/naersk"; naersk.url = "github:nix-community/naersk";
rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.url = "github:oxalica/rust-overlay";
}; };
@ -12,6 +13,7 @@
nixpkgs, nixpkgs,
utils, utils,
naersk, naersk,
devshell,
rust-overlay, rust-overlay,
}: }:
utils.lib.eachDefaultSystem (system: let utils.lib.eachDefaultSystem (system: let
@ -33,10 +35,16 @@
apps.default = utils.lib.mkApp {drv = packages.default;}; apps.default = utils.lib.mkApp {drv = packages.default;};
devShells.default = pkgs.mkShell { # Provide a dev env with rust and rust-analyzer
packages = with pkgs; [(rust.override {extensions = ["rust-src"];}) rust-analyzer gcc]; devShells.default = let
pkgs = import nixpkgs {
inherit system;
overlays = [devshell.overlays.default];
}; };
formatter = pkgs.nixfmt-rfc-style; in
pkgs.devshell.mkShell {
packages = with pkgs; [(rust.override {extensions = ["rust-src"];}) rust-analyzer gcc];
};
formatter = pkgs.alejandra;
}); });
} }