You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 23:13:07