Windows Desktop screen capture and transmission over UDP is failing

Joseph RW 105 Reputation points
2025-11-14T05:38:26.4933333+00:00

Hi Microsoft team I am trying to capture my desktop screen and transmit it over UDP to VLC media player running on a Raspberry PI, on the network but the UDP stream is not displaying anything

// Source - https://stackoverflow.com/a
// Posted by Chuque, modified by community. See post 'Timeline' for change history
// Retrieved 2025-11-11, License - CC BY-SA 4.0
//https://stackoverflow.com/questions/679145/how-to-set-up-a-winsock-udp-socket
//https://learn.microsoft.com/en-us/windows/win32/gdi/scaling-an-image
#include <string>
#include <WinSock2.h>
#include <Ws2tcpip.h>
// GDI_CapturingAnImage.cpp : Defines the entry point for the application.
//
#define MAX_LOADSTRING 100
#pragma comment(lib, "ws2_32.lib")
using namespace std;
SOCKET s;
sockaddr_in dest;
int CaptureAnImage()
{
    HDC hdcScreen;
    HDC hdcWindow;
    HDC hdcMemDC = NULL;
    HBITMAP hbmScreen = NULL;
    BITMAP bmpScreen;
    DWORD dwBytesWritten = 0;
    DWORD dwSizeofDIB = 0;
    //HANDLE hFile = NULL;
    char* lpbitmap = NULL;
    HANDLE hDIB = NULL;
    DWORD dwBmpSize = 0;
    SIZE_T totalSize = 0;
    BYTE* buffer;
    // Retrieve the handle to a display device context for the client 
    // area of the window. 
    //hdcScreen = GetDC(NULL);
    HWND hWnd = GetDesktopWindow();
    hdcWindow = GetDC(hWnd);
    // Create a compatible DC, which is used in a BitBlt from the window DC.
    hdcMemDC = CreateCompatibleDC(hdcWindow);
    if (!hdcMemDC)
    {
        MessageBox(hWnd, L"CreateCompatibleDC has failed", L"Failed", MB_OK);
        goto done;
    }
    // Get the client area for size calculation.
    RECT rcClient;
    GetClientRect(hWnd, &rcClient);
    // This is the best stretch mode.
    SetStretchBltMode(hdcWindow, HALFTONE);
    // The source DC is the entire screen, and the destination DC is the current window (HWND).
    if (!StretchBlt(hdcWindow,
        0, 0,
        rcClient.right, rcClient.bottom,
        /*hdcScreen*/hdcWindow,
        0, 0,
        GetSystemMetrics(SM_CXSCREEN),
        GetSystemMetrics(SM_CYSCREEN),
        SRCCOPY))
    {
        MessageBox(hWnd, L"StretchBlt has failed", L"Failed", MB_OK);
        goto done;
    }
    // Create a compatible bitmap from the Window DC.
    hbmScreen = CreateCompatibleBitmap(hdcWindow, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);
    if (!hbmScreen)
    {
        MessageBox(hWnd, L"CreateCompatibleBitmap Failed", L"Failed", MB_OK);
        goto done;
    }
    // Select the compatible bitmap into the compatible memory DC.
    SelectObject(hdcMemDC, hbmScreen);
    // Bit block transfer into our compatible memory DC.
    if (!BitBlt(hdcMemDC,
        0, 0,
        rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
        hdcWindow,
        0, 0,
        SRCCOPY))
    {
        MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK);
        goto done;
    }
    // Get the BITMAP from the HBITMAP.
    GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);
    BITMAPFILEHEADER   bmfHeader;
    BITMAPINFOHEADER   bi;
    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bmpScreen.bmWidth;
    bi.biHeight = bmpScreen.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = 32;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;
    dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
    // Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that 
    // call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc 
    // have greater overhead than HeapAlloc.
    hDIB = GlobalAlloc(GHND, dwBmpSize);
    lpbitmap = (char*)GlobalLock(hDIB);
    // Gets the "bits" from the bitmap, and copies them into a buffer 
    // that's pointed to by lpbitmap.
    GetDIBits(hdcWindow, hbmScreen, 0,
        (UINT)bmpScreen.bmHeight,
        lpbitmap,
        (BITMAPINFO*)&bi, DIB_RGB_COLORS);
    // A file is created, this is where we will save the screen capture.
    //hFile = CreateFile(L"captureqwsx.bmp",
    //    GENERIC_WRITE,
    //    0,
    //    NULL,
    //    CREATE_ALWAYS,
    //    FILE_ATTRIBUTE_NORMAL, NULL);
    // Add the size of the headers to the size of the bitmap to get the total file size.
    dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    // Offset to where the actual bitmap bits start.
    bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
    // Size of the file.
    bmfHeader.bfSize = dwSizeofDIB;
    // bfType must always be BM for Bitmaps.
    bmfHeader.bfType = 0x4D42; // BM.
    // Calculate total buffer size
    totalSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwBmpSize;
    // Allocate the buffer (use malloc or new; don't forget to free/delete it later)
    buffer = new BYTE[totalSize];
    if (buffer == NULL) {
        // Handle allocation failure
        return 1;
    }
    // Copy the file header
    memcpy(buffer, &bmfHeader, sizeof(BITMAPFILEHEADER));
    // Copy the info header (offset by file header size)
    memcpy(buffer + sizeof(BITMAPFILEHEADER), &bi, sizeof(BITMAPINFOHEADER));
    // Copy the pixel data (offset by both headers)
    memcpy(buffer + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER), lpbitmap, dwBmpSize);
    // Now 'buffer' contains the full BMP data, ready for UDP transmission
    sendto(s, reinterpret_cast<const char*>(buffer), totalSize, 0, (SOCKADDR*)&dest, sizeof(dest));
    // Clean up when done
    delete[] buffer;
    // Unlock and Free the DIB from the heap.
    GlobalUnlock(hDIB);
    GlobalFree(hDIB);
    // Close the handle for the file that was created.
    //CloseHandle(hFile);
    //return 0;
    // Clean up.
done:
    DeleteObject(hbmScreen);
    DeleteObject(hdcMemDC);
    //ReleaseDC(NULL, hdcScreen);
    ReleaseDC(hWnd, hdcWindow);
    return 0;
}
int main() {
    //const char* pkt = "Message to be sent";
    const char* srcIP = "192.168.1.65";
    const char* destIP = "192.168.1.43";
    
    sockaddr_in local;
    WSAData data;
    WSAStartup(MAKEWORD(2, 2), &data);
    local.sin_family = AF_INET;
    inet_pton(AF_INET, srcIP, &local.sin_addr.s_addr);
    local.sin_port = htons(0);
    dest.sin_family = AF_INET;
    inet_pton(AF_INET, destIP, &dest.sin_addr.s_addr);
    dest.sin_port = htons(5000);
    s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    bind(s, (sockaddr*)&local, sizeof(local));
    //sendto(s, pkt, strlen(pkt), 0, (sockaddr*)&dest, sizeof(dest));
    while (TRUE) {
        CaptureAnImage();
    }
    closesocket(s);
    WSACleanup();
    return 0;
}

User's image

Developer technologies | C++
Developer technologies | C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Adiba Khan 1,440 Reputation points Microsoft External Staff
    2025-11-14T12:31:54.1366667+00:00

    Thanks for reaching out. Based on the description(UDP stream is not displaying anything) the code snippets the primary issue is likely in how the screen capture data is being formatted and sent over UDP.

    The code appears to be attempting to capture the screen as a bitmap(.bmp) and then send the raw data (including the headers) over UDP. VLC yeah player is generally not designed to natively play a row stream of static, non-compressed bitmap images arriving over UDP.

    Analysis of code and problem

    the code performs the following major steps:

    1.      GDI screen capture: uses GDI functions like GetDC(NULL), CreateCompatibleDC, CreateCompatibleBitmap, BitBit(for DtretchBlt), and GetDIBits to capture the screen contents into a device-independent bitmap (DIB) and retrieve the raw pixel data and bitmap headers (BITMAPFILEHEADER, BITMAPINFOHEADER).

    2.      Buffer allocation: allocates a buffer(buffer) nice enough to hold the bitmap headers plus the pixel data(total size)

    3.      data copying: copies the headers and pixel data into the buffer.

    4.      UDP transmission: since the entire buffer using sendto in a while(TRUE) loop.

    5.      Cleanup: Frees resources and delete objects.

    The core problem: format and streaming

    • Bitmap is not a stream format: BMP is an uncompressed or minimally compressed image file format. A single bitmap image is very large and a continuous stream of these raw images is not a standardized right efficient video stream that VLC can decode.
    • Missing compression/encoding: for video streaming compression is essential the current setup is sending uncompressed, raw pixel data along with the BMP Headers for each frame. This uses massive bandwidth and is not in a format VLC can interpret as a video stream.
    • VLC expectation: VLC expects A continuous stream encoded in a standard format( e.g., RTP, RTSP or a raw MPEG/H.264/MJPEG stream) with frame boundaries, not a rapid succession of large, self-contained bitmap files

    Solution: Implementing Proper Video Encoding

    To resolve this issue and make the stream playable in VLC , the capture bitmap data must be compressed and encoded into a steamtable format.

    Recommended solution steps

    the most robust solution involves integrating a compression/encoding library

    **1.      **introduce an encoding library(e.g., FFmpeg)

    the captured raw bitmap data needs to be fed into a library capable of encoding it into a video stream format like MJPEG( simpler, frame-by-frame compression or H.264 (more complex, better compression). FFmpeg is the industry standard for this.

    **2.      **Modify the capture and encoding loop

    the captureAnImage() function needs to be fundamentally changed to:

    • Capture GDI image: (current step, keep this)
    • encode frame: use the FFmpeg library to take the row DIB data from the buffer and encode it into a compressed video frame(e.g., a JPEG or H.264 NAL unit).
    • Packetize and Send: Send the compressed frame data over UDP, potentially using a simple framing/chunking mechanism or ideally a standard protocol like RTP (real time transport protocol) which is what VLC expect from streaming

    code implementation changes(conceptual)

    since adding the full FFmpeg implementation is extensive, here is how you could implement a simpler MJPEG over UDP approach for demonstration/testing, which VLC can sometimes handle though a proper RTP stream is better:

    //*Conceptual Change in CaptureAnImage()**
    void CaptureAnImage()
    {
    	//1. GIR Capture steps(as you have now) to get raw data into 'buffer'.
    	//......
    	//....
    	//2. Encode the raw buffer data into a compressed format (e.g., JPEG).
    	//This require linking to and using a library like libjpeg or FFmpeg.
    
    	//Example (Requires library integration);
    	//unsigned char* jpeg_buffer;
    	//int jpeg_size= encode_to_jpeg(buffer, totalSize, &jpeg_buffer);
    
    	//3. instead of sending the full, massive BMP buffer:
    	//sendto(s, (const char*) jpeg_buffer, jpeg_size, 0, (SOCKADDR*) &dest, sizeof(dest));
    	
    	//4. Clean up jpeg_buffer.
    }
    

    quick fix/test (MJPEG with frame boundary)

    if you cannot integrate a full library immediately, a temper has to confirm connectivity would be an encode a single frame using an external tool and try sending it, or to see if VLC can be configured to read a very simple the stream format.

    However, the definitive solution is robust video encoding end the streaming via RTP. Without integrating a video encoder, the stream will remain unplayable by standard media players like VLC.

    Please let us know if you require any further assistance we’re happy to help. If you found this information useful, kindly mark this as "Accept Answer".


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.