Results 1 to 22 of 22

Thread: [RESOLVED] Named Pipes from External Programs

  1. #1

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Resolved [RESOLVED] Named Pipes from External Programs

    If anyone can help it would very much appreciated. (this code will help those who want stdin from an external app)

    Its my first time trying to get Pipes to work, all the exapmles on the net for vb.net show two applications both built in vb.net so Im struggling to work out whether its my application thats not working as it should or if its the ffmpeg command line. I don't believe its the ffmpeg command line because if I send it to stdout instead of the pipe, it puts the bitmap bytes to the console window.

    I believe its something to do with reading the pipe data because the app obviously has an inbound connection
    Name:  pipe-error.jpg
Views: 1765
Size:  20.0 KB


    Code:
    Imports System.Drawing.Imaging
    Imports System.IO
    
    Public Class Form1
    
        Dim ServerPipe As System.IO.Pipes.NamedPipeServerStream
    
        Dim OpenVideoName As String = ""
        Private Delegate Sub InvokeWithString(ByVal text As String)
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
            Dim t As New System.Threading.Thread(New System.Threading.ThreadStart(AddressOf StartServerPipe))
            t.Priority = Threading.ThreadPriority.Normal
            t.Start()
    
        End Sub
    
        Private Sub StartServerPipe()
            Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
            ServerPipe.WaitForConnection()
            Dim img() As Byte
            ' a continual listening loop
            Do
                Dim B(0 To 1000) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
                ReDim Preserve B(0 To L - 1)
                img = B ' copy the array to an array thats available outside this loop
            Loop While ServerPipe.IsConnected
    
    
            Dim MS As New MemoryStream(img)
            ' temporarily dump the data to a file to help with debugging
            ' remove this later
            Dim fname As String = Application.StartupPath + "\ByteData.bmp"
    
            ' convert the bytestream back to a bitmap
            System.IO.File.WriteAllBytes(fname, img)
            Dim Bmp As New Bitmap(MS)
    
            ' cross thread to update the picturebox
            If Me.InvokeRequired Then
                BeginInvoke(Sub()
                                pic1.Image = Bmp
                            End Sub)
            Else
                pic1.Image = Bmp
            End If
        End Sub
    
        Private Sub ButAskFFmpegForImage_Click(sender As Object, e As EventArgs) Handles ButAskFFmpegForImage.Click
    
            OpenFileDialog1.RestoreDirectory = True
            OpenFileDialog1.Filter = "Video Files (*.ts, *.mkv, *.mp4, *.avi)|*.ts;*.mkv;*.mp4; *.avi|All Files (*.*)|*.*"
            OpenFileDialog1.ShowDialog()
    
            OpenVideoName = OpenFileDialog1.FileName
            If File.Exists(Application.StartupPath + "\ffmpeg.exe") Then
                Dim ffmpegfilepath As String = Application.StartupPath + "\ffmpeg.exe"
    
                ' New ProcessStartInfo created
                Dim p As New Process
    
                ' Specify the location of the binary
                p.StartInfo.FileName = ffmpegfilepath
    
                p.StartInfo.Arguments = " -i """ + OpenVideoName + """ -ss 00:00:50 -s 720x560 -vframes 1 -c:v bmp -y -f image2pipe " + """\\.\pipe\From_FFmpeg"""
                ' command to dump it to file is working fine
                ' ffmpeg -i test.ts -ss 00:00:50 -s 720x560 -vframes 1 -c:v bmp -y $filename%03d.bmp
                p.StartInfo.UseShellExecute = False
                ' Use a hidden window (rem these out to debug)
                p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
                p.StartInfo.CreateNoWindow = True
                p.StartInfo.RedirectStandardError = True
                p.StartInfo.RedirectStandardOutput = True
                p.SynchronizingObject = Me
                p.EnableRaisingEvents = True
                AddHandler p.OutputDataReceived, AddressOf UpdateDataReceived
                AddHandler p.ErrorDataReceived, AddressOf UpdateDataReceived
                p.Start()
                p.BeginErrorReadLine()
                p.BeginOutputReadLine()
            Else
                MsgBox("No ffmpeg")
            End If
        End Sub
    
    
        Public Sub UpdateDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
            ' cross thread to update the picturebox
            Me.Invoke(New InvokeWithString(AddressOf SyncOutput), e.Data)
        End Sub
    
        Private Sub SyncOutput(ByVal text As String)
            ' update the ffmpeg stdout/stderr text in the textbox and scroll
            TextBox1.AppendText(text & Environment.NewLine)
            TextBox1.ScrollToCaret()
        End Sub
    
        Private Sub Form1_Closed(sender As Object, e As EventArgs) Handles Me.Closed
            ' close the pipe when leaving the form isn't needed
            'ServerPipe.Close()
        End Sub
    
    End Class
    * Update, Im not actually getting any data in. - The screenshot says 32 k of data, I should actuall get 1,209,654 bytes (over 1mb)
    Last edited by mickle026; Feb 24th, 2019 at 02:45 PM.

  2. #2

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    Quote Originally Posted by mickle026 View Post
    * Update, Im not actually getting any data in. - The screenshot says 32 k of data, I should actuall get 1,209,654 bytes (over 1mb)
    This is incorrect. My pipe is working. Im not building an array of bytes, im clearing it and reinitializing the array

    This is from a youtube text reader example
    Code:
                Dim B(0 To 1000) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
                ReDim Preserve B(0 To L - 1)
    What I need to do is expand the array as data is received - the data needs to be available to convert to a bitmap for a picturebox with a memory stream any ideas?

    Ive tried this, so that img holds the data. It works but then I end up with a zero byte appended to the data which i dont want

    Code:
    Dim img(0) As Byte
            ' a continual listening loop
            Do
                Dim B(0 To 1000) As Byte
    
                Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
                ReDim Preserve B(0 To L - 1)
    
                img = img.concat(B)
    
            Loop While ServerPipe.IsConnected

  3. #3
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Named Pipes from External Programs

    Perhaps rather then Redim Preserve, you should just use a List(Of Byte) to accumulate the bytes. Then you shouldn't need the separate img array either.

  4. #4

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    Quote Originally Posted by passel View Post
    Perhaps rather then Redim Preserve, you should just use a List(Of Byte) to accumulate the bytes. Then you shouldn't need the separate img array either.
    Thanks for the suggestion - it works! (You'll have to forgive me if i sound lost with this. I never learned vb by any educational facility - just a hobby coder.)

    However its low because I have to read the pipe 1 byte at a time

    Code:
    Dim B(0) As Byte
    Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
    byteList.Add(B)
    BUT i have another question if I may, the working example below is not as quick as I would like.

    Working Code

    Code:
        Private Sub StartServerPipe()
            Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
            ServerPipe.WaitForConnection()
            ' a continual listening loop
    
            Dim byteList As New List(Of Byte())
    
            Do
                Dim B(0) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
                byteList.Add(B)
            Loop While ServerPipe.IsConnected
    
            Dim img As Byte()
            'img = byteList.SelectMany(Function(x) x).ToArray()
            img = (From bytes In byteList From x In bytes Select x).ToArray()
    
            Dim MS As New MemoryStream(img)
            ' temporarily dump the data to a file to help with debugging
            ' remove this later
            Dim fname As String = Application.StartupPath + "\ByteData.bmp"
            System.IO.File.WriteAllBytes(fname, img)
            ' convert the bytestream back to a bitmap
            Dim Bmp As New Bitmap(MS)
            ' cross thread to update the picturebox
            If Me.InvokeRequired Then
                BeginInvoke(Sub()
                                pic1.Image = Bmp
                            End Sub)
            Else
                pic1.Image = Bmp
            End If
        End Sub
    Result
    Name:  lhp1.jpg
Views: 1531
Size:  31.6 KB


    However if I do the same but change the arraylist to hold arraylists instead of bytes its extremely fast but the image is screwed up, obvioulsy the arrays need flattening in to one.


    Code:
    Dim B(2000) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
                byteList.Add(B)
    I am currently doing this

    Code:
    Dim img As Byte()
    img = (From bytes In byteList From x In bytes Select x).ToArray()
    but my image is coming out like this, and im not sure why.

    Name:  lhp2.jpg
Views: 1555
Size:  43.4 KB

  5. #5
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Named Pipes from External Programs

    You shouldn't have to store one byte at a time in the list, you could use .AddRange to add an array of bytes.
    You can try larger sizes in your buffer, perhaps print out the value of L in the loop to see if it is often less than the size of the buffer or not.
    If the stream is keeping up with buffer (i.e. always filling it each loop), you can try a larger size to find an optimal value. I probably wouldn't make it larger than 64K, but 64K itself might be a good value.

    I don't know which would be quicker, sticking with the Redim Preserve to size the array to be used in the .AddRange, or to use a linq statement to pull a subarray from the array. I'm not that familiar with linq so would have to figure it out myself to test. Another option would be to use Array.Copy to copy from the buffer array to a properly sized array (based on the return value of L). Of course, the Redim Preserve or subarray or array copy would only need to be done if the buffer wasn't full, so if you checked that first, you could skip manipulating the array.
    Basically, based on your original code, I was envisioning you modifying it similar to below, other than I originally figured you may not need the ReDim Preserve, but you do need to size the array that is passed to the AddRange method, so I reinstated the ReDim Preserve. Since the ReDim Preserve will always make the array the same size, or less, it has the potential of being fairly efficient since in theory it should have to copy the data, just change the length of the array.
    Code:
            Do
                Dim B(4095) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, 4096)
                ReDim Preserve B(L - 1)
                byteList.AddRange(B)
            Loop ' While ServerPipe.IsConnected
    
            Dim img As Byte() = byteList.ToArray()
    p.s. Based on the image you get when you read arrays of 2000 bytes from the stream, it looks like you aren't filling the whole array, so my idea of increasing the buffer may not apply here. I would guess maybe a buffer of 1024 could be ideal.

    Also, given what I mentioned above, then I suspect you now know ways of flattening the arrays. If you added the Redim Preserve to trim off the unused bytes before storing the array in your list, that is one way of doing it.

    p.p.s I looked up the linq extensions way of doing it, so here is one. Don't know if it is faster than ReDim Preserve, or less memory thrashing.
    Code:
            Do
                Dim B(4095) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, 4096)
                byteList.AddRange(B.Take(L).ToArray)
            Loop ' While ServerPipe.IsConnected
    Last edited by passel; Feb 24th, 2019 at 09:41 PM.

  6. #6

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    Quote Originally Posted by passel View Post
    You shouldn't have to store one byte at a time in the list, you could use .AddRange to add an array of bytes.
    You can try larger sizes in your buffer, perhaps print out the value of L in the loop to see if it is often less than the size of the buffer or not.
    If the stream is keeping up with buffer (i.e. always filling it each loop), you can try a larger size to find an optimal value. I probably wouldn't make it larger than 64K, but 64K itself might be a good value.

    I don't know which would be quicker, sticking with the Redim Preserve to size the array to be used in the .AddRange, or to use a linq statement to pull a subarray from the array. I'm not that familiar with linq so would have to figure it out myself to test. Another option would be to use Array.Copy to copy from the buffer array to a properly sized array (based on the return value of L). Of course, the Redim Preserve or subarray or array copy would only need to be done if the buffer wasn't full, so if you checked that first, you could skip manipulating the array.
    Basically, based on your original code, I was envisioning you modifying it similar to below, other than I originally figured you may not need the ReDim Preserve, but you do need to size the array that is passed to the AddRange method, so I reinstated the ReDim Preserve. Since the ReDim Preserve will always make the array the same size, or less, it has the potential of being fairly efficient since in theory it should have to copy the data, just change the length of the array.
    Code:
            Do
                Dim B(4095) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, 4096)
                ReDim Preserve B(L - 1)
                byteList.AddRange(B)
            Loop ' While ServerPipe.IsConnected
    
            Dim img As Byte() = byteList.ToArray()
    p.s. Based on the image you get when you read arrays of 2000 bytes from the stream, it looks like you aren't filling the whole array, so my idea of increasing the buffer may not apply here. I would guess maybe a buffer of 1024 could be ideal.

    Also, given what I mentioned above, then I suspect you now know ways of flattening the arrays. If you added the Redim Preserve to trim off the unused bytes before storing the array in your list, that is one way of doing it.

    p.p.s I looked up the linq extensions way of doing it, so here is one. Don't know if it is faster than ReDim Preserve, or less memory thrashing.
    Code:
            Do
                Dim B(4095) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, 4096)
                byteList.AddRange(B.Take(L).ToArray)
            Loop ' While ServerPipe.IsConnected
    Thanks you for your response

    I think my main issue here is that both the Byte array and the List of Byte array are two seperate types of Byte array - ie Byte() and Byte()()

    Code:
    Dim B(4096) as Byte
    and

    Code:
    byteList.AddRange(B.Take(L).ToArray)
    Code:
    Unable to cast object of type 'System.Byte[]' to type 'System.Collections.Generic.IEnumerable`1[System.Byte[]]
    And this results in I am unable to push the list of Byte array to the img byte array because one list is Byte()() and the other is Byte()

    Code:
    Dim img As Byte() = byteList.ToArray()
    So an overview of my problem is I need to create a byte array that expands, then have it in a format that works with the memorystream to create a memory bitmap. We have already determined that the Pipe Server works, but its an issue with getting data from the pipe to the array. So its essentially an array problem.

    I have to admit though that I hadn't realised that the pipe may not have the full amount of bytes that I am trying to read. That makes sence to the image shifting. So thanks for pointing that out. So the array increase needs to be dynamic based of how many bytes are received.

    p.s. Based on the image you get when you read arrays of 2000 bytes from the stream, it looks like you aren't filling the whole array, so my idea of increasing the buffer may not apply here. I would guess maybe a buffer of 1024 could be ideal.
    I think I had better go and learn more about arrays .....

  7. #7

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    Because I am unable to figure out how to add the range to a list of byte array, ive tried a different approach

    Code:
             
            Dim img() As Byte
            Dim ImgSize As Integer = 0
    
            ' setup some array pointers
            Dim ArrayPointer As Integer = 0
            Dim PipePointer As Integer = 0
    
            Do
                Dim B(4095) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, 4096)
                ReDim Preserve B(L - 1)
                ' get the size that the array needs to be
                ImgSize = ImgSize + B.Length - 1 ' really needs to be serverpipe.length - but its not allowed
                ' increase its size and keep its contents
                ReDim Preserve img(0 To ImgSize)
    
                PipePointer = 0 ' pointer for the pipe data reads from 0 to 4096
                For a = ArrayPointer To img.Length - 1
                    ' If PipePointer = L Then Exit For
                    ' write to the correct arrayelement
                    img(a) = B(PipePointer) ' copy 0 to buffer length to the right element in the img array
                    PipePointer = PipePointer + 1
                    ArrayPointer = ArrayPointer + 1
                Next
    
            Loop While ServerPipe.IsConnected
    
            Dim MS As New MemoryStream(img)
            ' temporarily dump the data to a file to help with debugging
            ' remove this later
            Dim fname As String = Application.StartupPath + "\ByteData.bmp"
            System.IO.File.WriteAllBytes(fname, img)
            ' convert the bytestream back to a bitmap
            Dim Bmp As New Bitmap(MS)
    This is adding directly to the img byte array. Everyhing is working up to Dim Bmp As New Bitmap(MS) because the bitmap is not completely valid, the first 4096 bytes are correct but its probably after that that its not.

    Arrgh arrays ....

    When I ouput my memory stream to file, then try to open it Im getting bitmap cannot be rendered - unexpected end of file.

  8. #8
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Named Pipes from External Programs

    Quote Originally Posted by mickle026 View Post
    Thanks you for your response

    I think my main issue here is that both the Byte array and the List of Byte array are two seperate types of Byte array - ie Byte() and Byte()()

    Code:
    Dim B(4096) as Byte
    and

    Code:
    byteList.AddRange(B.Take(L).ToArray)
    Code:
    Unable to cast object of type 'System.Byte[]' to type 'System.Collections.Generic.IEnumerable`1[System.Byte[]]
    And this results in I am unable to push the list of Byte array to the img byte array because one list is Byte()() and the other is Byte()
    I believe you are correct, but you misunderstood what I was saying.
    You switched to a list of byte arrays, because you said you had to add one byte at a time to the List of bytes.
    I said you could use AddRange to add an array of bytes to a List of bytes.
    My example was based on you reverting to a list of byte. not a list of byte arrays.

    I figured that would be easier in the end since you wouldn't have to combine arrays in a list back together, as you do with your list of arrays of byte.

    If you wanted to keep the list of arrays of bytes, then you would just use Add, not AddRange since AddRange is for adding an array of your list type to the list, Add is for adding a single instance of your type to the list.

  9. #9

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    Quote Originally Posted by passel View Post
    I believe you are correct, but you misunderstood what I was saying.
    You switched to a list of byte arrays, because you said you had to add one byte at a time to the List of bytes.
    I said you could use AddRange to add an array of bytes to a List of bytes.
    My example was based on you reverting to a list of byte. not a list of byte arrays.

    I figured that would be easier in the end since you wouldn't have to combine arrays in a list back together, as you do with your list of arrays of byte.

    If you wanted to keep the list of arrays of bytes, then you would just use Add, not AddRange since AddRange is for adding an array of your list type to the list, Add is for adding a single instance of your type to the list.
    Thanks for your help with this.

    It seems that my knowledge is a bit lacking with trying to read from the pipe to an array, im going to go and learn some more over the next few weeks to try and figure this out.
    There seem to be plenty of examples of how to do it with text but I've not found a single one that does what im trying to do with byte output (ie image>to file) from another console application

    I have a feeling that im going to end up using a completely different approach .....

  10. #10
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Named Pipes from External Programs

    But I thought you had it working in Post #4, just slow because you were populating the list of bytes one byte at a time.
    From post #4, working code example:
    Code:
            Dim byteList As New List(Of Byte())
    
            Do
                Dim B(0) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
                byteList.Add(B)
            Loop While ServerPipe.IsConnected
    
            Dim img As Byte()
            'img = byteList.SelectMany(Function(x) x).ToArray()
            img = (From bytes In byteList From x In bytes Select x).ToArray()
    I figured if you just changed that working code to add a range of bytes to the list each time instead of one byte, then you would have faster working code.
    Code:
            Dim byteList As New List(Of Byte())
    
            Do
                Dim B(4095) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, 4096)
                If L > 0 Then
                  ReDim Preserve B(L - 1)
                  byteList.AddRange(B)
                End If
            Loop ' While ServerPipe.IsConnected
    
            Dim img As Byte() = byteList.ToArray()
    I added an If test to check that we actually read something from the stream. I'm not comfortable assuming that every time you read from the stream, that there is data available to be read.
    Last edited by passel; Feb 26th, 2019 at 10:41 AM.

  11. #11

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    It is working with one byte at a time. Its just very slow.

    Because I can't figure out how to add an array of Byte() to a list of byte()(), im pretty much flummoxed with it. Therefore I cannot use addrange without knowing how to change the values of Byte() to Byte()()

    Ill try and figure something out though ... im not giving in just yet

  12. #12
    Super Moderator si_the_geek's Avatar
    Join Date
    Jul 2002
    Location
    Bristol, UK
    Posts
    41,929

    Re: Named Pipes from External Programs

    It may be because I haven't had enough coffee yet, but I think you want just a list of Byte:
    Code:
            Dim byteList As New List(Of Byte)
    (ie: single bytes, not arrays of bytes)

  13. #13
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Named Pipes from External Programs

    I guess you tricked me. You said the code was working in the one post with a list of byte, but you must have already changed the code to be a list of byte arrays and I didn't notice, so the code wasn't the working code.
    I should have copied the code I had on my machine rather than copy from your post. So, Si is correct, and shows the line as I thought I was posting.
    Code:
     Dim byteList As New List(Of Byte)
    
            Do
                Dim B(4095) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, 4096)
                If L > 0 Then
                  ReDim Preserve B(L - 1)
                  byteList.AddRange(B)
                End If
            Loop ' While ServerPipe.IsConnected
    
            Dim img As Byte() = byteList.ToArray()
    I said to revert back to a list of byte rather than a list of byte arrays in multiple posts, and didn't post an example originally of the declaration as I thought you had it correct initially and just needed to revert it. Oh well, live and learn.

  14. #14

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    WOW!

    Fantastic. Thanks to BOTH of you for your input in this thread. I would not have picked up of the () in the list of byte as being the issue

    Code works perfectly - and super fast

    Sorry Passel I didnt mean to trick anyone. I was tinkering and retinkering all the time trying to fathom what the problem was. And all the time from your first input It was a pair of damn brackets. I wouldn't have picked up on that, at least not until I knew more about "List of" arrays.


    Here is the working code for anyone reading or following the thread. I increased the buffer to 4096 bytes.

    Code:
     
       Private Sub StartServerPipe()
            Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
    
            ServerPipe.WaitForConnection()
            ServerPipe.WaitForPipeDrain()
            ' a continual listening loop
    
            Dim byteList As New List(Of Byte)
    
            Do
                Dim B(4095) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
                byteList.AddRange(B)
            Loop While ServerPipe.IsConnected
    
            Dim img As Byte() = byteList.ToArray()
            Dim MS As New MemoryStream(img)
            ' convert the bytestream back to a bitmap
            Dim Bmp As New Bitmap(MS)
            ' cross thread to update the picturebox
            If Me.InvokeRequired Then
                BeginInvoke(Sub()
                                pic1.Image = Bmp
                            End Sub)
            Else
                pic1.Image = Bmp
            End If
            ServerPipe.Close()
        End Sub

    Again - Many thanks

  15. #15
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,335

    Re: Named Pipes from External Programs

    Quote Originally Posted by mickle026 View Post
    Code:
    Dim byteList As New List(Of Byte)
    
    Do
        Dim B(4095) As Byte
        Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
        byteList.AddRange(B)
    Loop While ServerPipe.IsConnected
    )
    That code is not safe. While it seems to work OK with your buffer sized at 4096 bytes, the read method does not guarantee that you will always get back the number of bytes that you request. That's why you should be checking the value of your 'L' variable and only adding 'L' bytes to the byteList when L is less than the size of the read buffer. It's the reason you were getting blocks in your image in post#4: the Read method was occasionally not filling the buffer , but you were adding the entire contents of buffer to the MemoryStream, along with the unused/unfilled/unwanted 0's at the end of the buffer.

    The final Read operation always returns with L = 0 (signifying the end of the stream has been reached), so the code as you have it is adding at least 4096 unexpected/unwanted '0' bytes to the end of the byteList, and probably some more from the previous successful Read operation as it is unlikely to completely fill the buffer (the stream is unlikely to be an exact multiple of the buffer size).

    Passel's code takes this into account with the extra lines:
    Code:
    Do
        Dim B(4095) As Byte
        Dim L As Integer = ServerPipe.Read(B, 0, 4096)
        If L > 0 Then
              ReDim Preserve B(L - 1)
              byteList.AddRange(B)
        End If
    Loop ' While ServerPipe.IsConnected

    If you are using the .NET framework 4.0 or higher, you should be able to make your life easier by using the Stream.CopyTo Method:
    VB>NET Code:
    1. Private Sub StartServerPipe()
    2.     Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
    3.  
    4.     ServerPipe.WaitForConnection()
    5.     ServerPipe.WaitForPipeDrain()
    6.     ' a continual listening loop
    7.  
    8.     Dim MS As New MemoryStream()
    9.  
    10.     ServerPipe.CopyTo(MS)
    11.  
    12.     ' convert the bytestream back to a bitmap
    13.     Dim Bmp As New Bitmap(MS)
    14.  
    15.     ' cross thread to update the picturebox
    16.     If Me.InvokeRequired Then
    17.         BeginInvoke(Sub()
    18.                         pic1.Image = Bmp
    19.                     End Sub)
    20.     Else
    21.         pic1.Image = Bmp
    22.     End If
    23.  
    24.     ServerPipe.Close()
    25. End Sub

    Moving forward, you might want to think about disposing the objects that use unmanaged resources, such as the streams and images. You do close the NamedPipeServerStream at the end of that sub, but you are also creating a MemoryStream and a Bitmap. Disposing of these isn't as straight forward as it seems at first sight: when you create a bitmap from a stream, you are not supposed to close/dispose that stream until you have disposed the Bitmap, but if you dispose the Bitmap while it is still being displayed by the PictureBox, chances are it will cause you grief. If you research this on-line, you'll see that it can be tricky to do it properly.


    Also, if you are using framework 4.6 or newer, you might want to look into using the Async methods to save dealing with background threads and having to handle the cross thread calls when updating the PictureBox.

  16. #16

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    Inferrd - As simple as that!

    VB>NET Code:
    1. Private Sub StartServerPipe()
    2.     Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
    3.  
    4.     ServerPipe.WaitForConnection()
    5.     ServerPipe.WaitForPipeDrain()
    6.     ' a continual listening loop
    7.  
    8.     Dim MS As New MemoryStream()
    9.  
    10.     ServerPipe.CopyTo(MS)
    11.  
    12.     ' convert the bytestream back to a bitmap
    13.     Dim Bmp As New Bitmap(MS)
    14.  
    15.     ' cross thread to update the picturebox
    16.     If Me.InvokeRequired Then
    17.         BeginInvoke(Sub()
    18.                         pic1.Image = Bmp
    19.                     End Sub)
    20.     Else
    21.         pic1.Image = Bmp
    22.     End If
    23.  
    24.     ServerPipe.Close()
    25. End Sub



    Many thanks

    And many thanks to all that have input here, I've learned so much more here than trawling the examples on the net , its been a bit of a brainstorming session, but RESULT !!

    My next attempt is to change the ffmpeg output to a continual stream of bitmaps. The ultimate goal here is to take a stream of bitmaps and show them (or manipulate them, process them). I want to make my own custom security cam detection routine. I know its very advanced for a newbie (well not exactly a newbie, buyt a hobby coder), but its how I learn so after I've got this pipe stuff nailed down, I'll be moving on to bitmaps and fast processing them

    This would produce a continual bitmap output from ffmpeg to my pipe at a constant size and format

    ffmpege.exe -i test.ts -s 720x560 -c:v bmp -y -f image2pipe "\\.\pipe\From_FFmpeg"

    The issue I forsee here is that the pipe will remain open and that there may not be any way to tell where the start of the next bitmap is and when to clear the buffer. Also because the pipe will remain open that it will continually fill the buffer until an overflow occurs.

    I'll probably need to detect the bitmap header bytes "BM6" or 42 4D 36 75 12 00 , the 00 12 75 36 (reversed) being the size of the file in bytes 1,209,654‬, then flush the pipe and continue.

    Off I go a happy chap
    Last edited by mickle026; Feb 28th, 2019 at 11:17 AM.

  17. #17

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    Ok, so I've started - LOL

    Im trying to read the stream for a header and length of the bitmap, flush the pipe, erase the array and do it all again
    Its kind of working but then crashing and saying that I don't have enough memory to allocate for managed something or other.

    On my machine its doing about 10 images or so before running out of memory, any ideas what im not managing or clearing here?

    It crashes and highlights this line byteList.AddRange(B)
    An unhandled exception of type 'System.OutOfMemoryException' occurred in mscorlib.dll


    My code can probably be condesed somewhat too, im not worried about that yet. What I would like is to know what im doing wrong.....again

    Code:
        Private Sub StartServerPipe()
            Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
    
            ServerPipe.WaitForConnection()
            ServerPipe.WaitForPipeDrain()
            ' a continual listening loop
    
            Dim ImageLength As Integer = 0 ' create a reader
            Dim tImageLength As Integer = 0 ' create a holder for imagelength as next pipe read may be zero
            Dim BitmapHeader As Integer = 0 ' create a reader
            Dim tBitmapHeader As Integer = 0 ' create a holder for Bitmap header as next pipe read may be zero
            Dim byteList As New List(Of Byte)
    
            Do
                Dim B(4095) As Byte
                Dim L As Integer = ServerPipe.Read(B, 0, 4096)
                If L > 0 Then
                    ReDim Preserve B(L - 1)
                    byteList.AddRange(B)
    
                    ' 2 byte convertor (i know the header is 3 bytes) (2 bytes header is 19778 in dec)
                    ' really need to do the 3 bytes !
                    BitmapHeader = BitConverter.ToInt16(B, 0)
                    ImageLength = BitConverter.ToInt32(B, 2) ' 4 byte convertor
    
                    If BitmapHeader = 19778 And ImageLength > 0 Then tImageLength = ImageLength
    
                    If byteList.Count >= tImageLength Then
    
                        ' got the length from a pipe read, ie found the bitmap and know how many bytes I need from this read
                        If byteList.Count = tImageLength Then
                            Dim img As Byte() = byteList.ToArray()
                            Dim MS As New MemoryStream(img)
                            Dim Bmp As New Bitmap(MS)
    
                            ' temporarily dump the data to a file to help with debugging
                            'Dim fname As String = Application.StartupPath + "\ByteData.bmp"
                            'System.IO.File.WriteAllBytes(fname, img)
                            ' convert the bytestream back to a bitmap
    
                            ' cross thread to update the picturebox
                            If Me.InvokeRequired Then
                                BeginInvoke(Sub()
                                                pic1.Image = Bmp
                                            End Sub)
                            Else
                                pic1.Image = Bmp
                            End If
    
                            ' reset/flush everything
                            byteList.Clear()            < ------------- Clearing the bytelist here
                            ServerPipe.Flush()
                            MS.flush()
                            ' leave disposing of the bitmap until I know how to do it properly - bmp.dispose() crashes  as suggested it probably would.
                            ' the bmp should dispose once the if...end is left anyway as its an enclosing block - not sure though ....... need to check this
                            L = 0
                            ImageLength = 0
                            tImageLength = 0
                            BitmapHeader = 0
                            tBitmapHeader = 0
                        End If
                    End If
                End If
            Loop While ServerPipe.IsConnected
    
    
    
            ServerPipe.Close()
        End Sub
    Last edited by mickle026; Feb 28th, 2019 at 11:21 AM.

  18. #18
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,335

    Re: Named Pipes from External Programs

    Each time you fetch a frame you are doing hundreds of Reads.

    Each time you do a read, you check the start of the read buffer for the presence of the Bitmap header.

    Chances are that, part way through reading a frame, you are coincidentally finding the BM part of the header and that it is followed by 4 bytes that represent a huge value (anything up to 2 Billion ; or down to -2 billion).

    Unfortunately the following line of code will then reset the size of the image you are searching for to that huge value, and you continue looping/reading/adding to byteList until you run out of memory:
    Code:
    '   found 'BM' part way through frame data (not at start)
    '   sets tImageLength to very large value
    If BitmapHeader = 19778 And ImageLength > 0 Then tImageLength = ImageLength
    
    '   byteList can never grow to be that large
    '   before we eventually run out of memory
    If byteList.Count >= tImageLength Then
    You need to revamp your code so you only read the header once per frame, work out how many bytes you need to fetch for this frame and then read in a separate loop until you have the required number of bytes. Then repeat for the next frame and so on.

    The code for reading exact amounts of data from streams is pretty much boilerplate, so you should be able to find plenty of examples (more so for file or network streams :-).
    Last edited by Inferrd; Feb 28th, 2019 at 01:01 PM.

  19. #19
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,335

    Re: Named Pipes from External Programs

    Here's a quick example of how to read a specific number of bytes from a stream.
    There's plenty of room for improvement.
    It will fail if the "image" size is less than the buffer length, but it should point you in the right direction:
    VB.NET Code:
    1. Private Sub StartServerPipe()
    2.     Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
    3.  
    4.     ServerPipe.WaitForConnection()
    5.     ServerPipe.WaitForPipeDrain()
    6.     ' a continual listening loop
    7.  
    8.     Dim ImageLength As Integer = 0 ' create a reader
    9.     Dim BitmapHeader As Integer = 0 ' create a reader
    10.     Dim byteList As New List(Of Byte)
    11.  
    12.  
    13.     Do
    14.         Dim B(4095) As Byte
    15.         Dim L As Integer = ServerPipe.Read(B, 0, B.Length)
    16.         If L > 0 Then
    17.             ReDim Preserve B(L - 1)
    18.             byteList.AddRange(B)
    19.         End If
    20.  
    21.         ' 2 byte convertor (i know the header is 3 bytes) (2 bytes header is 19778 in dec)
    22.         ' really need to do the 3 bytes !
    23.         BitmapHeader = BitConverter.ToInt16(B, 0)
    24.         ImageLength = BitConverter.ToInt32(B, 2) ' 4 byte convertor
    25.  
    26.         If BitmapHeader = 19778 Then
    27.             Dim remaining As Integer = ImageLength - byteList.Count
    28.             Do While remaining > 0
    29.                 L = ServerPipe.Read(B, 0, Math.Min(B.Length, remaining))
    30.                 If L > 0 Then
    31.                     ReDim Preserve B(L - 1)
    32.                     byteList.AddRange(B)
    33.                 End If
    34.                 remaining = ImageLength - byteList.Count
    35.             Loop
    36.  
    37.  
    38.             Dim img As Byte() = byteList.ToArray()
    39.             Dim MS As New MemoryStream(img)
    40.             Dim Bmp As New Bitmap(MS)
    41.  
    42.             ' temporarily dump the data to a file to help with debugging
    43.             'Dim fname As String = Application.StartupPath + "\ByteData.bmp"
    44.             'System.IO.File.WriteAllBytes(fname, img)
    45.             ' convert the bytestream back to a bitmap
    46.  
    47.             ' cross thread to update the picturebox
    48.             If Me.InvokeRequired Then
    49.                 BeginInvoke(Sub()
    50.                                 Pic1.Image = Bmp
    51.                             End Sub)
    52.             Else
    53.                 Pic1.Image = Bmp
    54.             End If
    55.  
    56.             ' reset/flush everything
    57.             byteList.Clear()         '   < ------------- Clearing the bytelist here
    58.  
    59.         Else
    60.             'output is not a valid Bitmap!
    61.             'Throw New CockupException
    62.         End If
    63.  
    64.     Loop While ServerPipe.IsConnected
    65.  
    66.     ServerPipe.Close()
    67. End Sub

    Note I've removed the ServerPipe.Flush() and MS.flush() statements as (as far as I know) they don't do anything for those types of Stream (but you should check the documentation yourself ;-)
    Last edited by Inferrd; Feb 28th, 2019 at 01:02 PM.

  20. #20

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    I've learn't a lot from this thread. Thanks so much - Also learnt how to colourise the code from your last post

    I changed the bitmap identifier to read the three bytes instead of an integer from 2 bytes

    VB.NET Code:
    1. Private Sub StartServerPipe()
    2.         Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
    3.  
    4.         ServerPipe.WaitForConnection()
    5.         ServerPipe.WaitForPipeDrain()
    6.         ' a continual listening loop
    7.  
    8.         Dim ImageLength As Integer = 0 ' create a reader
    9.         Dim byteList As New List(Of Byte)
    10.  
    11.         Do
    12.             Dim B(4095) As Byte
    13.             Dim L As Integer = ServerPipe.Read(B, 0, 4096)
    14.             If L > 0 Then
    15.                 ReDim Preserve B(L - 1)
    16.                 byteList.AddRange(B)
    17.  
    18.                 ' read the three byte header to check if its a BM6 type bitmap
    19.                If B(2) = &H36 AndAlso B(1) = &H4D AndAlso B(0) = &H42 Then
    20.                     ImageLength = BitConverter.ToInt32(B, 2)
    21.                     Dim remaining As Integer = ImageLength - byteList.Count
    22.                     Do While remaining > 0
    23.                         L = ServerPipe.Read(B, 0, Math.Min(B.Length, remaining))
    24.                         If L > 0 Then
    25.                             ReDim Preserve B(L - 1)
    26.                             byteList.AddRange(B)
    27.                         End If
    28.                         remaining = ImageLength - byteList.Count
    29.                     Loop
    30.  
    31.                     Dim img As Byte() = byteList.ToArray()
    32.                     Dim MS As New MemoryStream(img)
    33.                     Dim Bmp As New Bitmap(MS)
    34.  
    35.                     ' cross thread to update the picturebox
    36.                     If Me.InvokeRequired Then
    37.                         BeginInvoke(Sub()
    38.                                         pic1.Image = Bmp
    39.                                     End Sub)
    40.                     Else
    41.                         pic1.Image = Bmp
    42.                     End If
    43.  
    44.                     ' reset/flush the byte list array
    45.                     byteList.Clear()
    46.  
    47.                 Else
    48.                     'output is not a valid Bitmap!
    49.                     'Throw New CockupException
    50.                 End If
    51.  
    52.             End If
    53.  
    54.         Loop While ServerPipe.IsConnected
    55.  
    56.         ServerPipe.Close()
    57.     End Sub

    Heres a video showing it working, the test video is little house on the praire recording ( I dont care what the video is, its working!!)

    https://drive.google.com/open?id=1YO...hNzqe1y_UlG8Q5


    Again super advice, many thanks

  21. #21
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,335

    Re: Named Pipes from External Programs

    Aaaahh. Little house on the prairie. Was must see TV, back in the day :-)

    About 6 months ago, I actually had a need to do something similar to what you're doing here, but couldn't suss out how to create the images without saving each frame to individual files on disk. So this thread has been quite helpful to me too.


    Regarding the Bitmap header: the field that identifies the image as a Bitmap is only 2 bytes wide, not 3. In this case, the 2 bytes should resolve to the ASCII characters "BM". The "6" you are seeing is part of the length field (the 4 bytes starting at offset 2).

    If you are using the FFmpeg command you gave in post#16 :
    (ffmpege.exe -i test.ts -s 720x560 -c:v bmp -y -f image2pipe "\\.\pipe\From_FFmpeg")
    then FFmpeg seems to spit out a 720x560, 24bit bitmap, with a standard header and also a BITMAPINFOHEADER type of DIB header, and no extra colour information.

    So every image produced by that command would have the same length of 1,209,654 which is stored in the header as 36 75 12 00 (in hex). The 36 is just coincidentally "6" in ASCII, so the "6" is part of the 4 byte length field, not the 2 byte ID field.

    That said, for the given command in this case, every image could equally be identified by all of the 6 consecutive bytes 42 4D 36 75 12 00. However, the last 4 of those bytes will almost certainly change if you change the parameters you pass to FFmpeg (so don't rely on the '36' to identify the start of the Bitmap).

  22. #22

    Thread Starter
    Member
    Join Date
    Oct 2007
    Posts
    63

    Re: Named Pipes from External Programs

    Quote Originally Posted by Inferrd View Post
    Aaaahh. Little house on the prairie. Was must see TV, back in the day :-)

    About 6 months ago, I actually had a need to do something similar to what you're doing here, but couldn't suss out how to create the images without saving each frame to individual files on disk. So this thread has been quite helpful to me too.


    Regarding the Bitmap header: the field that identifies the image as a Bitmap is only 2 bytes wide, not 3. In this case, the 2 bytes should resolve to the ASCII characters "BM". The "6" you are seeing is part of the length field (the 4 bytes starting at offset 2).

    If you are using the FFmpeg command you gave in post#16 :
    (ffmpege.exe -i test.ts -s 720x560 -c:v bmp -y -f image2pipe "\\.\pipe\From_FFmpeg")
    then FFmpeg seems to spit out a 720x560, 24bit bitmap, with a standard header and also a BITMAPINFOHEADER type of DIB header, and no extra colour information.

    So every image produced by that command would have the same length of 1,209,654 which is stored in the header as 36 75 12 00 (in hex). The 36 is just coincidentally "6" in ASCII, so the "6" is part of the 4 byte length field, not the 2 byte ID field.

    That said, for the given command in this case, every image could equally be identified by all of the 6 consecutive bytes 42 4D 36 75 12 00. However, the last 4 of those bytes will almost certainly change if you change the parameters you pass to FFmpeg (so don't rely on the '36' to identify the start of the Bitmap).
    Duh, silly me!


    This can stay as an integer or be in bytes then... forgive me, It was about 4 am when I responded and just done a full shift at work though! (the time setting here says not though, but I was still at work then)

    VB.NET Code:
    1. If B(1) = &H4D AndAlso B(0) = &H42 Then

    Im glad this will help you too.

    I myself am really good at obscure stuff and working out problems, but when it comes down to what should be easy . . it often throws me a curve ball so to speak - lol, that I believe comes down to zero formal training since university 20+ years ago, and that was Verliog (VHDL) - a different language entirely plus i've never took a job afterwards that involved any kind of programming. Thats why I could figure out how to connect the pipe but struggled with the array.

    Anyway ... I think this thread judging on the views its had must be helpful to many

    Many thanks

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