/// <summary> /// 绘制波形 /// </summary> /// <param name="win"></param> /// <param name="waveQueueData"></param> /// <param name="polylineStyle"></param> /// <param name="id">区分线程,调试用</param> static void DrawWave(Window win, FixedQueueArray <float> waveQueueData, bool polylineStyle, int id = 0) { var lineColor = Color.FromRgb(17, 125, 187); var lineBrush = new SolidColorBrush(lineColor); lineBrush.Freeze(); var pen = new Pen(lineBrush, 1); pen.Freeze(); // WindowGraphics 这是一个可以跨进程绘制窗体的类 // 使用 WPF 中的 DrawingContext 进行绘制 var wg = new WindowGraphics(win); while (true) { Task.Delay(20).Wait(); using (var drawCtx = wg.OpenDraw()) { Span <float> waveData = new float[(int)drawCtx.Size.Width]; waveQueueData.Read(waveData); drawCtx.DrawingContext.DrawRectangle(Brushes.White, null, new Rect(drawCtx.Size)); DrawBorder(drawCtx); var h2 = drawCtx.Size.Height / 2; Point[] points; if (polylineStyle) { const int step = 10; points = new Point[waveData.Length / step + 1]; for (int i = 0; i < points.Length; i++) { double sum = 0; int count = 0; for (int j = 0; j < step; j++) { if (i * step + j >= waveData.Length) { break; } sum += waveData[i * step + j]; count++; } var avg = count > 0 ? sum / count : 0; points[i] = new Point(i * step, h2 + avg * h2); } points[points.Length - 1].X = drawCtx.Size.Width; } else { points = new Point[waveData.Length]; for (int i = 0; i < waveData.Length; i++) { double y = waveData[i] * h2; points[i] = new Point(i, h2 + y); } } var geometry = new StreamGeometry(); using (var geometryCtx = geometry.Open()) { geometryCtx.BeginFigure(points[0], false, false); geometryCtx.PolyLineTo(points, true, true); } geometry.Freeze(); drawCtx.DrawingContext.DrawGeometry(null, pen, geometry); } } }
static void Main(string[] args) { // 先注入一个dll,Hook要绘制的窗体的WndProc函数,吃掉WM_PAINT消息,不然任务管理器会闪烁 var dllInjectionContext = DllInjection.Injection("Taskmgr", Path.Combine(Environment.CurrentDirectory, "HookWinProc.dll")); controlCtrlDelegate = type => { switch (type) { case 0: // CTRL_C_EVENT case 2: // CTRL_CLOSE_EVENT case 5: // CTRL_LOGOFF_EVENT case 6: // CTRL_SHUTDOWN_EVENT // 控制台关闭后清理掉注入的dll DllInjection.FreeLibrary(dllInjectionContext); break; } return(true); }; SetConsoleCtrlHandler(controlCtrlDelegate, true); // 获得CPU核心数,其实我之后的代码已经写死默认是4个核心的情况 var kernelCount = Environment.ProcessorCount; var hwnd = WinApi.FindWindow("TaskManagerWindow", "任务管理器"); var root = new Window(hwnd); if (root.Childs.Count == 1 && root.Childs[0].ClassName == "NativeHWNDHost") { root = root.Childs[0]; } else { throw new Exception("未找到窗体"); } if (root.Childs.Count == 1 && root.Childs[0].ClassName == "DirectUIHWND") { root = root.Childs[0]; } else { throw new Exception("未找到窗体"); } // 拿到4个子窗体,但是顺序还不确定 var drawWindows = root.Childs .Where(w => w.ClassName == "CtrlNotifySink" && w.Childs.Count == 1 && w.Childs[0].ClassName == "CvChartWindow") .Select(w => w.Childs[0]) .OrderByDescending(w => w.Rect.Width * w.Rect.Height) .Take(kernelCount) .ToArray(); // 以下是捕获声音有关的代码,使用的是WASAPI Loopback的方式 var screenWidth = WinApi.GetScreenWidth(); FixedQueueArray <float> leftWaveQueue = new FixedQueueArray <float>(screenWidth), rightWaveQueue = new FixedQueueArray <float>(screenWidth); IWaveIn loopbackCapture = new WasapiLoopbackCapture(); var observer = new StreamObserver <float>(FFTSize, FFTSize / 2, 2); float[] leftFixedWaveData = new float[FFTSize], rightFixedWaveData = new float[FFTSize]; // 捕获声音 // leftFixedWaveData 和 rightFixedWaveData 之后会交给绘制频谱的线程处理 observer.Completed += newData => { lock (fftLock) { for (int i = 0; i < leftFixedWaveData.Length; i++) { leftFixedWaveData[i] = newData[2 * i + 0]; rightFixedWaveData[i] = newData[2 * i + 1]; } } }; // 捕获声音 // leftWaveQueue 和 rightWaveQueue 之后会交给绘制声波的线程处理 loopbackCapture.DataAvailable += (_, e) => { var waveData = MemoryMarshal.Cast <byte, float>(new ReadOnlySpan <byte>(e.Buffer, 0, e.BytesRecorded)); int copyLength = Math.Min(waveData.Length / 2, screenWidth); if (copyLength == 0) { return; } Span <float> leftNextData = stackalloc float[copyLength], rightNextData = stackalloc float[copyLength]; for (int i = 0; i < copyLength; i++) { leftNextData[i] = waveData[2 * i + 0]; rightNextData[i] = waveData[2 * i + 1]; } leftWaveQueue.Write(leftNextData); rightWaveQueue.Write(rightNextData); observer.Write(waveData); }; loopbackCapture.StartRecording(); var sampleRate = loopbackCapture.WaveFormat.SampleRate; bool polylineStyle = false; // 找出对应位置的子窗体 var leftTopWin = drawWindows.OrderBy(w => w.Rect.X + w.Rect.Y).First(); var rightTopWin = drawWindows.OrderBy(w => - w.Rect.X + w.Rect.Y).First(); var leftBottomWin = drawWindows.OrderBy(w => w.Rect.X - w.Rect.Y).First(); var rightBottomWin = drawWindows.OrderBy(w => - w.Rect.X - w.Rect.Y).First(); void StartThread(Action action) { new Thread(new ThreadStart(action)) { Priority = ThreadPriority.Highest }.Start(); } // 启动线程开始捕获声音并绘制,每个窗体一个线程 StartThread(() => DrawWave(leftTopWin, leftWaveQueue, polylineStyle, 1)); StartThread(() => DrawWave(rightTopWin, rightWaveQueue, polylineStyle, 2)); StartThread(() => DrawSpectrum(leftBottomWin, leftFixedWaveData, sampleRate, polylineStyle, 3)); StartThread(() => DrawSpectrum(rightBottomWin, rightFixedWaveData, sampleRate, polylineStyle, 4)); Thread.Sleep(Timeout.Infinite); }