JPEG压缩导致GDI +异常

时间:2016-01-03 10:21:04

标签: vb.net compression jpeg gdi+

所以,我正在开发一个LAN-Video-Streaming程序,它记录单个图像并将它们发送出去。因为每秒发送30张1920x1080图片太多了,为了获得30FPS,我做了一些研究并发现了JPEG压缩。问题是,当我尝试保存压缩的JPEG时,它会抛出System.Runtime.InteropServices.ExternalException,并附加以下信息:General error in GDI+.

这是我的代码:

Private Sub Stream() Handles StreamTimer.Tick

    If Streaming = True Then

        Try
            ScreenCap = New Bitmap(Bounds.Width, Bounds.Height)

            GFX = Graphics.FromImage(ScreenCap)
            GFX.CopyFromScreen(0, 0, 0, 0, ScreenCap.Size)

            Dim Frame As New Bitmap(ScreenCap, Resolution.Split(";")(0), Resolution.Split(";")(1))

            Dim jpgEncoder As ImageCodecInfo = GetEncoder(ImageFormat.Jpeg)
            Dim myEncoder As Encoder = Encoder.Quality
            Dim myEncoderParameters As New EncoderParameters(1)
            Dim myEncoderParameter As New EncoderParameter(myEncoder, Compression)
            myEncoderParameters.Param(0) = myEncoderParameter

            Frame.Save(My.Computer.FileSystem.SpecialDirectories.Temp & "\LSSFrame.jpg", jpgEncoder, myEncoderParameters) 'Error occurs in this line

            Using FS As New FileStream(My.Computer.FileSystem.SpecialDirectories.Temp & "\LSSFrame.jpg", FileMode.Open)
                Frame = Image.FromStream(FS)
                FrameSizeStatus.Text = Math.Round(FS.Length / 1000) & "KB"
                FS.Close()
            End Using

            PreviewBox.Image = Frame
            FPSStat += 1

            FlushMemory()

            If ViewerIPs.Count > 0 Then

                For i = 0 To ViewerIPs.Count - 1
                    SendFrame(ViewerIPs(i), Frame)
                Next

            End If
        Catch ex As Exception
            LostFrames += 1
        End Try
    End If

End Sub

感谢任何帮助!

1 个答案:

答案 0 :(得分:1)

部分原因是您没有处置您创建的GraphicsBitmap个对象。错误消息不是很有用,但不处理这些消息会使资源无法恢复。

该程序还有很多事情要做。如果将其分解为部分,则可能更容易对性能进行微调等。

' form level objects
Private jEncParams As EncoderParameters
Private jpgEncoder As ImageCodecInfo
...
' inititalize somewhere when the process starts:
Dim quality As Int64 = 95

jpgEncoder = GetEncoder(ImageFormat.Jpeg)
Dim myjEnc As Imaging.Encoder = Imaging.Encoder.Quality

jEncParams = New EncoderParameters(1)
' quality is inverse to compression
jEncParams.Param(0) = New EncoderParameter(myjEnc, quality)

由于每个屏幕快照都不会更改编码器和质量元素,因此请创建一次并重新使用它们。然后你的计时器事件:

Dim scrBytes = GetScreenSnap(1280, 720)
' do something to send them....maybe queue them?
Console.WriteLine("image size: {0}k", (scrBytes.Length / 1024).ToString)

优化SendFrame超出了此Q / A的范围,但获取屏幕截图与发送不同。

Private Function GetScreenSnap(w As Int32, h As Int32) As Byte()

    Using bmpScrn As New Bitmap(My.Computer.Screen.Bounds.Width, My.Computer.Screen.Bounds.Height)
        Using g As Graphics = Graphics.FromImage(bmpScrn)
            g.CopyFromScreen(0, 0, 0, 0, bmpScrn.Size)
        End Using       ' done with graphics

        Using bmpThumb As New Bitmap(bmpScrn, w, h),
            ms As New MemoryStream
            bmpThumb.Save(ms, jpgEncoder, jEncParams)
            Return ms.ToArray
        End Using        ' dispose of bmp
    End Using            ' dispose of bmpScrn

End Function

没有特别的原因,我正在缩略图整个屏幕。您似乎没有使用Bounds.Width, Bounds.Height,因为这会引用表单。如果表单最大化,它将仅用作屏幕快照。关键点:

  • 这是基于您可以/将要在流中发送字节数组的想法。因此,我将其保留为字节数组,而不是仅创建一个BMP(可能)稍后将其转换回来。
  • 无需创建磁盘文件即可获得大小。如果数组包含编码字节,则它将具有相同的大小。
  • 我从未使用过COMPRESSION参数,但我知道质量与压缩相反。

各种质量因素的结果大小:

  

100 = 462
      95 = 254
      90 = 195
      80 = 147

严格来说,您不需要编码器,bmpThumb.Save(ms, Imaging.ImageFormat.Jpeg)也可以使用,但是单个编码器对象可以提供更精细的调整。

对于发送部分,您可能需要StackQueueLinkedList来存储字节数组。这将进一步隔离让图像发送它们。该集合将是一种ToDo / ToSend列表。

然后,如果有多个收件人,我会考虑将SendFrame作为Task,或许每次发送2-3个。可能有一点,接收者的数量会影响你抓住新的速度。