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 1024×768. Other output resolutions will result in corruption, so you’ll need to change the parameter appropriately. It’ll also resize the image to 512×384.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | // 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.