How to unpack 32bit integer packed in a QByteArray? - qt

I'm working with serial communication, and I receive 32bit integers in a QByteArray, packed in 4 separate bytes (little-endian).
I attempt to unpack the value from the 4 bytes using QByteArray::toLong() but it fails the conversion and returns the wrong number:
quint8 packed_bytes[] { 0x12, 0x34, 0x56, 0x78 };
QByteArray packed_array { QByteArray(reinterpret_cast<char*>(packed_bytes),
sizeof(packed_bytes)) };
bool isConversionOK;
qint64 unpacked_value { packed_array.toLong(&isConversionOK) };
// At this point:
// unpacked_value == 0
// isConversionOK == false
The expected unpacked_value is 0x78563412 (little-endian unpacking). Why is the conversion failing?

You can use a QDataStream to read binary data.
quint8 packed_bytes[] { 0x12, 0x34, 0x56, 0x78 };
QByteArray packed_array { QByteArray(reinterpret_cast<char*>(packed_bytes), sizeof(packed_bytes)) };
QDataStream stream(packed_array);
stream.setByteOrder(QDataStream::LittleEndian);
int result;
stream >> result;
qDebug() << QString::number(result,16);

toLong() converts a char * digits string to long. Not bytes. And your values likely don't make the up the string "0x78563412" or its decimal equivalent. Hence the 0 result.
If you need the byte values interpreted as long you can do something like:
long value;
value == *((long*)packed_bytes.data());
Or to access an array of bytes as long array:
long * values;
values == (long*)packed_bytes.data();
values[0]; // contains first long
values[1]; // contains second long
...
Don't know whether my examples work out of the box but it should make clear the principle.
Check out this example:
char bytes[] = {255, 0};
QByteArray b(bytes, 2);
QByteArray c("255");
qDebug() << b.toShort() << c.toShort();
qDebug() << *((short*)b.data()) << *((short*)c.data());
the output is:
0 255
255 13618
You may need to change the byte order depending on the endianess. But it does what you need.

you can build your qint64 with bit manipulators:
#include <QtGlobal>
#include <QByteArray>
#include <QDebug>
int main()
{
quint8 packed_bytes[] { 0x12, 0x34, 0x56, 0x78 };
QByteArray packed_array { QByteArray(reinterpret_cast<char*>(packed_bytes),
sizeof(packed_bytes)) };
qint64 unpacked_value = 0;
unpacked_value |= packed_array.at(0) |
packed_array.at(1) << 8 |
packed_array.at(2) << 16 |
packed_array.at(3) << 24;
qDebug() << QString("0x%1").arg(unpacked_value, 0, 16);
}

Here's a generic solution for converting a QByteArray to "some other type" (such as what is specifically asked in the question) by running it through a QDataStream (as done by the accepted answer).
DISCLAIMER: I am only advocating for using this in a private implementation. I am aware there are many ways one could abuse the
macro!
Using this macro, you can easily produce many conversion functions such as the examples I've provided. Defining a series of such functions in this way may be useful if you need to pull a variety of types out of a stream. Obviously, you could tweak the macro for your use case, the point is the pattern can remain basically same and be put in a macro like this.
#define byteArrayToType( data, order, type ) \
QDataStream stream( data ); \
stream.setByteOrder( order ); \
type t; \
stream >> t; \
return t;
Example functions, which simply wrap the macro:
16 bit, signed
qint16 toQInt16( const QByteArray &data,
const QDataStream::ByteOrder order=QDataStream::BigEndian )
{ byteArrayToType( data, order, qint16 ) }
32 bit, signed
qint32 toQInt32( const QByteArray &data,
const QDataStream::ByteOrder order=QDataStream::BigEndian )
{ byteArrayToType( data, order, qint32 ) }
64 bit, signed
qint64 toQInt64( const QByteArray &data,
const QDataStream::ByteOrder order=QDataStream::BigEndian )
{ byteArrayToType( data, order, qint64 ) }

Cast the Byte array to the required format and use the built-in function qFromBigEndian or qFromLittleEndian to set the Byte order. Example code is shown below,
QByteArray byteArray("\x45\x09\x03\x00");
quint32 myValue = qFromBigEndian<quint32>(byteArray);
qDebug() << "Hex value: " << QString("0x%1").arg(myValue, 8, 16, QLatin1Char( '0' ));
myValue holds the converted value.
Don't forget to include the header file <QtEndian>

Related

QFile write a WAV header writes only 4 byte data

I'm writing a WAV recorder, using QFile as backbone. However, when I fill my Wav struct, and try to write it to my QFile it writes only "RIFF", I viewed it with unix's od -cb 1.wav. Here is the samle code:
wavwriter.cpp
Wav::Wav(const char *fname, QFile* parent)
: QFile(fname, parent),
m_fname(fname)
{
setFileName(fname);
bool res = this->open(QIODevice::ReadWrite);
if (res) {
std::cout << "File opened for RW\n";
}
}
Wav::~Wav()
{
}
void Wav::writeHeader(const WavHdr* hdr)
{
write((char*)hdr);
flush();
}
void Wav::appendData(const QByteArray &data)
{
m_data.append(data);
}
QByteArray Wav::getWavData()
{
return m_data;
}
And the usage is as follows:
WavHdr hdr;
hdr.bits_per_sample = 8;
hdr.riff[0] = 'R';
hdr.riff[1] = 'I';
hdr.riff[2] = 'F';
hdr.riff[3] = 'F';
hdr.sample_rate = 8;
hdr.fmt[0] = 'f';
hdr.fmt[1] = 'm';
hdr.fmt[2] = 't';
m_wavs[i]->writeHeader(&hdr);
The WavHdr has the following setup:
struct WavHdr
{
char riff[4];
qint32 file_size;
char wave[4];
char fmt[4];
char len[3];
qint16 type;
quint16 format;
qint32 sample_rate;
qint32 sr_bs_channs;
quint8 bits_per_sample;
char data[4];
qint32 fsize;
};
You can't dump WavHdr to disk directly.
The way you use the write method only makes sense for zero-terminated strings. It will stop writing at the first zero-valued byte. A WavHdr is not a null-terminated string.
You cannot assume that the struct has any particular representation in memory. The compiler is free to arrange that structure the way it sees fit. Not only it can pad and align the members arbitrarily, it can also rearrange them. So that's a non-portable anti-pattern: it may happen to work on some compilers, on others it will be thoroughly broken.
Your WavHdr is wrong.
See here for reference. I've included a correct header structure below.
You probably wish to use a QSaveFile.
When saving files, you usually intend the file writing to be atomic: either it succeeds and you get a complete, valid WAV file, or it fails and nothing changes on disk (e.g. an existing file is not overwritten and corrupted). That's what QSaveFile is designed for.
You probably want your wave class to use an I/O device, but not be one.
I/O can be done with just an instance of a QIODevice*: you'll then be able to easily write the data to in-memory buffers, files, network sockets, etc. The user of your class should be free to choose what particular device to use.
Instead, use QDataStream to write the header in a portable way:
struct WavHdr
{
constexpr static quint32 k_riff_id = 0x46464952;
constexpr static quint32 k_wave_format = 0x45564157;
constexpr static quint32 k_fmt_id = 0x20746d66;
constexpr static quint32 k_data_id = 0x61746164;
// RIFF
quint32 chunk_id = k_riff_id;
quint32 chunk_size;
quint32 chunk_format = k_wave_format;
// fmt
quint32 fmt_id = k_fmt_id;
quint32 fmt_size;
quint16 audio_format;
quint16 num_channels;
quint32 sample_rate;
quint32 byte_rate;
quint16 block_align;
quint16 bits_per_sample;
// data
quint32 data_id = k_data_id;
quint32 data_size;
};
bool write(QIODevice * dev, const WavHdr & h) {
QDataStream s{dev};
s.setByteOrder(QDataStream::LittleEndian); // for RIFF
s << h.chunk_id << h.chunk_size
<< h.chunk_format;
s << h.fmt_id << h.fmt_size
<< h.audio_format
<< h.num_channels
<< h.sample_rate
<< h.byte_rate
<< h.block_align
<< h.bits_per_sample;
s << h.data_id << h.data_size;
return s.status() == QDataStream::Ok;
}

QT QString from QDataStream

I'm working with a buffer and I'm trying to get a string from it, but isnt working...
Example:
*void myFunc(QDataStream& in)
{
quint8 v;
in >> v;
// Ok, I caught v value successfuly
QString s;
in >> s;
// Didnt work :<
}*
The string lenght is stored on 2 first bytes...
Thanks
If the string was not written as a QString, you need to read its length and content separately.
quint8 v;
in >> v;
quint16 length = 0;
in >> length;
// the string is probably utf8 or latin
QByteArray buffer(length, Qt::Uninitialized);
in.readRawData(buffer.data(), length);
QString string(buffer);
You might have to change the endianness of the QDataStream with QDataStream::setByteOrder before reading the 16-bit length.
We should really see the writing code and how you create the QDataStream. I tried with the following sample, and in this case your function works very well:
#include <QCoreApplication>
#include <QDebug>
#include <QDataStream>
#include <QBuffer>
void myFunc(QDataStream& in)
{
quint8 v;
in >> v;
qDebug() << v;
// Ok, I caught v value successfuly
QString s;
in >> s;
qDebug() << s;
// Didnt work :<
}
int main(int argc, char ** argv) {
QCoreApplication a(argc, argv);
QBuffer buffer;
buffer.open(QBuffer::ReadWrite);
// write test data into the buffer
QDataStream out(&buffer);
quint8 ival = 42;
QString sval = "Qt";
out << ival;
out << sval;
// read back data
buffer.seek(0);
myFunc(out);
return a.exec();
}
Output when executed:
$ ./App
42
"Qt"

How to create a Queue of unsigned char array in Qt?

I am new in Queue (FIFO) and Qt. I want to create a Queue of unsigned char array in Qt. How to do it? Please help
unsigned char buffer[1024];
If you want to use the Qt API, then you can use the QQueue class -
QQueue<unsigned char> queue;
queue.enqueue(65);
queue.enqueue(66);
queue.enqueue(67);
while (!queue.isEmpty())
cout << queue.dequeue() << endl;
If you want to build the queue on your own, then I guess you can declare a Queue class like this -
class Queue
{
private:
enum{SIZE=1024, EMPTY=0};
unsigned char buffer[SIZE];
int readHead, writeHead;
public:
Queue()
{
readHead = writeHead = EMPTY;
}
void push(unsigned char data);
unsigned char pop();
unsigned char peek();
bool isEmpty();
};
void Queue::push(unsigned char data)
{
if((readHead - writeHead) >= SIZE)
{
// You should handle Queue overflow the way you want here.
return;
}
buffer[writeHead++ % SIZE] = data;
}
unsigned char Queue::pop()
{
unsigned char item = peek();
readHead++;
return item;
}
unsigned char Queue::peek()
{
if(isEmpty())
{
// You should handle Queue underflow the way you want here.
return;
}
return buffer[readHead % SIZE];
}
bool Queue::isEmpty()
{
return (readHead == writeHead);
}
If you want to maintain a Queue of unsigned char array, then you will have to maintain a queue of unsigned char pointers -
QQueue<unsigned char *> queue;
unsigned char *array1 = new unsigned char[10]; // array of 10 items
array1[0] = 65;
array1[1] = 66;
queue.enqueue(array1);
unsigned char *array2 = new unsigned char[20]; // an array of 20 items
queue.enqueue(array2);
unsigned char *arr = queue.dequeue();
qDebug() << arr[0] << ", " << arr[1];
Note: You should take care of the memory cleanup after you are done with this queue. IMHO, you better avoid this type of design though.

QByteArray convert to/from unsigned char *

QByteArray inArray = " ... ";
unsigned char *in = convert1(inArray);
unsigned char *out;
someFunction(in, out);
QByteArray outArray = convert2(out);
the question is how can I correctly make these conversions (convert1 and convert2).
I cannot change someFunction(unsigned char *, unsigned char *), but I have to work with QByteArray here.
Qt has really great docs, you should use them.
If someFunction doesn't modify or store pointer to in data you can use this:
QByteArray inArray = " ... ";
unsigned char *out;
someFunction((unsigned char*)(inArray.data()), out);
QByteArray outArray((char*)out);
Otherwise you have to make a deep copy of the char* returned by QByteArray::data() (see the docs for code snippet).
if someFunction takes a const char* args then just use ConstData() or data() in QByteArray class.
if you need a char*, you can then use strdup(). This method is doing this
char *strdup (const char *s) {
char *d = malloc (strlen (s) + 1); // Space for length plus nul
if (d == NULL) return NULL; // No memory
strcpy (d,s); // Copy the characters
return d; // Return the new string
}
more info here: strdup() - what does it do in C?

C macro to C++\Qt

I have the following c macro from the libpurple yahoo plugin:
#define yahoo_put16(buf, data) ( \
(*(buf) = (unsigned char)((data)>>8)&0xff), \
(*((buf)+1) = (unsigned char)(data)&0xff), \
2)
I want to implement the same as a function in my class witch would receive as a parameter a quint16 value and return it as a QByteArray
I have the following but i don't seem to get the same result as with the macro above.
QByteArray YahooPacket::packQuint16(quint16 value) const
{
QByteArray data;
data.append(QByteArray::number((value >> 8) & 0xFF));
data.append(QByteArray::number(value & 0xFF));
return data;
}
How would i do to implement my function?
QByteArray::number() creates the printable (string) version of the number which is probably not what you want. Use the QByteArray constructor that takes a buffer pointer and a size parameter. I think this will do what you want.
QByteArray YahooPacket::packQuint16(quint16 value) const
{
QByteArray data;
data.append(QByteArray(((char*)&value)+1,1));
data.append(QByteArray((char*)&value,1));
return data;
}

Resources