基于OpenCV C++的TCP视频传输故障:仅单帧传输求助排查
OpenCV C++ TCP视频传输系统仅能传输单帧的问题排查与修复
需求与问题概述
- 摄像头作为TCP服务器,在客户端连接前持续录制并缓存视频帧
- PC作为客户端,连接后触发服务器停止录制,接收所有缓存帧并保存为AVI文件
- 当前问题:服务器仅能向客户端传输1帧,数据收发逻辑存在错误
客户端(PC)原始代码
#include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string> #include <vector> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int recv_size; Mat frame; std::vector<uchar> buffer; int size = 0; int main() { int sock; struct sockaddr_in addr; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); exit(1); } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = inet_addr("192.168.3.11"); if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("connect"); exit(2); } if ((recv_size = recv(sock, (char*)&size, sizeof(uint32_t), 0)) != sizeof(uint32_t)) { perror("recv"); exit(3); } cout << "SIZE FRAME: " << size << endl; VideoWriter outputVideo; Size S = Size((int)1920, (int)1080); outputVideo.open("receive.avi", VideoWriter::fourcc('H', '2', '6', '4'), 25, S, true); buffer.resize(size); int bytes_received = 0; while (bytes_received < size) { int chunk_size = min(size - bytes_received, 4096); int recv_size = recv(sock, &buffer[bytes_received], chunk_size, 0); if (recv_size <= 0) { perror("recv"); exit(3); } bytes_received += chunk_size; } cout << "IDET DO IMDECODE " << recv_size << endl; frame = imdecode(buffer, IMREAD_COLOR); outputVideo << frame; close(sock); outputVideo.release(); return 0; }
服务器(摄像头)原始代码
#include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string> #include <vector> #include <opencv2/opencv.hpp> #include <fcntl.h> #include <opencv2/highgui/highgui.hpp> #include <opencv2/core/core.hpp> using namespace std; using namespace cv; int c,new_socket; std::vector<uchar> buffer; std::vector<uchar> arrak; int i=0,k=0; int bbytee; Mat image; int bytes=0; int main() { int sock,listener; std::vector<uchar> buf; std::vector<int> params(2); params[0] = cv::IMWRITE_PNG_COMPRESSION; params[1] = 10; struct sockaddr_in addr,client; sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket"); exit(1); } fcntl(sock, F_SETFL, O_NONBLOCK); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = htonl(INADDR_ANY); VideoCapture cap(2); if(!cap.isOpened()) { cout<< "Could not open the camera" << endl; close(sock); return -1; } if(bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("[server] binding failed!"); exit(2); } Mat frame = Mat::zeros(1080, 1920, CV_8UC3); int imgSize = frame.cols*frame.rows*3; listen(sock, 1); c = sizeof(struct sockaddr_in); while (true) { puts("Waiting for incoming connections..."); new_socket = accept(sock, (struct sockaddr *)&client, (socklen_t*)&c); cout << "New_socket: " << new_socket << endl; cap >> frame; imencode(".jpg", frame, buffer,params); if(frame.empty()) { cerr<<"[client] VideoCapture(0) error!"<<endl; } cout<< ++i << ":"<< frame.isContinuous()<<"," <<frame.size()<<endl; if(new_socket == -1) { puts("Connection not accepted"); cout << "new_socket = " << new_socket << "\n"; } else { int size = buffer.size()*sizeof buffer[0]; send(new_socket, &size, sizeof(size), 0); cout << "Razmer" << size << endl; cout << "Transmission started" << endl; bytes=send(new_socket,buffer.data(), buffer.size()*sizeof buffer[0], 0); cout << "BYTES: "<<bytes<<endl; break; } } cout << "END" << endl; close(sock); return 0; }
问题根源分析与修复方案
1. 服务器端无帧缓存逻辑
原始服务器仅在循环中读取单帧,未缓存连接前的所有帧,连接后仅发送当前读取的1帧。
修复:添加vector<vector<uchar>> frame_cache来缓存所有录制的编码帧。
2. 非阻塞Accept导致资源浪费与帧读取异常
非阻塞模式下accept频繁返回-1,循环执行过快,帧读取频率远超摄像头帧率,且占用大量CPU资源。
修复:添加延时控制帧率(如usleep(33000)对应30fps),确保帧读取与摄像头输出同步。
3. 服务器端多帧发送逻辑缺失
原始服务器仅发送单帧数据,未处理缓存的所有帧。
修复:连接建立后,先发送缓存的总帧数,再依次发送每帧的大小和帧数据,确保所有缓存帧都能传输。
4. 客户端多帧接收逻辑缺失
原始客户端仅接收单帧,未处理多帧数据。
修复:先接收总帧数,再循环接收每帧的大小和数据,解码后写入视频文件。
修改后的服务器代码
#include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string> #include <vector> #include <opencv2/opencv.hpp> #include <fcntl.h> using namespace std; using namespace cv; int main() { int sock; struct sockaddr_in addr, client; socklen_t c = sizeof(struct sockaddr_in); // 创建TCP套接字 sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket"); exit(1); } fcntl(sock, F_SETFL, O_NONBLOCK); // 配置地址 memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定端口 if(bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("[server] binding failed!"); exit(2); } // 监听连接 listen(sock, 1); // 初始化摄像头 VideoCapture cap(2); if(!cap.isOpened()) { cout<< "Could not open the camera" << endl; close(sock); return -1; } // 配置编码参数(JPG压缩,质量80) vector<int> params = {IMWRITE_JPEG_QUALITY, 80}; vector<vector<uchar>> frame_cache; // 缓存所有帧 Mat frame; puts("Recording and caching frames..."); while (true) { // 尝试接受客户端连接 int new_socket = accept(sock, (struct sockaddr *)&client, &c); // 读取摄像头帧并缓存 cap >> frame; if(!frame.empty()) { vector<uchar> encoded_frame; imencode(".jpg", frame, encoded_frame, params); frame_cache.push_back(encoded_frame); cout << "Cached frame: " << frame_cache.size() << endl; } // 如果客户端连接成功,停止录制并发送数据 if(new_socket != -1) { puts("Client connected, starting transmission..."); // 发送总帧数 uint32_t total_frames = frame_cache.size(); send(new_socket, &total_frames, sizeof(total_frames), 0); cout << "Total frames to send: " << total_frames << endl; // 逐个发送帧 for(const auto& encoded_frame : frame_cache) { // 发送当前帧的大小 uint32_t frame_size = encoded_frame.size(); send(new_socket, &frame_size, sizeof(frame_size), 0); // 发送帧数据(确保全部发送) int bytes_sent = 0; const uchar* data_ptr = encoded_frame.data(); while(bytes_sent < frame_size) { int sent = send(new_socket, data_ptr + bytes_sent, frame_size - bytes_sent, 0); if(sent <= 0) { perror("send frame data failed"); close(new_socket); close(sock); return -1; } bytes_sent += sent; } cout << "Sent frame: " << &encoded_frame - &frame_cache[0] + 1 << endl; } close(new_socket); break; } // 控制帧率(约30fps) usleep(33000); } close(sock); return 0; }
修改后的客户端代码
#include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string> #include <vector> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; // 完整接收指定长度的数据 bool recv_all(int sock, void* buf, int len) { int bytes_received = 0; while(bytes_received < len) { int recv_len = recv(sock, (char*)buf + bytes_received, len - bytes_received, 0); if(recv_len <= 0) { perror("recv_all failed"); return false; } bytes_received += recv_len; } return true; } int main() { int sock; struct sockaddr_in addr; // 创建TCP套接字 sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); exit(1); } // 配置服务器地址 memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = inet_addr("192.168.3.11"); // 连接服务器 if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("connect"); exit(2); } // 接收总帧数 uint32_t total_frames; if(!recv_all(sock, &total_frames, sizeof(total_frames))) { exit(3); } cout << "Total frames to receive: " << total_frames << endl; // 初始化视频写入器 VideoWriter outputVideo; Size frame_size(1920, 1080); outputVideo.open("receive.avi", VideoWriter::fourcc('M', 'J', 'P', 'G'), 25, frame_size, true); if(!outputVideo.isOpened()) { cerr << "Failed to open video writer" << endl; close(sock); exit(4); } // 逐个接收并处理帧 for(uint32_t i=0; i<total_frames; i++) { // 接收帧大小 uint32_t frame_size; if(!recv_all(sock, &frame_size, sizeof(frame_size))) { break; } // 接收帧数据 vector<uchar> buffer(frame_size); if(!recv_all(sock, buffer.data(), frame_size)) { break; } // 解码并写入视频 Mat frame = imdecode(buffer, IMREAD_COLOR); if(!frame.empty()) { outputVideo << frame; cout << "Processed frame: " << i+1 << endl; } } // 释放资源 close(sock); outputVideo.release(); cout << "Video saved as receive.avi" << endl; return 0; }
内容的提问来源于stack exchange,提问作者konstitycii
相关产品推荐
相关产品推荐

