private void ProcessVideo(byte[] payload, byte[] spsPps, long pts, int widthSource, int heightSource) { int nalu_size = 0; while (nalu_size < payload.Length) { int nc_len = (payload[nalu_size + 3] & 0xFF) | ((payload[nalu_size + 2] & 0xFF) << 8) | ((payload[nalu_size + 1] & 0xFF) << 16) | ((payload[nalu_size] & 0xFF) << 24); if (nc_len > 0) { payload[nalu_size] = 0; payload[nalu_size + 1] = 0; payload[nalu_size + 2] = 0; payload[nalu_size + 3] = 1; nalu_size += nc_len + 4; } if (payload.Length - nc_len > 4) { return; } } if (spsPps.Length != 0) { var h264Data = new H264Data(); h264Data.FrameType = payload[4] & 0x1f; if (h264Data.FrameType == 5) { var payloadOut = (byte[])Array.CreateInstance(typeof(byte), payload.Length + spsPps.Length); Array.Copy(spsPps, 0, payloadOut, 0, spsPps.Length); Array.Copy(payload, 0, payloadOut, spsPps.Length, payload.Length); h264Data.Data = payloadOut; h264Data.Length = payload.Length + spsPps.Length; // Release payload payload = null; } else { h264Data.Data = payload; h264Data.Length = payload.Length; } h264Data.Pts = pts; h264Data.Width = widthSource; h264Data.Height = heightSource; _receiver.OnData(h264Data); } }
public override async Task OnDataReceivedAsync(Request request, Response response, CancellationToken cancellationToken) { // INFO: We must use the same ED25519 KeyPair created inside AirTunesListener // INFO: We must use the same sessionId to retrieve var session = await SessionManager.Current.GetSessionAsync(_sessionId); if (request.Type == RequestType.GET && "/server-info".Equals(request.Path, StringComparison.OrdinalIgnoreCase)) { var xml = @"<?xml version=""1.0"" encoding=""UTF-8""?> <!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd""> <plist version=""1.0""> <dict> <key>deviceid</key> <string>10:20:30:40:50:60</string> <key>features</key> <integer>0x5A7FFFF7,0x1E</integer> <key>model</key> <string>AppleTV5,3</string> <key>protovers</key> <string>1.0</string> <key>srcvers</key> <string>220.68</string> </dict> </plist>"; var bytes = Encoding.ASCII.GetBytes(xml); await response.WriteAsync(bytes); } if (request.Type == RequestType.POST && "/pair-verify".Equals(request.Path, StringComparison.OrdinalIgnoreCase)) { using (var mem = new MemoryStream(request.Body)) using (var reader = new BinaryReader(mem)) { // Request: 68 bytes (the first 4 bytes are 01 00 00 00) // Client request packet remaining 64 bytes of content // 01 00 00 00 -> use 01 as flag to check type of verify // If flag is 1: // 32 bytes ecdh_their // 32 bytes ed_their // If flag is 0: // 64 bytes signature var flag = reader.ReadByte(); if (flag > 0) { reader.ReadBytes(3); session.EcdhTheirs = reader.ReadBytes(32); session.EdTheirs = reader.ReadBytes(32); var curve25519 = Curve25519.getInstance(Curve25519.BEST); var curve25519KeyPair = curve25519.generateKeyPair(); session.EcdhOurs = curve25519KeyPair.getPublicKey(); var ecdhPrivateKey = curve25519KeyPair.getPrivateKey(); session.EcdhShared = curve25519.calculateAgreement(ecdhPrivateKey, session.EcdhTheirs); var aesCtr128Encrypt = Utilities.InitializeChiper(session.EcdhShared); byte[] dataToSign = new byte[64]; Array.Copy(session.EcdhOurs, 0, dataToSign, 0, 32); Array.Copy(session.EcdhTheirs, 0, dataToSign, 32, 32); var signature = Chaos.NaCl.Ed25519.Sign(dataToSign, _expandedPrivateKey); byte[] encryptedSignature = aesCtr128Encrypt.DoFinal(signature); byte[] output = new byte[session.EcdhOurs.Length + encryptedSignature.Length]; Array.Copy(session.EcdhOurs, 0, output, 0, session.EcdhOurs.Length); Array.Copy(encryptedSignature, 0, output, session.EcdhOurs.Length, encryptedSignature.Length); response.Headers.Add("Content-Type", "application/octet-stream"); await response.WriteAsync(output, 0, output.Length).ConfigureAwait(false); } else { reader.ReadBytes(3); var signature = reader.ReadBytes(64); var aesCtr128Encrypt = Utilities.InitializeChiper(session.EcdhShared); var signatureBuffer = new byte[64]; signatureBuffer = aesCtr128Encrypt.ProcessBytes(signatureBuffer); signatureBuffer = aesCtr128Encrypt.DoFinal(signature); byte[] messageBuffer = new byte[64]; Array.Copy(session.EcdhTheirs, 0, messageBuffer, 0, 32); Array.Copy(session.EcdhOurs, 0, messageBuffer, 32, 32); session.PairVerified = Chaos.NaCl.Ed25519.Verify(signatureBuffer, messageBuffer, session.EdTheirs); Console.WriteLine($"PairVerified: {session.PairVerified}"); } } } if (request.Type == RequestType.POST && "/play".Equals(request.Path, StringComparison.OrdinalIgnoreCase)) { using (var mem = new MemoryStream(request.Body)) using (var reader = new StreamReader(mem, Encoding.ASCII)) { var data = await reader.ReadToEndAsync().ConfigureAwait(false); var dict = data.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(kv => { var splitted = kv.Split(": ", StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()).ToArray(); return(new KeyValuePair <string, string>(splitted[0], splitted[1])); }).ToDictionary(k => k.Key, v => v.Value); var startAt = dict.TryGetValue("Start-Position", out string dStartAt) ? decimal.Parse(dStartAt) : 0M; var url = dict.TryGetValue("Content-Location", out string dUrl) ? dUrl : throw new ArgumentNullException(nameof(dUrl)); // DO SOMETHING HERE... var from = 0; using (var client = new HttpClient()) { var to = from + 1024; client.DefaultRequestHeaders.Add("Range", $"bytes={from}-{to}"); var result = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); if (result.IsSuccessStatusCode) { var length = result.Headers.TryGetValues("Content-Length", out IEnumerable <string> cLength) ? int.Parse(cLength.FirstOrDefault() ?? "0") : 0; var accept = result.Headers.TryGetValues("Accept-Ranges", out IEnumerable <string> cAccept) ? cAccept.FirstOrDefault() : null; var bytes = await result.Content.ReadAsByteArrayAsync().ConfigureAwait(false); _receiver.OnData(new H264Data { Data = bytes }); } from = to; } } } }