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

QML Audio with Surface3D FFT



  • I am trying to implement Audio FFT with Surface3D in qml to plot the FFT.
    Audio is working in a different thread and Surface3D (QML) is the main thread.

    Once QAudioInput writes to a buffer and once there is a value written a signal is emited. In the slot i read the writting buffer and do DSP and play it back to QAudioOutput. In the DSP i copy the values and do FFT and send the result to the main thread via signal.

    connect(&wrBuff, &QIODevice::bytesWritten, this, &AudioSoundcard::readBufferSlot);
    
      audioInput  = new QAudioInput (input, m_format, this);
      audioOutput = new QAudioOutput(output, m_format, this);
    
      audioInput->start(&wrBuff);
      audioOutput->start(&rdBuff);
    
    ....
    
    void AudioSoundcard::readBufferSlot(void)
    {
      if((!wrBuff.buffer().size()>=2))
          return;
    
      rdBuff.buffer().remove(0, rdBuff.pos());
    
      // set pointer to the beginning of the unread data
      rdBuff.seek(0);
    
      QDataStream in(wrBuff.buffer()); //< Attach a read-only stream to it
      in.setByteOrder(QDataStream::LittleEndian); //< Set the proper byte order
    
      QByteArray filter;
      quint16 outdata; //< The result you want
    
      float* audioData[2];
      audioData[0] = new float[wrBuff.buffer().size()/2];
      audioData[1] = new float[wrBuff.buffer().size()/2];
    
      int r=0,l=0;
      for(int i=0; i<wrBuff.buffer().size()/2; i++)
      {
          in >> outdata; //< Just read it from the stream
          //samplesSave.append(outdata);
          //audioData[0][i]=outdata;
          //audioData[1][i]=outdata;
          //r++;
          if(i % 2==0 && r< wrBuff.buffer().size()/2)
          {
              audioData[0][r]=outdata;
              r++;
          }
          else if(l< wrBuff.buffer().size()/2)
          {
              audioData[1][l]=outdata;
              l++;
          }
    
          //out <<  outdata;
      }
      if(l != 0 && r != 0)
      {
          if(HighfilterEnable)
              highpass->process (wrBuff.buffer().size()/4, audioData);
          if(LowfilterEnable)
              lowpass->process (wrBuff.buffer().size()/4, audioData);
    
      }
    
    
    
      for(int i=0; i<wrBuff.buffer().size()/4; i++)
      {
          qint16 valueR=audioData[0][i];
          qint16 valueL=audioData[1][i];
       
    
          samplesSaveR.append(valueR);
          samplesSaveL.append(valueL);
    
          filter.append(quint8(valueR));
          filter.append(quint8(valueR>>8));
          filter.append(quint8(valueL));
          filter.append(quint8(valueL>>8));
      }
    
      if (m_spectrumAnalyser.isReady() && samplesSaveR.size()>=m_spectrumBufferLength)
      {
          m_spectrumBuffer = QByteArray::fromRawData(samplesSaveR.constData(),m_spectrumBufferLength);
          m_spectrumPosition = 0;
    
          channel=0;
          m_spectrumAnalyser.calculate(m_spectrumBuffer, m_formatFFT);
          samplesSaveR.clear();
      }
    
      // write new data
      rdBuff.buffer().append(filter);
    
      // remove all data that was already written
      wrBuff.buffer().clear();
      wrBuff.seek(0);
    }
    

    So in the main thread i go ahead and write to the Vector and send a notification to QML to update the graph.

    void AudioMain::spectrumChanged(const FrequencySpectrum &spectrum)
    {
        FrequencySpectrum::const_iterator i = spectrum.begin();
        const FrequencySpectrum::const_iterator end = spectrum.end();
    
        QSurfaceDataArray &cache = m_data[0];
    
        QSurfaceDataRow &row = *(cache[timeCount]);
    
        float x = timeCount;
    
        int h=0;
        for ( ; i != end; ++i)
        {
            const FrequencySpectrum::Element e = *i;
            float z =e.frequency;
            float y =e.amplitude;
            row[h] = QVector3D(x, y, z);
            h++;
        }
    
        if(timeCount>=timebase-1)
        {
    
            timeCount=0;
            emit samplesArrived();
    
        }
        else
            timeCount++;
    }
    
    void AudioMain::update(QSurface3DSeries *series)
    {
    
        if (series && m_data.size())
        {
                m_index = 0;
    
            QSurfaceDataArray array = m_data.at(m_index);
            int newRowCount = array.size();
            int newColumnCount = array.at(0)->size();
    
            // If the first time or the dimensions of the cache array have changed,
            // reconstruct the reset array
            if (m_resetArray || series->dataProxy()->rowCount() != newRowCount
                    || series->dataProxy()->columnCount() != newColumnCount)
            {
                m_resetArray = new QSurfaceDataArray();
                m_resetArray->reserve(newRowCount);
                for (int i(0); i < newRowCount; i++)
                    m_resetArray->append(new QSurfaceDataRow(newColumnCount));
            }
    
            // Copy items from our cache to the reset array
            for (int i(0); i < newRowCount; i++)
            {
                const QSurfaceDataRow &sourceRow = *(array.at(i));
                QSurfaceDataRow &row = *(*m_resetArray)[i];
                for (int j(0); j < newColumnCount; j++)
                    row[j].setPosition(sourceRow.at(j).position());
            }
    
            // Notify the proxy that data has changed
            series->dataProxy()->resetArray(m_resetArray); 
        }
    
    }
    

    If the i call "series->dataProxy()->resetArray(m_resetArray); " the audioouput stops and says
    ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred

    This means that the audio thread isnt quick enought to write to the QAudioOutput buffer and play the sound.
    Any idea?


Log in to reply