完善 OTG 相机有线传图:支持 Sony/Canon 自动识别,页面 detach 保持会话以便再次进入复连。
引入进程级 CameraTetheringSession 与共享 USB Browser;离开传图页仅取消 UI 回调,App 终止时再完整 shutdown 释放 PTP 与 Browser 资源。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
85
suixinkanTests/CameraTethering/CanonPTPCommandsTests.swift
Normal file
85
suixinkanTests/CameraTethering/CanonPTPCommandsTests.swift
Normal file
@ -0,0 +1,85 @@
|
||||
//
|
||||
// CanonPTPCommandsTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// Canon PTP 命令与事件解析测试。
|
||||
final class CanonPTPCommandsTests: XCTestCase {
|
||||
/// 测试 SetRemoteMode 命令包 opcode。
|
||||
func testBuildSetRemoteModeCommand() {
|
||||
let data = PTPHelper.buildCommand(
|
||||
code: CanonPTPCommand.eosSetRemoteMode,
|
||||
transactionID: 1,
|
||||
parameters: [1]
|
||||
)
|
||||
|
||||
XCTAssertEqual(data.count, 16)
|
||||
XCTAssertEqual(data[6], 0x14)
|
||||
XCTAssertEqual(data[7], 0x91)
|
||||
}
|
||||
|
||||
/// 测试 ObjectAddedEx (0xC181) 事件解析。
|
||||
func testParseObjectAddedExEvent() {
|
||||
var blob = Data()
|
||||
let recordSize: UInt32 = 8 + 40
|
||||
blob.append(contentsOf: withUnsafeBytes(of: recordSize.littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: CanonPTPCommand.eosObjectAddedEx.littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0x1234).littleEndian) { Data($0) }) // handle
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0x00010001).littleEndian) { Data($0) }) // storage
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0x3801).littleEndian) { Data($0) }) // ofc
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0).littleEndian) { Data($0) }) // flags
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0).littleEndian) { Data($0) }) // parent
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0).littleEndian) { Data($0) }) // reserved
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(1024).littleEndian) { Data($0) }) // size
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0).littleEndian) { Data($0) }) // reserved
|
||||
let filename = "IMG_0001.JPG"
|
||||
blob.append(filename.data(using: .ascii)!)
|
||||
blob.append(0)
|
||||
|
||||
let events = CanonEosEventParser.parse(blob)
|
||||
XCTAssertEqual(events.count, 1)
|
||||
guard case .objectAdded(let added) = events[0] else {
|
||||
return XCTFail("expected objectAdded")
|
||||
}
|
||||
XCTAssertEqual(added.objectHandle, 0x1234)
|
||||
XCTAssertEqual(added.filename, "IMG_0001.JPG")
|
||||
XCTAssertEqual(added.size, 1024)
|
||||
}
|
||||
|
||||
/// 测试 ObjectAddedEx2 (0xC1A7) 大文件 size 解析。
|
||||
func testParseObjectAddedEx2Event() {
|
||||
var blob = Data()
|
||||
let recordSize: UInt32 = 8 + 48
|
||||
blob.append(contentsOf: withUnsafeBytes(of: recordSize.littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: CanonPTPCommand.eosObjectAddedEx2.littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0x5678).littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0x00010001).littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0x3801).littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0).littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0).littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(512).littleEndian) { Data($0) }) // sizeLow
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(1).littleEndian) { Data($0) }) // sizeHigh
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0).littleEndian) { Data($0) })
|
||||
blob.append(contentsOf: withUnsafeBytes(of: UInt32(0).littleEndian) { Data($0) })
|
||||
blob.append("LARGE.JPG".data(using: .ascii)!)
|
||||
blob.append(0)
|
||||
|
||||
let events = CanonEosEventParser.parse(blob)
|
||||
XCTAssertEqual(events.count, 1)
|
||||
guard case .objectAdded(let added) = events[0] else {
|
||||
return XCTFail("expected objectAdded")
|
||||
}
|
||||
XCTAssertEqual(added.objectHandle, 0x5678)
|
||||
XCTAssertEqual(added.size, 512 | (1 << 32))
|
||||
}
|
||||
|
||||
/// 测试支持的文件名扩展名过滤。
|
||||
func testSupportedFilenameExtensions() {
|
||||
XCTAssertTrue(CanonPTPHelper.isSupportedFilename("photo.JPG"))
|
||||
XCTAssertTrue(CanonPTPHelper.isSupportedFilename("photo.cr2"))
|
||||
XCTAssertFalse(CanonPTPHelper.isSupportedFilename("photo.tif"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user