Files
2025-05-18 13:04:45 +08:00

259 lines
7.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AvfMediaCapturePrivate.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "IMediaModule.h"
#include "IMediaPlayerFactory.h"
#include "Internationalization/Internationalization.h"
#include "Misc/Paths.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "UObject/NameTypes.h"
#include "IMediaCaptureSupport.h"
#include "Player/AvfMediaCapturePlayer.h"
#if WITH_EDITOR
#include "ISettingsModule.h"
#include "ISettingsSection.h"
#include "UObject/Class.h"
#include "UObject/WeakObjectPtr.h"
#endif
#import <AVFoundation/AVFoundation.h>
DEFINE_LOG_CATEGORY(LogAvfMediaCapture);
#define LOCTEXT_NAMESPACE "FAvfMediaCaptureFactoryModule"
#if (PLATFORM_IOS && (defined(__IPHONE_17_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_17_0))
#define IOS_17_APIS_AVAILABLE 1
#else
#define IOS_17_APIS_AVAILABLE 0
#endif
#if (PLATFORM_MAC && (defined(__MAC_14_0) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_14_0))
#define MAC_14_APIS_AVAILABLE 1
#else
#define MAC_14_APIS_AVAILABLE 0
#endif
// New AVCaptureDeviceType APIs don't compile on old IOS or MAC SDKs
#if (IOS_17_APIS_AVAILABLE || MAC_14_APIS_AVAILABLE)
#define USE_NEW_CAPTURE_DEVICE_TYPE_API 1
#else
#define USE_NEW_CAPTURE_DEVICE_TYPE_API 0
#endif
/**
* Implements the AvfMediaCapture module.
*/
class FAvfMediaCaptureModule
: public IModuleInterface
, public IMediaPlayerFactory
, public IMediaCaptureSupport
{
public:
void EnumerateCaptureDevices(TArray<FMediaCaptureDeviceInfo>& OutDeviceInfos, EMediaCaptureDeviceType TargetDeviceType)
{
SCOPED_AUTORELEASE_POOL
NSMutableArray* DeviceTypes = [[NSMutableArray alloc] init];
FString Scheme;
AVMediaType MediaType;
if (TargetDeviceType == EMediaCaptureDeviceType::Audio)
{
Scheme = TEXT("audcap://");
MediaType = AVMediaTypeAudio;
#if USE_NEW_CAPTURE_DEVICE_TYPE_API
[DeviceTypes addObject: AVCaptureDeviceTypeMicrophone];
#else
[DeviceTypes addObject: AVCaptureDeviceTypeBuiltInMicrophone];
#endif
}
else if (TargetDeviceType == EMediaCaptureDeviceType::Video)
{
Scheme = TEXT("vidcap://");
MediaType = AVMediaTypeVideo;
[DeviceTypes addObject: AVCaptureDeviceTypeBuiltInWideAngleCamera];
#if PLATFORM_IOS
[DeviceTypes addObject: AVCaptureDeviceTypeBuiltInUltraWideCamera];
[DeviceTypes addObject: AVCaptureDeviceTypeBuiltInTelephotoCamera];
#endif
#if USE_NEW_CAPTURE_DEVICE_TYPE_API
[DeviceTypes addObject: AVCaptureDeviceTypeExternal];
#elif PLATFORM_MAC
// AVCaptureDeviceTypeExternalUnknown is only available on macOS 10.15 - 14.0
// https://developer.apple.com/documentation/avfoundation/avcapturedevicetypeexternalunknown?language=objc
[DeviceTypes addObject: AVCaptureDeviceTypeExternalUnknown];
#endif
}
if ([DeviceTypes count] == 0)
{
return;
}
AVCaptureDeviceDiscoverySession* LocalDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes: DeviceTypes
mediaType: nil
position: AVCaptureDevicePositionUnspecified];
if (LocalDiscoverySession != nil)
{
NSArray<AVCaptureDevice*>* Devices = LocalDiscoverySession.devices;
for(uint32 i = 0; i < Devices.count; ++i)
{
AVCaptureDevice* AvailableDevice = Devices[i];
// It's not clear whether external media types will be limited to video.
// In which case we double check that the detected device supports the target media type.
if (![AvailableDevice hasMediaType: MediaType])
{
continue;
}
FMediaCaptureDeviceInfo DeviceInfo;
DeviceInfo.Type = TargetDeviceType;
DeviceInfo.DisplayName = FText::FromString(FString(AvailableDevice.localizedName));
DeviceInfo.Url = Scheme + FString(AvailableDevice.uniqueID);
DeviceInfo.Info = FString(AvailableDevice.manufacturer);
OutDeviceInfos.Add(MoveTemp(DeviceInfo));
}
}
}
//~ IMediaCaptureSupport interface
virtual void EnumerateAudioCaptureDevices(TArray<FMediaCaptureDeviceInfo>& OutDeviceInfos)
{
EnumerateCaptureDevices(OutDeviceInfos, EMediaCaptureDeviceType::Audio);
}
virtual void EnumerateVideoCaptureDevices(TArray<FMediaCaptureDeviceInfo>& OutDeviceInfos)
{
EnumerateCaptureDevices(OutDeviceInfos, EMediaCaptureDeviceType::Video);
}
//~ IMediaPlayerFactory interface
virtual bool CanPlayUrl(const FString& Url, const IMediaOptions* Options, TArray<FText>* OutWarnings, TArray<FText>* OutErrors) const override
{
return GetPlayabilityConfidenceScore(Url, Options, OutWarnings, OutErrors) > 0 ? true : false;
}
virtual int32 GetPlayabilityConfidenceScore(const FString& Url, const IMediaOptions* Options, TArray<FText>* OutWarnings, TArray<FText>* OutErrors) const override
{
FString Scheme;
FString Location;
// check scheme
if (!Url.Split(TEXT("://"), &Scheme, &Location, ESearchCase::CaseSensitive))
{
if (OutErrors != nullptr)
{
OutErrors->Add(LOCTEXT("NoSchemeFound", "No URI scheme found"));
}
return 0;
}
if (!SupportedUriSchemes.Contains(Scheme))
{
if (OutErrors != nullptr)
{
OutErrors->Add(FText::Format(LOCTEXT("SchemeNotSupported", "The URI scheme '{0}' is not supported"), FText::FromString(Scheme)));
}
return 0;
}
return 100;
}
virtual TSharedPtr<IMediaPlayer, ESPMode::ThreadSafe> CreatePlayer(IMediaEventSink& EventSink) override
{
return MakeShared<FAvfMediaCapturePlayer, ESPMode::ThreadSafe>(EventSink);
}
virtual FText GetDisplayName() const override
{
return LOCTEXT("MediaCaptureDisplayName", "Apple AV Foundation");
}
virtual FName GetPlayerName() const override
{
static FName PlayerName(TEXT("AvfMediaCapture"));
return PlayerName;
}
virtual FGuid GetPlayerPluginGUID() const override
{
static FGuid PlayerPluginGUID(0xcf78bfd2, 0x0c1111ed, 0x861d0242, 0xac120002);
return PlayerPluginGUID;
}
virtual const TArray<FString>& GetSupportedPlatforms() const override
{
return SupportedPlatforms;
}
virtual bool SupportsFeature(EMediaFeature Feature) const override
{
return ((Feature == EMediaFeature::AudioSamples) ||
(Feature == EMediaFeature::VideoSamples));
}
public:
//~ IModuleInterface interface
virtual void StartupModule() override
{
// supported schemes
SupportedUriSchemes.Add(TEXT("vidcap"));
SupportedUriSchemes.Add(TEXT("audcap"));
// supported platforms
SupportedPlatforms.Add(TEXT("Mac"));
SupportedPlatforms.Add(TEXT("iOS"));
// register factory support functions
auto MediaModule = FModuleManager::LoadModulePtr<IMediaModule>("Media");
if (MediaModule != nullptr)
{
MediaModule->RegisterPlayerFactory(*this);
MediaModule->RegisterCaptureSupport(*this);
}
}
virtual void ShutdownModule() override
{
// unregister factory support functions
auto MediaModule = FModuleManager::GetModulePtr<IMediaModule>("Media");
if (MediaModule != nullptr)
{
MediaModule->UnregisterPlayerFactory(*this);
MediaModule->UnregisterCaptureSupport(*this);
}
}
private:
/** List of platforms that the media player support. */
TArray<FString> SupportedPlatforms;
/** List of supported URI schemes. */
TArray<FString> SupportedUriSchemes;
};
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FAvfMediaCaptureModule, AvfMediaCapture);