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

如何通过TCP Socket正确传输NV21图像?Qt与Python跨端传输问题

解决TCP Socket传输NV21图像数据不完整的问题

看起来你遇到的核心问题是TCP流的无边界特性导致的传输不完整,再加上两端代码的逻辑漏洞,使得服务器只收到了部分图像数据。下面分客户端和服务器端逐一拆解修复:

一、Qt Android客户端的问题与修复

你的客户端代码存在两个关键漏洞:

  1. output变量持续累积帧数据,没有在每次发送后清空,导致发送的是多帧混合的无效数据,首次发送的可能只是部分帧内容。
  2. tcpSocket->write不一定能一次性写完所有数据——TCP是流式协议,需要循环确认所有字节都被写入缓冲区。

修正后的客户端代码:

void came::processFrame(const QVideoFrame& frame) {
    QVideoFrame f(frame);
    // 先确认能成功映射帧数据
    if (!f.map(QAbstractVideoBuffer::ReadOnly)) {
        qWarning() << "Failed to map video frame";
        return;
    }

    // 直接使用当前帧的原始数据,不累积旧数据
    QByteArray frameData((char*)f.bits(), f.mappedBytes());
    f.unmap(); // 用完及时解除映射,避免资源泄漏

    // 第一步:先发送数据长度(用4字节大端整数标记,让服务器知道要收多少)
    quint32 dataLen = frameData.size();
    QByteArray lenBytes;
    QDataStream stream(&lenBytes, QIODevice::WriteOnly);
    stream.setByteOrder(QDataStream::BigEndian);
    stream << dataLen;

    qint64 lenWritten = 0;
    while (lenWritten < lenBytes.size()) {
        qint64 written = tcpSocket->write(lenBytes.mid(lenWritten));
        if (written == -1) {
            qWarning() << "Failed to send data length:" << tcpSocket->errorString();
            return;
        }
        lenWritten += written;
        tcpSocket->waitForBytesWritten(100); // 等待缓冲区写入
    }

    // 第二步:循环发送完整的帧数据
    qint64 bytesWritten = 0;
    while (bytesWritten < frameData.size()) {
        qint64 written = tcpSocket->write(frameData.mid(bytesWritten));
        if (written == -1) {
            qWarning() << "Socket write error:" << tcpSocket->errorString();
            break;
        }
        bytesWritten += written;
        tcpSocket->waitForBytesWritten(100);
    }

    qDebug() << "Successfully sent" << bytesWritten << "bytes of NV21 data";
}

二、Python服务器端的问题与修复

你的服务器接收逻辑完全错误:不能用「单次recv返回的字节数小于blockSize」作为接收完成的标志——TCP传输中,数据会被拆分成多个数据包发送,即使还有剩余数据,某次recv也可能返回小于blockSize的字节数。

正确的做法是遵循「先长度后数据」的协议:先接收数据长度,再根据长度接收完整的图像数据。

修正后的服务器代码:

import socket
import numpy as np
import cv2

def recv_all(sock, target_length):
    """ 确保接收指定长度的完整数据,处理TCP拆包问题 """
    received_data = b''
    while len(received_data) < target_length:
        # 每次接收剩余长度和4096中的较小值,避免内存浪费
        chunk = sock.recv(min(target_length - len(received_data), 4096))
        if not chunk:
            raise ConnectionError("Socket connection was unexpectedly closed")
        received_data += chunk
    return received_data

def recv_nv21_image(sock, rows=1080, cols=1920):
    # 1. 先接收4字节的长度数据(大端格式)
    len_bytes = recv_all(sock, 4)
    total_length = int.from_bytes(len_bytes, byteorder='big')
    print(f"Expecting {total_length} bytes of NV21 data")

    # 2. 接收完整的NV21图像数据
    nv21_data = recv_all(sock, total_length)
    print(f"Received {len(nv21_data)} bytes in total")

    # 3. 解析NV21并转换为BGR格式
    bin_y = nv21_data[:rows * cols]
    img_y = np.frombuffer(bin_y, np.uint8).reshape((rows, cols))

    bin_u = nv21_data[rows * cols::2]
    img_u = np.frombuffer(bin_u, np.uint8).reshape((rows // 2, cols // 2))

    bin_v = nv21_data[rows * cols + 1::2]
    img_v = np.frombuffer(bin_v, np.uint8).reshape((rows // 2, cols // 2))

    # 放大UV通道到与Y通道同尺寸
    enlarge_u = cv2.resize(img_u, dsize=(cols, rows), interpolation=cv2.INTER_CUBIC)
    enlarge_v = cv2.resize(img_v, dsize=(cols, rows), interpolation=cv2.INTER_CUBIC)

    # 合并并转换颜色空间
    yuv_img = cv2.merge([img_y, enlarge_u, enlarge_v])
    bgr_img = cv2.cvtColor(yuv_img, cv2.COLOR_YUV2BGR)
    return bgr_img

if __name__ == '__main__':
    server_addr = ('0.0.0.0', 12345)
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(server_addr)
    server_socket.listen(1)
    print(f"Server listening on {server_addr}...")

    client_sock, client_addr = server_socket.accept()
    print(f"Connected to client: {client_addr}")

    try:
        received_image = recv_nv21_image(client_sock)
        cv2.imwrite("received_frame.jpg", received_image)
        print("Image saved successfully")
    except Exception as e:
        print(f"Error during transmission: {str(e)}")
    finally:
        client_sock.close()
        server_socket.close()

三、额外注意事项

  • 格式验证:确认QVideoFrame的像素格式确实是NV21,可通过frame.pixelFormat() == QVideoFrameFormat::Format_NV21检查,部分设备可能输出其他YUV变种格式。
  • 性能优化:1920*1080的NV21数据约3MB,若网络带宽有限,可考虑在客户端先将图像压缩为JPEG再传输,减少带宽占用。
  • 异步处理:如果需要更高的并发性能,Qt客户端可改用readyRead信号异步处理发送,Python服务器可采用多线程/异步IO框架。

内容的提问来源于stack exchange,提问作者wry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:43:50