c++ opencv+http推流摄像头数据
用大模型写的一个简单的http推流demo,用opencv读取摄像头数据作为数据源
#include <opencv2/opencv.hpp>
#include <boost/asio.hpp>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <atomic>
#include <chrono>
class UnifiedMJPEGStreamer {
public:
// 全局变量用于参数存储
std::string global_params = "";
protected:
boost::asio::io_service io_service;
private:
boost::asio::ip::tcp::acceptor acceptor;
std::vector<std::shared_ptr<boost::asio::ip::tcp::socket>> stream_clients;
std::queue<std::vector<uchar>> frame_queue;
std::mutex queue_mutex;
std::mutex client_mutex;
std::condition_variable queue_cv;
bool running;
std::thread stream_thread;
// 缓冲区管理
static const size_t MAX_QUEUE_SIZE = 50; // 最大50帧缓冲
// 帧率统计
std::atomic<int> frame_count{0};
std::chrono::steady_clock::time_point start_time;
public:
UnifiedMJPEGStreamer(int port = 8080)
: acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
running(false) {}
// 支持外部访问的构造函数
UnifiedMJPEGStreamer(const std::string& host, int port = 8080)
: acceptor(io_service, boost::asio::ip::tcp::endpoint(
boost::asio::ip::address::from_string(host), port)),
running(false) {}
~UnifiedMJPEGStreamer() {
stop();
}
void start() {
running = true;
frame_count = 0;
start_time = std::chrono::steady_clock::now();
startAccept();
stream_thread = std::thread([this]() {
io_service.run();
});
std::cout << "Unified MJPEG streamer started on port 8080" << std::endl;
}
void stop() {
running = false;
io_service.stop();
if (stream_thread.joinable()) {
stream_thread.join();
}
}
void pushFrame(const cv::Mat& frame) {
std::vector<uchar> buffer;
cv::imencode(".jpg", frame, buffer, {cv::IMWRITE_JPEG_QUALITY, 85});
{
std::lock_guard<std::mutex> lock(queue_mutex);
// 限制缓冲区大小,超过50帧时删除旧帧
while (frame_queue.size() >= MAX_QUEUE_SIZE) {
frame_queue.pop();
}
frame_queue.push(buffer);
}
queue_cv.notify_all();
// 更新帧率统计
frame_count++;
}
private:
// URL解码函数
std::string urlDecode(const std::string& str) {
std::string result;
for (size_t i = 0; i < str.length(); ++i) {
if (str[i] == '%' && i + 2 < str.length()) {
// 解码%xx格式的URL编码
std::string hexStr = str.substr(i + 1, 2);
char c = static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
result += c;
i += 2;
} else if (str[i] == '+') {
// +号解码为空格
result += ' ';
} else {
result += str[i];
}
}
return result;
}
void startAccept() {
auto socket = std::make_shared<boost::asio::ip::tcp::socket>(io_service);
acceptor.async_accept(*socket,
[this, socket](const boost::system::error_code& error) {
if (!error) {
handleRequest(socket);
}
startAccept();
});
}
void handleRequest(std::shared_ptr<boost::asio::ip::tcp::socket> socket) {
std::thread([this, socket]() {
try {
boost::asio::streambuf buffer;
boost::asio::read_until(*socket, buffer, "\r\n\r\n");
std::istream request(&buffer);
std::string request_line;
std::getline(request, request_line);
// std::cout << "[DEBUG] Request: " << request_line << std::endl;
if (request_line.find("GET /stream") != std::string::npos) {
// 处理MJPEG流请求
{
std::lock_guard<std::mutex> lock(client_mutex);
stream_clients.push_back(socket);
}
sendStreamHeader(socket);
startStreaming(socket);
} else if (request_line.find("POST /setparams") != std::string::npos) {
// 处理参数设置请求
handleParamSet(socket, request);
} else if (request_line.find("GET /fps") != std::string::npos) {
// 处理FPS查询请求
handleFPSRequest(socket);
} else {
// 默认返回主页面
sendMainPage(socket);
}
} catch (...) {
std::cout << "[DEBUG] Request handling error" << std::endl;
}
}).detach();
}
void sendMainPage(std::shared_ptr<boost::asio::ip::tcp::socket> socket) {
std::string html = generateMainPage();
std::string response = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Connection: close\r\n"
"Content-Length: " + std::to_string(html.length()) + "\r\n\r\n" + html;
boost::asio::write(*socket, boost::asio::buffer(response));
}
void handleParamSet(std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::istream& request) {
std::string line;
while (std::getline(request, line)) {
if (line.find("params=") != std::string::npos) {
size_t pos = line.find("params=") + 7;
std::string encoded_params = line.substr(pos);
global_params = urlDecode(encoded_params);
std::cout << "[DEBUG] 收到参数: " << global_params << std::endl;
break;
}
}
// 返回主页面
sendMainPage(socket);
}
double getFPS() {
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
double fps = 0.0;
if (duration.count() > 0 && frame_count > 0) {
fps = (frame_count * 1000.0) / duration.count();
}
return fps;
}
void handleFPSRequest(std::shared_ptr<boost::asio::ip::tcp::socket> socket) {
double fps = getFPS();
// std::cout << "[DEBUG] FPS请求 - frame_count: " << frame_count << ", duration: "
// << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start_time).count()
// << "ms, calculated_fps: " << fps << std::endl;
// 先生成内容体
std::ostringstream body_stream;
body_stream << std::fixed << std::setprecision(1) << fps;
std::string body = body_stream.str();
// 构建完整响应
std::ostringstream response;
response << "HTTP/1.1 200 OK\r\n"
<< "Content-Type: text/plain; charset=utf-8\r\n"
<< "Connection: close\r\n"
<< "Content-Length: " << body.length() << "\r\n\r\n"
<< body;
std::string response_str = response.str();
boost::asio::write(*socket, boost::asio::buffer(response_str));
// std::cout << "[DEBUG] FPS响应 - Content-Length: " << body.length()
// << ", Body: '" << body << "', Total response length: " << response_str.length() << std::endl;
}
std::string generateMainPage() {
std::string html = "<!DOCTYPE html>"
"<html lang=\"zh-CN\">"
"<head>"
" <meta charset=\"UTF-8\">"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
" <title>MJPEG摄像头推流</title>"
" <style>"
" body {"
" font-family: Arial, sans-serif;"
" margin: 0;"
" padding: 20px;"
" background-color: #f0f0f0;"
" }"
" .container {"
" max-width: 1000px;"
" margin: 0 auto;"
" background-color: white;"
" padding: 20px;"
" border-radius: 10px;"
" box-shadow: 0 2px 10px rgba(0,0,0,0.1);"
" }"
" .input-row {"
" display: flex;"
" align-items: center;"
" gap: 10px;"
" margin-bottom: 20px;"
" }"
" .input-row input[type=\"text\"] {"
" flex: 1;"
" padding: 8px;"
" border: 1px solid #ddd;"
" border-radius: 4px;"
" font-size: 14px;"
" }"
" .input-row button {"
" padding: 8px 20px;"
" background-color: #007bff;"
" color: white;"
" border: none;"
" border-radius: 4px;"
" cursor: pointer;"
" font-size: 14px;"
" }"
" .input-row button:hover {"
" background-color: #0056b3;"
" }"
" .params-display {"
" background-color: #f8f9fa;"
" padding: 15px;"
" border-radius: 4px;"
" margin-bottom: 20px;"
" font-family: monospace;"
" white-space: pre-wrap;"
" min-height: 50px;"
" border: 1px solid #ddd;"
" }"
" .video-container {"
" text-align: center;"
" }"
" .stream-img {"
" max-width: 100%;"
" height: auto;"
" border: 2px solid #ddd;"
" border-radius: 8px;"
" }"
" .status {"
" margin-top: 10px;"
" padding: 10px;"
" border-radius: 4px;"
" font-size: 12px;"
" }"
" .success {"
" background-color: #d4edda;"
" color: #155724;"
" border: 1px solid #c3e6cb;"
" }"
" .error {"
" background-color: #f8d7da;"
" color: #721c24;"
" border: 1px solid #f5c6cb;"
" }"
" .fps-display {"
" background-color: #e9ecef;"
" padding: 10px;"
" border-radius: 4px;"
" margin-top: 20px;"
" font-family: monospace;"
" font-size: 14px;"
" border: 1px solid #ddd;"
" }"
" </style>"
"</head>"
"<body>"
" <div class=\"container\">"
" <h1>MJPEG摄像头推流服务</h1>"
" "
" <div class=\"input-row\">"
" <input type=\"text\" id=\"paramInput\" placeholder=\"请输入参数...\" value=\"" + global_params + "\">"
" <button onclick=\"setParams()\">设置参数</button>"
" </div>"
" "
" <div class=\"params-display\" id=\"paramsDisplay\">" + global_params + "</div>"
" "
" <div class=\"video-container\">"
" <img id=\"stream\" class=\"stream-img\" src=\"/stream\" alt=\"MJPEG Stream\">"
" </div>"
" "
" <div class=\"fps-display\" id=\"fpsDisplay\">帧率: 0.0 fps</div>"
" </div>"
" "
" <script>"
" function setParams() {"
" const input = document.getElementById('paramInput');"
" const params = input.value;"
" "
" fetch('/setparams', {"
" method: 'POST',"
" headers: {"
" 'Content-Type': 'application/x-www-form-urlencoded',"
" },"
" body: 'params=' + encodeURIComponent(params)"
" })"
" .then(response => response.text())"
" .then(data => {"
" document.getElementById('paramsDisplay').textContent = params;"
" showStatus('参数设置成功: ' + params, 'success');"
" })"
" .catch(error => {"
" showStatus('参数设置失败: ' + error.message, 'error');"
" });"
" }"
" "
" function updateFPS() {"
" fetch('/fps')"
" .then(response => response.text())"
" .then(fps => {"
" document.getElementById('fpsDisplay').textContent = '帧率: ' + fps + ' fps';"
" })"
" .catch(error => {"
" console.log('[DEBUG] 获取帧率失败:', error);"
" });"
" }"
" "
" function showStatus(message, type) {"
" const status = document.getElementById('streamStatus');"
" status.textContent = message;"
" status.className = 'status ' + type;"
" }"
" "
" const streamImg = document.getElementById('stream');"
" streamImg.onload = function() {"
" showStatus('视频流连接正常', 'success');"
" };"
" streamImg.onerror = function() {"
" showStatus('视频流连接失败,请检查服务器', 'error');"
" };"
" "
" console.log('[DEBUG] 设置定时器,每1秒执行一次');"
" "
" setInterval(function() {updateFPS();}, 1000);"
" "
" document.getElementById('paramInput').addEventListener('keypress', function(e) {"
" if (e.key === 'Enter') {"
" setParams();"
" }"
" });"
" </script>"
"</body>"
"</html>";
return html;
}
void sendStreamHeader(std::shared_ptr<boost::asio::ip::tcp::socket> socket) {
std::string header =
"HTTP/1.0 200 OK\r\n"
"Cache-Control: no-cache\r\n"
"Pragma: no-cache\r\n"
"Connection: close\r\n"
"Content-Type: multipart/x-mixed-replace;boundary=frame\r\n\r\n";
boost::asio::async_write(*socket, boost::asio::buffer(header),
[](const boost::system::error_code&, size_t) {});
}
void startStreaming(std::shared_ptr<boost::asio::ip::tcp::socket> socket) {
std::thread([this, socket]() {
while (running) {
std::vector<uchar> frame;
{
std::unique_lock<std::mutex> lock(queue_mutex);
queue_cv.wait(lock, [this]() {
return !frame_queue.empty() || !running;
});
if (!running) break;
if (frame_queue.empty()) continue;
frame = frame_queue.front();
frame_queue.pop();
}
std::ostringstream header;
header << "--frame\r\n"
<< "Content-Type: image/jpeg\r\n"
<< "Content-Length: " << frame.size() << "\r\n\r\n";
std::string header_str = header.str();
std::vector<boost::asio::const_buffer> buffers = {
boost::asio::buffer(header_str),
boost::asio::buffer(frame)
};
try {
boost::asio::write(*socket, buffers);
} catch (...) {
std::lock_guard<std::mutex> lock(client_mutex);
auto it = std::find(stream_clients.begin(), stream_clients.end(), socket);
if (it != stream_clients.end()) {
stream_clients.erase(it);
}
break;
}
// 控制帧率
std::this_thread::sleep_for(std::chrono::milliseconds(33)); // ~30fps
}
}).detach();
}
};
int main() {
std::cout << "[DEBUG] 启动MJPEG推流测试程序..." << std::endl;
// 初始化摄像头 - 尝试不同的后端
cv::VideoCapture cap;
// 首先尝试V4L2后端
std::cout << "[DEBUG] 尝试使用V4L2后端打开摄像头..." << std::endl;
cap.open(1, cv::CAP_V4L2);
if (!cap.isOpened()) {
std::cout << "[DEBUG] V4L2失败,尝试默认后端..." << std::endl;
cap.open(1);
}
if (!cap.isOpened()) {
std::cout << "[DEBUG] 默认后端失败,尝试GStreamer后端..." << std::endl;
std::string pipeline = "v4l2src device=/dev/video1 ! videoconvert ! appsink";
cap.open(pipeline, cv::CAP_GSTREAMER);
}
if (!cap.isOpened()) {
std::cerr << "[ERROR] 所有后端都无法打开摄像头索引1" << std::endl;
return -1;
}
std::cout << "[DEBUG] 摄像头初始化成功" << std::endl;
// 设置摄像头参数
cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
cap.set(cv::CAP_PROP_FPS, 30);
cap.set(cv::CAP_PROP_BUFFERSIZE, 1); // 减少延迟
// 验证设置
double width = cap.get(cv::CAP_PROP_FRAME_WIDTH);
double height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
double fps = cap.get(cv::CAP_PROP_FPS);
std::cout << "[DEBUG] 摄像头实际参数: " << width << "x" << height << "@" << fps << "fps" << std::endl;
// 创建推流器 - 绑定到0.0.0.0支持外部访问
try {
UnifiedMJPEGStreamer streamer("0.0.0.0", 8080);
std::cout << "[DEBUG] 正在启动统一推流服务器..." << std::endl;
streamer.start();
std::cout << "[DEBUG] 统一推流服务器启动在端口8080 (支持外部访问)" << std::endl;
std::cout << "[INFO] 访问 http://服务器IP:8080/ 查看主页面" << std::endl;
std::cout << "[INFO] 访问 http://服务器IP:8080/stream 查看视频流" << std::endl;
std::cout << "[INFO] 访问 http://服务器IP:8080/setparams 设置参数" << std::endl;
// 等待服务器完全启动
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
cv::Mat frame;
int frame_count = 0;
while (true) {
cap >> frame;
if (frame.empty()) {
std::cerr << "[ERROR] 无法读取摄像头帧" << std::endl;
break;
}
// 每30帧打印一次状态
if (frame_count % 30 == 0) {
std::cout << "[DEBUG] 推流帧数: " << frame_count << ", 当前参数: '" << streamer.global_params << "'" << std::endl;
}
streamer.pushFrame(frame);
frame_count++;
// 检查键盘输入
char key = cv::waitKey(1) & 0xFF;
if (key == 'q' || key == 27) { // 'q'或ESC退出
std::cout << "[DEBUG] 用户请求退出" << std::endl;
break;
}
}
std::cout << "[DEBUG] 正在关闭推流器..." << std::endl;
streamer.stop();
} catch (const std::exception& e) {
std::cerr << "[ERROR] 异常: " << e.what() << std::endl;
cap.release();
return -1;
} catch (...) {
std::cerr << "[ERROR] 未知异常" << std::endl;
cap.release();
return -1;
}
cap.release();
cv::destroyAllWindows();
std::cout << "[DEBUG] 程序退出" << std::endl;
return 0;
}
/* build.sh
#!/bin/bash
echo "编译MJPEG推流测试程序..."
# 检查依赖
echo "检查OpenCV依赖..."
if [ ! -d "/usr/include/opencv4" ]; then
echo "错误: 未找到OpenCV4开发包"
echo "请安装: sudo apt-get install libopencv-dev"
exit 1
fi
if [ ! -d "/usr/include/boost" ]; then
echo "错误: 未找到Boost开发包"
echo "请安装: sudo apt-get install libboost-all-dev"
exit 1
fi
echo "依赖检查通过!"
# 设置编译标志
OPENCV_FLAGS="-I/usr/include/opencv4 -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_imgcodecs -lopencv_videoio"
BOOST_FLAGS="-I/usr/include/boost -lboost_system -lboost_thread"
echo "OpenCV标志: $OPENCV_FLAGS"
echo "Boost标志: $BOOST_FLAGS"
# 编译
g++ -std=c++11 \
00_test1.cpp \
$OPENCV_FLAGS \
$BOOST_FLAGS \
-pthread \
-o mjpeg_streamer
if [ $? -eq 0 ]; then
echo "编译成功!"
echo "运行程序: ./mjpeg_streamer"
else
echo "编译失败!"
exit 1
fi
echo "运行 pkill -f mjpeg_streamer || ./mjpeg_streamer "
*/

浙公网安备 33010602011771号