在當(dāng)今快速發(fā)展的技術(shù)環(huán)境中,實(shí)時(shí)多媒體流已經(jīng)成為數(shù)字體驗(yàn)中不可或缺的一部分。無(wú)論是視頻會(huì)議、直播還是遠(yuǎn)程監(jiān)控,實(shí)時(shí)捕獲和傳輸視頻幀的能力都是一項(xiàng)強(qiáng)大的功能。
在本文中,我們將探索如何使用Rust和OpenCV crate通過(guò)實(shí)時(shí)網(wǎng)絡(luò)攝像頭構(gòu)建一個(gè)簡(jiǎn)單的流媒體服務(wù)器。
我們的目標(biāo)是創(chuàng)建一個(gè)服務(wù)器,從網(wǎng)絡(luò)攝像頭捕獲幀,并通過(guò)HTTP實(shí)時(shí)傳輸?shù)竭B接的客戶(hù)端。我們將利用OpenCV crate進(jìn)行網(wǎng)絡(luò)攝像頭交互和圖像處理。創(chuàng)建項(xiàng)目
在Cargo.toml文件中加入依賴(lài)項(xiàng):[dependencies]
opencv = "0.84.5"
如果出現(xiàn)找不到libclang.dylib的錯(cuò)誤,參考下面的解決方法:mdfind -name libclang.dylib
sudo ln -s /usr/local/Cellar/llvm/16.0.6/lib/libclang.dylib /usr/local/lib
下面讓我們一步一步地分解實(shí)現(xiàn):
1,導(dǎo)入依賴(lài)項(xiàng)
在src/main.rs文件中,寫(xiě)入以下代碼:use opencv::{
core::{Mat, Vector}, imgcodecs, prelude::*, videoio,
};
use std::net::TcpListener;
use std::io::Write;
首先從OpenCV crate和標(biāo)準(zhǔn)Rust crate中導(dǎo)入必要的依賴(lài)項(xiàng),OpenCV crate用于圖像和視頻處理。
2,綁定TCP監(jiān)聽(tīng)器
代碼使用TcpListener::bind函數(shù)將TCP偵聽(tīng)器綁定到IP地址“127.0.0.1”和端口“8080”上。這將設(shè)置服務(wù)器偵聽(tīng)該IP和端口上的接入的客戶(hù)端連接。fn main() {
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
println!("服務(wù)器在端口8080上監(jiān)聽(tīng) ... ");
}
3,初始化攝像頭
初始化一個(gè)視頻捕獲對(duì)象(cam)來(lái)從默認(rèn)的webcam(設(shè)備索引0)捕獲幀,并設(shè)置主循環(huán)。fn main() {
......
let mut cam = videoio::VideoCapture::new(0, videoio::CAP_ANY).expect("視頻捕獲失敗");
let mut frame = Mat::default();
let mut buf = Vector::new();
loop{
// ...
}
}
4,接受客戶(hù)端連接
在循環(huán)中,服務(wù)器使用listener.accept()函數(shù)等待客戶(hù)端連接。一旦建立連接,它將返回一個(gè)TCP流(stream),該流將用于與客戶(hù)端通信。fn main() {
......
loop{
let (mut stream, _) = listener.accept().expect("無(wú)法接受連接");
}
}
上面的代碼進(jìn)入一個(gè)主循環(huán),在這個(gè)主循環(huán)中,它不斷地監(jiān)聽(tīng)傳入的客戶(hù)端連接,并為它們提供實(shí)時(shí)視頻幀。
5,捕獲和編碼幀
建立連接后,使用cam.read(&mut frame)函數(shù)從網(wǎng)絡(luò)攝像頭讀取視頻幀,然后使用imgcodecs::imencode函數(shù)將捕獲的幀編碼為JPEG圖像。編碼后的圖像數(shù)據(jù)存儲(chǔ)在buf向量中。fn main() {
.....
loop{
let (mut stream, _) = listener.accept().expect("Failed to accept connection");
cam.read(&mut frame).expect("捕獲幀失敗");
buf.clear();
let _ = imgcodecs::imencode(".jpg", &frame, &mut buf, &Vector::new());
}
}
6,創(chuàng)建響應(yīng)頭
該代碼創(chuàng)建了一個(gè)內(nèi)容類(lèi)型為“multipart/x-mix -replace”的HTTP響應(yīng)頭,并指定了一個(gè)名為“frame”的邊界。這個(gè)響應(yīng)頭表示多個(gè)圖像幀將依次發(fā)送,這將使客戶(hù)端實(shí)時(shí)接收和顯示幀,而無(wú)需為每個(gè)新幀關(guān)閉和重新打開(kāi)連接。fn main() {
......
loop{
......
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n"
);
stream.write_all(response.as_bytes()).unwrap();
}
}
使用stream.write_all函數(shù)響應(yīng)頭被寫(xiě)入客戶(hù)端的流,這通知客戶(hù)端,服務(wù)器已準(zhǔn)備好發(fā)送圖像幀。
7,內(nèi)部循環(huán):發(fā)送視頻幀
在嵌套循環(huán)中,服務(wù)器連續(xù)捕獲幀,對(duì)它們進(jìn)行編碼,并以連續(xù)流的形式將它們發(fā)送給客戶(hù)端。這個(gè)循環(huán)確保新幀一旦可用就被捕獲并發(fā)送。fn main() {
......
loop{
......
loop {
cam.read(&mut frame).expect("捕獲幀失敗");
buf.clear();
let _ = imgcodecs::imencode(".jpg", &frame, &mut buf, &Vector::new());
let image_data = format!(
"--frame\r\nContent-Type: image/jpeg\r\nContent-Length: {}\r\n\r\n",
buf.len()
);
stream.write_all(image_data.as_bytes()).unwrap();
stream.write_all(buf.as_slice()).unwrap();
stream.write_all(b"\r\n").unwrap();
stream.flush().unwrap();
}
}
}
在內(nèi)部循環(huán)中,服務(wù)器再次捕獲幀,對(duì)其進(jìn)行編碼,并將編碼后的數(shù)據(jù)存儲(chǔ)在buf向量中。然后,它為圖像構(gòu)造一個(gè)具有適當(dāng)內(nèi)容類(lèi)型和內(nèi)容長(zhǎng)度的HTTP響應(yīng)。使用stream.write_all函數(shù)將圖像數(shù)據(jù)發(fā)送到客戶(hù)端。
發(fā)送圖像數(shù)據(jù)后,服務(wù)器使用stream.flush()刷新流,以確保數(shù)據(jù)立即發(fā)送到客戶(hù)端。
use opencv::{
core::{Mat, Vector}, imgcodecs, prelude::*, videoio,
};
use std::net::TcpListener;
use std::io::Write;
fn main() {
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
println!("服務(wù)器在端口8080上監(jiān)聽(tīng) ... ");
let mut cam = videoio::VideoCapture::new(0, videoio::CAP_ANY).expect("視頻捕獲失敗");
let mut frame = Mat::default();
let mut buf = Vector::new();
loop{
let (mut stream, _) = listener.accept().expect("無(wú)法接受連接");
cam.read(&mut frame).expect("捕獲幀失敗");
buf.clear();
let _ = imgcodecs::imencode(".jpg", &frame, &mut buf, &Vector::new());
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n"
);
stream.write_all(response.as_bytes()).unwrap();
loop {
cam.read(&mut frame).expect("捕獲幀失敗");
buf.clear();
let _ = imgcodecs::imencode(".jpg", &frame, &mut buf, &Vector::new());
let image_data = format!(
"--frame\r\nContent-Type: image/jpeg\r\nContent-Length: {}\r\n\r\n",
buf.len()
);
stream.write_all(image_data.as_bytes()).unwrap();
stream.write_all(buf.as_slice()).unwrap();
stream.write_all(b"\r\n").unwrap();
stream.flush().unwrap();
}
}
}
運(yùn)行
輸入“cargo run”命令運(yùn)行程序,然后打開(kāi)瀏覽器,輸入IP地址127.0.0.1:8080開(kāi)始接收視頻數(shù)據(jù)。總結(jié)
恭喜你!你已經(jīng)成功地使用Rust和OpenCV crate構(gòu)建了一個(gè)實(shí)時(shí)網(wǎng)絡(luò)攝像頭流媒體服務(wù)器。你可以通過(guò)添加身份驗(yàn)證、多攝像頭支持和客戶(hù)端等功能或?qū)⑵渑cweb界面集成來(lái)進(jìn)一步增強(qiáng)該服務(wù)器的功能。
實(shí)時(shí)流只是Rust和OpenCV支持的眾多令人興奮的功能之一。通過(guò)掌握這些工具,你可以探索廣泛的多媒體應(yīng)用,從計(jì)算機(jī)視覺(jué)到視頻處理等等。
coding到燈火闌珊
專(zhuān)注于技術(shù)分享,包括Rust、Golang、分布式架構(gòu)、云原生等。
180篇原創(chuàng)內(nèi)容
公眾號(hào)
上一篇Rust中的設(shè)計(jì)模式:構(gòu)建者模式(Builder)下一篇Rust代碼安全:混淆的魔力
閱讀 1124
分享收藏312