Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QAudiobuffer how to get left and right channel from Qbytearray for playback in QAudioOutput



  • Hi all, Currently I'm stuck and the Qt documentation isn't helping me at all.

    I need a way to get and play the individual left and right audio channel of a QByteArray that contains PCM data.
    So far it looks like QAudioBuffer is the class to use however how do I use it.

    It doesn't have any members that I can find that I can fill it with a QByteArray.
    or maybe Im completely on the wrong track.

    I don't understand the QAudioBuffer class and how to use it.

    #include "Sound.h"
    #include <QDebug>
    
    #include <QtCore>
    #include <QtMultimedia/QAudioOutput>
    #include <QAudioBuffer>
    
    int sampleRate = 44100;
    int channelCount = 2;
    int sampleSize = 16;
    const QString codec = "audio/pcm";
    
    void SoundSystem::playSound(bool wait, qreal amplitude, float frequency, int msecs)
    {
        soundBuffer = new QByteArray();
    
        format = new QAudioFormat();
        format->setSampleRate(sampleRate);
        format->setChannelCount(channelCount);
        format->setSampleSize(sampleSize);
        format->setCodec(codec);
        format->setByteOrder(QAudioFormat::LittleEndian);
        format->setSampleType(QAudioFormat::UnSignedInt);
    
        output = new QAudioOutput(*format, this);
    
        outputBuffer = new QBuffer(soundBuffer);
        if (outputBuffer->open(QIODevice::ReadOnly) == false) {
            qCritical() << "Invalid operation while opening QBuffer. audio/pcm";
            return;
        }
    
        msecs = (msecs < 50) ? 50 : msecs;
    
        qreal singleWaveTime = amplitude / frequency;
    
        qreal samplesPerWave = qCeil(format->sampleRate() * singleWaveTime);
    
        quint32 waveCount = qCeil(msecs / (singleWaveTime * 1000.0));
    
        quint32 sampleSize = static_cast<quint32>(format->sampleSize() / 8.0);
    
        QByteArray data(waveCount * samplesPerWave * sampleSize * format->channelCount(), '\0');
    
        unsigned char* dataPointer = reinterpret_cast<unsigned char*>(data.data());
    
        for (quint32 currentWave = 0; currentWave < waveCount; currentWave++)
        {
            for (int currentSample = 0; currentSample < samplesPerWave; currentSample++)
            {
                double nextRadStep = (currentSample / static_cast<double>(samplesPerWave)) * (2 * M_PI);
    
                quint16 sampleValue = static_cast<quint16>((qSin(nextRadStep) > 0 ? 1 : -1) * 32767);
    
                for (int channel = 0; channel < format->channelCount(); channel++)
                {
                    qToLittleEndian(sampleValue, dataPointer);
                    dataPointer += sampleSize;
                }
            }
        }
       
        connect(output, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleAudioStateChanged(QAudio::State)));
    
        // Here I need to have Left, Right or Stereo channel data for playback.
        
            
        soundBuffer->append(data); 
       
        output->start(outputBuffer); // Output is a QAudioOutput  
    
        if(wait)
        {
            QEventLoop *loop = new QEventLoop();
            QObject::connect(output, SIGNAL(stateChanged(QAudio::State)), loop, SLOT(quit()));
    
            do
            {
                loop->exec(QEventLoop::WaitForMoreEvents);
    
                if(output->state() == QAudio::StoppedState)
                {
                    qDebug() << "Recieved Hard Stop";
                    output->stop();
                    soundBuffer->clear();
                }
            }
            while(output->state() == QAudio::ActiveState || output->state() == QAudio::SuspendedState); // Susspend to make pause working
            delete (loop);
        }
        soundBuffer->clear(); // Flush Buffer to append new sequance. else it will cause std::bad_alloc for large wave data. 
    }
    

    How can I achieve this using Qt Libs only ? any help would be greatly appreciated.
    Im using latest Qt 5.9.3 and Phonon isnt supported anymore


  • Lifetime Qt Champion

    Hi,

    If you have a working solution that uses Phonon, then you might want to check the phonon4qt5 project.



  • Sadly no I don't have a working solution, Im completely stuck right now. Ive read that Phonon has or had the ability to get the channel data separately.

    As Im using generated PCM data I just need a way to get the left and right channels data separately for playback. Either on left only or right only or both at the same time.


  • Lifetime Qt Champion

    Since it's interleaved data, you can mask the left or right data before putting it into the QByteArray.



  • Could you maybe provide an example, I have not done masking yet.

    one thing i tried was:

    QByteArray leftSide, rightSide;

    for(int i = 0; i < data.length() / 2; i+=2)
    {
           leftSide[i] = data[i];
           rightSide[i+1] = data[i+1];
    }
    soundBuffer->append(rightSide);
    

    which didnt do much other then lower the volume but the sound still plays from both left and right channels.


  • Lifetime Qt Champion

    quint16 input = 0xFFFF;
    
    quint16 leftOnly = input & 0x00FF;
    quint16 rightOnly = input & 0xFF00;
    


  • Still Lost. Ive tried a few things non of it seems to work for me. No audio on either channel. Im unclear on how

    quint16 input = 0xFFFF;
    

    Where does my QbyteArray data come into play if the quint16 is set ?
    Also I appriciate your help in this so far. Its just im lost.

    QDataStream dataStream(data);
        quint16 input;
        dataStream >> input;
    
        quint16  leftOnly = input & 0x00FF;
        quint16  rightOnly = input & 0xFF00;
    
    
        QByteArray LeftSideData;
        QDataStream out(&LeftSideData, QIODevice::WriteOnly | QIODevice::Append);
        //out.setByteOrder(QDataStream::LittleEndian);
        out << leftOnly;
    
        soundBuffer->append(LeftSideData);
        // soundBuffer->append(RightSideData);
    

    No audio when running Function.


  • Lifetime Qt Champion

    @Banshee10000

    If I look at the code, I assume you are masking 8 bits of data each time.

    Is your audio really 8 bit or do you use 16 bit resolution? Then I think you need to take every interleaved 16 bit for left and right (but I haven't looked up the PCM audio specs, so I might be wrong).

    Regards


  • Lifetime Qt Champion

    Since you want to do some "mixing" with your audio channels. What about using the DSPFilters project ?

    They provide a nice interface to do interleaving/de-interleaving and other manipulations that you might find useful.



  • When I create a test .Wav file by writing Wav header then this data then close and adjusting the header here is the data of the file. If that would help.

    • Format : Wave
    • File size : 30.5 MiB
    • Duration : 3 min 1 s
    • Overall bit rate mode : Constant
    • Overall bit rate : 1 411 kb/s
    • Format : PCM
    • Format settings : Little / Signed
    • Codec ID : 1
    • Duration : 3 min 1 s
    • Bit rate mode : Constant
    • Bit rate : 1 411.2 kb/s
    • Channel(s) : 2 channels
    • Sampling rate : 44.1 kHz
    • Bit depth : 16 bits
    • Stream size : 30.5 MiB (100%)

    The sampleSize = 16



  • Its not really mixing, It more just splitting the PCM to go either right or left. or play in stereo on the PC. Nothing more.
    I'll have a look at the project. But Id really prefer to use Qt Libs only if at all possible. I didn't think this would turn out to be such a mission hehe.

    Can't the QAudioBuffer class do this. Ive look st the documentation and they refer to left and right but i don't understand the Library as the documentation provided is very limited. Also please note that I'm new to Audio programming and Im also not a C++ expert by far. Its like stumbling around in the dark at this stage for me.



  • Ok so I have figured out a solution that will suite my needs
    After constructing the Wav and getting the PCM data into a QByteArray The format in the buffer for PCM 16 Bit will be as follows (L,L,R,R,L,L,R,R....) so every two bytes are sent to one channel (pcm 16) then I nullify the channel I want to cancel so If I want the the left channel only to work the buffer will be (L,L,0,0,L,L,0,0) or visa versa for right.

    So I don't need any external Libs and licenses for this project. Pure C++ and Qt Libs.

    Thanks to @SGaist trying to help me. I did learn more about masking through this ordeal even tough I ended up not using masking it in the final solution.


Log in to reply