using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Media.Imaging; namespace FrontEnd { // Source: https://gist.github.com/Larry57/958bd61252a3f810b75d static class SimpleMJPEGDecoder { /// /// Start a MJPEG on a http stream /// /// Delegate to run at each frame /// url of the http stream (only basic auth is implemented) /// optional login /// optional password (only basic auth is implemented) /// cancellation token used to cancel the stream parsing /// Max chunk byte size when reading stream /// Maximum frame byte size /// public async static Task StartAsync(Action action, string url, string login = null, string password = null, CancellationToken? token = null, int chunkMaxSize = 1024, int frameBufferSize = 1024 * 1024) { var tok = token ?? CancellationToken.None; tok.ThrowIfCancellationRequested(); using (var cli = new HttpClient()) { if (!string.IsNullOrEmpty(login) && !string.IsNullOrEmpty(password)) cli.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{login}:{password}"))); using (var stream = await cli.GetStreamAsync(url).ConfigureAwait(false)) { var streamBuffer = new byte[chunkMaxSize]; var frameBuffer = new byte[frameBufferSize]; var frameIdx = 0; var inPicture = false; var previous = (byte)0; var current = (byte)0; while (true) { var streamLength = await stream.ReadAsync(streamBuffer, 0, chunkMaxSize, tok).ConfigureAwait(false); ParseBuffer(action, frameBuffer, ref frameIdx, ref inPicture, ref previous, ref current, streamBuffer, streamLength); }; } } } static void ParseBuffer(Action action, byte[] frameBuffer, ref int frameIdx, ref bool inPicture, ref byte previous, ref byte current, byte[] streamBuffer, int streamLength) { var idx = 0; loop: if (idx < streamLength) { if (inPicture) { do { previous = current; current = streamBuffer[idx++]; frameBuffer[frameIdx++] = current; if (previous == (byte)0xff && current == (byte)0xd9) { BitmapImage img = new BitmapImage(); ; using (var s = new MemoryStream(frameBuffer, 0, frameIdx)) { try { img.BeginInit(); img.StreamSource = s; img.CacheOption = BitmapCacheOption.OnLoad; img.EndInit(); img.Freeze(); } catch { // dont care about errors while decoding bad picture } } Task.Run(() => action?.Invoke(img)); inPicture = false; goto loop; } } while (idx < streamLength); } else { do { previous = current; current = streamBuffer[idx++]; if (previous == (byte)0xff && current == (byte)0xd8) { frameIdx = 2; frameBuffer[0] = (byte)0xff; frameBuffer[1] = (byte)0xd8; inPicture = true; goto loop; } } while (idx < streamLength); } } } } }