micolous.id.au

The result of a blogging accident

A pipe-based VNC viewer

I’ve written a simple pipe-based VNC viewer, that is, it outputs a 32 bits-per-pixel RAW ARGB image stream to `stdout` every tenth of a second. The effect is you can chain this with ffmpeg to create a multicast UDP video stream:

./vnc-fifo vncserver:0 | ffmpeg -f rawvideo -pix_fmt argb -s 1024x768 -i -   -vsync 1 -s 512x384 -f mpegts -vcodec mpeg4 -b 1000k -threads 2 udp://239.255.2.3:1234

In this example, I assume your VNC server is running on `vncserver:0`, and it is outputting 1024x768. Other output resolutions will result in corruption, so you’ll need to change the parameter appropriately. It’ll also resize the image to 512x384.

// vnc-fifo.c
#include <stdio.h>
#include <rfb/rfbclient.h>
#include <time.h>static rfbBool resizeImage(rfbClient* client)
{
    int width=client->width;
    int height=client->height;
    int depth=client->format.bitsPerPixel;
    fprintf(stderr, "ResizeImage fired: %ix%i %ibpp\n", width, height, depth);   // change the screen resolution
    // deallocate any existing framebuffer
    if (client->frameBuffer != NULL)
        free (client->frameBuffer);

    client->frameBuffer = malloc(width * height * (depth / 8));

    if (client->frameBuffer == NULL) {
        fprintf(stderr, "Cannot allocate memory for framebuffer!");
        exit(1);
    }

    // we also need to tell libvncclient how it should put pixel data in there
    // in a format that ffmpeg will be pleased with (32bpp ARGB)
    client->format.redShift   = 8;
    client->format.greenShift = 16;
    client->format.blueShift  = 24;
    client->format.redMax     = 0xFF;
    client->format.greenMax   = 0xFF;
    client->format.blueMax    = 0xFF;

    // now we set the format and encoding method information back to libvncclient so it updates it's information.
    SetFormatAndEncodings(client);

    // report success to libvncclient
    return TRUE;
}int main (int argc, char *argv[])
{
    fprintf(stderr, "vnc-fifo\n");
    // create a 32-bpp "client".
    rfbClient* client = rfbGetClient(8,3,4);

    // tell the library we can handle being resized.
    client->canHandleNewFBSize = TRUE;
    client->MallocFrameBuffer = resizeImage;

    // we don't care about keyboard LEDs and tightvnc chat protocol.
    client->HandleKeyboardLedState = 0;
    client->HandleTextChat = 0;

    // connect to the server
    fprintf(stderr, "Connecting to server...\n");

    // rfbInitClient also allows us to use vncviewer-style commandline options.
    if(!rfbInitClient(client,&argc,argv))
        return 1;

    // start pumping the loop.
    fprintf(stderr, "Pumping...\n");

    clock_t last_frame = clock();
    clock_t this_frame = last_frame;
    do {
        int i = WaitForMessage(client, 10);

        if (i < 0)
            // there was an issue getting the message, probably our socket was closed.
            return 0;

        if (i)
            // handle message
            if (!HandleRFBServerMessage(client))
                // error handling message, die.
                return 0;

        // pump out a frame only if it has been 1/10 sec since the last one.
        this_frame = clock();
        if (this_frame - last_frame >= (CLOCKS_PER_SEC / 100)) {
            // pump out a frame
            last_frame = this_frame;
            fwrite(client->frameBuffer, 1, client->width * client->height * (client->format.bitsPerPixel/8), stdout);
            fflush(stdout);
        }    } while (1);
    return 0;
}

There is an issue with using ffmpeg in this fashion, because the framerate is variable, you essentially lie in your MPEG transport stream by saying you have a much higher framerate. The effect in ffplay is that it’ll buffer 10-45 seconds of video, play it all back to the end very fast, empty it’s buffer, then play normally. It’ll be “hanging on your every packet” for more video data.

Unfortunately, I couldn’t get VLC to act in the same fashion (as a reciever), even after telling it not to drop or skip frames, playback was jerky.

Oh well, an experiment to see if I could get out of writing a VLC or ffmpeg source module for VNC. Looks like if I want to go further with this, there’ll need to be a lot of changes to the code.