perf(camera): MJPEG negotiation before frame reader — camera drives at its best rate
Slice 10 from today-slices (d1126dd): UVC cameras default MediaFrameReader to their FIRST media type (YUY2 at crippled fps — C920 720p = 5fps). A 60fps container then shows a 5-20fps face cam as "every few frames cut out". Fix: VideoDeviceController.SetMediaStreamPropertiesAsync negotiated to best MJPEG >=640x360 @>=30fps (<=1280 wide) BEFORE creating the frame reader, with device-default fallback. Plus a startup.log truth line of the actual agreed media type so the next diagnosis starts from facts (no more re-deriving the fps). Build 0 warnings, 289 tests.
This commit is contained in:
@@ -102,6 +102,43 @@ public sealed class MediaCaptureFrameSource : ICameraFrameSource
|
|||||||
};
|
};
|
||||||
await capture.InitializeAsync(settings);
|
await capture.InitializeAsync(settings);
|
||||||
|
|
||||||
|
// MediaFrameReader defaults to the camera's FIRST media type — on UVC webcams
|
||||||
|
// (Logitech C920 et al.) that is often YUY2 at a crippled rate (720p5!), and a
|
||||||
|
// real-time 60fps container then shows a 5-20fps face cam as "every few frames
|
||||||
|
// cut out". OBS's mechanism, available via VideoDeviceController (VideoProfile
|
||||||
|
// is NOT projected in the 19041 SDK): negotiate the stream property. Pick the
|
||||||
|
// best MJPEG >=640x360 @>=30fps (largest, fastest), set it BEFORE the frame
|
||||||
|
// reader is created, fall back to device default on refusal, and log the live
|
||||||
|
// media type so the next diagnosis starts from facts.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var controller = capture.VideoDeviceController;
|
||||||
|
var candidates = controller == null
|
||||||
|
? new System.Collections.Generic.List<Windows.Media.MediaProperties.VideoEncodingProperties>()
|
||||||
|
: controller
|
||||||
|
.GetAvailableMediaStreamProperties(MediaStreamType.VideoPreview)
|
||||||
|
.OfType<Windows.Media.MediaProperties.VideoEncodingProperties>()
|
||||||
|
.Where(v => string.Equals(v.Subtype, "MJPG", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& v.Width >= 640 && v.Height >= 360
|
||||||
|
&& v.FrameRate.Denominator > 0
|
||||||
|
&& v.FrameRate.Numerator / v.FrameRate.Denominator >= 30.0)
|
||||||
|
.OrderByDescending(v => v.FrameRate.Numerator / (double)Math.Max(1, v.FrameRate.Denominator))
|
||||||
|
.ThenByDescending(v => (long)v.Width * v.Height)
|
||||||
|
.ToList();
|
||||||
|
if (candidates.Count > 0)
|
||||||
|
{
|
||||||
|
var pick = candidates
|
||||||
|
.OrderBy(v => (long)v.Width * v.Height)
|
||||||
|
.LastOrDefault(v => v.Width <= 1280) ?? candidates[0];
|
||||||
|
await controller!.SetMediaStreamPropertiesAsync(MediaStreamType.VideoPreview, pick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception fmtEx)
|
||||||
|
{
|
||||||
|
AppLog.Write($"MediaCaptureFrameSource: MJPG negotiation refused ({fmtEx.Message}); " +
|
||||||
|
"keeping device-default media type");
|
||||||
|
}
|
||||||
|
|
||||||
// Post-init resource validation: the capture must actually be bound to
|
// Post-init resource validation: the capture must actually be bound to
|
||||||
// the device we asked for and must expose a live video controller.
|
// the device we asked for and must expose a live video controller.
|
||||||
if (!string.Equals(capture.MediaCaptureSettings?.VideoDeviceId, _deviceId,
|
if (!string.Equals(capture.MediaCaptureSettings?.VideoDeviceId, _deviceId,
|
||||||
@@ -147,6 +184,17 @@ public sealed class MediaCaptureFrameSource : ICameraFrameSource
|
|||||||
if (reader == null)
|
if (reader == null)
|
||||||
throw new InvalidOperationException($"Camera '{_deviceId}' created no frame reader.");
|
throw new InvalidOperationException($"Camera '{_deviceId}' created no frame reader.");
|
||||||
|
|
||||||
|
// One-line truth of what the camera actually agreed to (format/size/fps).
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var active = capture.VideoDeviceController?
|
||||||
|
.GetMediaStreamProperties(colorSource.Info.MediaStreamType);
|
||||||
|
if (active is Windows.Media.MediaProperties.VideoEncodingProperties vep)
|
||||||
|
AppLog.Write($"Camera '{_deviceId}' media type: {vep.Subtype} " +
|
||||||
|
$"{vep.Width}x{vep.Height} @ {vep.FrameRate.Numerator}/{vep.FrameRate.Denominator}fps");
|
||||||
|
}
|
||||||
|
catch { /* logging must never kill acquisition */ }
|
||||||
|
|
||||||
reader.FrameArrived += OnFrameArrived;
|
reader.FrameArrived += OnFrameArrived;
|
||||||
var status = await reader.StartAsync();
|
var status = await reader.StartAsync();
|
||||||
if (status != MediaFrameReaderStartStatus.Success)
|
if (status != MediaFrameReaderStartStatus.Success)
|
||||||
|
|||||||
Reference in New Issue
Block a user