CameraInterops.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. namespace SinMaiLauncher.Interops;
  2. using System.Runtime.InteropServices;
  3. using DirectShowLib;
  4. internal static class CameraInterops
  5. {
  6. public static DsDevice[] GetAllCamera() => DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
  7. internal class CameraCaptureWrap : IDisposable
  8. {
  9. private readonly Control _container;
  10. private readonly IFilterGraph2 graphBuilder;
  11. private readonly ICaptureGraphBuilder2 captureGraphBuilder;
  12. private readonly IMediaControl mediaControl;
  13. private readonly IVideoWindow videoWindow;
  14. private readonly IBaseFilter captureFilter;
  15. private readonly IAMStreamConfig streamConfig;
  16. public IReadOnlyList<ICameraFormat> SupportedFormats { get; private set; }
  17. public CameraCaptureWrap(DsDevice camera, Control container)
  18. {
  19. _container = container;
  20. // 创建滤镜图
  21. graphBuilder = (IFilterGraph2)new FilterGraph();
  22. mediaControl = (IMediaControl)graphBuilder;
  23. videoWindow = (IVideoWindow)graphBuilder;
  24. // 创建捕获图构建器并设置滤镜图
  25. captureGraphBuilder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
  26. captureGraphBuilder.SetFiltergraph(graphBuilder);
  27. // 使用指定的摄像头设备
  28. var iid = typeof(IBaseFilter).GUID;
  29. camera.Mon.BindToObject(null, null, ref iid, out var source);
  30. captureFilter = (IBaseFilter)source;
  31. graphBuilder.AddFilter(captureFilter, "Video Capture");
  32. // 获取 IAMStreamConfig 接口
  33. captureGraphBuilder.FindInterface(
  34. PinCategory.Capture, MediaType.Video, captureFilter,
  35. typeof(IAMStreamConfig).GUID, out var configObj);
  36. streamConfig = configObj as IAMStreamConfig;
  37. // 枚举支持的视频格式
  38. streamConfig.GetNumberOfCapabilities(out var count, out var size);
  39. var fmtLst = new List<CameraFormat>(count);
  40. for (var i = 0; i < count; i++)
  41. {
  42. // 分配内存并获取指向 VideoStreamConfigCaps 的指针
  43. var capsPtr = Marshal.AllocHGlobal(size);
  44. try
  45. {
  46. // 调用 GetStreamCaps
  47. var hr = streamConfig.GetStreamCaps(i, out var mediaType, capsPtr);
  48. if (hr < 0)
  49. {
  50. Console.WriteLine($"GetStreamCaps failed with error: {hr}");
  51. continue;
  52. }
  53. // 解析媒体类型和 caps
  54. if (mediaType.formatType == FormatType.VideoInfo)
  55. {
  56. // 将指针转换回结构
  57. var caps = (VideoStreamConfigCaps)Marshal.PtrToStructure(capsPtr, typeof(VideoStreamConfigCaps));
  58. //TO DO: 使用caps,如果需要
  59. var fmt = new CameraFormat(mediaType);
  60. fmtLst.Add(fmt);
  61. }
  62. }
  63. finally
  64. {
  65. // 释放分配的内存
  66. Marshal.FreeHGlobal(capsPtr);
  67. }
  68. }
  69. SupportedFormats = fmtLst.ToArray();
  70. }
  71. public interface ICameraFormat
  72. {
  73. public int Width { get; }
  74. public int Height { get; }
  75. public double Fps { get; }
  76. public int Bits { get; }
  77. public string Name => $"{Width}x{Height} {Bits}Bit {Fps:N2}FPS";
  78. }
  79. public class CameraFormat : ICameraFormat
  80. {
  81. public AMMediaType MediaType { get; }
  82. public int Width { get; }
  83. public int Height { get; }
  84. public double Fps { get; }
  85. public int Bits { get; }
  86. public string Name => $"{Width}x{Height} {Bits}Bit {Fps:N2}FPS";
  87. public CameraFormat(AMMediaType mediaType)
  88. {
  89. MediaType = mediaType;
  90. var vih = (VideoInfoHeader)Marshal.PtrToStructure(mediaType.formatPtr, typeof(VideoInfoHeader));
  91. Width = vih.BmiHeader.Width;
  92. Height = vih.BmiHeader.Height;
  93. Bits = vih.BmiHeader.BitCount;
  94. Fps = 10000000.0 / vih.AvgTimePerFrame;
  95. }
  96. }
  97. public void Dispose()
  98. {
  99. var pfs = FilterState.Stopped;
  100. mediaControl?.GetState(100, out pfs);
  101. if (pfs != FilterState.Stopped) mediaControl?.Stop();
  102. if (videoWindow != null) videoWindow.put_Visible(OABool.False);
  103. Marshal.ReleaseComObject(mediaControl);
  104. Marshal.ReleaseComObject(videoWindow);
  105. Marshal.ReleaseComObject(graphBuilder);
  106. Marshal.ReleaseComObject(captureGraphBuilder);
  107. Marshal.ReleaseComObject(captureFilter);
  108. Marshal.ReleaseComObject(streamConfig);
  109. // 释放 AMMediaType
  110. foreach (var format in SupportedFormats)
  111. {
  112. DsUtils.FreeAMMediaType(((CameraFormat)format).MediaType);
  113. }
  114. SupportedFormats = Array.Empty<ICameraFormat>();
  115. }
  116. public void ApplySelectedFormat(ICameraFormat selectedItem)
  117. {
  118. mediaControl.GetState(100, out var pfs);
  119. if (pfs != FilterState.Stopped) mediaControl.Stop();
  120. var fmt = (CameraFormat)selectedItem;
  121. streamConfig.SetFormat(fmt.MediaType);
  122. // 渲染预览流
  123. captureGraphBuilder.RenderStream(PinCategory.Preview, MediaType.Video, captureFilter, null, null);
  124. mediaControl.Run();
  125. // 设置视频窗口
  126. videoWindow.put_Owner(_container.Handle);
  127. videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren);
  128. videoWindow.put_Visible(OABool.True); // 确保窗口可见
  129. videoWindow.SetWindowPosition(0, 0, _container.Width, _container.Height); // 设置初始大小
  130. //TODO: 估计要跟随 Resize 事件
  131. }
  132. public ICameraFormat? FindBestFormat(int targetWidth, int targetHeight, double targetRefreshRate)
  133. {
  134. ICameraFormat? bestMatch = null;
  135. var minScore = double.MaxValue;
  136. foreach (var format in SupportedFormats)
  137. {
  138. var fmt = (CameraFormat)format;
  139. var resolutionScore = (fmt.Width == targetWidth && fmt.Height == targetHeight) ? 0 : Math.Abs(fmt.Width - targetWidth) + Math.Abs(fmt.Height - targetHeight);
  140. var frameRateScore = Math.Abs(fmt.Fps - targetRefreshRate);
  141. var totalScore = resolutionScore * 1000 + frameRateScore; // 分辨率优先
  142. if (totalScore < minScore)
  143. {
  144. minScore = totalScore;
  145. bestMatch = format;
  146. }
  147. }
  148. return bestMatch;
  149. }
  150. }
  151. }