UDP Packete teilen (Desktop Capture)

  • VB.NET

Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von djmatrix1987me.

    UDP Packete teilen (Desktop Capture)

    Guten Abend / Nacht liebe Community,

    ich stehe vor nem problem... Und zwar einem UDP Send byte Problem. Ich laufe je nach Auflösung des Desktops in eine Exception hinein. (Das Bild ist zu groß für den UDP Stream)

    Was ich machen will / muss:
    Ich habe einen Server, der sendet den aktuellen Desktop an einen Client. Der Client bekommt diesen und packt es in eine Picturebox. Funktioniert auch.
    Das Problem hierbei, das ab einer Auflösung von 1024x768 nix mehr gesendet wird, da das UDP Packet zu groß ist. Somit bleiben mir nun 2 Verschiedene Lösungsmöglichkeiten.

    1. Die UDP Packete splitten und auf der Client seite zusammen setzten
    oder
    2. Die Desktopaufnahme bzw das Bild in Kacheln zu schneiden und zu senden.

    Wobei die 2. Möglichkeit mir etwas zu aufwendig erscheint.
    Nach ca 1 Woche googlen und lesen muss ich mich nun leider an euch wenden. Ich habe wirklich keinen ansatz, wie ich dieses bewärkstelligen könnte.

    Hier einmal der Code:

    Server Seite:

    VB.NET-Quellcode

    1. Try
    2. udpClient = New UdpClient
    3. udpClient.Connect(GLOIP, GLOINTPORT)
    4. Dim b As New Bitmap(My.Computer.Screen.WorkingArea.Width, My.Computer.Screen.WorkingArea.Height)
    5. Using g As Graphics = Graphics.FromImage(b)
    6. g.CopyFromScreen(0, 0, 0, 0, New Size(My.Computer.Screen.WorkingArea.Width, My.Computer.Screen.WorkingArea.Height))
    7. End Using
    8. Dim bytCommand As Byte() = Image2ByteArray(b)
    9. Dim pRet As Integer = udpClient.Send(bytCommand, bytCommand.Length)
    10. Console.WriteLine("bytes send to " & GLOIP.ToString & ": " & pRet.ToString)
    11. udpClient.Close()
    12. Catch e As Exception
    13. Console.WriteLine(e.Message)
    14. End Try

    Client Seite

    VB.NET-Quellcode

    1. Try
    2. Dim receiveBytes As [Byte]() = receivingUdpClient.Receive(RemoteIpEndPoint)
    3. Me.Invoke(New returntoimage_cb(AddressOf loadimage), ByteArray2Image(receiveBytes))
    4. Catch : End Try


    Und hier die Funktionen fürs Bild:

    VB.NET-Quellcode

    1. Public Function Image2ByteArray(ByVal Bild As Image) As Byte()
    2. Dim MS As New IO.MemoryStream
    3. Bild.Save(MS, System.Drawing.Imaging.ImageFormat.Jpeg)
    4. MS.Flush()
    5. Return MS.ToArray
    6. End Function

    VB.NET-Quellcode

    1. Public Function ByteArray2Image(ByVal ByAr() As Byte) As Image
    2. Dim img As Image
    3. Dim MS As New IO.MemoryStream(ByAr)
    4. Try
    5. img = Image.FromStream(MS)
    6. Catch ex As Exception
    7. Return Nothing
    8. End Try
    9. Return img
    10. End Function



    Ich hoffe ihr könnt mir paar Tips geben wie ich das Problem in den Griff bekomme.

    Bis dahin gute Nacht und ein schönen Sonntag noch :)
    Hi
    ich habe mir bereits mal ein Verfahren dazu ausgedacht. Ich würde erst eine Bitmaske bestimmen, die die Bits, die sich geändert haben, bestimmt (jene, die gleichgeblieben sind werden mit 0 markiert, sonstige mit 1) und dann diese Bitmaske so komprimieren, dass sich wiederholende Bits zu einem Byte zusammensetzen. Hierbei würde ich drei Fälle unterscheiden:
    - 64 1-Bits wiederholen sich
    - 128 0-Bits wiederholen sich
    - es ist keine Wiederholung sinnvoll
    Der erste Fall wird durch durch zwei Bits zum Wert 0 signalisiert. Danach folgt die Anzahl in den verbleibenden 6 Bits (00xxxxxx).
    Der zweite Fall wird durch 1 führendes Bit zum Wert 1 signalisiert. Danach folgt die Anzahl in den verbleibenden 7 Bits (1xxxxxxx).
    Der dritte Fall wird durch 1 führendes Bit zum Wert 0 und ein darauffolgendes Bit 1 signalisiert. Danach folgt die Anzahl in den verbleibenden 6 Bits und danach folgen Anzahl Bits, die die Maske fortsetzen (01xxxxxx(x^Anzahl)). Der Fall tritt dann in Kraft, wenn die Zahl der Bytes durch die anderen beiden Fälle größer wäre, als durch eine direkte Angabe. Sowas tritt bspw. bei einer Kombination von 010101 auf, da dann jeweils 1 Byte pro Datenpacket benötigt würde und sich die letztendliche Menge auf 6 Bytes belaufen würde. Das ist eigentlich das einzig schwierigere an dem Algorithmus (schwer ist es immer noch nicht, wenn man verstanden hat, was ich meine). Alternativ könntest du auch nicht nur Zeilen betrachten, sondern auch ganze Rechtecke, aber das macht den Algorithmus nur noch komplizierter und ineffizienter und spart in einigen Fällen sogar weniger ein. (I)

    Die zu übertragenden Rohdaten ergeben sich also zu den Pixeln, die sich im Vergleich zum letzten Datenpacket geändert haben (jeweils nur 3 Bytes oder bei niedrigerer Qualität weniger, siehe unten).

    Also schreibst du folgendes raus:
    - Anzahl der übertragenen Bildschirme
    und eben für jeden Bildschirm
    - Alter Screenindex
    - Neuer Screenindex
    - Breite und Höhe des Screens
    - Länge der komprimierten Bitmaske in Bytes
    - Länge der Rohdaten in Bytes
    - komprimierte Bitmaske
    - Rohdaten

    Die Rohdaten bestehen aus den RGB-Farben des Pixels, d.h. im (für 32-Bit-XRGB-Farbräume) verlustfreien Modus würden hier 8-Bit R, 8-Bit G und 8 Bit B übertragen. Du kannst auch pro Pixel noch Daten sparen, indem du die Qualität auf R5, G5, B5, X1 heruntersetzt, dann würden statt 256 Möglichkeiten pro Kanal nur noch 32 bleiben und damit ein hoher Verlust da sein, dafür aber nur noch 2 Bytes und nicht 3 benötigt. Außerdem könnte man mit dem verbleibenden Bit noch etwas herumtricksen, z.B. eine Auftrennung in zwei verschiedene Farbräume (Helligkeit < 0.5 und >= 0.5 oder sowas z.B.) aber das ist, denke ich erst mal, eher overkill.
    Als erstes definierst du global ein Array, das die alten Bitmapdaten (die kompletten, nicht die Rohdaten) enthalten wird und Size-Array, das die alten Bildschirmabmessungen enthalten wird. Zusätzlich solltest du noch schauen, dass du für die Screens eindeutige Identifikationen findest, damit du auf das auch reagieren kannst (z.B. ein Screen-Array behalten und überprüfen, ob die Screens identisch sind, das sollte aber nach der Umsetzung klar sein).
    Jetzt ermittelst du alle Bildschirme und schreibst die Anzahl an den Output. Folgendes machst du dann pro Screen (vergleiche auch, welche Bildschirme sich geändert haben und passe entsprechend Alter Screenindex und Neuer Screenindex im Header an):
    Zur Rohdatenbestimmung lädst du die Daten über LockBits in ein Integer-Array mit width * height Einträgen und gibst die Daten über UnlockBits sofort wieder frei, da du sie ja bereits im Array hast (siehe Code unten). Die Bitmap wird danach auch nicht mehr benötigt. Hierbei hat jeder Pixel 32 Bit, die im XRGB mit 1 Byte pro Kanal (also X8R8G8B8) vorliegen sollen. X wird nicht mit übertragen, da es 0 sein sollte. Jetzt überprüfst du, ob bereits einmal ein Bildschirminhalt übertragen wurde oder sich die dargestellte Auflösung des Bildschirms geändert hat. Wenn nicht, sind die Daten der letzten Übertragung noch Nothing und die Größe ist auf dem Screen-Objekt noch identisch.
    Wenn ein vollkommen neuer Block zu übertragen ist, ist die Länge der komprimierten Bitmaske entweder 0 oder -1, je nach gewähltem Datentypen. -1 ist sinnvoll, da der Fall 0 Änderungen ebenfalls eintreten kann. Du kannst es auch anders signalisieren, es sollte halt klar sein, dass eine komplette Übertragung stattfindet, da ist eine Bitmaske nur sinnloser Ballast. Danach folgt halt das komplette Integer-Array.
    Wenn kein neuer Block zu übertragen ist, sind die Array-Längen identisch. D.h. du gehst einfach von 0 bis length - 1 durch und überprüfst, ob die Werte gleich oder ungleich sind. Sind sie das, inkrementierst du die Zahl der identischen Bytes und merkst dir, ob das Bit gesetzt oder nicht gesetzt war. Sind die Bits verschieden, überprüfst du auf die oben definierte Bedingung zur Verschiedenheit (I) und schreibst die Daten je nach Fall entsprechend raus. Hierbei sind die Daten eben in Segmente zu maximal 64 bzw. 128 Bits zu unterteilen. Vergiss nicht, dass du den X-Wert nicht rausschreibst. Für die Bitmaske kannst du entweder ein Bit-Array oder aber ein Byte-Array verwenden (ich würde Zweiteres nehmen, auch wenn es komplizierter beim Ansteuern ist. Das i-te Bit setzt du beim ersten Schreibzugriff drauf über bitmask(i\8) = bitmask(i\8) Or (1 << (i Mod 8)))
    Jetzt setzt du den Wert der Variablen, die die alten Daten enthält, auf das Array mit den neuen Daten und updatest die restlichen oben definierten Variablen.
    Übertragen solltest du nur dann, wenn ein Request eingeht, sich der Bildschirminhalt geändert hat und ggf. noch, wenn ein gewisses Zeitintervall überschritten wurde. Requests solltest du dann schicken, wenn zum ersten mal eine Übertragung stattfindet, oder ein Packet übertragen wurde. Außerdem sollte Udp-typisch überprüft werden, dass die ganzen Udp-Datenblöcke übertragen wurden und korrupte Packete über Bord werfen. Der Header ist da besonders kritisch.

    Jetzt noch kurz zum LockBits. Zum Laden in's Array ist ein kleiner Trick nötig:

    VB.NET-Quellcode

    1. Imports System.Runtime.InteropServices
    2. Imports System.Drawing.Imaging

    VB.NET-Quellcode

    1. Shared Function GetBitmapData(bitmap As Bitmap) As Integer()
    2. Dim sz As Size = bitmap.Size
    3. Dim target(sz.Width * sz.Height - 1) As Integer 'hier landen die Daten
    4. 'Zeiger auf das erste Element des Arrays ermitteln
    5. Dim gch As GCHandle = GCHandle.Alloc(target, GCHandleType.Pinned)
    6. Dim bdata As BitmapData = Nothing
    7. Try
    8. 'Beschreibung für die Zielbitmap (hier das Array für die Daten)
    9. Dim tempData As New BitmapData() With {.Scan0 = gch.AddrOfPinnedObject(), _
    10. .Width = sz.Width, _
    11. .Height = sz.Height, _
    12. .Stride = sz.Width * 4, _
    13. .PixelFormat = PixelFormat.Format32bppRgb}
    14. 'Daten beschaffen
    15. bdata = bitmap.LockBits( _
    16. New Rectangle(Point.Empty, bitmap.Size), _
    17. ImageLockMode.ReadOnly Or ImageLockMode.UserInputBuffer, _
    18. PixelFormat.Format32bppRgb, _
    19. tempData)
    20. Finally 'auf jeden Fall den Zeiger auf das Handle und die Bitmapdaten freigeben
    21. gch.Free()
    22. If bdata IsNot Nothing Then bitmap.UnlockBits(bdata)
    23. End Try
    24. Return target 'Rohdaten zurückgeben
    25. End Function

    Das Schreiben funktioniert analog zum Lesen, hier setzt du einfach statt ImageLockMode.ReadOnly das Flag ImageLockMode.WriteOnly.

    Der Algorithmus für die 3 Fälle ist bisschen Tüftelei, aber sonst ist die Maske alleine schon width * height / 8 Bytes lang, das kann schon mal ziemlich viel sein.

    Achja:
    Try-Catches mit leerem Catch-Block und vollständigem Verschlucken der Exception führen schnell zu Fehlverhalten der Software.

    Gruß
    ~blaze~
    Guten "Morgen"

    Ich danke euch allen für eure tollen Tips. Leider muss ich sagen, das ich grade an meine grenzen in vb gestoßen bin :(
    Aber weil ich dieses Projekt dennoch fertig stellen will, werde ich folgendes Versuchen:

    Ich werde den aktuellen Bildschrim in genau 8 Teile Splitten, dieses wird denke ich leicht, in dem ich einfach von dem aktuellen bild 8 kleine Bilder erstelle.
    Dann werde ich die 8 Bilder einfach hintereinander senden. Evtl werd ich dort noch eine kleine Funktion hinzufügen, das vorher den alten Bildausschnitt mit dem neuen Bildausschnitt vergleicht (md5 checksum oder sowas).

    Dann sieht die sendefunktion ungefähr so aus:

    VB.NET-Quellcode

    1. Dim pRet1 As Integer = udpClient.Send(bytCommand1, bytCommand1.Length)
    2. Dim pRet2 As Integer = udpClient.Send(bytCommand2, bytCommand2.Length)
    3. Dim pRet3 As Integer = udpClient.Send(bytCommand3, bytCommand3.Length)
    4. Dim pRet4 As Integer = udpClient.Send(bytCommand4, bytCommand4.Length)
    5. Dim pRet5 As Integer = udpClient.Send(bytCommand5, bytCommand5.Length)
    6. Dim pRet6 As Integer = udpClient.Send(bytCommand6, bytCommand6.Length)
    7. Dim pRet7 As Integer = udpClient.Send(bytCommand7, bytCommand7.Length)
    8. Dim pRet8 As Integer = udpClient.Send(bytCommand8, bytCommand8.Length)


    Ich weiß dass das garantiert bescheuert ist und die experten von euch werden warscheinlich grade mit dem Kopf auf dem Tisch schlagen :D

    Nun stehe ich nur vor einem kleinen Problem. Und zwar muss ich das UDP Packet, das gesedet wird einen art Index mitgeben. Sprich

    VB.NET-Quellcode

    1. Dim pRet2 As Integer = udpClient.Send([INDEX2] & bytCommand2, bytCommand2.Length)

    damit ich weiß, welches Packet auf der anderen seite angekommen ist. Wie kann ich nun den index inklusive den Bytes zusammenführen und auf der anderen seite auseinanderfriemeln?

    Mfg

    Ben
    Ok ich habs soweit alles geschaft. Hänge wie gesagt nur noch an einem Problem.

    Mit dieser Funktion wandel ich die Bilder in ein byte Array

    VB.NET-Quellcode

    1. Public Function Image2ByteArray(ByVal index As Integer, ByVal Bild As Image) As Byte()
    2. Dim MS As New IO.MemoryStream
    3. Bild.Save(MS, System.Drawing.Imaging.ImageFormat.Jpeg)
    4. MS.Flush()
    5. Return MS.ToArray
    6. End Function


    und sende sie dann als udp packet.

    auf der client seite empfange ich den byte array und wandel sie mit dieser funktion wieder in ein bild um

    VB.NET-Quellcode

    1. Public Function ByteArray2Image(ByVal ByAr() As Byte) As Image
    2. Dim img As Image
    3. Dim MS As New IO.MemoryStream(ByAr)
    4. Try
    5. img = Image.FromStream(MS)
    6. Catch ex As Exception
    7. Return Nothing
    8. End Try
    9. Return img
    10. End Function


    Ich muss aber irgendwie diese Packete benennen, damit ich weiß welches Packet es nun ist. Ich dachte so, das ich einfach am anfang des Packets ein integer (1,2,3,4) hinzufügen könnte, das angibt, welches packet das nun ist.
    Aber das bekomm ich nicht hin. Was kann ich da jetzt machen?
    Wie kann ich an das UDP packet einen integer anhängen bzw vorhängen und dieses auf der anderen seite in den index und in das bild splitten?

    Mfg
    Ben