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.