Results 1 to 24 of 24

Thread: MJPEG Webcam Receiver

  1. #1

    Thread Starter
    Frenzied Member
    Join Date
    Oct 2008
    Posts
    1,181

    MJPEG Webcam Receiver

    Attached to this message is the complete source code for this program. It is designed to receive webcam video, but will work for any video source that has these specifications:
    Server Location = 127.0.0.1 (same computer as the VB application that is running)
    Network Protocol = TCP/IP
    Application Protocol = HTTP
    Port Number = 8080
    Container Format = AVI (each frame starts the String "00dc" followed by a Long who's value equals the size in bytes of the frame's payload data)
    Video Codec = MJPEG (Motion JPEG, where each frame of video is a JPEG image)
    Audio Codec = none (no audio is sent)

    To get those specs on your stream, the easiest way to do it is to use VLC Player to stream the video and make sure the protocol is set to HTTP, the port number is set to 8080, and that the transcoding options are set to use the above mentioned container format and video codec, and that audio is not sent. I won't explain how to use VLC Player here, as this isn't a VLC forum. However there is a VLC forum you can go to to get help (or if you are like me, and have used it a lot, you may already know how to do this). And make sure you are running VLC on the same computer that you are running this VB6 program on.

    Now as for my receiver's functionality, it can connect to and disconnect from a server. It can display the video directly, or process it prior to displaying it, using one of several different algorithms to add various image altering effects to the frames. You'll notice a drop in frame rate from 30fps to something much lower, as processing can't occur any faster than about one to two tenths of a second per frame on an image size of 640x480.
    Attached Files Attached Files

  2. #2
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: MJPEG Webcam Receiver

    This code "does so many don'ts" that it is downright scary.

    DoEvents calls are evil enough, but doubly so within code processing a non-GUI event like a Winsock data arraival.

    PeekData is on the "do not fly list" and the method only exists for legacy reasons. See INFO: Avoid Data Peeking in Winsock which only addresses the low-level problems with peeking, let alone the additional problems higher up in the ActiveX control you are using in VB6.

    Seriously? Receving binary data into a String and then converting that back? Not only do you risk data corruption from these conversions (depending on locale settings these may not even convert one-to-one) this is a very expensive process.

    I don't know of any IP webcam that sends live streams in AVI format, which is where you seem to be getting this "00DC" from.

    The code has to be throwing away frame after frame after frame as written. That's assuming it doesn't just lock up hard and accept no frames at all most of the time. PeekData is more evil than DoEvents.

    Your attempt at HTTP is far too simplistic to work with real IP webcams. There is a lot more to deal with that you are completely ignoring, and as soon as authentication is required all of this will fall apart.



    Not to mention that your attached Project requires some OCX we don't have, even in source form.

  3. #3

    Thread Starter
    Frenzied Member
    Join Date
    Oct 2008
    Posts
    1,181

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by dilettante View Post
    This code "does so many don'ts" that it is downright scary.

    DoEvents calls are evil enough, but doubly so within code processing a non-GUI event like a Winsock data arraival.
    Each piece of code I used for a very specific reason.

    DoEvents is needed in the image processing sub (not the actual data arrival event itself), so that any later DataArrival events that might occur while the current image is still being processed, can fire and drop frames as needed. Otherwise you risk frames getting backed up, which produces an increasing lag as frames pile up and have to wait to be processed. After a few seconds of running there is a delay of several seconds. After about 10 seconds the delay is like 10 seconds. This continues indefinitely producing a hugely unacceptable lag. If the processing of the frame can't keep up with incoming frames, then frames need to be dropped from Winsock's internal data buffer, in order to prevent the above mentioned increasing lag. And DoEvents is not used after every line of image processing. It is only used once every certain number of lines as determined by a certain variable.

    Quote Originally Posted by dilettante View Post
    PeekData is on the "do not fly list" and the method only exists for legacy reasons. See INFO: Avoid Data Peeking in Winsock which only addresses the low-level problems with peeking, let alone the additional problems higher up in the ActiveX control you are using in VB6.
    PeekData is needed to check to see when a complete frame has been sent. Only if a complete frame has been sent should it attempt to process the frame. Using GetData would simply destroy the data, and if it didn't contain a complete frame (no guaranty that it will contain a complete frame) then the frame will be rendered useless, as part of it will have been destroyed.


    Quote Originally Posted by dilettante View Post
    Seriously? Receving binary data into a String and then converting that back? Not only do you risk data corruption from these conversions (depending on locale settings these may not even convert one-to-one) this is a very expensive process.
    This is absolutely necessary. I need to check to see when a specific marker string "00dc" is present in the data. The only way to check for a specific thing in a chunk of data is to have that chunk of data be a string, and use the InStr function. There is no equivalent for a byte array (at least not using VB6's built-in commands, not sure about 3rd party DLLs or OCXs that might add certain functionality). If I could have done this with a byte array and no conversions, I would have. For the default codepage (English) the conversion from string to byte array and back is a 1-to-1 conversion (only changing whether the data is stored in unicode's 2-bytes-per-character format or ascii's 1-byte-per-character format).


    Quote Originally Posted by dilettante View Post
    I don't know of any IP webcam that sends live streams in AVI format, which is where you seem to be getting this "00DC" from.
    AVI is the simplest kind of stream to extract frames from. VLC player can turn any stream into the type of stream used by my project. To make sure this works, make sure you are streaming from VLC Player and transcoding your source (even if your video source is already an IP-webcam), and that it is configured to stream out using the exact specifications I listed in my opening post in this thread, to make sure it is in the proper format for this VB6 project to receive.

    Quote Originally Posted by dilettante View Post
    The code has to be throwing away frame after frame after frame as written. That's assuming it doesn't just lock up hard and accept no frames at all most of the time. PeekData is more evil than DoEvents.
    As I said above, this is absolutely necessary. Frames have to be dropped because it takes a bit longer to process a frame than the duration of a frame at 30fps.

    Quote Originally Posted by dilettante View Post
    Your attempt at HTTP is far too simplistic to work with real IP webcams. There is a lot more to deal with that you are completely ignoring, and as soon as authentication is required all of this will fall apart.
    It's all that is needed to access the HTTP streamed from VLC Player. As I said above, VLC Player is my video source for this project, converting any other video source into the exact format and protocol that my VB6 project depends on.

    Quote Originally Posted by dilettante View Post
    Not to mention that your attached Project requires some OCX we don't have, even in source form.
    If you have installed VB6 from the installation CD, you will already have the winsock OCX file. If you have Windows Vista or later for your OS, you'll have the needed wiaaut DLL file (the DLL for WIA 2.0 which this program uses). If you have Windows XP, you can download the DLL file from http://vbnet.mvps.org/files/updates/wiaautsdk.zip

    If you compile the project and use the Package and Deployment Wizard to make an installer for it and you then distribute it, your end users won't ever need to worry about these dependencies, because the needed OCX and DLL files will be included in the installer for the software.
    Last edited by Ben321; Jun 20th, 2014 at 02:43 PM.

  4. #4
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,412

    Re: MJPEG Webcam Receiver

    Re: the OCX - Diletantte was referring to Picbox2Pixels.ocx. It's non-standard,and others are unlikely to have it.

    Re: StrConv - You just shouldn't be using it. You should loop through a byte array to find the bytes you need (possibly even InstrB would be suitable, I'm not sure since I haven't looked closer a the code). Using Byte Arrays instead of strings and conversions back and forth might even speed up you program enough so that you can avoid the "backed up frames" problem you described (thus eliminating the need for DoEvents and PeekData).

    In either case, although Dilettante was pretty harsh (and probably got you in a defensive frame of mind), I would take his advice seriously. He clearly knows what he is talking about even though he may have come off a bit cruel.

  5. #5
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: MJPEG Webcam Receiver

    The reason I was harsh is that things posted in the CodeBank are meant as examples for others to follow. Nobody expects perfection, since none of us are capable of such. However something so clearly (dare I say?) awful needs to be called out as such.

    The title says "MJPEG Webcam" and as such the code posted is worthless. I'll say it again: IP cams do not stream live data as AVI, they send MJPEG streams over HTTP as described at Motion JPEG.

    What VLC does or does not do is irrelevant. It's a media player, not an IP cam.

    Here's a URL of a real one: http://plazacam.studentaffairs.duke.edu/mjpg/video.mjpg

    If you want reliable MJPEG support you might consider the free AXIS Media Control SDK or similar products from other sources instead.

  6. #6
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: MJPEG Webcam Receiver

    Hi Ben. Regarding this statement:

    This is absolutely necessary. I need to check to see when a specific marker string "00dc" is present in the data. The only way to check for a specific thing in a chunk of data is to have that chunk of data be a string, and use the InStr function. There is no equivalent for a byte array (at least not using VB6's built-in commands, not sure about 3rd party DLLs or OCXs that might add certain functionality). If I could have done this with a byte array and no conversions, I would have.
    Actually, you can use the little-known InStrB() function to do exactly what you describe. This article by Karl Petersen specifically describes InStrB() as a solution for searching a byte array for a known sequence of bytes:

    http://visualstudiomagazine.com/arti...te-arrays.aspx

    Hopefully that gives you a nice performance boost, while also avoiding any trouble from array -> String -> array conversions.

    Edit: should have read jpbro's comment more carefully before replying - he beat me to the punch!
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  7. #7

    Thread Starter
    Frenzied Member
    Join Date
    Oct 2008
    Posts
    1,181

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by jpbro View Post
    Re: the OCX - Diletantte was referring to Picbox2Pixels.ocx. It's non-standard,and others are unlikely to have it.
    Indeed it is nonstandard. I made it myself. It encapsulates a couple API calls and UDTs required by said API calls, as well as several useful functions and properties that use these API calls, so that they don't have to be manually declared each time I use them in a project that involves quickly modifying pixels in a picture box (in a way a lot faster than Point and PSet). My current project did originally use it, but it doesn't now. Before I used a very inefficient technique whereby I used an invisible picturebox to get the image, used this OCX to extract the pixels into a byte array, edited the raw pixel values then used the OCX to stick the pixels into a visible picture box (so I had 2 picture boxes and 2 instances of this OCX control on the form). Overall that was very inefficient. However I recently found the GetObject API call that gives me a memory address for the pixels in a StdPicture object (as well as the width and height of the image) which lets me CopyMemory them into a byte array. The StdPicture object is now replacing that invisible picturebox, and I'm using other API calls (and no OCX object to hold them either). So now I am using that API call GetObject to get the address of the pixels in the StdPicture, and CopyMemory to get the pixel values into a byte array as well as to send any edited values back into the StdPicture (after which I simply use Picture1.Picture=Pict to display the picture, where Pict is the name of the StdPicture object and Picture1 is the name of the picturebox control). This is much more efficient and does not use any custom OCX files. The current version of my program (the one that I uploaded) in fact doesn't even have one instance of that OCX file being used. Apparently I forgot to uncheck the checkbox beside it in the Components dialog box for my project. Thus there are still references to this OCX file still left in the VBP file for my project, but it is now a completely unused control. You won't find it anywhere on the form, or any code referencing it in my form's code.
    Last edited by Ben321; Jun 20th, 2014 at 08:42 PM.

  8. #8

    Thread Starter
    Frenzied Member
    Join Date
    Oct 2008
    Posts
    1,181

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by Tanner_H View Post
    Hi Ben. Regarding this statement:



    Actually, you can use the little-known InStrB() function to do exactly what you describe. This article by Karl Petersen specifically describes InStrB() as a solution for searching a byte array for a known sequence of bytes:

    http://visualstudiomagazine.com/arti...te-arrays.aspx

    Hopefully that gives you a nice performance boost, while also avoiding any trouble from array -> String -> array conversions.

    Edit: should have read jpbro's comment more carefully before replying - he beat me to the punch!


    Actually InStrB works on strings. Notice it isn't called InByteArray. What happens here is behind the scenes it performs a ByteArray-to-String conversion (just no ASCII-to-Unicode conversion), then it performs InStr on that string. This is time consuming, unless you already have a string you are working with. So in the end, your suggested use of InStrB is probably just as fast as my use of InStr, and therefore is no improvement to my code, if I'm understanding correctly how InStrB works.

  9. #9
    Hyperactive Member
    Join Date
    Oct 2013
    Posts
    389

    Re: MJPEG Webcam Receiver

    Ben, if i were you i would appreciate the situation.
    Other people criticizing your work, taking time to read your code and write comments just shows you how interested they were in your code.

    And i must admit that even if you don't agree with dilettante, he gave both you and me, a very informative comment, from which i learned something new.
    I thank both you and him for that.

  10. #10
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by Ben321 View Post
    Actually InStrB works on strings. Notice it isn't called InByteArray. What happens here is behind the scenes it performs a ByteArray-to-String conversion (just no ASCII-to-Unicode conversion), then it performs InStr on that string. This is time consuming, unless you already have a string you are working with. So in the end, your suggested use of InStrB is probably just as fast as my use of InStr, and therefore is no improvement to my code, if I'm understanding correctly how InStrB works.
    Don't ask for answers if you don't want them! You have a habit of making requests, getting answers to those requests, then pretending that you never wanted an answer in the first place.

    Earlier, you said:

    There is no equivalent for a byte array (at least not using VB6's built-in commands, not sure about 3rd party DLLs or OCXs that might add certain functionality). If I could have done this with a byte array and no conversions, I would have.
    You have been given a way to do it with a byte array and no conversions: InStrB. It operates on a byte array, and performs no conversions, exactly as you requested above.

    The performance savings are not in InStr vs InStrB, which is irrelevant. (They run the same underlying function, anyway; the main difference is whether they return byte position or character position.) The performance savings come from avoiding a conversion from binary data into a String, then converting it back using StrConv. You can skip those steps completely, which not just improves performance, but removes a huge potential bug-generator if anyone on a non-US locale runs your code.

    Both InStrB and InStr operate on arrays, if we want to get technical about it. (The only difference between strings and arrays is their header, and the way the bytes are interpreted.) There is no magical "behind-the-scenes" conversion from bytes to Strings using InStrB, and if you'd taken a moment to test the function, you'd realize that. Your argument would imply that other functions like Len() also magically convert data to a String before running their code. They do not.

    So saying things like:

    Actually InStrB works on strings. Notice it isn't called InByteArray. What happens here is behind the scenes it performs a ByteArray-to-String conversion (just no ASCII-to-Unicode conversion), then it performs InStr on that string. This is time consuming, unless you already have a string you are working with.
    ...is just silly, especially when you have been given a linked article that describes not just how the code works, but how it works for your specific scenario, e.g. locating a specific set of 4 bytes in a larger array.
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  11. #11
    Addicted Member
    Join Date
    Jun 2002
    Location
    Finland
    Posts
    169

    Re: MJPEG Webcam Receiver

    I would like to hear your opinion of my alternative.
    It parse picture from http stream.
    Still read data to string and convert that to bytearray, just because it's easy
    Attached Files Attached Files

  12. #12
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,219

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by pekko View Post
    I would like to hear your opinion of my alternative.
    It parse picture from http stream.
    Still read data to string and convert that to bytearray, just because it's easy
    Not bad - changed the String-Receiving/Parsing to ByteArrays, which is now
    about 3 times as fast as before - also moved a few Variables around, to not
    have that much at the Form-Module-Level, also added a tmrFPS and a lblFPS,
    the whole thing now looking this way:

    Code:
    Option Explicit
    
    Private xBorders As Long, yBorders As Long
    Private sckStates() As String
    Private LastDataTime As Date, FPS As Long
     
    Private Sub Form_Load()
    lblFPS.ForeColor = vbGreen
    xBorders = ScaleX(Width, vbTwips, vbPixels) - ScaleWidth
    yBorders = ScaleY(Height, vbTwips, vbPixels) - ScaleHeight
    
    sckStates = Split("Closed,Open,Listening,ConnectionPending,ResolvingHost,HostResolved,Connecting,Connected,Closing,Error", ",")
    
    'socket.RemoteHost = "plazacam.studentaffairs.duke.edu"
    socket.RemoteHost = "195.113.207.238" 'a cam with 15 FPS (Havlíckuv Brod Vysocina Czech Republic) - see http://www.mjpeg.net for more
    socket.RemotePort = 80
    tmrMain_Timer
    End Sub
    
    Private Sub socket_Connect()
    socket.SendData "GET /mjpg/video.mjpg HTTP/1.1" & vbCrLf & _
                    "Host: " & socket.RemoteHost & vbCrLf & _
                    "Connection: keep-alive" & vbCrLf & vbCrLf
    End Sub
    
    
    Private Sub socket_DataArrival(ByVal bytesTotal As Long)
    Dim i1 As Long, i2 As Long, d() As Byte, WTwips As Single, HTwips As Single
    Static ContentLength As Long, dd() As Byte
    
    LastDataTime = Now
    socket.GetData d
    dd = CStr(dd) & CStr(d)
    
    If ContentLength = 0 Then
        i1 = InStrB(1, dd, StrConv("Content-Length: ", vbFromUnicode))
        If i1 Then
            i1 = i1 + 16
            i2 = InStrB(i1, dd, StrConv(vbCrLf & vbCrLf, vbFromUnicode))
            If i2 Then
                ContentLength = StrConv((MidB$(dd, i1, i2 - i1)), vbUnicode)
                dd = MidB$(dd, i2 + 4)
            End If
        End If
    End If
    
    If ContentLength > 0 And UBound(dd) + 1 >= ContentLength Then
        FPS = FPS + 1
        Set Picture1.Picture = PictureFromByteStream(dd)
        dd = MidB$(dd, ContentLength)
        ContentLength = 0
     
        WTwips = ScaleX(Picture1.Width + xBorders, vbPixels, vbTwips)
        HTwips = ScaleY(Picture1.Height + yBorders, vbPixels, vbTwips)
        If Width <> WTwips Or Height <> HTwips Then
          Move (Screen.Width - WTwips) \ 2, (Screen.Height - HTwips) \ 2, WTwips, HTwips
        End If
    End If
    End Sub
    
    Private Sub socket_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)
      Debug.Print "socket_Error " & Description
      socket.Close
    End Sub
     
    Private Sub tmrFPS_Timer()
      lblFPS.Caption = "FPS: " & FPS: FPS = 0
    End Sub
    
    Private Sub tmrMain_Timer()
      If Caption <> sckStates(socket.State) Then Caption = sckStates(socket.State)
      If DateDiff("s", LastDataTime, Now) > 10 Then socket.Close
      If socket.State < sckResolvingHost Or socket.State > sckConnected Then socket.Connect
    End Sub
    If you want about factor 2-3 faster JPG-decoding (where now that the parsing is fast, most of the
    CPU-load is caused), you can use the decoder from vbRichClient5 or your own flat-API wrapper
    around libJPGTurbo (or alternatively the older Intel-JPG-Library, if you find it somewhere).

    Olaf

  13. #13
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: MJPEG Webcam Receiver

    This code is a little naive.

    It seems to make the assumption that the HTTP response stream's content arrives with the Content-Length header as the last header for each part of the multipart response stream:

    Code:
    ...{anything}Content-Length: {length}¶¶{JPEG encoded frame}...
    Where ¶ = Cr and Lf pair of characters.


    But this is only true if you get lucky.

    The stream really looks like:

    Code:
    ...{boundary-name}¶{one or more headers}¶¶{JPEG encoded frame}...
    I.e. the server could send:

    Code:
    ...{boundary-name}¶Content-Length: {length}¶Content-Type: image/jpeg¶¶{JPEG encoded frame}...

    But I agree, most of your CPU cycles should end up in the cost of JPEG decompression. The old Intel JPEG Library isn't a solution though, since it requires a license and they haven't been offered in over a decade.
    Last edited by dilettante; Jun 22nd, 2014 at 11:46 PM.

  14. #14
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,219

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by dilettante View Post
    This code is a little naive...
    ...I.e. the server could send
    Code:
    ...{boundary-name}¶Content-Length: {length}¶Content-Type: image/jpeg¶¶{JPEG encoded frame}...
    Ack.

    @Pekko - below is a version for the RC5 (demonstrating cTCPClient and cJPG,
    just in case you're still interested in studying this stuff):

    Into a Module (take care to switch the Project to start from Sub Main):

    Code:
    Option Explicit
    
    Public fMain As New cfMain
    
    Sub Main()
      fMain.Form.Show
      Cairo.WidgetForms.EnterMessageLoop
    End Sub
    And the code below into a private WidgetForm-wrapper-Class, named: 'cfMain'
    Code:
    Option Explicit
    
    Private Const SckRcvBufSize As Long = 2000000, HeaderLen As Long = 512
    
    Private mHost As String, JPG As cJPG, Srf As cCairoSurface
    Private WithEvents Sck As cTCPClient, WithEvents FPS As cTimer, WithEvents UpdGUI As cOneShotTimer
    
    Public WithEvents Form As cWidgetForm
    
    Private Sub Class_Initialize()
      Set Form = Cairo.WidgetForms.Create(vbSizable, "WebCam-View", , 656, 519)
      Set Sck = New_c.TCPClient
      Set JPG = New_c.JPG
      Set Srf = Cairo.CreateSurface(1, 1)
      Set FPS = New_c.Timer(1000, True, 0)
      Set UpdGUI = New_c.OneShotTimer
      
    '  ConnectTo "plazacam.studentaffairs.duke.edu"
    '  ConnectTo "195.113.207.238" '(Havlíckuv Brod Czech Republic)
    '  ConnectTo "81.167.131.240" '(Karmøy Rogaland Norway)
      ConnectTo "77.72.56.163" '(Helsinki Finland, 800x500 -> up to 30FPS max.)
    End Sub
    
    Public Sub ConnectTo(Host As String, Optional ByVal Port As Long = 80)
      mHost = Host
      Sck.Connect New_c.TCPServer.GetIP(mHost), Port, 3, SckRcvBufSize
    End Sub
    
    Private Sub Sck_TCPConnect(ByVal hSocket As Long)
    Static B() As Byte
      B = StrConv("GET /mjpg/video.mjpg HTTP/1.1" & vbCrLf & "Host: " & mHost & vbCrLf & _
                  "Connection: keep-alive" & vbCrLf & vbCrLf, vbFromUnicode)
      Sck.SendData hSocket, VarPtr(B(0)), UBound(B) + 1
    End Sub
    
    Private Sub Sck_SockError(ByVal hSocket As Long, ErrString As String)
      MsgBox "SockError-Msg: " & ErrString
    End Sub
    
    Private Sub Sck_DataArrival(ByVal hSocket As Long, ByVal BytesTotal As Long, ByVal FirstBufferAfterOverflow As Boolean)
    Static Header(HeaderLen) As Byte, JpgBytes(SckRcvBufSize) As Byte, ContentLen As Long
    Dim ContentOffs As Long, JpgW As Long, JpgH  As Long, JpgLen As Long
      
      If BytesTotal >= HeaderLen And ContentLen = 0 Then
        Sck.GetData hSocket, VarPtr(Header(0)), HeaderLen, True
        ContentLen = GetContentLen(StrConv(Header, vbUnicode), ContentOffs)
        Sck.GetData hSocket, VarPtr(JpgBytes(0)), IIf(ContentLen, ContentOffs, SckRcvBufSize)
        BytesTotal = BytesTotal - IIf(ContentLen, ContentOffs, SckRcvBufSize)
      End If
      
      If BytesTotal >= ContentLen And ContentLen > 0 Then
        JpgLen = ContentLen: ContentLen = 0 'buffer the static Variable in a stack-based one (and reset it)
        Sck.GetData hSocket, VarPtr(JpgBytes(0)), JpgLen
        If JPG.GetJPGDimensions(VarPtr(JpgBytes(0)), JpgLen, JpgW, JpgH) Then 'we have a valid JPG
          If Srf.Width <> JpgW Or Srf.Height <> JpgH Then Set Srf = Cairo.CreateSurface(JpgW, JpgH)
          JPG.DecodeJPG VarPtr(JpgBytes(0)), JpgLen, Srf.DataPtr, 4 * JpgW * JpgH, , , , , True
          UpdGUI.TriggerNextShot
        End If
      End If
    End Sub
    
    Private Sub UpdGUI_Shot() 'the decoupled Background-Image-Refresh of our Form
      Cairo.ImageList.AddSurface "CamImg", Srf
      Form.WidgetRoot.ImageKey = "CamImg" 
      FPS.Tag = FPS.Tag + 1
    End Sub
    
    Private Function GetContentLen(Header As String, ContentOffs As Long) As Long
      ContentOffs = InStr(InStr(1, Header, "Content-Length:", vbTextCompare) + 1, Header, vbCrLf & vbCrLf) + 3
      If ContentOffs > 3 Then GetContentLen = Val(Split(Header, "Content-Length:", , vbTextCompare)(1))
    End Function
     
     
    Private Sub FPS_Timer()
      Form.Caption = "WebCam-View (" & Srf.Width & "x" & Srf.Height & ", " & _
                     Int(FPS.Tag / Replace(New_c.Timing, "msec", "") * 1000) & "FPS)"
      FPS.Tag = 0
      New_c.Timing True
    End Sub
     
    Private Sub Form_Unload(Cancel As Integer)
      Set FPS = Nothing
      Set Sck = Nothing
      UpdGUI.CancelShot
    End Sub
    Olaf
    Last edited by Schmidt; Jun 23rd, 2014 at 10:48 PM. Reason: corrected the GetContentLen - Function, buffer and reset ContentLen earlier

  15. #15
    Addicted Member
    Join Date
    Jun 2002
    Location
    Finland
    Posts
    169

    Re: MJPEG Webcam Receiver

    Very impressive, like that scaling feature

    I note couple of things
    - fps counter jumping when you resize form, I understand if it decreasing but it's increasing, up close to 100
    - ui freeze when you resize form, only ui, picture is updating ok, mouse cursor stay as arrowed, can't go to taskbar, mouse restricted to desktop area
    - best way to freeze ui is to maximize form
    - second best way to freeze ui is resize form as narrow as possible and then fast resize it as width as possible

    btw: is it possible to add SendComplete event to cTCPClient and cTCPServer

  16. #16
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,219

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by pekko View Post
    I note couple of things
    - fps counter jumping when you resize form, I understand if it decreasing but it's increasing, up close to 100
    That's because Socket-messages have relative high, Timer-messages a relative low priority ...
    so "under stress" the Timer-Message gets blocked (not resetting the Counter) - whilst Socket-
    messages still get through, further increasing the FPS-Counter.
    That could be fixed with a true time-delta-measurement (not relying on the FPS-Timer to always
    exactly landing its Event at 1000msec intervals).

    Quote Originally Posted by pekko View Post
    - ui freeze when you resize form, only ui, picture is updating ok, mouse cursor stay as arrowed, can't go to taskbar, mouse restricted to desktop area
    There's only a very few places in the RC5, where I use DoEvents - the Socket- as well
    as the FormEngine- and -WidgetClasses definitely do not contain any such calls.

    And since the Socket-messages have a high prio (and are handled on the Main-Thread of the App),
    one might notice some "UI-freezing", since also the surrounding Demo-Code (so far) is
    not using any DoEvents.

    I've noticed the behaviour with the "Helsinki-Cam" myself (the one which is the default in the Demo),
    when it sends with its full rate of 30-FPS - if it's sending with only about 20FPS, then it leaves enough
    "breathing-space" for the App, to reach "idle-state" on its Message-Queue often enough.

    Well, it's just a Demo - for a true "product", I'd let the socket-parts (as well as the JPG-decoding)
    run on their own "thread-per-camera" - handing out the pointer to the "Decoder-DIBs" memory
    (allocated on the thread) back, over an appropriate Thread-Event (then only mem-copying from
    that DIB-Pointer over into a cairo-surface, which was allocated on the Main-Thread).

    Quote Originally Posted by pekko View Post
    btw: is it possible to add SendComplete event to cTCPClient and cTCPServer
    IMO not necessary, since the SendData-method will only return after any Byte in the Bytearr-Param
    was handed over to the systems socket-stack (I perform an internal loop there, using the
    ws2_32.dll WaitSelect API, to accomplish this "completely handing-over"-task).

    If you need "true reassurance" from the server-side, then it's better to let the server send
    a dedicated "receipt-packet".

    But we digress - apologies to Ben321 for that - though maybe dilettantes comments, as well as
    yours and my example gave some additional ideas on streaming MJPG-stuff in general.

    Olaf
    Last edited by Schmidt; Jun 23rd, 2014 at 07:08 PM.

  17. #17
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,219

    Re: MJPEG Webcam Receiver

    Final update on the posted RC5-WebCam-code in #14 (now with a true dT-based measuring for the FPS, along
    with a decoupling of the GUI-refresh from the socket-events with a OneShot-Timer (which fires back once,
    with a higher prio and a very short delay of 1-2msec and is thought for such purposes).

    Even with the fast "Helsinki-Cam", firing away with 30FPS - all looking good now, without
    introducing a threaded approach.

    Olaf

  18. #18

    Thread Starter
    Frenzied Member
    Join Date
    Oct 2008
    Posts
    1,181

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by Tanner_H View Post
    Don't ask for answers if you don't want them! You have a habit of making requests, getting answers to those requests, then pretending that you never wanted an answer in the first place.

    Earlier, you said:



    You have been given a way to do it with a byte array and no conversions: InStrB. It operates on a byte array, and performs no conversions, exactly as you requested above.

    The performance savings are not in InStr vs InStrB, which is irrelevant. (They run the same underlying function, anyway; the main difference is whether they return byte position or character position.) The performance savings come from avoiding a conversion from binary data into a String, then converting it back using StrConv. You can skip those steps completely, which not just improves performance, but removes a huge potential bug-generator if anyone on a non-US locale runs your code.

    Both InStrB and InStr operate on arrays, if we want to get technical about it. (The only difference between strings and arrays is their header, and the way the bytes are interpreted.) There is no magical "behind-the-scenes" conversion from bytes to Strings using InStrB, and if you'd taken a moment to test the function, you'd realize that. Your argument would imply that other functions like Len() also magically convert data to a String before running their code. They do not.

    So saying things like:



    ...is just silly, especially when you have been given a linked article that describes not just how the code works, but how it works for your specific scenario, e.g. locating a specific set of 4 bytes in a larger array.


    I am not turning down your advice. When you say that something should be different, I simply am explaining why I did it the way I did. Maybe it would have an effect on the advice you will give on the best alternative way to accomplish it. Just as it's important I listen to what you say, it's also important that you listen to what I say when I explain the reasoning behind each of the "bad" pieces of code I wrote. Unlike C, VB6 is quite limited, and so sometimes you need to "cheat" to get the desired result (even if that means using what would normally be considered "bad" coding practice, if it were in any other programming language). That's not to say that maybe there isn't a better way to do what I'm trying to do. But to give me proper advice, it helps to know what I'm trying to accomplish, which is why each piece of advice I've been given so far I've replied with an explanation for why I did it the way I did. Maybe the intent behind the code will effect what the better alternative method is for accomplishing that task.

    I feel like it's not me that's not listening. I feel like it is others who are not listening, and being rude on top of it. I come here for advice, and get the feeling everyone looks down on me as some kind of "noob", who deserves to get yelled at.

  19. #19
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: MJPEG Webcam Receiver

    I apologize for the tone of my earlier reply. It certainly could have been worded more tactfully.

    I don't think you're a noob. You've posted a lot of interesting code over the years, and your questions have led to many worthwhile discussions.

    What I find frustrating is when you're given input and advice, and instead of trying it out - or in this case, just reading the attached link, which comes from a prolific VB developer in Visual Studio Magazine, of all things - you immediately start throwing up excuses for the code you've written, or arguing over the suggestion without even trying it.

    I think I read your InStrB concerns very closely, which is why I provided a follow-up post that dealt with every concern you raised - even the ridiculous ones. (And let's be honest, they were ridiculous. If you don't know how a function like InStrB works, don't make up your own explanation. Google it!) On top of that, the post that frustrated me had no mention of why you'd written the code you did. It was just argumentative.

    To quote from it:

    "So in the end, your suggested use of InStrB is probably just as fast as my use of InStr, and therefore is no improvement to my code.
    People offering advice don't want to hear statements like this, especially when all your "facts" for such a strong position are things you just made up. If you tried a suggestion and it didn't work, say that, and we can discuss why. But don't shoot down suggestions without trying them - or in this case, reading the short link you've been supplied - unless you know with absolute certainty that they won't work. Otherwise, you're just wasting our time, and treating us like "noobs" who have no idea what we're doing. We don't like that any more than you do, especially when we're the ones trying to help.
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  20. #20

    Thread Starter
    Frenzied Member
    Join Date
    Oct 2008
    Posts
    1,181

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by Tanner_H View Post
    I apologize for the tone of my earlier reply. It certainly could have been worded more tactfully.

    I don't think you're a noob. You've posted a lot of interesting code over the years, and your questions have led to many worthwhile discussions.

    What I find frustrating is when you're given input and advice, and instead of trying it out - or in this case, just reading the attached link, which comes from a prolific VB developer in Visual Studio Magazine, of all things - you immediately start throwing up excuses for the code you've written, or arguing over the suggestion without even trying it.

    I think I read your InStrB concerns very closely, which is why I provided a follow-up post that dealt with every concern you raised - even the ridiculous ones. (And let's be honest, they were ridiculous. If you don't know how a function like InStrB works, don't make up your own explanation. Google it!) On top of that, the post that frustrated me had no mention of why you'd written the code you did. It was just argumentative.

    To quote from it:



    People offering advice don't want to hear statements like this, especially when all your "facts" for such a strong position are things you just made up. If you tried a suggestion and it didn't work, say that, and we can discuss why. But don't shoot down suggestions without trying them - or in this case, reading the short link you've been supplied - unless you know with absolute certainty that they won't work. Otherwise, you're just wasting our time, and treating us like "noobs" who have no idea what we're doing. We don't like that any more than you do, especially when we're the ones trying to help.


    There's still one other problem. Even if I search for a byte array in a byte array, using InStrB, there is still the issue of getting the string that you want to search for, into the form of a byte array. If I'm looking for the string "00db" in a long buffer of data, using InStrB, my first approach (for a file that is, but getting the data over the net would be similar) would be to use:

    Code:
    Dim Buffer() as Byte
    Dim DataToFind() as Byte
    Dim DataOffset as Long
    
    Open App.Path & "\testdata.dat" For Binary As #1
    Redim Buffer(LOF(1)-1)
    Get #1,1,Buffer()
    Close #1
    DataToFind()=StrConv("00dc",vbFromUnicode)
    
    DataOffset=InStrB(1,Buffer,DataToFind)
    Print DataOffset
    The problem with that is that while this code removes the need for StrConv when it comes to getting the file data into a byte array (the Buffer byte array), it still cannot avoid using StrConv when getting the data to search for (the DataToFind byte array). Is this an unavoidable use of StrConv? Or is there another way of getting the string "00dc" into a byte array?

    By the way what exactly is the problem with using StrConv in other locales? I thought StrConv automatically adapts to whatever your locale is, so that it will convert properly AnsiToUnicode and UnicodeToAnsi, regardless of what language your computer is setup for.
    Last edited by Ben321; Jun 26th, 2014 at 03:05 PM.

  21. #21
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,219

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by Ben321 View Post
    The problem with that is that while this code removes the need for StrConv when it comes to getting the file data into a byte array ...
    ...which is the important part, since it is usually the (much) larger allocation...

    Quote Originally Posted by Ben321 View Post
    ... it still cannot avoid using StrConv when getting the data to search for (the DataToFind byte array). Is this an unavoidable use of StrConv? Or is there another way of getting the string "00dc" into a byte array?
    Your search-String "00dc" is small - and well in the ASCII-range (and therefore making no problems on different locales) -
    so StrConv is fine in this special case...

    But there's also no problem in filling the Bytearray "by hand" with the appropriate Char-Values:
    Code:
    Dim DataToFind(0 to 3) as Byte
      DataToFind(0) = 48
      DataToFind(1) = 48
      DataToFind(2) = 100
      DataToFind(3) = 99
    Quote Originally Posted by Ben321 View Post
    By the way what exactly is the problem with using StrConv in other locales? I thought StrConv automatically adapts to whatever your locale is, so that it will convert properly AnsiToUnicode and UnicodeToAnsi, regardless of what language your computer is setup for.
    As you already wrote in your first sentence (the problem is usually not apparent,
    when you encode/decode on your own locale) - the problem is with *other* locales.

    E.g. when you save on your own locale to ANSI - and then send the Data to a
    machine with a different locale (e.g. per Sockets this is no problem at all, to cross
    "country-borders") - when your chinese friend on the receiving end wants to decode
    an 8Bit ANSI-ByteBuffer (you encoded with an english locale) - then it's not guaranteed,
    that he will receive the same content in his 16Bit VBString, as you've put in whilst earlier
    saving your String to a File on your own machine for example.

    Also when we talk about Binary-Content (JPGs or whatever), it is better to entirely remain
    in "ByteArray-Space".

    "String-Space" is for Text-Contents (and proper handling of the different text-encodings is
    already difficult enough) - no need to make life even more complicated, by adding pure binary
    contents to the list of things to take care of in "String-Space".

    Olaf

  22. #22
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: MJPEG Webcam Receiver

    Some locales don't use ANSI as such either, but a DBCS (double-byte character set). This can add distortion when you play with binary data as strings, since not everything maps 1-to-1 when you do the conversions.

    When you add in the cost of doing the two conversions for every TCP segment received, there just isn't any excuse for ever doing it.

  23. #23
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: MJPEG Webcam Receiver

    Quote Originally Posted by Ben321 View Post
    There's still one other problem. Even if I search for a byte array in a byte array, using InStrB, there is still the issue of getting the string that you want to search for, into the form of a byte array.
    Olaf already provided a nice reply, but this issue is covered in great detail in the original link I gave. Once again:

    http://visualstudiomagazine.com/arti...te-arrays.aspx
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  24. #24

    Thread Starter
    Frenzied Member
    Join Date
    Oct 2008
    Posts
    1,181

    Re: MJPEG Webcam Receiver

    Is this the right kind of code then for conversions?

    Code:
    Public Function StrToAry(ByVal Text As String) As Byte()
    Dim Bytes() As Byte
    Dim Bytes2() As Byte
    Bytes() = Text
    ReDim Bytes2(Len(Text) - 1)
    For n = 0 To UBound(Bytes2)
        Bytes2(n) = Bytes(n * 2)
    Next n
    StrToAry = Bytes2()
    End Function
    
    Public Function AryToStr(ByRef Bytes() As Byte) As String
    Dim Bytes2() As Byte
    ReDim Bytes2((UBound(Bytes) + 1) * 2 - 1)
    For n = 0 To UBound(Bytes)
        Bytes2(n * 2) = Bytes(n)
    Next n
    AryToStr = Bytes2()
    End Function
    
    Public Function InAry(ByVal StartOffset As Long, ByRef Array1() As Byte, ByRef Array2() As Byte) As Long
    InAry = InStrB(StartOffset + 1, Array1, Array2) - 1
    End Function
    These functions do direct conversion from double-byte form (unicode string) to single-byte form (ansi byte array), and back again. Unlike StrConv (which does locale conversions), this does direct conversions from string (unicode) to byte array (ansi), by putting the lower byte of each unicode character into a byte in the byte array, and ignores the upper byte of each unicode character. For the reverse conversion (converting byte array to string) each byte of the byte array is assigned to the lower byte each character in the string, and the upper byte of each string's character is left at 0x00.

    As a bonus, I included an extra function that allows searching in a byte array for another byte array. it is basically a wrapper for InStrB, with the advantage over the pure InStrB function in that its offsets start at 0, not 1. This way the output of the function can be directly used in an array without having to subtract 1 from the output, and also without having to add 1 to a byte array's index before plugging it into the Start parameter of the InStrB function.
    Last edited by Ben321; Jul 14th, 2014 at 07:35 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width