INTELLIGENT WORK FORUMS FOR COMPUTER PROFESSIONALS
Come Join Us!
Are you a Computer / IT professional? Join Tek-Tips now!
- Talk With Other Members
- Be Notified Of Responses
To Your Posts
- Keyword Search
- One-Click Access To Your
Favorite Forums
- Automated Signatures
On Your Posts
- Best Of All, It's Free!
*Tek-Tips's functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.
Partner With Us!
"Best Of Breed" Forums Add Stickiness To Your Site

(Download This Button Today!)
Feedback
"...keep up the good work with this forum, I think this
is the best one around. ...you actually try to help
people learn for themselves. ...I commend you on providing a
very good, open learning atmosphere, where usually egos are left behind..."
Geography
Where in the world do Tek-Tips members come from?
|
Converting a bitmap generated by a webcam to a jpg
|
|
I am trying to find ways to improve converting a bitmap generated by a webcam to a jpg so it can be sent faster via a network to another computer.
My problem is -
I tried GDIplus as per an old strongm examples with, instead of saving the jpg to file, I used:-
GdipCreateFromHDC Picture2.hDC, myGraphics
GdipDrawImage myGraphics, gdiImageTarget, 0, 0
This does quickly paint a jpg picture over a picturebox but this is wiped out by any object moving over it and it's .picture = 0 so there is nothing to send! It seems to be drawing only to the screen, not the picturebox.
Is there a way of using GDIplus to produce a data stream or fill a picturebox.picture like a loadpicture statement does so it can be sent over a network?
Currently I use AviCap32 feeding the webcam to the clipboard.
I then send the BMP in the clipboard over the network in a propertybag without any problem.
Using GDIplus I am able to capture the picture and convert it to the jpg picture'painted'box but I cant send this picture to the network.
GDIplus often freezes the app when you close it needing removal by task manager processes.
I haven't been able to get WIA to work on Win7 so I cant use this either unless someone has an answer to this separate problem! |
|
>GDIplus often freezes
Well, sort of. I'm guessing that it is more likely that the version of my code that you are using doesn't contain all the GDIplus clean-up code (even more likely if you modified my code without realising how important it is to clean up GDIplus objects) - thus, while it may well be GDIplus causing the freeze, the root cause is most likely a programming error. The GDIplus examples I've produced here are normally not of production code quality, they have simply been to illustrate techniques
>This does quickly paint a jpg picture
No, it doesn't.
It paints a bitmap over the device context
Windows knows nothing about jpegs (as we've previously tried to tell you - all graphics are internally represented as bitmaps), apart from how to load and immediately convert them into bitmaps (and, with the right library, how to convert a bitmap to a jpeg for saving)
And GDI+ knows nothing (and cares nothing) about VB controls such a picture boxes. The example code (presumably from my post thread222-1335999: How do I convert a BMP to JPG stream (not a file) 8 Mar 07 2:40, although it may have been from my earlier example that only showed how to save a jpeg) simply uses the VB picture box as the source of a valid Device Context.
>Is there a way of using GDIplus to produce a data stream
Yes, and I showed you exactly how to do that in the referenced example
>or fill a picturebox.picture like a loadpicture statement does so it can be sent over a network?
Well, yes (using the GDI Plus library's OleLoadPicture function which, unlike the API version that we've illustrated a number of times in this forum, can load direct from a stream) - but you need to understand that what ends up in the picture box is a bitmap, not a jpeg - so you'll have wasted a bunch of time doing the conversion to jpeg only to convert it straight back to a bitmap (and with a consequrent loss of quality compared to the origibnal bitmap) for transmission |
|
Thanks for your quick reply
The problem back in 2007 was the questions I asked all degenerated into huge discussions as to methods of transmission and speed and whether I really needed this facility or not. Not wishing to stir up more trouble I gave up really trying to understand it all !
With current computers conversion and display speed is now not an issue but slow networks still are.
So the problem has resurfaced.
I used the code you refer to without alteration and still get the same crash on exit or when I stop in the IDE and restart. Disposeof statements are still there but maybe there should be more?
However I wrongly assumed that a "jpg" could be stored in the second picture box even though I read through all your previous explanations. Now I think about it I should have known better!
As GDI in your example has created a JPG stream, how does one retrieve this stream and feed it to a propertybag and hence a winsock?
This is my current transmission code that has proved error free in sending a large picture
TxPb.WriteProperty "WebCam", Clipboard.Getdata
Winsock1.SendData TxPb.Contents
Then at the receiving end (plus a 2 byte finish sendcomplete code to ensure a complete pic)
set Image1.picture=RxPb.ReadProperty ("WebCam",0)
If I try to write MyStream to the propertybag the error says it "doesn't support persistence"
It does this even when I convert a simple BMP graphic
So now how do I feed the GDI stream to the propertybag writeproperty ?
No doubt the answer is simple but I can't see it. |
|
You need to get away from thinking that a propertybag is the only way of doing this. remember, the property bag is mostly useful for (peristable) ActiveX objects that VB understands (since it allows us to take some shortcuts).
And the way we use the propertybag is to serialise the ActiveX object so that we can send a buffer representing the object (propertybag.Contents) over winsock, which we then convert back in to propertybag at the other end, and from which we then extract the ActiveX object
So, with a stream we can extract the buffer representing that stream, send that buffer via winsock, then recreate the stream at the other end. Same process really.
And dilettante illustrated the basic technique for sending a stream (an ADODB stream rather than an IStream, but the principal is the same) in the same linked thread previously referenced. There now follows an illustrative example that works with an IStream (necessary for this to handle JPEGs). Note that you'll need the references mentioned in the previous thread to olelib and GDI Plus (and I've slightly improved GetEncoderCLSID routine, which was the actual cause of the instability you reported with the original examples).
You'll need two forms, the first with a picturebox and a command button, the second with a picturebox only. Both will require a winsock control. The example takes a bitmap displayed on the first form, converts it to a JPEG, sends the JPEG as a stream over winsock to the second form, where it is displayed:
CODE --> FirstForm
'Requires GDI Plus type library 1.3 and Edanmos OLE 1.8
Option Explicit
Public CannotClose As Boolean
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenW" (lpString As Any) As Long
'Private Declare Function IsBadStringPtrByLong Lib "kernel32" Alias "IsBadStringPtrW" (ByVal lpsz As Long, ByVal ucchMax As Long) As Long
Private Sub Command1_Click()
Dim GpInput As GdiplusStartupInput
Dim Token As Long
Dim myCLSID As CLSID
Dim myEncoderParameters As EncoderParameters
Dim myEncoder(0) As EncoderParameter
Dim gdiImage As Long
Dim gdiImageTarget As Long
Dim Quality As Long
Dim myGraphics As Long
Dim myStream As olelib.IStream ' Use olelib's IStream interface
Dim b() As Byte 'Source of an HGlobal
ReDim b(0) As Byte
' Get and check we have a CLSID for a jpeg codec
If GetEncoderCLSID("image/jpeg", myCLSID) Then
GpInput.GdiplusVersion = 1
If GdiplusStartup(Token, GpInput) <> Ok Then 'Ok comes from gpStatus enumeration
MsgBox "Error loading GDI+!", vbCritical
Else
myEncoderParameters.Count = 1
myEncoder(0).Guid = GdiPlus.CLSIDFromString(EncoderQuality)
myEncoder(0).Type = EncoderParameterValueTypeLong
myEncoder(0).NumberOfValues = 1
Quality = 25 ' 0 to 100%
myEncoder(0).ValuePtr = VarPtr(Quality)
myEncoderParameters.Parameter = myEncoder(0)
GdipCreateBitmapFromHBITMAP Picture1.Picture.Handle, Picture1.Picture.hPal, gdiImage
Set myStream = CreateStreamOnHGlobal(b(0), True)
' OK save image as a (possibly) compressed JPG into a stream
GdipSaveImageToStream gdiImage, myStream, myCLSID, VarPtr(myEncoderParameters) ' one change from my previous example, to save to a stream instead of a file
SendStream myStream ' send the stream via winsock
' OK we can dispose of stream
Set myStream = Nothing
GdipDisposeImage gdiImage
End If
End If
If Token Then GdiplusShutdown Token
End Sub
' helper function to get CLSID of output codec
Private Function GetEncoderCLSID(ByVal strMimeType As String, outCLSID As CLSID) As Long
Dim GpInput As GdiplusStartupInput
Dim Token As Long
Dim lEncoders As Long
Dim lEncodersBuffer As Long
Dim lp As Long
Dim arrEncoders() As ImageCodecInfo
GpInput.GdiplusVersion = 1
If GdiplusStartup(Token, GpInput) <> Ok Then
MsgBox "Error loading GDI+!", vbCritical
Else
GdipGetImageEncodersSize lEncoders, lEncodersBuffer
ReDim arrEncoders(lEncoders * 3 - 1) As ImageCodecInfo 'make sure we have enough space
GdipGetImageEncoders lEncoders, lEncodersBuffer, arrEncoders(0)
GetEncoderCLSID = False
For lp = LBound(arrEncoders) To UBound(arrEncoders)
If StringFromPointer(arrEncoders(lp).MimeTypePtr) = strMimeType Then
outCLSID = arrEncoders(lp).CLSID
GetEncoderCLSID = True
Exit For
End If
Next
End If
If Token Then GdiplusShutdown Token
End Function
' Works for strings that are nullchar terminated
Public Function StringFromPointer(lpString As Long) As String
'Dim sRet As String
'Dim lret As Long
Dim lMaxLength As Long
'Dim lCodec
If lpString = 0 Then
StringFromPointer = ""
Else
lMaxLength = lstrlen(ByVal lpString)
StringFromPointer = Space$(lMaxLength)
CopyMemory ByVal StrPtr(StringFromPointer), ByVal lpString, lMaxLength& * 2
End If
End Function
Private Sub SendStream(myStream As olelib.IStream)
Dim fred As olelib.STATSTG
Dim buffer() As Byte
myStream.Stat fred, STATFLAG_NONAME
ReDim buffer(fred.cbSize * 10000 - 1) As Byte
myStream.Seek 0, STREAM_SEEK_SET
myStream.Read buffer(0), fred.cbSize * 10000 ' stick stream into buffer
Winsock1.SendData CLng(fred.cbSize * 10000) ' Send actual size of stream first ...
Winsock1.SendData buffer ' ... and then send buffer
End Sub
Private Sub Form_Load()
'Form1.Show
Form2.Show
CannotClose = True 'ensures we cannot close form2 by mistake if form1 is still open
' Non-robust technique
Winsock1.Connect
Do Until Winsock1.State = sckConnected
DoEvents
Loop
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
CannotClose = False
Unload Form2
Winsock1.Close
End Sub
Private Sub Winsock1_Close()
Winsock1.Close
End Sub
CODE --> SecondForm
Option Explicit
Private myStream As olelib.IStream
Private Sub Form_Load()
Winsock1.Listen
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
If Form1.CannotClose Then Cancel = True
End Sub
Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
If Winsock1.State <> sckClosed Then Winsock1.Close
Winsock1.Accept requestID
ResetStream
End Sub
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
Dim StreamStats As olelib.STATSTG
Dim gdiImageTarget As Long
Dim GpInput As GdiplusStartupInput
Dim Token As Long
Dim myGraphics As Long
Dim buffer() As Byte
Static SentStreamSize As Long
If SentStreamSize = 0 Then ' Have we got size of data yet?
Winsock1.GetData SentStreamSize, vbLong
'Debug.Print "Expecting: " & SentStreamSize & " bytes in stream"
Else ' Ok, start accumulating data into the stream
ReDim buffer(0) As Byte ' reset input buffer
Winsock1.GetData buffer, vbArray Or vbByte ' read the byte array
myStream.Seek 0, STREAM_SEEK_END
myStream.Write buffer(0), bytesTotal ' append to a stream
myStream.Stat StreamStats, STATFLAG_NONAME
If SentStreamSize <= StreamStats.cbSize * 10000 Then 'we've accumulated all the data
'Debug.Print "StreamStats thinks: " & StreamStats.cbSize * 10000
GpInput.GdiplusVersion = 1
If GdiplusStartup(Token, GpInput) <> Ok Then
MsgBox "Error loading GDI+!", vbCritical
Else
GdipLoadImageFromStream myStream, gdiImageTarget 'load GDI+ image from stream (which in this case contains a JPEG)
' At this stage we have loaded a jpg into GDI+ from a stream
' so we can reset that stream for reuse
ResetStream
' Now display, letting GDI+ do all the hard work
GdipCreateFromHDC Picture1.hDC, myGraphics
GdipDrawImage myGraphics, gdiImageTarget, 0, 0
GdipDisposeImage gdiImageTarget
GdipDeleteGraphics myGraphics
End If
SentStreamSize = 0
End If
End If
If Token Then GdiplusShutdown Token
End Sub
Private Sub ResetStream()
Dim b(0) As Byte ' declaration to get an HGlobal
Set myStream = Nothing
Set myStream = CreateStreamOnHGlobal(b(0), True)
End Sub
|
|
Thank you for your detailed reply and the time you must have taken to do it.
It works beautifully between 2 computers however it does not answer my original question and I still can't achieve what I want.
The method of transmission is not the issue.
The received "jpg picture" using this method, is no different to what the transmitter GDI BMP to JPG conversion produces before it is sent to the winsock so that I still can't handle the received picture in any way.
I don't want to show the JPG in a picturebox at the receiving end and if I make a dummy invisible picturebox, I can't draw to it or extract it's picture for another purpose or send it on later to another computer as a jpg. As you pointed out it would be a bitmap again anyway.
At the receiver, I have to show 16 clients pictures on the one screen in reduced imageboxes and magnify the one that is clicked on for examination.
If I could just convert the GDI stream to a bunch of bits that an imagebox.picture will understand before or after sending them, then my problem would be solved.
Eg in Dataarrival instead of "letting GDI+ do all the hard work", something like perhaps:-
dim MyPicture as Picture
set MyPicture = GDISomeOtherProcess MyGraphics,gdiImageTarget
Image1.Pictue=MyPicture (locally - not a picturebox)
or
Image1.Picture=MagicalConversionProcess MyStream.read(?,?)
Is there a more detailed understandable description on the internet of the functions in GDI than Vb gives when you press f2? I couldn't find one.
|
|
>The method of transmission is not the issue.
Well then you shouldn't have said
>I cant send this picture to the network
or
>Is there a way of using GDIplus to produce a data stream ... so it can be sent over a network?
(I've ignored the 'or fill a picturebox.picture like a loadpicture statement does' because what you'd end up with there is a bitmap being converted to a jpg, then being converted to a bitmap before being sent, which is completely pointless and a waste of cycles)
Here, however, is an alternative version of the code for form 2, which includes a function that will turn a GDI+ image into a StdPicture (i.e a Picture) object: CODE --> Form2
Option Explicit
Private mystream As olelib.IStream
Private Sub Form_Load()
Winsock1.Listen
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
If Form1.CannotClose Then Cancel = True
End Sub
Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
If Winsock1.State <> sckClosed Then Winsock1.Close
Winsock1.Accept requestID
ResetStream
End Sub
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
Dim StreamStats As olelib.STATSTG
Dim gdiImageTarget As Long
Dim GpInput As GdiplusStartupInput
Dim Token As Long
Dim myGraphics As Long
Dim buffer() As Byte
Static SentStreamSize As Long
If SentStreamSize = 0 Then ' Have we got size of data yet?
Winsock1.GetData SentStreamSize, vbLong
'Debug.Print "Expecting: " & SentStreamSize & " bytes in stream"
Else ' Ok, start accumulating data into the stream
ReDim buffer(0) As Byte ' reset input buffer
Winsock1.GetData buffer, vbArray Or vbByte ' read the byte array
mystream.Seek 0, STREAM_SEEK_END
mystream.Write buffer(0), bytesTotal ' append to a stream
mystream.Stat StreamStats, STATFLAG_NONAME
If SentStreamSize <= StreamStats.cbSize * 10000 Then 'we've accumulated all the data
'Debug.Print "StreamStats thinks: " & StreamStats.cbSize * 10000
GpInput.GdiplusVersion = 1
If GdiplusStartup(Token, GpInput) <> Ok Then
MsgBox "Error loading GDI+!", vbCritical
Else
GdipLoadImageFromStream mystream, gdiImageTarget 'load GDI+ image from stream (which in this case contains a JPEG)
' At this stage we have loaded a jpg into GDI+ from a stream
' so we can reset that stream for reuse
ResetStream
' Now display, letting GDI+ do all the hard work
'GdipCreateFromHDC Picture1.hDC, myGraphics
'GdipDrawImage myGraphics, gdiImageTarget, 0, 0
' New version, working with a StdPicture object created from the GDI+ image
Set Picture1.Picture = CreatePictureFromGDIPlusImage(gdiImageTarget)
GdipDisposeImage gdiImageTarget
GdipDeleteGraphics myGraphics
End If
SentStreamSize = 0
End If
End If
If Token Then GdiplusShutdown Token
End Sub
Private Sub ResetStream()
Dim b(0) As Byte ' declaration to get an HGlobal
Set mystream = Nothing
Set mystream = CreateStreamOnHGlobal(b(0), True)
End Sub
Private Function CreatePictureFromGDIPlusImage(gdiImage As Long) As StdPicture
Dim lpPictDesc As olelib.PICTDESC
Dim myUUID As olelib.UUID
Dim hBitmap As Long
Dim background As Long
olelib.CLSIDFromString IPictureGUID, myUUID
GdipCreateHBITMAPFromBitmap gdiImage, hBitmap, background
With lpPictDesc
.cbSizeofstruct = Len(lpPictDesc)
.hBitmap = hBitmap
.hpal_or_xExt = 0&
.PICTYPE = PICTYPE_BITMAP
End With
Set CreatePictureFromGDIPlusImage = olelib.OleCreatePictureIndirect(lpPictDesc, myUUID, False)
End Function
|
|
Sorry, the bit about the network was only to give people an overview of what I was attempting.
If I had been able to "send this picture to the network" in the same format as a picture from a picturebox then I wouldn't have had the current problem.
I tried your latest example but it doesn't recognise CreatePictureFromGDIPlusImage (sub or function not found)
I can't see anything like it in GDIPlus or OLE in the object browser either.
References I am using:-
Edanmo's OLE interfaces & functions v1.81 olelib.tlb 1/2/2004
GDI+ Type Library 1.21( dseaman@ig.com.br) Gdiplus.tlb 11/9/2003
There are a number of other different versions of GDIPlus.dll in my computer with dates:-
6/2/2003, 7/2/2003, 4/08/2004, 27/02/2007, 13/8/2009, 14/4/2008
Are any of these the same as yours?
Most of them say "cant add a reference" when you try to use them.
|
|
>If I had been able to "send this picture to the network" in the same format as a picture from a picturebox then I wouldn't have had the current problem.
Quite so. But solving converting the GDI+ JPEG image into a picture object at the sending end, i.e into a bitmap, completely defeats the purpse of what you were trying to achieve, as you'd have:
1) Convert Picture (containing a bitmap) into JPEG
2) Convert JPEG back into a Picture (containing a bitmap)
3) Stick Picture into Propertybag
4) Send contents of Propertybag via winsock
5) Load transmitted contents into a new propertybag at destination
6) extract contents of propertybag as Picture
7) Convert Picture (containing a bitmap) into JPEG
8) Convert JPEG to back into a Picture (containing a bitmap)
9) Display Picture (containing bitmap)
Now at some point you'd presumably have figured out that since you were actually transmitting a Picture then steps 7 and 8 would be completeley superfluous. And then you'd realise that in that case steps 1 and 2 would not be necessary
And then you'd realise that transmitting a Picture completely defeats the purpose of what you are doing, so you need to figure out how to send a JPEG over Winsock - and THAT'S what I focussed on because that is what it looked like you were really asking (because simply providing you with code that would convert a JPEG back into a Picture object would NOT solve your problem)
>I tried your latest example but it doesn't recognise CreatePictureFromGDIPlusImage (sub or function not found)
>I can't see anything like it in GDIPlus or OLE in the object browser either
Er, its a function I wrote, and is the last few lines of the example. Are you SURE you are using my code? |
|
At last success!
It converts and transmits a 1 meg BMP converted to JPG ten times faster then it takes to send the BMP between 2 computers on a 100mb network.
When I cut and pasted your latest code I missed the function at the bottom!
Regarding my "methods"
I never intended such a convoluted solution as you suggest. I was asking various questions about various stages of the exercise that may have given this impression in an attempt to get to the bottom of it.
What I meant from the very first when I said JPG picture was, was the bunch of bits that represents an encoded RGB picture in JPG format.
I admit this is not the same as a Microsoft definition of a "Picture" object but I assumed you knew what I meant. Sorry for my bad use of definitions.
To me a JPG is still a picture but I will refrain from using this in future.
Microsoft have hijacked the word 'picture' which to all normal people is something you can see or file away.
So the very initial request was how do I
1) Convert Picture (containing a bitmap) into JPEG code
3) Stick JPG code into Propertybag
4) Send contents of Propertybag via winsock
6) extract contents of propertybag to a variable
7) feed this variable containing the JPG Code to an Imagebox and save it to feed to other computers when called on later.
Step 7 was the last remaining hurdle but now it is solved thanks to strongm again!
I will look at using your code for the transmission instead of a propertybag if I can make this coexist with sending text as well.
Is there any understandable published material on how to use GDIplus functions? I can't find one on the internet. |
|
>At last success!
Hurrah!
>I never intended such a convoluted solution as you suggest
I know. That method was essentially simply a thought experiment. I've lost the will to explain why I used it, though.
>What I meant from the very first when I said JPG picture was, was the bunch of bits that represents an encoded RGB picture in JPG format
I'm aware of that.
>any understandable published material on how to use GDIplus functions
Understandable from a VB perspective? Not really, not that I'm aware of. GDI+ was not really written with VB programmers in mind (indeed it is really only thanks to the efforts of Dana Seaman and his library that we get tou use large chunks of it at all). Most of the documentation is therefore written with C++ programmers in mind. And we know from past experience (remember all the stuff about BITMAP documentation) that you find it hard to get your mind around such documentation. |
|
I don't want to drag your thread off in the weeds, but I tried playing around with a minimal approach and found a number of woes. Are you seeing the same issues?
1.) Cameras usually have to use the WDM-VFW bridge (adapter) driver now. This means if you try to enumerate the VFW devices (capGetDriverDescription) you find only Device 0, and if you try to use it (via WM_CAP_DRIVER_CONNECT) a selection dialog appears if two or more actual cameras are present. Also I've noticed that on Win7 (32- or 64-bit) the CONNECT fails at this point.
2.) 64-bit Windows (Vista, Win7) often results in a hard program crash when using the AVICap32 API.
BTW:
As far as WIA 2.0 goes... Yes it no longers supports video devices as of Vista (and Win7, Win8). This was related to some kind of DRM issue and not a technical issue. But it still works great for things like compressing BMP to JPEG, converting a JPEG image file contained in a Byte array to a StdPicture object, etc. |
|
Sorry strongm I should have realised it but after testing, I found that your CreatePictureFromGDIPlusImage function create a Bitmat sized "picture" from the steam three times larger than the original bitmap.
I measured the size by putting into a propertybay and Ubound(Contents). I presume this is valid?
This is OK if this is at the receiving end but I was hoping to use at the transmitting end otherwise I have to rewrite the software in all the other receiving devices if I send the GDI stream.
I was hoping for a routine in the 'transmitter" that produced a reduced bunch of bits of similar same smaller size as a a saved jpg file that could be distributed and fed directly to an image like Loadpicture does (as per the steps in my last post ). This is always much smaller than the original and the imagebox does the bitmap picture reconstruction.
Is this possible?
dilettante
I have observed this even in XP when using 2 cameras
I have been using AVICap32 in Windows 7 64 with no crashes only capturing a picture every second (not continuous)
You can get a complete computer freeze for 30 seconds or so if you close the app or unplug the camera with the software still "connected" but not if you "disconnect" in software first. This freeze clears itself after about 30 seconds. Otherwise no problems.
Have you any suggestions on examples of using WIA to convert only?
|
|
Good to hear that Win7 64-bit is working ok for you. Perhaps the issues I ran into on 64-bit Windows (I know I tried Vista 64-bit at least if not Win7) were related to cameras and drivers.
I've been playing with an example I put together yesterday and have the nastiest bugs out of it now (I think).
This one is using WIA 2.0 to convert frames to JPEG and received JPEG "image file" streams to StdPicture objects for display. There are tweakable constants and they are set for 5 fps and 60% JPEG compression. Capturing is at 320x240 pixels, which could also be changed easily enough though I didn't create Consts for those (duh, next time for sure).
I've only done some LAN testing and I haven't tried to measure the bandwidth required, but it seems to work reasonably well. over a WAN it might be another story though.
I've posted this at another site, as AVICap32 Cam Streaming w/o Clipboard where you should be able to download it without being a member. There is a small server program and a small "multi-client" program that can connect to 4 server instances. Of course with the WDM/VFW driver issues right now every "server instance" must be on a separate machine even if you have multiple cameras.
Perhaps the code there will give you some ideas. If nothing else using WIA sure saves on lines of code to write and debug! However performance might be better using lower level APIs, perhaps even enough to be worth the effort.
|
|
>I was hoping for a routine in the 'transmitter" that produced a reduced bunch of bits of similar same smaller size as a a saved jpg file that could be distributed and fed directly to an image
I have on repeated occassions explained that you cannot do this at the transmitting end (yes, from a technical point of view you can do the conversion, but it is pointless). I'm not going to explain yet again why this is the case.
Let's use some traditional terms: transmitter= server, receiver = client
The code I've produced for you does exactly what you require:
It converts a bitmap into a jpeg on server
Server sends the jpeg representation to the client
Client loads the jpeg into a Picture object (which it has to do by converting it back into a bitmap)
I appreciate that you want to try and avoid rewriting any code on the clients, but that just isn't going to be possible, I'm afraid.
Let's try a simpler allegory. Let's say that you are just trying to send plain ASCII text. And you write the simplest method of sending and receiving that over winsock (doesn't matter what that method is, the point is it only knows how to deal with plain text). Now you discover that some of the blocks of text are too big, so you decide that you want to compress them for transmission, and you choose a simpe algorith such as RLE. That's great in that it reduces the size of the text being transmitted, but has the problem that the client cannot display the text in it's original form. You have to have a converter on the client to reconstitute the original text. There's no way around this.
Same thing with bitmaps - jpeg's are just a compression routine, and we need to decompress them on the client before they can be displayed
>like Loadpicture does
That's exactly what the CreatePictureFromGDIPlusImage function does. It takes a GDI+ image and returns a Picture object (LoadPicture takes a filename and returns a Picture object).
|
|
Thanks. I see your point more clearly now.
I am interested in what is the nature of the GDIplus stream in your example in layman's terms?
If, for illustration only, you open an existing jpg file for binary as #1 and feed it into a bytearray = input (Lof(1),#1) and transmit all these bytes in turn, is the GDI+ image in your example of similar nature to the received data or is it something entirely different again? |
|
>the GDI+ image
The GDI+ image is it's own format (and I have no idea what it uses internally), but it includes a fair amount of additional state information above and beyond the raw image itself. However, when we save it to a stream it is just the same as saving it to a file. So the stream in this case should just contain the bytes representing the jpeg. |
|
|
Hypetia (Programmer) |
1 Jul 12 21:37 |
I have been watching this thread, and other related threads silently for sometime. Main reason being that I have practically no idea how GDI+ stuff works. Had no plans to intervene until strongm's last post rang a bell.
>when we save it to a stream it is just the same as saving it to a file. So the stream in this case should just contain the bytes representing the jpeg.
I just checked it. And yes, the bytes in the stream simply represent the raw contents of the jpeg file, as if it were read directly into the array. I wrote the bytes representing the jpeg to a file with .jpg extension, and resulting file behaved like a normal jpeg file. I could open it in an image viewer and do other stuff. That really helped.
Having established this, I believe we can make the code on the client side much simpler.
Currently, strongm is doing following steps to reconstruct the Picture object on client side.
1. Receive the byte array (buffer) using Winsock.
2. Create a ole steam object (mystream) and write the byte array, buffer into it.
3. Create a GDI+ image object (gdiImageTarget) from mystream using GdipLoadImageFromStream function.
4. Create a vb Picture object (StdPicture) from gdiImageTarget using CreatePictureFromGDIPlusImage function.
I remembered posting PictureFromRawBits function in thread222-1541026: Memory calls to create a picture which loads a picture, like the LoadPicture function, with only difference that it reads the data from memory, instead of a file.
So if we pass the byte array, buffer, from step 1 to this function, it will create the Picture object directly, without going into the trouble of all GDI+ stuff on the client side. Of course, GDI+ will be needed on server side to create the jpeg stream.
I just checked using PictureFromRawBits and it worked perfectly.
Using PictureFromRawBits on client side has another advantage. It supports all image file formats which are supported by LoadPicture function. These include BMP, JPG, GIF, icons/cursors and even metafiles. So, if some day, you decide to send the image, in a different format, say GIF, or BMP again, you only would need to modify your server code, and the client will display the new content seamlessly. On the other hand, if you use GDI+ decoder, you would need to modify your client code as well, depending on the type of image being sent.
PictureFromRawBits also retains the "originality" of the Picture object, in contrast to CreatePictureFromGDIPlusImage function. So if you put the resulting picture into a PropertyBag and measure its size using Ubound(Contents), it will return the same size as the original jpeg in compressed format.
Hope it helps.
Watch out for the CreateStreamOnHGlobal API function used in above referenced thread. It might clash with CreateStreamOnHGlobal function used in Edanmo's OLE library, if you have referenced it in your project. You might need to use an Alias for the API function to avoid name confusion. |
|
>I believe we can make the code on the client side much simpler
I took the approach that you outline originally, but couldn't get it working properly for some reason (oddly seemed to claim the stream was an 'invalid picture', and I didn't have time to investigate), and so reverted to the method presented.
I may revisit |
|
Ah! I was using OleLoadPicture from Edanmo's library - and it would appear to have a bug! If I use the straight API call (aliased) it works fine, so we can have a function like:
CODEPrivate Declare Function vbOleLoadPicture Lib "olepro32" Alias "OleLoadPicture" (pStream As Any, ByVal lSize As Long, ByVal fRunmode As Long, riid As Any, ppvObj As Any) As Long
Private Function LoadPictureFromStream(jpgStream As IStream) As StdPicture
Dim myUUID As olelib.UUID
jpgStream.Seek 0, STREAM_SEEK_SET ' make sure we are at beginning of stream
olelib.CLSIDFromString IPictureGUID, myUUID
vbOleLoadPicture ByVal ObjPtr(myStream), 0&, 0&, myUUID, LoadPictureFromStream
End Function
However, whilst revisiting, I recalled that StdPicture has a hidden iPersistStream interface - so we ought to be able to load it directly from the stream ... and indeed we can, giving us an alternative LoadPictureFromStream:
CODEPrivate Function LoadPictureFromStream2(myStream As IStream) As StdPicture
Dim oPersist As IPersistStream
Set LoadPictureFromStream2 = New StdPicture
myStream.Seek 0, STREAM_SEEK_SET
Set oPersist = LoadPictureFromStream2 ' Get the hidden IPersistStream interface of the StdPicture object
oPersist.Load myStream
End Function |
|
Great!
One quandary is what is be best way to transmit other types of data on the same port.
I need to reply that a picture has been received, and to send a text report back to the server.
I tried a rather cludgy method using SentStreamSize as an indicator of what was to follow with the following concept -
Client side:
'Acknowledge
Senddata clng(1)
Senddata "OK"
'Report
Senddata clng(2)
Senddata txtMessage
'Picture
as previous code that works
'Server side
Select case SentStreamSize
Case 1
'Acknowledge
case 2
'text received
case else
'picture received previous code that works
end select
Any better ideas? |
|
Got to leave you something to do, Ted |
|
I thought this was all about video capture and "streaming" the frames over TCP to be displayed there. If so, what use would it be to send any "text" back to the capture program sending the video?
I think I have my "capture and send a stream of JPEGs" code working pretty solid now. I was able to completely eliminate the use of Preview windows for capture/streaming, which means the program will send video even when minimized. I haven't dug into it yet but this might even work as a Windows Service.
No PropertyBags used at all here. WIA 2.0 is being used to convert captured raw bitmaps to JPEG image file data for sending and to convert this data back into StdPicture objects for display. That logic is pretty well isolated so you could easily rip out WIA and use other techniques for handling the JPEG processing. |
|
Yes
Thanks for all your help.
I am grateful to you all for finally explaining the subject in terms I can understand.
I am not asking for the code this time - just a comment on my proposed method as the change to GDI+ stream impinges on my app.
I previously used propertybags and a stream to accumulate the BMP pic because they gave an extra check on the integrity of the network data and were very convenient to program and quick in operation.
As I obviously should use strongm's GDI+ method or WIA, I will dispense with propertybags entirely and the reasons I included this last question was the change impinges on transmission of other things and if anyone else had found a more elegant method of mixing text and pics coexisting. I obviously don't want to put the GDI+stream into a property bag too to differentiate it!
The reason I have to send text back is that the client is an odd sort of information display at bus stops that gather information around it such as a sensor to tell when a bus pulls up (and relays this back to a control room). I also must send a verification that the information being displayed has been received from the server is exactly what was sent.
My setup using AVICap and strongm's GDI+ stream doesn't NEED any picture or image boxes or any forms visible to send pics. It just happens to display the camera locally as well on 3 multiple monitors and text or graphic information.
Incidently, I found I can have 16 clients feeding to the one server Windock_Dataarrival and all showing their pictures in shrunk indexed image boxes(15) that are magnified when you click on them. I initially load indexed winsocks(15) each with a different local port, dimmed MyStream(15) and SentStreamSize(15) so they don't get confused when 2 cameras send nearly at once. It works without a hitch even with 1meg BMPs - now will be even quicker with 20k JPGs. |
|
I'm not sure why you need to send an "ok" back at all. The source sending JPEGs (not sure which you call client and which server) can tell when it can send another via SendComplete events. By making this a half-duplex protocol you slow things down quite a bit, including Nagle issues that make it even worse.
But let's say you just insist.
Your "back channel" with Acks and Msgs can be managed entirely differently. But it will still need message framing.
This could be via length prefix or terminating char(s)/byte(s):
CODE.SendData "1|"
.SendData "2:message text|"
Note that the "message type" could be text-based too (above). I'm not sure sending a Long buys you anything but extra grief to handle it on receive. Sending "ok text" after a message type 1 seems redundant. If your message type codes are all 1 character (even 0-9, A-Z gives you 36 types) then you can even dispense with the colon. |
|
Thanks
Client is the one with the camera, Server is the one that receives it.
There are 16 clients(cameras) and one server.
The OK has nothing to do with sending the JPG from Client to Server. It is an acknowledgement that data has been sent in the other direction from Server to CLient.
Its just that the OK is sent in the same direction as the JPG but in response to a text data transmission from the server to the client at an unrelated time interval.
I an concerned that the OK might coincidentally be sent at the same time as the picture so I need to accurately separate them. This is where Propertybag was so useful. They have to use the same port.
With the propertybag I used a Sendcomplete signal but found this unrelaible depending on the speed of the computers. This sendcomplete uased to get attached to the end of the intital packet and was lost so I had to put in a 30ms delay before the sendcomplete fired.
With strongm'm method this delay is not needed so the speed is always at maximum.
I notice if you play around with the LAN plug while transmitting to simulate a dodgy network you can get Strong's method to eventually fail due to overflow on SetStreamSize. So I put in an error trap to make this 0.
The client has a dual purpose:
1. sending pictures of the bus arrival when a motion sense routine detects a bus arriving or leaving. This does not need acknowledgement.
2. locally showing departure and message information on it's screens. This does need acknowledgement because there is no one in authority to check it was received. It might show the previous bus leaving resulting passengers getting on the wrong bus. |
|
The reason for "OK" is that if the message received by the client does not conform to guidelines or is not a valid picture, it will echo back "Fault" followed by description of the error instead of "OK"
If no message is echoed back at all, the server will show a Fault one second after transmitting.
I still use propertybags for the transmission Server to Client as this is much smaller and the code is already there. (I'm lazy) |
|
>I previously used propertybags and a stream to accumulate the BMP pic because they gave an extra check on the integrity of the network data and were very convenient to program and quick in operation.
If you find property bags so useful for merging data and handshaking information, with ease of programming, you can still use it with your client to server communication. After encoding your picture with GDI+ to a jpeg stream using GdipSaveImageToStream function, use strongm's LoadPictureFromStream or LoadPictureFromStream2 function above to convert that jpeg stream into a vb picture again.
As I told above, these OLE functions preserve the originality of the picture. So if you put the resulting StdPicture into a property bag, it will not inflate the bag and maintain its original size matching with the size of the jpeg stream. You can put additional handshaking information in the same bag and send it over the network to the server. There you can extract the StdPicture directly and can display it without doing any conversion at all!
>Client is the one with the camera, Server is the one that receives it.
If this is the correct definition, then swap all servers and clients in my previous post to understand it correctly.
I would like a small modification to strongm's LoadPictureFromStream/LoadPictureFromStream2 functions. The argument should be declared as olelib.IStream instead of just IStream. This is because ISteam interface is defined in both OLE and GDI+ library, but the one in GDI+ does not expose the Seek method.
So, if you have referenced both GDI+ and OLE library in your project, plain IStream definition will not work if you have GDI+ listed above OLE in References. |
|
>PictureFromRawBits also retains the "originality" of the Picture object, in contrast to CreatePictureFromGDIPlusImage function. So if you put the resulting picture into a PropertyBag and measure its size using Ubound(Contents), it will return the same size as the original jpeg in compressed format
I don't believe this is correct. Which means that
>these OLE functions preserve the originality of the picture. So if you put the resulting StdPicture into a property bag, it will not inflate the bag and maintain its original size matching with the size of the jpeg stream
is also incorrect.
I'll repeat, the Picture object holds a BITMAP (plus some additional bits and pieces). The Handle property (described in the documentation as an OLE_HANDLE (int)) is in fact an hBitmap.
|
|
(Ok, to be pedantic a Picture object can also hold a metafile, but let's not complicate things) |
|
|
Hypetia (Programmer) |
3 Jul 12 11:22 |
>I'll repeat, the Picture object holds a BITMAP ... The Handle property ... is in fact an hBitmap.
I never contradicted that. After all, we pass Picture.Handle to Win32 GDI functions expecting an hBitmap all the time.
But both of my statements above were based on my observations.
I just tested again. I placed a 800x600 jpg in a picture box, whose file size is 56.4 KB. I used your jpg compression code in your first post above to compress the picture. After calling GdipSaveImageToStream, I used your LoadPictureFromStream2 function to convert mystream back to an StdPicture object.
Putting that picture in a property box and querying its size returned 22296 bytes (well, decrease in size from 56 KB is explained due to substantial loss of quality (25%)). If the Picture object were saved in the property bag as a BITMAP, it would have occupied 800 x 600 x 3 = 1440000 + overhead bytes instead.
See this again in the example below. This time I use PictureFromRawBits function.
___
Private Declare Function CLSIDFromString Lib "ole32" (ByVal lpsz As Any, pclsid As Any) As Long
Private Declare Function CreateStreamOnHGlobal Lib "ole32" (ByVal hGlobal As Long, ByVal fDeleteOnRelease As Long, ppstm As Any) As Long
Private Declare Function OleLoadPicture Lib "olepro32" (pStream As Any, ByVal lSize As Long, ByVal fRunmode As Long, riid As Any, ppvObj As Any) As Long
Private Declare Function GlobalAlloc Lib "kernel32" (ByVal uFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalFree Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSource As Any, ByVal dwLength As Long)
Const GMEM_MOVEABLE = &H2
Function PictureFromRawBits(bytes() As Byte) As IPicture
Dim cbMem As Long, hMem As Long, IPic(15) As Byte, istm As Object
'data length
cbMem = (UBound(bytes)) - LBound(bytes) + 1
'allocate global memory block
hMem = GlobalAlloc(GMEM_MOVEABLE, cbMem)
'copy picture data to global memory block
CopyMemory ByVal GlobalLock(hMem), bytes(LBound(bytes)), cbMem
'reset lock count
GlobalUnlock hMem
'create stream object from global memory
CreateStreamOnHGlobal hMem, 1, istm
'fill the Guid of IPicture interface from its CLSID
CLSIDFromString StrPtr("{7BF80980-BF32-101A-8BBB-00AA00300CAB}"), IPic(0)
'create picture from stream object
OleLoadPicture ByVal ObjPtr(istm), 0, 0, IPic(0), PictureFromRawBits
'free global memory
GlobalFree hMem
End Function
Private Sub Form_Load()
Dim B() As Byte, File As String
File = "C:\Users\Public\Pictures\Poster.jpg"
Open File For Binary As #1
ReDim B(LOF(1) - 1)
Get #1, , B
Close
Dim pic As StdPicture, pb As PropertyBag
Set pic = PictureFromRawBits(B)
Set pb = New PropertyBag
pb.WriteProperty "Picture", pic
Debug.Print "File name:", File
Debug.Print "Picture width:", Round(ScaleX(pic.Width, vbHimetric, vbPixels))
Debug.Print "Picture height:", Round(ScaleX(pic.Height, vbHimetric, vbPixels))
Debug.Print "File size:", UBound(B) + 1 'FileLen(File)
Debug.Print "Size of property bag:", UBound(pb.Contents) + 1
Unload Me
End Sub
___
The picture I used in the code is same as I referenced above. Here is the debug output.
File name: C:\Users\Public\Pictures\Poster.jpg
File size: 57842
Picture width: 800
Picture height: 600
Size of property bag: 57900
Once again the property bag has the same size as the jpg file. Slight difference is due to the property bag overhead. A true color bitmap of same dimensions would have occupied more than 1.37 MB.
>plus some additional bits and pieces
Well, my theory is that, somewhere in those bits and pieces, the original stream which was used to create the picture is also preserved, and same is written to the property bag instead of the whole BITMAP data. Same stream is written to .frx files when we load a jpg picture in the picture property of a form, for instance. Otherwise, loading a jpg or a bmp file would have made no difference on the size of .frx file, and same would have reflected in the size of resulting exe file.
Well, there is another interesting observation. While PictureFromRawBits (and thus OleLoadPicture) appears to preserve the original stream, LoadPicture does not.
So if we create a Picture using LoadPicture, the resulting picture always occupies the size as that would be required by the BITMAP. See below.
___
Private Sub Form_Load()
Dim File As String
File = "C:\Users\Public\Pictures\Poster.jpg"
Dim pic As StdPicture, pb As PropertyBag
Set pic = LoadPicture(File)
Set pb = New PropertyBag
pb.WriteProperty "Picture", pic
Debug.Print "File name:", File
Debug.Print "File size:", FileLen(File)
Debug.Print "Picture width:", Round(ScaleX(pic.Width, vbHimetric, vbPixels))
Debug.Print "Picture height:", Round(ScaleX(pic.Height, vbHimetric, vbPixels))
Debug.Print "Size of property bag:", UBound(pb.Contents) + 1
Unload Me
End Sub
___
This time the output was as below. We see that the size of the property bag has inflated to the size of the property bag.
File name: C:\Users\Public\Pictures\Poster.jpg
File size: 57842
Picture width: 800
Picture height: 600
Size of property bag: 1440112 |
|
|
Hypetia (Programmer) |
3 Jul 12 11:32 |
Oops! Feeling very drowsy at the moment.
>We see that the size of the property bag has inflated to the size of the property bag BITMAP. |
|
This would seem to be an ... oddity ...
Particularly since if you dump pic.type it definitely thinks it is a bitmap (returning vbPicTypeBitmap)
Ah - and I think I see what is going on here. And it might be quite handy ... just need to investigate a little further. If you want to look, it's related to OleLoadPicture's fRunmode parameter (try changing it to 1, rather than 0, and you'll see it now works just like VB's LoadPicture). It indicates that StdPicture is somewhat more clever than previous investigations had suggested, in that we can keep a copy of the source data cached along with the bitmap it is turned into and - and this is the surprisong bit - it is this cache plus some state data (which is why the proopertybag is slighly bigger than the raw jpeg file itself) that gets serialised when whe try to persist it (which is what we are doing when we stick it into a propertybag)
|
|
|
Hypetia (Programmer) |
3 Jul 12 15:06 |
Yes, I see the effect of fRunmode parameter. In fact, if we change the KeepOriginalFormat property of returned picture to false, using its IPicture interface, the effect is same. The modified code fragment shows this.
Dim pic As IPicture, pb As New PropertyBag
Set pic = PictureFromRawBits(B)
pic.KeepOriginalFormat = False
pb.WriteProperty "Picture", pic
This causes the pic object to loose its cached data and save complete BITMAP in property bag.
However, doing the reverse is not possible, perhaps, because the cached data has already been thrown away. So doing the same thing with a picture returned from LoadPicture fails. You cannot set KeepOriginalFormat to True once it has been set to False.
Dim pic As IPicture
Set pic = LoadPicture("C:\Users\Public\Pictures\Poster.jpg")
pic.KeepOriginalFormat = True '<- Fails |
|
I think the properytybag will always be slightly bigger than the data because it contains such things as the start and end characters and name of the "bag" but the pic or data after readproperty is the same.
I am more than happy with strongm's original solution and dont really want to go back to propertybags
The final problem was how to seperate pics and text reliably.
If you sent a pic immediately after data is received, the acknowledgement OK sometimes gets tacked onto the pic before or after it in the one DataArrival instance - which is hard to separate.
Maybe I should always send dummy data when no real text is required so the dataarrival always treats every reception the same?
The following code seems to work but sometimes misses a pic if you send a lot text and pics together in quick succession.
Any comments would be appreciated
CODE'++++++++++++++++++++++++++++++++ WINSOCK SERVER ROUTINES connecting to all bus stop clients by Index ++++++++++++++++++++++++++
Private myStream(16) As olelib.IStream
Sub Winsock_DataArrival(Index As Integer, ByVal bytesTotal As Long)
Dim StreamStats As olelib.STATSTG
Dim gdiImageTarget As Long
Dim GpInput As GdiplusStartupInput
Dim Token As Long
Dim myGraphics As Long
Dim buffer() As Byte
Dim Message As String
On Error GoTo WDAError
SelectMessageType:
Select Case SentStreamSize(Index)
Case 0
'Start of a new transmission
Winsock(Index).GetData SentStreamSize(Index), vbLong
MainForm.EventList.SelText = "Got Stream size" & SentStreamSize(Index) & vbCr
If SentStreamSize(Index) = 1 Then
'Acknowledgement of received departure data at bus stop
SentStreamSize(Index) & vbCr
MainForm.lblIndicator(Index).Caption = vbCr & "OK"
Updated(Index) = 0
'check if more data appended
ReDim buffer(0) As Byte ' reset input buffer
Winsock(Index).GetData buffer, vbArray Or vbByte ' read the byte array
Select Case UBound(buffer)
Case 5 To 255
'further report message (text encoded as binary)
For a = 0 To UBound(buffer)
Message = Message & buffer(a) 'form message
Next
MainForm.BusStopErrors.Caption = "Faulty " & MainForm.BusStop(Index).Caption & " " & Message 'fault reported from bus stop
SentStreamSize(Index) = 0
Case Else
'picture was appended to initial received data
SentStreamSize(Index) = Val(buffer(5) & buffer(6) & buffer(7) & buffer(8))
GoSub ProcessPicture
End Select
End If
If SentStreamSize(Index) = 2 Then
'received bus presence detect from bus stop
'Future ?????? write routine to receive 1=bus arrived, 0=bus just departed & show model bus graphic at stop if no VID
End If
'
Case Else
'large size pic without text so start accumulating camera picture data into the stream
ReDim buffer(0) As Byte ' reset input buffer
Winsock(Index).GetData buffer, vbArray Or vbByte ' read the byte array
GoSub ProcessPicture
End Select
If Token Then GdiplusShutdown Token
endWDA:
On Error GoTo 0
Exit Sub
ProcessPicture:
myStream(Index).Seek 0, STREAM_SEEK_END
myStream(Index).Write buffer(0), bytesTotal ' append to a stream
myStream(Index).Stat StreamStats, STATFLAG_NONAME
If SentStreamSize(Index) <= StreamStats.cbSize * 10000 Then 'we've accumulated all the data
GpInput.GdiplusVersion = 1
If GdiplusStartup(Token, GpInput) <> Ok Then
MainForm.ShowErrors "Error loading GDI+! [WinsockServer]"
Else
GdipLoadImageFromStream myStream(Index), gdiImageTarget 'load GDI+ image from stream (which in this case contains a JPEG)
' At this stage we have loaded a jpg into GDI+ from a stream
' so we can reset that stream for reuse
ResetStream Index
' working with a StdPicture object created from the GDI+ image
MainForm.EventList.SelText = "total Image received " & UBound(buffer) & vbCr
Set MainForm.ImageCamera(Index).Picture = CreatePictureFromGDIPlusImage(gdiImageTarget)
MainForm.ImageCamera(Index).Visible = True
GdipDisposeImage gdiImageTarget
GdipDeleteGraphics myGraphics
End If
SentStreamSize(Index) = 0
End If
Return
WDAError:
MainForm.ShowErrors Error & " [WinsockServer]"
SentStreamSize(Index) = 0
Resume endWDA
End Sub |
|
Whew, lots of problems there.
You cannot rely on the bytesTotal value passed to DataArrival, contrary to what the original docs suggest. This was covered in some MS KB article, somewhere around VB6 SP3. Instead use UBound(buffer) + 1, or risk a value that is too small and you'll truncate your data when you write it to the Stream.
Never, never .GetData into a Long. Only use Byte arrays. What if you haven't received 4 bytes yet? What if you've received more than 4 bytes already? Kaboom! This was also covered in an MS KB article sometime well after VB6 was released. It really only works in simple "dummy" projects where things don't happen very quickly.
Probably why you have that funky error handling in there, hmm?
Strictly speaking, it is illegal to send Long values over TCP/IP anyway as part of a protocol. Only big-endian values are considered acceptable. However I do it myself sometimes, even though it makes such protocols Windows-specific. Winsock libraries provide htonl() and ntohl() functions for handling this correctly. This is more a "nice to have" rather than a "must have" though.
As written this code will fail miserably unless you stick with a chatty, slow, half-duplex protocol. Doing things correctly means the logic works for both half- and full-duplex protocols.
Remember, on any given DataArrival you are only guaranteed one byte of data is available. There might be a little more, or much more, or even part of two or more messages. You really need to buffer everything you get on a DataArrival, and only then try to parse out and process any whole messages in the buffer.
Failing to do this means trouble. I'm starting to understand why you feel you need these "ok" acks. TCP is a reliable protocol by its nature, there should be no reason at all to "check that stuff gets there right" but your logic has gaping flaws. |
|
Thanks for the time you people have spent examining my new proposal, I appreciate it very much.
I just can't wait to see strongm's response to this!
This use of bytestotal and vblong are straight out of his example.
The use of vbLong as a winsock receive format in the first getdata, I would have thought is just a handy way of telling getdata to receive only 4 bytes.
The first block of data to be received contains the Streamsize and the first 8k of the picture.
It does seem to actually get only the first 4 bytes, leaving the others behind to be detected by the next getdata statement. Getting only 3 bytes at the start of transmission with the rest to follow would be extremely unlikely and would signify a fault like somebody pulling out the LAN plug that would be soon known by my OK method.
When the text and pic are occasionally transmitted with no gap between them (eg a bus arrives at the same time an OK has to be sent), they get mixed up with the pic I receive all together in one dataarrival operation
Streamsize (text 1)
OK
Streamsize (Picture size >20k)
Picture
That is why I have to separate them with the Else stuff and the processpicture in a separate subroutine so it can also be used when only a pic is received. I suspect I have something wrong here.
I always need more than 1 byte to convey the streamsize and I need the streamsize to be sure to capture all of it. (previously I used a fixed end code)
Why is my error routine 'funky'? I though that is what error routines are for!
Because the client is unattended it has to never show an error message or stop working if any errors of any kind happen - it just let the control room know.
The NEED for acknowledgement of only the text has nothing to do with the current problem because the text data (to be acknowledged) is sent the opposite way to the pictures.
The OK alert is not sent back just to confirm the network.
The handy thing about the OK idea is that it alerts if anything at all is wrong with the whole system including whether it got to the right bus stop (except if the fluro tubes in the LCD monitors have blown - but I'm even working on that)
If an OK is not received within 1 second an alert shows on the control room monitor.
This project (that I have been tweaking every few years for the last 26 years when the customer wanted a change) seems strange to many because generally a person is at the receiving end of a system and can see immediately when there are problems. Mine is the opposite with unattended clients so I have had to adopt many other unconventional methods over the years to make it keep working 100%at all costs.
My very first server version was written in DOS basic, RS232 and client Commodore 64s feeding TV sets in 1986!
|
|
>strongm's original solution
I think I'd be using either my later
LoadPictureFromStream
or
LoadPictureFromStream2
functions at the receiving end since they both eliminate the requirement to implement any GDI+ code at the receiving end, thus simplifying things somewhat
>I just can't wait to see strongm's response to this!
>This use of bytestotal and vblong are straight out of his example.
As I've previously pointed out, my code examples in this forum are not intended to be production code quality. They are generally designed to illustrate a possible avenue of attack. |
|
Thanks however
I wouldn't have thought that your later versions you mention would have any bearing on the current problem of reliably separating pictures and text?
The use of bytestotal and vblong seem to work perfectly as long as I only transmit a picture (contrary to the previous advice). I only get unreliability when I mix them so I ma working an another approach.
>not intended to be production code quality.
I understand this and modify to suit or add other things where necessary to make it bomb proof.
I have even guilty of not using some of your suggestions if I find an alternative that performs better to suit the circumstances.
I am sure you wouldn't be suggesting a principle that I shouldn't be using at all. |
|
As the manual says for GetData:
Quote:data
Where retrieved data will be stored after the method returns successfully. If there is not enough data available for requested type, data will be set to Empty.
If you pass a Long there, you'll frequently not have enough data to fit yet (4 bytes). When it tries to return Empty your Long would likely end up being 0.
From what I can see in your code a 0 there makes you exit the event handler without doing anything, apparaently "by guess and by golly" rather than intent. When more data is received you go through this again until there are 4 bytes or more to get with GetData.
Even then you ignore the rest of the received data and fall out once more, waiting for more data to be received to cause another DataArrival event. When this occurs, you blindly GetData everything received so far and treat it as the image data or text... even if it contains part or all of the next (or next 12) messages!
When this code blows up on some logic error your "error handler" muddies the waters by setting StreamSize(n) = 0, which appears to tell the rest of your code there "ditch everything and assume the very next byte received is exactly the beginning of a new message" - which it just might, on rare occasion. But rather than an error handler you have an error maker there.
I won't attempt to contribute further because it just gets frustrating for all involved. |
|
>I wouldn't have thought that your later versions you mention would have any bearing on the current problem of reliably separating pictures and text?
No |
|
Thanks dilettante.
I am not trying to say you are wrong, it's just that I was getting what appeared to me to be conflicting advice or opinions from two well respected and obviously very experienced contributors.
I did not realise this could happen here because, in my limited experience, although I have done a lot of practical testing in an attempt to get 100% reliability, I have never seen a dataarrival accidentally receive less than 4 bytes of a larger message except at the very end of a transmission that is a few bytes bigger than a multiple of 8k in which case it would be part of the picture and wasn't looking for a long anyway. (I know it it often happens with RS232 serial with a fast computer)
Do you think it would be better then if I received the whole of a transmission in to a large array or string and separated any text after the last was received? (in other words a sort of propertybag like I originally had?)
I not asking how to code it, just the method.
Thanks Strongm do you mean No it would have a bearing or No it wouldn't have a bearing? |
|
>Thanks Strongm do you mean No it would have a bearing or No it wouldn't have a bearing?
The latter. My example (and modifications) were not related to your later requirements. |
|
Happy to tell you that I have worked out a simple and quick way to reliably send mixed pic streams and text.
I use strongm's methods of first sending the size of the data then using this size value to collect all the data.
No propertybags needed.
To differentiate between pis and other, because a pic is far larger, when I initially measure the received size, if it is under 255 bytes I treat it completely separately as text. I use the next byte after the size as a code for what sort of other data follows (OK's, text or numbers.)
When sending text, I use bytes equal to the ascii value of the text rather than strings to keep the same type of data transmission for both.
Works flawlessly so far with nothing getting mixed up.
If by chance less than 255 bytes of picture is first received, it is still treated as picture because I am using the sent size in the first 4 bytes and not ubound(buffer).
The problem of using getdata format clng is fixed by only getting data when bytestotal > 3 and as far as I can measure it never lies. If <4, Dataarrival fires again and gets it on the next round.
The end result is 20 times faster than my original.
If anyone wants to be bored with the code I can post it.
Thanks everyone. |
|
Glad you got things sorted out to your satisfaction. |
|
I'm not sure if you have a need for it, but playing around with this stuff a little more I ran into the need for YUV-decoding with newer webcams. I've also added image timestamping you may or may not need.
AVICap32 Cam Streaming w/o Clipboard
Seems to even work on Win95 OSR2, works with Vista 32- and 64-bit. I don't have a Win7 machine without multiple webcams though, which seems to be a problem. I'll probably try another Win7 machine soon. |
|
thanks
You certainly have put a lot of work into it!
I am trying to get two types of "modern" webcam to work with Windows 98 (but not at the same time).
Please don't ask why I am trying to do such a stupid thing!
They are a 3 yr old Logitec Quickcam Communicate and a new Windows Lifecam
They both work well using their own drivers in Win 7 64b but I can't locate any drivers that work in Windows 98.
Win7 64 automatically installs their different drivers on the run when you pull one camera out and substitute the other without having to even restart my VB6 app (using AVIcap).
I there perhaps something like an API or driver for any OS that can be used with vb6 instead of OS specific drivers? (in Win98?) |
|
I was getting a lot of "help" here ("What about...?" "It is leaking memory." sorts of things). Hot weather here of late so plinking around with AviCap, GDI and GDI+ was an indoor diversion - I'm not a graphics guy.
The only cameras I have gotten to work on Win9x are two old Logitech cams (old black "ball" VC, newer grey "ball" QC Express, micro-camera Blink StyleCam which has a webcam mode). A few more work on Win2K and XP but no later (no Vista or Win7 drivers). Newer cameras like Kinobo Origami work on Vista and Win7, probably on XP too, but don't even come with a driver disk. I haven't tried one of these on Win9x yet. |
|
One nagging thing though...
Since you can buy an IP webcam now for well under $100 isn't this sort of thing a little obsolete? The ones I looked at can be operated wired or wireless, support remote pan and tilt, "night vision" mode, etc. No need for the cost and support of a PC at each camera location. |
|
|
 |
|