Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dedicated HID IO thread to prevent blocking by HIDAPIs slow processing of OutputReports #4585

Merged
merged 95 commits into from
Feb 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
42d5608
Fix performance issues by slow hid_write command of HIDAPI, which can…
JoergAtGithub Dec 27, 2021
4ebaec9
Renamed HidIo to HidIoThread
JoergAtGithub Dec 28, 2021
8399b46
Replaced DirectConnections by Mutex-Protected function calls
JoergAtGithub Jan 2, 2022
daf55d3
Added QUnused for timerEvent argument
JoergAtGithub Jan 2, 2022
3cd3c3c
Replaced QByteArray prepend function with more efficient method of ar…
JoergAtGithub Jan 4, 2022
f35c6db
Created local RuntimeLoggingCategories in HidIoThread, which improved…
JoergAtGithub Jan 5, 2022
600d02a
Separate class HidIoReport into dedicated files
JoergAtGithub Jan 5, 2022
c7dd3b9
Use thread-safe deep-copy for polled data to emit, and for return values
JoergAtGithub Jan 6, 2022
d6a3545
Renamed hidio.cpp/h to hidiothread.cpp/h to match the class name
JoergAtGithub Jan 6, 2022
7d605bb
Use QObjectsmember function disconnect to disconnect all connections …
JoergAtGithub Jan 6, 2022
bf93455
Renamed m_lastSentOutputReport to m_lastSentOutputReportData with des…
JoergAtGithub Jan 6, 2022
8aa0111
Removed redundant member initialization m_lastSentOutputReport()
JoergAtGithub Jan 6, 2022
5f27099
Use early return after hid_write fail for better readability
JoergAtGithub Jan 6, 2022
f9d12c2
Use early return were suitable
JoergAtGithub Jan 6, 2022
470e812
Changed m_pHidIoThread from pointer to std::unique_ptr
JoergAtGithub Jan 7, 2022
fd72f4c
m_pHidIoThread->stop() -> stopPollTimer() + quit()
JoergAtGithub Jan 7, 2022
acda37e
Added missing hid_close for error branch of hid_set_nonblocking in Hi…
JoergAtGithub Jan 7, 2022
60df533
Reordered initialization/deinitialization order, to ensure, that the …
JoergAtGithub Jan 7, 2022
0216e8b
Merge remote-tracking branch 'upstream/main' into hid_io_thread2
JoergAtGithub Jan 11, 2022
60a1e82
Explicitly destroy m_pHidIoThread object, in HidController::close()
JoergAtGithub Jan 15, 2022
8a14abe
Replaced C style cast by C++ style cast
JoergAtGithub Jan 15, 2022
6dea3e2
Set mPollTimerId to zero if poll timer is stopped from outside
JoergAtGithub Jan 16, 2022
b4db2c5
Added VERIFY_OR_DEBUG_ASSERT at various places, to verify that m_pHid…
JoergAtGithub Jan 16, 2022
91814ff
Use a shared_ptr for device_info instead of std::move. device_info is…
JoergAtGithub Jan 16, 2022
eefd54a
Use QStringLiteral instead of QString to concatenate thread name
JoergAtGithub Jan 16, 2022
58d91c4
Described why inhibiting sending identical OutputReport is safe. Same…
JoergAtGithub Jan 16, 2022
d204016
Added p prefix to pointer arguments
JoergAtGithub Jan 16, 2022
fcabfef
Replaced QueuedConnection for OutputReports by latchOutputReport
JoergAtGithub Jan 18, 2022
ba6336b
Added debug message to indicate skipped OutputReports
JoergAtGithub Jan 18, 2022
aa524a3
Replaced poll timer with run loop with usleep, which is controlled by…
JoergAtGithub Jan 19, 2022
963aa43
Pre-Commit fixes
JoergAtGithub Jan 19, 2022
eaf8a37
Clazy fixes
JoergAtGithub Jan 19, 2022
c6d2730
Moved pDevice into HidIoThread class - it can no longer outlive the c…
JoergAtGithub Jan 20, 2022
f95e682
Fix for classy error: Missing reference on large type (sizeof const m…
JoergAtGithub Jan 20, 2022
d56e6c6
Renamed poll function and added comments to the run loop, that explai…
JoergAtGithub Jan 20, 2022
decd877
hid_device* is now passed as parameter to sendOutputReport()
JoergAtGithub Jan 20, 2022
6f60e66
Not use deviceInfo after moving, revert to use m_deviceInfo instead
JoergAtGithub Jan 29, 2022
ad09a59
Assign pHidDevice directly
JoergAtGithub Jan 29, 2022
cd3802d
Preallocate the QByteArray with known size
JoergAtGithub Jan 29, 2022
3aebbc6
Initialize m_lastPollSize in the initializer list.
JoergAtGithub Jan 29, 2022
2009300
Replaced QByteArray .clear() .isEmpty() by an additional boolean m_un…
JoergAtGithub Jan 30, 2022
7fd7fb0
Replaced QT_RECURSIVE_MUTEX with QMutex, because there is no need for…
JoergAtGithub Jan 31, 2022
bf27e63
Adjusted member names to naming convention
JoergAtGithub Jan 31, 2022
41e7dc0
Fixed and improved comments describing members
JoergAtGithub Jan 31, 2022
cecf167
Lock m_hidDeviceMutex not before it's needed
JoergAtGithub Jan 31, 2022
f79cac2
Removed outdated initialization of mutex with QT_RECURSIVE_MUTEX_INIT
JoergAtGithub Jan 31, 2022
7659e52
Use typedef for std::map<unsigned char, std::unique_ptr<HidIoReport>>
JoergAtGithub Jan 31, 2022
abba386
Added comments to make clear, that it's not an error case, when HidIo…
JoergAtGithub Jan 31, 2022
d978a7b
Restrict m_outputReportMapMutex to protect only map/iterator operations.
JoergAtGithub Feb 5, 2022
3ceeb76
The atomic int m_state is now a private member and modified by CAS op…
JoergAtGithub Feb 5, 2022
98b839a
Fixed wrong usage of DEBUG_ASSERT, instead of VERIFY_OR_DEBUG_ASSERT
JoergAtGithub Feb 6, 2022
713d8ff
Handle always case that the thread is not the expected. Use [[nodisca…
JoergAtGithub Feb 6, 2022
c948254
Added line breaks, to shorten comment lines
JoergAtGithub Feb 6, 2022
7b6257a
Spelling/wording in comments
JoergAtGithub Feb 6, 2022
ac9e337
Added comment what happens if the OutputReport map is modified concur…
JoergAtGithub Feb 6, 2022
1540368
Renamed m_OutputReportIterator according naming conventions
JoergAtGithub Feb 6, 2022
73acad6
Added missing m_hidDeviceMutex lock for call of sendOutputReport
JoergAtGithub Feb 6, 2022
5b96590
Restricted the scope of m_outputReportDataMutex to the latched data a…
JoergAtGithub Feb 6, 2022
9b5b04b
Simply negate the variable with !
JoergAtGithub Feb 7, 2022
fd51a53
Naming consistence: Renamed 'pDevice' to 'pHidDevice' in header
JoergAtGithub Feb 7, 2022
8e6522d
Reorder member declarations in headers and initialization
JoergAtGithub Feb 7, 2022
98feb8d
Ensure, that StopRequested is only set, if thread state is not yet St…
JoergAtGithub Feb 7, 2022
4392b44
Pass m_hidDeviceMutex as argument to sendOutputReport function and lo…
JoergAtGithub Feb 7, 2022
f82b5b2
Improve documentation of use and mutual operations on m_outputReports…
JoergAtGithub Feb 7, 2022
59c2388
Restricted the lock of pHidDeviceMutex as much as possible.
JoergAtGithub Feb 7, 2022
a1aee22
Defined magic numbers as constants in anonymous namespace
JoergAtGithub Feb 7, 2022
1b9c500
Insert blank lines and placed comment above the respective constant f…
JoergAtGithub Feb 7, 2022
8bf3a3c
Removed unnecessary variable initialization, which just shadows poten…
JoergAtGithub Feb 8, 2022
99d5f66
Restricted scope of m_hidDeviceMutex mutex lock to the hid_ operation…
JoergAtGithub Feb 8, 2022
07ba0a7
Took over explaining comments for pollBufferedInputReports from #4573
JoergAtGithub Feb 8, 2022
9ffa700
Corrected order in getInputReport: Create the byte array -> Unlock th…
JoergAtGithub Feb 9, 2022
e96426e
Early mutex unlock in branch of identical OutputReports
JoergAtGithub Feb 10, 2022
d1e3073
Info/Warning wording
JoergAtGithub Feb 10, 2022
c12578f
Added comment regarding OSes that do not support thread priorities
JoergAtGithub Feb 10, 2022
83c7040
Renamed latchedOutputreport to cachedOutputReport
JoergAtGithub Feb 11, 2022
d4db2be
Added the requested comments to explain kSleepTimeWhenIdleMicros
JoergAtGithub Feb 12, 2022
6be8957
Changed replace command to safe heap memory in the unlikely case of a…
JoergAtGithub Feb 12, 2022
10eafba
Fixed QT6 build fail by a compatibility wrapper for QByteArray.replace
JoergAtGithub Feb 12, 2022
e1187ff
Added a warning when cacheOutputReport is called with a different rep…
JoergAtGithub Feb 12, 2022
c8d9e22
Use passing by reference, in QT6 compatibility wrapper for QByteArray…
JoergAtGithub Feb 12, 2022
a75093a
Put reportID into m_cachedOutputReportData at initialization and only…
JoergAtGithub Feb 12, 2022
49930dd
Changed wait for end of run loop implementation to QSemaphore
JoergAtGithub Feb 12, 2022
f6de985
Improved comment formatting - line breaks - replace of inline comment…
JoergAtGithub Feb 14, 2022
b83c7d2
Corrected English grammar in comment
JoergAtGithub Feb 14, 2022
eb09c32
Use QSemaphoreReleaser instead of .release()
JoergAtGithub Feb 15, 2022
1c13901
Use .unlock() and .relock() instead of extra scope for mutex
JoergAtGithub Feb 15, 2022
cedc863
unsend -> unsent
JoergAtGithub Feb 16, 2022
4a9c060
Use an iterator, instead of invoking operator[] outside mutex protection
JoergAtGithub Feb 16, 2022
ed872b7
Renaming for better code readability:
JoergAtGithub Feb 16, 2022
65b0744
Spelling
JoergAtGithub Feb 16, 2022
d4786ff
Replaced unnecessary for loop in sendFeatureReport
JoergAtGithub Feb 16, 2022
8ab1895
Use range-base for loop instead of Qts foreach macro
JoergAtGithub Feb 16, 2022
ca3e246
Use return {};
JoergAtGithub Feb 16, 2022
3ba0f1c
Improved mutex locker naming and style
JoergAtGithub Feb 17, 2022
463519c
Renamed hid dievice protection mutex, to make clear, that it also pro…
JoergAtGithub Feb 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2774,6 +2774,8 @@ if(HID)
endif()
target_sources(mixxx-lib PRIVATE
src/controllers/hid/hidcontroller.cpp
src/controllers/hid/hidiothread.cpp
src/controllers/hid/hidiooutputreport.cpp
src/controllers/hid/hiddevice.cpp
src/controllers/hid/hidenumerator.cpp
src/controllers/hid/legacyhidcontrollermapping.cpp
Expand Down
275 changes: 86 additions & 189 deletions src/controllers/hid/hidcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,10 @@
#include "util/time.h"
#include "util/trace.h"

namespace {
constexpr int kReportIdSize = 1;
constexpr int kMaxHidErrorMessageSize = 512;
} // namespace

HidController::HidController(
mixxx::hid::DeviceInfo&& deviceInfo)
: Controller(deviceInfo.formatName()),
m_deviceInfo(std::move(deviceInfo)),
m_pHidDevice(nullptr),
m_pollingBufferIndex(0) {
m_deviceInfo(std::move(deviceInfo)) {
setDeviceCategory(mixxx::hid::DeviceCategory::guessFromDeviceInfo(m_deviceInfo));

// All HID devices are full-duplex
Expand Down Expand Up @@ -64,55 +57,83 @@ int HidController::open() {
return -1;
}

VERIFY_OR_DEBUG_ASSERT(!m_pHidIoThread) {
qWarning() << "HidIoThread already present for" << getName();
return -1;
}

// Open device by path
qCInfo(m_logBase) << "Opening HID device" << getName() << "by HID path"
<< m_deviceInfo.pathRaw();

m_pHidDevice = hid_open_path(m_deviceInfo.pathRaw());
hid_device* pHidDevice = hid_open_path(m_deviceInfo.pathRaw());

// If that fails, try to open device with vendor/product/serial #
if (!m_pHidDevice) {
if (!pHidDevice) {
qCWarning(m_logBase) << "Failed. Trying to open with make, model & serial no:"
<< m_deviceInfo.vendorId() << m_deviceInfo.productId()
<< m_deviceInfo.serialNumber();
m_pHidDevice = hid_open(
pHidDevice = hid_open(
m_deviceInfo.vendorId(),
m_deviceInfo.productId(),
m_deviceInfo.serialNumberRaw());
}

// If it does fail, try without serial number WARNING: This will only open
// one of multiple identical devices
if (!m_pHidDevice) {
if (!pHidDevice) {
qCWarning(m_logBase) << "Unable to open specific HID device" << getName()
<< "Trying now with just make and model."
<< "(This may only open the first of multiple identical devices.)";
m_pHidDevice = hid_open(m_deviceInfo.vendorId(),
pHidDevice = hid_open(m_deviceInfo.vendorId(),
m_deviceInfo.productId(),
nullptr);
}

// If that fails, we give up!
if (!m_pHidDevice) {
if (!pHidDevice) {
qCWarning(m_logBase) << "Unable to open HID device" << getName();
return -1;
}

// Set hid controller to non-blocking
if (hid_set_nonblocking(m_pHidDevice, 1) != 0) {
if (hid_set_nonblocking(pHidDevice, 1) != 0) {
qCWarning(m_logBase) << "Unable to set HID device " << getName() << " to non-blocking";
hid_close(pHidDevice);
return -1;
}

// This isn't strictly necessary but is good practice.
for (int i = 0; i < kNumBuffers; i++) {
memset(m_pPollData[i], 0, kBufferSize);
setOpen(true);

m_pHidIoThread = std::make_unique<HidIoThread>(pHidDevice, m_deviceInfo);
m_pHidIoThread->setObjectName(QStringLiteral("HidIoThread ") + getName());

connect(m_pHidIoThread.get(),
&HidIoThread::receive,
this,
&HidController::receive,
Qt::QueuedConnection);

// Controller input needs to be prioritized since it can affect the
// audio directly, like when scratching
// The effect of the priority parameter is dependent on the operating system's scheduling policy.
// In particular, the priority will be ignored on systems that do not support thread priorities (as Linux).
m_pHidIoThread->start(QThread::HighPriority);
JoergAtGithub marked this conversation as resolved.
Show resolved Hide resolved

VERIFY_OR_DEBUG_ASSERT(m_pHidIoThread->testAndSetThreadState(
HidIoThreadState::Initialized, HidIoThreadState::OutputActive)) {
qWarning() << "HidIoThread wasn't in expected Initialized state";
}
m_lastPollSize = 0;

setOpen(true);
// This executes the init function of the JavaScript mapping
startEngine();

VERIFY_OR_DEBUG_ASSERT(m_pHidIoThread->testAndSetThreadState(
HidIoThreadState::OutputActive,
HidIoThreadState::InputOutputActive)) {
qWarning() << "HidIoThread wasn't in expected OutputActive state";
}

return 0;
}

Expand All @@ -124,196 +145,72 @@ int HidController::close() {

qCInfo(m_logBase) << "Shutting down HID device" << getName();

// Stop the InputReport polling, but allow sending OutputReports in JavaScript mapping shutdown procedure
VERIFY_OR_DEBUG_ASSERT(m_pHidIoThread) {
qWarning() << "HidIoThread not present for" << getName()
<< "while closing the device !";
}
else {
VERIFY_OR_DEBUG_ASSERT(m_pHidIoThread->testAndSetThreadState(
HidIoThreadState::InputOutputActive,
HidIoThreadState::OutputActive)) {
qWarning() << "HidIoThread wasn't in expected InputOutputActive state";
}
}

// Stop controller engine here to ensure it's done before the device is closed
// in case it has any final parting messages
// in case it has any final parting messages
// This executes the shutdown function of the JavaScript mapping
stopEngine();

// Close device
qCInfo(m_logBase) << "Closing device";
hid_close(m_pHidDevice);
setOpen(false);
return 0;
}
if (m_pHidIoThread) {
disconnect(m_pHidIoThread.get());

void HidController::processInputReport(int bytesRead) {
Trace process("HidController processInputReport");
unsigned char* pPreviousBuffer = m_pPollData[(m_pollingBufferIndex + 1) % kNumBuffers];
unsigned char* pCurrentBuffer = m_pPollData[m_pollingBufferIndex];
// Some controllers such as the Gemini GMX continuously send input reports even if it
// is identical to the previous send input report. If this loop processed all those redundant
// input report, it would be a big performance problem to run JS code for every input report and
// would be unnecessary.
// This assumes that the redundant input report all use the same report ID. In practice we
// have not encountered any controllers that send redundant input report with different report
// IDs. If any such devices exist, this may be changed to use a separate buffer to store
// the last input report for each report ID.
if (bytesRead == m_lastPollSize &&
memcmp(pCurrentBuffer, pPreviousBuffer, bytesRead) == 0) {
return;
}
// Cycle between buffers so the memcmp above does not require deep copying to another buffer.
m_pollingBufferIndex = (m_pollingBufferIndex + 1) % kNumBuffers;
m_lastPollSize = bytesRead;
auto incomingData = QByteArray::fromRawData(
reinterpret_cast<char*>(pCurrentBuffer), bytesRead);

// Execute callback function in JavaScript mapping
// and print to stdout in case of --controllerDebug
receive(incomingData, mixxx::Time::elapsed());
}
// Request stop after sending the last cached OutputReport
VERIFY_OR_DEBUG_ASSERT(m_pHidIoThread->testAndSetThreadState(
HidIoThreadState::OutputActive,
HidIoThreadState::StopWhenAllReportsSent)) {
qWarning() << "HidIoThread wasn't in expected OutputActive state";
m_pHidIoThread->setThreadState(HidIoThreadState::StopWhenAllReportsSent);
}

QByteArray HidController::getInputReport(unsigned int reportID) {
Trace hidRead("HidController getInputReport");
int bytesRead;

m_pPollData[m_pollingBufferIndex][0] = reportID;
// FIXME: implement upstream for hidraw backend on Linux
// https://github.com/libusb/hidapi/issues/259
bytesRead = hid_get_input_report(m_pHidDevice, m_pPollData[m_pollingBufferIndex], kBufferSize);

qCDebug(m_logInput) << bytesRead
<< "bytes received by hid_get_input_report" << getName()
<< "serial #" << m_deviceInfo.serialNumber()
<< "(including one byte for the report ID:"
<< QString::number(static_cast<quint8>(reportID), 16)
.toUpper()
.rightJustified(2, QChar('0'))
<< ")";

if (bytesRead <= kReportIdSize) {
// -1 is the only error value according to hidapi documentation.
// Otherwise minimum possible value is 1, because 1 byte is for the reportID,
// the smallest report with data is therefore 2 bytes.
DEBUG_ASSERT(bytesRead <= kReportIdSize);
return QByteArray();
}
qCInfo(m_logBase) << "Waiting on HID IO thread to send the last remaining OutputReports";
VERIFY_OR_DEBUG_ASSERT(m_pHidIoThread->waitUntilRunLoopIsStopped(50)) {
qWarning() << "Sending the last remaining OutputReports "
"reached timeout!";

return QByteArray::fromRawData(
reinterpret_cast<char*>(m_pPollData[m_pollingBufferIndex]), bytesRead);
}
qCInfo(m_logBase) << "Enforce stop of HID IO thread run loop!";
m_pHidIoThread->setThreadState(HidIoThreadState::StopRequested);
JoergAtGithub marked this conversation as resolved.
Show resolved Hide resolved

bool HidController::poll() {
Trace hidRead("HidController poll");

// This loop risks becoming a high priority endless loop in case processing
// the mapping JS code takes longer than the controller polling rate.
// This could stall other low priority tasks.
// There is no safety net for this because it has not been demonstrated to be
// a problem in practice.
while (true) {
int bytesRead = hid_read(m_pHidDevice, m_pPollData[m_pollingBufferIndex], kBufferSize);
if (bytesRead < 0) {
// -1 is the only error value according to hidapi documentation.
DEBUG_ASSERT(bytesRead == -1);
return false;
} else if (bytesRead == 0) {
// No packet was available to be read
return true;
VERIFY_OR_DEBUG_ASSERT(m_pHidIoThread->waitUntilRunLoopIsStopped(100)) {
qWarning() << "Stopping run loop reached timeout!";
}
}
processInputReport(bytesRead);

// After completion of all HID communication deconstruct m_pHidIoThread
m_pHidIoThread.reset();
}
}

bool HidController::isPolling() const {
return isOpen();
// Close device
setOpen(false);
qCInfo(m_logBase) << "Device closed";
return 0;
}

void HidController::sendReport(QList<int> data, unsigned int length, unsigned int reportID) {
void HidController::sendReport(const QList<int>& data, unsigned int length, unsigned int reportID) {
Q_UNUSED(length);
QByteArray temp;
foreach (int datum, data) {
temp.reserve(data.size());
for (int datum : data) {
temp.append(datum);
}
sendBytesReport(temp, reportID);
m_pHidIoThread->updateCachedOutputReportData(temp, reportID);
}

void HidController::sendBytes(const QByteArray& data) {
sendBytesReport(data, 0);
}

void HidController::sendBytesReport(QByteArray data, unsigned int reportID) {
// Append the Report ID to the beginning of data[] per the API..
data.prepend(reportID);

int result = hid_write(m_pHidDevice, (unsigned char*)data.constData(), data.size());
if (result == -1) {
qCWarning(m_logOutput) << "Unable to send data to" << getName() << ":"
<< mixxx::convertWCStringToQString(
hid_error(m_pHidDevice),
kMaxHidErrorMessageSize);
} else {
qCDebug(m_logOutput) << result << "bytes sent to" << getName()
<< "serial #" << m_deviceInfo.serialNumber()
<< "(including report ID of" << reportID << ")";
}
}

void HidController::sendFeatureReport(
const QByteArray& reportData, unsigned int reportID) {
QByteArray dataArray;
dataArray.reserve(kReportIdSize + reportData.size());

// Append the Report ID to the beginning of dataArray[] per the API..
dataArray.append(reportID);

for (const int datum : reportData) {
dataArray.append(datum);
}

int result = hid_send_feature_report(m_pHidDevice,
reinterpret_cast<const unsigned char*>(dataArray.constData()),
dataArray.size());
if (result == -1) {
qCWarning(m_logOutput) << "sendFeatureReport is unable to send data to"
<< getName() << "serial #" << m_deviceInfo.serialNumber()
<< ":"
<< mixxx::convertWCStringToQString(
hid_error(m_pHidDevice),
kMaxHidErrorMessageSize);
} else {
qCDebug(m_logOutput) << result << "bytes sent by sendFeatureReport to" << getName()
<< "serial #" << m_deviceInfo.serialNumber()
<< "(including report ID of" << reportID << ")";
}
m_pHidIoThread->updateCachedOutputReportData(data, 0);
}

ControllerJSProxy* HidController::jsProxy() {
return new HidControllerJSProxy(this);
}

QByteArray HidController::getFeatureReport(
unsigned int reportID) {
unsigned char dataRead[kReportIdSize + kBufferSize];
dataRead[0] = reportID;

int bytesRead;
bytesRead = hid_get_feature_report(m_pHidDevice,
dataRead,
kReportIdSize + kBufferSize);
if (bytesRead <= kReportIdSize) {
// -1 is the only error value according to hidapi documentation.
// Otherwise minimum possible value is 1, because 1 byte is for the reportID,
// the smallest report with data is therefore 2 bytes.
qCWarning(m_logInput) << "getFeatureReport is unable to get data from" << getName()
<< "serial #" << m_deviceInfo.serialNumber() << ":"
<< mixxx::convertWCStringToQString(
hid_error(m_pHidDevice),
kMaxHidErrorMessageSize);
} else {
qCDebug(m_logInput) << bytesRead
<< "bytes received by getFeatureReport from" << getName()
<< "serial #" << m_deviceInfo.serialNumber()
<< "(including one byte for the report ID:"
<< QString::number(static_cast<quint8>(reportID), 16)
.toUpper()
.rightJustified(2, QChar('0'))
<< ")";
}

// Convert array of bytes read in a JavaScript compatible return type
// For compatibility with input array HidController::sendFeatureReport, a reportID prefix is not added here
QByteArray byteArray;
byteArray.reserve(bytesRead - kReportIdSize);
auto featureReportStart = reinterpret_cast<const char*>(dataRead + kReportIdSize);
return QByteArray(featureReportStart, bytesRead);
}
Loading