Wed. Jul 29th, 2026
#pragma once

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <vector>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <opencv2/opencv.hpp>

// Ensure Windows socket libraries are linked
#pragma comment(lib, "Ws2_32.lib")

class NetworkCamera {
public:
    NetworkCamera();
    ~NetworkCamera();

    // Establishes connection to the Raspberry Pi
    bool connectToDevice(const std::string& ipAddress, int port);
    
    // Disconnects and shuts down background threads cleanly
    void disconnect();

    // Call this after your settle delay to get the absolute freshest frame
    cv::Mat getLatestFrame();

    bool isConnected() const { return m_connected; }

private:
    // Background thread function that continuously drains the socket network buffer
    void captureLoop();

    // Network handles
    SOCKET m_connectSocket;
    bool m_connected;
    bool m_running;

    // Background streaming thread
    std::thread m_captureThread;

    // Double-buffering architecture
    cv::Mat m_bufferA;
    cv::Mat m_bufferB;
    cv::Mat* m_writePtr; // Where the network thread decodes the incoming image
    cv::Mat* m_readPtr;  // Where the main loop safely reads from
    
    std::mutex m_bufferMutex;
};
#include "NetworkCamera.h"

NetworkCamera::NetworkCamera() 
    : m_connectSocket(INVALID_SOCKET), m_connected(false), m_running(false) {
    // Point our buffers to our local matrices
    m_writePtr = &m_bufferA;
    m_readPtr = &m_bufferB;
}

NetworkCamera::~NetworkCamera() {
    disconnect();
}

bool NetworkCamera::connectToDevice(const std::string& ipAddress, int port) {
    // Initialize Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "Winsock initialization failed.\n";
        return false;
    }

    // Create Socket
    m_connectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_connectSocket == INVALID_SOCKET) {
        std::cerr << "Socket creation failed.\n";
        WSACleanup();
        return false;
    }

    // Setup hint structure
    sockaddr_in clientService;
    clientService.sin_family = AF_INET;
    clientService.sin_port = htons(port);
    inet_pton(AF_INET, ipAddress.c_str(), &clientService.sin_addr);

    // Connect to Raspberry Pi
    if (connect(m_connectSocket, (SOCKADDR*)&clientService, sizeof(clientService)) == SOCKET_ERROR) {
        std::cerr << "Failed to connect to Pi camera server.\n";
        closesocket(m_connectSocket);
        WSACleanup();
        return false;
    }

    m_connected = true;
    m_running = true;

    // Spin up the background network thread
    m_captureThread = std::thread(&NetworkCamera::captureLoop, this);
    return true;
}

void NetworkCamera::disconnect() {
    if (m_running) {
        m_running = false;
        
        // Close socket to unblock any pending recv() calls in the thread
        if (m_connectSocket != INVALID_SOCKET) {
            closesocket(m_connectSocket);
            m_connectSocket = INVALID_SOCKET;
        }

        if (m_captureThread.joinable()) {
            m_captureThread.join();
        }
        
        WSACleanup();
        m_connected = false;
    }
}

void NetworkCamera::captureLoop() {
    while (m_running) {
        uint32_t imageSize = 0;

        // 1. Read the 4-byte header telling us how big the upcoming image is
        int result = recv(m_connectSocket, reinterpret_cast<char*>(&imageSize), sizeof(imageSize), 0);
        if (result <= 0 || !m_running) break; 

        // Convert from network byte order to host byte order (just in case)
        imageSize = ntohl(imageSize); 

        // 2. Read the actual image payload bytes (looping until full size reached)
        std::vector<uchar> byteBuffer(imageSize);
        int totalBytesRead = 0;
        
        while (totalBytesRead < static_cast<int>(imageSize) && m_running) {
            int bytesLeft = imageSize - totalBytesRead;
            int received = recv(m_connectSocket, reinterpret_cast<char*>(byteBuffer.data() + totalBytesRead), bytesLeft, 0);
            if (received <= 0) break;
            totalBytesRead += received;
        }

        if (totalBytesRead < static_cast<int>(imageSize)) continue; // Fragmented or broken frame

        // 3. Decode the compressed JPEG bytes straight into our current "Write" buffer
        // This takes the heavy CPU compression-lifting out of your main logic thread.
        cv::imdecode(byteBuffer, cv::IMREAD_UNCHANGED, m_writePtr);

        if (!m_writePtr->empty()) {
            // 4. Instantly swap pointers. The new image becomes readable, 
            // and the old image memory space is recycled for the next incoming data.
            std::lock_guard<std::mutex> lock(m_bufferMutex);
            std::swap(m_writePtr, m_readPtr);
        }
    }
}

cv::Mat NetworkCamera::getLatestFrame() {
    std::lock_guard<std::mutex> lock(m_bufferMutex);
    // Returns a fast, shallow-copy header of the matrix. 
    // It is fully isolated and thread-safe because the capture loop will 
    // write to the *other* buffer pointer on its next iteration.
    return *m_readPtr;
}

By admin

Leave a Reply

Your email address will not be published. Required fields are marked *