fix #5 camera video preview with settling and diagnostics
- Wait until the picker movie's file size stops changing before staging it, so a movie whose final bytes are still being flushed is never copied truncated (which produced drafts without a thumbnail and unplayable previews). - Log the whole camera video path (pickup size, staging result, analyze outcome, thumbnail decode) under the video-preview os_log category for fast on-device diagnosis. - Cover the video analysis pipeline with a simulator test that generates an HEVC QuickTime movie and asserts duration plus JPEG thumbnail, and guard the diagnostics in Scripts/verify.sh.
This commit is contained in:
1 parent
75fbcc5346
commit
496ee6f0f2
7 files changed
+137
-5
No files matched your search
@@ -0,0 +1,68 @@
|
||||
import AVFoundation
|
||||
import XCTest
|
||||
@testable import Luma
|
||||
|
||||
final class MediaPreviewProcessorVideoTests: XCTestCase {
|
||||
/// Reproduces the camera-video analysis on the simulator: the pipeline
|
||||
/// must produce a duration and a JPEG thumbnail for a freshly written
|
||||
/// HEVC QuickTime movie.
|
||||
func testVideoAnalysisProducesDurationAndThumbnail() async throws {
|
||||
let url = try await Self.makeTestMovie()
|
||||
defer { try? FileManager.default.removeItem(at: url) }
|
||||
|
||||
let processor = MediaPreviewProcessor()
|
||||
let analysis = await processor.analyze(
|
||||
url: url,
|
||||
kind: .video,
|
||||
mimeType: "video/quicktime"
|
||||
)
|
||||
XCTAssertNotNil(analysis.duration)
|
||||
if let duration = analysis.duration {
|
||||
XCTAssertGreaterThan(duration, 0)
|
||||
}
|
||||
XCTAssertNotNil(analysis.thumbnailData)
|
||||
XCTAssertFalse(analysis.thumbnailData?.isEmpty ?? true)
|
||||
}
|
||||
|
||||
private static func makeTestMovie() async throws -> URL {
|
||||
let output = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("test-camera-\(UUID().uuidString).mov")
|
||||
let writer = try AVAssetWriter(outputURL: output, fileType: .mov)
|
||||
let input = AVAssetWriterInput(
|
||||
mediaType: .video,
|
||||
outputSettings: [
|
||||
AVVideoCodecKey: AVVideoCodecType.hevc,
|
||||
AVVideoWidthKey: 640,
|
||||
AVVideoHeightKey: 480,
|
||||
]
|
||||
)
|
||||
input.expectsMediaDataInRealTime = true
|
||||
let adaptor = AVAssetWriterInputPixelBufferAdaptor(
|
||||
assetWriterInput: input,
|
||||
sourcePixelBufferAttributes: [
|
||||
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
|
||||
kCVPixelBufferWidthKey as String: 640,
|
||||
kCVPixelBufferHeightKey as String: 480,
|
||||
]
|
||||
)
|
||||
writer.add(input)
|
||||
guard writer.startWriting() else {
|
||||
throw writer.error ?? NSError(domain: "test", code: 1)
|
||||
}
|
||||
writer.startSession(atSourceTime: .zero)
|
||||
for frame in 0..<60 {
|
||||
while !input.isReadyForMoreMediaData {
|
||||
try await Task.sleep(nanoseconds: 2_000_000)
|
||||
}
|
||||
var buffer: CVPixelBuffer?
|
||||
CVPixelBufferCreate(
|
||||
kCFAllocatorDefault, 640, 480, kCVPixelFormatType_32BGRA, nil, &buffer
|
||||
)
|
||||
adaptor.append(buffer!, withPresentationTime: CMTime(value: Int64(frame), timescale: 30))
|
||||
}
|
||||
input.markAsFinished()
|
||||
await writer.finishWriting()
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user