Search code examples
c++qtrtp

How to write N bits to QByteArray


I have a task to decode an RTP packet and extract the payload of this packet (audio data). In order to bypass all the RTP header fields, I have this function:


quint32 readNBitsByRange(quint32 position, quint32 count, const QByteArray &array)
{
    quint32 accuml = 0;
    while (count != 0) {
            const quint32 l = (8 - position % 8);
            const quint32 u = (l < count ? l : count);
            const quint32 f = (8 - u);
            accuml  <<= u;
            accuml   |= ((*(array.data() + position / 8) << (8 - l)) & (((1 << u) - 1) << f)) >> f;
            position += u;
            count    -= u;
    }
    return accuml;
}

As arguments, the function takes the position from which the read will be made, the number of bits, and the buffer from which the read should be made. Thanks to this function, I can read all the RTP header fields. Example of using this function:


int main()
{
    // ... We get The RTP packet in binary form and write it to QByteArray ...
    QByteArray array;
    for (quint32 i = 0; i < rtpBinaryDataLength; ++i) {
        array.push_back(rtpBinaryData[i]);
    }
    
    // 0                   1                   2                   3
    // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |V=2|P|X|  CC   |M|     PT      |       sequence number         |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                           timestamp                           |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |           synchronization source (SSRC) identifier            |
    // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
    // |            contributing source (CSRC) identifiers             |
    // |                             ....                              |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    quint16 rtpVersion   = readNBitsByRange(0x000, 0x002, array);
    quint16 rtpPadding   = readNBitsByRange(0x002, 0x001, array);
    quint16 rtpExtension = readNBitsByRange(0x003, 0x001, array);
    
    // .. And so on ...
}

It all works! But here's the problem! Now I have a task to write the RTP packet values to QByteArray. I don't know how to do it! We only know that, for example, the values of 4 fields (V, P, X, CC) of the RTP packet must be written to the 1st byte of the buffer.

I would like to have an example function, like for reading, so that you can easily work with it. Example of a function:


void writeNBits(quint32 position, quint32 count, quint32 val, QByteArray &array)
{
   // ...
}

I tried writing all the values to the header, and then just write them to the QByteArray:

struct RtpHeader
{
    unsigned m_v :2; 
    unsigned m_p :1; 
    unsigned m_x :1; 
    unsigned m_cc:4; 
    unsigned m_m :1; 
    unsigned m_pt:7; 
    uint16_t m_sn;   
    uint32_t m_tm;   
    uint32_t m_ssrc;   
};

int main(int argc, char *argv[])
{
    RtpHeader hdr;
    
    hdr.m_v    = 2;
    hdr.m_p    = 0;
    hdr.m_x    = 0;
    hdr.m_cc   = 0;
    hdr.m_m    = 1;
    hdr.m_pt   = 8;
    hdr.m_sn   = 59133;
    hdr.m_tm   = 240;
    hdr.m_ssrc = 0xDEE0EE8F;

    QByteArray array(reinterpret_cast<const char*>(&hdr), sizeof(hdr));
    for (quint8 i = 0; i < 240; i++) {
        array.push_back(0xD5); // Silence 
    }

    QFile file("./rawRtpPacket.bin");
    file.open(QIODevice::WriteOnly);
    file.write(array);
    file.close();
    
    return EXIT_SUCCESS;
}

But that's not what I should get! For example, I should have received these results of a 12-byte header :

80 88 E6 FD  00 00 00 F0  DE E0 EE 8F

But I get different results:

02 11 FD E6  F0 00 00 00  8F EE E0 DE 

If you look closely, from 4 bytes to 12, I have to mirror these values, but at the expense of the first two bytes, I can't understand why I get a completely different one.

I write: V-2, P-0, X-0, CC-0 and should get 10000000, but I get 00000010


Solution

  • After experimenting a bit, I found a more correct way to write the RTP packet in QByteArray and realized my previous errors. Therefore, I wrote a sample version of the correct RTP packet entry in QByteArray:

    
    class RtpHeader
    {
    public:
    
        quint16 m_vp:0x02;
        quint16 m_pf:0x01;
        quint16 m_xf:0x01;
        quint16 m_cc:0x04;
        quint16 m_mb:0x01;
        quint16 m_pt:0x07;
        quint16 m_sn;
        quint32 m_tm;
        quint32 m_ss;
    
    };
    
    class RtpHeaderEncoder
    {
    public:
    
        RtpHeaderEncoder(void)                                noexcept = delete;
        RtpHeaderEncoder &operator=(const RtpHeaderEncoder &) noexcept = delete;
        RtpHeaderEncoder &operator=(RtpHeaderEncoder &&)      noexcept = delete;
        RtpHeaderEncoder(const RtpHeaderEncoder &)            noexcept = delete;
        RtpHeaderEncoder(RtpHeaderEncoder &&)                 noexcept = delete;
       ~RtpHeaderEncoder(void)                                noexcept = delete;
    
        static QByteArray encode(const RtpHeader &hdr)  noexcept;
    
    };
    
    QByteArray RtpHeaderEncoder::encode(const RtpHeader &hdr) noexcept
    {
        QByteArray array;
    
        if ((hdr.m_vp == 0x02) && (hdr.m_pf == 0x00) && (hdr.m_cc <= 0x0F) && (hdr.m_pt <= 0x12)) {
    
            QDataStream stream(&array, QIODevice::WriteOnly);
            stream << (((hdr.m_vp & 0x00003) << 0x01E)|
                       ((hdr.m_pf & 0x00001) << 0x01D)|
                       ((hdr.m_xf & 0x00001) << 0x01C)|
                       ((hdr.m_cc & 0x0000F) << 0x018)|
                       ((hdr.m_mb & 0x00001) << 0x017)|
                       ((hdr.m_pt & 0x0007F) << 0x010)|
                       ((hdr.m_sn & 0x0FFFF) << 0x000));
    
            stream << hdr.m_tm << hdr.m_ss;
        }
        return array;
    }
    
    int main(int argc, char *argv[])
    {
        RtpHeader hdr;
        hdr.m_vp = 2;
        hdr.m_pf = 0;
        hdr.m_xf = 0;
        hdr.m_cc = 0;
        hdr.m_mb = 0;
        hdr.m_pt = 8;
        hdr.m_sn = 1;
        hdr.m_tm = 201452158;
        hdr.m_ss = 111537764;
    
        QFile file("./rawRtpHeader.bin");
        file.open (QIODevice::WriteOnly);
        file.write(RtpHeaderEncoder::encode(hdr));
        file.close();
        
        return EXIT_SUCCESS;
    }
    
    

    This method is both more secure and guarantees that all fields of the fixed RTP packet header are filled in correctly. If you need to fill QByteArray and payload, then just after filling the array with the package header, we write the payload itself.

    P.S. If there are any questions or comments related to the code, I will be happy to answer and accept the criticism.