using System; using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Application.Domain.Entities; using Infrastructure.Data; using Infrastructure.Extensions; using Infrastructure.Models; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace ONVIFService { public class OnvifService : IDisposable { private readonly IHostingEnvironment _env; private readonly IConfiguration _configuration; private readonly IHttpClientFactory _httpClientFactory; private readonly IServiceProvider _applicationServices; private CancellationTokenSource _tokenSource; private readonly ConcurrentDictionary _list = new ConcurrentDictionary(); private readonly IOnvifDeviceManagement _onvifDeviceManagement; public OnvifService(IHostingEnvironment env, IServiceProvider applicationServices, IConfiguration configuration, IHttpClientFactory httpClientFactory) { this._env = env; this._applicationServices = applicationServices; this._configuration = configuration; this._httpClientFactory = httpClientFactory; this._tokenSource = new CancellationTokenSource(); this._onvifDeviceManagement = new OnvifDeviceManagement(httpClientFactory); } public void Start() { Task.Run(async () => { while (!_tokenSource.IsCancellationRequested) { try { Search(); Notify(); } catch (Exception ex) { ex.PrintStack(); } await Task.Delay(this._configuration.GetValue("timer.seconds") * 1000); } }); } public void Search() { try { var list = this._onvifDeviceManagement.Discovery(); foreach (var ipCamera in list) { if (string.IsNullOrEmpty(ipCamera.Id) || string.IsNullOrEmpty(ipCamera.DeviceUrl)) { continue; } Console.WriteLine(ipCamera.DeviceUrl); using (var scope = _applicationServices.CreateScope()) { var repo = scope.ServiceProvider.GetService>(); var camera = repo.Table().FirstOrDefault(o => o.Number == ipCamera.Id); if (camera == null) { camera = new Camera { Name = "摄像头", Number = ipCamera.Id, Rtmp1 = $"rtmp://{this._configuration["stream.local.rtmp"]}/live/{ipCamera.Id}", Flv1 = $"http://{this._configuration["stream.local.flv"]}/live/{ipCamera.Id}.flv", Hls1 = $"http://{this._configuration["stream.local.hls"]}/live/{ipCamera.Id}.m3u8", Rtmp2 = $"rtmp://{this._configuration["stream.remote.rtmp"]}/live/{ipCamera.Id}", Flv2 = $"http://{this._configuration["stream.remote.flv"]}/live/{ipCamera.Id}.flv", Hls2 = $"http://{this._configuration["stream.remote.hls"]}/live/{ipCamera.Id}.m3u8", Arguments = this._configuration["ffmpeg.args"], File = this._configuration["ffmpeg.file"] }; repo.Add(camera); } var profiles = this._onvifDeviceManagement.GetProfiles(ipCamera.DeviceUrl, ipCamera.MediaUrl); if (profiles == "") { camera.NeedAuth = true; camera.UserName = camera.UserName ?? this._configuration["camera.usr"]; camera.Password = camera.Password ?? this._configuration["camera.pwd"]; profiles = this._onvifDeviceManagement.GetProfiles(ipCamera.DeviceUrl, ipCamera.MediaUrl, camera.UserName, camera.Password); if (profiles == "") { camera.HasAuth = false; } else { camera.HasAuth = true; ipCamera.GetProfilesXml = profiles; } } else { ipCamera.GetProfilesXml = profiles; } if (!camera.NeedAuth || camera.HasAuth) { ipCamera.ParseProfiles(); ipCamera.StreamUriXml = camera.NeedAuth ? this._onvifDeviceManagement.GetStreamUri(ipCamera.DeviceUrl, ipCamera.MediaUrl, camera.UserName, camera.Password, ipCamera.Tokens.FirstOrDefault()) : this._onvifDeviceManagement.GetStreamUri(ipCamera.DeviceUrl, ipCamera.MediaUrl, ipCamera.Tokens.FirstOrDefault()); ipCamera.ParseStreamUri(); ipCamera.SnapshotUriXml = camera.NeedAuth ? this._onvifDeviceManagement.GetSnapshotUri(ipCamera.DeviceUrl, ipCamera.MediaUrl, camera.UserName, camera.Password, ipCamera.Tokens.FirstOrDefault()) : this._onvifDeviceManagement.GetSnapshotUri(ipCamera.DeviceUrl, ipCamera.MediaUrl, ipCamera.Tokens.FirstOrDefault()); ipCamera.ParseSnapshotUri(); } camera.DeviceUrl = ipCamera.DeviceUrl; camera.StreamUri = ipCamera.StreamUri; camera.SnapshotUri = ipCamera.SnapshotUri; camera.PtzAddress = ipCamera.PTZAddress; camera.Ptz3DZoomSupport = ipCamera.Ptz3DZoomSupport; camera.UpdatedOn = DateTime.Now; repo.SaveChanges(); } } } catch (Exception ex) { ex.PrintStack(); } } public void Notify() { var host = Helper.Instance.GetLocalIP().ToString(); var prot = Convert.ToInt32(Regex.Match(this._configuration["server.urls"], @"(?<=:)\d+").Value); using (var scope = _applicationServices.CreateScope()) { var repo = scope.ServiceProvider.GetService>(); var cameras = repo.ReadOnlyTable().Where(o => o.Enabled).ToList(); foreach (var key in this._list.Keys) { if (!cameras.Where(o => o.Publish).Where(o => !o.NeedAuth || (o.NeedAuth && o.HasAuth)).Any(o => o.Number == key)) { this.Remove(key); } } foreach (var camera in cameras) { try { var model = new NotifyModel { CategoryName = "安防", CategoryNumber = "10", Name = "摄像头", Number = camera.Number, Icon = "camera", IsOnline = true, BaseUrl = $"http://{host}:{prot}/camera", ApiPath = "/api" }; model.Data.Add(new DataModel { Type = DataValueType.Text.ToString(), Key = nameof(camera.Ptz3DZoomSupport), Name = "缩放支持", Value = camera.Ptz3DZoomSupport ? "是" : "否", DisplayOrder = 1 }); model.Data.Add(new DataModel { Type = DataValueType.Text.ToString(), Key = "rtsp", Name = "rtsp", Value = camera.StreamUri, DisplayOrder = 2 }); model.Data.Add(new DataModel { Type = DataValueType.Text.ToString(), Key = "rtmp1", Name = "rtmp1", Value = camera.Rtmp1, DisplayOrder = 3 }); model.Data.Add(new DataModel { Type = DataValueType.Text.ToString(), Key = "flv1", Name = "flv1", Value = camera.Flv1, DisplayOrder = 4 }); model.Data.Add(new DataModel { Type = DataValueType.Text.ToString(), Key = "hls1", Name = "hls1", Value = camera.Hls1, DisplayOrder = 5 }); model.Data.Add(new DataModel { Type = DataValueType.Text.ToString(), Key = "rtmp2", Name = "rtmp2", Value = camera.Rtmp2, DisplayOrder = 6 }); model.Data.Add(new DataModel { Type = DataValueType.Text.ToString(), Key = "flv2", Name = "flv2", Value = camera.Flv2, DisplayOrder = 7 }); model.Data.Add(new DataModel { Type = DataValueType.Text.ToString(), Key = "hls2", Name = "hls2", Value = camera.Hls2, DisplayOrder = 8 }); this.Update(model); if (camera.Publish && (!camera.NeedAuth || camera.HasAuth)) { this.Publish(camera); } } catch (Exception ex) { ex.PrintStack(); } } } } private void Update(IPCamera ipCamera) { } private void Publish(Camera camera) { try { if (!this._list.Any(o => o.Key == camera.Number)) { this.Add(camera); } } catch (Exception ex) { ex.PrintStack(); } } public void Add(string key) { using (var scope = _applicationServices.CreateScope()) { var repo = scope.ServiceProvider.GetService>(); var camera = repo.ReadOnlyTable().FirstOrDefault(o => o.Number == key); if (camera != null && camera.Enabled && camera.Publish && (!camera.NeedAuth || (camera.NeedAuth && camera.HasAuth))) { this.Add(camera); } } } public void Add(Camera camera) { if (this._list.Any(o => o.Key == camera.Number)) { return; } var rtspUrl = $"rtsp://{(camera.NeedAuth ? $"{camera.UserName}:{camera.Password}@" : "")}{camera.StreamUri.Substring(7)}"; var fileName = $"ffmpeg-{Helper.Instance.GetRunTime()}{(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : "")}"; var file = Path.Combine(this._env.WebRootPath, fileName); if (camera.UseCustomFile) { file = camera.File; } Console.WriteLine(file); var arguments = camera.UseCustomArguments ? camera.Arguments : this._configuration["ffmpeg.args"]; Process local = null, remote = null; if (!string.IsNullOrEmpty(camera.StreamUri)) { if (!string.IsNullOrEmpty(camera.Rtmp1)) { local = this.SetProcess(file, string.Format(arguments, Environment.ProcessorCount, rtspUrl, camera.Rtmp1)); } if (!string.IsNullOrEmpty(camera.Rtmp2) && camera.Rtmp2 != camera.Rtmp1) { remote = this.SetProcess(file, string.Format(arguments, Environment.ProcessorCount, rtspUrl, camera.Rtmp2)); } } if (this._list.TryAdd(camera.Number, (camera, local, remote))) { Console.WriteLine($"add {camera.Number} to list"); } else { if (local != null) { this.CloseProcess(local); this.CloseProcess(remote); } } } public void Remove(string key) { var item = this._list[key]; this.CloseProcess(item.local); this.CloseProcess(item.remote); if (this._list.TryRemove(key, out item)) { Console.WriteLine($"remove {key} from list"); } } private Process SetProcess(string file, string arguments) { Console.WriteLine(file); var process = new Process { StartInfo = new ProcessStartInfo { WorkingDirectory = this._env.WebRootPath, FileName = file, Arguments = arguments, UseShellExecute = false, CreateNoWindow = true, RedirectStandardError = true }, EnableRaisingEvents = true }; process.Exited += (s, e) => { var _process = s as Process; Console.WriteLine($"list:{_list.Count},exit:{_process.ExitCode},args:{arguments}"); if (_process != null) { Thread.Sleep(10 * 1000); _process.CancelErrorRead(); _process.Start(); _process.BeginErrorReadLine(); } }; process.ErrorDataReceived += (s, e) => { if (!string.IsNullOrEmpty(e.Data)) { Console.WriteLine(e.Data); if (e.Data.IndexOf("forcing output") > -1) { process.Kill(); } } }; Console.WriteLine(arguments); process.Start(); process.BeginErrorReadLine(); return process; } private void CloseProcess(Process process) { if (process != null) { process.EnableRaisingEvents = false; try { process.Kill(); process.WaitForExit(); } catch (Exception ex) { ex.PrintStack(); } } } public void Update(NotifyModel model) { try { var url = $"http://{this._configuration["node.url"]}/Notify"; Console.WriteLine(url); var hc = this._httpClientFactory.CreateClient(); var task = this._httpClientFactory.CreateClient().PostAsync(url, new FormUrlEncodedContent(model.ToList())); task.Wait(); using (var response = task.Result) { using (var content = response.Content) { var value = content.ReadAsStringAsync().Result; Console.WriteLine($"end:{url}:{value}"); } } } catch (Exception ex) { ex.PrintStack(); } } private string GetOnoce(string deviceUrl) { var message = @" "; var hc = this._httpClientFactory.CreateClient(); hc.DefaultRequestHeaders.Add("ContentType", $"application/soap+xml; charset=utf-8; action=\"{Template.GetDeviceInformationAction}\""); var task = hc.PostAsync(deviceUrl, new StringContent(message)); var result = task.Result.Headers.WwwAuthenticate; return Regex.Match(result.ToString(), "nonce=\"([^\"]+)\"").Groups[1].Value; } private string RequestXml(string url, string action, string body, string userName, string password, string onoce) { var nonce_b = Convert.FromBase64String(onoce); var now = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddThh:mm:ss.fffZ"); var creationtime_b = Encoding.ASCII.GetBytes(now); var password_b = Encoding.ASCII.GetBytes(password); var concatenation_b = new byte[nonce_b.Length + creationtime_b.Length + password_b.Length]; Buffer.BlockCopy(nonce_b, 0, concatenation_b, 0, nonce_b.Length); Buffer.BlockCopy(creationtime_b, 0, concatenation_b, nonce_b.Length, creationtime_b.Length); Buffer.BlockCopy(password_b, 0, concatenation_b, nonce_b.Length + creationtime_b.Length, password_b.Length); var sha = new SHA1CryptoServiceProvider(); var pdresult = sha.ComputeHash(concatenation_b); var passworddigest = Convert.ToBase64String(pdresult); var message = string.Format(Template.AuthTemplate, userName, passworddigest, Convert.ToBase64String(nonce_b), now, body); var result = SoapRequest(url, action, message); return result; } private string SoapRequest(string url, string action, string message) { var hc = this._httpClientFactory.CreateClient(); hc.DefaultRequestHeaders.Add("ContentType", $"application/soap+xml; charset=utf-8; action=\"{action}\""); var task = hc.PostAsync(url, new StringContent(message)); var result = task.Result.Content.ReadAsStringAsync().Result; return result; } private string RegexMatch(string input, string pattern, string prefix, string suffix) { return Regex.Match(input, $"(?<={prefix}){pattern}(?={suffix})").Value; } public void ZoomIn(string id, float speed) { this.Move(id, speed, 0, 0); } public void ZoomOut(string id, float speed) { this.Move(id, -speed, 0, 0); } public void StopZoom(string id) { this.Stop(id, false, true); } public void Up(string id, float speed) { this.Move(id, 0, 0, speed); } public void Right(string id, float speed) { this.Move(id, 0, speed, 0); } public void Down(string id, float speed) { this.Move(id, 0, 0, -speed); } public void Left(string id, float speed) { this.Move(id, 0, -speed, 0); } public void StopTurn(string id) { this.Stop(id, true, false); } public void Move(string id, float zx, float px, float py) { var camera = this.GetCamera(id); if (camera != null) { var ptzAddress = camera.PtzAddress; if (!string.IsNullOrEmpty(ptzAddress)) { var deviceUrl = camera.DeviceUrl; RequestXml(ptzAddress, Template.ContinuousMoveAction, String.Format(Template.ContinuousMoveMessage, zx, px, py), camera.UserName, camera.Password, GetOnoce(deviceUrl)); } } } public void Stop(string id, bool panTilt, bool zoom) { var camera = this.GetCamera(id); if (camera != null) { var ptzAddress = camera.PtzAddress; if (!string.IsNullOrEmpty(ptzAddress)) { var deviceUrl = camera.DeviceUrl; RequestXml(ptzAddress, Template.StopAction, String.Format(Template.StopMessage, panTilt, zoom), camera.UserName, camera.Password, GetOnoce(deviceUrl)); } } } public byte[] ScreenShot(string id) { var camera = this.GetCamera(id); var url = camera.SnapshotUri; var hc = this._httpClientFactory.CreateClient(); if (!camera.NeedAuth) { return hc.GetByteArrayAsync(url).Result; } return hc.GetByteDigest(url, camera.UserName, camera.Password); } private Camera GetCamera(string number) { using (var scope = _applicationServices.CreateScope()) { var repo = scope.ServiceProvider.GetService>(); return repo.ReadOnlyTable().FirstOrDefault(o => o.Number == number); } } public void Clear() { foreach (var item in this._list.Keys.ToList()) { this.Remove(item); } } public void Dispose() { Console.WriteLine("OnvifService dispose..."); try { this._tokenSource.Cancel(); foreach (var item in this._list.Keys.ToList()) { this.Remove(item); } } catch (Exception ex) { ex.PrintStack(); } } } }