blob: 3d3b318e35c90c250247ecbf332377be2a84337c [file] [log] [blame] [edit]
/*
* Copyright (C) 2025 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "PlatformUtilities.h"
#import "Test.h"
#import <AVFoundation/AVAssetTrack.h>
#import <CoreMedia/CMFormatDescription.h>
#import <WebCore/FormatDescriptionUtilities.h>
#import <WebCore/SharedBuffer.h>
#import <WebCore/SourceBufferParser.h>
#import <WebKit/WKRetainPtr.h>
#import <wtf/cf/TypeCastsCF.h>
#import <pal/cf/CoreMediaSoftLink.h>
#import <pal/cocoa/AVFoundationSoftLink.h>
#if HAVE(IMMERSIVE_VIDEO_METADATA_SUPPORT)
namespace TestWebKitAPI {
static bool compareCameraCalibrationDictionary(CFDictionaryRef original, CFDictionaryRef copy)
{
static std::array<CFStringRef, 13> compulsoryKeys = {
PAL::kCMFormatDescriptionCameraCalibration_LensAlgorithmKind,
PAL::kCMFormatDescriptionCameraCalibration_LensDomain,
PAL::kCMFormatDescriptionCameraCalibration_LensIdentifier,
PAL::kCMFormatDescriptionCameraCalibration_LensRole,
PAL::kCMFormatDescriptionCameraCalibration_LensDistortions,
PAL::kCMFormatDescriptionCameraCalibration_LensFrameAdjustmentsPolynomialX,
PAL::kCMFormatDescriptionCameraCalibration_LensFrameAdjustmentsPolynomialY,
PAL::kCMFormatDescriptionCameraCalibration_RadialAngleLimit,
PAL::kCMFormatDescriptionCameraCalibration_IntrinsicMatrix,
PAL::kCMFormatDescriptionCameraCalibration_IntrinsicMatrixProjectionOffset,
PAL::kCMFormatDescriptionCameraCalibration_IntrinsicMatrixReferenceDimensions,
PAL::kCMFormatDescriptionCameraCalibration_ExtrinsicOriginSource,
PAL::kCMFormatDescriptionCameraCalibration_ExtrinsicOrientationQuaternion
};
for (auto key : compulsoryKeys) {
RetainPtr originalValue = CFDictionaryGetValue(original, key);
RetainPtr copyValue = CFDictionaryGetValue(copy, key);
bool result = CFEqual(originalValue.get(), copyValue.get());
EXPECT_TRUE(result);
if (!result)
return false;
}
return true;
}
static bool compareCameraCalibrationArray(CFArrayRef original, CFArrayRef copy)
{
int size = CFArrayGetCount(original);
if (size != CFArrayGetCount(copy))
return false;
for (int index = 0; index < size; index++) {
RetainPtr originalEntry = dynamic_cf_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(original, index));
RetainPtr copyEntry = dynamic_cf_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(copy, index));
EXPECT_TRUE(!!originalEntry);
EXPECT_TRUE(!!copyEntry);
if (!originalEntry || !copyEntry)
return false;
if (!compareCameraCalibrationDictionary(originalEntry.get(), copyEntry.get()))
return false;
}
return true;
}
TEST(ImmersiveVideoMetadata, Spatial)
{
RetainPtr url = [NSBundle.test_resourcesBundle URLForResource:@"spatial" withExtension:@"mp4"];
RetainPtr asset = adoptNS([PAL::allocAVURLAssetInstance() initWithURL:url.get() options:nil]);
RetainPtr<NSArray> tracks = [asset tracks];
EXPECT_GT([tracks count], 0UL);
RetainPtr<NSArray<AVAssetTrack *>> videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
EXPECT_GT([videoTracks count], 0UL);
RetainPtr<AVAssetTrack> track = [videoTracks firstObject];
RetainPtr<NSArray> descriptions = [track formatDescriptions];
EXPECT_GT([descriptions count], 0UL);
RetainPtr description = (CMFormatDescriptionRef)[descriptions firstObject];
EXPECT_TRUE(!!description);
auto immersiveVideoMetadata = WebCore::immersiveVideoMetadataFromFormatDescription(description.get());
EXPECT_TRUE(immersiveVideoMetadata.has_value());
EXPECT_TRUE(immersiveVideoMetadata->isSpatial());
EXPECT_EQ(immersiveVideoMetadata->horizontalFieldOfView, 63400);
EXPECT_EQ(immersiveVideoMetadata->stereoCameraBaseline, 19274u);
EXPECT_EQ(immersiveVideoMetadata->horizontalDisparityAdjustment, 200);
RetainPtr dictionary = WebCore::formatDescriptionDictionaryFromImmersiveVideoMetadata(*immersiveVideoMetadata);
EXPECT_TRUE(!!dictionary);
EXPECT_EQ(CFDictionaryGetCount(dictionary.get()), 7L);
}
TEST(ImmersiveVideoMetadata, ImmersiveMono)
{
RetainPtr url = [NSBundle.test_resourcesBundle URLForResource:@"immersivemono" withExtension:@"mp4"];
RetainPtr asset = adoptNS([PAL::allocAVURLAssetInstance() initWithURL:url.get() options:nil]);
RetainPtr<NSArray> tracks = [asset tracks];
EXPECT_GT([tracks count], 0UL);
RetainPtr<NSArray<AVAssetTrack *>> videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
EXPECT_GT([videoTracks count], 0UL);
RetainPtr<AVAssetTrack> track = [videoTracks firstObject];
RetainPtr<NSArray> descriptions = [track formatDescriptions];
EXPECT_GT([descriptions count], 0UL);
RetainPtr description = (CMFormatDescriptionRef)[descriptions firstObject];
RetainPtr originalDictionary = PAL::CMFormatDescriptionGetExtensions(description.get());
EXPECT_TRUE(!!description);
RetainPtr originalCameraCalibrationArray = dynamic_cf_cast<CFArrayRef>(PAL::CMFormatDescriptionGetExtension(description.get(), PAL::kCMFormatDescriptionExtension_CameraCalibrationDataLensCollection));
EXPECT_TRUE(!!originalCameraCalibrationArray);
auto immersiveVideoMetadata = WebCore::immersiveVideoMetadataFromFormatDescription(description.get());
EXPECT_TRUE(immersiveVideoMetadata.has_value());
EXPECT_EQ(immersiveVideoMetadata->kind, WebCore::VideoProjectionMetadataKind::Parametric);
EXPECT_FALSE(immersiveVideoMetadata->hasLeftStereoEyeView.has_value());
EXPECT_FALSE(immersiveVideoMetadata->hasRightStereoEyeView.has_value());
EXPECT_FALSE(immersiveVideoMetadata->heroEye.has_value());
EXPECT_EQ(immersiveVideoMetadata->cameraCalibrationDataLensCollection.size(), 1UL);
EXPECT_EQ(immersiveVideoMetadata->cameraCalibrationDataLensCollection[0].lensRole, WebCore::LensRole::Mono);
RetainPtr dictionary = WebCore::formatDescriptionDictionaryFromImmersiveVideoMetadata(*immersiveVideoMetadata);
EXPECT_TRUE(!!dictionary);
EXPECT_EQ(CFDictionaryGetCount(dictionary.get()), 2L);
RetainPtr cameraCalibrationArray = dynamic_cf_cast<CFArrayRef>(CFDictionaryGetValue(dictionary.get(), PAL::kCMFormatDescriptionExtension_CameraCalibrationDataLensCollection));
EXPECT_TRUE(compareCameraCalibrationArray(originalCameraCalibrationArray.get(), cameraCalibrationArray.get()));
}
TEST(ImmersiveVideoMetadata, ImmersiveStereo_SideBySide)
{
RetainPtr url = [NSBundle.test_resourcesBundle URLForResource:@"immersivestereo-sbs" withExtension:@"mp4"];
RetainPtr asset = adoptNS([PAL::allocAVURLAssetInstance() initWithURL:url.get() options:nil]);
RetainPtr<NSArray> tracks = [asset tracks];
EXPECT_GT([tracks count], 0UL);
RetainPtr<NSArray<AVAssetTrack *>> videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
EXPECT_GT([videoTracks count], 0UL);
RetainPtr<AVAssetTrack> track = [videoTracks firstObject];
RetainPtr<NSArray> descriptions = [track formatDescriptions];
EXPECT_GT([descriptions count], 0UL);
RetainPtr description = (CMFormatDescriptionRef)[descriptions firstObject];
RetainPtr originalDictionary = PAL::CMFormatDescriptionGetExtensions(description.get());
EXPECT_TRUE(!!description);
RetainPtr originalCameraCalibrationArray = dynamic_cf_cast<CFArrayRef>(PAL::CMFormatDescriptionGetExtension(description.get(), PAL::kCMFormatDescriptionExtension_CameraCalibrationDataLensCollection));
EXPECT_TRUE(!!originalCameraCalibrationArray);
auto immersiveVideoMetadata = WebCore::immersiveVideoMetadataFromFormatDescription(description.get());
EXPECT_TRUE(immersiveVideoMetadata.has_value());
EXPECT_EQ(immersiveVideoMetadata->kind, WebCore::VideoProjectionMetadataKind::Parametric);
EXPECT_TRUE(immersiveVideoMetadata->hasLeftStereoEyeView.has_value());
EXPECT_TRUE(*immersiveVideoMetadata->hasLeftStereoEyeView);
EXPECT_TRUE(immersiveVideoMetadata->hasRightStereoEyeView.has_value());
EXPECT_TRUE(*immersiveVideoMetadata->hasRightStereoEyeView);
EXPECT_FALSE(immersiveVideoMetadata->heroEye.has_value());
EXPECT_TRUE(immersiveVideoMetadata->viewPackingKind.has_value());
EXPECT_EQ(*immersiveVideoMetadata->viewPackingKind, WebCore::ViewPackingKind::SideBySide);
EXPECT_EQ(immersiveVideoMetadata->cameraCalibrationDataLensCollection.size(), 2UL);
RetainPtr dictionary = WebCore::formatDescriptionDictionaryFromImmersiveVideoMetadata(*immersiveVideoMetadata);
EXPECT_TRUE(!!dictionary);
EXPECT_EQ(CFDictionaryGetCount(dictionary.get()), 5L);
RetainPtr cameraCalibrationArray = dynamic_cf_cast<CFArrayRef>(CFDictionaryGetValue(dictionary.get(), PAL::kCMFormatDescriptionExtension_CameraCalibrationDataLensCollection));
EXPECT_TRUE(compareCameraCalibrationArray(originalCameraCalibrationArray.get(), cameraCalibrationArray.get()));
}
TEST(ImmersiveVideoMetadata, ImmersiveStereo_OverUnder)
{
RetainPtr url = [NSBundle.test_resourcesBundle URLForResource:@"immersivestereo-overunder" withExtension:@"mp4"];
RetainPtr asset = adoptNS([PAL::allocAVURLAssetInstance() initWithURL:url.get() options:nil]);
RetainPtr<NSArray> tracks = [asset tracks];
EXPECT_GT([tracks count], 0UL);
RetainPtr<NSArray<AVAssetTrack *>> videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
EXPECT_GT([videoTracks count], 0UL);
RetainPtr<AVAssetTrack> track = [videoTracks firstObject];
RetainPtr<NSArray> descriptions = [track formatDescriptions];
EXPECT_GT([descriptions count], 0UL);
RetainPtr description = (CMFormatDescriptionRef)[descriptions firstObject];
RetainPtr originalDictionary = PAL::CMFormatDescriptionGetExtensions(description.get());
EXPECT_TRUE(!!description);
RetainPtr originalCameraCalibrationArray = dynamic_cf_cast<CFArrayRef>(PAL::CMFormatDescriptionGetExtension(description.get(), PAL::kCMFormatDescriptionExtension_CameraCalibrationDataLensCollection));
EXPECT_TRUE(!!originalCameraCalibrationArray);
auto immersiveVideoMetadata = WebCore::immersiveVideoMetadataFromFormatDescription(description.get());
EXPECT_TRUE(immersiveVideoMetadata.has_value());
EXPECT_EQ(immersiveVideoMetadata->kind, WebCore::VideoProjectionMetadataKind::Parametric);
EXPECT_TRUE(immersiveVideoMetadata->hasLeftStereoEyeView.has_value());
EXPECT_TRUE(*immersiveVideoMetadata->hasLeftStereoEyeView);
EXPECT_TRUE(immersiveVideoMetadata->hasRightStereoEyeView.has_value());
EXPECT_TRUE(*immersiveVideoMetadata->hasRightStereoEyeView);
EXPECT_FALSE(immersiveVideoMetadata->heroEye.has_value());
EXPECT_EQ(*immersiveVideoMetadata->viewPackingKind, WebCore::ViewPackingKind::OverUnder);
EXPECT_EQ(immersiveVideoMetadata->cameraCalibrationDataLensCollection.size(), 2UL);
RetainPtr dictionary = WebCore::formatDescriptionDictionaryFromImmersiveVideoMetadata(*immersiveVideoMetadata);
EXPECT_TRUE(!!dictionary);
EXPECT_EQ(CFDictionaryGetCount(dictionary.get()), 5L);
RetainPtr cameraCalibrationArray = dynamic_cf_cast<CFArrayRef>(CFDictionaryGetValue(dictionary.get(), PAL::kCMFormatDescriptionExtension_CameraCalibrationDataLensCollection));
EXPECT_TRUE(compareCameraCalibrationArray(originalCameraCalibrationArray.get(), cameraCalibrationArray.get()));
}
}
#endif