Commit 00f2687f authored by Evan Shrubsole's avatar Evan Shrubsole Committed by Chromium LUCI CQ

Add UMAs for video capture on Mac

These metrics measure how our algorithm for opening video devices on Mac
work with devices in the wild by seeing what happens on the first frame
after opening the camera. We compare what we asked the webcam to do with
what we actually get. We also will see how often we receive IOSurfaces
in the Mac capture pipeline.

correct values for histograms.

Bug: 1156144
Test: Ran locally with appr.tc with 2 different cameras to confirm
Change-Id: I58eb1414d9def98cf4cf2b90e15c69d29bdc0148
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2621817Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarNico Weber <thakis@chromium.org>
Reviewed-by: default avatarMarkus Handell <handellm@google.com>
Reviewed-by: default avatarCaitlin Fischer <caitlinfischer@google.com>
Commit-Queue: Nico Weber <thakis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#844075}
parent 4a209f28
......@@ -190,6 +190,8 @@ component("capture_lib") {
"video/mac/video_capture_device_factory_mac.mm",
"video/mac/video_capture_device_mac.h",
"video/mac/video_capture_device_mac.mm",
"video/mac/video_capture_metrics_mac.h",
"video/mac/video_capture_metrics_mac.mm",
]
deps += [
"//services/video_capture/public/uma",
......@@ -493,6 +495,7 @@ test("capture_unittests") {
"video/mac/video_capture_device_avfoundation_mac_unittest.mm",
"video/mac/video_capture_device_factory_mac_unittest.mm",
"video/mac/video_capture_device_mac_unittest.mm",
"video/mac/video_capture_metrics_mac_unittest.mm",
]
frameworks = [
"AVFoundation.framework",
......@@ -500,6 +503,7 @@ test("capture_unittests") {
"CoreVideo.framework",
"IOSurface.framework",
]
deps += [ "//third_party/ocmock" ]
}
if (is_win) {
......
......@@ -2,3 +2,9 @@ include_rules = [
"+third_party/decklink",
"+services/video_capture/public/uma",
]
specific_include_rules = {
"video_capture_metrics_mac_unittest.mm": [
"+third_party/ocmock"
]
}
......@@ -53,6 +53,7 @@ CAPTURE_EXPORT
base::Lock _lock;
media::VideoCaptureDeviceAVFoundationFrameReceiver* _frameReceiver
GUARDED_BY(_lock); // weak.
bool _capturedFirstFrame GUARDED_BY(_lock);
base::scoped_nsobject<AVCaptureSession> _captureSession;
......
......@@ -23,6 +23,7 @@
#import "media/capture/video/mac/video_capture_device_avfoundation_utils_mac.h"
#include "media/capture/video/mac/video_capture_device_factory_mac.h"
#include "media/capture/video/mac/video_capture_device_mac.h"
#import "media/capture/video/mac/video_capture_metrics_mac.h"
#include "media/capture/video_capture_types.h"
#include "services/video_capture/public/uma/video_capture_service_event.h"
#include "ui/gfx/geometry/size.h"
......@@ -162,6 +163,7 @@ AVCaptureDeviceFormat* FindBestCaptureFormat(
DISPATCH_QUEUE_SERIAL),
base::scoped_policy::ASSUME);
DCHECK(frameReceiver);
_capturedFirstFrame = false;
_weakPtrFactoryForTakePhoto =
std::make_unique<base::WeakPtrFactory<VideoCaptureDeviceAVFoundation>>(
self);
......@@ -350,6 +352,8 @@ AVCaptureDeviceFormat* FindBestCaptureFormat(
}
}
base::AutoLock lock(_lock);
_capturedFirstFrame = false;
return YES;
}
......@@ -692,6 +696,10 @@ AVCaptureDeviceFormat* FindBestCaptureFormat(
return;
const base::TimeDelta timestamp = GetCMSampleBufferTimestamp(sampleBuffer);
bool logUma = !std::exchange(_capturedFirstFrame, true);
if (logUma) {
media::LogFirstCapturedVideoFrame(_bestCaptureFormat, sampleBuffer);
}
// The SampleBufferTransformer CHECK-crashes if the sample buffer is not MJPEG
// and does not have a pixel buffer (https://crbug.com/1160647) so we fall
......
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_CAPTURE_VIDEO_MAC_VIDEO_CAPTURE_METRICS_MAC_H_
#define MEDIA_CAPTURE_VIDEO_MAC_VIDEO_CAPTURE_METRICS_MAC_H_
#import <AVFoundation/AVFoundation.h>
#include <CoreMedia/CoreMedia.h>
#import <Foundation/Foundation.h>
#include "media/capture/capture_export.h"
#include "ui/gfx/geometry/size.h"
namespace media {
CAPTURE_EXPORT
void LogFirstCapturedVideoFrame(const AVCaptureDeviceFormat* bestCaptureFormat,
const CMSampleBufferRef buffer);
} // namespace media
#endif // MEDIA_CAPTURE_VIDEO_MAC_VIDEO_CAPTURE_METRICS_MAC_H_
\ No newline at end of file
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "media/capture/video/mac/video_capture_metrics_mac.h"
#include "base/metrics/histogram_functions.h"
#import "media/capture/video/mac/video_capture_device_avfoundation_mac.h"
#include "media/capture/video/video_capture_device_info.h"
namespace media {
namespace {
enum class ResolutionComparison {
kWidthGtHeightEq = 0,
kWidthLtHeightEq = 1,
kWidthEqHeightGt = 2,
kWidthEqHeightLt = 3,
kEq = 4,
kWidthGtHeightGt = 5,
kWidthLtHeightGt = 6,
kWidthGtHeightLt = 7,
kWidthLtHeightLt = 8,
kMaxValue = kWidthLtHeightLt,
};
ResolutionComparison CompareDimensions(const CMVideoDimensions& requested,
const CMVideoDimensions& captured) {
if (requested.width > captured.width) {
if (requested.height > captured.height)
return ResolutionComparison::kWidthGtHeightGt;
if (requested.height < captured.height)
return ResolutionComparison::kWidthGtHeightLt;
return ResolutionComparison::kWidthGtHeightEq;
} else if (requested.width < captured.width) {
if (requested.height > captured.height)
return ResolutionComparison::kWidthLtHeightGt;
if (requested.height < captured.height)
return ResolutionComparison::kWidthLtHeightLt;
return ResolutionComparison::kWidthLtHeightEq;
} else {
if (requested.height > captured.height)
return ResolutionComparison::kWidthEqHeightGt;
if (requested.height < captured.height)
return ResolutionComparison::kWidthEqHeightLt;
return ResolutionComparison::kEq;
}
}
} // namespace
void LogFirstCapturedVideoFrame(const AVCaptureDeviceFormat* bestCaptureFormat,
const CMSampleBufferRef buffer) {
if (bestCaptureFormat) {
const CMFormatDescriptionRef requestedFormat =
[bestCaptureFormat formatDescription];
base::UmaHistogramEnumeration(
"Media.VideoCapture.Mac.Device.RequestedPixelFormat",
[VideoCaptureDeviceAVFoundation
FourCCToChromiumPixelFormat:CMFormatDescriptionGetMediaSubType(
requestedFormat)],
media::VideoPixelFormat::PIXEL_FORMAT_MAX);
if (buffer) {
const CMFormatDescriptionRef capturedFormat =
CMSampleBufferGetFormatDescription(buffer);
base::UmaHistogramBoolean(
"Media.VideoCapture.Mac.Device.CapturedWithRequestedPixelFormat",
CMFormatDescriptionGetMediaSubType(capturedFormat) ==
CMFormatDescriptionGetMediaSubType(requestedFormat));
base::UmaHistogramEnumeration(
"Media.VideoCapture.Mac.Device.CapturedWithRequestedResolution",
CompareDimensions(
CMVideoFormatDescriptionGetDimensions(requestedFormat),
CMVideoFormatDescriptionGetDimensions(capturedFormat)));
const CVPixelBufferRef pixelBufferRef =
CMSampleBufferGetImageBuffer(buffer);
bool is_io_sufrace =
pixelBufferRef && CVPixelBufferGetIOSurface(pixelBufferRef);
base::UmaHistogramBoolean(
"Media.VideoCapture.Mac.Device.CapturedIOSurface", is_io_sufrace);
}
}
}
} // namespace media
\ No newline at end of file
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "media/capture/video/mac/video_capture_metrics_mac.h"
#import <AVFoundation/AVFoundation.h>
#include <CoreMedia/CoreMedia.h>
#import <Foundation/Foundation.h>
#include "base/mac/scoped_cftyperef.h"
#include "base/test/metrics/histogram_tester.h"
#include "media/base/video_types.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#include "third_party/ocmock/gtest_support.h"
namespace media {
namespace {} // namespace
TEST(VideoCaptureMetricsMacTest, NoMetricsLoggedIfNullRequestedCaptureFormat) {
base::HistogramTester histogram_tester;
LogFirstCapturedVideoFrame(nullptr, nullptr);
EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("Media."),
testing::IsEmpty());
}
TEST(VideoCaptureMetricsMacTest, LogRequestedPixelFormat) {
base::HistogramTester histogram_tester;
base::ScopedCFTypeRef<CMFormatDescriptionRef> requested_format;
OSStatus status = CMVideoFormatDescriptionCreate(
kCFAllocatorDefault,
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange /*NV12*/, 320, 180,
nullptr, requested_format.InitializeInto());
ASSERT_EQ(0, status);
id capture_format = OCMClassMock([AVCaptureDeviceFormat class]);
OCMStub([capture_format formatDescription]).andReturn(requested_format.get());
LogFirstCapturedVideoFrame(capture_format, nullptr);
EXPECT_THAT(histogram_tester.GetAllSamples(
"Media.VideoCapture.Mac.Device.RequestedPixelFormat"),
testing::UnorderedElementsAre(
base::Bucket(VideoPixelFormat::PIXEL_FORMAT_NV12, 1)));
}
TEST(VideoCaptureMetricsMacTest, LogFirstFrameWhenAsRequested) {
base::HistogramTester histogram_tester;
base::ScopedCFTypeRef<CMFormatDescriptionRef> requested_format;
OSStatus status = CMVideoFormatDescriptionCreate(
kCFAllocatorDefault,
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange /*NV12*/, 320, 180,
nullptr, requested_format.InitializeInto());
ASSERT_EQ(0, status);
id capture_format = OCMClassMock([AVCaptureDeviceFormat class]);
OCMStub([capture_format formatDescription]).andReturn(requested_format.get());
// First frame equal.
base::ScopedCFTypeRef<CMSampleBufferRef> first_frame;
status = CMSampleBufferCreate(kCFAllocatorDefault, nullptr, false, nullptr,
nullptr, requested_format, 0, 0, nullptr, 0,
nullptr, first_frame.InitializeInto());
ASSERT_EQ(0, status);
LogFirstCapturedVideoFrame(capture_format, first_frame);
EXPECT_THAT(histogram_tester.GetAllSamples(
"Media.VideoCapture.Mac.Device.RequestedPixelFormat"),
testing::UnorderedElementsAre(
base::Bucket(VideoPixelFormat::PIXEL_FORMAT_NV12, 1)));
EXPECT_THAT(
histogram_tester.GetAllSamples(
"Media.VideoCapture.Mac.Device.CapturedWithRequestedPixelFormat"),
testing::UnorderedElementsAre(base::Bucket(1, 1)));
EXPECT_THAT(
histogram_tester.GetAllSamples(
"Media.VideoCapture.Mac.Device.CapturedWithRequestedResolution"),
testing::UnorderedElementsAre(base::Bucket(4, 1)));
EXPECT_THAT(histogram_tester.GetAllSamples(
"Media.VideoCapture.Mac.Device.CapturedIOSurface"),
testing::UnorderedElementsAre(base::Bucket(0, 1)));
}
} // namespace media
......@@ -65104,6 +65104,20 @@ https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
<int value="5" label="RESOLVE_SPECULATIVE_ABORT"/>
</enum>
<enum name="ResolutionComparison">
<int value="0" label="Requested width larger than capture"/>
<int value="1" label="Requested width smaller than capture"/>
<int value="2" label="Requested height larger than capture"/>
<int value="3" label="Requested height smaller than capture"/>
<int value="4" label="Requested resolution equals capture"/>
<int value="5" label="Requested width and height larger than capture"/>
<int value="6"
label="Requested width smaller, and height larger than capture"/>
<int value="7"
label="Requested width larger and height smaller than capture"/>
<int value="8" label="Requested width and height smaller than capture"/>
</enum>
<enum name="ResolutionUnspecWasteCategory">
<int value="0" label="AF_WASTE_IPV4_ONLY">
Running in a IPv4-only configuration. No waste.
......@@ -3877,6 +3877,51 @@ reviews. Googlers can read more about this at go/gwsq-gerrit.
</summary>
</histogram>
<histogram name="Media.VideoCapture.Mac.Device.CapturedIOSurface"
enum="Boolean" expires_after="M92">
<owner>eshr@google.com</owner>
<owner>handellm@google.com</owner>
<summary>
A count of how often the capture device delivers an IOSurface to the capture
pipeline. This is recorded once per opening of the camera, when the first
video frame is captured.
</summary>
</histogram>
<histogram
name="Media.VideoCapture.Mac.Device.CapturedWithRequestedPixelFormat"
enum="Boolean" expires_after="M92">
<owner>eshr@google.com</owner>
<owner>handellm@google.com</owner>
<summary>
A boolean count of how often the requested pixel format is the one that was
actually captured. This is recorded once per opening of the camera, when the
first video frame is captured.
</summary>
</histogram>
<histogram name="Media.VideoCapture.Mac.Device.CapturedWithRequestedResolution"
enum="ResolutionComparison" expires_after="M92">
<owner>eshr@google.com</owner>
<owner>handellm@google.com</owner>
<summary>
An enumeration count of whether or not the requested resolution equals the
captured resolution, detailing which dimensions differ in the enum. This is
recorded once per opening of the camera, when the first video frame is
captured.
</summary>
</histogram>
<histogram name="Media.VideoCapture.Mac.Device.RequestedPixelFormat"
enum="VideoResolutionDesignation" expires_after="M92">
<owner>eshr@google.com</owner>
<owner>handellm@google.com</owner>
<summary>
Counts the pixel formats requested by the VideoCaptureDevice on Mac. This is
recorded when the first video frame is captured.
</summary>
</histogram>
<histogram name="Media.VideoCapture.MacBook.AttemptCountWhenNoCamera"
units="attempts" expires_after="2020-03-15">
<owner>chfremer@chromium.org</owner>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment