// Copyright (c) 2012-2017 VideoStitch SAS // Copyright (c) 2018 stitchEm #include "ximea_reader.hpp" #include "ximea_unpack.hpp" #include "libvideostitch/gpu_cuda.hpp" #include "libvideostitch/logging.hpp" #include "libvideostitch/parse.hpp" #include <algorithm> using namespace VideoStitch; using namespace Input; namespace { // Sensor constants: see xiB_QuickStartGuide section "6.4 Bandwitch control". const int sensorWidth = 5120; const int sensorHeight = 3840; const int bitsPerPixels = 12; const double marginError = 0.99; const int defaultChannels = 16; // Number of channels employed const int halfDefaultChannels = 8; const int defaultSensorOperatingFrequency = 480, minSensorOperatingFrequency = 120, maxSensorOperatingFrequency = 480; // MHz (bit frequency of one data channel from sensor) // See xiB_QuickStartGuide section "6.5 Gain and Black Level Offset". These parameters are sensor specific. They should // be based on sensor calibration data. const int defaultGain = 43, minGain = 1, maxGain = 63; const int defaultBlackLevelOffset = -55; // See acquisition.cpp in the xiApiViewver project const int headerSize = 512; const int imageHeaderSize = headerSize * 8; std::string xiReturnToString(const xi_return_e e) { switch (e) { case XI_OK: return "Function call succeeded"; case XI_INVALID_HANDLE: return "Invalid handle"; case XI_READREG: return "Register read error"; case XI_WRITEREG: return "Register write error"; case XI_FREE_RESOURCES: return "Freeing resources error"; case XI_FREE_CHANNEL: return "Freeing channel error"; case XI_FREE_BANDWIDTH: return "Freeing bandwidth error"; case XI_READBLK: return "Read block error"; case XI_WRITEBLK: return "Write block error"; case XI_NO_IMAGE: return "No image"; case XI_TIMEOUT: return "Timeout"; case XI_INVALID_ARG: return "Invalid arguments supplied"; case XI_NOT_SUPPORTED: return "Not supported"; case XI_ISOCH_ATTACH_BUFFERS: return "Attach buffers error"; case XI_GET_OVERLAPPED_RESULT: return "Overlapped result"; case XI_MEMORY_ALLOCATION: return "Memory allocation error"; case XI_DLLCONTEXTISNULL: return "DLL context is NULL"; case XI_DLLCONTEXTISNONZERO: return "DLL context is non zero"; case XI_DLLCONTEXTEXIST: return "DLL context exists"; case XI_TOOMANYDEVICES: return "Too many devices connected"; case XI_ERRORCAMCONTEXT: return "Camera context error"; case XI_UNKNOWN_HARDWARE: return "Unknown hardware"; case XI_INVALID_TM_FILE: return "Invalid TM file"; case XI_INVALID_TM_TAG: return "Invalid TM tag"; case XI_INCOMPLETE_TM: return "Incomplete TM"; case XI_BUS_RESET_FAILED: return "Bus reset error"; case XI_NOT_IMPLEMENTED: return "Not implemented"; case XI_SHADING_TOOBRIGHT: return "Shading too bright"; case XI_SHADING_TOODARK: return "Shading too dark"; case XI_TOO_LOW_GAIN: return "Gain is too low"; case XI_INVALID_BPL: return "Invalid bad pixel list"; case XI_BPL_REALLOC: return "Bad pixel list realloc error"; case XI_INVALID_PIXEL_LIST: return "Invalid pixel list"; case XI_INVALID_FFS: return "Invalid Flash File System"; case XI_INVALID_PROFILE: return "Invalid profile"; case XI_INVALID_CALIBRATION: return "Invalid calibration"; case XI_INVALID_BUFFER: return "Invalid buffer"; case XI_INVALID_DATA: return "Invalid data"; case XI_TGBUSY: return "Timing generator is busy"; case XI_IO_WRONG: return "Wrong operation open/write/read/close"; case XI_ACQUISITION_ALREADY_UP: return "Acquisition already started"; case XI_OLD_DRIVER_VERSION: return "Old version of device driver installed to the system"; case XI_GET_LAST_ERROR: return "To get error code please call GetLastError function"; case XI_CANT_PROCESS: return "Data can't be processed"; case XI_ACQUISITION_STOPED: return "Acquisition has been stopped It should be started before GetImage"; case XI_ACQUISITION_STOPED_WERR: return "Acquisition has been stopped with error"; case XI_INVALID_INPUT_ICC_PROFILE: return "Input ICC profile missed or corrupted"; case XI_INVALID_OUTPUT_ICC_PROFILE: return "Output ICC profile missed or corrupted"; case XI_DEVICE_NOT_READY: return "Device not ready to operate"; case XI_SHADING_TOOCONTRAST: return "Shading too contrast"; case XI_ALREADY_INITIALIZED: return "Mobile already initialized"; case XI_NOT_ENOUGH_PRIVILEGES: return "Application doesn't enough privileges (one or more app)"; case XI_NOT_COMPATIBLE_DRIVER: return "Installed driver not compatible with current software"; case XI_TM_INVALID_RESOURCE: return "TM file was not loaded successfully from resources"; case XI_DEVICE_HAS_BEEN_RESETED: return "Device has been reseted, abnormal initial state"; case XI_NO_DEVICES_FOUND: return "No Devices Found"; case XI_RESOURCE_OR_FUNCTION_LOCKED: return "Resource(device) or function locked by mutex"; case XI_UNKNOWN_PARAM: return "Unknown parameter"; case XI_WRONG_PARAM_VALUE: return "Wrong parameter value"; case XI_WRONG_PARAM_TYPE: return "Wrong parameter type"; case XI_WRONG_PARAM_SIZE: return "Wrong parameter size"; case XI_BUFFER_TOO_SMALL: return "Input buffer too small"; case XI_NOT_SUPPORTED_PARAM: return "Parameter info not supported"; case XI_NOT_SUPPORTED_PARAM_INFO: return "Parameter info not supported"; case XI_NOT_SUPPORTED_DATA_FORMAT: return "Data format not supported"; case XI_READ_ONLY_PARAM: return "Read only parameter"; case XI_WRITE_ONLY_PARAM: return "Write only parameter"; case XI_BANDWIDTH_NOT_SUPPORTED: return "This camera does not support currently available bandwidth"; case XI_RESOURCE_IN_USE: return "The resource is already in use"; case XI_EVENT_KILLED: return "Event waiting was terminated by kill"; case XI_LENS_NOT_DETECTED: return "Lens does not respond to Poll Busy command"; case XI_LENS_NOT_ENABLED: return "Lens was not enabled"; case XI_ERR_UNKNOWN_FILE: return "File or partition not found"; case XI_ERR_SIZE_OF_DATA_OVERFLOW: return "Size of data to be copied is larger than internal buffer (eg Set_FileAccessBuffer)"; case XI_ERR_FILE_NOT_SELECTED: return "No file is selected (FAL)"; case XI_ERR_FILE_ALREADY_OPENED: return "File is already open"; case XI_ERR_FILE_ALREADY_CLOSED: return "File is already closed"; case XI_ERR_FILE_INVALID_OPEN_MODE: return "File is opened in wrong mode for this operation"; case XI_ERR_FILE_INVALID_PARTITION: return "Partition table not found - file can not be used"; case XI_ERR_FILE_NOT_OPENED: return "File is not opened"; case XI_ERR_FILE_KEY_INVALID: return "File key is invalid"; case XI_ERR_FILE_IS_LOCKED_FOR_WRITE: return "File is locked for write"; case XI_ERR_LOCAL_FILE_OPEN_ERROR: return "Local file for read/write can not be open"; case XI_ERR_FEATURE_NOT_FOUND: return "Requested feature not found"; case XI_ERR_USERSET_NOT_FOUND: return "User set is not defined"; case XI_ERR_USERSET_PARSING_ERR: return "User set parsing finished with error"; case XI_ERR_USERSET_UNKNOWN_VAR_TYPE: return "User set value type is not implemented"; default: return "Unknown error"; } } } // namespace XimeaReader* XimeaReader::create(const Ptv::Value* config, const int64_t width, const int64_t height) { xi4Camera* device = nullptr; // Get the configuration std::string name; if (Parse::populateString("Ximea reader", *config, "name", name, true) != VideoStitch::Parse::PopulateResult_Ok) { Logger::get(Logger::Error) << "Error: Ximea device name couldn't be retrieved. Please, give a device name (\"name\") to your input." << std::endl; goto shutdown; } double fps; if (Parse::populateDouble("Ximea reader", *config, "fps", fps, true) != VideoStitch::Parse::PopulateResult_Ok) { Logger::get(Logger::Error) << "Error: Ximea framerate couldn't be retrieved on the device " << name << ". Please, give a framerate (\"fps\") to your input." << std::endl; goto shutdown; } int exposure; if (Parse::populateInt("Ximea reader", *config, "exposure", exposure, true) != VideoStitch::Parse::PopulateResult_Ok) { Logger::get(Logger::Error) << "Error: Ximea exposure couldn't be retrieved on the device " << name << ". Please, give an exposure (\"exposure\" in microseconds) to your input." << std::endl; goto shutdown; } int channels = defaultChannels; if (Parse::populateInt("Ximea reader", *config, "channels", channels, false) == VideoStitch::Parse::PopulateResult_Ok) { Logger::get(Logger::Info) << "Error: Ximea PCIe used channels number (\"channels\") can be set on the device " << name << ": " << defaultChannels << " by default, " << halfDefaultChannels << " on slower systems." << std::endl; } else if (channels != defaultChannels && channels != halfDefaultChannels) { Logger::get(Logger::Error) << "Error: Ximea device channels number is not correct on the device " << name << ". Please, give a correct channels number (\"channels\"): " << defaultChannels << " by default, " << halfDefaultChannels << " on slower systems." << std::endl; goto shutdown; } double sensorOperatingFrequency = 0; if (Parse::populateDouble("Ximea reader", *config, "frequency", sensorOperatingFrequency, false) != VideoStitch::Parse::PopulateResult_Ok) { Logger::get(Logger::Info) << "Ximea device frequency (\"frequency\" in MHz) can be set on the device " << name << ". If it's not, the approximate maximum frequency will be used: calculated from " "\"fps\", \"width\", \"height\" and \"channels\". The default value is " << defaultSensorOperatingFrequency << " MHz." << std::endl; } else if (sensorOperatingFrequency <= 0) { Logger::get(Logger::Error) << "Error: Ximea sensor operating frequency is not correct on the device " << name << ". Please, give a sensor operating frequency (\"frequency\") larger than 0." << std::endl; goto shutdown; } int gain; if (Parse::populateInt("Ximea reader", *config, "gain", gain, false) == VideoStitch::Parse::PopulateResult_Ok) { Logger::get(Logger::Info) << "Error: Ximea device gain (\"gain\") can be set between " << minGain << " and " << maxGain << " on the device " << name << ". If it's not, the default value (" << defaultGain << ") will be used." << std::endl; gain = defaultGain; } else if (gain < minGain || gain > maxGain) { Logger::get(Logger::Error) << "Error: Ximea device gain is not correct on the device " << name << ". Please, give a correct gain (\"gain\") between " << minGain << " and " << maxGain << "." << std::endl; goto shutdown; } int blackLevelOffset = defaultBlackLevelOffset; if (Parse::populateInt("Ximea reader", *config, "black_level_offset", blackLevelOffset, false) != VideoStitch::Parse::PopulateResult_Ok) { Logger::get(Logger::Info) << "Ximea device black level offset (\"black_level_offset\") can be set on the device " << name << ". The default value (" << defaultBlackLevelOffset << ") will be used." << std::endl; } double lensFStop = 0.; Parse::PopulateResult lensFStopPopulate = Parse::populateDouble("Ximea reader", *config, "f-stop", lensFStop, false); // Get the device DWORD id, devicesCount = 0; xiGetNumberDevices(&devicesCount); if (devicesCount == 0) { Logger::get(Logger::Error) << "Error: Ximea could not list any device." << std::endl; goto shutdown; } for (id = 0; id < devicesCount; id++) { Logger::get(Logger::Info) << "Ximea: found the device " << name << "." << std::endl; if (std::stoi(name) == id) { Logger::get(Logger::Debug) << "Ximea: found the suitable device " << name << "." << std::endl; break; } } if (std::stoi(name) != id) { Logger::get(Logger::Error) << "Error: Ximea could not found any suitable device for " << name << "." << std::endl; goto shutdown; } // Check if the device is connected int serial; DWORD serial_s = 4; XI_PRM_TYPE serial_t = XI_PRM_TYPE::xiTypeInteger; xi_return_e r = xiGetDeviceInfo(id, XI_PRM_DEVICE_SERIAL_NUMBER, &serial, &serial_s, &serial_t); if (r != XI_OK || serial == -1) { Logger::get(Logger::Error) << "Error: Ximea could not retrieve the device " << name << ". Please check if the device is connected and available to capture." << std::endl; goto shutdown; } // Open the device try { device = new xi4Camera; device->Open(id, XI_DEVICE_OPEN_INDEX); } catch (xi_return_e& e) { Logger::get(Logger::Error) << "Error: Ximea could not open the device " << name << ". Returned code is " << e << " \"" << xiReturnToString(e) << "\"." << std::endl; goto shutdown; } // Configure bandwitch control try { double calculatedSensorOperatingFrequency = fps * width * height * bitsPerPixels * marginError / channels; calculatedSensorOperatingFrequency /= 1000000; // MHz calculatedSensorOperatingFrequency = (std::max)( minSensorOperatingFrequency, (std::min)(maxSensorOperatingFrequency, (int)calculatedSensorOperatingFrequency)); Logger::get(Logger::Info) << "Ximea: approximate maximum sensor operating frequency calculated on the device " << name << " is " << calculatedSensorOperatingFrequency << " MHz." << std::endl; if (sensorOperatingFrequency <= 0) { sensorOperatingFrequency = calculatedSensorOperatingFrequency; } device->SetParamFloat(XI_PRM_FREQ, (float)sensorOperatingFrequency); device->SetParamInt(XI_PRM_OUTPUT_MODE, (channels == halfDefaultChannels) ? 1 : 0); // For CMV20000 0 = 16 channels, 1 = 8 channels } catch (xi_return_e& e) { Logger::get(Logger::Error) << "Error: Ximea could not configure the bandwitch control (frequency and channels) of the device " << name << ". Returned code is " << e << " \"" << xiReturnToString(e) << "\"." << std::endl; goto shutdown; } // Set ROI try { const int offsetX = 0, offsetY = 0; device->SetParamInt(XI_PRM_OFFSET_X, 0); device->SetParamInt(XI_PRM_OFFSET_Y, 0); device->SetParamInt(XI_PRM_WIDTH, (int)width); device->SetParamInt(XI_PRM_HEIGHT, (int)height); Logger::get(Logger::Info) << "Ximea: set the origin of the Region Of Interest on the device " << name << " to (" << offsetX << "," << offsetY << ") and the image resolution to " << width << "x" << height << "." << std::endl; } catch (xi_return_e& e) { Logger::get(Logger::Error) << "Error: Ximea could not configure the Region Of Intereset on the device " << name << ". Returned code is " << e << " \"" << xiReturnToString(e) << "\"." << std::endl; goto shutdown; } // Set Gain, Black Level Offset and Exposure try { device->SetParamInt(XI_PRM_GAIN, gain); } catch (xi_return_e& e) { Logger::get(Logger::Error) << "Error: Ximea could not configure the gain of the device " << name << ". Returned code is " << e << " \"" << xiReturnToString(e) << "\"." << std::endl; goto shutdown; } try { device->SetParamInt(XI_PRM_BLACK_LEVEL_OFFSET, blackLevelOffset); } catch (xi_return_e& e) { Logger::get(Logger::Error) << "Error: Ximea could not configure the black level of the device " << name << ". Returned code is " << e << " \"" << xiReturnToString(e) << "\"." << std::endl; goto shutdown; } try { device->SetParamInt(XI_PRM_EXPOSURE, exposure); } catch (xi_return_e& e) { Logger::get(Logger::Error) << "Error: Ximea could not configure the exposure of the device " << name << ". Returned code is " << e << " \"" << xiReturnToString(e) << "\"." << std::endl; goto shutdown; } // Configure the lens f-stop aperture float minLensFStop, maxLensFStop; try { device->SetParamInt(XI_PRM_LC_ENABLE, 1); minLensFStop = device->GetParamFloat(XI_PRM_LC_FSTOP_MIN_VAL); maxLensFStop = device->GetParamFloat(XI_PRM_LC_FSTOP_MAX_VAL); } catch (xi_return_e& e) { Logger::get(Logger::Error) << "Error: Ximea could not configure the lens of the device " << name << ". Returned code is " << e << " \"" << xiReturnToString(e) << "\"." << std::endl; goto capture; } if (minLensFStop == 0 || maxLensFStop == 0) { // Values are set to 0 if incorrectly detected Logger::get(Logger::Error) << "Error: Ximea could not configure the lens of the device " << name << ": incorrect lens parameters detected." << std::endl; goto capture; } if (lensFStopPopulate != VideoStitch::Parse::PopulateResult_Ok) { Logger::get(Logger::Info) << "Ximea lens' f-stop value (\"f-stop\") can be set on the device " << name << ". The detected lens' f-stop range is between f/" << minLensFStop << " and f/" << maxLensFStop << ". The minimum value (f/" << minLensFStop << ") will be used." << std::endl; lensFStop = minLensFStop; } else if (lensFStop < minLensFStop || lensFStop > maxLensFStop) { Logger::get(Logger::Error) << "Ximea lens' f-stop value (\"f-stop\") is not correct on the device " << name << ". The detected lens' f-stop range on the device " << name << " is between f/" << minLensFStop << " and f/" << maxLensFStop << "." << std::endl; goto shutdown; } try { device->SetParamFloat(XI_PRM_LC_FSTOP_VAL, (float)lensFStop); Logger::get(Logger::Debug) << "Ximea lens' f-stop value set to f/" << device->GetParamFloat(XI_PRM_LC_FSTOP_VAL) << " on the device " << name << std::endl; } catch (xi_return_e& e) { Logger::get(Logger::Error) << "Error: Ximea could not configure the lens' f-stop value on the device " << name << ". Returned code is " << e << " \"" << xiReturnToString(e) << "\"." << std::endl; goto shutdown; } // Start capture capture: try { device->FlushQueue(XI_ACQ_QUEUE_ALL_DISCARD); device->BuffersAllocate(); device->BuffersQueue(); device->SetParamInt(XI_PRM_ACQUISITION_FRAME_COUNT, 0); device->StartAcquisition(); } catch (xi_return_e& e) { Logger::get(Logger::Error) << "Error: Ximea could not start the capture on the device " << name << ". Returned code is " << e << " \"" << xiReturnToString(e) << "\"." << std::endl; goto shutdown; } // Set capture parameters int timeout = (int)(1000 / fps); int64_t frameDataSize = (width * height * 3) / 2; // Expects mono 12 bits return new XimeaReader(width, height, fps, frameDataSize, name, device, timeout); shutdown: if (device) { delete device; } return nullptr; } XimeaReader::XimeaReader(const int64_t width, const int64_t height, double fps, int64_t frameDataSize, std::string name, xi4Camera* device, int timeout) : StatefulReader<unsigned char*>(width, height, frameDataSize, Unknown, fps, 0, VideoStitch::Input::Reader::NO_LAST_FRAME, NULL, 0, Audio::SamplingRate::SR_NONE, Audio::SamplingDepth::SD_NONE), name(name), device(device), timeout(timeout), currFrame(-1) {} XimeaReader::~XimeaReader() { std::lock_guard<std::mutex> lk(mutex); device->StopAcquisition(); device->FlushQueue(XI_ACQ_QUEUE_ALL_DISCARD); device->EventFlush(device->GetNewImageEventHandle()); device->BuffersDeAllocate(); device->CloseDevice(); delete device; } bool XimeaReader::handles(const Ptv::Value* config) { return config && config->has("type") && (config->has("type")->asString() == "ximea"); } Status XimeaReader::readFrame(int& frameId, unsigned char* data, Audio::Samples& /*audio*/) { std::lock_guard<std::mutex> lk(mutex); xi_return_e e = device->WaitForNextImage(timeout); if (e == XI_TIMEOUT) { return VideoStitch::Status::OK(); } if (e != XI_OK) { Logger::get(Logger::Error) << "Error: Ximea device " << name << " couldn't reads frame, code " << e << "." << "Lost frames: " << device->GetParamInt(XI_PRM_ACQUISITION_FRAME_LOST) << " underrun frames: " << device->GetParamInt(XI_PRM_ACQUISITION_FRAME_UNDERRUN) << "." << std::endl; return VideoStitch::Status(ReaderError); } EVENT_NEW_BUFFER* bufferInfo = device->GetNewImageEventBufferInfo(); memcpy(data, (unsigned char*)(bufferInfo->UserPointer) + imageHeaderSize, getFrameDataSize()); device->QueueBuffer(bufferInfo->BufferHandle); frameId = ++currFrame; return VideoStitch::Status::OK(); } Status XimeaReader::readFrameAudioOnly(Audio::Samples& /*audio*/) { return VideoStitch::Status::OK(); } Status XimeaReader::seekFrame(unsigned /*targetFrame*/) { return Status::OK(); } Status XimeaReader::unpackDevBuffer(const GPU::Buffer<uint32_t>& dst, const GPU::Buffer<const unsigned char>& src, GPU::Stream& stream) const { unsigned char* const* frameBuffer = getCurrentDeviceData(); const unsigned char* srcRaw = VideoStitch::GPU::getRawGPUPointer(src); cudaStream_t cudaStream = VideoStitch::GPU::getCudaStream(stream); unpackMono12p(*frameBuffer, srcRaw, getWidth(), getHeight(), cudaStream); VideoStitch::GPU::Buffer<const unsigned char>& frameBufferRef = VideoStitch::GPU::createBufferReference<const unsigned char>(*frameBuffer, getWidth() * getHeight()); Status unpackStatus = Reader::unpackDevBuffer(VideoStitch::Bayer_RGGB, dst, frameBufferRef, getSpec().width, getSpec().height, stream); VideoStitch::GPU::destroyBufferReference(frameBufferRef); return unpackStatus; } Status XimeaReader::perThreadInit() { unsigned char* frameBuffer; cudaMalloc((void**)&frameBuffer, getWidth() * getHeight()); cudaMemset(frameBuffer, 0, getWidth() * getHeight()); return setCurrentDeviceData(frameBuffer); } void XimeaReader::perThreadCleanup() { unsigned char* const* frameBuffer = getCurrentDeviceData(); cudaFree(*frameBuffer); StatefulBase::perThreadCleanup(); } int XimeaReader::getIntegerValue(const char* key, int defaultValue) const { if (strcmp(key, "exposure") == 0) { return device->GetParamInt(XI_PRM_EXPOSURE); } else { return defaultValue; } } void XimeaReader::setIntegerValue(const char* key, int value) { if (strcmp(key, "exposure") == 0) { device->SetParamInt(XI_PRM_EXPOSURE, value); } }