Log MediaCodec errors to UMA
Currently the errors are returned as a TypedStatus so they are visible
to users via chrome://media-internals. However, the UMA used for
PipelineStatus just logs "Error", so the detailed reason is lost. Adding
a new UMA to record the error returned by MediaCodec. Like the
PipelineStatus UMA, there is one for each different video codec, and
each are split on whether they use software or hardware secure codecs.
Tested by modifying the code to simulate an error and seeing it reported
via chrome://histograms.
- Histogram: Media.MediaCodecError.H264.HardwareSecure recorded 1 samples, mean = 11.0 (flags = 0x41) [#]
0 ...
11 -O (1 = 100.0%) {0.0%}
12 ...
Bug: 376720476
Test: see above
Change-Id: I59cd978832f67af813fe95ed22645f189c5f5b39
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5983407
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: John Rummell <jrummell@chromium.org>
Reviewed-by: Evan Liu <evliu@google.com>
Cr-Commit-Position: refs/heads/main@{#1376710}
diff --git a/media/base/android/media_codec_bridge.h b/media/base/android/media_codec_bridge.h
index c6e0682..ef32ce3 100644
--- a/media/base/android/media_codec_bridge.h
+++ b/media/base/android/media_codec_bridge.h
@@ -36,28 +36,31 @@
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.media
// GENERATED_JAVA_PREFIX_TO_STRIP: MEDIA_CODEC_
+// These enums are also reported to UMA so values should not be renumbered or
+// reused.
enum MediaCodecStatus {
- MEDIA_CODEC_OK,
- MEDIA_CODEC_TRY_AGAIN_LATER,
- MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED,
- MEDIA_CODEC_OUTPUT_FORMAT_CHANGED,
- MEDIA_CODEC_NO_KEY,
- MEDIA_CODEC_ERROR,
- MEDIA_CODEC_KEY_EXPIRED,
- MEDIA_CODEC_RESOURCE_BUSY,
- MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION,
- MEDIA_CODEC_SESSION_NOT_OPENED,
- MEDIA_CODEC_UNSUPPORTED_OPERATION,
- MEDIA_CODEC_INSUFFICIENT_SECURITY,
- MEDIA_CODEC_FRAME_TOO_LARGE,
- MEDIA_CODEC_LOST_STATE,
- MEDIA_CODEC_GENERIC_OEM,
- MEDIA_CODEC_GENERIC_PLUGIN,
- MEDIA_CODEC_LICENSE_PARSE,
- MEDIA_CODEC_MEDIA_FRAMEWORK,
- MEDIA_CODEC_ZERO_SUBSAMPLES,
- MEDIA_CODEC_UNKNOWN_CIPHER_MODE,
- MEDIA_CODEC_PATTERN_ENCRYPTION_NOT_SUPPORTED,
+ MEDIA_CODEC_OK = 0,
+ MEDIA_CODEC_TRY_AGAIN_LATER = 1,
+ MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED = 2,
+ MEDIA_CODEC_OUTPUT_FORMAT_CHANGED = 3,
+ MEDIA_CODEC_NO_KEY = 4,
+ MEDIA_CODEC_ERROR = 5,
+ MEDIA_CODEC_KEY_EXPIRED = 6,
+ MEDIA_CODEC_RESOURCE_BUSY = 7,
+ MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION = 8,
+ MEDIA_CODEC_SESSION_NOT_OPENED = 9,
+ MEDIA_CODEC_UNSUPPORTED_OPERATION = 10,
+ MEDIA_CODEC_INSUFFICIENT_SECURITY = 11,
+ MEDIA_CODEC_FRAME_TOO_LARGE = 12,
+ MEDIA_CODEC_LOST_STATE = 13,
+ MEDIA_CODEC_GENERIC_OEM = 14,
+ MEDIA_CODEC_GENERIC_PLUGIN = 15,
+ MEDIA_CODEC_LICENSE_PARSE = 16,
+ MEDIA_CODEC_MEDIA_FRAMEWORK = 17,
+ MEDIA_CODEC_ZERO_SUBSAMPLES = 18,
+ MEDIA_CODEC_UNKNOWN_CIPHER_MODE = 19,
+ MEDIA_CODEC_PATTERN_ENCRYPTION_NOT_SUPPORTED = 20,
+ MEDIA_CODEC_MAX = MEDIA_CODEC_PATTERN_ENCRYPTION_NOT_SUPPORTED,
};
struct MediaCodecResultTraits {
diff --git a/media/base/android/media_codec_bridge_impl.cc b/media/base/android/media_codec_bridge_impl.cc
index 1dbc33e..04184d5 100644
--- a/media/base/android/media_codec_bridge_impl.cc
+++ b/media/base/android/media_codec_bridge_impl.cc
@@ -23,16 +23,17 @@
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "media/base/android/jni_hdr_metadata.h"
+#include "media/base/android/media_codec_bridge.h"
#include "media/base/android/media_codec_util.h"
#include "media/base/audio_codecs.h"
#include "media/base/media_switches.h"
#include "media/base/subsample_entry.h"
#include "media/base/video_codecs.h"
-#include "media_codec_bridge.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "media/base/android/media_jni_headers/MediaCodecBridgeBuilder_jni.h"
@@ -114,8 +115,9 @@
return false;
}
header_length[i] += size;
- if (size < 0xFF)
+ if (size < 0xFF) {
break;
+ }
}
if (total_length >= extra_data_size) {
LOG(ERROR) << "Invalid vorbis header size in the extra data";
@@ -318,8 +320,9 @@
const std::string mime = MediaCodecUtil::CodecToAndroidMimeType(
config.codec(), config.target_output_sample_format());
- if (mime.empty())
+ if (mime.empty()) {
return nullptr;
+ }
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> j_mime = ConvertUTF8ToJavaString(env, mime);
@@ -344,20 +347,22 @@
j_csd0, j_csd1, j_csd2, output_frame_has_adts_header,
!!on_buffers_available_cb));
- if (j_bridge.is_null())
+ if (j_bridge.is_null()) {
return nullptr;
+ }
- return base::WrapUnique(
- new MediaCodecBridgeImpl(CodecType::kAny, std::move(j_bridge),
- std::move(on_buffers_available_cb)));
+ return base::WrapUnique(new MediaCodecBridgeImpl(
+ CodecType::kAny, std::nullopt, std::move(j_bridge),
+ std::move(on_buffers_available_cb)));
}
// static
std::unique_ptr<MediaCodecBridge> MediaCodecBridgeImpl::CreateVideoDecoder(
const VideoCodecConfig& config) {
const std::string mime = MediaCodecUtil::CodecToAndroidMimeType(config.codec);
- if (mime.empty())
+ if (mime.empty()) {
return nullptr;
+ }
JNIEnv* env = AttachCurrentThread();
auto j_mime = ConvertUTF8ToJavaString(env, mime);
@@ -381,11 +386,13 @@
/*useAsyncApi=*/!!config.on_buffers_available_cb,
/*useBlockModel=*/config.use_block_model, j_decoder_name,
config.profile));
- if (j_bridge.is_null())
+ if (j_bridge.is_null()) {
return nullptr;
+ }
return base::WrapUnique(new MediaCodecBridgeImpl(
- config.codec_type, std::move(j_bridge), config.on_buffers_available_cb));
+ config.codec_type, config.codec, std::move(j_bridge),
+ config.on_buffers_available_cb));
}
// static
@@ -397,8 +404,9 @@
int i_frame_interval,
int color_format) {
const std::string mime = MediaCodecUtil::CodecToAndroidMimeType(codec);
- if (mime.empty())
+ if (mime.empty()) {
return nullptr;
+ }
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> j_mime = ConvertUTF8ToJavaString(env, mime);
@@ -407,11 +415,12 @@
env, j_mime, size.width(), size.height(), kBitrateModeCBR, bit_rate,
frame_rate, i_frame_interval, color_format));
- if (j_bridge.is_null())
+ if (j_bridge.is_null()) {
return nullptr;
+ }
- return base::WrapUnique(
- new MediaCodecBridgeImpl(CodecType::kAny, std::move(j_bridge)));
+ return base::WrapUnique(new MediaCodecBridgeImpl(
+ CodecType::kAny, std::nullopt, std::move(j_bridge)));
}
// static
@@ -422,15 +431,18 @@
MediaCodecBridgeImpl::MediaCodecBridgeImpl(
CodecType codec_type,
+ std::optional<VideoCodec> video_decoder_codec,
ScopedJavaGlobalRef<jobject> j_bridge,
base::RepeatingClosure on_buffers_available_cb)
: codec_type_(codec_type),
+ video_decoder_codec_(std::move(video_decoder_codec)),
on_buffers_available_cb_(std::move(on_buffers_available_cb)),
j_bridge_(std::move(j_bridge)) {
DCHECK(!j_bridge_.is_null());
- if (!on_buffers_available_cb_)
+ if (!on_buffers_available_cb_) {
return;
+ }
// Note this should be done last since setBuffersAvailableListener() may
// immediately invoke the callback if buffers came in during construction.
@@ -440,8 +452,9 @@
MediaCodecBridgeImpl::~MediaCodecBridgeImpl() {
JNIEnv* env = AttachCurrentThread();
- if (j_bridge_.obj())
+ if (j_bridge_.obj()) {
Java_MediaCodecBridge_release(env, j_bridge_);
+ }
}
void MediaCodecBridgeImpl::Stop() {
@@ -453,6 +466,7 @@
JNIEnv* env = AttachCurrentThread();
MediaCodecStatus status = static_cast<MediaCodecStatus>(
Java_MediaCodecBridge_flush(env, j_bridge_));
+ ReportAnyErrorToUMA(status);
return {ConvertToMediaCodecEnum(status), ApplyDescriptiveMessage(status)};
}
@@ -622,6 +636,7 @@
static_cast<MediaCodecStatus>(Java_MediaCodecBridge_queueInputBuffer(
env, j_bridge_, index, 0, data_size,
presentation_time.InMicroseconds(), 0));
+ ReportAnyErrorToUMA(status);
return {ConvertToMediaCodecEnum(status), ApplyDescriptiveMessage(status)};
}
@@ -656,6 +671,7 @@
presentation_time.InMicroseconds(),
is_eos ? kBufferFlagEndOfStream : 0));
Java_ObtainBlockResult_recycle(env, j_result);
+ ReportAnyErrorToUMA(status);
return {ConvertToMediaCodecEnum(status), ApplyDescriptiveMessage(status)};
}
@@ -719,6 +735,7 @@
static_cast<int>(
encryption_pattern ? encryption_pattern->skip_byte_block() : 0),
presentation_time.InMicroseconds()));
+ ReportAnyErrorToUMA(status);
return {ConvertToMediaCodecEnum(status), ApplyDescriptiveMessage(status)};
}
@@ -739,6 +756,7 @@
MediaCodecStatus status = static_cast<MediaCodecStatus>(
Java_DequeueInputResult_status(env, result));
DVLOG(3) << __func__ << ": status: " << status << ", index: " << *index;
+ ReportAnyErrorToUMA(status);
return {ConvertToMediaCodecEnum(status), ApplyDescriptiveMessage(status)};
}
@@ -764,15 +782,18 @@
Java_DequeueOutputResult_presentationTimeMicroseconds(env, result));
}
int flags = Java_DequeueOutputResult_flags(env, result);
- if (end_of_stream)
+ if (end_of_stream) {
*end_of_stream = flags & kBufferFlagEndOfStream;
- if (key_frame)
+ }
+ if (key_frame) {
*key_frame = flags & kBufferFlagSyncFrame;
+ }
MediaCodecStatus status = static_cast<MediaCodecStatus>(
Java_DequeueOutputResult_status(env, result));
DVLOG(3) << __func__ << ": status: " << status << ", index: " << *index
<< ", offset: " << *offset << ", size: " << *size
<< ", flags: " << flags;
+ ReportAnyErrorToUMA(status);
return {ConvertToMediaCodecEnum(status), ApplyDescriptiveMessage(status)};
}
@@ -865,4 +886,24 @@
return true;
}
+void MediaCodecBridgeImpl::ReportAnyErrorToUMA(MediaCodecStatus status) {
+ // Only reporting errors for known video streams. Audio streams and video
+ // encoding are skipped.
+ if (!video_decoder_codec_.has_value()) {
+ return;
+ }
+
+ // Don't bother reporting `status` if it's not a error that stops playback.
+ if (ConvertToMediaCodecEnum(status) != MediaCodecResult::Codes::kError) {
+ return;
+ }
+
+ std::string uma_name =
+ "Media.MediaCodecError." +
+ GetCodecNameForUMA(video_decoder_codec_.value()) +
+ (IsSoftwareCodec() ? ".SoftwareSecure" : ".HardwareSecure");
+ base::UmaHistogramExactLinear(uma_name, status,
+ MediaCodecStatus::MEDIA_CODEC_MAX + 1);
+}
+
} // namespace media
diff --git a/media/base/android/media_codec_bridge_impl.h b/media/base/android/media_codec_bridge_impl.h
index b14145c..92fd909 100644
--- a/media/base/android/media_codec_bridge_impl.h
+++ b/media/base/android/media_codec_bridge_impl.h
@@ -172,6 +172,7 @@
private:
MediaCodecBridgeImpl(CodecType codec_type,
+ std::optional<VideoCodec> video_decoder_codec,
base::android::ScopedJavaGlobalRef<jobject> j_bridge,
base::RepeatingClosure on_buffers_available_cb =
base::RepeatingClosure());
@@ -193,8 +194,13 @@
JNIEnv* /* env */,
const base::android::JavaParamRef<jobject>& /* obj */) override;
+ void ReportAnyErrorToUMA(MediaCodecStatus status);
+
const CodecType codec_type_;
+ // Keep track of the codec used for decoding.
+ const std::optional<VideoCodec> video_decoder_codec_;
+
base::RepeatingClosure on_buffers_available_cb_;
// The Java MediaCodecBridge instance.
diff --git a/tools/metrics/histograms/metadata/media/enums.xml b/tools/metrics/histograms/metadata/media/enums.xml
index a3007ae4b..16854dc8 100644
--- a/tools/metrics/histograms/metadata/media/enums.xml
+++ b/tools/metrics/histograms/metadata/media/enums.xml
@@ -1069,6 +1069,30 @@
<int value="7" label="Not shown because incognito"/>
</enum>
+<enum name="MediaCodecError">
+ <int value="0" label="MEDIA_CODEC_OK"/>
+ <int value="1" label="MEDIA_CODEC_TRY_AGAIN_LATER"/>
+ <int value="2" label="MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED"/>
+ <int value="3" label="MEDIA_CODEC_OUTPUT_FORMAT_CHANGED"/>
+ <int value="4" label="MEDIA_CODEC_NO_KEY"/>
+ <int value="5" label="MEDIA_CODEC_ERROR"/>
+ <int value="6" label="MEDIA_CODEC_KEY_EXPIRED"/>
+ <int value="7" label="MEDIA_CODEC_RESOURCE_BUSY"/>
+ <int value="8" label="MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION"/>
+ <int value="9" label="MEDIA_CODEC_SESSION_NOT_OPENED"/>
+ <int value="10" label="MEDIA_CODEC_UNSUPPORTED_OPERATION"/>
+ <int value="11" label="MEDIA_CODEC_INSUFFICIENT_SECURITY"/>
+ <int value="12" label="MEDIA_CODEC_FRAME_TOO_LARGE"/>
+ <int value="13" label="MEDIA_CODEC_LOST_STATE"/>
+ <int value="14" label="MEDIA_CODEC_GENERIC_OEM"/>
+ <int value="15" label="MEDIA_CODEC_GENERIC_PLUGIN"/>
+ <int value="16" label="MEDIA_CODEC_LICENSE_PARSE"/>
+ <int value="17" label="MEDIA_CODEC_MEDIA_FRAMEWORK"/>
+ <int value="18" label="MEDIA_CODEC_ZERO_SUBSAMPLES"/>
+ <int value="19" label="MEDIA_CODEC_UNKNOWN_CIPHER_MODE"/>
+ <int value="20" label="MEDIA_CODEC_PATTERN_ENCRYPTION_NOT_SUPPORTED"/>
+</enum>
+
<enum name="MediaContainers">
<int value="0" label="Unknown"/>
<int value="1" label="AAC (Advanced Audio Coding)"/>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index adc1699..536e793a 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -3978,6 +3978,21 @@
</summary>
</histogram>
+<histogram name="Media.MediaCodecError.{VideoCodec}.{Secure}"
+ enum="MediaCodecError" expires_after="2025-06-30">
+ <owner>jrummell@chromium.org</owner>
+ <owner>media-dev-uma@chromium.org</owner>
+ <summary>
+ Error reported by MediaCodec video streams using codec {VideoCodec} with
+ {Secure}. Reported at most once per playback if an error occurs.
+ </summary>
+ <token key="VideoCodec" variants="VideoCodec"/>
+ <token key="Secure">
+ <variant name="HardwareSecure" summary="hardware secure decryption"/>
+ <variant name="SoftwareSecure" summary="software secure decryption"/>
+ </token>
+</histogram>
+
<histogram name="Media.MediaDevices.EnumerateDevices.Latency" units="ms"
expires_after="2025-03-30">
<owner>toprice@chromium.org</owner>