JPEG 压缩导致 GDI+ 异常
JPEG Compression causes GDI+ Exception
所以,我目前正在开发一个 LAN-Video-Streaming 程序,它可以记录单个图像并将它们发送过来。因为每秒发送 30 张 1920x1080 图片太多了,要获得 30FPS,我做了一些研究并找到了 JPEG-Compression。问题是,当我尝试保存压缩的 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
感谢任何帮助!
在某种程度上,您没有处置您创建的 Graphics
或 Bitmap
对象。错误信息不是很有帮助,但不处理这些信息会使资源无法恢复。
该过程中还有很多事情要做。如果它被分解成多个部分,可能更容易 fine-tune 性能等。
' 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
因为那会引用表格。如果表单最大化,它只能用作屏幕快照。要点:
- 这是基于您 can/will 在流中发送字节数组的想法。因此,我将其保留为字节数组,而不是创建 BMP 只是为了(大概)稍后将其转换回来。
- 不需要创建磁盘文件来获取大小。如果数组包含编码字节,它将是相同的大小。
- 我从未使用过 COMPRESSION 参数,但我知道质量与压缩相反。
各种质量因素的结果尺寸:
100 = 462
95 = 254
90 = 195
80 = 147
严格来说您不需要编码器,bmpThumb.Save(ms, Imaging.ImageFormat.Jpeg)
也可以,但单个编码器对象提供更多微调。
对于发送部分,您可能需要 Stack
、Queue
或 LinkedList
来存储字节数组。这将进一步隔离获取图像与发送图像。该集合将是一种 ToDo/ToSend 列表。
然后,如果有多个收件人,我会考虑将 SendFrame
作为 Task
,或许一次发送 2-3 个。接收者的数量可能会影响您获取新接收者的速度。
所以,我目前正在开发一个 LAN-Video-Streaming 程序,它可以记录单个图像并将它们发送过来。因为每秒发送 30 张 1920x1080 图片太多了,要获得 30FPS,我做了一些研究并找到了 JPEG-Compression。问题是,当我尝试保存压缩的 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
感谢任何帮助!
在某种程度上,您没有处置您创建的 Graphics
或 Bitmap
对象。错误信息不是很有帮助,但不处理这些信息会使资源无法恢复。
该过程中还有很多事情要做。如果它被分解成多个部分,可能更容易 fine-tune 性能等。
' 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
因为那会引用表格。如果表单最大化,它只能用作屏幕快照。要点:
- 这是基于您 can/will 在流中发送字节数组的想法。因此,我将其保留为字节数组,而不是创建 BMP 只是为了(大概)稍后将其转换回来。
- 不需要创建磁盘文件来获取大小。如果数组包含编码字节,它将是相同的大小。
- 我从未使用过 COMPRESSION 参数,但我知道质量与压缩相反。
各种质量因素的结果尺寸:
100 = 462
95 = 254
90 = 195
80 = 147
严格来说您不需要编码器,bmpThumb.Save(ms, Imaging.ImageFormat.Jpeg)
也可以,但单个编码器对象提供更多微调。
对于发送部分,您可能需要 Stack
、Queue
或 LinkedList
来存储字节数组。这将进一步隔离获取图像与发送图像。该集合将是一种 ToDo/ToSend 列表。
然后,如果有多个收件人,我会考虑将 SendFrame
作为 Task
,或许一次发送 2-3 个。接收者的数量可能会影响您获取新接收者的速度。