Implementando um VNC

31 respostas
saoj

Pessoal,

Gostaria de tentar implementar um VNC, para permitir que clientes vejam um videozinho do que eu estou fazendo no meu desktop, por exemplo enquanto eu mexo no Eclipse. Não preciso do controle remoto, apenas do video.

Estou meio perdido:

  1. Existe um protocolo padrão para VNC com baixo consumo de banda?

  2. Pensando um pouco pensei que eu poderia implementar o meu próprio protocolo, enviando apenas os pixels que estão mudando na tela. Daí se o cara fica olhando admirado para o eclipse sem fazer nada ele não manda nada. Se o cara digita um código ele só manda os pixelzinhos do texto que está aparecendo ali. É assim que o VNC funciona ou é algo bem mais sofisticado?

  3. Existe alguma implementação em Java open-source?

31 Respostas

cv1

Fala Sergio!

  1. Yeap: http://www.tightvnc.com

  2. Bem mais sofisticado, mas no geral eh isso mesmo.

  3. Yeap, mas so pro viewer, nao pro servidor. http://www.tightvnc.com/download.html

T

De qualquer maneira, pegar os pixels e ver o que foi alterado é uma operação terrivelmente onerosa.

Provavelmente você deve ter já usado o VNC (tanto faz se é o TightVNC ou o RealVNC) e visto que ele puxa uma CPU violenta da máquina que está sendo controlada (eu estava controlando uma máquina Windows 98, Celeron 333 para poder fazer uns testes. O RealVNC estava puxando 30% da CPU :wink: )

O que o RealVNC faz agora é algo um pouco mais esperto. Ele instala um “hook” no Windows que vigia todas as operações de mouse, teclado e janelas. Quando alguma coisa é alterada na máquina que está sendo controlada, ele usa o “hook” para enviar para o client VNC os parâmetros da API que está sendo chamada pelo Windows para mover o mouse, ou tratar o teclado, ou pintar as janelas. Só que isso é tudo código em C mesmo. Ele só não usa o hook quando você está usando uma aplicação console. Nesse caso ele tem de usar o método de pegar os pixels e ver o que foi alterado - aí o bicho pega.

saoj

Mas aí perde a independência de plataforma, né?

Acho que no meu caso, onde eu quero apenas um videozinho que vai passar dentro de uma janela com scrollbar, o primeiro método é mais indicado né?

Agora vc tem razão quanto a fome disso: 1024 x 768 = 786432 pixels

Se eu tenho um frame rate de 1 por segundo, vou ter que percorrer esse array a cada um segundo pra ver se algo mudou. O CPU não vai ficar idle nunca assim… É assim mesmo que eu vou ter que fazer isso né ??? Depois é legal meter um Zip encima dos pixels que mudaram né… Se o cara abriu uma tela branca encima, eu não vou querer mandar 786432 pixels brancos.

T

Quanto à “independência de plataforma” acho que você vai ter de se restringir à parte client. Tanto o TightVNC quanto o RealVNC têm clients Java.

E o esquema de compressão de ambos é bastante complicado, porque para fazer uma compressão eficiente, é necessário fazê-la em duas dimensões, não somente em uma, se você fosse usar um esquema ingênuo de .zip (e até 3 se você considerar a cor como sendo uma dimensão; o VNC tem a opção de diminuir a profundidade das cores - de 16M cores para somente 256 cores, por exemplo - para diminuir a quantidade de dados a serem transmitidos) . Além disso, usam alguns truques, por exemplo, é interessante atualizar mais freqüentemente a área em volta do “caret” e do ponteiro do mouse, porque é provavelmente lá onde você está focalizando sua atenção, e é provavelmente lá que a tela está sendo modificada.

louds

Usa mpeg1, ou pelo menos jpeg, pra comprimir os frames. Codificar jpeg com java é brincadeira de criança (já tem API pra isso). Já mpeg exige um pouquinho mais de trabalho. Se usar jpeg, use tabelas fixas de compressão e quantização, isso te salva uns tantos bytes.

A parte ruim de usar técnicas lossy é que texto fica dificil de ler com compressão muito alta.

Mas se você estiver realmente se sentindo aventuroso, use lzw mesmo e use motion vectors, damage zones e toda esses truques para acelerar a transmissão.

E o mais importante, fique o mais longe possivel do TCP se você quer algo que funcione em real-time.

T

Fico imaginando um VNC com “lossy compression” (não só a redução de profundidade nas cores)…
Ia ser meio complicado digitar texto no site do GUJ, que usa umas letras muito pequenas. Ia aparecer só “sujeira”.

louds

Depende, se você tiver acesso ao toolkit, pode tratar texto de forma especial.

Teoricamente isso é poderia ser feito com AWT/Swing usando algo semelhante ao xprotocol.

Rubem_Azenha

tenho um colega que implementou uma especie VNC, mas só para mouse…
nada muito sofisticado…

se for open source o programa ele com certeza libera o fonte…

e aí???

cv1

Nossa, essa thread mal comecou e ja ta com aquele cheirinho de reinvencao de roda… hmmmmmmm… delicia :mrgreen:

Nao sei nem se a AWT te da esse controle todo a pegar os pixels da tela. Rola fazer isso com o java.awt.Robot e amigos?

T

Pois é, já tem tudo pronto, e o client do RealVNC é minúsculo (tanto o applet quanto o executável), e foi portado para N arquiteturas, que custa rodar o client e o server? Eu lembro que existe um modo do VNC que lhe permite conectar N clients a um único server, para que você possa mostrar o tal “videozinho”.

pcalcado

Eu acho que o que o Sérgio quer é um VNC muito mais simples e jmais flexível apra embutir em algo (lohis?).

Por que tu não tenta o método mais simples e vê se dá galho? Eu acho que se a área monitorada for pequena não deve ter muitos problemas.

Shoes

saoj

O programinha abaixo já te permite brincar de fazer um codec VNC.

Ele te fala quantos pixels estão mudando conforme vc brinca com a tela.

Se alguem fizer um codec de brincadeira, tipo um MPEG da vida e quiser me doar eu aceito. :stuck_out_tongue:

O objetivo e compremir esses bytes que estão mudando para eu poder jogar isso na rede.

import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;

public class ScreenVideo extends JPanel implements Runnable {
	
	private int x, y, w, h;
	private float fps;
	private Robot robot;	
	private Rectangle rect;
	private Thread thread;
	private volatile boolean bThread;
	private int sleeptime;
	private Dimension size;
	private int [] imageBytes;
	private int [] lastFrame;
	
	public ScreenVideo(int x, int y, int w, int h, float fps) throws AWTException {
		this.x = x;
		this.y = y;
		this.w = w;
		this.h = h;
		this.fps = fps;
		this.rect = new Rectangle(x, y, w, h);
		this.size = new Dimension(w, h);
		this.robot = new Robot();
		this.sleeptime = (int) ((1 / fps) * 1000);
		this.imageBytes = new int[w * h];
		this.lastFrame = new int[w * h];
	}
	
	public void start() {
		startThread();
	}
	
	public void stop() {
		stopThread();
	}
	
	public Dimension getPreferredSize() {
		return size;
	}
	
	private void startThread() {
		bThread = true;
		thread = new Thread(this);
		thread.start();
	}
	
	private void stopThread() {
		bThread = false;
		thread.interrupt();
		thread = null;
	}
	
	public void run() {
		try {
			while(bThread) {
				BufferedImage bi = robot.createScreenCapture(rect);
				Graphics g = getGraphics();
				g.drawImage(bi, 0, 0, this);
				
				// veja o que mudou...
				bi.getRGB(0, 0, w, h, imageBytes, 0, w);
				int changed = 0;
				for(int i=0;i<imageBytes.length;i++) {
					if (imageBytes[i] != lastFrame[i]) {
						changed++;
						lastFrame[i] = imageBytes[i];
					}
				}
				System.out.println("Mudou: " + changed);
				
				Thread.sleep(sleeptime);
			}
		} catch(InterruptedException e) {
			// bye
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String [] args) throws Exception {
		JFrame frame = new JFrame("Minha Tela");
		final ScreenVideo sv = new ScreenVideo(0, 0, 1024, 768, 1);
		frame.setContentPane(sv);
		
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent evt) {
				sv.stop();
				System.exit(0);
			}
		});
		
		frame.pack();
		sv.start();
		frame.setVisible(true);
	}
}

>

louds

Sergio, qual a performance disso?

saoj

Uma merda como o thingol falou, mas o P4 aqui tá aguentando tranquilo… :slight_smile:

Não há nada que um pentium não resolva hoje em dia, né? :stuck_out_tongue:

PS: Gostou do volatile no bThread ??? :thumbup:

cv1

Nao rodei o codigo, mas se eu entendi ele desenha um JFrame de 1024x768 na tela, e poe ele como visivel em cima de tudo, capturando o que estiver ali. Se for pra capturar soh coisas que acontecem dentro de uma janela AWT que vc tem controle, eh mais facil escutar a EventQueue - e tirar o screenshot sempre que um evento passar por ali :wink:

saoj

Acho que o legal seria vc minimizar esse JFrame e começar a mexer no Eclipse por exemplo. Daí o frame vai pegando tudo.

Mas concordo contigo, esse JFrame ficou sem utilidade. Não faz sentido para a pessoa que está capturando ver o que ela está capturando. Se não acaba virando espelho infinito.

Pô, tentei comprimir com ZIP e ficou ruim. Picos de 10 k por segundo. Fica muito pesado em termos de banda.

Vou dar uma olhada no VNC. Reinventar a roda dessa vez vai ser realmente loucura. :cry:

Acabei de ver tb que para cada pixel vc tem que enviar a posição e o valor, ou seja, o dobro de informações. :cry:

T

Que pena que o Swing não tem um esquema de permitir você amarrar um “hook” nas rotinas que você está chamando - ou teria se você mexesse em javax.swing.DebugGraphics?
(O fonte do Swing está disponível, mas você tem coragem de mexer nele?).
Senão dava para fazer uma coisa parecida com o RealVNC (ou o TightVNC usando um driver especial). Ou seja, em vez de ler a tela (uma operação lenta), a cada vez que o server executasse um comando do tipo drawString ou drawOval, por exemplo, enviasse para seus clientes (talvez usando UDP e multicast :wink: ) o comando drawString codificado, com os parâmetros…

louds

Seria que não rola usar uma Toolkit wrapper?

plentz

Se o interesse é em fazer algo só para o Eclipse, já tentou ver se a API dele não oferece algo do gênero?

saoj

Queria fazer algo genérico que filmasse qualquer coisa na minha tela e transmitisse pela Internet, por isso que eu acho que esse esquemas de hook não me serve.

Pô, no final das contas eu quero passar um filme pela Internet, e isso é praticamente impossível devido as minhas limitações de banda, tanto no cliente, onde eu não exijo banda larga, tanto no servidor, onde tenho uma banda limitada.

Vou dar uma olhada no VNC de qualquer jeito, e tentar meter um MPEG em cima pra ver o que acontece. O ideal seria MPEG4, mas java ainda não suporta. Tem um tal de ffmpeg, mas está além das minhas forças entender aquilo.

Valeu pessoal pelas dicas!

louds

Para ter uma ideia da qualidade que vai sair usando MPEG2/4, basta assistir matrix e tentar ler o shell quanto a Trinity executa um nmap na matrix http://www.insecure.org/nmap/nmap_inthenews.html.

Diogenes

:shock:
Eu héin?!

KWill

saoj:
O programinha abaixo já te permite brincar de fazer um codec VNC.

Ele te fala quantos pixels estão mudando conforme vc brinca com a tela.

Se alguem fizer um codec de brincadeira, tipo um MPEG da vida e quiser me doar eu aceito. :stuck_out_tongue:

O objetivo e compremir esses bytes que estão mudando para eu poder jogar isso na rede.

>

Eu fiz dois codecs de compressão de vídeo extremamente primitivos ao meu ver, mas que normalmente funcionam pelo que deu para mim testar.
A versão 1 e a versão 2 não são compatíveis entre si.
Ambos possuem suporte para frames codificados na forma de array de bytes, array de shorts e array de ints, só que usando-se arrays de shorts e arrays de ints, cada elemento do array precisa representar exatamente 1 pixel.
Para usar os codecs, basta chamar encodeFrame passando-se parâmetros e depois decodeFrame passando-se parâmetros.
O tamanho do vídeo e a paleta de cores deve se manter constante ou o codec pára de funcionar sem avisar.
Ambos os codecs também não suportam múltiplas threads.
Uma diferença entre os codecs é que a versão 2 usa compressão via análise de macroblocos 8x8, o que aumenta significativamente a compressão pelo que pude testar.
Ambos codecs não criam qualquer tipo de perda na qualidade da imagem (são “lossless”).
Ambos codecs são parte do código da minha solução de acesso remoto sob licença GPLv3 batizada como “Satan-AnyWhere” que devo lançar em algum momento.
Se der, gostaria de ser alertado caso os codecs apresentem algum erro.

Inté.

saoj

Parabéns!!!

KWill

Consegui, acho que praticamente implementei um quase-shell-remoto com um quase-VNC e outra coisas. Os meus amigos que testaram o Satan-AnyWhere afirmam que ele poderia se chamar JNetBus ou NetBus4J, devido a algumas funcionalidades remotas existentes nele. Aviso que o código do Satan-AnyWhere começou a ser desenvolvido a um bom tempo atrás, e pode muito bem ter diversas pérolas. Antes de sair usando ele, recomendo uma leitura ao arquivo “readme-pt-br.txt”, que é o único manual do Satan-AnyWhere que disponibilizo. A versão que estou disponibilizando no fórum não é uma versão definitiva de lançamento ainda. Para ligar o “modo VNC”, deve-se procurar no manual instruções sobre como inicializar o modo gráfico.

Por causa do limite de tamanho dos anexos, vou ter que disponibilizar o Satan-AnyWhere na forma de 5 volumes zip no fórum. Espero que esse aplicativo que desenvolvo sirva para alguma coisa.

Editado: Atualizei os anexos, pois haviam bugs em alguns módulos.

Inté.

KWill

Parte 2.

Inté.

KWill

Parte 3.

Inté.

KWill

Parte 4.

Inté.

KWill

Parte 5.

Inté.

fabiofalci

KWill, q tal colocar num sourceforge ou java.net da vida?

KWill

Era uma das idéias, mas estou pensando em fazer isso depois de um completo refactoring pois pelo menos para mim o código tá uma grande bagunça.

Por ora eu deixo por aqui no fórum para o pessoal usar/opinar/etc.

Inté.

Criado 9 de junho de 2005
Ultima resposta 18 de ago. de 2008
Respostas 31
Participantes 10