Add MediaCapabilities support for DolbyVision codecs.
https://bugs.webkit.org/show_bug.cgi?id=203170

Reviewed by Eric Carlson.

Source/WebCore:

Test: media/dovi-codec-parameters.html

Add support for parsing DolbyVision (DoVi) style codec profile strings.

* platform/cocoa/VideoToolboxSoftLink.cpp:
* platform/cocoa/VideoToolboxSoftLink.h:
* platform/graphics/HEVCUtilities.cpp:
(WebCore::codecStringForDoViCodecType):
(WebCore::profileIDForAlphabeticDoViProfile):
(WebCore::isValidDoViProfileID):
(WebCore::maximumLevelIDForDoViProfileID):
(WebCore::isValidProfileIDForCodecName):
(WebCore::parseDoViCodecParameters):
* platform/graphics/HEVCUtilities.h:
* platform/graphics/cocoa/HEVCUtilitiesCocoa.h:
* platform/graphics/cocoa/HEVCUtilitiesCocoa.mm:
(WebCore::codecStringToCodecTypeMap):
(WebCore::CFStringArrayToNumberVector):
(WebCore::validateDoViParameters):
* platform/graphics/cocoa/MediaEngineConfigurationFactoryCocoa.cpp:
(WebCore::createMediaPlayerDecodingConfigurationCocoa):
* testing/Internals.cpp:
(WebCore::Internals::parseDoViCodecParameters):
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

* media/dovi-codec-parameters-expected.txt: Added.
* media/dovi-codec-parameters.html: Added.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251396 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index bfdf6a0..db00f15 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,13 @@
+2019-10-21  Jer Noble  <jer.noble@apple.com>
+
+        Add MediaCapabilities support for DolbyVision codecs.
+        https://bugs.webkit.org/show_bug.cgi?id=203170
+
+        Reviewed by Eric Carlson.
+
+        * media/dovi-codec-parameters-expected.txt: Added.
+        * media/dovi-codec-parameters.html: Added.
+
 2019-10-21  John Wilander  <wilander@apple.com>
 
         Resource Load Statistics: Update cookie blocking in NetworkStorageSession after first user interaction
diff --git a/LayoutTests/media/dovi-codec-parameters-expected.txt b/LayoutTests/media/dovi-codec-parameters-expected.txt
new file mode 100644
index 0000000..79eeb5f
--- /dev/null
+++ b/LayoutTests/media/dovi-codec-parameters-expected.txt
@@ -0,0 +1,16 @@
+EXPECTED (internals.parseDoViCodecParameters("bad-parameter") == 'null') OK
+EXPECTED (internals.parseDoViCodecParameters("dvh1") == 'null') OK
+EXPECTED (internals.parseDoViCodecParameters("dvh1.04.09") === '{ hvc1, 4, 9 }') OK
+EXPECTED (internals.parseDoViCodecParameters("dvh1.05.13") === '{ hvc1, 5, 13 }') OK
+EXPECTED (internals.parseDoViCodecParameters("dvhe.07.09") === '{ hev1, 7, 9 }') OK
+EXPECTED (internals.parseDoViCodecParameters("dvhe.dtr.9") === '{ hev1, 4, 9 }') OK
+EXPECTED (internals.parseDoViCodecParameters("dvhe.stn.9") === '{ hev1, 5, 9 }') OK
+EXPECTED (internals.parseDoViCodecParameters("dvhe.dtb.9") === '{ hev1, 7, 9 }') OK
+EXPECTED (internals.parseDoViCodecParameters("dvhe.st.9") === '{ hev1, 8, 9 }') OK
+EXPECTED (internals.parseDoViCodecParameters("dvav.se.5") === '{ avc3, 9, 5 }') OK
+EXPECTED (internals.parseDoViCodecParameters("dvh1.06.01") == 'null') OK
+EXPECTED (internals.parseDoViCodecParameters("dvhe.03.01") == 'null') OK
+EXPECTED (internals.parseDoViCodecParameters("dvh1.09.01") == 'null') OK
+EXPECTED (internals.parseDoViCodecParameters("dvav.04.01") == 'null') OK
+END OF TEST
+
diff --git a/LayoutTests/media/dovi-codec-parameters.html b/LayoutTests/media/dovi-codec-parameters.html
new file mode 100644
index 0000000..2608062
--- /dev/null
+++ b/LayoutTests/media/dovi-codec-parameters.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="video-test.js"></script>
+    <script>
+    function DoViParameterSetToString(set) {
+        if (!set)
+            return `null`;
+        return `{ ${set.codecName}, ${set.bitstreamProfileID }, ${set.bitstreamLevelID } }`;
+    }
+    function isEqualDoViParameterSet(setA, setB) {
+        return setA && setB 
+            && setA.codecName === setB.codecName
+            && setA.bitstreamProfileID === setB.bitstreamProfileID
+            && setA.bitstreamLevelID === setB.bitstreamLevelID
+    }
+    function makeDoViParameterSet(codecName, bitstreamProfileID, bitstreamLevelID)
+    {
+        return {
+            codecName: codecName,
+            bitstreamProfileID: bitstreamProfileID,
+            bitstreamLevelID: bitstreamLevelID,
+        };
+    }
+    function testExpectedDoViParameterSet(testFuncString, expected)
+    {
+        let observed = eval(testFuncString);
+        let success = isEqualDoViParameterSet(observed, expected);
+        reportExpected(success, testFuncString, '===', DoViParameterSetToString(expected), DoViParameterSetToString(observed));
+    }
+    window.addEventListener('load', event => {
+        testExpected('internals.parseDoViCodecParameters("bad-parameter")', null);
+        testExpected('internals.parseDoViCodecParameters("dvh1")', null);
+        testExpectedDoViParameterSet('internals.parseDoViCodecParameters("dvh1.04.09")', makeDoViParameterSet('hvc1', 4, 9));
+        testExpectedDoViParameterSet('internals.parseDoViCodecParameters("dvh1.05.13")', makeDoViParameterSet('hvc1', 5, 13));
+        testExpectedDoViParameterSet('internals.parseDoViCodecParameters("dvhe.07.09")', makeDoViParameterSet('hev1', 7, 9));
+        testExpectedDoViParameterSet('internals.parseDoViCodecParameters("dvhe.dtr.9")', makeDoViParameterSet('hev1', 4, 9));
+        testExpectedDoViParameterSet('internals.parseDoViCodecParameters("dvhe.stn.9")', makeDoViParameterSet('hev1', 5, 9));
+        testExpectedDoViParameterSet('internals.parseDoViCodecParameters("dvhe.dtb.9")', makeDoViParameterSet('hev1', 7, 9));
+        testExpectedDoViParameterSet('internals.parseDoViCodecParameters("dvhe.st.9")', makeDoViParameterSet('hev1', 8, 9));
+        testExpectedDoViParameterSet('internals.parseDoViCodecParameters("dvav.se.5")', makeDoViParameterSet('avc3', 9, 5));
+        testExpected('internals.parseDoViCodecParameters("dvh1.06.01")', null);
+        testExpected('internals.parseDoViCodecParameters("dvhe.03.01")', null);
+        testExpected('internals.parseDoViCodecParameters("dvh1.09.01")', null);
+        testExpected('internals.parseDoViCodecParameters("dvav.04.01")', null);
+
+        endTest();
+    }, { once: true });
+    </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 969d412..2f34d4c 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,36 @@
+2019-10-21  Jer Noble  <jer.noble@apple.com>
+
+        Add MediaCapabilities support for DolbyVision codecs.
+        https://bugs.webkit.org/show_bug.cgi?id=203170
+
+        Reviewed by Eric Carlson.
+
+        Test: media/dovi-codec-parameters.html
+
+        Add support for parsing DolbyVision (DoVi) style codec profile strings.
+
+        * platform/cocoa/VideoToolboxSoftLink.cpp:
+        * platform/cocoa/VideoToolboxSoftLink.h:
+        * platform/graphics/HEVCUtilities.cpp:
+        (WebCore::codecStringForDoViCodecType):
+        (WebCore::profileIDForAlphabeticDoViProfile):
+        (WebCore::isValidDoViProfileID):
+        (WebCore::maximumLevelIDForDoViProfileID):
+        (WebCore::isValidProfileIDForCodecName):
+        (WebCore::parseDoViCodecParameters):
+        * platform/graphics/HEVCUtilities.h:
+        * platform/graphics/cocoa/HEVCUtilitiesCocoa.h:
+        * platform/graphics/cocoa/HEVCUtilitiesCocoa.mm:
+        (WebCore::codecStringToCodecTypeMap):
+        (WebCore::CFStringArrayToNumberVector):
+        (WebCore::validateDoViParameters):
+        * platform/graphics/cocoa/MediaEngineConfigurationFactoryCocoa.cpp:
+        (WebCore::createMediaPlayerDecodingConfigurationCocoa):
+        * testing/Internals.cpp:
+        (WebCore::Internals::parseDoViCodecParameters):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2019-10-21  Dean Jackson  <dino@apple.com>
 
         Dispatch AR event on the originating anchor element
diff --git a/Source/WebCore/platform/cocoa/VideoToolboxSoftLink.cpp b/Source/WebCore/platform/cocoa/VideoToolboxSoftLink.cpp
index 1d56365..e936512 100644
--- a/Source/WebCore/platform/cocoa/VideoToolboxSoftLink.cpp
+++ b/Source/WebCore/platform/cocoa/VideoToolboxSoftLink.cpp
@@ -74,6 +74,9 @@
 SOFT_LINK_CONSTANT_MAY_FAIL_FOR_SOURCE(WebCore, VideoToolbox, kVTHEVCDecoderProfileCapability_IsHardwareAccelerated, CFStringRef)
 SOFT_LINK_CONSTANT_MAY_FAIL_FOR_SOURCE(WebCore, VideoToolbox, kVTHEVCDecoderProfileCapability_MaxDecodeLevel, CFStringRef)
 SOFT_LINK_CONSTANT_MAY_FAIL_FOR_SOURCE(WebCore, VideoToolbox, kVTHEVCDecoderProfileCapability_MaxPlaybackLevel, CFStringRef)
+SOFT_LINK_CONSTANT_MAY_FAIL_FOR_SOURCE(WebCore, VideoToolbox, kVTDolbyVisionDecoderCapability_SupportedProfiles, CFStringRef)
+SOFT_LINK_CONSTANT_MAY_FAIL_FOR_SOURCE(WebCore, VideoToolbox, kVTDolbyVisionDecoderCapability_SupportedLevels, CFStringRef)
+SOFT_LINK_CONSTANT_MAY_FAIL_FOR_SOURCE(WebCore, VideoToolbox, kVTDolbyVisionDecoderCapability_IsHardwareAccelerated, CFStringRef)
 
 SOFT_LINK_FUNCTION_FOR_SOURCE(WebCore, VideoToolbox, VTPixelBufferConformerCreateWithAttributes, OSStatus, (CFAllocatorRef allocator, CFDictionaryRef attributes, VTPixelBufferConformerRef* conformerOut), (allocator, attributes, conformerOut));
 SOFT_LINK_FUNCTION_FOR_SOURCE(WebCore, VideoToolbox, VTPixelBufferConformerIsConformantPixelBuffer, Boolean, (VTPixelBufferConformerRef conformer, CVPixelBufferRef pixBuf), (conformer, pixBuf))
diff --git a/Source/WebCore/platform/cocoa/VideoToolboxSoftLink.h b/Source/WebCore/platform/cocoa/VideoToolboxSoftLink.h
index f156547..62533ac 100644
--- a/Source/WebCore/platform/cocoa/VideoToolboxSoftLink.h
+++ b/Source/WebCore/platform/cocoa/VideoToolboxSoftLink.h
@@ -110,6 +110,12 @@
 #define kVTHEVCDecoderProfileCapability_MaxDecodeLevel get_VideoToolbox_kVTHEVCDecoderProfileCapability_MaxDecodeLevel()
 SOFT_LINK_CONSTANT_MAY_FAIL_FOR_HEADER(WebCore, VideoToolbox, kVTHEVCDecoderProfileCapability_MaxPlaybackLevel, CFStringRef)
 #define kVTHEVCDecoderProfileCapability_MaxPlaybackLevel get_VideoToolbox_kVTHEVCDecoderProfileCapability_MaxPlaybackLevel()
+SOFT_LINK_CONSTANT_MAY_FAIL_FOR_HEADER(WebCore, VideoToolbox, kVTDolbyVisionDecoderCapability_SupportedProfiles, CFStringRef)
+#define kVTDolbyVisionDecoderCapability_SupportedProfiles get_VideoToolbox_kVTDolbyVisionDecoderCapability_SupportedProfiles()
+SOFT_LINK_CONSTANT_MAY_FAIL_FOR_HEADER(WebCore, VideoToolbox, kVTDolbyVisionDecoderCapability_SupportedLevels, CFStringRef)
+#define kVTDolbyVisionDecoderCapability_SupportedLevels get_VideoToolbox_kVTDolbyVisionDecoderCapability_SupportedLevels()
+SOFT_LINK_CONSTANT_MAY_FAIL_FOR_HEADER(WebCore, VideoToolbox, kVTDolbyVisionDecoderCapability_IsHardwareAccelerated, CFStringRef)
+#define kVTDolbyVisionDecoderCapability_IsHardwareAccelerated get_VideoToolbox_kVTDolbyVisionDecoderCapability_IsHardwareAccelerated()
 
 SOFT_LINK_FUNCTION_FOR_HEADER(WebCore, VideoToolbox, VTPixelBufferConformerCreateWithAttributes, OSStatus, (CFAllocatorRef allocator, CFDictionaryRef attributes, VTPixelBufferConformerRef* conformerOut), (allocator, attributes, conformerOut));
 #define VTPixelBufferConformerCreateWithAttributes softLink_VideoToolbox_VTPixelBufferConformerCreateWithAttributes
diff --git a/Source/WebCore/platform/graphics/HEVCUtilities.cpp b/Source/WebCore/platform/graphics/HEVCUtilities.cpp
index 3be05f9..32b7223 100644
--- a/Source/WebCore/platform/graphics/HEVCUtilities.cpp
+++ b/Source/WebCore/platform/graphics/HEVCUtilities.cpp
@@ -26,6 +26,9 @@
 #include "config.h"
 #include "HEVCUtilities.h"
 
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+#include <wtf/NeverDestroyed.h>
 #include <wtf/text/StringToIntegerConversion.h>
 
 namespace WebCore {
@@ -110,4 +113,133 @@
     return parameters;
 }
 
+static String codecStringForDoViCodecType(const String& codec)
+{
+    using MapType = HashMap<String, String>;
+    static NeverDestroyed<MapType> types = std::initializer_list<MapType::KeyValuePairType>({
+        { "dvhe", "hev1" },
+        { "dvh1", "hvc1" },
+        { "dvav", "avc3" },
+        { "dva1", "avc1" }
+    });
+
+    auto findResults = types.get().find(codec);
+    if (findResults == types.get().end())
+        return nullString();
+    return findResults->value;
+}
+
+static Optional<unsigned short> profileIDForAlphabeticDoViProfile(const String& profile)
+{
+    // See Table 7 of "Dolby Vision Profiles and Levels Version 1.3.2"
+    using MapType = HashMap<String, unsigned short>;
+    static NeverDestroyed<MapType> map = std::initializer_list<MapType::KeyValuePairType>({
+        { "dvhe.dtr", 4 },
+        { "dvhe.stn", 5 },
+        { "dvhe.dtb", 7 },
+        { "dvhe.st", 8 },
+        { "dvav.se", 9 }
+    });
+
+    auto findResults = map.get().find(profile);
+    if (findResults == map.get().end())
+        return WTF::nullopt;
+    return findResults->value;
+}
+
+static bool isValidDoViProfileID(unsigned short profileID)
+{
+    switch (profileID) {
+    case 4:
+    case 5:
+    case 7:
+    case 8:
+    case 9:
+        return true;
+    default:
+        return false;
+    }
+}
+
+static Optional<unsigned short> maximumLevelIDForDoViProfileID(unsigned short profileID)
+{
+    // See Section 4.1 of "Dolby Vision Profiles and Levels Version 1.3.2"
+    switch (profileID) {
+    case 4: return 9;
+    case 5: return 13;
+    case 7: return 9;
+    case 8: return 13;
+    case 9: return 5;
+    default: return WTF::nullopt;
+    }
+}
+
+static bool isValidProfileIDForCodecName(unsigned short profileID, const String& codecName)
+{
+    if (profileID == 9)
+        return codecName == "avc1" || codecName == "avc3";
+    return codecName == "hvc1" || codecName == "hev1";
+}
+
+Optional<DoViParameterSet> parseDoViCodecParameters(const String& codecString)
+{
+    // The format of the DoVi codec string is specified in "Dolby Vision Profiles and Levels Version 1.3.2"
+    StringView codecView(codecString);
+    auto codecSplit = codecView.split('.');
+    auto nextElement = codecSplit.begin();
+    if (nextElement == codecSplit.end())
+        return WTF::nullopt;
+
+    DoViParameterSet parameters;
+
+    parameters.codecName = codecStringForDoViCodecType((*nextElement).toString());
+    if (!parameters.codecName)
+        return WTF::nullopt;
+
+    if (++nextElement == codecSplit.end())
+        return WTF::nullopt;
+
+    auto profileID = *nextElement;
+    if (!profileID.length())
+        return WTF::nullopt;
+
+    bool isIntegral = false;
+    auto firstCharacter = profileID[0];
+    // Profile definition can either be numeric or alpha:
+    if (firstCharacter == '0') {
+        parameters.bitstreamProfileID = toIntegralType<uint8_t>(profileID, &isIntegral, 10);
+        if (!isIntegral)
+            return WTF::nullopt;
+    } else {
+        auto alphanumericProfileString = codecView.left(5 + profileID.length()).toString();
+        auto profileID = profileIDForAlphabeticDoViProfile(alphanumericProfileString);
+        if (!profileID)
+            return WTF::nullopt;
+        parameters.bitstreamProfileID = profileID.value();
+    }
+
+    if (!isValidDoViProfileID(parameters.bitstreamProfileID))
+        return WTF::nullopt;
+
+    if (!isValidProfileIDForCodecName(parameters.bitstreamProfileID, parameters.codecName))
+        return WTF::nullopt;
+
+    if (++nextElement == codecSplit.end())
+        return WTF::nullopt;
+
+    auto levelID = *nextElement;
+    if (!levelID.length())
+        return WTF::nullopt;
+
+    parameters.bitstreamLevelID = toIntegralType<uint8_t>(levelID, &isIntegral, 10);
+    if (!isIntegral)
+        return WTF::nullopt;
+
+    auto maximumLevelID = maximumLevelIDForDoViProfileID(parameters.bitstreamProfileID);
+    if (!maximumLevelID || parameters.bitstreamLevelID > maximumLevelID.value())
+        return WTF::nullopt;
+
+    return parameters;
+}
+
 }
diff --git a/Source/WebCore/platform/graphics/HEVCUtilities.h b/Source/WebCore/platform/graphics/HEVCUtilities.h
index a20992c..43eea87 100644
--- a/Source/WebCore/platform/graphics/HEVCUtilities.h
+++ b/Source/WebCore/platform/graphics/HEVCUtilities.h
@@ -42,4 +42,12 @@
 
 WEBCORE_EXPORT Optional<HEVCParameterSet> parseHEVCCodecParameters(const String& codecString);
 
+struct DoViParameterSet {
+    String codecName;
+    unsigned short bitstreamProfileID { 0 };
+    unsigned short bitstreamLevelID { 0 };
+};
+
+WEBCORE_EXPORT Optional<DoViParameterSet> parseDoViCodecParameters(const String& codecString);
+
 }
diff --git a/Source/WebCore/platform/graphics/cocoa/HEVCUtilitiesCocoa.h b/Source/WebCore/platform/graphics/cocoa/HEVCUtilitiesCocoa.h
index 6b5cf1c..da91c07 100644
--- a/Source/WebCore/platform/graphics/cocoa/HEVCUtilitiesCocoa.h
+++ b/Source/WebCore/platform/graphics/cocoa/HEVCUtilitiesCocoa.h
@@ -34,6 +34,7 @@
 struct MediaCapabilitiesInfo;
 
 extern bool validateHEVCParameters(HEVCParameterSet&, MediaCapabilitiesInfo&, bool hasAlphaChannel, bool hdrSupport);
+extern bool validateDoViParameters(DoViParameterSet&, MediaCapabilitiesInfo&, bool hasAlphaChannel, bool hdrSupport);
 
 }
 
diff --git a/Source/WebCore/platform/graphics/cocoa/HEVCUtilitiesCocoa.mm b/Source/WebCore/platform/graphics/cocoa/HEVCUtilitiesCocoa.mm
index 7ba06da..f111f28 100644
--- a/Source/WebCore/platform/graphics/cocoa/HEVCUtilitiesCocoa.mm
+++ b/Source/WebCore/platform/graphics/cocoa/HEVCUtilitiesCocoa.mm
@@ -32,8 +32,10 @@
 #import "HEVCUtilities.h"
 #import "MediaCapabilitiesInfo.h"
 
-#import "VideoToolboxSoftLink.h"
 #import <pal/cocoa/AVFoundationSoftLink.h>
+#import <wtf/text/StringToIntegerConversion.h>
+
+#import "VideoToolboxSoftLink.h"
 
 namespace WebCore {
 
@@ -122,6 +124,116 @@
     return true;
 }
 
+static Optional<CMVideoCodecType> codecTypeForDoViCodecString(const String& codecString)
+{
+    static auto map = makeNeverDestroyed<HashMap<String, CMVideoCodecType>>({
+        { "avc1", kCMVideoCodecType_H264 },
+        { "avc3", kCMVideoCodecType_H264 },
+        { "hvc1", kCMVideoCodecType_HEVC },
+        { "hev1", kCMVideoCodecType_HEVC },
+    });
+
+    auto findResult = map.get().find(codecString);
+    if (findResult == map.get().end())
+        return WTF::nullopt;
+    return findResult->value;
+}
+
+static Optional<Vector<unsigned short>> CFStringArrayToNumberVector(CFArrayRef arrayCF)
+{
+    if (!arrayCF || CFGetTypeID(arrayCF) != CFArrayGetTypeID())
+        return WTF::nullopt;
+
+    auto arrayNS = (__bridge NSArray<NSString*>*)arrayCF;
+    Vector<unsigned short> values;
+    values.reserveInitialCapacity(arrayNS.count);
+
+    bool areAllValidNumbers = true;
+    [arrayNS enumerateObjectsUsingBlock:[&] (NSString* value, NSUInteger, BOOL *stop) {
+        if (![value isKindOfClass:NSString.class]) {
+            areAllValidNumbers = false;
+            if (stop)
+                *stop = true;
+            return;
+        }
+
+        bool isValidNumber = false;
+        auto numericValue = toIntegralType<unsigned short>(String(value), &isValidNumber);
+        if (!isValidNumber) {
+            areAllValidNumbers = false;
+            if (stop)
+                *stop = true;
+            return;
+        }
+
+        values.uncheckedAppend(numericValue);
+    }];
+
+    if (!areAllValidNumbers)
+        return WTF::nullopt;
+    return values;
+}
+
+bool validateDoViParameters(DoViParameterSet& parameters, MediaCapabilitiesInfo& info, bool hasAlphaChannel, bool hdrSupport)
+{
+    if (hasAlphaChannel)
+        return false;
+
+    if (hdrSupport) {
+        // Platform supports HDR playback of HEVC Main10 Profile, which is signalled by DoVi profiles 4, 5, 7, & 8.
+        switch (parameters.bitstreamProfileID) {
+        case 4:
+        case 5:
+        case 7:
+        case 8:
+            break;
+        default:
+            return false;
+        }
+    }
+
+    auto codecType = codecTypeForDoViCodecString(parameters.codecName);
+    if (!codecType)
+        return false;
+
+    OSStatus status = VTSelectAndCreateVideoDecoderInstance(codecType.value(), kCFAllocatorDefault, nullptr, nullptr);
+    if (status != noErr)
+        return false;
+
+    if (!canLoad_VideoToolbox_VTCopyHEVCDecoderCapabilitiesDictionary()
+        || !canLoad_VideoToolbox_kVTDolbyVisionDecoderCapability_SupportedProfiles()
+        || !canLoad_VideoToolbox_kVTDolbyVisionDecoderCapability_SupportedLevels()
+        || !canLoad_VideoToolbox_kVTDolbyVisionDecoderCapability_IsHardwareAccelerated())
+        return false;
+
+    RetainPtr<CFDictionaryRef> capabilities = adoptCF(VTCopyHEVCDecoderCapabilitiesDictionary());
+    if (!capabilities)
+        return false;
+
+    auto supportedProfilesCF = (CFArrayRef)CFDictionaryGetValue(capabilities.get(), kVTDolbyVisionDecoderCapability_SupportedProfiles);
+    auto supportedProfiles = CFStringArrayToNumberVector(supportedProfilesCF);
+    if (!supportedProfiles)
+        return false;
+
+    auto supportedLevelsCF = (CFArrayRef)CFDictionaryGetValue(capabilities.get(), kVTDolbyVisionDecoderCapability_SupportedLevels);
+    auto supportedLevels = CFStringArrayToNumberVector(supportedLevelsCF);
+    if (!supportedLevels)
+        return false;
+
+    auto isHardwareAcceleratedCF = (CFBooleanRef)CFDictionaryGetValue(capabilities.get(), kVTDolbyVisionDecoderCapability_IsHardwareAccelerated);
+    if (!isHardwareAcceleratedCF || CFGetTypeID(isHardwareAcceleratedCF) != CFBooleanGetTypeID())
+        return false;
+
+    if (!supportedProfiles.value().contains(parameters.bitstreamProfileID) || !supportedLevels.value().contains(parameters.bitstreamLevelID))
+        return false;
+
+    info.supported = true;
+    info.smooth = true;
+    info.powerEfficient = CFBooleanGetValue(isHardwareAcceleratedCF);
+
+    return true;
+}
+
 }
 
 #endif // PLATFORM(COCOA)
diff --git a/Source/WebCore/platform/graphics/cocoa/MediaEngineConfigurationFactoryCocoa.cpp b/Source/WebCore/platform/graphics/cocoa/MediaEngineConfigurationFactoryCocoa.cpp
index bc9056f..2ee3827 100644
--- a/Source/WebCore/platform/graphics/cocoa/MediaEngineConfigurationFactoryCocoa.cpp
+++ b/Source/WebCore/platform/graphics/cocoa/MediaEngineConfigurationFactoryCocoa.cpp
@@ -71,7 +71,7 @@
         info.supported = true;
         auto& codec = codecs[0];
         auto videoCodecType = videoCodecTypeFromRFC4281Type(codec);
-        if (!videoCodecType) {
+        if (!videoCodecType && !(codec.startsWith("dvh1") || codec.startsWith("dvhe"))) {
             callback({{ }, WTFMove(configuration)});
             return;
         }
@@ -85,6 +85,12 @@
                 callback({{ }, WTFMove(configuration)});
                 return;
             }
+        } else if (codec.startsWith("dvh1") || codec.startsWith("dvhe")) {
+            auto parameters = parseDoViCodecParameters(codec);
+            if (!parameters || !validateDoViParameters(parameters.value(), info, alphaChannel, hdrSupported)) {
+                callback({{ }, WTFMove(configuration)});
+                return;
+            }
         } else {
             if (alphaChannel || hdrSupported) {
                 callback({{ }, WTFMove(configuration)});
diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp
index a5042e5..f60f6c1 100644
--- a/Source/WebCore/testing/Internals.cpp
+++ b/Source/WebCore/testing/Internals.cpp
@@ -5157,6 +5157,11 @@
     return WebCore::parseHEVCCodecParameters(codecString);
 }
 
+Optional<DoViParameterSet> Internals::parseDoViCodecParameters(const String& codecString)
+{
+    return WebCore::parseDoViCodecParameters(codecString);
+}
+
 auto Internals::getCookies() const -> Vector<CookieData>
 {
     auto* document = contextDocument();
diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h
index e195bc8..b5dc261 100644
--- a/Source/WebCore/testing/Internals.h
+++ b/Source/WebCore/testing/Internals.h
@@ -818,6 +818,9 @@
     using HEVCParameterSet = WebCore::HEVCParameterSet;
     Optional<HEVCParameterSet> parseHEVCCodecParameters(const String& codecString);
 
+    using DoViParameterSet = WebCore::DoViParameterSet;
+    Optional<DoViParameterSet> parseDoViCodecParameters(const String& codecString);
+
     struct CookieData {
         String name;
         String value;
diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl
index 28902ff..c6ca7e2 100644
--- a/Source/WebCore/testing/Internals.idl
+++ b/Source/WebCore/testing/Internals.idl
@@ -139,6 +139,15 @@
 [
     ExportMacro=WEBCORE_TESTSUPPORT_EXPORT,
     JSGenerateToJSObject,
+] dictionary DoViParameterSet {
+    DOMString codecName;
+    unsigned short bitstreamProfileID;
+    unsigned short bitstreamLevelID;
+};
+
+[
+    ExportMacro=WEBCORE_TESTSUPPORT_EXPORT,
+    JSGenerateToJSObject,
 ] dictionary AcceleratedAnimation {
     DOMString property;
     double speed;
@@ -784,6 +793,7 @@
     boolean supportsVCPEncoder();
 
     HEVCParameterSet? parseHEVCCodecParameters(DOMString codecParameters);
+    DoViParameterSet? parseDoViCodecParameters(DOMString codecParameters);
 
     sequence<CookieData> getCookies();