From 968faf92008555f580fff38c2628d3941f41f0d4 Mon Sep 17 00:00:00 2001 From: Milan Hanajik Date: Thu, 14 Sep 2017 11:54:32 +0200 Subject: [PATCH] CLP1611.Camera live image using MJpeg RTP protocol support implemented. --- .../Network/Camera/CLP1611/Camera.cs | 99 ++++- .../Network/Camera/CLP1611/MJpegFrame.cs | 382 ++++++++++++++++++ .../Network/Camera/CLP1611/RtpFrame.cs | 65 +++ .../Network/Camera/CLP1611/RtpPacket.cs | 47 +++ .../Screens/PicturesTabPageCtrl.Designer.cs | 48 +-- .../Screens/PicturesTabPageCtrl.cs | 31 +- .../Screens/PicturesTabPageCtrl.resx | 16 +- .../Screens/ResultsTabPageCtrl.cs | 15 +- TestBenchFramework/TBF.csproj | 5 +- TestBenchFramework/UiBridge/Bridge.cs | 20 +- TestBenchFramework/UiBridge/ImageEventArgs.cs | 20 + .../UiBridge/ImageUpdatedEventArgs.cs | 20 - 12 files changed, 682 insertions(+), 86 deletions(-) create mode 100644 TestBenchFramework/BenchControl/Network/Camera/CLP1611/MJpegFrame.cs create mode 100644 TestBenchFramework/BenchControl/Network/Camera/CLP1611/RtpFrame.cs create mode 100644 TestBenchFramework/BenchControl/Network/Camera/CLP1611/RtpPacket.cs create mode 100644 TestBenchFramework/UiBridge/ImageEventArgs.cs delete mode 100644 TestBenchFramework/UiBridge/ImageUpdatedEventArgs.cs diff --git a/TestBenchFramework/BenchControl/Network/Camera/CLP1611/Camera.cs b/TestBenchFramework/BenchControl/Network/Camera/CLP1611/Camera.cs index 3382bbc2e..f14da0fa8 100644 --- a/TestBenchFramework/BenchControl/Network/Camera/CLP1611/Camera.cs +++ b/TestBenchFramework/BenchControl/Network/Camera/CLP1611/Camera.cs @@ -61,6 +61,13 @@ namespace TBF.BenchControl.Network.Camera.CLP1611 public bool Running { get { return running; } } bool running; + public int CameraIdx { get { return cameraIdx; } } + int cameraIdx; + /// + static int nextCameraIdx = 0; + static int GetCameraIdx() { return nextCameraIdx++; } /// Used in Initialize() + public static new void ResetStaticProperties() { nextCameraIdx = 0; } + /// /// Telnet support /// @@ -76,7 +83,7 @@ namespace TBF.BenchControl.Network.Camera.CLP1611 PasswordConnectionInfo connectionInfo; ScpClient scpClient; bool scpConnected; - bool closeScpThread; + bool stopScpThreadFlag; string scpSrcFileName; string scpDstFileName; enum ScpCommand @@ -86,6 +93,14 @@ namespace TBF.BenchControl.Network.Camera.CLP1611 } ScpCommand scpCommand; + /// + /// MJpeg RTP protocol support + /// + const int RtpUdpPortBase = 20000; + int rtpUdpLocalPort; + UdpClient rtpUdpClient; + Thread rtpListenerThread; + static bool stopRtpListenerFlag; /// /// ROI related parameters @@ -154,6 +169,8 @@ namespace TBF.BenchControl.Network.Camera.CLP1611 public void Initialize() { + cameraIdx = GetCameraIdx(); + if (CameraCfg.DebugLevel == DebugMode.Simulate) return; /// Detect a camera @@ -203,6 +220,9 @@ namespace TBF.BenchControl.Network.Camera.CLP1611 scpThread = new Thread(new ThreadStart(ScpWorker)); scpThread.Start(); + rtpUdpLocalPort = RtpUdpPortBase + 2 * cameraIdx; + StartRtpListener(NetAdapter.IPAddress, rtpUdpLocalPort); + running = true; log.FatalFormat("'{0}' with address {1} detected, IP address = {2}", Name, CameraCfg.HardwareAddress, ipAddress); @@ -215,7 +235,8 @@ namespace TBF.BenchControl.Network.Camera.CLP1611 public void StopDevice() { - closeScpThread = true; + stopScpThreadFlag = true; + stopRtpListenerFlag = true; if (Telnet != null) { @@ -224,6 +245,13 @@ namespace TBF.BenchControl.Network.Camera.CLP1611 } if (scpThread != null) scpThread.Join(500); + if (rtpListenerThread != null) rtpListenerThread.Join(1000); + + if (rtpUdpClient != null) + { + rtpUdpClient.Close(); + rtpUdpClient = null; + } running = false; } @@ -371,7 +399,7 @@ namespace TBF.BenchControl.Network.Camera.CLP1611 if (scpConnected) { - while (!closeScpThread) + while (!stopScpThreadFlag) { switch (scpCommand) { @@ -410,6 +438,71 @@ namespace TBF.BenchControl.Network.Camera.CLP1611 + private void StartRtpListener(IPAddress localIP, int localPort) + { + rtpUdpClient = new UdpClient(new IPEndPoint(localIP, localPort)); + + rtpListenerThread = new Thread(new ThreadStart(RtpListener)); + rtpListenerThread.Start(); + } + + void RtpListener() + { + JpegFrame jpegFrame = new JpegFrame(); + bool synced = false; + + while (!stopRtpListenerFlag) + { + try + { + IPEndPoint endpoint = null; + Byte[] data = rtpUdpClient.Receive(ref endpoint); + + if (data.Length > 20) + { + /// Process the packet + RtpPacket newPacket = new RtpPacket(data); + if (synced) + { + /// Sychronized: process the new packet + switch (jpegFrame.AddPacket(newPacket)) + { + case RtpFrame.STATE.Complete: + UiBridge.Bridge.OnImage(this.cameraIdx, jpegFrame.ToImage()); + jpegFrame.Clear(); + break; + case RtpFrame.STATE.Error: + jpegFrame.Clear(); + synced = false; + break; + default: + break; + } + } + else if (newPacket.Mark) + { + /// Synchronize when a packet with Mark occurs, process the next packet + jpegFrame.Clear(); + synced = true; + } + else + { + /// Stay unsynchronized + } + } + } + catch (Exception exc) + { + string msg = string.Format("Exception in the listener thread: {0}", exc.Message); + log.Error(msg); + } + } + + return; + } + + + public IOperation LiveStreamOp(bool hiRes) { return new LiveStreamOp(this, Telnet, hiRes ? "clp/hrlivestream.sh" : "clp/livestream.sh"); diff --git a/TestBenchFramework/BenchControl/Network/Camera/CLP1611/MJpegFrame.cs b/TestBenchFramework/BenchControl/Network/Camera/CLP1611/MJpegFrame.cs new file mode 100644 index 000000000..864869c83 --- /dev/null +++ b/TestBenchFramework/BenchControl/Network/Camera/CLP1611/MJpegFrame.cs @@ -0,0 +1,382 @@ +using System; +using System.Collections.Generic; +using log4net; + +namespace TBF.BenchControl.Network.Camera +{ + /// + /// Implements RFC2435 + /// + public class JpegFrame : RtpFrame + { + static readonly ILog log = LogManager.GetLogger(typeof(MainWnd)); + + #region Statics + + static byte[] CreateStartOfInformation() + { + return new byte[] { 0xff, 0xd8 }; + } + + static byte[] CreateJFIFHeader(uint type, uint width, uint height, ArraySegment tables, uint dri) + { + List result = new List(); + result.AddRange(CreateStartOfInformation()); + + result.Add(0xff); + result.Add(0xe0);//AppFirst + result.Add(0x00); + result.Add(0x10);//length + result.Add((byte)'J'); + result.Add((byte)'F'); + result.Add((byte)'I'); + result.Add((byte)'F'); + result.Add(0x00); + + result.Add(0x01);//Version Major + result.Add(0x01);//Version Minor + + result.Add(0x00);//Units + + result.Add(0x00);//Horizontal + result.Add(0x01); + + result.Add(0x00);//Vertical + result.Add(0x01); + + result.Add(0x00);//No thumb + result.Add(0x00); + + if (dri > 0) + { + result.AddRange(CreateDataRestartIntervalMarker(dri)); + } + + result.AddRange(CreateQuantizationTablesMarker(tables)); + + result.Add(0xff); + result.Add(0xc0);//SOF + result.Add(0x00); + result.Add(0x11); + result.Add(0x08); + result.Add((byte)(height >> 8)); + result.Add((byte)height); + result.Add((byte)(width >> 8)); + result.Add((byte)width); + + result.Add(0x03); + result.Add(0x01); + result.Add((byte)(type > 0 ? 0x22 : 0x21)); + result.Add(0x00); + + result.Add(0x02); + result.Add(0x11); + result.Add(0x01); + + result.Add(0x03); + result.Add(0x11); + result.Add(0x01); + + //Huffman Tables + result.AddRange(CreateHuffmanTableMarker(lum_dc_codelens, lum_dc_symbols, 0, 0)); + result.AddRange(CreateHuffmanTableMarker(lum_ac_codelens, lum_ac_symbols, 0, 1)); + result.AddRange(CreateHuffmanTableMarker(chm_dc_codelens, chm_dc_symbols, 1, 0)); + result.AddRange(CreateHuffmanTableMarker(chm_ac_codelens, chm_ac_symbols, 1, 1)); + + result.Add(0xff); + result.Add(0xda);//Marker SOS + result.Add(0x00); + result.Add(0x0c); + result.Add(0x03); + result.Add(0x01); + result.Add(0x00); + result.Add(0x02); + result.Add(0x11); + result.Add(0x03); + result.Add(0x11); + result.Add(0x00); + result.Add(0x3f); + result.Add(0x00); + + return result.ToArray(); + } + + // The default 'luma' and 'chroma' quantizer tables, in zigzag order: + static byte[] defaultQuantizers = new byte[] + { + // luma table: + 16, 11, 12, 14, 12, 10, 16, 14, + 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, + 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, + 87, 69, 55, 56, 80, 109, 81, 87, + 95, 98, 103, 104, 103, 62, 77, 113, + 121, 112, 100, 120, 92, 101, 103, 99, + // chroma table: + 17, 18, 18, 24, 21, 24, 47, 26, + 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + }; + + /// + /// Creates a Luma and Chroma Table using the default quantizer + /// + /// The quality + /// 64 luma bytes and 64 chroma + static byte[] CreateQuantizationTables(uint Q) + { + int factor = (int)Q; + int q; + + if (Q < 1) factor = 1; + else if (Q > 99) factor = 99; + + if (Q < 50) + { + q = 5000 / factor; + } + else + { + q = 200 - factor * 2; + } + byte[] resultTables = new byte[128]; + for (int i = 0; i < 128; ++i) + { + int newVal = (defaultQuantizers[i] * q + 50) / 100; + if (newVal < 1) newVal = 1; + else if (newVal > 255) newVal = 255; + resultTables[i] = (byte)newVal; + } + return resultTables; + } + + static byte[] CreateQuantizationTablesMarker(ArraySegment tables) + { + List result = new List(); + + int tableSize = tables.Count / 2; + + //Luma + + result.Add(0xff); + result.Add(0xdb); + + result.Add(0x00); + result.Add((byte)(tableSize + 3)); + result.Add(0x00); + + for (int i = 0, e = tableSize; i < e; ++i) + { + result.Add(tables.Array[tables.Offset + i]); + } + + //Chroma + + result.Add(0xff); + result.Add(0xdb); + + result.Add(0x00); + result.Add((byte)(tableSize + 3)); + result.Add(0x01); + + for (int i = tableSize, e = tables.Count; i < e; ++i) + { + result.Add(tables.Array[tables.Offset + i]); + } + + return result.ToArray(); + + } + + static byte[] lum_dc_codelens = { 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; + + static byte[] lum_dc_symbols = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static byte[] lum_ac_codelens = { 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d, }; + + static byte[] lum_ac_symbols = { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, + }; + + static byte[] chm_dc_codelens = { 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; + + static byte[] chm_dc_symbols = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static byte[] chm_ac_codelens = { 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; + + static byte[] chm_ac_symbols = { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, + }; + + static byte[] CreateHuffmanTableMarker(byte[] codeLens, byte[] symbols, int tableNo, int tableClass) + { + List result = new List(); + result.Add(0xff); + result.Add(0xc4); + result.Add(0x00); + result.Add((byte)(3 + codeLens.Length + symbols.Length)); + result.Add((byte)((tableClass << 4) | tableNo)); + result.AddRange(codeLens); + result.AddRange(symbols); + return result.ToArray(); + } + + static byte[] CreateDataRestartIntervalMarker(uint dri) + { + return new byte[] { 0xff, 0xdd, 0x00, 0x04, (byte)(dri >> 8), (byte)(dri) }; + } + + #endregion + + public JpegFrame() : base(26) { } + + /// + /// //Writes the packets to a memory stream and created the default header and quantization tables if necessary. + /// + void ProcessPackets(System.IO.MemoryStream buffer) + { + ArraySegment tables = default(ArraySegment); + uint FragmentOffset, TypeSpecific, Type, Quality, Width, Height, DataRestartInterval = 0; + + for (int i = 0, e = this.Packets.Count, last = e - 1; i < e; ++i) + { + int offset = 0; + + RtpPacket packet = this.Packets[i]; + + TypeSpecific = (uint)(packet.Payload[offset++]); + + //FragmentOffset = (uint)(packet.Payload[offset++] | packet.Payload[offset++] << 8 | packet.Payload[offset++] << 16); + FragmentOffset = (uint)(packet.Payload[offset++] << 16 | packet.Payload[offset++] << 8 | packet.Payload[offset++]); + + //Ensure start packet + if (i == 0 && FragmentOffset != 0) continue; + + Type = (uint)(packet.Payload[offset++]); + Quality = (uint)packet.Payload[offset++]; + Width = (uint)(packet.Payload[offset++] * 8); + Height = (uint)(packet.Payload[offset++] * 8); + + //Only occur in the first packet + if (FragmentOffset == 0) + { + //If There is already a StartOfInformation present just copy + if (packet.Payload[offset] == 0xff && packet.Payload[offset + 1] == 0xd8) + { + goto WritePacket; + } + + //Get the restart interval is present + if (Type > 63) + { + DataRestartInterval = (uint)(packet.Payload[offset++] << 8 | packet.Payload[offset++]); + } + + //Get the Q Tables if present + if (Quality > 127) + { + uint MBZ = (uint)(packet.Payload[offset++]);//Should only proceed if MBZ > 0? + uint Precision = (uint)(packet.Payload[offset++]);//Wasted read + uint Length = (uint)(packet.Payload[offset++] << 8 | packet.Payload[offset++]); + if(Length > 0) + { + tables = new ArraySegment(packet.Payload, offset, (int)Length); + offset += (int)Length; + } + else + { + tables = new ArraySegment(CreateQuantizationTables(Quality)); + } + } + + //Write the header to the buffer + byte[] header = CreateJFIFHeader(Type, Width, Height, tables, DataRestartInterval); + buffer.Write(header, 0, header.Length); + + } + + WritePacket: //Copy rest of data to buffer + buffer.Write(packet.Payload, offset, packet.Payload.Length - offset); + + //Check to see if EOI is present or needs to be added on the last packet + if (i == last && packet.Payload[packet.Payload.Length - 2] != 0xff && packet.Payload[packet.Payload.Length - 1] != 0xd9) + { + buffer.WriteByte(0xff); + buffer.WriteByte(0xd9); + } + + } + + //Go back to the beginning + buffer.Seek(0, System.IO.SeekOrigin.Begin); + } + + + /// + /// Creates a image from the processed packets in the memory stream + /// + /// The image created from the packets + public System.Drawing.Image ToImage() + { + try + { + using (System.IO.MemoryStream buffer = new System.IO.MemoryStream()) + { + ProcessPackets(buffer); + return System.Drawing.Image.FromStream(buffer, false, true); + } + } + catch (Exception exc) + { + log.ErrorFormat("Exception in ToImage(): {0}", exc.Message); + return null; + } + } + } +} diff --git a/TestBenchFramework/BenchControl/Network/Camera/CLP1611/RtpFrame.cs b/TestBenchFramework/BenchControl/Network/Camera/CLP1611/RtpFrame.cs new file mode 100644 index 000000000..f08abd2bf --- /dev/null +++ b/TestBenchFramework/BenchControl/Network/Camera/CLP1611/RtpFrame.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TBF.BenchControl.Network.Camera +{ + public class RtpFrame + { + public enum STATE + { + Empty, + OK, + Error, + Complete, + } + + public readonly int PayloadType; + public IList Packets; + public STATE State { get { return state; } } + STATE state; + + + public RtpFrame(int payloadType) + { + PayloadType = payloadType; + Packets = new List(); + state = STATE.Empty; + } + + public STATE AddPacket(RtpPacket packet) + { + if (state == STATE.Error || packet.PayloadType != PayloadType) + { + return (state = STATE.Error); /// Already in error or wrong payload type + } + else if (state == STATE.Complete) + { + return STATE.Complete; /// Already complete + } + else if (state != STATE.Empty && packet.SequenceNumber != Packets.Last().SequenceNumber + 1) + { + return (state = STATE.Error); /// Packet out of order + } + + /// Going to add a new packet previous ones + Packets.Add(packet); + + if (packet.Mark) + { + return (state = STATE.Complete); /// Complete now + } + else + { + return (state = STATE.OK); /// OK, not complete yet + } + } + + public void Clear() + { + Packets.Clear(); + state = STATE.Empty; + } + } +} diff --git a/TestBenchFramework/BenchControl/Network/Camera/CLP1611/RtpPacket.cs b/TestBenchFramework/BenchControl/Network/Camera/CLP1611/RtpPacket.cs new file mode 100644 index 000000000..8f7ae2a69 --- /dev/null +++ b/TestBenchFramework/BenchControl/Network/Camera/CLP1611/RtpPacket.cs @@ -0,0 +1,47 @@ +using System; + +namespace TBF.BenchControl.Network.Camera +{ + public class RtpPacket + { + public readonly int Version; + public readonly bool P; + public readonly bool X; + public readonly int CC; + public readonly bool Mark; + public readonly int PayloadType; + public readonly int SequenceNumber; + public readonly int Timestamp; + public readonly int SsrcId; + public readonly Byte[] Payload; /// Data without RTP header + + public RtpPacket(Byte[] data) + { + if (data.Length < 12) throw new Exception("Packet too short"); + + Version = (data[0] >> 6) & 0x02; /// 1 RTP Version + P = ((data[0] & 0x20) != 0); /// 2 RTP P + X = ((data[0] & 0x10) != 0); /// 3 RTP X + CC = (data[0] & 0x0F); /// 4 RTP CC + Mark = ((data[1] & 0x80) != 0); /// 5 RTP M + PayloadType = (data[1] & 0x7F); /// 6 RTP PT + SequenceNumber = data[2] * 256 + data[3]; /// 7 RTP Seq# + Timestamp = ((data[4] * 256 + data[5]) * 256 + data[6]) * 256 + data[7]; /// 8 RTP Timestamp + SsrcId = ((data[8] * 256 + data[9]) * 256 + data[10]) * 256 + data[11]; /// 9 RTP SSRC Id + + int ExtensionHeaderSize = 0; + if (X && data.Length >= 16 + 4*CC) + { + ExtensionHeaderSize = data[14 + 4*CC] * 256 + data[15 + 4*CC] + 4; + } + + int payloadDataOffset = 12 + 4 * CC + ExtensionHeaderSize; + int payloadDataCount = data.Length - 12 - 4 * CC - ExtensionHeaderSize; + + if (payloadDataCount < 0) throw new Exception("Invalid packet"); + + Payload = new byte[payloadDataCount]; + Array.Copy(data, payloadDataOffset, Payload, 0, payloadDataCount); + } + } +} diff --git a/TestBenchFramework/Screens/PicturesTabPageCtrl.Designer.cs b/TestBenchFramework/Screens/PicturesTabPageCtrl.Designer.cs index 4311ae60e..b6a5e8c7d 100644 --- a/TestBenchFramework/Screens/PicturesTabPageCtrl.Designer.cs +++ b/TestBenchFramework/Screens/PicturesTabPageCtrl.Designer.cs @@ -34,6 +34,9 @@ namespace TBF.Screens System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PicturesTabPageCtrl)); this.logTextBox = new System.Windows.Forms.TextBox(); this.splitContainer = new System.Windows.Forms.SplitContainer(); + this.focusButton = new System.Windows.Forms.Button(); + this.liveButton = new System.Windows.Forms.Button(); + this.grabButton = new System.Windows.Forms.Button(); this.configureButton = new System.Windows.Forms.Button(); this.cameraPictureCtrl6 = new TBF.UiControls.CameraPictureCtrl(); this.cameraPictureCtrl5 = new TBF.UiControls.CameraPictureCtrl(); @@ -41,9 +44,6 @@ namespace TBF.Screens this.cameraPictureCtrl3 = new TBF.UiControls.CameraPictureCtrl(); this.cameraPictureCtrl2 = new TBF.UiControls.CameraPictureCtrl(); this.cameraPictureCtrl1 = new TBF.UiControls.CameraPictureCtrl(); - this.grabButton = new System.Windows.Forms.Button(); - this.liveButton = new System.Windows.Forms.Button(); - this.focusButton = new System.Windows.Forms.Button(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); this.splitContainer.Panel1.SuspendLayout(); this.splitContainer.Panel2.SuspendLayout(); @@ -78,6 +78,27 @@ namespace TBF.Screens this.splitContainer.Panel2.Controls.Add(this.cameraPictureCtrl2); this.splitContainer.Panel2.Controls.Add(this.cameraPictureCtrl1); // + // focusButton + // + resources.ApplyResources(this.focusButton, "focusButton"); + this.focusButton.Name = "focusButton"; + this.focusButton.UseVisualStyleBackColor = true; + this.focusButton.Click += new System.EventHandler(this.focusButton_Click); + // + // liveButton + // + resources.ApplyResources(this.liveButton, "liveButton"); + this.liveButton.Name = "liveButton"; + this.liveButton.UseVisualStyleBackColor = true; + this.liveButton.Click += new System.EventHandler(this.liveButton_Click); + // + // grabButton + // + resources.ApplyResources(this.grabButton, "grabButton"); + this.grabButton.Name = "grabButton"; + this.grabButton.UseVisualStyleBackColor = true; + this.grabButton.Click += new System.EventHandler(this.grabButton_Click); + // // configureButton // resources.ApplyResources(this.configureButton, "configureButton"); @@ -121,27 +142,6 @@ namespace TBF.Screens resources.ApplyResources(this.cameraPictureCtrl1, "cameraPictureCtrl1"); this.cameraPictureCtrl1.Name = "cameraPictureCtrl1"; // - // grabButton - // - resources.ApplyResources(this.grabButton, "grabButton"); - this.grabButton.Name = "grabButton"; - this.grabButton.UseVisualStyleBackColor = true; - this.grabButton.Click += new System.EventHandler(this.grabButton_Click); - // - // liveButton - // - resources.ApplyResources(this.liveButton, "liveButton"); - this.liveButton.Name = "liveButton"; - this.liveButton.UseVisualStyleBackColor = true; - this.liveButton.Click += new System.EventHandler(this.liveButton_Click); - // - // focusButton - // - resources.ApplyResources(this.focusButton, "focusButton"); - this.focusButton.Name = "focusButton"; - this.focusButton.UseVisualStyleBackColor = true; - this.focusButton.Click += new System.EventHandler(this.focusButton_Click); - // // PicturesTabPageCtrl // resources.ApplyResources(this, "$this"); diff --git a/TestBenchFramework/Screens/PicturesTabPageCtrl.cs b/TestBenchFramework/Screens/PicturesTabPageCtrl.cs index 9c5e9c0ac..2a2f3ca44 100644 --- a/TestBenchFramework/Screens/PicturesTabPageCtrl.cs +++ b/TestBenchFramework/Screens/PicturesTabPageCtrl.cs @@ -1,5 +1,5 @@ /// -/// Copyright (c) 2013-2015 Sensus Metering Systems +/// Copyright (c) 2013-2017 Sensus Metering Systems /// using System; using System.Windows.Forms; @@ -16,13 +16,16 @@ namespace TBF.Screens Localize(); - Bridge.ImageUpdatedHandler += delegate(object sender, ImageUpdatedEventArgs args) + Bridge.ImageHandler += delegate(object sender, ImageEventArgs args) { - if (InvokeRequired) - { - Invoke(new EventHandler(OnImageUpdated), sender, args); - } - else OnImageUpdated(sender, args); + if (InvokeRequired) + { + Invoke(new EventHandler(ShowImage), sender, args); + } + else + { + ShowImage(sender, args); + } }; } @@ -34,16 +37,16 @@ namespace TBF.Screens focusButton.Text = Strings.Focus; } - void OnImageUpdated(object sender, ImageUpdatedEventArgs args) + void ShowImage(object sender, ImageEventArgs args) { switch (args.CameraIdx) { - case 0: cameraPictureCtrl1.PictureBox.ImageLocation = args.ImageLocation; break; - case 1: cameraPictureCtrl2.PictureBox.ImageLocation = args.ImageLocation; break; - case 2: cameraPictureCtrl3.PictureBox.ImageLocation = args.ImageLocation; break; - case 3: cameraPictureCtrl4.PictureBox.ImageLocation = args.ImageLocation; break; - case 4: cameraPictureCtrl5.PictureBox.ImageLocation = args.ImageLocation; break; - case 5: cameraPictureCtrl6.PictureBox.ImageLocation = args.ImageLocation; break; + case 0: cameraPictureCtrl1.PictureBox.Image = args.Image; break; + case 1: cameraPictureCtrl2.PictureBox.Image = args.Image; break; + case 2: cameraPictureCtrl3.PictureBox.Image = args.Image; break; + case 3: cameraPictureCtrl4.PictureBox.Image = args.Image; break; + case 4: cameraPictureCtrl5.PictureBox.Image = args.Image; break; + case 5: cameraPictureCtrl6.PictureBox.Image = args.Image; break; default: break; } } diff --git a/TestBenchFramework/Screens/PicturesTabPageCtrl.resx b/TestBenchFramework/Screens/PicturesTabPageCtrl.resx index 00e84dd5f..b6b1a68d9 100644 --- a/TestBenchFramework/Screens/PicturesTabPageCtrl.resx +++ b/TestBenchFramework/Screens/PicturesTabPageCtrl.resx @@ -295,7 +295,7 @@ cameraPictureCtrl6 - TBF.UiControls.CameraPictureCtrl, TBF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + TBF.UiControls.CameraPictureCtrl, TBF, Version=2.16.655.1, Culture=neutral, PublicKeyToken=null splitContainer.Panel2 @@ -316,7 +316,7 @@ cameraPictureCtrl5 - TBF.UiControls.CameraPictureCtrl, TBF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + TBF.UiControls.CameraPictureCtrl, TBF, Version=2.16.655.1, Culture=neutral, PublicKeyToken=null splitContainer.Panel2 @@ -337,7 +337,7 @@ cameraPictureCtrl4 - TBF.UiControls.CameraPictureCtrl, TBF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + TBF.UiControls.CameraPictureCtrl, TBF, Version=2.16.655.1, Culture=neutral, PublicKeyToken=null splitContainer.Panel2 @@ -358,7 +358,7 @@ cameraPictureCtrl3 - TBF.UiControls.CameraPictureCtrl, TBF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + TBF.UiControls.CameraPictureCtrl, TBF, Version=2.16.655.1, Culture=neutral, PublicKeyToken=null splitContainer.Panel2 @@ -379,7 +379,7 @@ cameraPictureCtrl2 - TBF.UiControls.CameraPictureCtrl, TBF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + TBF.UiControls.CameraPictureCtrl, TBF, Version=2.16.655.1, Culture=neutral, PublicKeyToken=null splitContainer.Panel2 @@ -400,7 +400,7 @@ cameraPictureCtrl1 - TBF.UiControls.CameraPictureCtrl, TBF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + TBF.UiControls.CameraPictureCtrl, TBF, Version=2.16.655.1, Culture=neutral, PublicKeyToken=null splitContainer.Panel2 @@ -421,7 +421,7 @@ 1 - 965, 689 + 1033, 689 42 @@ -451,7 +451,7 @@ Microsoft Sans Serif, 8.25pt - 965, 689 + 1033, 689 PicturesTabPageCtrl diff --git a/TestBenchFramework/Screens/ResultsTabPageCtrl.cs b/TestBenchFramework/Screens/ResultsTabPageCtrl.cs index 747aa819b..3d8c6ec99 100644 --- a/TestBenchFramework/Screens/ResultsTabPageCtrl.cs +++ b/TestBenchFramework/Screens/ResultsTabPageCtrl.cs @@ -40,12 +40,15 @@ namespace TBF.Screens { InitializeComponent(); - MetersArrangement = Program.LocalSettings.ResultsConfigMeters; - TestsArrangement = Program.LocalSettings.ResultsConfigTests; - NrMetersInOneGroup = Math.Max(1, Program.LocalSettings.ResultsConfigMetersInOneGroup); - RsltItems_Screen_SingleWM = Results.WMeterRsltItemSpec.FromStrArray(Program.LocalSettings.RsltItems_Screen_SingleWM); - RsltItems_Screen_CombinedWM = Results.WMeterRsltItemSpec.FromStrArray(Program.LocalSettings.RsltItems_Screen_CombinedWM); - RsltItems_Screen_HeatM = Results.WMeterRsltItemSpec.FromStrArray(Program.LocalSettings.RsltItems_Screen_HeatM); + if (Program.LocalSettings != null) + { + MetersArrangement = Program.LocalSettings.ResultsConfigMeters; + TestsArrangement = Program.LocalSettings.ResultsConfigTests; + NrMetersInOneGroup = Math.Max(1, Program.LocalSettings.ResultsConfigMetersInOneGroup); + RsltItems_Screen_SingleWM = Results.WMeterRsltItemSpec.FromStrArray(Program.LocalSettings.RsltItems_Screen_SingleWM); + RsltItems_Screen_CombinedWM = Results.WMeterRsltItemSpec.FromStrArray(Program.LocalSettings.RsltItems_Screen_CombinedWM); + RsltItems_Screen_HeatM = Results.WMeterRsltItemSpec.FromStrArray(Program.LocalSettings.RsltItems_Screen_HeatM); + } Bridge.ProcedureSelectedHandler += delegate(object sender, ProcedureSelectedEventArgs args) { diff --git a/TestBenchFramework/TBF.csproj b/TestBenchFramework/TBF.csproj index 04cac5e1d..526d5b81a 100644 --- a/TestBenchFramework/TBF.csproj +++ b/TestBenchFramework/TBF.csproj @@ -643,7 +643,10 @@ + + + Form @@ -1674,7 +1677,7 @@ - + diff --git a/TestBenchFramework/UiBridge/Bridge.cs b/TestBenchFramework/UiBridge/Bridge.cs index 1e31fe4bb..c9201ddc1 100644 --- a/TestBenchFramework/UiBridge/Bridge.cs +++ b/TestBenchFramework/UiBridge/Bridge.cs @@ -142,16 +142,16 @@ namespace TBF.UiBridge public static event EventHandler ProcedureCompletedHandler; - /// - /// Called from the state machine when the procedure is completed and UI needs to be updated. - /// - public static void OnImageUpdated(object sender, ImageUpdatedEventArgs data) - { - if (ImageUpdatedHandler == null) return; - try { ImageUpdatedHandler(sender, data); } - catch (Exception e) { log.Error("ImageUpdatedHandler(...) failed", e); } - } - public static event EventHandler ImageUpdatedHandler; + /// + /// Called from any RTP listener thread (e.g. MJpeg RTP listener thread) + /// + public static void OnImage(int cameraIdx, System.Drawing.Image image) + { + if (ImageHandler == null) return; + try { ImageHandler(null, new ImageEventArgs(cameraIdx, image)); } + catch (Exception e) { log.Error("ImageHandler(...) failed", e); } + } + public static event EventHandler ImageHandler; /// diff --git a/TestBenchFramework/UiBridge/ImageEventArgs.cs b/TestBenchFramework/UiBridge/ImageEventArgs.cs new file mode 100644 index 000000000..724934f37 --- /dev/null +++ b/TestBenchFramework/UiBridge/ImageEventArgs.cs @@ -0,0 +1,20 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +using System; +using System.Drawing; + +namespace TBF.UiBridge +{ + public class ImageEventArgs : EventArgs + { + public int CameraIdx; + public Image Image; + + public ImageEventArgs(int cameraIdx, Image image) + { + this.CameraIdx = cameraIdx; + this.Image = image; + } + } +} diff --git a/TestBenchFramework/UiBridge/ImageUpdatedEventArgs.cs b/TestBenchFramework/UiBridge/ImageUpdatedEventArgs.cs deleted file mode 100644 index 449806ba8..000000000 --- a/TestBenchFramework/UiBridge/ImageUpdatedEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -/// -/// Copyright (c) 2013-2015 Sensus Metering Systems -/// -using System; -using TBF.BenchControl; - -namespace TBF.UiBridge -{ - public class ImageUpdatedEventArgs : EventArgs - { - public int CameraIdx; - public string ImageLocation; - - public ImageUpdatedEventArgs(int cameraIdx, string imageLocation) - { - CameraIdx = cameraIdx; - ImageLocation = imageLocation; - } - } -}