-
Feb 24th, 2019, 02:42 PM
#1
Thread Starter
Member
[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
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.
-
Feb 24th, 2019, 04:25 PM
#2
Thread Starter
Member
Re: Named Pipes from External Programs
Originally Posted by mickle026
* 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
-
Feb 24th, 2019, 05:32 PM
#3
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.
-
Feb 24th, 2019, 07:07 PM
#4
Thread Starter
Member
Re: Named Pipes from External Programs
Originally Posted by passel
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
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.
-
Feb 24th, 2019, 08:58 PM
#5
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.
-
Feb 25th, 2019, 10:03 AM
#6
Thread Starter
Member
Re: Named Pipes from External Programs
Originally Posted by passel
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 .....
-
Feb 25th, 2019, 11:04 AM
#7
Thread Starter
Member
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.
-
Feb 25th, 2019, 07:00 PM
#8
Re: Named Pipes from External Programs
Originally Posted by mickle026
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.
-
Feb 26th, 2019, 10:11 AM
#9
Thread Starter
Member
Re: Named Pipes from External Programs
Originally Posted by passel
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 .....
-
Feb 26th, 2019, 10:38 AM
#10
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.
-
Feb 27th, 2019, 12:40 AM
#11
Thread Starter
Member
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
-
Feb 27th, 2019, 04:29 AM
#12
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)
-
Feb 27th, 2019, 08:35 PM
#13
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.
-
Feb 27th, 2019, 11:57 PM
#14
Thread Starter
Member
Re: Named Pipes from External Programs
-
Feb 28th, 2019, 08:07 AM
#15
Re: Named Pipes from External Programs
Originally Posted by mickle026
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:
Private Sub StartServerPipe() Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg") ServerPipe.WaitForConnection() ServerPipe.WaitForPipeDrain() ' a continual listening loop Dim MS As New MemoryStream() ServerPipe.CopyTo(MS) ' 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
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.
-
Feb 28th, 2019, 09:54 AM
#16
Thread Starter
Member
Re: Named Pipes from External Programs
Inferrd - As simple as that!
VB>NET Code:
Private Sub StartServerPipe() Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg") ServerPipe.WaitForConnection() ServerPipe.WaitForPipeDrain() ' a continual listening loop Dim MS As New MemoryStream() ServerPipe.CopyTo(MS) ' 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
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.
-
Feb 28th, 2019, 10:43 AM
#17
Thread Starter
Member
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.
-
Feb 28th, 2019, 12:31 PM
#18
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.
-
Feb 28th, 2019, 12:57 PM
#19
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:
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 BitmapHeader As Integer = 0 ' create a reader Dim byteList As New List(Of Byte) Do Dim B(4095) As Byte Dim L As Integer = ServerPipe.Read(B, 0, B.Length) If L > 0 Then ReDim Preserve B(L - 1) byteList.AddRange(B) End If ' 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 Then Dim remaining As Integer = ImageLength - byteList.Count Do While remaining > 0 L = ServerPipe.Read(B, 0, Math.Min(B.Length, remaining)) If L > 0 Then ReDim Preserve B(L - 1) byteList.AddRange(B) End If remaining = ImageLength - byteList.Count Loop 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 Else 'output is not a valid Bitmap! 'Throw New CockupException End If Loop While ServerPipe.IsConnected ServerPipe.Close() 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.
-
Feb 28th, 2019, 11:12 PM
#20
Thread Starter
Member
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:
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 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)
' read the three byte header to check if its a BM6 type bitmap
If B(2) = &H36 AndAlso B(1) = &H4D AndAlso B(0) = &H42 Then
ImageLength = BitConverter.ToInt32(B, 2)
Dim remaining As Integer = ImageLength - byteList.Count
Do While remaining > 0
L = ServerPipe.Read(B, 0, Math.Min(B.Length, remaining))
If L > 0 Then
ReDim Preserve B(L - 1)
byteList.AddRange(B)
End If
remaining = ImageLength - byteList.Count
Loop
Dim img As Byte() = byteList.ToArray()
Dim MS As New MemoryStream(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
' reset/flush the byte list array
byteList.Clear()
Else
'output is not a valid Bitmap!
'Throw New CockupException
End If
End If
Loop While ServerPipe.IsConnected
ServerPipe.Close()
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
-
Mar 1st, 2019, 06:55 AM
#21
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).
-
Mar 1st, 2019, 09:51 AM
#22
Thread Starter
Member
Re: Named Pipes from External Programs
Originally Posted by Inferrd
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:
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|