twonly-app-dependencies/camera_avfoundation/darwin/Tests/CameraTestUtils.swift
2026-10-07 22:18:23 +02:00

219 lines
7.2 KiB
Swift

// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import AVFoundation
import XCTest
@testable import camera_avfoundation
/// Utils for creating default class instances used in tests
enum CameraTestUtils {
/// This method provides a convenient way to create media settings with minimal configuration.
/// Audio is enabled by default, while other parameters use platform-specific defaults.
static func createDefaultMediaSettings(resolutionPreset: PlatformResolutionPreset)
-> PlatformMediaSettings
{
return PlatformMediaSettings(
resolutionPreset: resolutionPreset,
framesPerSecond: nil,
videoBitrate: nil,
audioBitrate: nil,
enableAudio: true)
}
/// Creates a test `CameraConfiguration` with a default mock setup.
static func createTestCameraConfiguration() -> CameraConfiguration {
let captureSessionQueue = DispatchQueue(label: "capture_session_queue")
let videoSessionMock = MockCaptureSession()
videoSessionMock.canSetSessionPresetStub = { _ in true }
let audioSessionMock = MockCaptureSession()
audioSessionMock.canSetSessionPresetStub = { _ in true }
let frameRateRangeMock1 = MockFrameRateRange.init(minFrameRate: 3, maxFrameRate: 30)
let captureDeviceFormatMock1 = MockCaptureDeviceFormat()
captureDeviceFormatMock1.flutterVideoSupportedFrameRateRanges = [frameRateRangeMock1]
let frameRateRangeMock2 = MockFrameRateRange.init(minFrameRate: 3, maxFrameRate: 60)
let captureDeviceFormatMock2 = MockCaptureDeviceFormat()
captureDeviceFormatMock2.flutterVideoSupportedFrameRateRanges = [frameRateRangeMock2]
let captureDeviceMock = MockCaptureDevice()
captureDeviceMock.flutterFormats = [captureDeviceFormatMock1, captureDeviceFormatMock2]
var currentFormat: CaptureDeviceFormat = captureDeviceFormatMock1
captureDeviceMock.activeFormatStub = { currentFormat }
captureDeviceMock.setActiveFormatStub = { format in
currentFormat = format
}
let configuration = CameraConfiguration(
mediaSettings: createDefaultMediaSettings(
resolutionPreset: PlatformResolutionPreset.medium),
mediaSettingsWrapper: FLTCamMediaSettingsAVWrapper(),
captureDeviceFactory: { _ in captureDeviceMock },
audioCaptureDeviceFactory: { MockCaptureDevice() },
captureSessionFactory: { videoSessionMock },
captureSessionQueue: captureSessionQueue,
captureDeviceInputFactory: MockCaptureDeviceInputFactory(),
initialCameraName: "camera_name"
)
configuration.videoCaptureSession = videoSessionMock
configuration.audioCaptureSession = audioSessionMock
configuration.orientation = .portrait
configuration.assetWriterFactory = { _, _ in MockAssetWriter() }
configuration.inputPixelBufferAdaptorFactory = { _, _ in
MockAssetWriterInputPixelBufferAdaptor()
}
return configuration
}
static func createTestCamera(_ configuration: CameraConfiguration) -> DefaultCamera {
return (try? DefaultCamera(configuration: configuration))!
}
static func createTestCamera() -> DefaultCamera {
return createTestCamera(createTestCameraConfiguration())
}
static func createCameraWithCaptureSessionQueue(_ captureSessionQueue: DispatchQueue)
-> DefaultCamera
{
let configuration = createTestCameraConfiguration()
configuration.captureSessionQueue = captureSessionQueue
return createTestCamera(configuration)
}
/// Creates a test sample buffer.
/// @return a test sample buffer.
static func createTestSampleBuffer(
timestamp: CMTime = .zero,
duration: CMTime = CMTimeMake(value: 1, timescale: 44100)
) -> CMSampleBuffer {
var pixelBuffer: CVPixelBuffer?
CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, nil, &pixelBuffer)
var formatDescription: CMFormatDescription?
CMVideoFormatDescriptionCreateForImageBuffer(
allocator: kCFAllocatorDefault,
imageBuffer: pixelBuffer!,
formatDescriptionOut: &formatDescription)
var timingInfo = CMSampleTimingInfo(
duration: duration,
presentationTimeStamp: timestamp,
decodeTimeStamp: .invalid)
var sampleBuffer: CMSampleBuffer?
CMSampleBufferCreateReadyWithImageBuffer(
allocator: kCFAllocatorDefault,
imageBuffer: pixelBuffer!,
formatDescription: formatDescription!,
sampleTiming: &timingInfo,
sampleBufferOut: &sampleBuffer)
return sampleBuffer!
}
/// Creates a test audio sample buffer.
/// @return a test audio sample buffer.
static func createTestAudioSampleBuffer(
timestamp: CMTime = .zero,
duration: CMTime = CMTimeMake(value: 1, timescale: 44100)
) -> CMSampleBuffer {
var blockBuffer: CMBlockBuffer?
CMBlockBufferCreateWithMemoryBlock(
allocator: kCFAllocatorDefault,
memoryBlock: nil,
blockLength: Int(duration.value),
blockAllocator: kCFAllocatorDefault,
customBlockSource: nil,
offsetToData: 0,
dataLength: Int(duration.value),
flags: kCMBlockBufferAssureMemoryNowFlag,
blockBufferOut: &blockBuffer)
var formatDescription: CMFormatDescription?
var basicDescription = AudioStreamBasicDescription(
mSampleRate: Float64(duration.timescale),
mFormatID: kAudioFormatLinearPCM,
mFormatFlags: 0,
mBytesPerPacket: 1,
mFramesPerPacket: 1,
mBytesPerFrame: 1,
mChannelsPerFrame: 1,
mBitsPerChannel: 8,
mReserved: 0)
CMAudioFormatDescriptionCreate(
allocator: kCFAllocatorDefault,
asbd: &basicDescription,
layoutSize: 0,
layout: nil,
magicCookieSize: 0,
magicCookie: nil,
extensions: nil,
formatDescriptionOut: &formatDescription)
var sampleBuffer: CMSampleBuffer?
CMAudioSampleBufferCreateReadyWithPacketDescriptions(
allocator: kCFAllocatorDefault,
dataBuffer: blockBuffer!,
formatDescription: formatDescription!,
sampleCount: CMItemCount(duration.value),
presentationTimeStamp: timestamp,
packetDescriptions: nil,
sampleBufferOut: &sampleBuffer)
return sampleBuffer!
}
static func createTestAudioOutput() -> AVCaptureOutput {
return AVCaptureAudioDataOutput()
}
static func createTestConnection(_ output: AVCaptureOutput) -> AVCaptureConnection {
return AVCaptureConnection(inputPorts: [], output: output)
}
}
extension XCTestCase {
/// Wait until a round trip of a given `DispatchQueue` is complete. This allows for testing
/// side-effects of async functions that do not provide any notification of completion.
func waitForQueueRoundTrip(with queue: DispatchQueue) {
let expectation = expectation(description: "Queue flush")
queue.async {
if queue == DispatchQueue.main {
expectation.fulfill()
} else {
DispatchQueue.main.async {
expectation.fulfill()
}
}
}
wait(for: [expectation], timeout: 1)
}
func assertSuccess<T>(
_ result: Result<T, any Error>, file: StaticString = #file, line: UInt = #line
) -> T? {
switch result {
case .success(let value):
return value
case .failure(let error):
XCTFail("Expected success, but got failure: \(error)", file: file, line: line)
return nil
}
}
}