openigtlink_rust/protocol/types/video.rs
1//! VIDEO message type implementation
2//!
3//! The VIDEO message is used to transfer video frame data for real-time visualization
4//! during image-guided procedures.
5//!
6//! # Use Cases
7//!
8//! - **Endoscopic Surgery** - Streaming laparoscopic or arthroscopic camera feeds
9//! - **Microscopy** - Real-time surgical microscope video during neurosurgery
10//! - **External Cameras** - Room cameras for documenting procedures
11//! - **Ultrasound Video** - Color Doppler or B-mode ultrasound video streams
12//! - **Robotic Surgery** - Multiple camera angles from surgical robots
13//!
14//! # Supported Codecs
15//!
16//! - **H.264 (AVC)** - Efficient compression for high-quality video
17//! - **MJPEG** - Motion JPEG for lower latency
18//! - **VP9** - Open codec alternative
19//! - **Raw/Uncompressed** - Maximum quality, higher bandwidth
20//!
21//! # Examples
22//!
23//! ## Streaming Laparoscopic Video (H.264)
24//!
25//! ```no_run
26//! use openigtlink_rust::protocol::types::{VideoMessage, VideoCodec};
27//! use openigtlink_rust::io::IgtlClient;
28//!
29//! let mut client = IgtlClient::connect("127.0.0.1:18944")?;
30//!
31//! let mut video = VideoMessage::new();
32//! video.set_device_name("LaparoscopicCamera");
33//! video.set_codec(VideoCodec::H264);
34//! video.set_width(1920);
35//! video.set_height(1080);
36//! video.set_frame_rate(30);
37//!
38//! // Simulated H.264 frame data
39//! let frame_data = vec![0u8; 50000]; // Compressed frame
40//! video.set_frame_data(frame_data);
41//!
42//! client.send(&video)?;
43//! # Ok::<(), openigtlink_rust::IgtlError>(())
44//! ```
45//!
46//! ## Receiving MJPEG Surgical Video
47//!
48//! ```no_run
49//! use openigtlink_rust::io::IgtlServer;
50//! use openigtlink_rust::protocol::types::{VideoMessage, VideoCodec};
51//! use openigtlink_rust::protocol::message::Message;
52//!
53//! let server = IgtlServer::bind("0.0.0.0:18944")?;
54//! let mut client_conn = server.accept()?;
55//!
56//! loop {
57//! let message = client_conn.receive()?;
58//!
59//! if message.header.message_type == "VIDEO" {
60//! let video = VideoMessage::from_bytes(&message.body)?;
61//! println!("Video frame: {}x{} @ {}fps",
62//! video.width, video.height, video.frame_rate);
63//!
64//! if video.codec == VideoCodec::Mjpeg {
65//! // Decode MJPEG frame
66//! println!("MJPEG frame size: {} bytes", video.frame_data.len());
67//! }
68//! }
69//! }
70//! # Ok::<(), openigtlink_rust::IgtlError>(())
71//! ```
72
73use crate::protocol::message::Message;
74use crate::error::{IgtlError, Result};
75use bytes::{Buf, BufMut};
76
77/// Video codec type
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum CodecType {
80 H264 = 1,
81 VP9 = 2,
82 HEVC = 3,
83 MJPEG = 4,
84 Raw = 5,
85}
86
87impl CodecType {
88 /// Create from codec value
89 pub fn from_u8(value: u8) -> Result<Self> {
90 match value {
91 1 => Ok(CodecType::H264),
92 2 => Ok(CodecType::VP9),
93 3 => Ok(CodecType::HEVC),
94 4 => Ok(CodecType::MJPEG),
95 5 => Ok(CodecType::Raw),
96 _ => Err(IgtlError::InvalidSize {
97 expected: 1,
98 actual: value as usize,
99 }),
100 }
101 }
102}
103
104/// VIDEO message for video frame data
105///
106/// # OpenIGTLink Specification
107/// - Message type: "VIDEO"
108/// - Format: CODEC (uint8) + WIDTH (uint16) + HEIGHT (uint16) + Reserved (uint8) + Frame data
109/// - Header size: 6 bytes + variable frame data
110#[derive(Debug, Clone, PartialEq)]
111pub struct VideoMessage {
112 /// Video codec type
113 pub codec: CodecType,
114 /// Frame width in pixels
115 pub width: u16,
116 /// Frame height in pixels
117 pub height: u16,
118 /// Encoded frame data
119 pub frame_data: Vec<u8>,
120}
121
122impl VideoMessage {
123 /// Create a new VIDEO message
124 pub fn new(
125 codec: CodecType,
126 width: u16,
127 height: u16,
128 frame_data: Vec<u8>,
129 ) -> Self {
130 VideoMessage {
131 codec,
132 width,
133 height,
134 frame_data,
135 }
136 }
137
138 /// Get frame size in bytes
139 pub fn frame_size(&self) -> usize {
140 self.frame_data.len()
141 }
142
143 /// Check if frame is empty
144 pub fn is_empty(&self) -> bool {
145 self.frame_data.is_empty()
146 }
147}
148
149impl Message for VideoMessage {
150 fn message_type() -> &'static str {
151 "VIDEO"
152 }
153
154 fn encode_content(&self) -> Result<Vec<u8>> {
155 let mut buf = Vec::with_capacity(6 + self.frame_data.len());
156
157 // Encode CODEC (uint8)
158 buf.put_u8(self.codec as u8);
159
160 // Encode WIDTH (uint16)
161 buf.put_u16(self.width);
162
163 // Encode HEIGHT (uint16)
164 buf.put_u16(self.height);
165
166 // Encode Reserved (uint8)
167 buf.put_u8(0);
168
169 // Encode frame data
170 buf.extend_from_slice(&self.frame_data);
171
172 Ok(buf)
173 }
174
175 fn decode_content(mut data: &[u8]) -> Result<Self> {
176 if data.len() < 6 {
177 return Err(IgtlError::InvalidSize {
178 expected: 6,
179 actual: data.len(),
180 });
181 }
182
183 // Decode CODEC (uint8)
184 let codec = CodecType::from_u8(data.get_u8())?;
185
186 // Decode WIDTH (uint16)
187 let width = data.get_u16();
188
189 // Decode HEIGHT (uint16)
190 let height = data.get_u16();
191
192 // Decode Reserved (uint8)
193 let _reserved = data.get_u8();
194
195 // Decode frame data (remaining bytes)
196 let frame_data = data.to_vec();
197
198 Ok(VideoMessage {
199 codec,
200 width,
201 height,
202 frame_data,
203 })
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_message_type() {
213 assert_eq!(VideoMessage::message_type(), "VIDEO");
214 }
215
216 #[test]
217 fn test_new() {
218 let frame = vec![0u8; 1920 * 1080 * 3]; // RGB frame
219 let msg = VideoMessage::new(CodecType::Raw, 1920, 1080, frame);
220
221 assert_eq!(msg.width, 1920);
222 assert_eq!(msg.height, 1080);
223 assert_eq!(msg.codec, CodecType::Raw);
224 }
225
226 #[test]
227 fn test_frame_size() {
228 let frame = vec![0u8; 1024];
229 let msg = VideoMessage::new(CodecType::H264, 640, 480, frame);
230
231 assert_eq!(msg.frame_size(), 1024);
232 }
233
234 #[test]
235 fn test_is_empty() {
236 let msg = VideoMessage::new(CodecType::MJPEG, 320, 240, vec![]);
237 assert!(msg.is_empty());
238 }
239
240 #[test]
241 fn test_encode() {
242 let frame = vec![1, 2, 3, 4, 5];
243 let msg = VideoMessage::new(CodecType::H264, 100, 100, frame);
244 let encoded = msg.encode_content().unwrap();
245
246 // 6 bytes header + 5 bytes data = 11 bytes
247 assert_eq!(encoded.len(), 11);
248 assert_eq!(encoded[0], CodecType::H264 as u8);
249 }
250
251 #[test]
252 fn test_roundtrip_h264() {
253 let original = VideoMessage::new(
254 CodecType::H264,
255 1920,
256 1080,
257 vec![0xFF; 1000], // Simulated compressed data
258 );
259
260 let encoded = original.encode_content().unwrap();
261 let decoded = VideoMessage::decode_content(&encoded).unwrap();
262
263 assert_eq!(decoded.codec, CodecType::H264);
264 assert_eq!(decoded.width, 1920);
265 assert_eq!(decoded.height, 1080);
266 assert_eq!(decoded.frame_data.len(), 1000);
267 }
268
269 #[test]
270 fn test_roundtrip_raw() {
271 let original = VideoMessage::new(
272 CodecType::Raw,
273 640,
274 480,
275 vec![128u8; 640 * 480 * 3],
276 );
277
278 let encoded = original.encode_content().unwrap();
279 let decoded = VideoMessage::decode_content(&encoded).unwrap();
280
281 assert_eq!(decoded.codec, CodecType::Raw);
282 assert_eq!(decoded.width, 640);
283 assert_eq!(decoded.height, 480);
284 }
285
286 #[test]
287 fn test_decode_invalid_header() {
288 let data = vec![0u8; 5]; // Too short
289 let result = VideoMessage::decode_content(&data);
290 assert!(result.is_err());
291 }
292
293 #[test]
294 fn test_decode_invalid_codec() {
295 let data = vec![99, 0, 100, 0, 100, 0]; // Invalid codec
296 let result = VideoMessage::decode_content(&data);
297 assert!(result.is_err());
298 }
299}