建立 MAG160C 逆向工程交接仓库

This commit is contained in:
ZXCLI
2026-08-11 19:08:44 +08:00
commit 8409b27ba3
3135 changed files with 534408 additions and 0 deletions
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+189
View File
@@ -0,0 +1,189 @@
000000000023f60c B __bss_start
000000000023f60c D _edata
00000000002666f0 B _end
000000000002c734 T _fini
0000000000005cb8 T _init
0000000000021fb0 T libusb_alloc_streams
0000000000024d90 T libusb_alloc_transfer
00000000000221a0 T libusb_attach_kernel_driver
00000000000273c0 T libusb_bulk_transfer
00000000000252b0 T libusb_cancel_transfer
0000000000021cf0 T libusb_claim_interface
0000000000021ef0 T libusb_clear_halt
0000000000021800 T libusb_close
0000000000027130 T libusb_control_transfer
0000000000022130 T libusb_detach_kernel_driver
00000000000206e0 T libusb_dev_mem_alloc
0000000000020710 T libusb_dev_mem_free
0000000000022890 T libusb_error_name
0000000000026040 T libusb_event_handler_active
0000000000025fd0 T libusb_event_handling_ok
0000000000022600 T libusb_exit
0000000000023cf0 T libusb_free_bos_descriptor
0000000000023bc0 T libusb_free_config_descriptor
0000000000024430 T libusb_free_container_id_descriptor
0000000000021060 T libusb_free_device_list
0000000000026d70 T libusb_free_pollfds
0000000000023ce0 T libusb_free_ss_endpoint_companion_descriptor
0000000000024360 T libusb_free_ss_usb_device_capability_descriptor
0000000000022040 T libusb_free_streams
0000000000024e10 T libusb_free_transfer
0000000000024290 T libusb_free_usb_2_0_extension_descriptor
0000000000023790 T libusb_get_active_config_descriptor
0000000000023d50 T libusb_get_bos_descriptor
00000000000205e0 T libusb_get_bus_number
00000000000238c0 T libusb_get_config_descriptor
0000000000023b20 T libusb_get_config_descriptor_by_value
0000000000021b50 T libusb_get_configuration
0000000000024370 T libusb_get_container_id_descriptor
00000000000206d0 T libusb_get_device
0000000000020610 T libusb_get_device_address
0000000000023740 T libusb_get_device_descriptor
0000000000021170 T libusb_get_device_list
0000000000020620 T libusb_get_device_speed
0000000000020f10 T libusb_get_max_iso_packet_size
0000000000020e90 T libusb_get_max_packet_size
0000000000026250 T libusb_get_next_timeout
0000000000020600 T libusb_get_parent
0000000000026cc0 T libusb_get_pollfds
00000000000205f0 T libusb_get_port_number
0000000000020de0 T libusb_get_port_numbers
0000000000020e80 T libusb_get_port_path
0000000000023bf0 T libusb_get_ss_endpoint_companion_descriptor
00000000000242a0 T libusb_get_ss_usb_device_capability_descriptor
0000000000024440 T libusb_get_string_descriptor_ascii
00000000000241d0 T libusb_get_usb_2_0_extension_descriptor
0000000000022a00 T libusb_get_version
00000000000266e0 T libusb_handle_events
0000000000026710 T libusb_handle_events_completed
0000000000026740 T libusb_handle_events_locked
00000000000266d0 T libusb_handle_events_timeout
0000000000026550 T libusb_handle_events_timeout_completed
0000000000020770 T libusb_has_capability
0000000000024820 T libusb_hotplug_deregister_callback
00000000000248d0 T libusb_hotplug_register_callback
0000000000022210 T libusb_init
00000000000260c0 T libusb_interrupt_event_handler
00000000000273e0 T libusb_interrupt_transfer
00000000000220c0 T libusb_kernel_driver_active
0000000000026160 T libusb_lock_event_waiters
0000000000025f30 T libusb_lock_events
00000000000215a0 T libusb_open
0000000000021730 T libusb_open_device_with_vid_pid
0000000000026220 T libusb_pollfds_handle_timeouts
0000000000020630 T libusb_ref_device
0000000000021da0 T libusb_release_interface
0000000000021f60 T libusb_reset_device
0000000000020730 T libusb_set_auto_detach_kernel_driver
0000000000021ca0 T libusb_set_configuration
0000000000020750 T libusb_set_debug
0000000000021e40 T libusb_set_interface_alt_setting
00000000000267b0 T libusb_set_pollfd_notifiers
0000000000024e70 T libusb_submit_transfer
0000000000025c00 T libusb_transfer_get_stream_id
0000000000025bf0 T libusb_transfer_set_stream_id
0000000000025e90 T libusb_try_lock_events
0000000000026190 T libusb_unlock_event_waiters
0000000000025f70 T libusb_unlock_events
0000000000020fb0 T libusb_unref_device
00000000000261c0 T libusb_wait_for_event
0000000000021420 T libusb_wrap_fd
000000000001b6a0 T MAG_DelChannel
000000000001b770 T MAG_DisLinkCamera
000000000001b110 T MAG_EnableAutoReConnect
000000000001b120 T MAG_EnumCameras
000000000001ded0 T MAG_EstimateUnderArmTempFromForeheadRect
000000000001c9e0 T MAG_FixTemperature
000000000001ca60 T MAG_FixTemperature2
000000000001b5e0 T MAG_Free
000000000001b030 T MAG_GetCameraTemperature
000000000001b230 T MAG_GetCamInfo
000000000001aff0 T MAG_GetCurrentCameraInnerTemperature
000000000001aeb0 T MAG_GetCurrentOffset
000000000001cd40 T MAG_GetEllipseTemperatureInfo
000000000001de60 T MAG_GetEstimatedEnvTemp
000000000001de30 T MAG_GetEstimateUnderArmTempMode
000000000001c960 T MAG_GetEXLevel
000000000001c000 T MAG_GetFilteredRaw
000000000001c750 T MAG_GetFixPara
000000000001cac0 T MAG_GetFrameStatisticalData
000000000001cc30 T MAG_GetLineTemperatureInfo
000000000001b0e0 T MAG_GetLocalIp
000000000001b280 T MAG_GetMulticastState
000000000001c050 T MAG_GetOutputBMPdata
000000000001c100 T MAG_GetOutputBMPdataRGB24
000000000001c2a0 T MAG_GetOutputColorBardata
000000000001c250 T MAG_GetOutputRawData
000000000001c350 T MAG_GetOutputVideoData
000000000001c400 T MAG_GetOutputVideoYV12
000000000001b1a0 T MAG_GetRecentHeartBeat
000000000001ccb0 T MAG_GetRectTemperatureInfo
000000000001cb00 T MAG_GetRemoteInfo
000000000001cdb0 T MAG_GetRgnTemperatureInfo
000000000001b070 T MAG_GetSenorTemperature
000000000001c510 T MAG_GetTemperatureData
000000000001c570 T MAG_GetTemperatureData_Raw
000000000001cb40 T MAG_GetTemperatureProbe
000000000001cbe0 T MAG_GetTemperatureProbe2
000000000001b2a0 T MAG_GetTerminalList
000000000001c450 T MAG_GetVideoPPS
000000000001c490 T MAG_GetVideoSPS
000000000001c4d0 T MAG_GetVideoVCL
000000000001b540 T MAG_Initialize
000000000001ad90 T MAG_IsChannelAvailable
000000000001b0b0 T MAG_IsInitialized
000000000001b150 T MAG_IsLinked
000000000001b180 T MAG_IsListening
000000000001b320 T MAG_IsProcessingImage
000000000001ce50 T MAG_IsUsingTemperatureMask
000000000001d880 T MAG_LinkCamera
000000000001d780 T MAG_LinkCameraEx
000000000001b190 T MAG_ListenTo
000000000001d080 T MAG_LoadBufferedDDT
000000000001e9f0 T MAG_LoadDDT
000000000001cf70 T MAG_LockFrame
000000000001abd0 T MAG_NewChannel
000000000001b350 T MAG_PrepareProcessImage
000000000001cf10 T MAG_QueryPTZState
000000000001bff0 T MAG_ResetCamera
000000000001ae40 T MAG_RevisedTemp2Gray
000000000001add0 T MAG_RevisedTemp2InnerTemp
000000000001ce90 T MAG_SaveBMP
000000000001f830 T MAG_SaveDDT
000000000001f990 T MAG_SaveDDT2Buffer
000000000001f1b0 T MAG_SaveMGT
000000000001d0f0 T MAG_SDCardStorage
000000000001d0d0 T MAG_SDStorageAviStart
000000000001d0e0 T MAG_SDStorageAviStop
000000000001d0a0 T MAG_SDStorageBMP
000000000001d0b0 T MAG_SDStorageMGSStart
000000000001d0c0 T MAG_SDStorageMGSStop
000000000001d090 T MAG_SDStorageMGT
000000000001be50 T MAG_SetAutoEnlargePara
000000000001bcf0 T MAG_SetColorPalette
000000000001c9a0 T MAG_SetDetailEnhancement
000000000001de90 T MAG_SetEnvTempEstimateMode
000000000001ddd0 T MAG_SetEstimateUnderArmTempMode
000000000001c900 T MAG_SetEXLevel
000000000001b810 T MAG_SetFFCMode
000000000001b100 T MAG_SetFilter
000000000001c7f0 T MAG_SetFixPara
000000000001bce0 T MAG_SetIoAlarmState
000000000001bf30 T MAG_SetIsothermalPara
000000000001cf00 T MAG_SetPTZCmd
000000000001b1f0 T MAG_SetReConnectCallBack
000000000001cf40 T MAG_SetSerialCallBack
000000000001cf20 T MAG_SetSerialCmd
000000000001bd60 T MAG_SetSubsectionEnlargePara
000000000001bfe0 T MAG_SetUserROIs
000000000001cf60 T MAG_SetVideoBrightness
000000000001cf30 T MAG_SetVideoContrast
000000000001dc50 T MAG_StartProcessImage
000000000001dcb0 T MAG_StartProcessPulseImage
000000000001b7e0 T MAG_StopListen
000000000001b490 T MAG_StopProcessImage
000000000001b440 T MAG_TransferPulseImage
000000000001bcc0 T MAG_TriggerFFC
000000000001cfc0 T MAG_UnLockFrame
000000000001ce10 T MAG_UseTemperatureMask
000000000001b3f0 T MAG_WillStartImmediately
Binary file not shown.
+331
View File
@@ -0,0 +1,331 @@
Symbol table '.dynsym' contains 328 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000005cb8 0 SECTION LOCAL DEFAULT 9 .init
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exp@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_getspecific@GLIBC_2.2.5 (3)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_cond_destroy@GLIBC_2.3.2 (4)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memset@GLIBC_2.2.5 (5)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND ftell@GLIBC_2.2.5 (5)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND close@GLIBC_2.2.5 (3)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND ioctl@GLIBC_2.2.5 (5)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_setspecific@GLIBC_2.2.5 (3)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
11: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND recvmsg@GLIBC_2.2.5 (3)
13: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _Znam@GLIBCXX_3.4 (6)
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND uname@GLIBC_2.2.5 (5)
15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fseek@GLIBC_2.2.5 (5)
16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_cond_signal@GLIBC_2.3.2 (4)
17: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZdlPv@GLIBCXX_3.4 (6)
18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZNSs4_Rep10_M_disposeERKSaIcE@GLIBCXX_3.4 (6)
19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutexattr_destroy@GLIBC_2.2.5 (3)
20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __assert_fail@GLIBC_2.2.5 (5)
21: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __printf_chk@GLIBC_2.3.4 (7)
22: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gettimeofday@GLIBC_2.2.5 (5)
23: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZNSt8ios_base4InitC1Ev@GLIBCXX_3.4 (6)
24: 0000000000000000 0 FUNC GLOBAL DEFAULT UND read@GLIBC_2.2.5 (3)
25: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strncmp@GLIBC_2.2.5 (5)
26: 0000000000000000 0 FUNC GLOBAL DEFAULT UND malloc@GLIBC_2.2.5 (5)
27: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fopen@GLIBC_2.2.5 (5)
28: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_cond_timedwait@GLIBC_2.3.2 (4)
29: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __memcpy_chk@GLIBC_2.3.4 (7)
30: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@GLIBC_2.2.5 (5)
31: 0000000000000000 0 FUNC GLOBAL DEFAULT UND setsockopt@GLIBC_2.2.5 (5)
32: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutex_init@GLIBC_2.2.5 (3)
33: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fgets@GLIBC_2.2.5 (5)
34: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __vsnprintf_chk@GLIBC_2.3.4 (7)
35: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev@GLIBCXX_3.4 (6)
36: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_attr_destroy@GLIBC_2.2.5 (3)
37: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getpwuid@GLIBC_2.2.5 (5)
38: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable
39: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@GLIBCXX_3.4 (6)
40: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __strdup@GLIBC_2.2.5 (5)
41: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free@GLIBC_2.2.5 (5)
42: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.2.5 (5)
43: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
44: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZNSs4_Rep10_M_destroyERKSaIcE@GLIBCXX_3.4 (6)
45: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __asprintf_chk@GLIBC_2.8 (8)
46: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_create@GLIBC_2.2.5 (3)
47: 0000000000000000 0 FUNC GLOBAL DEFAULT UND opendir@GLIBC_2.2.5 (5)
48: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat@GLIBC_2.2.5 (5)
49: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_cond_init@GLIBC_2.3.2 (4)
50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_b_loc@GLIBC_2.3 (9)
51: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (5)
52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_key_delete@GLIBC_2.2.5 (3)
53: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND _ZTVN10__cxxabiv117__class_type_infoE@CXXABI_1.3 (10)
54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND readdir@GLIBC_2.2.5 (5)
55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strrchr@GLIBC_2.2.5 (5)
56: 0000000000000000 0 FUNC GLOBAL DEFAULT UND syscall@GLIBC_2.2.5 (5)
57: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sscanf@GLIBC_2.2.5 (5)
58: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pipe@GLIBC_2.2.5 (5)
59: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_join@GLIBC_2.2.5 (3)
60: 0000000000000000 0 FUNC GLOBAL DEFAULT UND poll@GLIBC_2.2.5 (5)
61: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutex_trylock@GLIBC_2.2.5 (3)
62: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pow@GLIBC_2.2.5 (2)
63: 0000000000000000 0 FUNC GLOBAL DEFAULT UND usleep@GLIBC_2.2.5 (5)
64: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strerror@GLIBC_2.2.5 (5)
65: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND _ZSt4cout@GLIBCXX_3.4 (6)
66: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.2.5 (5)
67: 0000000000000000 0 FUNC GLOBAL DEFAULT UND lseek@GLIBC_2.2.5 (3)
68: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtol@GLIBC_2.2.5 (5)
69: 0000000000000000 0 FUNC GLOBAL DEFAULT UND readlink@GLIBC_2.2.5 (5)
70: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_pure_virtual@CXXABI_1.3 (10)
71: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __strcpy_chk@GLIBC_2.3.4 (7)
72: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l@GLIBCXX_3.4.9 (11)
73: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memmove@GLIBC_2.2.5 (5)
74: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strchr@GLIBC_2.2.5 (5)
75: 0000000000000000 0 FUNC GLOBAL DEFAULT UND socket@GLIBC_2.2.5 (5)
76: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fread@GLIBC_2.2.5 (5)
77: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getenv@GLIBC_2.2.5 (5)
78: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.2.5 (3)
79: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND _ZTVN10__cxxabiv120__si_class_type_infoE@CXXABI_1.3 (10)
80: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutexattr_settype@GLIBC_2.2.5 (3)
81: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (12)
82: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcmp@GLIBC_2.2.5 (5)
83: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_allocate_exception@CXXABI_1.3 (10)
84: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZNSs6assignEPKcm@GLIBCXX_3.4 (6)
85: 0000000000000000 0 FUNC GLOBAL DEFAULT UND nanosleep@GLIBC_2.2.5 (3)
86: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getuid@GLIBC_2.2.5 (5)
87: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_cond_wait@GLIBC_2.3.2 (4)
88: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZdaPv@GLIBCXX_3.4 (6)
89: 0000000000000000 0 FUNC GLOBAL DEFAULT UND calloc@GLIBC_2.2.5 (5)
90: 0000000000000000 0 FUNC GLOBAL DEFAULT UND munmap@GLIBC_2.2.5 (5)
91: 0000000000000000 0 FUNC GLOBAL DEFAULT UND feof@GLIBC_2.2.5 (5)
92: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_throw@CXXABI_1.3 (10)
93: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fclose@GLIBC_2.2.5 (5)
94: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strncpy@GLIBC_2.2.5 (5)
95: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_attr_init@GLIBC_2.2.5 (3)
96: 0000000000000000 0 FUNC GLOBAL DEFAULT UND localtime_r@GLIBC_2.2.5 (5)
97: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_end_catch@CXXABI_1.3 (10)
98: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZSt17__throw_bad_allocv@GLIBCXX_3.4 (6)
99: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND _ZNSs4_Rep20_S_empty_rep_storageE@GLIBCXX_3.4 (6)
100: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fscanf@GLIBC_2.2.5 (5)
101: 0000000000000000 0 FUNC GLOBAL DEFAULT UND closedir@GLIBC_2.2.5 (5)
102: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __sprintf_chk@GLIBC_2.3.4 (7)
103: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND stderr@GLIBC_2.2.5 (5)
104: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __snprintf_chk@GLIBC_2.3.4 (7)
105: 0000000000000000 0 FUNC GLOBAL DEFAULT UND access@GLIBC_2.2.5 (5)
106: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GLIBCXX_3.4 (6)
107: 0000000000000000 0 FUNC WEAK DEFAULT UND __pthread_key_create@GLIBC_2.2.5 (3)
108: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_begin_catch@CXXABI_1.3 (10)
109: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bind@GLIBC_2.2.5 (5)
110: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fwrite@GLIBC_2.2.5 (5)
111: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutex_lock@GLIBC_2.2.5 (3)
112: 0000000000000000 0 FUNC GLOBAL DEFAULT UND realloc@GLIBC_2.2.5 (5)
113: 0000000000000000 0 FUNC GLOBAL DEFAULT UND timerfd_create@GLIBC_2.8 (8)
114: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutexattr_init@GLIBC_2.2.5 (3)
115: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_key_create@GLIBC_2.2.5 (3)
116: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __gxx_personality_v0@CXXABI_1.3 (10)
117: 0000000000000000 0 FUNC GLOBAL DEFAULT UND timerfd_settime@GLIBC_2.8 (8)
118: 0000000000000000 0 FUNC GLOBAL DEFAULT UND localtime@GLIBC_2.2.5 (5)
119: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutex_destroy@GLIBC_2.2.5 (3)
120: 0000000000000000 0 FUNC GLOBAL DEFAULT UND write@GLIBC_2.2.5 (3)
121: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _Znwm@GLIBCXX_3.4 (6)
122: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _Unwind_Resume@GCC_3.0 (13)
123: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_cond_broadcast@GLIBC_2.3.2 (4)
124: 0000000000000000 0 FUNC GLOBAL DEFAULT UND clock_gettime@GLIBC_2.2.5 (14)
125: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sqrt@GLIBC_2.2.5 (2)
126: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtoul@GLIBC_2.2.5 (5)
127: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutex_unlock@GLIBC_2.2.5 (3)
128: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcpy@GLIBC_2.14 (15)
129: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fcntl@GLIBC_2.2.5 (3)
130: 0000000000000000 0 FUNC GLOBAL DEFAULT UND open@GLIBC_2.2.5 (3)
131: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __open_2@GLIBC_2.7 (16)
132: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mmap@GLIBC_2.2.5 (5)
133: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND stdout@GLIBC_2.2.5 (5)
134: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mkdir@GLIBC_2.2.5 (5)
135: 0000000000000000 0 FUNC GLOBAL DEFAULT UND time@GLIBC_2.2.5 (5)
136: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fflush@GLIBC_2.2.5 (5)
137: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sqrtf@GLIBC_2.2.5 (2)
138: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_attr_setdetachstate@GLIBC_2.2.5 (3)
139: 0000000000020770 99 FUNC GLOBAL DEFAULT 11 libusb_has_capability
140: 000000000001b1a0 70 FUNC GLOBAL DEFAULT 11 MAG_GetRecentHeartBeat
141: 0000000000023ce0 5 FUNC GLOBAL DEFAULT 11 libusb_free_ss_endpoint_companion_descriptor
142: 000000000001cf40 22 FUNC GLOBAL DEFAULT 11 MAG_SetSerialCallBack
143: 000000000001c4d0 57 FUNC GLOBAL DEFAULT 11 MAG_GetVideoVCL
144: 0000000000025e90 159 FUNC GLOBAL DEFAULT 11 libusb_try_lock_events
145: 000000000001c900 90 FUNC GLOBAL DEFAULT 11 MAG_SetEXLevel
146: 0000000000020fb0 170 FUNC GLOBAL DEFAULT 11 libusb_unref_device
147: 0000000000020e80 14 FUNC GLOBAL DEFAULT 11 libusb_get_port_path
148: 000000000001ad90 61 FUNC GLOBAL DEFAULT 11 MAG_IsChannelAvailable
149: 000000000001b110 10 FUNC GLOBAL DEFAULT 11 MAG_EnableAutoReConnect
150: 000000000001dc50 96 FUNC GLOBAL DEFAULT 11 MAG_StartProcessImage
151: 000000000001f1b0 330 FUNC GLOBAL DEFAULT 11 MAG_SaveMGT
152: 0000000000024d90 124 FUNC GLOBAL DEFAULT 11 libusb_alloc_transfer
153: 000000000001e9f0 193 FUNC GLOBAL DEFAULT 11 MAG_LoadDDT
154: 0000000000005cb8 0 FUNC GLOBAL DEFAULT 9 _init
155: 00000000000205e0 5 FUNC GLOBAL DEFAULT 11 libusb_get_bus_number
156: 0000000000020e90 115 FUNC GLOBAL DEFAULT 11 libusb_get_max_packet_size
157: 0000000000025c00 4 FUNC GLOBAL DEFAULT 11 libusb_transfer_get_stream_id
158: 000000000001b540 154 FUNC GLOBAL DEFAULT 11 MAG_Initialize
159: 000000000001ce90 103 FUNC GLOBAL DEFAULT 11 MAG_SaveBMP
160: 0000000000023790 298 FUNC GLOBAL DEFAULT 11 libusb_get_active_config_descriptor
161: 00000000000266d0 7 FUNC GLOBAL DEFAULT 11 libusb_handle_events_timeout
162: 000000000001b3f0 68 FUNC GLOBAL DEFAULT 11 MAG_WillStartImmediately
163: 00000000000241d0 183 FUNC GLOBAL DEFAULT 11 libusb_get_usb_2_0_extension_descriptor
164: 0000000000021cf0 167 FUNC GLOBAL DEFAULT 11 libusb_claim_interface
165: 0000000000024360 5 FUNC GLOBAL DEFAULT 11 libusb_free_ss_usb_device_capability_descriptor
166: 00000000000238c0 354 FUNC GLOBAL DEFAULT 11 libusb_get_config_descriptor
167: 0000000000023d50 1149 FUNC GLOBAL DEFAULT 11 libusb_get_bos_descriptor
168: 000000000001c100 330 FUNC GLOBAL DEFAULT 11 MAG_GetOutputBMPdataRGB24
169: 000000000001de60 34 FUNC GLOBAL DEFAULT 11 MAG_GetEstimatedEnvTemp
170: 000000000001c000 79 FUNC GLOBAL DEFAULT 11 MAG_GetFilteredRaw
171: 000000000001bf30 174 FUNC GLOBAL DEFAULT 11 MAG_SetIsothermalPara
172: 000000000001b810 52 FUNC GLOBAL DEFAULT 11 MAG_SetFFCMode
173: 0000000000025fd0 108 FUNC GLOBAL DEFAULT 11 libusb_event_handling_ok
174: 000000000001cbe0 74 FUNC GLOBAL DEFAULT 11 MAG_GetTemperatureProbe2
175: 000000000001c250 79 FUNC GLOBAL DEFAULT 11 MAG_GetOutputRawData
176: 0000000000026cc0 167 FUNC GLOBAL DEFAULT 11 libusb_get_pollfds
177: 000000000001c510 94 FUNC GLOBAL DEFAULT 11 MAG_GetTemperatureData
178: 0000000000022890 360 FUNC GLOBAL DEFAULT 11 libusb_error_name
179: 00000000000273c0 25 FUNC GLOBAL DEFAULT 11 libusb_bulk_transfer
180: 00000000000266e0 36 FUNC GLOBAL DEFAULT 11 libusb_handle_events
181: 000000000001b0e0 18 FUNC GLOBAL DEFAULT 11 MAG_GetLocalIp
182: 000000000001b7e0 48 FUNC GLOBAL DEFAULT 11 MAG_StopListen
183: 0000000000020f10 148 FUNC GLOBAL DEFAULT 11 libusb_get_max_iso_packet_size
184: 000000000001ded0 2838 FUNC GLOBAL DEFAULT 11 MAG_EstimateUnderArmTempFromForeheadRect
185: 000000000001c750 147 FUNC GLOBAL DEFAULT 11 MAG_GetFixPara
186: 0000000000026740 108 FUNC GLOBAL DEFAULT 11 libusb_handle_events_locked
187: 000000000001b350 154 FUNC GLOBAL DEFAULT 11 MAG_PrepareProcessImage
188: 000000000001b280 18 FUNC GLOBAL DEFAULT 11 MAG_GetMulticastState
189: 00000000000206d0 5 FUNC GLOBAL DEFAULT 11 libusb_get_device
190: 000000000001ce10 64 FUNC GLOBAL DEFAULT 11 MAG_UseTemperatureMask
191: 000000000001cd40 107 FUNC GLOBAL DEFAULT 11 MAG_GetEllipseTemperatureInfo
192: 0000000000021ca0 66 FUNC GLOBAL DEFAULT 11 libusb_set_configuration
193: 00000000000220c0 111 FUNC GLOBAL DEFAULT 11 libusb_kernel_driver_active
194: 000000000001b0b0 41 FUNC GLOBAL DEFAULT 11 MAG_IsInitialized
195: 00000000000242a0 183 FUNC GLOBAL DEFAULT 11 libusb_get_ss_usb_device_capability_descriptor
196: 000000000001b490 169 FUNC GLOBAL DEFAULT 11 MAG_StopProcessImage
197: 000000000001be50 217 FUNC GLOBAL DEFAULT 11 MAG_SetAutoEnlargePara
198: 0000000000024e10 90 FUNC GLOBAL DEFAULT 11 libusb_free_transfer
199: 00000000000221a0 111 FUNC GLOBAL DEFAULT 11 libusb_attach_kernel_driver
200: 0000000000021800 842 FUNC GLOBAL DEFAULT 11 libusb_close
201: 00000000000260c0 159 FUNC GLOBAL DEFAULT 11 libusb_interrupt_event_handler
202: 000000000001b320 37 FUNC GLOBAL DEFAULT 11 MAG_IsProcessingImage
203: 000000000001cf20 16 FUNC GLOBAL DEFAULT 11 MAG_SetSerialCmd
204: 000000000001c9e0 115 FUNC GLOBAL DEFAULT 11 MAG_FixTemperature
205: 000000000001add0 103 FUNC GLOBAL DEFAULT 11 MAG_RevisedTemp2InnerTemp
206: 000000000001de30 38 FUNC GLOBAL DEFAULT 11 MAG_GetEstimateUnderArmTempMode
207: 0000000000021170 485 FUNC GLOBAL DEFAULT 11 libusb_get_device_list
208: 0000000000026190 46 FUNC GLOBAL DEFAULT 11 libusb_unlock_event_waiters
209: 000000000001ae40 103 FUNC GLOBAL DEFAULT 11 MAG_RevisedTemp2Gray
210: 0000000000020de0 157 FUNC GLOBAL DEFAULT 11 libusb_get_port_numbers
211: 000000000001b770 105 FUNC GLOBAL DEFAULT 11 MAG_DisLinkCamera
212: 0000000000020730 30 FUNC GLOBAL DEFAULT 11 libusb_set_auto_detach_kernel_driver
213: 000000000001d880 41 FUNC GLOBAL DEFAULT 11 MAG_LinkCamera
214: 000000000001c7f0 257 FUNC GLOBAL DEFAULT 11 MAG_SetFixPara
215: 000000000001aeb0 317 FUNC GLOBAL DEFAULT 11 MAG_GetCurrentOffset
216: 0000000000022130 111 FUNC GLOBAL DEFAULT 11 libusb_detach_kernel_driver
217: 00000000000215a0 394 FUNC GLOBAL DEFAULT 11 libusb_open
218: 000000000001c2a0 171 FUNC GLOBAL DEFAULT 11 MAG_GetOutputColorBardata
219: 000000000001dcb0 99 FUNC GLOBAL DEFAULT 11 MAG_StartProcessPulseImage
220: 000000000001b030 60 FUNC GLOBAL DEFAULT 11 MAG_GetCameraTemperature
221: 000000000001bcf0 103 FUNC GLOBAL DEFAULT 11 MAG_SetColorPalette
222: 0000000000026160 46 FUNC GLOBAL DEFAULT 11 libusb_lock_event_waiters
223: 000000000001c490 57 FUNC GLOBAL DEFAULT 11 MAG_GetVideoSPS
224: 000000000001b120 48 FUNC GLOBAL DEFAULT 11 MAG_EnumCameras
225: 000000000001cf30 16 FUNC GLOBAL DEFAULT 11 MAG_SetVideoContrast
226: 0000000000023b20 152 FUNC GLOBAL DEFAULT 11 libusb_get_config_descriptor_by_value
227: 000000000001cf10 16 FUNC GLOBAL DEFAULT 11 MAG_QueryPTZState
228: 000000000001cfc0 65 FUNC GLOBAL DEFAULT 11 MAG_UnLockFrame
229: 0000000000024440 378 FUNC GLOBAL DEFAULT 11 libusb_get_string_descriptor_ascii
230: 000000000001bcc0 29 FUNC GLOBAL DEFAULT 11 MAG_TriggerFFC
231: 0000000000026250 604 FUNC GLOBAL DEFAULT 11 libusb_get_next_timeout
232: 000000000001cf00 16 FUNC GLOBAL DEFAULT 11 MAG_SetPTZCmd
233: 0000000000020710 30 FUNC GLOBAL DEFAULT 11 libusb_dev_mem_free
234: 0000000000021ef0 100 FUNC GLOBAL DEFAULT 11 libusb_clear_halt
235: 0000000000022a00 8 FUNC GLOBAL DEFAULT 11 libusb_get_version
236: 0000000000024370 183 FUNC GLOBAL DEFAULT 11 libusb_get_container_id_descriptor
237: 0000000000021730 195 FUNC GLOBAL DEFAULT 11 libusb_open_device_with_vid_pid
238: 000000000001c450 57 FUNC GLOBAL DEFAULT 11 MAG_GetVideoPPS
239: 000000000023f60c 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
240: 000000000001c350 171 FUNC GLOBAL DEFAULT 11 MAG_GetOutputVideoData
241: 000000000001f830 346 FUNC GLOBAL DEFAULT 11 MAG_SaveDDT
242: 000000000001d0f0 32 FUNC GLOBAL DEFAULT 11 MAG_SDCardStorage
243: 000000000001b440 69 FUNC GLOBAL DEFAULT 11 MAG_TransferPulseImage
244: 000000000001c9a0 61 FUNC GLOBAL DEFAULT 11 MAG_SetDetailEnhancement
245: 000000000001d090 16 FUNC GLOBAL DEFAULT 11 MAG_SDStorageMGT
246: 0000000000024820 167 FUNC GLOBAL DEFAULT 11 libusb_hotplug_deregister_callback
247: 0000000000024290 5 FUNC GLOBAL DEFAULT 11 libusb_free_usb_2_0_extension_descriptor
248: 0000000000027130 652 FUNC GLOBAL DEFAULT 11 libusb_control_transfer
249: 000000000001d0a0 16 FUNC GLOBAL DEFAULT 11 MAG_SDStorageBMP
250: 000000000001cc30 119 FUNC GLOBAL DEFAULT 11 MAG_GetLineTemperatureInfo
251: 0000000000025bf0 4 FUNC GLOBAL DEFAULT 11 libusb_transfer_set_stream_id
252: 0000000000026220 46 FUNC GLOBAL DEFAULT 11 libusb_pollfds_handle_timeouts
253: 000000000001f990 227 FUNC GLOBAL DEFAULT 11 MAG_SaveDDT2Buffer
254: 000000000001cac0 52 FUNC GLOBAL DEFAULT 11 MAG_GetFrameStatisticalData
255: 0000000000025f70 92 FUNC GLOBAL DEFAULT 11 libusb_unlock_events
256: 000000000001d0c0 16 FUNC GLOBAL DEFAULT 11 MAG_SDStorageMGSStop
257: 0000000000023bc0 34 FUNC GLOBAL DEFAULT 11 libusb_free_config_descriptor
258: 0000000000024e70 1080 FUNC GLOBAL DEFAULT 11 libusb_submit_transfer
259: 000000000001cf70 65 FUNC GLOBAL DEFAULT 11 MAG_LockFrame
260: 000000000001d0e0 16 FUNC GLOBAL DEFAULT 11 MAG_SDStorageAviStop
261: 000000000001d080 5 FUNC GLOBAL DEFAULT 11 MAG_LoadBufferedDDT
262: 000000000001b5e0 183 FUNC GLOBAL DEFAULT 11 MAG_Free
263: 000000000001d0d0 16 FUNC GLOBAL DEFAULT 11 MAG_SDStorageAviStart
264: 000000000001b100 7 FUNC GLOBAL DEFAULT 11 MAG_SetFilter
265: 000000000001ca60 91 FUNC GLOBAL DEFAULT 11 MAG_FixTemperature2
266: 0000000000022600 652 FUNC GLOBAL DEFAULT 11 libusb_exit
267: 000000000001b070 60 FUNC GLOBAL DEFAULT 11 MAG_GetSenorTemperature
268: 000000000001bce0 16 FUNC GLOBAL DEFAULT 11 MAG_SetIoAlarmState
269: 000000000001de90 58 FUNC GLOBAL DEFAULT 11 MAG_SetEnvTempEstimateMode
270: 00000000002666f0 0 NOTYPE GLOBAL DEFAULT 25 _end
271: 00000000000248d0 543 FUNC GLOBAL DEFAULT 11 libusb_hotplug_register_callback
272: 000000000001c050 171 FUNC GLOBAL DEFAULT 11 MAG_GetOutputBMPdata
273: 000000000001c400 79 FUNC GLOBAL DEFAULT 11 MAG_GetOutputVideoYV12
274: 0000000000026d70 18 FUNC GLOBAL DEFAULT 11 libusb_free_pollfds
275: 000000000002c734 0 FUNC GLOBAL DEFAULT 12 _fini
276: 0000000000021da0 149 FUNC GLOBAL DEFAULT 11 libusb_release_interface
277: 0000000000020600 5 FUNC GLOBAL DEFAULT 11 libusb_get_parent
278: 000000000001cb40 152 FUNC GLOBAL DEFAULT 11 MAG_GetTemperatureProbe
279: 0000000000021e40 175 FUNC GLOBAL DEFAULT 11 libusb_set_interface_alt_setting
280: 000000000023f60c 0 NOTYPE GLOBAL DEFAULT 24 _edata
281: 000000000001d0b0 16 FUNC GLOBAL DEFAULT 11 MAG_SDStorageMGSStart
282: 000000000001abd0 434 FUNC GLOBAL DEFAULT 11 MAG_NewChannel
283: 000000000001bff0 16 FUNC GLOBAL DEFAULT 11 MAG_ResetCamera
284: 0000000000021fb0 135 FUNC GLOBAL DEFAULT 11 libusb_alloc_streams
285: 000000000001b1f0 60 FUNC GLOBAL DEFAULT 11 MAG_SetReConnectCallBack
286: 0000000000026710 37 FUNC GLOBAL DEFAULT 11 libusb_handle_events_completed
287: 000000000001c570 472 FUNC GLOBAL DEFAULT 11 MAG_GetTemperatureData_Raw
288: 000000000001b190 16 FUNC GLOBAL DEFAULT 11 MAG_ListenTo
289: 000000000001b230 77 FUNC GLOBAL DEFAULT 11 MAG_GetCamInfo
290: 00000000000267b0 44 FUNC GLOBAL DEFAULT 11 libusb_set_pollfd_notifiers
291: 00000000000273e0 25 FUNC GLOBAL DEFAULT 11 libusb_interrupt_transfer
292: 0000000000026040 124 FUNC GLOBAL DEFAULT 11 libusb_event_handler_active
293: 000000000001b150 44 FUNC GLOBAL DEFAULT 11 MAG_IsLinked
294: 0000000000023bf0 240 FUNC GLOBAL DEFAULT 11 libusb_get_ss_endpoint_companion_descriptor
295: 0000000000021b50 333 FUNC GLOBAL DEFAULT 11 libusb_get_configuration
296: 000000000001b6a0 195 FUNC GLOBAL DEFAULT 11 MAG_DelChannel
297: 000000000001ddd0 85 FUNC GLOBAL DEFAULT 11 MAG_SetEstimateUnderArmTempMode
298: 0000000000020620 4 FUNC GLOBAL DEFAULT 11 libusb_get_device_speed
299: 000000000001ce50 62 FUNC GLOBAL DEFAULT 11 MAG_IsUsingTemperatureMask
300: 0000000000021420 378 FUNC GLOBAL DEFAULT 11 libusb_wrap_fd
301: 0000000000023740 72 FUNC GLOBAL DEFAULT 11 libusb_get_device_descriptor
302: 0000000000022040 119 FUNC GLOBAL DEFAULT 11 libusb_free_streams
303: 0000000000022210 993 FUNC GLOBAL DEFAULT 11 libusb_init
304: 00000000000206e0 35 FUNC GLOBAL DEFAULT 11 libusb_dev_mem_alloc
305: 000000000001ccb0 143 FUNC GLOBAL DEFAULT 11 MAG_GetRectTemperatureInfo
306: 0000000000023cf0 87 FUNC GLOBAL DEFAULT 11 libusb_free_bos_descriptor
307: 00000000000261c0 92 FUNC GLOBAL DEFAULT 11 libusb_wait_for_event
308: 000000000001c960 49 FUNC GLOBAL DEFAULT 11 MAG_GetEXLevel
309: 0000000000025f30 52 FUNC GLOBAL DEFAULT 11 libusb_lock_events
310: 00000000000252b0 241 FUNC GLOBAL DEFAULT 11 libusb_cancel_transfer
311: 0000000000021060 71 FUNC GLOBAL DEFAULT 11 libusb_free_device_list
312: 0000000000024430 5 FUNC GLOBAL DEFAULT 11 libusb_free_container_id_descriptor
313: 0000000000020630 26 FUNC GLOBAL DEFAULT 11 libusb_ref_device
314: 000000000001bd60 232 FUNC GLOBAL DEFAULT 11 MAG_SetSubsectionEnlargePara
315: 000000000001d780 243 FUNC GLOBAL DEFAULT 11 MAG_LinkCameraEx
316: 00000000000205f0 5 FUNC GLOBAL DEFAULT 11 libusb_get_port_number
317: 000000000001b2a0 114 FUNC GLOBAL DEFAULT 11 MAG_GetTerminalList
318: 0000000000020610 5 FUNC GLOBAL DEFAULT 11 libusb_get_device_address
319: 000000000001cdb0 94 FUNC GLOBAL DEFAULT 11 MAG_GetRgnTemperatureInfo
320: 0000000000026550 375 FUNC GLOBAL DEFAULT 11 libusb_handle_events_timeout_completed
321: 000000000001cf60 16 FUNC GLOBAL DEFAULT 11 MAG_SetVideoBrightness
322: 000000000001bfe0 16 FUNC GLOBAL DEFAULT 11 MAG_SetUserROIs
323: 000000000001cb00 52 FUNC GLOBAL DEFAULT 11 MAG_GetRemoteInfo
324: 000000000001aff0 60 FUNC GLOBAL DEFAULT 11 MAG_GetCurrentCameraInnerTemperature
325: 0000000000021f60 71 FUNC GLOBAL DEFAULT 11 libusb_reset_device
326: 000000000001b180 16 FUNC GLOBAL DEFAULT 11 MAG_IsListening
327: 0000000000020750 22 FUNC GLOBAL DEFAULT 11 libusb_set_debug
Binary file not shown.
+416
View File
@@ -0,0 +1,416 @@
# MAG160C 项目交接文档 (2026-08-10)
> **2026-08-10 晚更新(本会话)**:FFC 停帧问题已解决并多次实机验证;
> 坏点/对比度/标定管线已按开源方案重做并合入 csdk。详见第 10 节。
## 1. 项目目标
对 Elo/Magnity MAG160C USB 热成像相机进行完整逆向,实现一个独立、可用的
Linux/Windows C SDK + Windows GUI Demo。已完成的逆向依据:
- `analysis/protocol_spec.md`(协议全谱)
- `analysis/findings.md`(证据链)
- `analysis/captures/libusb0_trace.txt`(官方 demo 完整 USB 流量,26092 行)
- `analysis/captures/official_hand.pcap`(官方 demo 手掌遮挡抓包)
- `analysis/captures/csdk_verified.pcap`(csdk 抓包)
## 2. 硬件事实(实机确认)
- 设备名:Magnity Thermal Camera,VID 0x833C PID 0x0001,序列号 160043865
- 分辨率 160x120,帧率 ~15fps,每帧 38456 字节
- USB:config 值 1(仅一个配置),interface 0,端点:0x03 OUT、0x81/0x82/0x84 IN
- 设备只有 config 1——Linux SDK 逆向中的 set_configuration(2) 在这台机器上失败,
会回退到 1(逆向代码 0x1865b 分支)
## 3. 已验证协议(权威,实机+官方流量双重确认)
### 3.1 命令(4 字节魔数,无参数;仅 FFC 用 8 字节 {magic,param})
| 命令 | 字节 | 响应 |
|---|---|---|
| 0x6bb6b66b 信息查询 | 4 | 0x5bb5b55b(60B:pid@+4,width=160@+0x14,height=120@+0x18,fps=15@+0x1c) |
| 0x6bb6b66c | 4 | 0x5bb5b55c(60B,另一数据块,勿当 info 解析) |
| 0x6bb6b66f | 4 | 0x5bb5b55e(20B) |
| 0x6bb6b672 FFC | 8 | 0x5bb5b55f(4B OK) |
| 0x6bb6b673 START | 4 | 0x5bb5b55f |
| 0x6bb6b674 STOP | 4 | 0x5bb5b55f(需先 clear_halt 0x03/0x82) |
注意:帧头字段偏移以"帧内"为准:marker@0、cnt@4、len@8、type@0xc、shutter@0x10
### 3.2 启动序列(官方一致)
```
open(config 1, claim iface 0)
66b → 66c → 66f (各 4B)
FFC(0) → 100ms → FFC(0) → 300ms → START(4B) → 700ms
```
### 3.3 FFC 语义(关键难点)
- FFC(1) 使流切换为 **type=0 帧(温度数据,avg ~11720,手掌遮挡 +1200 counts)**
- FFC(0) 使流切换为 type=1 帧(avg ~16000,手掌遮挡变化小 <100)
- **官方 demo 在 type=0 下每 ~10 帧发一次 FFC(0/1 交替)维持推流**
- FFC 必须在"完整帧(头+数据)之后"发送
- 问题:在 libusb-1.0(TI DLL)或 libusb0 下,FFC(1) 切换后设备经常停帧;
`csdk/tools/thermal_viewer.c`(单线程、frames==3 时 FFC(1)+Sleep(400))
能稳定工作(已验证多次,显示 type=0 帧 100+)
- `csdk/tools/mag160c_demo2.c` 基于 viewer 核心,但被强杀/反复运行后设备
进入坏状态需 libusb_reset_device 重试(已加 4 次重试)
### 3.4 帧格式
```
+0x00 u32 0x1bb1b11b
+0x04 u32 frame counter
+0x08 u32 0x9600 (38400 data length)
+0x0c u32 type (0=温度/response, 1=raw)
+0x10 u32 shutter
+0x14..0x1b 保留
+0x1c .. 38400 字节像素 (u16 LE)
+0x1c+38400 u32 0x1bb1b11c (尾标记)
之后 24 字节尾字段(含 0x6cf0 类统计值)
```
## 4. 温度数据(实测)
- type=0 帧:背景 avg≈11720,手掌遮挡(整个 FOV)+1200 counts
- 线性标定:约 **147 counts/°C**(假设手掌 34°C vs 背景 25°C,未精确标定)
- demo 温度显示:25 + (v - reference)/147
- 官方 Android SDK 有 T2E 表(0x112 项,已 dump 到 analysis/t2e_table.json)
和 E2TAccQ10 表(逆向的 ReviseTemperature/CorrectTemperature 用)
## 5. 当前 Demo 状态(csdk/tools/)
| 文件 | 状态 |
|---|---|
| `thermal_viewer.c` | ✅ 稳定工作:单线程循环、frames==3 时 FFC(1)、percentile 2%-98% 渲染、type=0 显示 |
| `mag160c_demo2.c` | 🔶 基于 viewer 核心,加面板/按钮/温度/坏点检测;有设备状态残留问题(需 reset 重试) |
| `mag160c_demo.c` | ❌ 弃用(自行写的 init/FFC 逻辑设备会停帧) |
| `mag160c_demo_usb0.c` | ❌ 弃用(libusb0 API 版,同样停帧——问题不在传输层) |
### 已知显示问题(需参考开源实现解决)
1. **坏点(红蓝固定点)**:type=0 帧存在固定坏像素,显示为红/蓝点。
已在 demo2 加"参考采集时 min-max 波动 >400 标记坏点、邻居平均替换",
但用户反馈仍可见(阈值/方法需优化,或坏点表应在 FFC 后重新采集)
2. **对比度**:diff 模式(当前-参考,deadband±70,span 1500)手掌轮廓可见但偏弱;
absolute 模式 percentile 拉伸对比度差
3. **FFC 后设备停帧**:不稳定,与官方 demo(持续工作)行为不一致,
差异可能在于官方 FFC 的精确时序/频率
## 6. 环境与工具
- Windows 11,MinGW gcc 14.2 (C:\mingw64)
- 设备驱动:**libusb-win32 v1.2.6.0**(Zadig 安装;WinUSB 也可但 TI libusb DLL 的
FFC 写失败;官方 libusb-1.0.27 在两种驱动下都崩溃)
- 传输库:**TI libusb-1.0.dll**(C:\ti\ccs2050\ccs\ccs_base\common\bin\,
支持 libusb0 后端,有无害的 monotonic clock 警告)
+ dlltool 生成的导入库 csdk/third_party/libusb/win64/
- USBPcap 1.5.4 + Wireshark(重启后可用,设备在 \\\\.\USBPcap4)
- 抓包 pcap 解析脚本:analysis/captures/ 下(参考之前会话)
## 7. 开源热成像 SDK 参考(下一轮调研目标)
网络在部分时段可用,建议用 webfetch 搜索:
- **FLIR Lepton SDK**(github: groupgets/LeptonModule 或 purethermal1):展示了
raw→radiance→温度、AGC(自动增益控制)、伪彩色流程;坏点补偿思路
- **Seek Thermal 开源驱动**(github: ThermalScope/seek-thermal-camera 或
fnoop/seek-thermal-linux):EEPROM 坏点表、NUC(非均匀性校正)
- **MLX90640 库**(github: intergatedcircuits/Pico-MLX90640):插值+显示
- **AMG8833/MLX90640 的 Adafruit 库**:显示映射方法
- 关键要学习:①坏点检测与补偿算法 ②AGC/显示拉伸(直方图均衡 vs percentile)
③NUC/FFC 后的温度图管线 ④伪彩色 LUT 设计
## 8. 下一步工作
1. 调研开源热成像显示管线,改进 demo2 的:坏点补偿、AGC(显示拉伸)、伪彩色
2. 稳定 FFC:对照官方 trace 精确复刻 FFC 时序(包括周期性),解决停帧
3. 温度标定:用已知温度(冰水/热水)做两点标定,替代 147 counts/°C 假设
4. 把验证过的协议与显示管线合并回 csdk 的 mag160c_ir 实现
5. Linux 移植(最终目标)
## 9. 快速启动命令
```powershell
# 设备恢复(设备停帧时)
.\build-artifacts\usb_hardreset.exe
# 运行 demo(能看手掌的稳定版)
.\build-artifacts\thermal_viewer.exe
# 功能完整版(重试+坏点+新 FFC 调度)
.\build-artifacts\mag160c_demo2.exe
# 无头 FFC 稳定性验证(1400 帧 type=0,~95s)
.\build-artifacts\mag160c_ffc_test.exe
# csdk 线程管线验证(reader thread + FFC scheduler)
.\build-artifacts\csdk_stream_test.exe
# csdk CLI 综合(显示管线自检 / 流验证)
.\build-artifacts\csdk_cli_libusb.exe display-test
.\build-artifacts\csdk_cli_libusb.exe ffc-test 1000
# 抓包
& "C:\Program Files\USBPcap\USBPcapCMD.exe" -d \\.\USBPcap4 -o out.pcap -A -s 65535
```
## 10. 2026-08-10 晚:FFC 停帧解决 + 显示管线重做(本会话)
### 10.1 官方 FFC 时序(从 libusb0_trace.txt 精确解码)
逐行统计 26092 行官方流量,每帧 = 28B 头 + 38428B 数据。FFC 事件与帧计数:
```
init: 66b 66c 66f -> FFC(0) -> START -> [frame 1] -> FFC(0)
frame 10: FFC(1) -> 流切换 type=0(第 11 帧起)
然后周期循环: FFC(0) --9 帧--> FFC(1) --N 帧--> FFC(0) ...
```
- **FFC(1) 总是在 FFC(0) 后恰 9~10 帧**发出(0.6s@15fps,即快门闭合→恢复)。
- FFC(1) 后到下一个 FFC(0) 的间隔 N 不固定:34, 48, 66, 101, 166, 373, 346,
103, 1383, 381, 48, 1006, 317, 171, 158, 189, 219, 294, 425, 628, 723,
1012, 1450, 1804, 2680, 403, 105 …(用户/定时器驱动,非固定周期)。
- 全程 10898 帧持续推送,type=0 占 10519 帧(96.5%),type=1 只出现在每个
FFC(0)→FFC(1) 的 9 帧窗口。
- **FFC 必须在一整帧(头+数据)读完之后发送**(trace 中 FFC 行紧跟帧数据行)。
- **教训**:demo2 旧版只发一次 FFC(1) 且周期 44 帧过密 → 设备停帧或 type=1
占比过高(17.7%)、fps 下滑。官方式 **周期 400 帧 + 9 帧间隔** 完美工作。
### 10.2 实机验证结果(全部 PASS)
| 验证项 | 工具 | 结果 |
|---|---|---|
| FFC 周期 400 + gap 9 | `mag160c_ffc_test.exe` | 1400 type=0 帧 / 95s / 15.1fps / 0 停帧(2 次) |
| csdk 线程管线(reader thread + scheduler) | `csdk_stream_test.exe` | 1030 type=0 帧 / 70s / 14.7fps / 0 停帧 |
| csdk CLI `ffc-test` | `csdk_cli_libusb.exe` | 同上,PASS |
| 显示管线单测 | `csdk_test_display.exe` | 20/20 通过 |
| GUI demo2 | `mag160c_demo2.exe` | 窗口实时显示 type=0,坏点红蓝占比 0.02%,无饱和坏点 |
| 回归(旧 4 套) | csdk test_* | 全部通过 |
### 10.3 显示管线(按开源方案重做,已合入 csdk)
新增 `csdk/src/mag160c_display.c` + `csdk/include/mag160c/mag160c_display.h`,
纯 C 无平台依赖(Linux 可直接编):
1. **坏点检测**(参考 OpenThermal/libseek-thermal SeekCam.cpp + MLX90640):
- 时域:参考帧 min-max 波动 > 400 → 坏点(保留旧法)
- 直方图峰偏离(Seek 法):`thr = histPeak - (frameMax - histPeak)`,
并加 `histPeak + 200` 下界防孤立极值误杀全图
- **拓扑序填充**:坏簇从边缘向中心迭代,存填充顺序;参考图与每帧
都按该顺序用 4 邻域均值替换(坏簇逐层内缩)
2. **AGC/显示拉伸**:
- 绝对模式:2%~98% percentile 拉伸 + **ironbow LUT**(FLIR 风格 7 锚点)
- diff 模式:自适应 span = 2×mean|diff|,clamp [120, 4000],
deadband = span/20(轮廓对比度显著提升)
3. **FFC 调度器** `mag160c_ffc_scheduler_t`:tick(帧后)返回应发的 FFC
参数(0/1/-1);`mag160c_ir_set_ffc_scheduler()` 挂到 reader thread,
Linux 线程模型直接可用。
4. **两点线性标定** `mag160c_temp_calibrate_linear(counts0,temp0,counts1,temp1)`
→ a/b;demo2 有 Cal Cold(0°C)/Cal Hot(90°C) 按钮,CLI 有 `calibrate` 占位。
### 10.4 遗留事项(下一步)
- **物理两点标定**:需实际操作(冰水 0°C / 开水 90°C+ 充满 FOV),用 demo2
的 Cal Cold/Cal Hot 按钮或记录 counts 代入 `mag160c_temp_calibrate_linear`
当前仍是 147 counts/°C 假设(参考背景 ~11720 counts @ 25°C)。
- demo2 内联的坏点/AGC 逻辑与 csdk 模块功能重复,后续可让 demo2 直接调
csdk 模块(现 demo2 已用 csdk 的 FFC scheduler)。
- Linux 移植:csdk 侧已就绪(纯 C + pthread + libusb);GUI demo 的 Win32
部分需换 SDL/GTK。
### 10.5 后续小 bug 修复(2026-08-10 深夜)
用户反馈两个现象,均已修复并实机验证:
1. **鬼影**:旧参考帧用逐像素 EMA(`(r*199+v)/200`)持续吸收当前画面,
静止物体 ~13s 就被"吃进"参考帧,移开后留下反向残影。改为**漂移跟随**:
仅当场景安静(mean|diff| < 40)时,把参考帧整体平移全局均值差
(clamp ±400),不再吸收任何空间内容 —— 跟温度漂移/FFC 基线,不跟场景。
2. **FFC 响应红黄闪烁 + 卡顿**:FFC(0) 后设备输出 ~9 帧 type=1(avg ~16000,
与 type=0 基线差 +4300),旧 demo 照常渲染 → 全屏红黄;手动 FFC 按钮在
UI 线程 `Sleep(300)` 阻塞。修复:
- 主循环**跳过非 type=0 帧的渲染**(冻结画面,与官方 app 一致)—— 实测
218s/3744 帧/~8 次自动 FFC 周期标题栏从未出现 type=1,闪烁消除。
- 手动 FFC 改**异步排队**:按钮只置 `g_manual_ffc` 标志,主循环在下一
完整帧后经 `mag160c_ffc_scheduler_trigger()` 发 FFC(0),9 帧后自动
发 FFC(1),UI 不再阻塞。
- csdk 新增 `mag160c_ffc_scheduler_trigger()`(test_display 22/22 通过)。
### 10.6 二次修复:鬼影根因 + FFC 残闪(2026-08-10 深夜,实机验证)
用户复测:鬼影仍存在,且高温物体进入鬼影区会被"遮住"成低温色;
红黄帧不再卡住但偶尔闪过。根因分析 + 修复(参考 OpenCV MOG 背景建模
与 Seek 挡片校准思路):
1. **鬼影根因**(两层):
- 旧参考更新用"全局 mean drift":物体占 FOV 比例小时 mean|d| < 40
误判安静,全局偏移把**小物体逐步吸收进参考帧**(数秒~数十秒),
移开后留下高温残影;真实高温物体移到该位置时 diff=live-ref 变负
→ 显示成低温,即"被遮住"。
- 初始参考采集是 30 帧**均值**:采集窗口内若有物体在动,均值会把
物体烘焙进参考,永久鬼影。
- 修复:
a. **MOG 逐像素背景模型**(`mag160c_display_ref_track`):背景像素
(|d| < 60)慢速 EMA 跟踪漂移;前景像素(|d| >= 60)**完全冻结**,
永不吸收(之前是 clamp+漏吸收,约 112 counts/s 泄漏)。
b. **安静门控的初始采集**:每帧与上一帧 mean|Δ| < 25 才入样本,
30 帧逐像素取**中位数**(物体短暂进入窗口不会污染参考);
场景一直不静则参考一直不落(可手动 Set reference)。
c. 合成测试 `MOG_GHOST_TEST`:静态 +1200 热块 4500 帧(模拟 5 分钟)
吸收 0 像素;移开后 diff≈0 无鬼影;慢漂移仍可跟踪 → PASS。
2. **FFC 残闪**:FFC(1) 后 type=0 基线跳变(官方 trace 实测最大 +1000
counts),旧重对齐 ±400 clamp 不够,残余偏移需 MOG 数秒才消化 →
短暂偏红。修复:重对齐 clamp 放宽到 ±2000(中位数本身抗 >50% FOV
物体),FFC(1) 后跳过前 2 帧渲染(`g_skip_ffc`)。
实测连续 3+ 个 FFC 周期(period 400)红色占比前后无跳变(11.1% →
11.1%),蓝色始终 0.01%,type=1 帧从不渲染,流不断(1500+ 帧)。
证据:`analysis/demo2_v3_mog_verified_20260810.png`;单测 28/28 通过
(`csdk_test_display.exe`),旧 4 套回归全绿。
### 10.7 三次修复:开机乱图 + 残余鬼影(2026-08-10 深夜,实机验证)
用户复测:FFC 红黄闪帧已解决,但(1) 刚开软件画面有红黄点乱分布,
且不会消失、叠加到正常画面上;(2) 鬼影仍存在。
**根因**(两层,均为 MOG"永久冻结"缺陷):
1. **开机乱图**:参考帧首次采集在 nread>=150(~10s,第一个自动 FFC 周期
之前)进行。设备刚完成启动 FFC(1),type=0 基线/坏点尚未稳定,启动
噪声(红黄点)被中位数采入参考;之后 live 已正常,这些像素
|d| >= 60 被 MOG **永久冻结** → 乱图叠加且永不消失。
2. **鬼影**:同上——参考一旦采错(含物体/含噪声),MOG 冻结使其无法自愈;
FFC 后一次性 median rebase 只平移全局,修不掉逐像素错误。
**修复**:
1. **首次参考采集提前到启动 FFC(1) 稳定之后**(nread >= 40 + 跳过 2 帧
过渡),不在第一个自动 FFC 周期前采集 → 启动噪声不再进入参考。
2. **每次 FFC(1) 后重新采集参考**(REF_REINIT_AFTER_FFC):FFC 校准后
设备输出最干净的 type=0 帧,用它重建参考(安静帧中位数,30 帧,
沿用坏点表填充),取代一次性 rebase。每 ~27s 自动刷新一次,任何
残留错误最长一个周期内被清除。
3. **MOG 自愈**(`mag160c_display_ref_track_heal`):孤立像素(8 邻域前景
< 2)连续前景 >= 30 帧 → 判定为参考错误(启动噪声/坏点)而非物体
(物体是连片的),重置 ref = live。乱图/残点最长 ~2s 内自愈。
**验证**:
- 合成测试 `SELFHEAL_TEST` / csdk `test_display` 30/30:40 个孤立噪声点
全被治愈;60x40 连片热块 200 帧吸收 0 像素、不被误治;移开后无鬼影。
- 实机:开机 t=4s 孤立噪声 0.12%,t=34s(参考+FFC 后)0.11%,t=90s
(5+ FFC 周期)0.10% —— 乱图不出现、不叠加、不增长;红色像素全部为
真实场景连片目标(45~71 个大 blob);FFC 周期红%无跳变(6.4~8.6%),
蓝%恒 0.01%;流持续 2157+ 帧无停帧。
证据:`analysis/demo2_v4_selfheal_verified_20260810.png`
### 10.8 四次修复:根因定位(传感器 mura)+ NUC 平场校正(2026-08-10 深夜,数据驱动)
用户复测:开机乱图和鬼影依然存在。本次用数据驱动定位**真正根因**,并
采用开源(Seek/FLIR 挡片帧)同款方案修复。
**实测数据(mag160c_frame_dump 抓 60 帧 type=0 分析)**:
- 每像素时域噪声 std ~248 counts(99.9% 像素"波动")→ diff 模式自适应
span≈400、deadband≈20 把噪声全部放大成可见红蓝点 = **开机乱图**
- 固定传感器 mura:行剖面跨度 15594(row1 恒定 +5000,25418 vs row98 9824)、
列剖面 8714;FFT 行/列轴能量占比 ~10%
- mura 会话内漂移 ~350 counts,FFC 后突变 → diff 参考帧跟不上 = **鬼影**
- flat-field 校正验证:`live - (ref - mean(ref))` 把 mura std 3557→379
(行跨度 15704→401),中值滤波后噪声 ~80
**为什么官方 app 没有乱图/鬼影**:Android 官方管线是 raw→温度→**绝对
温度窗口显示(25~40°C)**,不用场景参考帧做 diff,天然免疫噪声放大与
参考漂移。
**修复(推翻 diff 方案,改 NUC 平场 + 绝对温度显示)**:
1. **flat-field(NUC)校正**(`mag160c_display_nuc`,csdk):`nuc = live -
(ref - mean(ref))`,消除固定 mura;显示绝对温度级别,median 居中
±1500 窗口 + ironbow。
2. **3x3 中值平滑**:把 248-count 时域噪声降 ~3 倍,画面平滑无噪点。
3. 参考帧只做**固定图案**,不跟踪场景:采集时 5% 像素偏离阈值即拒帧
(持久物体永不进参考)+ MOG 冻结 + FFC 后重采集 → 无鬼影。
4. diff 模式降级为可选(默认 absolute)。
**实机验证**:speckle residual 2.2~2.7(平滑)、纯蓝 0%、红色均为真实场景
连片目标(1~1.4%);连续 5+ FFC 周期无闪帧、无图案出现、无噪声增长;
流持续 2268+ 帧。csdk 单测 32/32,旧回归全绿。
证据:`analysis/demo2_v5_nuc_verified_20260810.png`。
工具:`csdk/tools/mag160c_frame_dump.c`、`mag160c_frame_stats.c`。
### 10.9 五次修复:数据驱动根因 + 官方 DLL 活体对比(2026-08-10 深夜)
用户复测:画面更混乱。本次彻底转向**官方实现对比**:
**逆向成果(活体官方管线)**:
1. 反汇编 Windows 官方 CoreSDKLib.dll + ThermalSDK.dll(65 个 MAG 导出,
精确 RVA 全部解析),确认官方渲染管线:
- `MAG_GetOutputBMPdataRGB24`:8-bit 灰度 `[dev+0xb00]` → 调色板查表
(256x4 BGR),输出 320x240 RGB
- `MAG_GetTemperatureData`:逐像素 counts→temp,T2E 表(646 项,已从
`.rdata 0x5cde0` 提取)+ 斜率表,公式:
`temp = slope[i]*diff>>12 + (i<<12) - 0x249f0`
- Windows 调用序列:NewChannel(chan) → EnumCameras(chan) →
LinkCamera(chan,pid) → StartProcessImage(chan)
2. **用官方 ThermalSDK.dll 活体驱动设备成功**(tsdk_debug/tsdk_capture3):
- 官方渲染帧 320x240,**紫红调色板(G=0,R/B 渐变),极平滑
(speckle 0.00%,仅 106 色),无 mura 无噪点**
- 官方温度:背景 26.7~27.4°C(ReadTemperatureAtPoint)
3. **数据对比锁定根因**:
- counts 有大尺度固定 mura:空间 std 3560、行跨度 15000+、
row0 17318 → row120 7954(固定,会话间一致,row1 漂移 1535/FFC)
- **官方图像完全无此 mura** → 官方用 per-pixel 校正(baseline/DDT)
消除了它
- **NUC 验证**:`live - (ref - mean(ref))` 把 mura std 3560→29
(行跨度 15000→23~49),mura 消除 99%
4. **demo2 的 bug**:参考采集的 quiet-gate(要求 mean|d|<25)在用户
观看时永不满足 → **NUC 从未激活** → 显示 raw counts 乱图。
**修复(最终方案)**:
1. 参考采集**去掉 quiet-gate**,直接取 30 帧逐像素**修剪中位数**
(中间 50% 均值,抗物体/死点)
2. **NUC 平场校正**为默认显示(绝对温度),消除 mura
3. **自适应窄窗口**:NUC 值 median ± 4*mean|d|(实测 std~29 → span~120,
手掌 +1200 清晰弹出),替代过宽的 ±1500 固定窗口
4. 3x3 中值平滑 + MOG 冻结/自愈 + FFC 后重采集(保留)
**实机验证**:unique colors 22260(高对比)、纯蓝 0%、孤立噪点 0.62%、
310 个大 blob(真实场景)、speckle 2.3、6+ FFC 周期无闪帧无鬼影累积、
流持续 2621+ 帧。csdk 单测 32/32 通过。
证据:`analysis/demo2_v6_nuc_final_20260810.png`。
工具:`csdk/tools/tsdk_debug.c`、`tsdk_capture3.c`、`official_harness.c`、
`analysis/temp/official_t2e.txt`(官方 646 项 T2E 表)。
### 10.10 六次修复:逐像素复刻官方显示管线(2026-08-10 深夜)
用户指出显示效果与官方不一致。通过**官方 DLL 活体驱动 + 逐层提取**,
完整复刻了官方的显示管线:
**逆向提取(全部来自官方活体)**:
1. **官方温度公式**(CoreSDKLib 0x180016290):counts → 646 项 T2E 表
二分查找 + 斜率插值:
`temp_mc = slope[i]*diff>>12 + (i<<12) - 0x249f0`
T2E 表已从 .rdata 0x5cde0 提取(646 int32,单调递增)。
**shift=6 验证**:counts 11224 → 24.8°C(与官方探针 26.7-27.4 一致,
差异来自 NUC/offset)。
2. **官方调色板**(从 8 帧官方渲染提取 115 色 + 插值到 256 项):
**品红/紫红系**:深红(74,0,0)→ 红 → 品红(145,0,145)→ 紫 →
蓝紫(115,32,210)→ 紫(154,0,188)。G 通道中段有小值。
3. **官方显示特征**:背景落在**调色板中段(品红)**,不是黑/深红——
因为显示窗口围绕背景温度(自适应),背景 ~25°C 映射到灰度 ~128。
**demo2 复刻(最终显示管线)**:
```
counts → NUC 平场(live - (ref - mean(ref)),消除 mura)
→ T2E 官方温度(shift=6)
→ 自适应窄窗口(背景温度 ± 2°C)
→ 官方品红调色板查表
```
**两个关键 bug 修复**:
1. 窗口单位 bug:temp_mc 是毫°C,窗口却用 °C 比较 → 全图灰度 255。
2. NUC 参考被 MOG 更新吸收背景 → NUC 失效(参考只 FFC 后重采集,
MOG 仅用于可选 diff 模式)。
**实机验证**:画面 mean RGB 139/25/144 vs 官方 141/1/130(几乎一致);
背景品红(139,0,117)与官方(145,0,145)同色系;黑色 0.1% vs 0.0%;
4+ FFC 周期无闪帧、稳定。csdk 单测 32/32。
证据:`analysis/demo2_v7_official_look_20260810.png`。
新增:`csdk/src/mag160c_official_palette.h`(256 项官方调色板)、
`csdk/src/mag160c_official_t2e.h`(646 项官方 T2E 表)、
`csdk/tools/tsdk_pair.c`、`tsdk_gray.c`、`tsdk_palette.c`。
+191
View File
@@ -0,0 +1,191 @@
# MAG160C 逆向交接文档 v2(2026-08-11)
> **本文件是当前最新、最完整的上下文快照。** 覆盖硬件/协议/官方SDK逆向/
> 显示管线演进/全部失败教训。下一轮请从第 1 节开始读,严格按第 7 节
> 的方法论执行。
## 1. 项目目标
对 Elo/Magnity MAG160C USB 热成像相机(160x120, 15fps)完整逆向:
1. 官方从"USB 读取 raw → 显示到屏幕"的**完整管线**逐层复原
2. 自研 C SDK(csdk/)+ Windows demo 的**最终屏幕画面必须与官方 app 一致**
3. Linux 移植(最终目标)
## 2. 硬件与协议(已验证,权威)
### 2.1 USB 拓扑
- VID 0x833C PID 0x0001,序列号 160043865,config 1,interface 0
- 端点:0x03 OUT(命令)、0x82 IN(命令响应)、0x81 IN(帧流)、0x84 IN(大读取,未用)
- 驱动:libusb-win32 v1.2.6.0;传输库:TI libusb-1.0.dll + dlltool 导入库
### 2.2 命令(已验证,与官方流量一致)
| 命令 | 字节 | 响应 |
|---|---|---|
| 0x6bb6b66b | 4 | 0x5bb5b55b 60B(pid@0, w=160@0x10, h=120@0x14, fps=15@0x18) |
| 0x6bb6b66c | 4 | 0x5bb5b55c 60B(另一块,勿当 info) |
| 0x6bb6b66f | 4 | 0x5bb5b55e 20B |
| 0x6bb6b672 FFC | **8**(magic+param) | 0x5bb5b55f 4B |
| 0x6bb6b673 START | 4 | 0x5bb5b55f |
| 0x6bb6b674 STOP | 4 | 0x5bb5b55f(需先 clear_halt 0x03/0x82) |
启动序列:`66b → 66c → 66f → FFC(0)×2 → 300ms → START → 700ms`
### 2.3 FFC 语义与时序(已精确解码,稳定方案已验证)
- FFC(1):流切换为 **type=0**(温度数据,手掌 +1200 counts)
- FFC(0):流切换为 type=1(raw)
- 官方 cadence:FFC(0)→ 恰 9 帧 → FFC(1)→ 间隔 34~2680 帧 → 循环
- **已验证稳定方案**:period=400 帧 + gap=9 帧(FFC 必须在完整帧后发)
- 实测:1400+ type=0 帧 / 15.1fps / 0 停帧(多次)
### 2.4 帧格式(重要:像素偏移!)
```
28B 头: 0x1bb1b11b | cnt | len=0x9600(38400) | type | shutter | 保留
38400B: 像素 u16 LE
28B 尾: 0x1bb1b11c @ offset 38400 | 统计值(如 0x7839)
```
**关键:第二次 bulk read 返回 38428 字节 = 38400 像素(从偏移 0 开始)+ 28 尾。
demo 曾用偏移 +28 读像素是错的(错位 14 u16)。** 正确:像素从 data[0] 开始。
## 3. 实机数据(决定性事实)
### 3.1 counts 数据特征
- 背景 counts 均值 ~11224(会话间 11224~13431 漂移)
- **巨大固定 mura**:空间 std 3560,行跨度 15000+
(row0 ~17318 → row120 ~7954,row119 回升;两个会话剖面几乎相同)
- row1 会话内恒定(+5000),漂移 62~1535(FFC 状态不同)
- **每像素时域噪声 std ~248 counts**(99.9% 像素波动,非坏点)
- 坏点:min=0/max=45340 恒定出现的像素(少数)
### 3.2 NUC 验证(关键结论)
- `live - (ref - mean(ref))`:mura std 3560 → **29**,行跨度 15000 → 23~49
- NUC 后温度(官方 T2E shift=6):**24.9°C 均匀(std 0.19)**,与官方探针
26.7~27.4°C 接近
- **结论:NUC 平场校正是消除 mura 的正确方法**
## 4. 官方 SDK 逆向成果(本会话最大收获)
### 4.1 可活体驱动的官方管线
Windows 官方 `CoreSDKLib.dll`(490KB, 65 导出,精确 RVA 已解析)+
`ThermalSDK.dll`(90KB, 19 导出)。**ThermalSDK 高层 API 可直接驱动设备**:
```c
// 已验证可用的调用(tsdk_debug/tsdk_capture3/tsdk_gray/tsdk_palette):
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
LoadLibraryA("ThermalSDK.dll"); // 依赖同目录 CoreSDKLib.dll 等
SetUnitMode(0); SetTempBoundary(30,44,37); SetNewIRFrameDelegate(onIR);
Start(); // 内部:NewChannel(0) -> EnumCameras -> LinkCamera(chan,pid) -> StartProcessImage
// 回调:onIR(buf, 320, 240, 3) 拿到官方渲染 RGB 帧(2x 放大)
// ReadTemperatureAtPoint(x, y, res) -> 16B 结构, tempRaw=double @16, tempArm=double @24
```
### 4.2 已提取的官方数据(analysis/ 目录)
| 数据 | 位置 | 说明 |
|---|---|---|
| 官方 T2E 表(646 int32) | `analysis/official_t2e_table.txt` | 从 .rdata 0x5cde0 提取,单调递增 51→4200129 |
| 官方渲染帧 ×8 | `build-artifacts/official_ir_00..07.rgb` | 320x240 RGB,品红系 |
| 官方灰度图 | `build-artifacts/official_gray.bin` | 160x120,93% 像素=0,顶部14行坏点带 |
| 官方调色板(错误读取) | `build-artifacts/official_palette_live.bin` | Jet 色系 BGR,疑似偏移错误,勿用 |
| 官方调色板(帧反推 115色) | `analysis/temp/official_palette_full.npy` | 品红系,可信 |
| 官方探针温度 | 记录在 handoff | 背景 26.7~27.4°C |
| 官方 T2E 表(已转C头) | `csdk/src/mag160c_official_t2e.h` | 646 项 |
| 官方调色板(已转C头,插值) | `csdk/src/mag160c_official_palette.h` | 256 项,品红系(可能需修正) |
### 4.3 官方温度公式(0x180016290 反汇编,权威)
```
x = counts << (7 - shift) ; shift 参数运行时确定(本机验证=6)
i = 二分查找 T2E[i] <= x < T2E[i+1]
diff = x - T2E[i]
slope[i] = (0x1000000 + (T2E[i+1]-T2E[i])/2) / (T2E[i+1]-T2E[i])
temp_mc = (slope[i]*diff >> 12) + (i<<12) - 0x249f0 ; 毫°C
```
验证:counts 11224 → shift=6 → 24.82°C;12500 → 31.83°C;13000 → 34.48°C
NUC 后背景 → 24.9°C(均匀)。
### 4.4 官方渲染机制(部分逆向)
- `MAG_GetOutputBMPdataRGB24(chan, buf, size, order)`:8-bit 灰度 `[dev+0xb00]`
→ 调色板查表(256×4 BGR)→ 输出 320x240 RGB
- `MAG_GetOutputBMPdata(chan, w, buf)`:返回灰度缓冲指针 `dev+0xaf0`(19200B)
- **未逆向:counts → 灰度(dev+0xaf0)的生成逻辑**(AGC/温度窗口在哪)
- **未确认:调色板确切偏移**(dev+0xb00 vs graybuf+0x10 读到的数据不符)
### 4.5 官方显示效果(用户标准)
- 官方 app 画面:背景**品红(145,0,145)**,热物体紫红渐变,顶部坏点带白色
- 官方灰度:93% 像素=0(说明显示窗口下限 > 背景温度,背景灰度≈0?
但渲染背景是品红 → 矛盾,需重新验证灰度与渲染的对应!)
- **注意:official_gray.bin 与 official_ir_03.rgb 是不同会话抓的,
不能直接配对!必须同一时刻抓灰度+RGB+温度**
## 5. 显示管线演进与失败教训(重要)
| 版本 | 方案 | 结果 | 教训 |
|---|---|---|---|
| v1 原始 | 直接显示 counts + percentile | 乱图/坏点 | raw 不可直接显示 |
| v2 | EMA 参考 + diff | 鬼影 | EMA 吸收静态场景 |
| v3 | 全局漂移 + 坏点拓扑填充 | 鬼影仍在 | 参考吸收小物体 |
| v4 | MOG 冻结 + 自愈 | 乱图叠加 | 参考采集被 quiet-gate 卡住 |
| v5 | MOG 冻结 + FFC 重采集 | 画面更混乱 | 参考从未建立(NUC 未激活) |
| v6 | NUC 平场 + 自适应窗口 | 平滑但对比度差、颜色不对 | 找到了 NUC,但窗口/调色板错 |
| v7 | NUC + T2E + 官方调色板 | 品红背景与官方一致但**什么都分辨不出** | 窗口/灰度映射仍不对,对比度丢失 |
**用户最新反馈(2026-08-11)**:v7 画面"什么都分辨不出来"(对比度丢失,
物体与背景无法区分),且"还是有鬼影"。结论:即使色系对了,灰度映射/
窗口机制仍然错误,参考帧方案仍有残留问题。**必须回到官方完整管线**。
### 5.1 核心失败教训
1. **不能边猜边试**:v1-v7 都是"猜显示算法→实机看效果→再猜"。
用户明确要求:**先把官方"读取→显示"全流程逆向完毕,再改代码**。
2. **最终屏幕画面必须与官方一致**:用户以此为标准,不是"看起来平滑"。
3. **参考帧/NUC 的副作用(鬼影)未解决**:NUC 参考冻结后无更新,
温度漂移会导致残留。
4. **官方灰度图与渲染帧未配对**:不同会话数据不可用于像素级对比。
5. **调色板偏移未确认**:graybuf+0x10 读到的 Jet 色系与渲染帧品红系矛盾,
说明读错位置或存在多个调色板。
## 6. 当前代码状态
- `csdk/tools/mag160c_demo2.c`:最新版(官方管线尝试,v7)
- 显示:counts → NUC → T2E temp(shift=6)→ 自适应窗口(背景±2°C)→
品红调色板(插值版)
- 已知问题:对比度差(分辨不出物体)、可能有鬼影
- `csdk/tools/mag160c_ffc_test.c` / `csdk_stream_test.exe`:FFC 稳定性验证(已 PASS)
- `csdk/tools/tsdk_*.c`:官方 ThermalSDK 活体 harness(tsdk_debug 最稳定)
- `csdk/src/mag160c_display.c`:纯 C 显示模块(NUC/坏点/AGC/FFC 调度)
- `csdk/src/mag160c_official_t2e.h` / `mag160c_official_palette.h`:官方表
- `csdk/tests/test_display.c`:32/32 通过
- 证据图:`analysis/demo2_v2..v7_*.png`
## 7. 下一轮方法论(必须遵守)
### 7.1 总原则
**先把官方"读取→显示"全流程逆向完毕并像素级对比一致,再动 demo 代码。**
### 7.2 具体步骤(建议顺序)
1. **同帧抓取**:写一个 harness(基于 tsdk_debug),在**同一帧**内抓:
- 官方灰度(MAG_GetOutputBMPdata → dev+0xaf0)
- 官方渲染 RGB24(MAG_GetOutputBMPdataRGB24)
- 官方温度(MAG_GetTemperatureData 或 ReadTemperatureAtPoint 多点多帧)
- 官方调色板(需先确认正确偏移)
存成配对文件(如 official_pair_%03d.gray/.rgb/.bin)。
2. **逆向 counts→灰度 生成代码**:在 CoreSDKLib.dll 找写 dev+0xaf0 的
函数(帧处理线程),确认:AGC 算法(线性?直方图?)、温度窗口、
是否含 NUC/坏点。这是"读取→显示"缺失的核心环节。
3. **确认调色板**:用同帧灰度+RGB 精确提取 256 项调色板
(gray→color 逐项),替换现在的插值版。
4. **像素级对比**:用我们的 counts + 逆向的完整管线重建画面,与
官方同帧渲染逐像素对比(相关系数、色差),迭代到一致。
5. **温度标定**:用官方温度数据校准 shift 和 offset,替换 147 counts/C。
6. **鬼影**:参考帧方案在官方管线确定后重新设计(官方是否有参考帧?
还是纯逐帧处理?)。
### 7.3 工具与入口
- 设备空闲检查:`Get-PnpDevice | ? InstanceId -match 833C`
- 官方 harness:`build-artifacts\tsdk_debug.exe`(最稳定,先跑这个验证设备)
- 我们的帧 dump:`build-artifacts\mag160c_frame_dump.exe <n> <dir>`
- 分析脚本:`analysis/temp/`(Python,numpy/scipy/PIL 可用)
- 官方 DLL 反汇编:objdump -d -Mintel(RVA 见 `analysis/temp/exports2.py`)
### 7.4 环境
- Windows 11 + MinGW gcc 14.2(`C:\mingw64\bin`),无 cmake
- libusb:`csdk/third_party/libusb/win64/`
- 官方 SDK:`IR_Camera_SDK-1.0.1/windows/windows/app/`
- Python 3.10 + numpy/scipy/PIL
+361
View File
@@ -0,0 +1,361 @@
# MAG160C 热像仪渲染项目交接 - 2026-08-11
项目目录: `C:\Project\MAG160C`
目标: 修复 `csdk\tools\mag160c_demo3.c` 的移动物体鬼影,同时不要破坏当前已经修好的画面问题。
## 1. 当前结论先说
- 黑洞问题已经基本消失。
- 固定遮蔽问题已经消失。
- 最高温点显示黑色的问题已经修掉。
- 当前主要剩余问题: 移动物体会留下鬼影,尤其是“小温差鬼影”擦除很慢。
- 官方 SDK 自己也存在类似“旧位置偏暗/偏黑”的残影,不是 demo3 独有问题。
- 现在最需要做的是: 在不破坏现有稳定画面的前提下,只针对“目标离开后的小残影”加速擦除。
## 2. 设备与构建
- 设备: MAG160C, 160x120, USB, VID `0x833C`
- 官方 SDK: `CoreSDKLib.dll`
- 当前主程序: `C:\Project\MAG160C\csdk\tools\mag160c_demo3.c`
- 输出程序: `C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe`
编译命令:
```powershell
gcc -O2 -w -DMAG160C_STATIC -I"C:\Project\MAG160C\csdk\third_party\libusb\win64" -I"C:\Project\MAG160C\csdk\include" -I"C:\Project\MAG160C\csdk\src" -o "C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe" "C:\Project\MAG160C\csdk\tools\mag160c_demo3.c" "C:\Project\MAG160C\csdk\src\mag160c_display.c" "C:\Project\MAG160C\csdk\src\mag160c_error.c" "C:\Project\MAG160C\csdk\third_party\libusb\win64\libusb-1.0.x64.a" -lgdi32 -luser32
```
重启命令:
```powershell
Stop-Process -Name mag160c_demo3 -Force -ErrorAction SilentlyContinue
Start-Process "C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe" -WorkingDirectory "C:\Project\MAG160C\build-artifacts"
```
日志与抓图:
- `demo3_diag.txt`
- `demo3_auto_*.bmp`
官方抓帧:
```powershell
C:\Project\MAG160C\build-artifacts\tsdk_pair2.exe C:\Project\MAG160C\analysis\pairs_move 12 6000
```
注意:
-`tsdk_pair2.exe` 前要先停 `demo3`
- 如果报 `no channel`,通常要重插 USB
- 这个工具默认要给官方 SDK 几秒初始化时间,`6000ms` 延时是成功过的
## 3. 官方渲染管线逆向结论
已验证主链路:
```text
USB帧(type=0)
-> 平滑器 0x41f10 (模式1: 首帧 memcpy + EMA)
-> f20 (dev+0x41f20)
-> NUC查表 0x180017200
-> 输出到 dev+0x270
-> 窗口 hi/lo (dev+0x4202c / 0x42030)
-> gray (dev+0x220, 320x240)
-> palette 256
-> RGB
```
NUC 查表 `0x180017200` 已确认:
- `d2 = (f20 - ref) >> 1`
- 段阈值在 `dev+0x41858`
- gain/off 表在 `dev+0x41870`
- `shift = 12`
- 输出 clamp 到 `[0, 65535]`
参考帧 `ref`:
- 来自平滑器 `0x41ee0`
- 启动早期采集几帧后冻结
- FFC 后只做全局偏移,不重新采集整帧 ref
- `ref``live/f20` 同源 mura,能很好抵消固定纹理
FFC:
- 周期约 `1800` 帧,也就是约 `120s`
- 或者传感器温差变化超过阈值时触发
- `FFC(0)` 后约 `9` 帧再 `FFC(1)`
- `type=1` 期间官方会冻结画面
关键逆向修正:
- 原来以为 `0x180017330` 是整帧 3x3 平滑,这是错的
- 现在确认它更像“盲元/坏点补偿”,不是全帧模糊
- 所以 demo3 里已经去掉“整帧 3x3 blur”这条错误路径
## 4. 已经修好的问题
### 4.1 颗粒重
- 原因: NUC 斜率过大
- 修复: `NUC_GAIN=19`
### 4.2 FFC 后颜色跳变
- 原因: FFC(1) 后重采集 ref,把场景吸进参考
- 修复: `REF_REINIT_AFTER_FFC=0`
### 4.3 物体破碎 / 椒盐
- 原因: 固定窗口过窄
- 修复: 改为自适应窗口
### 4.4 蒙层 / mura 感
- 原因: 常数 ref 或错误参考导致 mura 直接显示
- 修复: 启动阶段采集每像素平场 `g_ref_mura`
### 4.5 中上固定遮蔽
- 原因: 动态 badmap / scene-derived bad map 会把错误结构钉在屏幕坐标
- 修复: `BADMAP_ENABLE 0`
### 4.6 右下炫光
- 原因: 误把某层处理当成全帧平滑
- 修复: 去掉整帧 3x3 blur 路径
### 4.7 最高温点变黑
- 原因: 调色或极值像素处理不稳
- 修复:
- 加了 `idx >= hi -> 1023` 饱和保护
- 保留保守的 `correct_zero_pixels()`
## 5. 当前稳定基线
请把下面这些当作“目前相对稳定、不要随便一起推翻”的基线:
- `CONSTANT_REF=1`
- `SBNUC_ENABLE=1`
- `BADMAP_ENABLE=0`
- 不启用显示层 `deghost_nuc()`
- 保留 `correct_zero_pixels(g_live)`
- 不使用动态 badmap
- 不重新采集 FFC 后参考
- `g_reference``g_ref_mura + shift` 生成
当前 `SBNUC` 的核心逻辑仍在 `CONSTANT_REF && g_ref_mura_ready` 分支里。
## 6. 当前主要问题
用户最新稳定反馈是:
- 画面别的问题基本都行
- 主要剩下鬼影
- 大温差目标离开后擦得还可以
- 小温差目标离开后,残影很明显,而且消失慢
也就是说问题已经不是“黑洞”了,而是“低对比度残影消退太慢”。
## 7. 已尝试过的鬼影方案与结果
以下这些路线都试过了,后续模型不要再盲目重复:
### 7.1 直接全局加速 SBNUC
试过:
- 去掉 `df` 条件
- 放宽 `dd` 阈值
- `alpha``1/64` 提到 `1/16`
结果:
- 对黑洞有帮助
- 但对小温差鬼影改善有限
- 太激进时容易把画面别的东西带坏
### 7.2 用 `dd > 120` 冻结热物体,负残差快速吸收
当前稳定版本里保留了这条思路:
- `dd > 120` 认为是热目标,冻结
- `dd < -120` 用更快步长
结果:
- 黑洞基本缓解
- 但小温差鬼影仍然慢
### 7.3 显示层去鬼影
试过:
- 最近热区 mask
- motion history
- 在显示层替换 ghost 区像素
结果:
- 效果不明显,或者直接把显示搞坏
- 用户明确反馈“显示不对了,有地方看不见了”
结论:
- 不建议再从显示层硬补丁下手
### 7.4 更复杂的热区/静止检测
试过:
- `g_sb_still`
- `g_hot_hist`
- `g_motion_hist`
- `g_disp_hot`
结果:
- 大部分复杂掩码方案收益低,且很容易引入副作用
## 8. 最后一次实际修改
最后一次修改是在当前 `SBNUC` 块上,加入了“最近热像素释放加速”:
- 新增常量:
- `SB_HOT_T 120`
- `SB_HOT_SEED 48`
- `SB_HOT_HOLD 24`
- 逻辑:
- `dd > SB_HOT_SEED` 时给该像素一个短期热记忆
- 当像素刚从热态退出,且 `dd < 0`
- 临时把更新步长提高到 `denom = 1``2`
- 目标是让“小温差尾巴”比以前更快擦掉
改动位置:
- [mag160c_demo3.c](/abs/path/C:/Project/MAG160C/csdk/tools/mag160c_demo3.c)
这一版已经成功编译并启动过,但还没有得到“明显修好鬼影”的确认。
## 9. 官方抓帧证据
已成功抓到:
- 目录: `C:\Project\MAG160C\analysis\pairs_move`
- 预览图: `C:\Project\MAG160C\analysis\pairs_move\preview`
- 统计表: `C:\Project\MAG160C\analysis\pairs_move\preview\summary.tsv`
已经确认的事实:
- 官方 `f20-ref` 的整帧残差均值并不在 `0` 附近,而是大约 `-5000`
- 官方自身也能看到“人物旧位置偏暗”的形态
- 说明鬼影不是 demo3 独有 bug,而是官方这类冻结参考体系本来就会带来的现象
几个关键数值:
- `pair_000``pair_011``d_mean` 大约在 `-5012``-5249`
- `d_std` 大约在 `735``993`
- 官方 `gray` 帧间变化大多不高,但在 FFC 附近会跳
重要提醒:
- `tsdk_pair2.c``.thr/.gain` 有重复写文件的问题
- 前面本来先按指针保存了真实表,后面又被 `dev+0x41848 / 0x41870` 覆盖
- 所以 `pairs_move/*.gain` 不能直接当纯 gain/off 表来解析
## 10. 当前源文件里还留着但未启用的东西
`mag160c_demo3.c` 里还有一些历史试验残留,很多已经不在主路径使用:
- `g_sb_prev_d`
- `g_sb_still`
- `g_hot_hist`
- `g_disp_hot`
- `g_disp_hot2`
- `g_motion_hist`
- `g_disp_prev_live`
- `g_disp_prev_ready`
- `g_disp_cur_hot`
- `g_disp_ghost`
- `g_disp_repl`
- `deghost_nuc()`
其中:
- `g_hot_hist` 现在又被重新用于“最近热像素释放加速”
- `deghost_nuc()` 仍然存在,但调用被注释掉了
## 11. 建议下一步
优先顺序建议如下:
1. 不要再做显示层补丁。
2. 继续只在 `SBNUC`/参考更新层做改动。
3. 重点针对“刚离开热目标后的低对比度负残差”做更稳的状态机。
4. 如果参数法还是没明显改善,直接上 Hardie 风格时域方差门控。
更具体地说:
### 方案 A: 做“退出热目标后的短时释放状态机”
比当前 `g_hot_hist` 更严格一点:
- 进入热目标态: `dd > hot_enter`
- 退出热目标态: 从热态回落到 `dd <= hot_exit`
- 退出后 `N` 帧内:
-`dd < 0`,快速拉 `g_ref_mura -> g_live`
- 若再次变热,立刻取消释放
这比单纯看当前 `dd` 更稳,因为它显式编码了“曾经热过,现在走了”。
### 方案 B: Hardie / Kalman 风格时域方差门控
这是最值得上的正路。
对每个像素维护:
- `d = live - ref`
- 短窗均值
- 短窗方差
规则:
- 方差小且接近背景偏移 -> 当作背景,允许吸收
- 方差大 -> 当作运动目标,冻结
这类方法比单阈值 `dd` 更适合处理“小温差但持续可见的鬼影”。
## 12. 不要轻易改的点
后续模型请避免同时改这些东西,否则很难定位:
- `BADMAP_ENABLE`
- `correct_zero_pixels()`
- FFC 重采样策略
- 调色和窗口逻辑
- 显示层 `deghost_nuc()`
- 已验证的官方 LUT / palette 路径
除非有充分证据,否则这次只改 `SBNUC` 块。
## 13. 关键文件
- 主程序: [mag160c_demo3.c](/abs/path/C:/Project/MAG160C/csdk/tools/mag160c_demo3.c)
- 官方抓帧工具: [tsdk_pair2.c](/abs/path/C:/Project/MAG160C/csdk/tools/tsdk_pair2.c)
- 当前交接: [handoff_20260811_full.md](/abs/path/C:/Project/MAG160C/analysis/handoff_20260811_full.md)
- 历史进展: [progress_reverse.md](/abs/path/C:/Project/MAG160C/analysis/progress_reverse.md)
- 官方抓帧预览表: [summary.tsv](/abs/path/C:/Project/MAG160C/analysis/pairs_move/preview/summary.tsv)
## 14. 给下一个模型的直接任务
首选任务:
- 先读本文件
- 再读 `C:\Project\MAG160C\csdk\tools\mag160c_demo3.c`
- 只改 `SBNUC`
- 目标是明显改善“小温差鬼影擦除慢”
- 不要碰别的已稳定逻辑
- 改完后编译并启动 demo 给用户实测
+138
View File
@@ -0,0 +1,138 @@
# MAG160C 逆向交接文档 v3(2026-08-12)
> **本文件是最新、最完整上下文快照。** 上一版:handoff_20260811.md。
> 本轮(2026-08-11 晚)完成**官方"读取→显示"完整管线逆向,全部实机
> 像素级验证(100.0% 精确),并交付 demo3(官方管线版)**。
## 1. 本轮成果(概览)
1. **同帧配对 harness**(tsdk_pair2.exe):同一帧抓官方 gray + RGB24 +
raw counts + palette + per-pixel 温度(m°C)+ 窗口/LUT 字段。
2. **完整显示管线逆向完毕**(Windows CoreSDKLib.dll + 实机验证):
```
USB counts → NUC: nuc[i] = clamp((counts[i]-ref[i])*3 + comp, 0, 65535)
→ 统计: mean/min/max(每帧)
→ 窗口: lo = min(max, mean-312), hi = max(min, mean+312) (X=624)
→ 灰度: idx = (nuc[i]-lo) * (0xFFC00000/(hi-lo)) >> 22 (u32 截断!)
gray = LUT1024[dev+0x4284c][idx] (非线性对比度曲线)
→ 颜色: palette[dev+0xb18][gray] (256×4 BGR,含 alpha=0)
→ 2x bilinear 放大 → 320x240 输出
```
3. **像素级验证**:raw→gray 重建 = **100.0% 精确**(与官方同帧 gray
逐像素一致,mean|diff|=0.000);palette 渲染 vs 官方 RGB24 =
**100.0% 精确**。
4. **温度公式**:temp_mC = T2E[3*raw - C],C≈5797(5784-5812 会话
漂移)。12 帧验证 mean|err| = 1.3 mC。
5. **demo3**(mag160c_demo3.c,已编译运行):完整官方管线 + 修正了
**像素偏移 bug**(旧代码 +28 是错的,正确从 data[0] 开始,探针
验证:read1=28B 头,read2=38400 像素+28B 尾)。
6. **鬼影结论**:官方 NUC 参考帧只在 FFC 时更新(冻结),无时域滤波
→ 无鬼影。demo3 沿用此机制(FFC 后重采集参考帧)。
## 2. 官方显示管线(权威,全部实机验证)
### 2.1 关键内存布局(Windows CoreSDKLib.dll 运行时对象 dev)
| 偏移 | 内容 | 说明 |
|---|---|---|
| dev+0x220 | 灰度缓冲指针 | 8-bit,显示尺寸(此处 320x240) |
| dev+0x270 | NUC 后帧指针 | u16,160x120,NUC 输出 |
| dev+0x2a0 | (另一缓冲,MapTemperature 输出目标之一) | |
| dev+0xaf0 | 显示结构 | +4 w,+8 h(此处 320x240) |
| dev+0xb18 | 调色板 | 256×4 BGRα,与渲染 100% 一致 |
| dev+0xf18 | 色彩条 | 128 级灰度渐变 RGBA |
| dev+0x42010 | 帧 min | u32(0x1800108a0 统计输出) |
| dev+0x42014 | 帧 max | u32 |
| dev+0x42018 | 帧 mean | u32 |
| dev+0x4201c | 帧 std | u32 |
| dev+0x4202c | 窗口 hi | u32(实测 ~10088-10116) |
| dev+0x42030 | 窗口 lo | u32(实测 ~9464-9492) |
| dev+0x4204c | 直方图 | 256×u32(灰度 idx>>2) |
| dev+0x4284c | **LUT1024** | 1024 字节,非线性对比度曲线 |
| dev+0x41ef0 | NUC 参考帧指针 | dev+0x41f0c=是否有参考帧 |
| dev+0x47938 | 像素数 | 19200 |
| dev+0x47948/0x4794c | comp 参数 | comp=(0xc350-offset)>>shift |
| [0x18006ea60] | NUC 增益(全局) | 会话自适应,实测=3,饱和时 -1 |
### 2.2 帧处理流程(0x180017040 = NUC+增益)
```
nuc[i] = clamp((frame[i] - ref[i]) * gain + comp, 0, 0xffff)
frame = USB 原始帧(参数),ref = dev+0x41ef0 参考帧,
gain = [0x18006ea60](饱和像素 >0x100 时减 1),
comp = (0xc350 - dev+0x47948) >> dev+0x4794c (clamp>=0)
输出写 dev+0x270 指向的缓冲
```
之后 0x180017200 做 3x3 加权滤波(系数 1/3,1/6,1/7 等,写回 dev+0x270)。
### 2.3 灰度生成(0x180010e85 = Windows MapTemperature)
```
S = 0xFFC00000 / (hi - lo) ; u32 除法
idx = (nuc[i] - lo) * S >> 22 ; u32 截断乘法!
gray[i] = LUT1024[dev+0x4284c + idx]
直方图[dev+0x4204c + (idx>>2)]++
```
### 2.4 窗口自适应(0x1800109c0/0x180010b0d)
```
统计(0x1800108a0):min→0x42010, max→0x42014, mean→0x42018, std→0x4201c
half = X/2, X 来自积分时间×增益(0x1800108a0 内部,会话恒定,实测 624)
lo = min(max, mean - 312); hi = max(min, mean + 312)
clamp lo>=0, hi<=0xffff
```
实测窗口中心 == 帧 mean(9804.8→9804 ✓),X=624 恒定。
### 2.5 温度(实测公式)
```
temp_mC = T2E[3*raw - C], C≈5797(5784-5812 逐帧漂移)
T2E:646 项 int32 表(analysis/official_t2e_table.txt,已转 C 头)
slope = (0x1000000 + span/2) / span; temp = slope*diff>>12 + (i<<12) - 0x249f0
验证:12 帧 × 19200 像素 mean|err| = 1.3 mC
```
## 3. 已导出资源(csdk/src/)
- `mag160c_official_palette256.h`:256×4 BGR(dev+0xb18,12 帧一致)
- `mag160c_official_lut1024.h`:1024 字节(dev+0x4284c,非线性曲线)
- `mag160c_official_t2e.h`:646 项(已有)
- 验证脚本:`analysis/temp/`(Python)与本次新增:
- `tsdk_pair2.c`(harness)→ `build-artifacts/tsdk_pair2.exe`
- `mag160c_demo3.c` → `build-artifacts/mag160c_demo3.exe`
- 配对数据:`analysis/pairs2/`(12 帧 × gray/rgb/raw/pal/t32),
`analysis/pairs_win/`(窗口/LUT/直方图字段),
`analysis/pairs_ffc/`(FFC 前后)
## 4. 帧解码修正(重要 bug)
**像素从第二次 bulk read 的 data[0] 开始**(探针 mag160c_layout_probe2
实测:read1 恰 28B 头,read2 = 38400 像素 + 28B 尾,尾魔数 0x1bb1b11c
@ offset 38400)。
- `mag160c_demo2.c` 的 `decode_live`(+28)是错的 → demo3 已改为 +0。
- `mag160c_frame_dump.c` 的 `fwrite(frame+28)` 也是错的(错位 14 u16),
统计均值看似正常是因为行 mura 平滑,但像素错位。
- 注意:这与官方 SDK 内部不同(官方 buf+0x1c 起拷像素,因为官方
的帧缓冲含 28B 头)。
## 5. 温度/窗口剩余问题(未完全确定)
1. **LUT 高端/低端形态**:场景只有 26-28°C,只覆盖 LUT 部分 idx。
LUT 已直接从内存取全 1024 项,无需外推 ✓(这个已经解决了)。
2. **X=624 的来源**:会话恒定,但可能随积分时间/增益模式变化。
已从 dev+0x4202c/0x42030 实测;demo3 硬编码 312 half。
3. **comp 绝对校准**:官方 comp=(0xc350-offset)>>shift 自适应;
demo3 简化为 ref 捕获时 comp=9804(官方 NUC 中心水平)。
4. **ref 捕获时机**:官方在 FFC 后捕获;demo3 沿用"FFC(1)后 30 帧
中值"方案(已验证 mura std 3560→29)。
## 6. 下一步建议
1. 实机对比 demo3 画面与官方 app(用户操作,目视确认)。
2. 若需要长期稳定性:把 comp 校准改为跟随窗口中心
(mean±312 自动居中,comp 只影响绝对温度显示,画面自适应)。
3. Linux 移植:管线纯 C 已在 csdk/src(display/t2e/palette/lut),
把 demo3 的 render 逻辑移植到 mag160c_display.c 官方版 API。
4. 热源实验(可选):宽温度范围数据可验证 LUT 外推与窗口 clamp
行为(idx 溢出回绕)。
## 7. 环境与工具
- 设备检测:`Get-PnpDevice | ? InstanceId -match 833C`
- 官方 harness:`build-artifacts\tsdk_pair2.exe <dir> <npairs> <delay_ms>`
(可选第 4-6 参数 SetTempBoundary)
- 官方 SDK:`IR_Camera_SDK-1.0.1\windows\windows\app\`(ThermalSDK/
CoreSDKLib/CameraSDK + libusb0.dll)
- 反汇编:objdump -d -Mintel;RVA 表见各 disasm_*.txt
- Python 3.10 + numpy/scipy/PIL
+72
View File
@@ -0,0 +1,72 @@
# MAG160C 逆向交接文档 v4(2026-08-12 深夜)
> **最新快照。** 前版:handoff_20260812.md(白天)。
> 本轮完成:官方 NUC/参考帧机制完整逆向 + demo3 修复(颗粒感/FFC 变色)。
## 1. 本轮成果
1. **完整逆向官方帧处理链(Windows CoreSDKLib.dll)**:
```
USB帧 → 帧计数分支(0x18000a0xx):
帧1-5: 0x18000c760(校正表生成, 无显示)
帧6-9: 0x18000c620(推帧0x41ee0平滑器→生成ref) + 0x18000c760
帧==9: 发送FFC(0x6bb6b672,1)
帧10+: 0x18000ca30(主处理)
→ 0x18000c950: 0x41f10平滑器(模式1: 首帧拷贝+EMA1/2) → f20缓冲(0x41f20)
→ 0x180017200(NUC查表: d=(f20-ref)>>1, 阈值表*(0x41858), 增益表*(0x41870),
out=gain×d>>12+off, shift=0x11c=12) → dev+0x270
→ 0x180017330(3x3加权, 表驱动) → dev+0x270
→ 0x18001e920(0x41f40平滑) → 0x180010780(温度) → 0x1800109c0(窗口) → 放大
```
2. **参考帧机制(决定性)**:
- ref(0x41ef0) = 平滑器0x41ee0(模式4)输出 = 启动时~22帧累加>>2(≈5.4×帧均值)
- 只在启动(帧6-9)与FFC时更新;**正常流中不更新**(帧10+不推帧0x41ee0)
- FFC后ref仅全局偏移变化(-145/-214),空间结构保持
- **官方ref含启动场景,不吸收后续场景 → 物体显示正常、无鬼影**
3. **表机制**:
- 0x41848/0x41850 = 端点表1, 0x41858 = 插值输出(阈值表, 3项s16全局)
- 0x41860/0x41868 = 端点表2, 0x41870 = 插值输出(增益表 nsegs×19200×(gain,off))
- 0x180016dd0/0x180016f10 = 分段线性插值(按dev+0x54温度在断点0x41564间插值)
- 0x180016ae0 = 端点表初始化(帧0, 从配置0x417xx加载)
- nsegs=0x41554=3, 端点索引0x41840, 断点0x41564/0x41568
4. **demo3 修复(用户反馈: 颗粒感+FFC变色)**:
- **斜率 3 → 0.41**(官方量级: 物体100 counts → nuc 41; 之前3倍放大27倍噪声)
- **FFC后不重采集ref**(REF_REINIT_AFTER_FFC=0, 只rebase全局偏移) —
重采集会吸收场景使物体消失/FFC后画面错
- 3x3双向滤波(live与ref同滤波, mura匹配)
- 像素偏移+0修正, 启动延迟采集, 采集期间冻结显示
- **实测: temporal gray std 54→22, FFC前后gray均值恒定(74.5), 无颜色跳变**
## 2. 关键实测数据(pairs_nuc2)
| 量 | 值 |
|---|---|
| nuc(0x270) | mean~9706 std~41 时域噪声13.7 |
| f20(0x41f20) | ~10855±3639(≈USB帧, 含mura) |
| ref(0x41ef0) | ~14654±3656(=f20+3875, mura同源corr0.998) |
| d2=(f20-ref)>>1 | std~114, 与nuc相关仅0.04(坐标/语义错位, 未完全解密) |
| 增益表seg1 | gain均值3352(≈0.82×4096), off≈9660(≈nuc) |
| 平滑器模式 | 0x41fd8=4(0x41ee0), 0x41fdc=1(0x41f10), 0x41fe0=1, 0x41fe4=5 |
| 帧流参数 | 2d78=4 2d88=5 2d8c=4 2d90=1800 2d94=250 |
## 3. demo3 当前状态
- 编译: build-artifacts/mag160c_demo3.exe
- 管线: counts →(3x3+坏点)→ nuc=(live-ref)×0.41+9804 → 窗口[mean±312] →
LUT1024 → palette256 → 2x显示
- ref: 启动nread>=130后45帧中值采集; FFC后只rebase全局偏移(不重采集)
- 诊断: demo3_diag.txt(每帧 ref/live/nuc/窗口统计), demo3_auto_*.bmp(自动截图)
## 4. 未完成/待验证
- [ ] 官方nuc与(f20-ref)的精确映射(相关0.04, 表机制复杂未完全解码)
- [ ] demo3斜率0.41的实机画面确认(对比度/颜色方向)
- [ ] 0x180016ae0端点表初始化(0x417xx配置/DDT加载)
- [ ] FFC状态机(0x73bf4, 帧号75/130)
- [ ] ThermalSDK.dll完整逆向
- [ ] 启动黑屏~9秒可优化(更早采集或显示预热画面)
- [ ] 热源场景验证(确认颜色方向: 热=白/红)
## 5. 环境
- 设备: Get-PnpDevice | ? InstanceId -match 833C
- harness: build-artifacts/tsdk_pair2.exe <dir> <npairs> <delay> [边界参数]
- 官方SDK: IR_Camera_SDK-1.0.1/windows/windows/app/
- 反汇编: analysis/disasm/coresdk_windows_disasm.txt(193k行, UTF-16)
- Python: numpy/scipy/PIL
+163
View File
@@ -0,0 +1,163 @@
# MAG160C 热像仪渲染管线交接文档 v5
> 项目:MAG160C 热像仪(160x120, USB, VID 0x833C)自实现渲染管线 demo3
> 目标:复刻官方 CoreSDKLib.dll 渲染效果
> 状态:颗粒 ✓ / FFC 颜色 ✓ / 蒙层 ✓ / **黑洞·黑拖影 ✗(当前阻塞)**
---
## 1. 已解决问题(历史)
| 问题 | 根因 | 修复 |
|---|---|---|
| 画面颗粒重 | NUC 斜率 3 过大(官方 0.19) | `NUC_GAIN=19`(0.19) |
| FFC 后颜色跳变 | FFC 后重采集 ref 吸收场景 | `REF_REINIT_AFTER_FFC=0`:FFC(1) 后只 rebase 全局偏移 |
| 物体显示破碎/椒盐 | 固定窗口 ±312,物体 nuc 超窗口饱和 | 自适应 P2..P98 百分位窗口(下限 624,官方实测) |
| 画面"蒙一层东西" | 常数 ref 不做平场,mura(空间 std 3560)直接显示 | 启动采集每像素平场(median,运动门控) |
| 黑拖影/黑洞 | ref 冻结 + 物体移动 → 旧位置 d = live-ref < 0 → nuc 低 → 黑 | **未解决**(见 §3) |
---
## 2. 官方逆向成果(已验证,disasm 证据)
### 2.1 渲染数据流
```
USB帧(type=0)
→ 平滑器 0x41f10(模式1: 首帧 memcpy + EMA) → f20 (dev+0x41f20)
→ NUC 查表 0x180017200: d2=(f20-ref)>>1, 段查表, clamp[0,65535]
→ 3x3 加权 0x180017330(写回) → dev+0x270 (raw/NUC输出)
→ 窗口 0x4202c/0x42030 (hi/lo, 动态) → gray (dev+0x220, 2x 升采样 320x240)
→ 调色板 256 色 (pal[0]=黑, pal[255]=白) → RGB
```
### 2.2 NUC 查表(0x180017200, 完整逆向)
```
每像素:
d2 = (f20[i] - ref[i]) >> 1 (sar)
段选择: for(edx=0; edx<nsegs-1; edx++)
if (d2 <= s16 thresh[edx]) break; (thresh 在 dev+0x41858, 值 [3136, 2484, 644])
偏移 = edx * 19200 * 2
nuc = (u16 gain[段][i] * d2) >> (shift=0x11c=12) + u16 off[段][i]
clamp [0, 65535]
→ dev+0x270
```
- gain/off 表:dev+0x41870,nsegs=dev+0x41554=3,每段 19200 像素 × (gain u16, off u16)
- **注意:d2 全负(正常场景,ref≈f20+3800)→ 段0。段1/2 用于大 d(物体),官方对物体响应待验证**
### 2.3 参考帧 ref(dev+0x41ef0)
- 来源:平滑器 0x41ee0(模式4 = 累加平均 acc+=in, out=acc>>2)
- 采集:启动帧 6-9(仅 4 次调用 0x18000c620),之后**冻结**
- FFC 后:只全局偏移变化(-145/-214 counts),空间结构不变(不重采集)
- 与 f20 同源 mura(corr 0.998),精确抵消 mura → d2 空间 std 仅 ~114
### 2.4 FFC 触发(0x18000a1d2, 0x180002f50)
- 条件 A:帧号 ≥ r9 + [0x2d90](0x2d90=1800 帧 ≈ 120s 周期)
- 条件 B:帧号 ≥ r9 + r8(30/100 冷却)且 |sensor_temp - last| > [0x2d94]=250
- 动作:发 FFC(0) (0x6bb6b672, param=0),更新温度记录(0x54/0x58),重置计数(0x49c)
- FFC(1) 在 9 帧后(0x18000c620 链路),type=1 帧期间不渲染(官方冻结画面)
### 2.5 温度
- NUC 输出与温度**完全正相关(corr=1.0, 热=亮)**
- temp_mC = T2E 查表(3×nuc - C, C~5797, 表 0x249f0 645 项)
- 平滑器 0x41f40(模式?)→ 0x180010780(温度)
### 2.6 关键结论
- **官方 ref 冻结 → 官方同样会黑洞/拖影**(移动物体),官方无场景检测
- 官方 FFC 周期 120s,比 demo3 早期 27s 长得多
- 官方窗口动态(无手 span≈624,有手 span≈1381)
---
## 3. 当前阻塞:黑洞/黑拖影
### 3.1 现象
用户敲键盘(手在镜头前小幅移动):手移动后,手旧位置/背景最热处显示为黑色(d 负 → nuc 低 → 黑),拖影明显。
### 3.2 物理机制
```
ref 冻结(平场) → 物体移动 → 旧位置:
d = live(当前背景) - ref(启动背景)
若背景温度漂移/场景变化 → d < 0 → nuc < comp → 黑
```
### 3.3 已试方案与结果
| 方案 | 结果 | 失败原因 |
|---|---|---|
| 固定窗口 ±312 | 物体破碎 | 窗口太窄 |
| FFC 后重采集 ref | 颜色跳变 | 吸收场景 |
| 冻结 median 平场 | 黑洞 | ref 冻结 |
| 常数 ref(每帧全局最低) | 无黑洞但蒙层 | 不平场 |
| 平场+min 平移 | 蒙层✓(修复采集 bug 后) 黑洞回归 | ref 仍冻结 |
| SBNUC 条件更新(df<50 且 dd<80, α=1/64) | 蒙层✓ **黑洞仍存** | 见 3.4 |
### 3.4 SBNUC 失效根因(diag 实测 d=(-467±1479))
1. **df 条件(帧差<50)**:手快速移动时,手经过的像素帧差大 → 永不满足 → 永不吸收 → 拖影持续
2. **dd 阈值 80 太严**:背景 d 空间 std ~458 → 大部分背景像素 |d-med|>80 → 吸收率极低
3. **α=1/64 太慢**:完整吸收需 64 帧(4s),拖影长
4. **背景漂移**:场景整体漂移(实测 d mean 从 +1734 → -467,场景变冷)→ d 负 → 黑
### 3.5 下一步建议(按优先级)
1. **改 SBNUC 参数**:去掉 df 条件(或放宽到 200),dd 阈值 ~120,α=1/16(1s 吸收)
- 理由:只用 dd(物体距离)判断,手经过区域(背景没变,d≈med)→ 立即吸收
2. **每像素时域 d 方差检测**(Hardie Kalman SBNUC, IEEE TIP 1998):
- 维护 d[i] 短窗方差:背景稳定(方差小)→ 吸收;物体经过(方差大)→ 不吸收
3. **双缓冲 ref**:背景 ref(条件更新)+ 物体区域冻结保护
4. 验证指标(demo3_diag.txt):d mean → 0,d 空间 std 应 < 300,物体移动后旧位置 d 快速回 0
5. 若参数方案有效,复刻官方查表(0.19 线性 → 官方分段)对比官方画面
---
## 4. 代码结构与关键参数(demo3.c)
```
常量:
CONSTANT_REF=1 常数/平场+SBNUC 模式
NUC_GAIN=19 (0.19)
FFC_PERIOD=1800 官方周期(120s)
FFC_GAP=9
REF_INIT_N=12 平场采集帧数
REF_REINIT_AFTER_FFC=0 FFC 后不重采集
WIN_HALF=312 窗口下限半宽(P2-P98 自适应)
平场采集:g_ref_phase==1(启动 nread>=60 后),运动门控(帧差>30 或 4% 像素动 → 重置窗口)
SBNUC 块:CONSTANT_REF && g_ref_mura_ready 分支(行 ~885):
med = 直方图中值(live - g_ref_mura)
if (|live-prev|<50 && |(live-mura)-med|<80) mura += (live-mura)/64
平移: ref = mura - min(mura) + min(live)
关键数组:g_ref_mura[NPIX] 平场, g_reference[NPIX] 渲染用
```
### 编译
```
gcc -O2 -w -DMAG160C_STATIC -I"C:\Project\MAG160C\csdk\third_party\libusb\win64" -I"C:\Project\MAG160C\csdk\include" -I"C:\Project\MAG160C\csdk\src" -o "C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe" "C:\Project\MAG160C\csdk\tools\mag160c_demo3.c" "C:\Project\MAG160C\csdk\src\mag160c_display.c" "C:\Project\MAG160C\csdk\src\mag160c_error.c" "C:\Project\MAG160C\csdk\third_party\libusb\win64\libusb-1.0.x64.a" -lgdi32 -luser32
```
### 运行/测试
```
Stop-Process -Name mag160c_demo3 -Force
Start-Process "C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe" -WorkingDirectory "C:\Project\MAG160C\build-artifacts"
日志:demo3_diag.txt(每帧 ref/live/nuc/窗口/d), demo3_auto_*.bmp(每 30 帧自动截图)
用户测试:启动 → 等平场(~5s) → 手放镜头前小幅移动(敲键盘) → 观察黑拖影
```
### 工具
- `build-artifacts/tsdk_pair2.exe <dir> <npairs> <delay>`:官方 SDK 抓帧(需先停 demo3;若 "no channel" 需重插设备)
- `build-artifacts/mag160c_frame_stats.exe`:libusb 直连抓帧
- 反汇编:`analysis/disasm/coresdk_windows_disasm.txt`(UTF-16,193k 行)
- 官方数据:`analysis/pairs_nuc2/pair_000.*`(f20/ref/raw=nuc/gray/rgb/cbrgb/t32/thr/gain)
---
## 5. 方法参考(专利/开源)
- Scribner et al., "Adaptive nonuniformity correction for IR focal-plane arrays using neural networks", SPIE 1541, 1991/1993 — 最经典 SBNUC
- Hardie et al., "Scene-based nonuniformity correction with reduced ghosting using a recursive Kalman filter", IEEE Trans. Image Process. 7(10), 1998 — **专治移动物体鬼影,与当前问题最相关**
- FLIR/ULIS 自适应 FFC 专利(如 US 7,940,301 系列)— 静止场景才刷新校正表
- 开源:seek-thermal-suite(madsci1016)、Melexis MLX90640 API(无快门自校正)
- 核心思想:背景像素"静止且接近参考"才更新;物体区域冻结
## 6. 未完成逆向
- [ ] 官方查表段 1/2(大 d 物体)的增益/偏移值与物体响应(线性 0.19 vs 官方分段)
- [ ] 官方窗口(0x4202c/0x42030)更新机制(动态 span 怎么算)
- [ ] 0x180016ae0 菜单表初始化(0x417xx/DDT)
- [ ] FFC 状态机 0x73bf4(帧 75/130)
- [ ] 官方输出 2x 升采样算法(0x220 38400 → RGB)
+91
View File
@@ -0,0 +1,91 @@
cmake_minimum_required(VERSION 3.16)
project(mag160c LANGUAGES C CXX)
option(MAG160C_BUILD_TESTS "Build MAG160C tests" ON)
option(MAG160C_BUILD_CLI "Build MAG160C CLI tools" ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(LIBUSB QUIET libusb-1.0)
endif()
if(MAG160C_BUILD_CLI)
add_executable(mag160c-cli tools/mag160c_cli.cpp)
target_link_libraries(mag160c-cli PRIVATE mag160c_core)
endif()
add_library(mag160c_core SHARED
src/core/error.cpp
src/core/context.cpp
src/core/tcm_frame.cpp
src/core/tcm_device.cpp
src/core/device.cpp
src/core/ir_device.cpp
src/core/ir_frame.cpp
src/c_api.cpp
)
target_include_directories(mag160c_core
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_compile_definitions(mag160c_core PRIVATE MAG160C_BUILDING_LIBRARY)
if(LIBUSB_FOUND)
set(MAG160C_HAS_LIBUSB_VALUE 1)
target_compile_definitions(mag160c_core PRIVATE MAG160C_HAS_LIBUSB=1)
target_include_directories(mag160c_core PRIVATE ${LIBUSB_INCLUDE_DIRS})
target_link_directories(mag160c_core PRIVATE ${LIBUSB_LIBRARY_DIRS})
target_link_libraries(mag160c_core PRIVATE ${LIBUSB_LIBRARIES})
target_compile_options(mag160c_core PRIVATE ${LIBUSB_CFLAGS_OTHER})
else()
set(MAG160C_HAS_LIBUSB_VALUE 0)
target_compile_definitions(mag160c_core PRIVATE MAG160C_HAS_LIBUSB=0)
endif()
if(MAG160C_BUILD_TESTS)
enable_testing()
add_executable(test_c_api tests/cpp/test_c_api.cpp)
target_link_libraries(test_c_api PRIVATE mag160c_core)
target_compile_definitions(test_c_api PRIVATE MAG160C_HAS_LIBUSB=${MAG160C_HAS_LIBUSB_VALUE})
add_test(NAME test_c_api COMMAND test_c_api)
add_executable(test_tcm_frame tests/cpp/test_tcm_frame.cpp)
target_link_libraries(test_tcm_frame PRIVATE mag160c_core)
target_include_directories(test_tcm_frame PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_test(NAME test_tcm_frame COMMAND test_tcm_frame)
add_executable(test_tcm_device tests/cpp/test_tcm_device.cpp)
target_link_libraries(test_tcm_device PRIVATE mag160c_core)
target_include_directories(test_tcm_device PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_test(NAME test_tcm_device COMMAND test_tcm_device)
add_executable(test_device_model tests/cpp/test_device_model.cpp)
target_link_libraries(test_device_model PRIVATE mag160c_core)
target_include_directories(test_device_model PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_test(NAME test_device_model COMMAND test_device_model)
add_executable(test_ir_skeleton tests/cpp/test_ir_skeleton.cpp)
target_link_libraries(test_ir_skeleton PRIVATE mag160c_core)
add_test(NAME test_ir_skeleton COMMAND test_ir_skeleton)
add_executable(test_ir_frame tests/cpp/test_ir_frame.cpp)
target_link_libraries(test_ir_frame PRIVATE mag160c_core)
target_include_directories(test_ir_frame PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_test(NAME test_ir_frame COMMAND test_ir_frame)
endif()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+340
View File
@@ -0,0 +1,340 @@
#include "mag160c/mag160c.h"
#include "core/context.hpp"
#include "core/device.hpp"
#include "core/error.hpp"
#include "core/ir_device.hpp"
#include "core/tcm_device.hpp"
#include "core/tcm_frame.hpp"
#include <new>
#include <stdexcept>
#include <vector>
namespace {
constexpr size_t MAX_TCM_PAYLOAD_SIZE = 0xffffU - 5U;
} // namespace
struct mag160c_context_t {
mag160c::core::Context context;
};
struct mag160c_ir_device_t {
mag160c::core::IrDevice device;
};
mag160c_error_t mag160c_init(mag160c_context_t** out_ctx) {
if (out_ctx == nullptr) {
mag160c::core::set_last_error("mag160c_init: out_ctx must not be null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_ctx = nullptr;
try {
*out_ctx = new mag160c_context_t{};
mag160c::core::clear_last_error();
return MAG160C_OK;
} catch (const std::bad_alloc&) {
mag160c::core::set_last_error("mag160c_init: failed to allocate context");
return MAG160C_ERR_INTERNAL;
} catch (...) {
mag160c::core::set_last_error("mag160c_init: unexpected context construction failure");
return MAG160C_ERR_INTERNAL;
}
}
void mag160c_shutdown(mag160c_context_t* ctx) {
delete ctx;
}
const char* mag160c_last_error(void) {
return mag160c::core::last_error();
}
const char* mag160c_error_name(mag160c_error_t code) {
return mag160c::core::error_name(code);
}
mag160c_error_t mag160c_list_devices(
mag160c_context_t* ctx,
mag160c_device_info_t** out_devices,
size_t* out_count
) {
if (ctx == nullptr || out_devices == nullptr || out_count == nullptr) {
mag160c::core::set_last_error(
"mag160c_list_devices: ctx, out_devices, and out_count must not be null"
);
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_devices = nullptr;
*out_count = 0;
std::vector<mag160c::core::DeviceInfo> devices;
const mag160c_error_t rc = mag160c::core::list_devices(&devices);
if (rc != MAG160C_OK) {
return rc;
}
if (devices.empty()) {
mag160c::core::clear_last_error();
return MAG160C_OK;
}
mag160c_device_info_t* raw = new (std::nothrow) mag160c_device_info_t[devices.size()];
if (raw == nullptr) {
mag160c::core::set_last_error("mag160c_list_devices: allocation failed");
return MAG160C_ERR_INTERNAL;
}
for (size_t i = 0; i < devices.size(); ++i) {
raw[i] = devices[i].c_info;
}
*out_devices = raw;
*out_count = devices.size();
mag160c::core::clear_last_error();
return MAG160C_OK;
}
void mag160c_free_device_list(mag160c_device_info_t* devices) {
delete[] devices;
}
mag160c_error_t mag160c_ir_open_first(
mag160c_context_t* ctx,
mag160c_ir_device_t** out_device
) {
if (ctx == nullptr || out_device == nullptr) {
mag160c::core::set_last_error(
"mag160c_ir_open_first: ctx and out_device must not be null"
);
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_device = nullptr;
mag160c_ir_device_t* device = new (std::nothrow) mag160c_ir_device_t{};
if (device == nullptr) {
mag160c::core::set_last_error("mag160c_ir_open_first: allocation failed");
return MAG160C_ERR_INTERNAL;
}
*out_device = device;
mag160c::core::clear_last_error();
return MAG160C_OK;
}
void mag160c_ir_close(mag160c_ir_device_t* device) {
delete device;
}
mag160c_error_t mag160c_ir_get_info(
mag160c_ir_device_t* device,
mag160c_ir_info_t* out_info
) {
if (device == nullptr || out_info == nullptr) {
mag160c::core::set_last_error(
"mag160c_ir_get_info: device and out_info must not be null"
);
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_info = device->device.info();
mag160c::core::clear_last_error();
return MAG160C_OK;
}
mag160c_error_t mag160c_ir_trigger_ffc(mag160c_ir_device_t* device) {
if (device == nullptr) {
mag160c::core::set_last_error("mag160c_ir_trigger_ffc: device must not be null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
return device->device.trigger_ffc();
}
mag160c_error_t mag160c_ir_read_raw_once(
mag160c_ir_device_t* device,
uint8_t* out_bytes,
size_t out_capacity,
size_t* out_size,
int timeout_ms
) {
if (device == nullptr) {
if (out_size != nullptr) {
*out_size = 0;
}
mag160c::core::set_last_error("mag160c_ir_read_raw_once: device must not be null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
return device->device.read_raw_once(out_bytes, out_capacity, out_size, timeout_ms);
}
mag160c_error_t mag160c_tcm_encode_frame(
uint8_t main_cmd,
uint8_t sub_cmd,
uint16_t frame_id,
const uint8_t* payload,
size_t payload_size,
uint8_t* out_bytes,
size_t out_capacity,
size_t* out_size
) {
if (out_size == nullptr) {
mag160c::core::set_last_error("mag160c_tcm_encode_frame: out_size must not be null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (payload_size > MAX_TCM_PAYLOAD_SIZE) {
*out_size = 0;
mag160c::core::set_last_error("mag160c_tcm_encode_frame: TCM payload is too large");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (payload_size != 0 && payload == nullptr) {
*out_size = 0;
mag160c::core::set_last_error(
"mag160c_tcm_encode_frame: payload must not be null when payload_size is nonzero"
);
return MAG160C_ERR_INVALID_ARGUMENT;
}
std::vector<uint8_t> payload_vec;
if (payload_size != 0) {
payload_vec.assign(payload, payload + payload_size);
}
std::vector<uint8_t> encoded;
try {
encoded = mag160c::core::encode_tcm_frame(main_cmd, sub_cmd, frame_id, payload_vec);
} catch (const std::length_error&) {
*out_size = 0;
mag160c::core::set_last_error("mag160c_tcm_encode_frame: TCM payload is too large");
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_size = encoded.size();
if (out_bytes == nullptr || out_capacity < encoded.size()) {
mag160c::core::set_last_error("mag160c_tcm_encode_frame: output buffer is too small");
return MAG160C_ERR_INVALID_ARGUMENT;
}
for (size_t i = 0; i < encoded.size(); ++i) {
out_bytes[i] = encoded[i];
}
mag160c::core::clear_last_error();
return MAG160C_OK;
}
mag160c_error_t mag160c_tcm_decode_header(
const uint8_t* data,
size_t size,
uint8_t* out_main_cmd,
uint8_t* out_sub_cmd,
uint16_t* out_frame_id,
size_t* out_payload_size
) {
if (out_main_cmd == nullptr || out_sub_cmd == nullptr || out_frame_id == nullptr ||
out_payload_size == nullptr) {
mag160c::core::set_last_error(
"mag160c_tcm_decode_header: all output pointers must not be null"
);
return MAG160C_ERR_INVALID_ARGUMENT;
}
mag160c::core::TcmFrame frame;
const mag160c_error_t rc = mag160c::core::decode_tcm_frame(data, size, &frame);
if (rc != MAG160C_OK) {
return rc;
}
*out_main_cmd = frame.main_cmd;
*out_sub_cmd = frame.sub_cmd;
*out_frame_id = frame.frame_id;
*out_payload_size = frame.payload.size();
mag160c::core::clear_last_error();
return MAG160C_OK;
}
namespace {
mag160c_error_t copy_frame_to_output(
const std::vector<uint8_t>& frame,
uint8_t* out_bytes,
size_t out_capacity,
size_t* out_size
) {
if (out_size == nullptr) {
mag160c::core::set_last_error("TCM frame builder: out_size must not be null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_size = frame.size();
if (out_bytes == nullptr || out_capacity < frame.size()) {
mag160c::core::set_last_error("TCM frame builder: output buffer is too small");
return MAG160C_ERR_INVALID_ARGUMENT;
}
for (size_t i = 0; i < frame.size(); ++i) {
out_bytes[i] = frame[i];
}
mag160c::core::clear_last_error();
return MAG160C_OK;
}
} // namespace
mag160c_error_t mag160c_tcm_build_rotate_frame(
int angle,
uint8_t* out_bytes,
size_t out_capacity,
size_t* out_size
) {
mag160c::core::TcmCommandBuilder builder;
return copy_frame_to_output(builder.build_rotate_frame(angle), out_bytes, out_capacity, out_size);
}
mag160c_error_t mag160c_tcm_build_light_frame(
mag160c_tcm_light_color_t color,
mag160c_tcm_light_mode_t mode,
uint8_t* out_bytes,
size_t out_capacity,
size_t* out_size
) {
switch (color) {
case MAG160C_TCM_LIGHT_OFF:
case MAG160C_TCM_LIGHT_RED:
case MAG160C_TCM_LIGHT_GREEN:
case MAG160C_TCM_LIGHT_BLUE:
case MAG160C_TCM_LIGHT_YELLOW:
break;
default:
if (out_size != nullptr) {
*out_size = 0;
}
mag160c::core::set_last_error("mag160c_tcm_build_light_frame: invalid color");
return MAG160C_ERR_INVALID_ARGUMENT;
}
switch (mode) {
case MAG160C_TCM_LIGHT_STEADY:
case MAG160C_TCM_LIGHT_BLINK:
case MAG160C_TCM_LIGHT_BREATH:
break;
default:
if (out_size != nullptr) {
*out_size = 0;
}
mag160c::core::set_last_error("mag160c_tcm_build_light_frame: invalid mode");
return MAG160C_ERR_INVALID_ARGUMENT;
}
mag160c::core::TcmCommandBuilder builder;
return copy_frame_to_output(builder.build_light_frame(color, mode), out_bytes, out_capacity, out_size);
}
+17
View File
@@ -0,0 +1,17 @@
#include "core/context.hpp"
#ifndef MAG160C_HAS_LIBUSB
#define MAG160C_HAS_LIBUSB 0
#endif
namespace mag160c::core {
Context::Context()
: has_libusb_(MAG160C_HAS_LIBUSB != 0) {}
bool Context::has_libusb() const noexcept {
return has_libusb_;
}
} // namespace mag160c::core
+19
View File
@@ -0,0 +1,19 @@
#ifndef MAG160C_CORE_CONTEXT_HPP
#define MAG160C_CORE_CONTEXT_HPP
namespace mag160c::core {
class Context {
public:
Context();
bool has_libusb() const noexcept;
private:
bool has_libusb_;
};
} // namespace mag160c::core
#endif
+185
View File
@@ -0,0 +1,185 @@
#include "core/device.hpp"
#include "core/error.hpp"
#include <cstring>
#if MAG160C_HAS_LIBUSB
#include <libusb.h>
#endif
namespace mag160c::core {
bool is_in_endpoint(uint8_t address) {
return (address & 0x80U) != 0;
}
bool find_bulk_pair(const std::vector<EndpointDescriptor>& endpoints, EndpointPair* out) {
if (out == nullptr) {
return false;
}
EndpointPair pair{};
for (const EndpointDescriptor& endpoint : endpoints) {
if (endpoint.type != EndpointType::Bulk) {
continue;
}
if (is_in_endpoint(endpoint.address) && pair.bulk_in == 0) {
pair.bulk_in = endpoint.address;
} else if (!is_in_endpoint(endpoint.address) && pair.bulk_out == 0) {
pair.bulk_out = endpoint.address;
}
}
if (pair.bulk_in == 0 || pair.bulk_out == 0) {
return false;
}
*out = pair;
return true;
}
#if MAG160C_HAS_LIBUSB
namespace {
EndpointType endpoint_type_from_libusb(uint8_t attributes) {
switch (attributes & LIBUSB_TRANSFER_TYPE_MASK) {
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
return EndpointType::Isochronous;
case LIBUSB_TRANSFER_TYPE_BULK:
return EndpointType::Bulk;
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
return EndpointType::Interrupt;
default:
return EndpointType::Other;
}
}
void copy_string_descriptor(
libusb_device_handle* handle,
uint8_t index,
char* out,
size_t out_size
) {
if (out == nullptr || out_size == 0) {
return;
}
out[0] = '\0';
if (handle == nullptr || index == 0) {
return;
}
unsigned char buffer[128] = {};
const int rc = libusb_get_string_descriptor_ascii(handle, index, buffer, sizeof(buffer));
if (rc <= 0) {
return;
}
const size_t n = static_cast<size_t>(rc) < out_size - 1 ? static_cast<size_t>(rc) : out_size - 1;
std::memcpy(out, buffer, n);
out[n] = '\0';
}
} // namespace
#endif
mag160c_error_t list_devices(std::vector<DeviceInfo>* out) {
if (out == nullptr) {
set_last_error("list_devices: out must not be null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
out->clear();
#if !MAG160C_HAS_LIBUSB
set_last_error("list_devices: libusb-1.0 was not available at build time");
return MAG160C_ERR_UNSUPPORTED;
#else
libusb_context* ctx = nullptr;
int rc = libusb_init(&ctx);
if (rc != 0) {
set_last_error("list_devices: libusb_init failed");
return MAG160C_ERR_USB;
}
libusb_device** list = nullptr;
const ssize_t count = libusb_get_device_list(ctx, &list);
if (count < 0) {
libusb_exit(ctx);
set_last_error("list_devices: libusb_get_device_list failed");
return MAG160C_ERR_USB;
}
for (ssize_t i = 0; i < count; ++i) {
libusb_device* device = list[i];
libusb_device_descriptor desc{};
if (libusb_get_device_descriptor(device, &desc) != 0) {
continue;
}
if (desc.idVendor != MAG_IR_VENDOR_ID || desc.idProduct != MAG_IR_PRODUCT_ID) {
continue;
}
libusb_config_descriptor* config = nullptr;
if (libusb_get_active_config_descriptor(device, &config) != 0 || config == nullptr) {
continue;
}
bool added = false;
for (uint8_t iface_index = 0; iface_index < config->bNumInterfaces && !added; ++iface_index) {
const libusb_interface& iface = config->interface[iface_index];
for (int alt_index = 0; alt_index < iface.num_altsetting && !added; ++alt_index) {
const libusb_interface_descriptor& alt = iface.altsetting[alt_index];
std::vector<EndpointDescriptor> endpoints;
endpoints.reserve(alt.bNumEndpoints);
for (uint8_t ep_index = 0; ep_index < alt.bNumEndpoints; ++ep_index) {
const libusb_endpoint_descriptor& ep = alt.endpoint[ep_index];
endpoints.push_back({ep.bEndpointAddress, endpoint_type_from_libusb(ep.bmAttributes)});
}
EndpointPair pair{};
if (!find_bulk_pair(endpoints, &pair)) {
continue;
}
DeviceInfo info{};
info.c_info.vendor_id = desc.idVendor;
info.c_info.product_id = desc.idProduct;
info.c_info.bus = static_cast<uint8_t>(libusb_get_bus_number(device));
info.c_info.address = static_cast<uint8_t>(libusb_get_device_address(device));
info.c_info.interface_number = alt.bInterfaceNumber;
info.c_info.bulk_in_endpoint = pair.bulk_in;
info.c_info.bulk_out_endpoint = pair.bulk_out;
libusb_device_handle* handle = nullptr;
if (libusb_open(device, &handle) == 0) {
copy_string_descriptor(handle, desc.iProduct, info.c_info.product, sizeof(info.c_info.product));
copy_string_descriptor(
handle,
desc.iManufacturer,
info.c_info.manufacturer,
sizeof(info.c_info.manufacturer)
);
copy_string_descriptor(handle, desc.iSerialNumber, info.c_info.serial, sizeof(info.c_info.serial));
libusb_close(handle);
}
out->push_back(info);
added = true;
}
}
libusb_free_config_descriptor(config);
}
libusb_free_device_list(list, 1);
libusb_exit(ctx);
clear_last_error();
return MAG160C_OK;
#endif
}
} // namespace mag160c::core
+41
View File
@@ -0,0 +1,41 @@
#ifndef MAG160C_CORE_DEVICE_HPP
#define MAG160C_CORE_DEVICE_HPP
#include "mag160c/mag160c.h"
#include <cstdint>
#include <vector>
namespace mag160c::core {
constexpr uint16_t MAG_IR_VENDOR_ID = 0x833c;
constexpr uint16_t MAG_IR_PRODUCT_ID = 0x0001;
enum class EndpointType {
Other,
Bulk,
Interrupt,
Isochronous
};
struct EndpointDescriptor {
uint8_t address = 0;
EndpointType type = EndpointType::Other;
};
struct EndpointPair {
uint8_t bulk_in = 0;
uint8_t bulk_out = 0;
};
struct DeviceInfo {
mag160c_device_info_t c_info{};
};
bool is_in_endpoint(uint8_t address);
bool find_bulk_pair(const std::vector<EndpointDescriptor>& endpoints, EndpointPair* out);
mag160c_error_t list_devices(std::vector<DeviceInfo>* out);
} // namespace mag160c::core
#endif
+51
View File
@@ -0,0 +1,51 @@
#include "core/error.hpp"
#include <utility>
namespace mag160c::core {
namespace {
thread_local std::string g_last_error;
} // namespace
const char* error_name(mag160c_error_t code) noexcept {
switch (code) {
case MAG160C_OK:
return "MAG160C_OK";
case MAG160C_ERR_INVALID_ARGUMENT:
return "MAG160C_ERR_INVALID_ARGUMENT";
case MAG160C_ERR_NO_DEVICE:
return "MAG160C_ERR_NO_DEVICE";
case MAG160C_ERR_PERMISSION:
return "MAG160C_ERR_PERMISSION";
case MAG160C_ERR_USB:
return "MAG160C_ERR_USB";
case MAG160C_ERR_TIMEOUT:
return "MAG160C_ERR_TIMEOUT";
case MAG160C_ERR_CHECKSUM:
return "MAG160C_ERR_CHECKSUM";
case MAG160C_ERR_PROTOCOL_UNKNOWN:
return "MAG160C_ERR_PROTOCOL_UNKNOWN";
case MAG160C_ERR_UNSUPPORTED:
return "MAG160C_ERR_UNSUPPORTED";
case MAG160C_ERR_INTERNAL:
return "MAG160C_ERR_INTERNAL";
default:
return "MAG160C_ERR_UNKNOWN_CODE";
}
}
const char* last_error() noexcept {
return g_last_error.c_str();
}
void clear_last_error() {
g_last_error.clear();
}
void set_last_error(std::string message) {
g_last_error = std::move(message);
}
} // namespace mag160c::core
+18
View File
@@ -0,0 +1,18 @@
#ifndef MAG160C_CORE_ERROR_HPP
#define MAG160C_CORE_ERROR_HPP
#include "mag160c/mag160c.h"
#include <string>
namespace mag160c::core {
const char* error_name(mag160c_error_t code) noexcept;
const char* last_error() noexcept;
void clear_last_error();
void set_last_error(std::string message);
} // namespace mag160c::core
#endif
@@ -0,0 +1,65 @@
#include "core/ir_device.hpp"
#include "core/error.hpp"
#include <cstring>
namespace mag160c::core {
namespace {
void copy_literal(char* out, size_t out_size, const char* value) {
if (out == nullptr || out_size == 0) {
return;
}
std::strncpy(out, value, out_size - 1);
out[out_size - 1] = '\0';
}
} // namespace
IrDevice::IrDevice() {
info_.width = 160;
info_.height = 120;
info_.output_width = 160;
info_.output_height = 120;
info_.max_fps = 25;
info_.current_fps = 0;
copy_literal(info_.name, sizeof(info_.name), "MAG160C");
copy_literal(info_.type, sizeof(info_.type), "vendor-bulk-ir");
}
const mag160c_ir_info_t& IrDevice::info() const noexcept {
return info_;
}
mag160c_error_t IrDevice::trigger_ffc() const {
set_last_error(
"IR FFC protocol recovered from libmagcore.so.2.1.1: write {u32 0x6bb6b672, u32 param} "
"on bulk endpoint OUT 0x03, then read response on IN 0x82 (0x1000 max, 2000 ms timeout); "
"live USB transport is not available in this build"
);
return MAG160C_ERR_UNSUPPORTED;
}
mag160c_error_t IrDevice::read_raw_once(
unsigned char* /*out_bytes*/,
size_t /*out_capacity*/,
size_t* out_size,
int /*timeout_ms*/
) const {
if (out_size == nullptr) {
set_last_error("mag160c_ir_read_raw_once: out_size must not be null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_size = 0;
set_last_error(
"IR raw read protocol recovered: start stream with {0x6bb6b673} on OUT 0x03, then read "
"frame stream on IN 0x81 (markers 0x1bb1b11b/0x1bb1b11c, data at +0x1c, size 0x38+len); "
"live USB transport is not available in this build"
);
return MAG160C_ERR_UNSUPPORTED;
}
} // namespace mag160c::core
@@ -0,0 +1,27 @@
#ifndef MAG160C_CORE_IR_DEVICE_HPP
#define MAG160C_CORE_IR_DEVICE_HPP
#include "mag160c/mag160c.h"
namespace mag160c::core {
class IrDevice {
public:
IrDevice();
const mag160c_ir_info_t& info() const noexcept;
mag160c_error_t trigger_ffc() const;
mag160c_error_t read_raw_once(
unsigned char* out_bytes,
size_t out_capacity,
size_t* out_size,
int timeout_ms
) const;
private:
mag160c_ir_info_t info_{};
};
} // namespace mag160c::core
#endif
+107
View File
@@ -0,0 +1,107 @@
#include "core/ir_frame.hpp"
namespace mag160c::core {
mag160c_error_t parse_ir_frame(
const uint8_t* data,
size_t size,
IrFrameHeader* out_header,
const uint16_t** out_pixels
) {
if (data == nullptr || out_header == nullptr || out_pixels == nullptr) {
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (size < MAG160C_IR_FRAME_DATA_OFFSET + 4) {
return MAG160C_ERR_PROTOCOL_UNKNOWN;
}
const uint32_t marker =
static_cast<uint32_t>(data[0]) |
(static_cast<uint32_t>(data[1]) << 8) |
(static_cast<uint32_t>(data[2]) << 16) |
(static_cast<uint32_t>(data[3]) << 24);
if (marker != MAG160C_IR_FRAME_MARKER) {
return MAG160C_ERR_PROTOCOL_UNKNOWN;
}
auto rd32 = [&](size_t off) {
return static_cast<uint32_t>(data[off]) |
(static_cast<uint32_t>(data[off + 1]) << 8) |
(static_cast<uint32_t>(data[off + 2]) << 16) |
(static_cast<uint32_t>(data[off + 3]) << 24);
};
IrFrameHeader h{};
h.frame_counter = rd32(4);
h.data_length = rd32(8);
h.frame_type = rd32(12);
h.period_shutter = rd32(16);
if (h.frame_type > 1) {
return MAG160C_ERR_PROTOCOL_UNKNOWN;
}
if (size < MAG160C_IR_FRAME_OVERHEAD + h.data_length) {
return MAG160C_ERR_PROTOCOL_UNKNOWN;
}
const size_t trailing_offset = MAG160C_IR_FRAME_DATA_OFFSET + h.data_length;
if (rd32(trailing_offset) != MAG160C_IR_FRAME_TRAILING_MARKER) {
return MAG160C_ERR_PROTOCOL_UNKNOWN;
}
h.marker = marker;
*out_header = h;
*out_pixels = reinterpret_cast<const uint16_t*>(data + MAG160C_IR_FRAME_DATA_OFFSET);
return MAG160C_OK;
}
void calibrate_frame(
const uint16_t* frame,
const IrCalibrationTables& tables,
uint16_t* out
) {
if (frame == nullptr || tables.coeff == nullptr || tables.thresholds == nullptr ||
out == nullptr || tables.pixel_count == 0) {
return;
}
const uint32_t pixels = tables.pixel_count;
const uint32_t bands = tables.band_count == 0 ? 1 : tables.band_count;
const uint32_t search_bands = bands - 1;
for (uint32_t p = 0; p < pixels; ++p) {
int32_t diff;
if (tables.has_baseline && tables.baseline != nullptr) {
diff = static_cast<int32_t>(static_cast<int16_t>(
frame[p] - tables.baseline[p]));
} else {
diff = static_cast<int32_t>(frame[p]);
}
diff >>= 1;
uint32_t band = 0;
if (search_bands > 0) {
for (uint32_t i = 0; i < search_bands; ++i) {
if (diff <= tables.thresholds[p * search_bands + i]) {
band = i;
break;
}
band = i + 1;
}
}
const size_t entry = (static_cast<size_t>(band) * pixels + p) * 2;
const uint32_t coeff = tables.coeff[entry];
const uint32_t offset = tables.coeff[entry + 1];
int32_t v = static_cast<int32_t>(offset) + ((diff * static_cast<int32_t>(coeff)) >> 12);
if (v < 0) {
v = 0;
}
if (v > 0xffff) {
v = 0xffff;
}
out[p] = static_cast<uint16_t>(v);
}
}
} // namespace mag160c::core
+57
View File
@@ -0,0 +1,57 @@
#ifndef MAG160C_CORE_IR_FRAME_HPP
#define MAG160C_CORE_IR_FRAME_HPP
#include "mag160c/mag160c.h"
#include <cstddef>
#include <cstdint>
namespace mag160c::core {
struct IrFrameHeader {
uint32_t marker;
uint32_t frame_counter;
uint32_t data_length;
uint32_t frame_type;
uint32_t period_shutter;
};
struct IrCalibrationTables {
const int16_t* thresholds; /* per-pixel band thresholds, [bands] per pixel */
const uint16_t* coeff; /* per-pixel per-band {coeff, offset} pairs, 2*bands per pixel */
uint32_t pixel_count; /* 160*120 = 19200 */
uint32_t band_count; /* thresholds per pixel (band_count-1 searched) */
const uint16_t* baseline; /* optional baseline frame (may be null) */
bool has_baseline;
};
/*
* Parse a frame stream chunk recovered from libmagcore.so.2.1.1:
* marker 0x1bb1b11b @0, frame counter @4, data length @8, type @0xc, shutter @0x10,
* pixel data @0x1c, trailing marker 0x1bb1b11c @0x1c+data_length, total 0x38+data_length.
* Returns MAG160C_OK when a complete, valid frame is present.
*/
mag160c_error_t parse_ir_frame(
const uint8_t* data,
size_t size,
IrFrameHeader* out_header,
const uint16_t** out_pixels
);
/*
* Recovered CFunctions::Calibration piecewise-linear map (libcoresdk.so 0x6a41c):
* diff = (int16)(frame[p] - baseline[p]) >> 1
* band = first index i where diff <= thresholds[p*bands+i] (linear scan, max bands-1)
* v = coeff[(band*pixels + p)*2 + 1] + ((diff * coeff[(band*pixels + p)*2]) >> 12)
* out[p] = clamp(v, 0, 0xffff)
* When no baseline is set, diff = frame[p] >> 1.
*/
void calibrate_frame(
const uint16_t* frame,
const IrCalibrationTables& tables,
uint16_t* out
);
} // namespace mag160c::core
#endif
@@ -0,0 +1,70 @@
#include "core/tcm_device.hpp"
#include <algorithm>
namespace mag160c::core {
namespace {
constexpr uint8_t MAIN_TCM = 0x02;
constexpr uint8_t SUB_ROTATE = 0x77;
constexpr uint8_t SUB_LIGHT_STEADY = 0x31;
constexpr uint8_t SUB_LIGHT_BLINK = 0x32;
constexpr uint8_t SUB_LIGHT_BREATH = 0x33;
uint8_t light_subcommand(mag160c_tcm_light_mode_t mode) {
switch (mode) {
case MAG160C_TCM_LIGHT_STEADY:
return SUB_LIGHT_STEADY;
case MAG160C_TCM_LIGHT_BLINK:
return SUB_LIGHT_BLINK;
case MAG160C_TCM_LIGHT_BREATH:
return SUB_LIGHT_BREATH;
}
return SUB_LIGHT_STEADY;
}
std::vector<uint8_t> light_payload(mag160c_tcm_light_color_t color) {
switch (color) {
case MAG160C_TCM_LIGHT_OFF:
return {0x00, 0x00, 0x00, 0x00};
case MAG160C_TCM_LIGHT_RED:
return {0x01, 0xff, 0x00, 0x00};
case MAG160C_TCM_LIGHT_GREEN:
return {0x01, 0x00, 0xff, 0x00};
case MAG160C_TCM_LIGHT_BLUE:
return {0x01, 0x00, 0x00, 0xff};
case MAG160C_TCM_LIGHT_YELLOW:
return {0x01, 0xff, 0xff, 0x00};
}
return {0x00, 0x00, 0x00, 0x00};
}
} // namespace
TcmCommandBuilder::TcmCommandBuilder() : frame_id_(1) {}
uint16_t TcmCommandBuilder::next_frame_id() {
const uint16_t current = frame_id_;
++frame_id_;
if (frame_id_ >= 0x8000) {
frame_id_ = 1;
}
return current;
}
std::vector<uint8_t> TcmCommandBuilder::build_rotate_frame(int angle) {
angle = std::clamp(angle, -128, 128);
const uint8_t direction = angle < 0 ? 0x01 : 0x00;
const uint8_t magnitude = static_cast<uint8_t>(angle < 0 ? -angle : angle);
return encode_tcm_frame(MAIN_TCM, SUB_ROTATE, next_frame_id(), {direction, magnitude});
}
std::vector<uint8_t> TcmCommandBuilder::build_light_frame(
mag160c_tcm_light_color_t color,
mag160c_tcm_light_mode_t mode
) {
return encode_tcm_frame(MAIN_TCM, light_subcommand(mode), next_frame_id(), light_payload(color));
}
} // namespace mag160c::core
@@ -0,0 +1,30 @@
#ifndef MAG160C_CORE_TCM_DEVICE_HPP
#define MAG160C_CORE_TCM_DEVICE_HPP
#include "core/tcm_frame.hpp"
#include "mag160c/mag160c.h"
#include <cstdint>
#include <vector>
namespace mag160c::core {
class TcmCommandBuilder {
public:
TcmCommandBuilder();
std::vector<uint8_t> build_rotate_frame(int angle);
std::vector<uint8_t> build_light_frame(
mag160c_tcm_light_color_t color,
mag160c_tcm_light_mode_t mode
);
private:
uint16_t next_frame_id();
uint16_t frame_id_;
};
} // namespace mag160c::core
#endif
+101
View File
@@ -0,0 +1,101 @@
#include "core/tcm_frame.hpp"
#include "core/error.hpp"
#include <stdexcept>
namespace mag160c::core {
namespace {
constexpr size_t MAX_TCM_PAYLOAD_SIZE = 0xffffU - 5U;
} // namespace
uint8_t checksum(const uint8_t* data, size_t begin, size_t end) {
if (data == nullptr || begin > end) {
return 0;
}
uint8_t sum = 0;
for (size_t i = begin; i < end; ++i) {
sum = static_cast<uint8_t>(sum + data[i]);
}
return sum;
}
std::vector<uint8_t> encode_tcm_frame(
uint8_t main_cmd,
uint8_t sub_cmd,
uint16_t frame_id,
const std::vector<uint8_t>& payload
) {
if (payload.size() > MAX_TCM_PAYLOAD_SIZE) {
throw std::length_error("TCM payload is too large");
}
const uint16_t body_length = static_cast<uint16_t>(payload.size() + 5U);
std::vector<uint8_t> out(static_cast<size_t>(body_length) + 4U, 0);
out[0] = 0x7e;
out[1] = static_cast<uint8_t>((body_length >> 8) & 0xff);
out[2] = static_cast<uint8_t>(body_length & 0xff);
out[3] = checksum(out.data(), 0, 3);
out[4] = main_cmd;
out[5] = sub_cmd;
out[6] = static_cast<uint8_t>((frame_id >> 8) & 0xff);
out[7] = static_cast<uint8_t>(frame_id & 0xff);
for (size_t i = 0; i < payload.size(); ++i) {
out[8 + i] = payload[i];
}
out[out.size() - 1] = checksum(out.data(), 4, out.size() - 1);
return out;
}
mag160c_error_t decode_tcm_frame(const uint8_t* data, size_t size, TcmFrame* out) {
if (data == nullptr || out == nullptr) {
set_last_error("decode_tcm_frame: data and out must not be null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (size < 9) {
set_last_error("decode_tcm_frame: packet shorter than minimum TCM frame");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (data[0] != 0x7e) {
set_last_error("decode_tcm_frame: missing 0x7e header byte");
return MAG160C_ERR_CHECKSUM;
}
if (data[3] != checksum(data, 0, 3)) {
set_last_error("decode_tcm_frame: invalid header checksum");
return MAG160C_ERR_CHECKSUM;
}
const uint16_t body_length =
static_cast<uint16_t>((static_cast<uint16_t>(data[1]) << 8) | data[2]);
const size_t expected_size = static_cast<size_t>(body_length) + 4U;
if (size != expected_size) {
set_last_error("decode_tcm_frame: packet size does not match encoded body length");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (data[size - 1] != checksum(data, 4, size - 1)) {
set_last_error("decode_tcm_frame: invalid body checksum");
return MAG160C_ERR_CHECKSUM;
}
out->main_cmd = data[4];
out->sub_cmd = data[5];
out->frame_id = static_cast<uint16_t>((static_cast<uint16_t>(data[6]) << 8) | data[7]);
out->payload.assign(data + 8, data + size - 1);
clear_last_error();
return MAG160C_OK;
}
} // namespace mag160c::core
@@ -0,0 +1,30 @@
#ifndef MAG160C_CORE_TCM_FRAME_HPP
#define MAG160C_CORE_TCM_FRAME_HPP
#include "mag160c/mag160c.h"
#include <cstddef>
#include <cstdint>
#include <vector>
namespace mag160c::core {
struct TcmFrame {
uint8_t main_cmd = 0;
uint8_t sub_cmd = 0;
uint16_t frame_id = 0;
std::vector<uint8_t> payload;
};
uint8_t checksum(const uint8_t* data, size_t begin, size_t end);
std::vector<uint8_t> encode_tcm_frame(
uint8_t main_cmd,
uint8_t sub_cmd,
uint16_t frame_id,
const std::vector<uint8_t>& payload
);
mag160c_error_t decode_tcm_frame(const uint8_t* data, size_t size, TcmFrame* out);
} // namespace mag160c::core
#endif
@@ -0,0 +1,20 @@
#ifndef MAG160C_CORE_TRANSPORT_HPP
#define MAG160C_CORE_TRANSPORT_HPP
#include "mag160c/mag160c.h"
#include <cstdint>
#include <vector>
namespace mag160c::core {
class Transport {
public:
virtual ~Transport() = default;
virtual mag160c_error_t write(const std::vector<uint8_t>& bytes) = 0;
virtual mag160c_error_t read(std::vector<uint8_t>* out, int timeout_ms) = 0;
};
} // namespace mag160c::core
#endif
@@ -0,0 +1,106 @@
#include "mag160c/mag160c.h"
#include <cassert>
#include <cstdint>
#include <cstring>
#include <vector>
int main() {
assert(std::strcmp(mag160c_error_name(MAG160C_OK), "MAG160C_OK") == 0);
assert(std::strcmp(mag160c_error_name(MAG160C_ERR_PROTOCOL_UNKNOWN),
"MAG160C_ERR_PROTOCOL_UNKNOWN") == 0);
assert(std::strcmp(mag160c_error_name(static_cast<mag160c_error_t>(9999)),
"MAG160C_ERR_UNKNOWN_CODE") == 0);
assert(mag160c_init(nullptr) == MAG160C_ERR_INVALID_ARGUMENT);
assert(std::strstr(mag160c_last_error(), "out_ctx") != nullptr);
mag160c_context_t* ctx = nullptr;
assert(mag160c_init(&ctx) == MAG160C_OK);
assert(ctx != nullptr);
assert(std::strcmp(mag160c_last_error(), "") == 0);
mag160c_shutdown(ctx);
mag160c_shutdown(nullptr);
assert(mag160c_list_devices(nullptr, nullptr, nullptr) == MAG160C_ERR_INVALID_ARGUMENT);
ctx = nullptr;
assert(mag160c_init(&ctx) == MAG160C_OK);
mag160c_device_info_t* devices = reinterpret_cast<mag160c_device_info_t*>(0x1);
size_t device_count = 42;
#if MAG160C_HAS_LIBUSB
assert(mag160c_list_devices(ctx, &devices, &device_count) == MAG160C_OK);
mag160c_free_device_list(devices);
#else
assert(mag160c_list_devices(ctx, &devices, &device_count) == MAG160C_ERR_UNSUPPORTED);
assert(devices == nullptr);
assert(device_count == 0);
assert(std::strstr(mag160c_last_error(), "libusb") != nullptr);
mag160c_free_device_list(devices);
#endif
mag160c_shutdown(ctx);
const uint8_t payload[] = {0x00, 0x05};
const uint8_t expected[] = {
0x7e, 0x00, 0x07, 0x85, 0x02, 0x77, 0x00, 0x01, 0x00, 0x05, 0x7f};
size_t encoded_size = 0;
assert(mag160c_tcm_encode_frame(0x02, 0x77, 0x0001, payload, sizeof(payload), nullptr, 0,
&encoded_size) == MAG160C_ERR_INVALID_ARGUMENT);
assert(encoded_size == sizeof(expected));
uint8_t encoded[sizeof(expected)] = {};
assert(mag160c_tcm_encode_frame(0x02, 0x77, 0x0001, payload, sizeof(payload), encoded,
sizeof(encoded), &encoded_size) == MAG160C_OK);
assert(encoded_size == sizeof(expected));
assert(std::memcmp(encoded, expected, sizeof(expected)) == 0);
uint8_t main_cmd = 0;
uint8_t sub_cmd = 0;
uint16_t frame_id = 0;
size_t payload_size = 0;
assert(mag160c_tcm_decode_header(encoded, encoded_size, &main_cmd, &sub_cmd, &frame_id,
&payload_size) == MAG160C_OK);
assert(main_cmd == 0x02);
assert(sub_cmd == 0x77);
assert(frame_id == 0x0001);
assert(payload_size == sizeof(payload));
size_t rotate_size = 0;
assert(mag160c_tcm_build_rotate_frame(5, nullptr, 0, &rotate_size) ==
MAG160C_ERR_INVALID_ARGUMENT);
assert(rotate_size == sizeof(expected));
uint8_t rotate_frame[sizeof(expected)] = {};
assert(mag160c_tcm_build_rotate_frame(5, rotate_frame, sizeof(rotate_frame), &rotate_size) ==
MAG160C_OK);
assert(rotate_size == sizeof(expected));
assert(std::memcmp(rotate_frame, expected, sizeof(expected)) == 0);
const uint8_t expected_green_blink[] = {
0x7e, 0x00, 0x09, 0x87, 0x02, 0x32, 0x00, 0x01, 0x01, 0x00, 0xff, 0x00, 0x35};
size_t light_size = 0;
uint8_t light_frame[sizeof(expected_green_blink)] = {};
assert(mag160c_tcm_build_light_frame(MAG160C_TCM_LIGHT_GREEN, MAG160C_TCM_LIGHT_BLINK,
light_frame, sizeof(light_frame), &light_size) ==
MAG160C_OK);
assert(light_size == sizeof(expected_green_blink));
assert(std::memcmp(light_frame, expected_green_blink, sizeof(expected_green_blink)) == 0);
assert(mag160c_tcm_build_light_frame(static_cast<mag160c_tcm_light_color_t>(99),
MAG160C_TCM_LIGHT_BLINK, light_frame,
sizeof(light_frame), &light_size) ==
MAG160C_ERR_INVALID_ARGUMENT);
assert(std::strstr(mag160c_last_error(), "color") != nullptr);
const std::vector<uint8_t> oversized_payload(65531U, 0xaa);
encoded_size = 123U;
assert(mag160c_tcm_encode_frame(0x02, 0x77, 0x0001, oversized_payload.data(),
oversized_payload.size(), nullptr, 0, &encoded_size) ==
MAG160C_ERR_INVALID_ARGUMENT);
assert(encoded_size == 0);
assert(std::strstr(mag160c_last_error(), "too large") != nullptr);
return 0;
}
@@ -0,0 +1,34 @@
#include "core/device.hpp"
#include <cassert>
#include <vector>
int main() {
using mag160c::core::EndpointDescriptor;
using mag160c::core::EndpointPair;
using mag160c::core::EndpointType;
const std::vector<EndpointDescriptor> endpoints = {
{0x01, EndpointType::Interrupt},
{0x82, EndpointType::Bulk},
{0x03, EndpointType::Bulk},
};
EndpointPair pair{};
assert(mag160c::core::find_bulk_pair(endpoints, &pair));
assert(pair.bulk_in == 0x82);
assert(pair.bulk_out == 0x03);
const std::vector<EndpointDescriptor> missing_out = {
{0x82, EndpointType::Bulk},
};
pair = {};
assert(!mag160c::core::find_bulk_pair(missing_out, &pair));
assert(!mag160c::core::find_bulk_pair(endpoints, nullptr));
assert(mag160c::core::is_in_endpoint(0x82));
assert(!mag160c::core::is_in_endpoint(0x03));
return 0;
}
@@ -0,0 +1,180 @@
#include "core/ir_frame.hpp"
#include <cassert>
#include <cstdint>
#include <vector>
namespace {
void test_parse_valid_frame() {
// 2x1 frame: marker + counter + len + type + shutter + 2 pixels + trailing marker + tail
std::vector<uint8_t> data(0x38 + 4, 0xaa);
auto w32 = [&](size_t off, uint32_t v) {
data[off] = static_cast<uint8_t>(v);
data[off + 1] = static_cast<uint8_t>(v >> 8);
data[off + 2] = static_cast<uint8_t>(v >> 16);
data[off + 3] = static_cast<uint8_t>(v >> 24);
};
w32(0x00, 0x1bb1b11b);
w32(0x04, 42);
w32(0x08, 4); // data_length
w32(0x0c, 1); // raw frame type
w32(0x10, 1000); // shutter
data[0x1c] = 0x34;
data[0x1d] = 0x12;
data[0x1e] = 0x78;
data[0x1f] = 0x56;
w32(0x1c + 4, 0x1bb1b11c);
mag160c::core::IrFrameHeader h{};
const uint16_t* pixels = nullptr;
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) == MAG160C_OK);
assert(h.marker == 0x1bb1b11b);
assert(h.frame_counter == 42);
assert(h.data_length == 4);
assert(h.frame_type == 1);
assert(h.period_shutter == 1000);
assert(pixels[0] == 0x1234);
assert(pixels[1] == 0x5678);
}
void test_parse_rejects_bad_marker() {
std::vector<uint8_t> data(0x40, 0);
data[0] = 0xab;
mag160c::core::IrFrameHeader h{};
const uint16_t* pixels = nullptr;
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) ==
MAG160C_ERR_PROTOCOL_UNKNOWN);
}
void test_parse_rejects_bad_type_and_trailer() {
std::vector<uint8_t> data(0x40, 0);
auto w32 = [&](size_t off, uint32_t v) {
data[off] = static_cast<uint8_t>(v);
data[off + 1] = static_cast<uint8_t>(v >> 8);
data[off + 2] = static_cast<uint8_t>(v >> 16);
data[off + 3] = static_cast<uint8_t>(v >> 24);
};
w32(0x00, 0x1bb1b11b);
w32(0x08, 4);
w32(0x0c, 2); // invalid type
mag160c::core::IrFrameHeader h{};
const uint16_t* pixels = nullptr;
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) ==
MAG160C_ERR_PROTOCOL_UNKNOWN);
w32(0x0c, 0);
w32(0x1c + 4, 0xdeadbeef); // bad trailing marker
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) ==
MAG160C_ERR_PROTOCOL_UNKNOWN);
}
void test_calibrate_flat_band() {
// 4 pixels, 2 bands (1 search threshold), threshold = {5} per pixel
const std::vector<uint16_t> frame{1000, 2000, 3000, 4000};
const std::vector<int16_t> thresholds{5, 5, 5, 5};
// per band per pixel {coeff, offset}: band0 = {0, 100}, band1 = {0, 200}
const std::vector<uint16_t> coeff{
0, 100, 0, 100, 0, 100, 0, 100,
0, 200, 0, 200, 0, 200, 0, 200,
};
mag160c::core::IrCalibrationTables tables{};
tables.thresholds = thresholds.data();
tables.coeff = coeff.data();
tables.pixel_count = 4;
tables.band_count = 2;
tables.baseline = nullptr;
tables.has_baseline = false;
std::vector<uint16_t> out(4);
mag160c::core::calibrate_frame(frame.data(), tables, out.data());
// diff = frame >> 1 = 500..2000, all > 5 → band 1 → offset 200
for (auto v : out) {
assert(v == 200);
}
}
void test_calibrate_interpolation_and_clamp() {
// 2 pixels, 2 bands, threshold = {500}
const std::vector<uint16_t> frame{2000, 0x8000};
const std::vector<uint16_t> baseline{0, 0x100};
const std::vector<int16_t> thresholds{500, 500};
// band0: {coeff=0x1000 (4096), offset=1000}; band1: {coeff=0x100, offset=2000}
const std::vector<uint16_t> coeff{
0x1000, 1000, 0x1000, 1000,
0x0100, 2000, 0x0100, 2000,
};
mag160c::core::IrCalibrationTables tables{};
tables.thresholds = thresholds.data();
tables.coeff = coeff.data();
tables.pixel_count = 2;
tables.band_count = 2;
tables.baseline = baseline.data();
tables.has_baseline = true;
std::vector<uint16_t> out(2);
mag160c::core::calibrate_frame(frame.data(), tables, out.data());
// pixel 0: diff = (2000-0)>>1 = 1000 > 500 → band1: 2000 + (1000*0x100 >> 12) = 2000 + 62
assert(out[0] == 2062);
// pixel 1: diff = (0x8000-0x100)>>1 = 0x3f80 (16256) > 500 → band1: 2000 + (16256*256 >> 12)
// = 2000 + 1016 = 3016
assert(out[1] == 3016);
}
void test_calibrate_negative_diff_zero() {
const std::vector<uint16_t> frame{100};
const std::vector<uint16_t> baseline{200};
const std::vector<int16_t> thresholds{5};
const std::vector<uint16_t> coeff{0x1000, 1000, 0x1000, 1000};
mag160c::core::IrCalibrationTables tables{};
tables.thresholds = thresholds.data();
tables.coeff = coeff.data();
tables.pixel_count = 1;
tables.band_count = 2;
tables.baseline = baseline.data();
tables.has_baseline = true;
std::vector<uint16_t> out(1);
mag160c::core::calibrate_frame(frame.data(), tables, out.data());
// diff = (int16)(100-200) >> 1 = -50; negative coeff*offset path clamps to >= 0:
// band0 (diff <= 5): 1000 + (-50 * 4096 >> 12) = 1000 - 50 = 950
assert(out[0] == 950);
}
void test_parse_rejects_truncated_frame() {
std::vector<uint8_t> data(0x38 + 4, 0);
auto w32 = [&](size_t off, uint32_t v) {
data[off] = static_cast<uint8_t>(v);
data[off + 1] = static_cast<uint8_t>(v >> 8);
data[off + 2] = static_cast<uint8_t>(v >> 16);
data[off + 3] = static_cast<uint8_t>(v >> 24);
};
w32(0x00, 0x1bb1b11b);
w32(0x08, 4);
mag160c::core::IrFrameHeader h{};
const uint16_t* pixels = nullptr;
data.resize(data.size() - 1); // truncate
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) ==
MAG160C_ERR_PROTOCOL_UNKNOWN);
}
} // namespace
int main() {
test_parse_valid_frame();
test_parse_rejects_bad_marker();
test_parse_rejects_bad_type_and_trailer();
test_parse_rejects_truncated_frame();
test_calibrate_flat_band();
test_calibrate_interpolation_and_clamp();
test_calibrate_negative_diff_zero();
return 0;
}
@@ -0,0 +1,46 @@
#include "mag160c/mag160c.h"
#include <cassert>
#include <cstring>
int main() {
assert(mag160c_ir_open_first(nullptr, nullptr) == MAG160C_ERR_INVALID_ARGUMENT);
assert(std::strstr(mag160c_last_error(), "ctx") != nullptr);
mag160c_ir_close(nullptr);
assert(mag160c_ir_get_info(nullptr, nullptr) == MAG160C_ERR_INVALID_ARGUMENT);
assert(std::strstr(mag160c_last_error(), "device") != nullptr);
mag160c_context_t* ctx = nullptr;
assert(mag160c_init(&ctx) == MAG160C_OK);
mag160c_ir_device_t* ir = nullptr;
assert(mag160c_ir_open_first(ctx, &ir) == MAG160C_OK);
assert(ir != nullptr);
mag160c_ir_info_t info{};
assert(mag160c_ir_get_info(ir, &info) == MAG160C_OK);
assert(info.width == 160);
assert(info.height == 120);
assert(info.output_width == 160);
assert(info.output_height == 120);
assert(info.max_fps == 25);
assert(info.current_fps == 0);
assert(std::strcmp(info.name, "MAG160C") == 0);
assert(std::strcmp(info.type, "vendor-bulk-ir") == 0);
assert(mag160c_ir_trigger_ffc(ir) == MAG160C_ERR_UNSUPPORTED);
assert(std::strstr(mag160c_last_error(), "0x6bb6b672") != nullptr);
assert(std::strstr(mag160c_last_error(), "0x03") != nullptr);
size_t raw_size = 123;
assert(mag160c_ir_read_raw_once(ir, nullptr, 0, &raw_size, 100) ==
MAG160C_ERR_UNSUPPORTED);
assert(raw_size == 0);
assert(std::strstr(mag160c_last_error(), "0x1bb1b11b") != nullptr);
mag160c_ir_close(ir);
mag160c_shutdown(ctx);
return 0;
}
@@ -0,0 +1,49 @@
#include "core/tcm_device.hpp"
#include <cassert>
#include <cstdint>
#include <vector>
namespace {
void test_rotate_positive_payload() {
mag160c::core::TcmCommandBuilder builder;
const std::vector<uint8_t> expected{
0x7e, 0x00, 0x07, 0x85, 0x02, 0x77, 0x00, 0x01, 0x00, 0x05, 0x7f};
const auto encoded = builder.build_rotate_frame(5);
assert(encoded == expected);
}
void test_rotate_negative_payload_and_frame_increment() {
mag160c::core::TcmCommandBuilder builder;
(void)builder.build_rotate_frame(5);
const std::vector<uint8_t> expected{
0x7e, 0x00, 0x07, 0x85, 0x02, 0x77, 0x00, 0x02, 0x01, 0x03, 0x7f};
const auto encoded = builder.build_rotate_frame(-3);
assert(encoded == expected);
}
void test_green_blink_payload() {
mag160c::core::TcmCommandBuilder builder;
const std::vector<uint8_t> expected{
0x7e, 0x00, 0x09, 0x87, 0x02, 0x32, 0x00, 0x01, 0x01, 0x00, 0xff, 0x00, 0x35};
const auto encoded = builder.build_light_frame(MAG160C_TCM_LIGHT_GREEN, MAG160C_TCM_LIGHT_BLINK);
assert(encoded == expected);
}
} // namespace
int main() {
test_rotate_positive_payload();
test_rotate_negative_payload_and_frame_increment();
test_green_blink_payload();
return 0;
}
@@ -0,0 +1,64 @@
#include "core/tcm_frame.hpp"
#include <cassert>
#include <cstdint>
#include <stdexcept>
#include <vector>
namespace {
void test_rotate_frame_encoding() {
const std::vector<uint8_t> payload{0x00, 0x05};
const std::vector<uint8_t> expected{
0x7e, 0x00, 0x07, 0x85, 0x02, 0x77, 0x00, 0x01, 0x00, 0x05, 0x7f};
const auto encoded = mag160c::core::encode_tcm_frame(0x02, 0x77, 0x0001, payload);
assert(encoded == expected);
}
void test_decode_rejects_bad_header_checksum() {
auto encoded = mag160c::core::encode_tcm_frame(0x02, 0x77, 0x0001, {0x00, 0x05});
encoded[3] ^= 0xff;
mag160c::core::TcmFrame decoded;
assert(mag160c::core::decode_tcm_frame(encoded.data(), encoded.size(), &decoded) ==
MAG160C_ERR_CHECKSUM);
}
void test_decode_roundtrip() {
const std::vector<uint8_t> payload{0x10, 0x20, 0x30};
const auto encoded = mag160c::core::encode_tcm_frame(0xa1, 0xb2, 0xc3d4, payload);
mag160c::core::TcmFrame decoded;
assert(mag160c::core::decode_tcm_frame(encoded.data(), encoded.size(), &decoded) == MAG160C_OK);
assert(decoded.main_cmd == 0xa1);
assert(decoded.sub_cmd == 0xb2);
assert(decoded.frame_id == 0xc3d4);
assert(decoded.payload == payload);
}
void test_encode_rejects_oversized_payload() {
const std::vector<uint8_t> payload(65531U, 0xaa);
bool threw_length_error = false;
try {
(void)mag160c::core::encode_tcm_frame(0x02, 0x77, 0x0001, payload);
} catch (const std::length_error&) {
threw_length_error = true;
}
assert(threw_length_error);
}
} // namespace
int main() {
test_rotate_frame_encoding();
test_decode_rejects_bad_header_checksum();
test_decode_roundtrip();
test_encode_rejects_oversized_payload();
return 0;
}
+232
View File
@@ -0,0 +1,232 @@
#include "mag160c/mag160c.h"
#include <cctype>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
namespace {
void print_usage() {
std::cout << "usage:\n"
<< " mag160c-cli probe\n"
<< " mag160c-cli tcm-rotate --dry-run ANGLE\n"
<< " mag160c-cli tcm-light --dry-run COLOR MODE\n"
<< " mag160c-cli ir-info\n";
}
std::string lower(std::string value) {
for (char& ch : value) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
}
return value;
}
void print_hex(const uint8_t* bytes, size_t size) {
std::ios old_state(nullptr);
old_state.copyfmt(std::cout);
for (size_t i = 0; i < size; ++i) {
if (i != 0) {
std::cout << ' ';
}
std::cout << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<unsigned int>(bytes[i]);
}
std::cout << '\n';
std::cout.copyfmt(old_state);
}
int parse_int(const char* text, int* out) {
if (text == nullptr || out == nullptr) {
return 0;
}
char* end = nullptr;
const long value = std::strtol(text, &end, 10);
if (end == text || *end != '\0') {
return 0;
}
*out = static_cast<int>(value);
return 1;
}
int command_tcm_rotate(int argc, char** argv) {
if (argc != 4 || std::string(argv[2]) != "--dry-run") {
print_usage();
return 1;
}
int angle = 0;
if (!parse_int(argv[3], &angle)) {
std::cerr << "invalid angle\n";
return 1;
}
uint8_t frame[64] = {};
size_t frame_size = 0;
const mag160c_error_t rc = mag160c_tcm_build_rotate_frame(
angle, frame, sizeof(frame), &frame_size
);
if (rc != MAG160C_OK) {
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
return 2;
}
print_hex(frame, frame_size);
return 0;
}
int parse_color(const std::string& value, mag160c_tcm_light_color_t* out) {
const std::string v = lower(value);
if (v == "off") { *out = MAG160C_TCM_LIGHT_OFF; return 1; }
if (v == "red") { *out = MAG160C_TCM_LIGHT_RED; return 1; }
if (v == "green") { *out = MAG160C_TCM_LIGHT_GREEN; return 1; }
if (v == "blue") { *out = MAG160C_TCM_LIGHT_BLUE; return 1; }
if (v == "yellow") { *out = MAG160C_TCM_LIGHT_YELLOW; return 1; }
return 0;
}
int parse_mode(const std::string& value, mag160c_tcm_light_mode_t* out) {
const std::string v = lower(value);
if (v == "steady") { *out = MAG160C_TCM_LIGHT_STEADY; return 1; }
if (v == "blink") { *out = MAG160C_TCM_LIGHT_BLINK; return 1; }
if (v == "breath") { *out = MAG160C_TCM_LIGHT_BREATH; return 1; }
return 0;
}
int command_tcm_light(int argc, char** argv) {
if (argc != 5 || std::string(argv[2]) != "--dry-run") {
print_usage();
return 1;
}
mag160c_tcm_light_color_t color = MAG160C_TCM_LIGHT_OFF;
mag160c_tcm_light_mode_t mode = MAG160C_TCM_LIGHT_STEADY;
if (!parse_color(argv[3], &color)) {
std::cerr << "invalid color\n";
return 1;
}
if (!parse_mode(argv[4], &mode)) {
std::cerr << "invalid mode\n";
return 1;
}
uint8_t frame[64] = {};
size_t frame_size = 0;
const mag160c_error_t rc = mag160c_tcm_build_light_frame(
color, mode, frame, sizeof(frame), &frame_size
);
if (rc != MAG160C_OK) {
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
return 2;
}
print_hex(frame, frame_size);
return 0;
}
int command_ir_info() {
mag160c_context_t* ctx = nullptr;
mag160c_ir_device_t* ir = nullptr;
mag160c_ir_info_t info{};
mag160c_error_t rc = mag160c_init(&ctx);
if (rc != MAG160C_OK) {
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
return 2;
}
rc = mag160c_ir_open_first(ctx, &ir);
if (rc == MAG160C_OK) {
rc = mag160c_ir_get_info(ir, &info);
}
if (rc != MAG160C_OK) {
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
mag160c_ir_close(ir);
mag160c_shutdown(ctx);
return 2;
}
std::cout << "name: " << info.name << '\n'
<< "type: " << info.type << '\n'
<< "size: " << info.width << "x" << info.height << '\n'
<< "output: " << info.output_width << "x" << info.output_height << '\n'
<< "protocol: vid 0x833c config=2 iface=0; cmd EP OUT 0x03 / IN 0x82;\n"
<< " stream EP IN 0x81 (marker 0x1bb1b11b, data at +0x1c, size 0x38+len);\n"
<< " cmds 0x6bb6b66b..0x6bb6b677 (start 0x6bb6b673, stop 0x6bb6b674,\n"
<< " ffc 0x6bb6b672); responses 0x5bb5b55b..0x5bb5b57b\n";
mag160c_ir_close(ir);
mag160c_shutdown(ctx);
return 0;
}
int command_probe() {
mag160c_context_t* ctx = nullptr;
mag160c_error_t rc = mag160c_init(&ctx);
if (rc != MAG160C_OK) {
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
return 2;
}
mag160c_device_info_t* devices = nullptr;
size_t count = 0;
rc = mag160c_list_devices(ctx, &devices, &count);
if (rc != MAG160C_OK) {
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
mag160c_shutdown(ctx);
return 2;
}
std::cout << "devices: " << count << '\n';
std::ios old_state(nullptr);
old_state.copyfmt(std::cout);
for (size_t i = 0; i < count; ++i) {
const mag160c_device_info_t& d = devices[i];
std::cout << "[" << i << "] vid:pid "
<< std::hex << std::setfill('0') << std::setw(4) << d.vendor_id
<< ':' << std::setw(4) << d.product_id
<< std::dec << " bus " << static_cast<unsigned int>(d.bus)
<< " address " << static_cast<unsigned int>(d.address)
<< " interface " << static_cast<unsigned int>(d.interface_number)
<< " bulk-in 0x" << std::hex << static_cast<unsigned int>(d.bulk_in_endpoint)
<< " bulk-out 0x" << static_cast<unsigned int>(d.bulk_out_endpoint)
<< '\n';
}
std::cout.copyfmt(old_state);
mag160c_free_device_list(devices);
mag160c_shutdown(ctx);
return 0;
}
} // namespace
int main(int argc, char** argv) {
if (argc < 2) {
print_usage();
return 1;
}
const std::string command = argv[1];
if (command == "probe") {
return command_probe();
}
if (command == "tcm-rotate") {
return command_tcm_rotate(argc, argv);
}
if (command == "tcm-light") {
return command_tcm_light(argc, argv);
}
if (command == "ir-info") {
return command_ir_info();
}
print_usage();
return 1;
}
Binary file not shown.
+646
View File
@@ -0,0 +1,646 @@
51
70
94
125
162
208
264
331
410
503
612
737
881
1045
1231
1440
1675
1937
2227
2547
2899
3285
3705
4162
4658
5192
5768
6386
7047
7754
8506
9305
10153
11050
11997
12995
14045
15147
16303
17513
18778
20098
21473
22905
24393
25938
27540
29199
30916
32690
34522
36412
38360
40366
42430
44552
46731
48968
51262
53613
56022
58487
61008
63586
66220
68910
71654
74454
77308
80217
83179
86195
89263
92385
95558
98783
102059
105387
108764
112191
115668
119194
122768
126391
130060
133777
137541
141350
145205
149105
153050
157039
161072
165147
169266
173426
177629
181873
186157
190482
194847
199251
203694
208175
212695
217252
221847
226478
231145
235849
240587
245361
250169
255012
259888
264797
269740
274715
279722
284761
289831
294933
300064
305227
310419
315640
320891
326170
331478
336814
342178
347569
352988
358433
363904
369402
374925
380474
386048
391647
397271
402919
408591
414286
420005
425747
431512
437300
443109
448941
454795
460670
466566
472484
478422
484381
490360
496359
502378
508416
514474
520551
526647
532761
538894
545045
551215
557402
563606
569828
576068
582324
588597
594887
601193
607516
613854
620209
626579
632965
639366
645782
652213
658660
665120
671596
678086
684589
691108
697639
704185
710744
717317
723903
730502
737114
743740
750377
757028
763691
770366
777053
783753
790464
797187
803922
810669
817427
824196
830977
837768
844571
851384
858209
865043
871889
878745
885611
892487
899374
906270
913177
920093
927019
933954
940899
947854
954818
961791
968773
975764
982764
989773
996791
1003818
1010853
1017897
1024949
1032009
1039078
1046155
1053240
1060333
1067435
1074544
1081661
1088785
1095918
1103058
1110205
1117360
1124523
1131692
1138869
1146053
1153245
1160443
1167648
1174860
1182080
1189305
1196538
1203777
1211023
1218276
1225535
1232800
1240072
1247350
1254635
1261925
1269222
1276525
1283834
1291149
1298470
1305797
1313129
1320468
1327812
1335162
1342517
1349878
1357245
1364617
1371995
1379378
1386766
1394160
1401559
1408963
1416372
1423786
1431206
1438631
1446060
1453495
1460934
1468379
1475828
1483282
1490741
1498204
1505672
1513145
1520622
1528104
1535591
1543082
1550578
1558077
1565582
1573090
1580603
1588121
1595642
1603168
1610698
1618232
1625770
1633312
1640859
1648409
1655963
1663522
1671084
1678650
1686220
1693794
1701371
1708953
1716538
1724127
1731719
1739315
1746915
1754519
1762126
1769736
1777350
1784968
1792589
1800214
1807842
1815473
1823108
1830746
1838387
1846032
1853680
1861331
1868986
1876643
1884304
1891968
1899635
1907306
1914979
1922655
1930335
1938017
1945703
1953391
1961082
1968777
1976474
1984174
1991877
1999583
2007292
2015003
2022718
2030435
2038155
2045877
2053602
2061330
2069061
2076794
2084530
2092269
2100010
2107754
2115500
2123249
2131001
2138755
2146511
2154270
2162031
2169795
2177562
2185330
2193101
2200875
2208651
2216429
2224210
2231993
2239778
2247565
2255355
2263147
2270941
2278738
2286537
2294338
2302141
2309946
2317754
2325563
2333375
2341189
2349005
2356823
2364643
2372465
2380290
2388116
2395944
2403775
2411607
2419441
2427278
2435116
2442956
2450798
2458642
2466488
2474336
2482186
2490038
2497891
2505747
2513604
2521463
2529324
2537187
2545051
2552918
2560786
2568656
2576527
2584401
2592276
2600153
2608031
2615911
2623793
2631677
2639562
2647449
2655338
2663228
2671120
2679014
2686909
2694806
2702704
2710604
2718505
2726408
2734313
2742219
2750127
2758036
2765947
2773859
2781773
2789688
2797605
2805524
2813443
2821364
2829287
2837211
2845137
2853064
2860992
2868922
2876853
2884786
2892720
2900655
2908592
2916530
2924470
2932411
2940353
2948296
2956241
2964187
2972135
2980084
2988034
2995985
3003938
3011892
3019847
3027803
3035761
3043720
3051680
3059642
3067605
3075569
3083534
3091500
3099468
3107436
3115406
3123378
3131350
3139323
3147298
3155274
3163251
3171229
3179208
3187189
3195170
3203153
3211137
3219121
3227107
3235095
3243083
3251072
3259062
3267054
3275046
3283040
3291035
3299030
3307027
3315025
3323024
3331023
3339024
3347026
3355029
3363033
3371038
3379044
3387051
3395059
3403068
3411078
3419089
3427101
3435113
3443127
3451142
3459158
3467174
3475192
3483211
3491230
3499250
3507272
3515294
3523317
3531341
3539366
3547392
3555419
3563447
3571475
3579505
3587535
3595566
3603598
3611631
3619665
3627700
3635735
3643772
3651809
3659847
3667886
3675926
3683966
3692008
3700050
3708093
3716137
3724182
3732227
3740273
3748321
3756368
3764417
3772467
3780517
3788568
3796620
3804672
3812726
3820780
3828835
3836891
3844947
3853004
3861062
3869121
3877180
3885240
3893301
3901363
3909425
3917488
3925552
3933617
3941682
3949748
3957814
3965882
3973950
3982019
3990088
3998158
4006229
4014301
4022373
4030446
4038519
4046594
4054669
4062744
4070820
4078897
4086975
4095053
4103132
4111212
4119292
4127373
4135454
4143536
4151619
4159703
4167787
4175871
4183957
4192042
4200129

Some files were not shown because too many files have changed in this diff Show More