Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions docs/operator-validation-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,19 @@ Troubleshooting:

1. Ensure sign-in from step 8 (required for external-account meetings).
2. Enter meeting URL (and passcode if needed) in the join UI.
3. After join, verify:
3. Wait for **Zoom Live**, then turn **Capture** on. Joining establishes the
Meeting SDK session; Capture is the separate control that subscribes and
forwards raw participant video/audio into CoreVideo Pro.
4. Verify Capture is genuinely running before evaluating the show:
- `engineOn` is `true` in control state / diagnostics.
- Participant `video_frame_received` counters advance for every expected feed.
- Expected participant audio callbacks and meters advance when each guest speaks.
5. After join and Capture On, verify:
- **Participants** roster populates (not empty / not stale stub).
- **Program** tile shows video (GPU/full-res if D3D11 interop works, else CPU/BGRA preview).
- **Breakout room** name updates when you move rooms.
- Diagnostics show live Zoom capabilities when the dev SDK path is active (`zoom-raw-video`, `zoom-raw-audio`).
4. **Leave** meeting; roster clears, meeting state returns to idle.
6. **Leave** meeting; roster clears, meeting state returns to idle.

## 10. Headless live Zoom harness (optional)

Expand Down
32 changes: 32 additions & 0 deletions native-shell/CoreVideoPro.WinUI.Tests/AudioMeterScaleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,36 @@ public void ToLevel_UsesCalibratedMinus60ToZeroDbfsScale(double dbfs, int expect
[Fact]
public void ToLevel_MutedAlwaysReportsSilence() =>
Assert.Equal(0, AudioMeterScale.ToLevel(-3, muted: true));

[Theory]
[InlineData(324)]
[InlineData(576)]
[InlineData(900)]
public void FitVerticalSegments_FillsAvailableWindowHeight(double availableHeight)
{
var layout = AudioMeterScale.FitVerticalSegments(availableHeight, 36);

Assert.Equal(36, layout.SegmentCount);
Assert.Equal(availableHeight, layout.OccupiedSize, precision: 6);
}

[Fact]
public void FitVerticalSegments_GrowsSegmentsInTallWindow()
{
var compact = AudioMeterScale.FitVerticalSegments(324, 36);
var tall = AudioMeterScale.FitVerticalSegments(576, 36);

Assert.True(tall.SegmentSize > compact.SegmentSize);
Assert.True(tall.SegmentSize > 7);
}

[Fact]
public void FitVerticalSegments_ReducesResolutionRatherThanOverflowingShortWindow()
{
var layout = AudioMeterScale.FitVerticalSegments(72, 36);

Assert.True(layout.SegmentCount < 36);
Assert.True(layout.SegmentSize >= 2);
Assert.Equal(72, layout.OccupiedSize, precision: 6);
}
}
42 changes: 28 additions & 14 deletions native-shell/CoreVideoPro.WinUI/Controls/AudioLevelMeter.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,26 +253,40 @@ private void RenderSegments()
double segMain = IsVertical ? 7 : 4;
if (availableMain > 0)
{
if ((segMain + spacing) * count > availableMain)
if (IsVertical)
{
spacing = 1;
}
var perSegment = (availableMain - spacing * (count - 1)) / count;
if (perSegment < segMain)
{
segMain = Math.Max(2, Math.Floor(perSegment));
}
var fits = (int)Math.Floor((availableMain + spacing) / (segMain + spacing));
if (fits < count && fits >= 4)
{
// Too small even at 2px segments: re-quantize onto fewer segments
// so the meter stays proportional instead of clipping.
count = fits;
// Keep the segment bar and the dB scale on the same responsive
// height instead of pinning a fixed-size bar to the bottom.
var layout = Models.AudioMeterScale.FitVerticalSegments(availableMain, count);
count = layout.SegmentCount;
segMain = layout.SegmentSize;
spacing = layout.Spacing;
activeSegments = (int)Math.Round(level / 100.0 * count, MidpointRounding.AwayFromZero);
peakSegment = _peakLevel > 0
? (int)Math.Round(Math.Clamp(_peakLevel, 0, 100) / 100.0 * count, MidpointRounding.AwayFromZero) - 1
: -1;
}
else
{
if ((segMain + spacing) * count > availableMain)
{
spacing = 1;
}
var perSegment = (availableMain - spacing * (count - 1)) / count;
if (perSegment < segMain)
{
segMain = Math.Max(2, Math.Floor(perSegment));
}
var fits = (int)Math.Floor((availableMain + spacing) / (segMain + spacing));
if (fits < count && fits >= 4)
{
count = fits;
activeSegments = (int)Math.Round(level / 100.0 * count, MidpointRounding.AwayFromZero);
peakSegment = _peakLevel > 0
? (int)Math.Round(Math.Clamp(_peakLevel, 0, 100) / 100.0 * count, MidpointRounding.AwayFromZero) - 1
: -1;
}
}
}

var panel = new StackPanel
Expand Down
42 changes: 42 additions & 0 deletions native-shell/CoreVideoPro.WinUI/Models/AudioMeterScale.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,46 @@ public static int ToLevel(double dbfs, bool muted = false)
var normalized = (dbfs - MinimumDbfs) / (MaximumDbfs - MinimumDbfs);
return Math.Clamp((int)Math.Round(normalized * 100), 0, 100);
}

/// <summary>
/// Fits a segmented vertical meter to its current viewport. Full-size
/// consoles keep the requested resolution and grow the segment bodies;
/// compact windows reduce the segment count only when 2 px bodies no
/// longer fit. The returned stack always occupies the available height.
/// </summary>
public static AudioMeterSegmentLayout FitVerticalSegments(double availableHeight, int requestedCount)
{
var count = Math.Clamp(requestedCount, 8, 48);
if (!double.IsFinite(availableHeight) || availableHeight <= 0)
{
return new AudioMeterSegmentLayout(count, 7, 2);
}

const double minimumSegmentSize = 2;
var spacing = availableHeight / count >= 5 ? 2d : 1d;
var segmentSize = (availableHeight - spacing * (count - 1)) / count;

if (segmentSize < minimumSegmentSize)
{
spacing = 1;
count = Math.Min(count, Math.Max(1,
(int)Math.Floor((availableHeight + spacing) / (minimumSegmentSize + spacing))));

if (count == 1)
{
spacing = 0;
}

segmentSize = Math.Max(0,
(availableHeight - spacing * (count - 1)) / count);
}

return new AudioMeterSegmentLayout(count, segmentSize, spacing);
}
}

public readonly record struct AudioMeterSegmentLayout(int SegmentCount, double SegmentSize, double Spacing)
{
public double OccupiedSize =>
SegmentCount * SegmentSize + Math.Max(0, SegmentCount - 1) * Spacing;
}
Loading
Loading