1. celex相机事件的极性是通过计算得到的,和像素点的上一个灰度值(准确来说是analog to digital converter(ADC)值)比较得到
// src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5dataprocessor.cpp line:2700
//--- cal polarity ---
if (adc12bit > m_pLastADC[index])
eventData.polarity = 1;
else if (adc12bit < m_pLastADC[index])
eventData.polarity = -1;
else
eventData.polarity = 0;
2. open usb 追踪
2.1 类CeleX5ROSBean的成员调用CeleX5的openSensor函数
// src/CeleX5-ROS/celex5_ros/src/bean/celex5_ros_bean.cpp
// void celex5_ros::CeleX5ROSBean::Run()
p_celex5_sensor_->openSensor(static_cast<CeleX5::DeviceType>(device_type));
// std::shared_ptr<CeleX5> p_celex5_sensor_;
2.2 类CeleX5的成员调用CeleDriver的openUSB函数
// src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5.cpp
bool CeleX5::openSensor(DeviceType type)
if (!m_pCeleDriver->openUSB())
// CeleDriver* m_pCeleDriver;
2.3 类CeleDriver的成员调用Cypress的openUSB函数
// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/celedriver.cpp
bool CeleDriver::openUSB()
{
return m_pCypress->openUSB();
}
// Cypress* m_pCypress;
2.4 类Cypress继承了USBInterface,因此也继承了USBInterface的公有函数usbOpen
// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/cypress.cpp
bool Cypress::openUSB(void)
{
if (usbOpen(CYPRESS_DEVICE_VENDOR_ID, CYPRESS_DEVICE_PRODUCT_ID, LIBUSB_TRANSFER_TYPE_BULK))
{
return true;
}
usbClose();
return false;
}
// class Cypress : public USBInterface
2.5 usbOpen主要用来打开usb设备,并获得usb设备的句柄:m_pDeviceHandle和视频末端地址:m_iVideoEndpointAddress,这两个变量很重要!!!
// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/usbinterface.cpp
// 只考虑对后面代码有用的关键变量
bool USBInterface::usbOpen(int vid, int pid, int transMode)
if (usbGetInterface(vid, pid, transMode) == true)
// 如果打开成功:会得到device handle libusb_device_handle* m_pDeviceHandle;
m_pDeviceHandle = libusb_open_device_with_vid_pid(nullptr, vid, pid);
2.6 打开usb成功,获得关键变量之后,返回到CeleX5的openSensor函数,开始配置usb
// src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5.cpp
bool CeleX5::openSensor(DeviceType type)
m_emDeviceType = type;
if (!m_pCeleDriver->openUSB()) // 如果打开usb成功,才有下文,否则返回false
if (CeleX5::CeleX5_MIPI == type) // 当前就是这个type
if (!configureSettings(type)) return false; // 配置设置
setISOLevel(m_uiISOLevel);
//m_pDataProcessor->setISOLevel(m_uiISOLevel);
clearData();
m_pDataProcessThread->start();
2.7 在配置结束之后,configureSettings函数有个很关键的步骤,开启视频流
bool CeleX5::configureSettings(CeleX5::DeviceType type)
if (m_pCeleDriver) m_pCeleDriver->openStream(); // 开启视频流
// 配置结束之后开启视频流
2.8 openStream这个函数最终是Cypress的函数,调用了继承来自USBInterface的start函数
// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/cypress.cpp
bool Cypress::openStream(void)
{
//开启视频流
uint8_t video_start[] = { 0x00, 0x00,0x01,0x02,0x0A,0x8B,0x02,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x20,0x1C,0x00, 0x00,0x90,0x00, 0x00 };
//usb_control(0x41, 0x99, 0x00, 0x00, nullptr, 0);
if (usbControl(0x21, 0x01, 0x201, 0x0, video_start, sizeof(video_start)))
{
if (start() == true) //关键这个start!!!!!!!
{
return true;
}
usbControl(0x41, 0x88, 0x00, 0x00, nullptr, 0);
}
return false;
}
2.9 USBInterface的start函数有两个关键调用videoStart和usbAllocBulkTransfer,因为不用imu,因此暂时不考虑usbAllocInterruptTransfer
- videoStart开启了一个分离线程,专门检测usb的数据传输是否失败,失败则关闭然后重启,videoStop可以结束这个线程
- usbAllocBulkTransfer最终设置了接受到数据的回调函数callbackUSBTransferCompleted,获得原始数据buffer和actual_length,这里就是最原始的数据buffer来源!!!!
- callbackUSBTransferCompleted传输正常的话会调用generate_image将buffer转化为其他数据类型!!
// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/usbinterface.cpp
bool USBInterface::start(void)
{
if (videoStart() == true) // 这个也是关键!!!!
{
bool bSucceed1 = false, bSucceed2 = false;
if (usbAllocBulkTransfer() == true)
{
bSucceed1 = true;
printf("usb_alloc_bulk_transfer was successful!\n");
//return true;
}
//added by xiaoqin @2019.06.11 for receiving IMU data
if (g_bUsingIMUCallback)
{
if (usbAllocInterruptTransfer() == true)
{
bSucceed2 = true;
printf("usb_alloc_interrupt_transfer was successful!\n");
//return true;
}
}
if (bSucceed1)
{
return true;
}
videoStop();
}
return false;
}
// videoStart()开启了一个后台线程!!!!
/*
* @function: videoStart
* @brief : start USB transfer thread
*/
bool USBInterface::videoStart(void)
{
m_bRunning = true;
m_threadProcessor = std::thread(&USBInterface::worker, this);
m_threadProcessor.detach();
// detach()函数
// 称为分离线程函数,使用detach()函数会让线程在后台运行,即说明主线程不会等待子线程运行结束才结束
// 通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,没有任何显式的用户接口,并在后台运行的线程。
// 这种线程的特点就是长时间运行;线程的生命周期可能会从某一个应用起始到结束
return true;
}
3. 追踪void generate_image(uint8_t *buffer, int length)
在生成数据之后,首先调用的就是这个函数,这个函数是对g_PackageList包数组的循环填充,usb传递过来的数据是一个一个的包,这个函数将包一个一个放入g_PackageList中,g_PackageList是一个类数组,类的成员有m_pPackageBuffer,专门用来存放包数据。g_uiTailIndex表示将要填充的位置,填充完成之后会指向下一个位置,因此只要不读取g_uiTailIndex指向的位置,就不会发生数据冲突!!!!g_uiPackageCount描述未读的包数量,g_uiHeadIndex指向下一个要读的包位置,包读取完成之后才会指向下一个位置,或者未读的包数量超过最大之后,因为当未读的包数量达到最大时,g_uiTailIndex和g_uiHeadIndex相差1个单位,也就是g_uiTailIndex当前指向的读取位置。
void generate_image(uint8_t *buffer, int length)
{
if (current_package == nullptr) // current_package这玩意儿只是个指针
{
current_package = &g_PackageList[g_uiTailIndex]; // 指向数据末尾地址
// CPackage g_PackageList[MAX_IMAGE_BUFFER_NUMBER];
//ofTest << "------- " << g_uiTailIndex << std::endl;
current_package->clearData(); // 状态设置为空,偏置为0 m_emStatus = BUFFER_STATUS_EMPTY; m_uiOffset = 0;
// 这里的clearData只是针对末尾的这个数据,而不是整个g_PackageList数组!!!!
if (g_uiPackageCount >= MAX_IMAGE_BUFFER_NUMBER) // 先假设这个不发生,那就只是获取数组末端的指针和设置状态
{
g_uiPackageCount--;
g_uiHeadIndex++;
// 当不考虑读取导致的g_uiPackageCount变化时,进入新一轮数据加入后,数据会重新从0开始加入,也就是舍弃掉0位置的包
// 时间最早的数据index变为了1,因此g_uiHeadIndex++,同时g_uiPackageCount--
// g_uiPackageCount表示未读的包数量
if (g_uiHeadIndex >= MAX_IMAGE_BUFFER_NUMBER)
g_uiHeadIndex = 0;
printf("------- generate_image: buffer is full! -------\n");
//ofTest << "------- generate_image: buffer is full! -------" << std::endl;
}
}
//
if (current_package) // 当前的package
{
if (!g_bUsingIMUCallback) // 没有使用IMU,这里是false,不考虑
{
if (buffer[7] == 1)
{
IMURawData imu_data;
memcpy(imu_data.imuData, buffer + 8, 20);
imu_data.timestamp = getTimeStamp();
current_package->m_vecIMUData.push_back(imu_data);
}
}
if (buffer[1] & 0x02) // 这是什么标志位??表示 Package 的类型(ID)
{
current_package->m_lTimestampEnd = getTimeStamp(); // 获取时间戳
//cout << "------------ image time stamp = " << current_package->m_lTime_Stamp_End << endl;
buffer[length] = *(buffer + 6);
current_package->insert(buffer + buffer[0], length - buffer[0] + 1);
// 将buffer保存到这个m_pPackageBuffer = new uint8_t[MAX_ELEMENT_BUFFER_SIZE * 50];
//current_package->Insert(buffer + 6, 1);
current_package->end(); // m_emStatus = BUFFER_STATUS_FULL; m_uiOffset描述m_pPackageBuffer的偏置offset
current_package = nullptr; // 重新指向空
g_uiTailIndex++;
// g_uiTailIndex的值基本只在这里赋予,除了clearData
// g_PackageList这个数组里循环填充
if (g_uiTailIndex >= MAX_IMAGE_BUFFER_NUMBER)
g_uiTailIndex = 0;
g_uiPackageCount++;
}
else // 没有标志位的话就是package没有读取结束
{
current_package->insert(buffer + buffer[0], length - buffer[0]);
}
}
}
4. 数据的从usb获取后就存放在g_PackageList中,接下来考虑其他程序的读取。从g_PackageList开始反推
4.1 g_PackageList只在以下getSensorData函数中使用
bool CeleDriver::getSensorData(uint8_t* pData, uint32_t& length)
bool CeleDriver::getSensorData(uint8_t* pData, uint32_t& length, std::time_t& timestampEnd, std::vector<IMURawData>& imuData)
4.1.1 首先来看第一个getSensorData函数:
bool CeleDriver::getSensorData(uint8_t* pData, uint32_t& length)
{
// 不读时间戳,也不读imu数据
if (g_uiPackageCount > 0) // 未读包数量
{
g_mtxSensorData.lock(); // 线程锁起到保护g_IMURawDataList的作用,但是目前没有使用这个数据。。。。。。
if (g_uiHeadIndex != g_uiTailIndex) // 太机智了!!!!!!!!避免了数据冲突!!!!牛逼!!!
{
g_PackageList[g_uiHeadIndex].getData(pData, length);
// 将包中的m_pPackageBuffer全部读入pData,length就是偏置,之后将包的偏置设置为0,状态为空,防止重复读取
if (g_bUsingIMUCallback)
{
g_IMURawDataList.clear();
}
g_PackageList[g_uiHeadIndex].m_vecIMUData.clear(); // 没有用imu,不考虑
g_uiHeadIndex++;
if (g_uiHeadIndex >= MAX_IMAGE_BUFFER_NUMBER)
g_uiHeadIndex = 0;
g_uiPackageCount--;
if (length > 0)
{
g_mtxSensorData.unlock();
return true;
}
}
else
{}
g_mtxSensorData.unlock();
}
}
4.1.2 首先来看第二个getSensorData函数:
只是加了时间戳和IMU数据的读取而已!没啥变化!
bool CeleDriver::getSensorData(uint8_t* pData, uint32_t& length, std::time_t& timestampEnd, std::vector<IMURawData>& imuData)
{
if (g_uiPackageCount > 0)
{
g_mtxSensorData.lock();
if (g_uiHeadIndex != g_uiTailIndex)
{
g_PackageList[g_uiHeadIndex].getData(pData, length);
timestampEnd = g_PackageList[g_uiHeadIndex].m_lTimestampEnd;
if (g_bUsingIMUCallback)
{
imuData = g_IMURawDataList;
g_IMURawDataList.clear();
}
else
{
imuData = g_PackageList[g_uiHeadIndex].m_vecIMUData;
}
g_PackageList[g_uiHeadIndex].m_vecIMUData.clear();
g_uiHeadIndex++;
if (g_uiHeadIndex >= MAX_IMAGE_BUFFER_NUMBER)
g_uiHeadIndex = 0;
g_uiPackageCount--;
if (length > 0)
{
g_mtxSensorData.unlock();
return true;
}
}
else
{ }
g_mtxSensorData.unlock();
}
return false;
}
4.3 getSensorData被getCeleXRawData函数调用
void CeleX5::getCeleXRawData(uint8_t* pData, uint32_t& length)
void CeleX5::getCeleXRawData(uint8_t* pData, uint32_t& length, std::time_t& timestampEnd, std::vector<IMURawData>& imu_data)
4.3.1 考虑第一个函数,就是单纯调用一下,没其他处理
void CeleX5::getCeleXRawData(uint8_t* pData, uint32_t& length)
{
if (CeleX5::CeleX5_MIPI != m_emDeviceType)
{
return;
}
m_pCeleDriver->getSensorData(pData, length);
}
4.3.2 考虑第二个函数,因为不用imu数据,不考虑写入bin,只考虑前三种模式,因此和第一个没什么差别
void CeleX5::getCeleXRawData(uint8_t* pData, uint32_t& length, std::time_t& timestampEnd, std::vector<IMURawData>& imu_data)
{
if (CeleX5::CeleX5_MIPI != m_emDeviceType)
{
return;
}
std::vector<IMURawData> imu_raw_data;
// pData, length, timestampEnd, imu_raw_data 都是通过getSensorData获得数据,不是输入
if (m_pCeleDriver->getSensorData(pData, length, timestampEnd, imu_raw_data)) // 获取数据
{
imu_data = std::vector<IMURawData>(imu_raw_data.size()); // 不考虑imu
for (int i = 0; i < imu_raw_data.size(); i++)
{
//cout << "--------------"<<imu_raw_data[i].time_stamp << endl;
memcpy(imu_data[i].imuData, imu_raw_data[i].imuData, sizeof(imu_raw_data[i].imuData)); // 复制数据到imu_data
imu_data[i].timestamp = imu_raw_data[i].timestamp; // 获得时间戳
//imu_data[i] = ((struct IMURawData*)&imu_raw_data[i]);
}
//record sensor data
if (m_pDataRecorder->isRecording()) // 不考虑写入bin,可以考虑写入txt,或者其他!!!!
{
m_pDataRecorder->writeData(pData, length, timestampEnd, imu_data);
}
//calculate the package count per second
if (!m_bLoopModeEnabled && getSensorFixedMode() > 2)
{
m_uiPackageCounter++;
#ifdef _WIN32
uint32_t t2 = GetTickCount();
#else
uint32_t t2 = clock() / 1000;
#endif
m_uiPackageTDiff += (t2 - m_uiPackageBeginT);
m_uiPackageBeginT = t2;
if (m_uiPackageTDiff > 1000)
{
//cout << "--- package count = " << m_uiPackageCounter << endl;
m_pDataProcessor->getProcessedData()->setFullFrameFPS(m_uiPackageCountPS);
m_uiPackageTDiff = 0;
m_uiPackageCountPS = m_uiPackageCounter;
m_uiPackageCounter = 0;
}
}
}
}
4.4 getCeleXRawData被DataProcessThread线程中被调用
通过getCeleXRawData获得数据,之后用processMIPIData处理数据。
void DataProcessThread::run()
{
while (m_bRun)
{
//cout << "---------- DataProcessThread::run ----------" << endl;
#ifndef _WIN32
pthread_mutex_lock(&m_mutex); // //这个mutex主要是用来保证pthread_cond_wait的并发性
// //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何这里要有一个while (m_bSuspended)呢?
// 因为pthread_cond_wait里的线程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。
// 这个时候,应该让线程继续进入pthread_cond_wait
while (m_bSuspended)
{
pthread_cond_wait(&m_cond, &m_mutex);
}
// pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,然后阻塞在等待对列里休眠,
// 直到再次被唤醒(大多数情况下是等待的条件成立而被唤醒,唤醒后,
// 该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
pthread_mutex_unlock(&m_mutex); // 在这就释放了?????
#endif
if (m_bPlaybackBinFile) //for playback
{
}
else //--- for real display ---
{
std::time_t time_stamp_end = 0;
std::vector<IMURawData> imu_data;
uint32_t dataLen = 0;
m_pCeleX5->getCeleXRawData(m_pMipiPackage, dataLen, time_stamp_end, imu_data);
// 获取原始数据,形参都是返回的数据
if (dataLen > 0)
{
if (!m_bRecordData || (m_bRecordData && m_bShowImagesEnabled))
m_pDataProcessor->processMIPIData(m_pMipiPackage, dataLen, time_stamp_end, imu_data); // 数据处理
}
}
}
}
4.4.1 processMIPIData数据处理函数
void CeleX5DataProcessor::processMIPIData(uint8_t* pData, uint32_t dataSize, std::time_t time_stamp_end, std::vector<IMURawData>& imuData)
{
int size = imuData.size();
for (int i = 0; i < size; i++) // 不考虑imu
{
m_vectorIMURawData.push_back(imuData[i]);
}
if (*(pData + dataSize - 1) == 0 /*|| dataSize > 500000*/) //full pic mode: package size = 1536001 = 1280 * 800 * 1.5 + 1
{ }
else
{
if (m_iMIPIDataFormat == 0)// Format0 (CSR_73 = 0), Package Size: 1024*200*1.5 = 307200 Byte
{
parseEventDataFormat0(pData, dataSize, time_stamp_end);
}
else if (m_iMIPIDataFormat == 1)// Format1 (CSR_73 = 1), Package Size: 1024*200*1.75 = 358400 Byte
{
parseEventDataFormat1(pData, dataSize, time_stamp_end);
}
// 当前使用2
else if (m_iMIPIDataFormat == 2)// Format2 (CSR_73 = 2), Package Size: 1024*200*1.75 = 358400 Byte
{
parseEventDataFormat2(pData, dataSize, time_stamp_end);
}
}
m_lLastPackageTimestamp = time_stamp_end;
}
4.4.1.1 parseEventDataFormat2数据处理函数
4.5 DataProcessThread线程类的实例m_pDataProcessThread在 CeleX5::openSensor开启线程
在usb配置好之后,线程就开启了
4.5.1 m_iMIPIDataFormat(1)改为1
尝试把src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5dataprocessor.cpp, m_iMIPIDataFormat(1)改为1
没发现问题!这里直接决定了数据处理的方式,在src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5dataprocessor.cpp的CeleX5DataProcessor::processMIPIData函数中数据的解析形式!
4.5.2 parseEventDataFormat1到图片生成
parseEventDataFormat1每生成一个数据,调用processMIPIEventTimestamp,processMIPIEventTimestamp每次调用checkIfShowImage,当达到一定的时间段之后,checkIfShowImage开始show图片bShowImage = true;调用创建图片函数:createImage(0);
4.5.3 默认固定模式改变出错
将src/CeleX5-ROS/celex5_ros/launch/celex5_ros_node.launch里的
改为2,就出现了下面的错误
CeleX5DataProcessor::parseEventDataFormat1: Not a full package: 355216
4.5.3.1 追踪这个问题!
- parseEventDataFormat1的package的来源:初步来源于m_pDataProcessor->processMIPIData(m_pMipiPackage, dataLen, time_stamp_end, imu_data)
- processMIPIData 的数据来源:线程DataProcessThread::run里的m_pCeleX5->getCeleXRawData(m_pMipiPackage, dataLen, time_stamp_end, imu_data);
- getCeleXRawData的数据来源:m_pCeleDriver->getSensorData(pData, length, timestampEnd, imu_raw_data)
- getSensorData数据来源:g_PackageList[g_uiHeadIndex].getData(pData, length);
- g_PackageList的数据只在这里生成:generate_image(uint8_t *buffer, int length),current_package->insert(buffer + buffer[0], length - buffer[0] + 1);除了这里涉及长度外,其他都没有处理,都是直接使用!
- generate_image函数被调用:callbackUSBTransferCompleted(libusb_transfer *xfr),而这个函数就是usb数据传递过来的回调函数。
- 错误对比:
m_uiOffset:42672
m_uiOffset:85344
m_uiOffset:128016
m_uiOffset:170688
m_uiOffset:213360
m_uiOffset:256032
m_uiOffset:298704
m_uiOffset:341376
m_uiOffset:355216
CeleX5DataProcessor::parseEventDataFormat1: Not a full package: 355216
m_uiOffset:42672
m_uiOffset:85344
m_uiOffset:128016
m_uiOffset:170688
m_uiOffset:213360
m_uiOffset:256032
m_uiOffset:298704
m_uiOffset:341376
m_uiOffset:357001
最后一个出错了 - 错误分析:由此看来应该是配置usb时候出现的问题,因为从数据生成之后的处理应该没啥问题。固定模式如何影响usb数据传输。。。。。。我草
4.5.3.2 追踪usb配置
- callbackUSBTransferCompleted设置:alloc_bulk_transfer(libusb_device_handle
*device_handle, uint8_t address, uint8_t *buffer) - alloc_bulk_transfer:USBInterface::usbAllocBulkTransfer(void)中alloc_bulk_transfer(m_pDeviceHandle, m_iVideoEndpointAddress, m_uiBulkBuffer[i]);
- 所以关键还是m_pDeviceHandle和m_iVideoEndpointAddress
- m_pDeviceHandle:在函数USBInterface::usbOpen(int vid, int pid, int transMode)中
USBInterface::usbOpen(int vid, int pid, int transMode)
{
if (libusb_init(nullptr) == LIBUSB_SUCCESS)
{
m_vecInterfaceNumberList.clear(); // std::vector<int> m_vecInterfaceNumberList;
if (usbGetInterface(vid, pid, transMode) == true)//m_iVideoEndpointAddress
{
m_pDeviceHandle = libusb_open_device_with_vid_pid(nullptr, vid, pid);
- 但是usbOpen的参数都是默认的。。。。。
- 尝试其他模式1
m_uiOffset:42672
m_uiOffset:85344
m_uiOffset:128016
m_uiOffset:170688
m_uiOffset:213360
m_uiOffset:256032
m_uiOffset:298704
m_uiOffset:341376
m_uiOffset:355216
CeleX5DataProcessor::parseEventDataFormat1: Not a full package: 355216
saveIntensityEvent
CeleX5::setSensorFixedMode(CeleX5Mode mode)
m_iEventDataFormat = 1;
wireIn(EVENT_PACKET_SELECT, m_iEventDataFormat, 0xFF); //EVENT_PACKET_SELECT
m_pDataProcessor->setMIPIDataFormat(m_iEventDataFormat);
writeRegister(MIPI_ROW_NUM_EVENT_H, -1, MIPI_ROW_NUM_EVENT_L, 400); //
CeleX5DataProcessor::parseEventDataFormat1: Not a full package: 712216
好久没来了,继续作死,这次多删除一点东西!
1. 固定模式切换
直接从src/CeleX5-ROS/celex5_ros/launch/celex5_ros_node.launch里修改fixed_mode为2的话会失败
通过Event_Intensity_Mode获得的灰度也只是事件位置处的灰度,不是整张照片的灰度!!!
2. 追踪full pic生成
- cv::Mat full_frame_img = p_celex5_sensor_->getFullPicMat();获取full pic
- memcpy(buffer, m_pFullPic[s_uiReadFrameIndex], CELEX5_PIXELS_NUMBER);m_pFullPic转化
- uint8_t* pFullPic = m_pFullPic[s_uiWriteFrameIndex];m_pFullPic生成bool CeleX5DataProcessor::createImage(std::time_t timestampEnd),所以图片都是在这里生成的!!!
- 看createImage被什么调用void CeleX5DataProcessor::checkIfShowImage()、void CeleX5DataProcessor::processFullPicData(uint8_t* pData, int dataSize, std::time_t timestampEnd)
- checkIfShowImage 只被processMIPIEventTimestamp调用,所以这里生成的应该是事件图片,而不是full pic,所以应该是processFullPicData生成的full pic
- processFullPicData只被void CeleX5DataProcessor::processMIPIData调用
- processMIPIData只被void DataProcessThread::run()调用