2009-12-01 11 views
22

Sto imparando WCF, LINQ e alcune altre tecnologie scrivendo, da zero, un'applicazione di controllo remoto personalizzata come VNC. La sto creando con tre obiettivi principali in mente:Modo efficiente per inviare immagini tramite WCF?

  1. Il server fornirà il "controllo remoto" a livello di applicazione (ovvero finestre senza interruzioni) anziché accesso desktop completo.
  2. Il client può selezionare qualsiasi numero di applicazioni in esecuzione sul server e ricevere un flusso di immagini di ciascuna di esse.
  3. Un client può connettersi a più di un server contemporaneamente.

In questo momento sto usando WCF per inviare un array di byte che rappresenta la finestra di essere inviato: implementazione

using (var ms = new MemoryStream()) { 
    window.GetBitmap().Save(ms, ImageFormat.Jpeg); 
    frame.Snapshot = ms.ToArray(); 
} 

GetBitmap:

var wRectangle = GetRectangle(); 
var image = new Bitmap(wRectangle.Width, wRectangle.Height); 
var gfx = Graphics.FromImage(image); 

gfx.CopyFromScreen(wRectangle.Left, wRectangle.Top, 0, 0, wRectangle.Size, CopyPixelOperation.SourceCopy); 

return image; 

E 'poi inviato tramite WCF (TCPBinding e sarà sempre su LAN) al client e ricostruito in un modulo di finestre vuoto senza bordi come questo:

using (var ms = new MemoryStream(_currentFrame.Snapshot)) 
{ 
    BackgroundImage = Image.FromStream(ms); 
} 

Mi piacerebbe rendere questo processo il più efficiente possibile sia in termini di CPU che di utilizzo della memoria con larghezza di banda in arrivo al terzo posto. Sto mirando ad avere il client connesso a 5+ server con 10+ applicazioni per server.

Il mio metodo esistente è l'approccio migliore (pur continuando a utilizzare queste tecnologie) e c'è qualcosa che posso fare per migliorarlo?

idee che sto cercando in (ma non ho alcuna esperienza con):

  • utilizzando una libreria grafica open source per catturare e salvare le immagini al posto della soluzione NET.
  • Salvataggio come PNG o altro tipo di immagine anziché JPG.
  • Invia immagine delta anziché un'immagine completa ogni volta.
  • Provare e "registrare" le finestre e creare un flusso video compresso anziché immagini istantanee (mpeg?).

risposta

20

È necessario essere consapevoli di questi punti:

  • trasporto: TCP codifica dei messaggi/binaria sarà modo più veloce per trasferire i dati di immagine
  • cattura
  • Image: potete contare su P/Invoke per accedere ai dati dello schermo, in quanto ciò può essere più veloce e consumare più memoria. Alcuni esempi: Capturing the Screen Image in C# [P/Invoke], How to take a screen shot using .NET [Managed] e Capturing screenshots using C# (Managed)
  • È necessario ridurre i dati dell'immagine prima di inviarli;
    • scegliere il formato di immagine con saggezza, come alcuni formati hanno compressione nativo (in formato JPG)
    • un esempio dovrebbe essere Find differences between images C#
    • invio unica immagine diff, è possibile ritagliare e basta inviare le zone non vuoti
  • Cercare di ispezionare i messaggi WCF. Questo ti aiuterà a capire come i messaggi sono formattati e ti aiuterà a identificare come rendere i messaggi più piccoli.

Proprio dopo passando per tutte questa procedura e di essere soddisfatti del vostro codice finale, è possibile scaricare VncSharp source code. Implementa the RFB Protocol(Wikipedia entry), "a simple protocol for remote access to graphical user interfaces. Because it works at the framebuffer level it is applicable to all windowing systems and applications, including X11, Windows and Macintosh. RFB is the protocol used in VNC (Virtual Network Computing)."

4

Dai un'occhiata a questo: Large Data and Streaming (WCF)

+0

Ho già letto quella sezione su msdn prima di fare la domanda qui ... Sono molto curioso di capire cosa dovrebbe essere cambiato nel mio approccio e perché. – InvertedAcceleration

0

Invece di catturare l'intera immagine è sufficiente inviare sottosezioni più piccoli dell'immagine. Significato: iniziare nell'angolo in alto a sinistra, inviare un'immagine di 10x10 pixel, quindi "spostare" dieci pixel e inviare il successivo 10px quadrato e così via. È quindi possibile inviare decine di piccole immagini e quindi aggiornare l'immagine completa dipinta sul client. Se hai usato RDC per visualizzare le immagini su una macchina remota, probabilmente l'hai visto fare questo tipo di pittura su schermo.

Utilizzando le sezioni di immagine più piccole è quindi possibile suddividere i delta, quindi se non è cambiato nulla nella sezione corrente, si può saltare tranquillamente, informare il cliente che si sta saltando e poi passare alla successiva sezione.

Sicuramente vorrete usare la compressione per inviare le immagini. Tuttavia, dovresti controllare se ottieni file di dimensioni più ridotte utilizzando la compressione simile a gZip o se l'uso di un codec di immagine ti dà risultati migliori. Non ho mai eseguito un confronto, quindi non posso dire per certo in un modo o nell'altro.

+0

Sì, credo che dividere l'immagine in tessere e rilevare se una tessera è cambiata dall'ultimo fotogramma è sicuramente un'ottimizzazione che devo implementare. Penso che il percorso più efficace sia quello di far funzionare tutto il meglio possibile sull'intera immagine (ottenere la giusta compressione, rilevare se l'immagine è cambiata dall'ultimo fotogramma, utilizzare il metodo più veloce per serializzare l'immagine, ecc.) E poi dividere l'immagine e applica tutti i miglioramenti a ciascuna tessera. – InvertedAcceleration

+0

Verificherò la differenza di dimensioni tra i vari formati di immagine VS compressione generica, ma sono abbastanza sicuro che per essere veramente efficace la compressione deve essere specifica per il contesto. Penso che jpg e png produrranno dimensioni di immagine molto più piccole di un bmp compresso usando bzip o gzip. – InvertedAcceleration

1

Il modo più veloce per inviare dati tra client/server è inviare un array di byte o più array di byte. In questo modo WCF non deve eseguire serializzazioni personalizzate sui dati.

Detto questo. Dovresti usare la nuova libreria WPF/.Net 3.5 per comprimere le tue immagini invece di quelle di System.Drawing. Le funzioni nello spazio dei nomi System.Windows.Media.Imaging sono più veloci di quelle precedenti e possono ancora essere utilizzate in winforms.

Per sapere se la compressione è la strada da percorrere, è necessario eseguire un benchmark dello scenario per sapere come il tempo di compressione/decompressione si confronta con il trasferimento di tutti i byte non compressi.

Se si trasferiscono i dati su Internet, la compressione aiuterà sicuramente. Tra i componenti sulla stessa macchina o su una LAN, il vantaggio potrebbe non essere così ovvio.

Si potrebbe anche provare a comprimere l'immagine, quindi tagliare i dati e inviare in modo asincrono con un chunk id che si puzzle insieme sul client. Le connessioni Tcp iniziano lentamente e aumentano la larghezza di banda nel tempo, quindi l'avvio di due o quattro allo stesso tempo dovrebbe ridurre il tempo di trasferimento totale (tutto dipende dalla quantità di dati che si sta inviando). Il chunking dei byte di immagini compresse è anche più semplice in termini di logica rispetto a fare le tessere nelle immagini reali.

Riassunto: System.Windows.Media.Imaging dovrebbe aiutare sia la cpu che la larghezza di banda rispetto al codice corrente. Per quanto riguarda la memoria, direi lo stesso.

0
  1. La soluzione mi sembra soddisfacente, ma suggerisco (come altri) di utilizzare le tessere e di comprimere il traffico quando possibile. Inoltre, penso che dovresti inviare l'intera immagine una volta ogni tanto, solo per essere sicuro che i delta del cliente abbiano una "base" comune.

  2. Forse è possibile utilizzare una soluzione esistente per lo streaming, ad esempio RTP-H263 per lo streaming video. Funziona alla grande, utilizza la compressione ed è ben documentato e ampiamente utilizzato. È quindi possibile saltare la parte WCF e passare direttamente alla parte di streaming (su TCP o su UDP). Se la soluzione dovesse andare in produzione, forse l'approccio di streaming H263 sarebbe migliore in termini di reattività e utilizzo della rete.

4

Ho lavorato su un progetto simile qualche tempo fa. Questo è stato il mio approccio generale:

  • Rasterized la bitmap catturato alle piastrelle di 32x32
  • Per determinare quali piastrelle avevano cambiato tra i fotogrammi che ho usato il codice non sicuro per confrontarli 64-bit alla volta
  • Sul set di piastrelle delta ho applicato uno dei filtri PNG per migliorare comprimibilità e aveva i migliori risultati con il Paeth filter
  • Utilizzato DeflateStream per comprimere i delta filtrati
  • Usato BinaryMessageEncoding personalizzato vincolanti per il servizio per trasmettere il dati in binario invece della versione codificata Base64 predefinita

Alcune considerazioni sul lato client. Quando si trattava di grandi quantità di dati trasferiti tramite un servizio WCF, ho riscontrato che alcuni parametri di HttpTransportBinding e XmlDictionaryRenderQuotas erano impostati su valori piuttosto conservativi. Quindi vorrai aumentarli.

+0

"Sul set di tessere delta ho applicato uno dei filtri PNG per migliorare la compressibilità e ho ottenuto i risultati migliori con il filtro Paeth" Mi chiedevo come sarebbe stato realizzato in C#, poiché non vedo dove applicare tale filtro e EncoderParameters non accetta nulla del genere. Qualche suggerimento? –

0
Bitmap scrImg = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); 
Graphics scr; 
scr.CopyFromScreen(new Point(0, 0), new Point(0, 0), Screen.PrimaryScreen.Bounds.Size); 
testPictureBox.Image = (Image)scrImg; 

Uso questo codice per acquisire il mio schermo.

Problemi correlati