micolous.id.au

the result of a blogging accident

Tuesday, February 2, 2010

A pipe-based VNC viewer

Filed under: Coding — micolous @ 23:04

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.

Powered by WordPress