micolous.id.au

the result of a blogging accident

Wednesday, February 17, 2010

SDL VNC Server module (v2)

Filed under: Coding — micolous @ 02:01

Time for a version two! There’s a new version of the patch to SDL 1.2.14. You need a clean source tree with this, so remove v1 of the patch before applying this one.

Changes:

  • Mouse cursors now work correctly.
  • You can now disable the “always shared” function of the VNC server and allow a client to take exclusive control with SDLVNCALWAYSSHARED="0".
  • You can change the display number manually with SDLVNCDISPLAY.
  • 8 bpp displays are now supported.
  • pygame applications work at 16, 24 and 32 bpp display depths. 8 doesn’t work properly.
  • This module now lies by default about the supported display depths reported by SDLListModes. It defaults to 16 bpp only, in order to make pygame work properly. You can change what it says this with SDLVNCDEPTH, or setting it to 0 to say all depths are supported. This doesn’t limit calls to SDLSetVideoMode.
  • You can tell the VNC server to ignore all client events by setting SDLVNCVIEWONLY="1".

There’s also a README.VNC included, which documents the functionality of the library and use of it’s environment variables.

Tuesday, February 16, 2010

SDL VNC Server module

Filed under: Coding — micolous @ 05:15

I keep writing things related to VNC, and coding in C. It’s seriously starting to worry me.

I’ve spent the last couple of days writing a new SDL video output module, which acts as a VNC server using libvncserver. Here are the patches against SDL 1.2.14. To use VNC support, you need to run ./configure with --enable-video-vnc, and once built and installed, run the SDL application with the environment variable SDL_VIDEODRIVER="vnc".

What works:

  • 16, 24 and 32-bit true-colour displays.
  • Mouse events.
  • Keyboard events.

What’s left to do:

  • Fix mouse cursors so they display correctly.
  • 8-bit (paletted) displays.
  • Handle surface locking properly.
  • Reduce/eliminate tearing on frequent screen updates.
  • Run the libvncserver event loop inside SDL’s event loop system.
  • Implement a password on the VNC server and some sort of simple access control.
  • Implement a view-only mode.
  • Allow setting the display number manually.
  • pygame applications.
  • Fix screen resolution change colour issues.

What will never work:

  • OpenGL surfaces.
  • CDROMs, Joysticks and Sound over VNC.
  • YUV video output.

I’ve had a lot of success with using the Origyn web browser (a WebKit/SDL-based web browser) and cgterm (a SDL C64/C128 telnet client) with this output method. Any SDL application that doesn’t use OpenGL surfaces should be able to run with this output module.

Some pictures:

[origyn web browser in tightvnc] [cgterm in tightvnc]

Friday, February 5, 2010

VNC Splitter

Filed under: Coding — micolous @ 01:00

More VNC clients!

This time, this one will split up a single VNC server input into smaller pieces to form a 3×3 grid of smaller VNC server images. It’ll automatically resize it to 640×480. That stuff is configurable in the defines at the top of the file. It works best when the ratio between the source image and the destination image is a whole number, (ie: 4×4 of 640×480 images and an input of 1280×960 has a 2:1 ratio), because it uses a simple “nearest neighbour” scaling algorithm.

The idea of this client is that you take some input into this, run a whole bunch of thin clients or low end machines connecting to each of the pieces of the screen, and align some cheap CRT monitors in a grid, creating a much bigger screen. While there are pretty huge bezels with CRT monitors, it is a very low budget way to create a 107cm (42″) display from a 3×3 array of cheap 36cm (14″) monitors, for example.

Be aware that some VNC servers implement JPEG compression, which looks really horrible when you scale it up (for example, Vine). This program also requires that your VNC server has no password.

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// vnc-splitter
// Copyright 2010 Michael Farrell <http://micolous.id.au>
// build with: gcc -o vnc-splitter vnc-splitter.c -lvncserver -lvncclient
#include <stdio.h>
#include <rfb/rfb.h>
#include <rfb/rfbclient.h>
 
// the height and width of the screens
// this should match the aspect ratio of the input
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
 
// the amount of screens is this number squared
#define SCREEN_SQUARE_SIZE 3
 
// where to start displays counting from.
// the first screen number is this, and subsequent screens are other positions.
#define DISPLAY_START 30
 
// output buffers for child vnc server processes
rfbScreenInfoPtr output_screens[SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE];
 
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
	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;
}
 
static void updateImage(rfbClient* client,int u_x,int u_y,int u_w,int u_h)
{
	unsigned int x, y, src_x, src_y;
	unsigned int *dest;
	unsigned int *src;
 
	unsigned int max_x = SCREEN_SQUARE_SIZE * SCREEN_WIDTH;
	unsigned int max_y = SCREEN_SQUARE_SIZE * SCREEN_HEIGHT;
 
	// precalculate the region we actually need to update.
	unsigned int u_min_x = ((double)(u_x) / (double)(client->width)) * max_x;
	unsigned int u_min_y = ((double)(u_y) / (double)(client->height)) * max_y;
 
	unsigned int u_max_x = ((double)(u_x + u_w) / (double)(client->width)) * max_x;
	unsigned int u_max_y = ((double)(u_y + u_h) / (double)(client->height)) * max_y;
 
	unsigned char updated[SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE];
	memset(updated, 0, SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE);
 
	// This scaling algorithm really sucks.  It's a "nearest neighbour" scaling format.
	// It's really easy to implement, really fast, but looks absolutely terrible.
	// Try and end up with the image being blown up so that 1x1 on the original maps to 2x2
	// on the destination.  If it maps to a fraction of pixels things look really bad.
	for (y=u_min_y; y<u_max_y; y++) {
		for (x=u_min_x; x<u_max_x; x++) {
			// now find out where that maps to on the original framebuffer.
			src_x = (unsigned int)(((double)x / (double)max_x) * (double)client->width);
			src_y = (unsigned int)(((double)y / (double)max_y) * (double)client->height);
 
			// figure out where this pixel is
			unsigned int d = ((y / SCREEN_HEIGHT) * SCREEN_SQUARE_SIZE) + (x / SCREEN_WIDTH);
 
			// now create a pointer to where that pixel is stored
			dest = (unsigned int*)&output_screens[d]->frameBuffer[(((y % SCREEN_HEIGHT) * SCREEN_WIDTH) + (x % SCREEN_WIDTH)) * 4];
 
			// and fix a pointer to that
			src = (unsigned int*)&client->frameBuffer[((src_y * client->width) + src_x) * 4];
 
			// and copy the pixel
			*dest = *src;
 
			// mark that screen as updated
			updated[d] = 1;
		}
	}
 
	for (x=0; x<SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE; x++)
		if (updated[x])
			// mark the entire screen as updated for now.  this could really
			// use some proper optimisations.
			rfbMarkRectAsModified(output_screens[x], 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}
 
int main (int argc, char *argv[])
{
	fprintf(stderr, "vnc-splitter\n");
	fprintf(stderr, "Copyright 2010 Michael Farrell <http://micolous.id.au>\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;
 
	// handle framebuffer updates
	client->GotFrameBufferUpdate = updateImage;
 
	// we don't care about keyboard LEDs and tightvnc chat protocol.
	client->HandleKeyboardLedState = 0;
	client->HandleTextChat = 0;
 
	// make a blank cursor
	rfbCursorPtr cursor = rfbMakeXCursor(0, 0, "\0", "\0");
 
	// create the vnc servers
	unsigned int id;
 
	for (id=0; id<SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE; id++) {
		output_screens[id] = rfbGetScreen(0, NULL, SCREEN_WIDTH, SCREEN_HEIGHT, 8, 3, 4);
		output_screens[id]->frameBuffer = malloc(SCREEN_WIDTH * SCREEN_HEIGHT * 4);
 
		output_screens[id]->serverFormat.redShift   = 8;
		output_screens[id]->serverFormat.greenShift = 16;
		output_screens[id]->serverFormat.blueShift  = 24;
		output_screens[id]->serverFormat.redMax     = 0xFF;
		output_screens[id]->serverFormat.greenMax   = 0xFF;
		output_screens[id]->serverFormat.blueMax    = 0xFF;
 
		output_screens[id]->autoPort = FALSE;
		output_screens[id]->port = SERVER_PORT_OFFSET + DISPLAY_START + id;
		output_screens[id]->alwaysShared = TRUE;
		rfbSetCursor(output_screens[id], cursor);
 
		// create the server in another thread
		rfbInitServer(output_screens[id]);
		rfbRunEventLoop(output_screens[id],-1,TRUE);
	}
 
	// 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, 100);
 
		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;
	} while (1);
	return 0;
}

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.

Sunday, January 24, 2010

VNC Client as Screen Saver

Filed under: Coding — micolous @ 06:36

I’ve whipped up a VNC Screen Saver in .NET for Windows. It is based on this screensaver example and VncSharp. It’s a bit of a quick hack, but works.

To get it, download the binaries and extract it to your Windows system32 folder (typically C:\Windows\System32\). Then open the screensaver settings, select Vncscreensaver and then configure it with your VNC server details. You can then use the screen saver!

Make sure the server you’re connecting to doesn’t require a password, otherwise this won’t work. The client runs in “view only” mode. It will quit when you either click the mouse or press a key, because hooking mouse movement seems to cause problems with the VNC client when it tries to reconnect.

If you’re interested, there’s also source code available for the program, licensed under the GPLv2 (the same license as VncSharp).

Older Posts »

Powered by WordPress