using System; using System.Diagnostics; using System.Globalization; // (c) Roger Hardiman 2016 // This class uses a System Timer to generate a YUV image at regular intervals // The ReceivedYUVFrame event is fired for each new YUV image namespace Inspectron.HawkEye.RTSP.Server { public class TestCard { // Events that applications can receive public event ReceivedYUVFrameHandler ReceivedYUVFrame; // Delegated functions (essentially the function prototype) public delegate void ReceivedYUVFrameHandler(uint timestamp, int width, int height, byte[] data); // Local variables private System.Timers.Timer frame_timer; private int fps = 0; private Stopwatch stopwatch; private byte[] yuv_frame = null; private int x_position = 0; private int y_position = 0; private int width = 0; private int height = 0; private Object generate_lock = new Object(); private long count = 0; // ASCII Font // Created by Roger Hardiman using an online generation tool // http://www.riyas.org/2013/12/online-led-matrix-font-generator-with.html byte[] ascii_0 = { 0x00, 0x3c, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c }; byte[] ascii_1 = { 0x00, 0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x3e }; byte[] ascii_2 = { 0x00, 0x3e, 0x42, 0x02, 0x0c, 0x30, 0x40, 0x7e }; byte[] ascii_3 = { 0x00, 0x7c, 0x02, 0x02, 0x3c, 0x02, 0x02, 0x7c }; byte[] ascii_4 = { 0x00, 0x0c, 0x14, 0x24, 0x44, 0x7e, 0x04, 0x04 }; byte[] ascii_5 = { 0x00, 0x7e, 0x40, 0x40, 0x7c, 0x02, 0x02, 0x7c }; byte[] ascii_6 = { 0x00, 0x3e, 0x40, 0x40, 0x7c, 0x42, 0x42, 0x3c }; byte[] ascii_7 = { 0x00, 0x7e, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20 }; byte[] ascii_8 = { 0x00, 0x3c, 0x42, 0x42, 0x3c, 0x42, 0x42, 0x3c }; byte[] ascii_9 = { 0x00, 0x3c, 0x42, 0x42, 0x3c, 0x02, 0x02, 0x3e }; byte[] ascii_colon = { 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00 }; byte[] ascii_space = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; byte[] ascii_dot = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00 }; // Constructor public TestCard(int width, int height, int fps) { this.width = width; this.height = height; this.fps = fps; // YUV size int y_size = width * height; int u_size = (width >> 1) * (height >> 1); int v_size = (width >> 1) * (height >> 1); yuv_frame = new byte[y_size + u_size + v_size]; // Set all values to 127 for (int x = 0; x < yuv_frame.Length; x++) { yuv_frame[x] = 127; } stopwatch = new Stopwatch(); stopwatch.Start(); // Start timer. The Timer will generate each YUV frame frame_timer = new System.Timers.Timer(); frame_timer.Interval = 1; // on first pass timer will fire straight away (cannot have zero interval) frame_timer.AutoReset = false; // do not restart timer after the time has elapsed frame_timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) => { // send a frame Send_YUV_Frame(); count++; // Some CPU cycles will have been used in Sending the YUV Frame. // Compute the delay required (the Timer Interval) before sending the next YUV frame long time_for_next_tick_ms = (count * 1000) / fps; long time_to_wait = time_for_next_tick_ms - stopwatch.ElapsedMilliseconds; if (time_to_wait <= 0) time_to_wait = 1; // cannot have negative or zero intervals frame_timer.Interval = time_to_wait; frame_timer.Start(); }; frame_timer.Start(); } // Dispose public void Disconnect() { // Stop the frame timer frame_timer.Stop(); frame_timer.Dispose(); } private void Send_YUV_Frame() { lock (generate_lock) { // Get the current time DateTime now_utc = DateTime.UtcNow; DateTime now_local = now_utc.ToLocalTime(); long timestamp_ms = ((long)(now_utc.Ticks / TimeSpan.TicksPerMillisecond)); // Generate the String to write char[] overlay = null; if (width >= 96) { // Need 12 characters of 8x8 pixels. 12*8 = 96 // HH:MM:SS.mmm String overlay_str = now_local.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture); // do not replace : or . by local formats overlay = overlay_str.ToCharArray(); } else { // Min for most video formats is 16x16, enough for 2 characters String overlay_str = now_local.ToString("ss", CultureInfo.InvariantCulture); // do not replace : or . by local formats overlay = overlay_str.ToCharArray(); } // process each character int start_row = ((height / 2) - 4); // start 4 pixels above the centre row (4 is half the font height) for (int c = 0; c < overlay.Length; c++) { byte[] font = ascii_space; if (overlay[c] == '0') font = ascii_0; if (overlay[c] == '1') font = ascii_1; if (overlay[c] == '2') font = ascii_2; if (overlay[c] == '3') font = ascii_3; if (overlay[c] == '4') font = ascii_4; if (overlay[c] == '5') font = ascii_5; if (overlay[c] == '6') font = ascii_6; if (overlay[c] == '7') font = ascii_7; if (overlay[c] == '8') font = ascii_8; if (overlay[c] == '9') font = ascii_9; if (overlay[c] == ' ') font = ascii_space; if (overlay[c] == ':') font = ascii_colon; if (overlay[c] == '.') font = ascii_dot; // process the font character for (int rows = 0; rows < 8; rows++) { int y_plane_pos = (start_row * width) + (rows * width) + (c * 8); byte row_byte = font[rows]; // bit shift the row byte into individual pixels where the font On/Off maps to Y intensity 50 or 200 for (int bits = 0; bits < 8; bits++) { if ((row_byte & 0x80) == 0x80) { // Pixel On yuv_frame[y_plane_pos] = 200; } else { yuv_frame[y_plane_pos] = 50; } y_plane_pos++; row_byte = (byte)(row_byte << 1); // shift up so the next 'bit' to process is the most significant bit } } } // Toggle the pixel value byte pixel_value = yuv_frame[(y_position * width) + x_position]; // change brightness of pixel if (pixel_value > 128) pixel_value = 30; else pixel_value = 230; yuv_frame[(y_position * width) + x_position] = pixel_value; // move the x and y position x_position = x_position + 5; if (x_position >= width) { x_position = 0; y_position = y_position + 1; } if (y_position >= height) { y_position = 0; } // fire the Event if (ReceivedYUVFrame != null) { ReceivedYUVFrame((uint)stopwatch.ElapsedMilliseconds, width, height, yuv_frame); } } } } }