Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt
  • Unsolved
Collapse
qtws26_logo

The Qt Event of the Year: Qt World Summit 2026

Save Your Spot >
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QPainter uses a lot of CPU
Qt 6.12 LTS is out! See what's new in the release blog

QPainter uses a lot of CPU

Scheduled Pinned Locked Moved Unsolved General and Desktop
2 Posts 2 Posters 676 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    marcelnitan
    wrote on last edited by
    #1

    I'm using QPainter to draw waveforms from an audio file and keep track of the current position. So far everything works great, except it's using a lot of CPU usage and messes up with the audio processing my player has. The library I used as base is also 12 years old so that also doesn't help at all.

    Is there any way to improve it's performance?

    WaveformWidget.h

    #ifndef WAVEFORMWIDGET_H
    #define WAVEFORMWIDGET_H
    
    #include "AudioUtil.h"
    #include "MathUtil.h"
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #include <vector>
    
    #include <sndfile.h>
    
    #include <QSize>
    #include <QColor>
    #include <QWidget>
    #include <QAbstractSlider>
    #include <QPainter>
    #include <QPaintEvent>
    #include <QDebug>
    #include <QPoint>
    #include <QResizeEvent>
    #include <QProcess>
    #include <QFileInfo>
    #include <QMouseEvent>
    
    /*!
        \file WaveformWidget.h
        \brief WaveformWidget header file.
    */
    
    using namespace std;
    
    /*!
    \brief A Qt widget to display the waveform of an audio file.
    
    
    */
    class WaveformWidget : public QAbstractSlider
    {
        Q_PROPERTY( QColor waveformColor MEMBER m_waveformColor );
        Q_PROPERTY( QColor waveformProgressColor MEMBER m_progressColor );
        Q_OBJECT
    public:
        WaveformWidget(QWidget *parent = nullptr);
        ~WaveformWidget();
        void setSource(QFileInfo *fileName);
        void setFfmpegConvertToMono(bool convert);
        void resetFile(QFileInfo *fileName);
        enum FileHandlingMode {FULL_CACHE, DISK_MODE};
        void setColor(QColor color);
        void setFileHandlingMode(FileHandlingMode mode);
        void setFfmpegPath(QString path);
        void setClickable(bool clickable);
        FileHandlingMode getFileHandlingMode();
    
    protected:
        virtual void resizeEvent(QResizeEvent *);
        virtual void paintEvent( QPaintEvent * event );
        void mouseMoveEvent(QMouseEvent *event) override;
        void mousePressEvent(QMouseEvent *event);
    
    
    private:
        AudioUtil *srcAudioFile;
        FileHandlingMode currentFileHandlingMode;
        vector<double> peakVector;
        vector<double> dataVector;
        QString audioFilePath;
        double max_peak;
        double padding;
        bool ffmpegConvertToMono;
        QSize lastSize;
        QColor m_waveformColor { Qt::blue };
        QColor m_progressColor { QColor(246, 134, 86) };
    
        QProcess *convert_process;
    
        double scaleFactor;
    
        QString ffmpeg_path;
    
        void recalculatePeaks();
        void overviewDraw(QPaintEvent* event);
    
        void convertAudio(QFileInfo *fileName);
        void setSourceFromConverted();
        int mouseEventPosition(const QMouseEvent *event) const;
    
        bool is_clickable;
    signals:
      void barClicked(int);
    };
    
    #endif // WAVEFORMWIDGET_H
    
    

    WaveformWidget.cpp

    #include "WaveformWidget.h"
    
    #include <QStandardPaths>
    #include <QDir>
    #include <QToolTip>
    #include <QDebug>
    
    #define DEFAULT_PADDING 0.3
    #define LINE_WIDTH 1
    #define POINT_SIZE 5
    #define DEFAULT_COLOR Qt::blue
    #define INDIVIDUAL_SAMPLE_DRAW_TOGGLE_POINT 9.0
    
    /*!
    \file WaveformWidget.cpp
    \brief WaveformWidget implementation file.
    */
    
    /*!
        \mainpage WaveformWidget
    
        \brief This library provides a Qt widget for visualizing audio waveforms.<br><br>
    
        It depends on Erik de Castro Lopo's <a href='http://www.mega-nerd.com/libsndfile/'>libsndfile</a> and
        the <a href='http://qt.nokia.com/'>Qt framework</a>.  It has been tested on Linux (kernel version 2.6.31-21) under Gnome with
        version 4.6 of the Qt framework, but could well function in other environments.
        <br><br>
        A source archive can be found <a href='http://www.columbia.edu/~naz2106/WaveformWidget.tar.gz'>here</a>.
        <br><br>For build instructions, see the README.txt file contained in the top level directory of the source archive.
    */
    
    /*!
    \brief Constructs an instance of WaveformWidget.
    @param filePath Valid path to a WAV file.
    */
    WaveformWidget::WaveformWidget(QWidget *parent) : QAbstractSlider(parent),
        is_clickable(false)
    {
        clearFocus();
        setFocusPolicy(Qt::NoFocus);
    
        this->srcAudioFile = new AudioUtil();
        this->ffmpegConvertToMono = true;
        this->convert_process = new QProcess;
        connect(convert_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &WaveformWidget::setSourceFromConverted);
    }
    
    /*The AudioUtil instance "srcAudioFile" is our only dynamically allocated object*/
    WaveformWidget::~WaveformWidget()
    {
        delete this->srcAudioFile;
    }
    
    
    void WaveformWidget::setSource(QFileInfo *fileName)
    {
        this->audioFilePath = fileName->canonicalFilePath();
        if ((fileName->completeSuffix() != "wav" && !this->ffmpeg_path.isEmpty()) || (this->ffmpegConvertToMono && !this->ffmpeg_path.isEmpty()))
        {
            this->convertAudio(fileName);
        }
        else
        {
            this->currentFileHandlingMode = FULL_CACHE;
            this->resetFile(fileName);
            this->scaleFactor = -1.0;
            this->lastSize = this->size();
            this->padding = DEFAULT_PADDING;
        }
    }
    
    void WaveformWidget::setSourceFromConverted()
    {
        if (!this->audioFilePath.isEmpty())
        {
            QFileInfo *audioFileInfo = new QFileInfo(audioFilePath);
            QString newFileName = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + audioFileInfo->completeBaseName() + QString(".wav");
            QFileInfo *fileInfo = new QFileInfo(newFileName);
            this->currentFileHandlingMode = FULL_CACHE;
            this->resetFile(fileInfo);
            this->scaleFactor = -1.0;
            this->lastSize = this->size();
            this->padding = DEFAULT_PADDING;
        }
    }
    
    void WaveformWidget::mousePressEvent(QMouseEvent *event)
    {
      if ((event->button() == Qt::LeftButton) && is_clickable)
        emit barClicked(mouseEventPosition(event));
    
      event->accept();
    }
    
    
    // Returns the position in milliseconds corresponding to the mouse position on the progress bar where the event occured
    int WaveformWidget::mouseEventPosition(const QMouseEvent *event) const
    {
      return event->x() * (maximum() / width());
    }
    
    void WaveformWidget::mouseMoveEvent(QMouseEvent *event)
    {
      event->accept();
    }
    
    void WaveformWidget::setClickable(bool clickable)
    {
      if (clickable)
        setCursor(Qt::PointingHandCursor);
      else {
        setCursor(Qt::ForbiddenCursor);
        if (underMouse())
          QToolTip::hideText();
      }
    
      setMouseTracking(clickable);
    
      is_clickable = clickable;
    }
    
    
    
    /*!
    \brief Reset the audio file to be visualized by this instance of WaveformWidget.
    
    An important consideration when invoking this function is the file-handling mode that you
    have set for the current instance of WaveformWidget.  If the instance of WaveformWidget is in FULL_CACHE mode,
    this function will take considerably longer to execute (posssibly as long as a few seconds for an audio file of several
    minutes' duration) as the entirety of the audio file to be visualized
    by the widget must be loaded into memory.
    @param fileName Valid path to a WAV file
    */
    void WaveformWidget::resetFile(QFileInfo *fileName)
    {
        this->audioFilePath = fileName->canonicalFilePath();
        this->srcAudioFile->setFile(audioFilePath);
    
        switch(this->currentFileHandlingMode)
        {
            case FULL_CACHE:
                this->srcAudioFile->setFileHandlingMode(AudioUtil::FULL_CACHE);
    
            case DISK_MODE:
                this->srcAudioFile->setFileHandlingMode(AudioUtil::DISK_MODE);
        }
    
        this->peakVector.clear();
        this->dataVector.clear();
        this->repaint();
     }
    
    /*!
      \brief Mutator for the file-handling mode of a given instance of WaveformWidget.
    
    An instance of WaveformWidget relies on an AudioUtil object to do much of the analysis of the audio file
    that it visualizes.  This AudioUtil object can function in one of two modes: DISK_MODE or FULL_CACHE.
    For a comprehensive outline of the benefits and drawbacks of each mode, see the documentation for
    AudioUtil::setFileHandlingMode(FileHandlingMode mode).
    
    @param mode The desired file-handling mode.  Valid options: WaveformWidget::FULL_CACHE, WaveformWidget::DISK_MODE
    */
    void WaveformWidget::setFileHandlingMode(FileHandlingMode mode)
    {
        this->currentFileHandlingMode = mode;
    
        switch (this->currentFileHandlingMode)
        {
            case FULL_CACHE:
                this->srcAudioFile->setFileHandlingMode(AudioUtil::FULL_CACHE);
    
            case DISK_MODE:
                this->srcAudioFile->setFileHandlingMode(AudioUtil::DISK_MODE);
        }
    }
    
    /*!
    \brief Accessor for the file-handling mode of a given instance of WaveformWidget.
    
    @return The file-handling mode of a given instance of WaveformWidget.
    */
    WaveformWidget::FileHandlingMode WaveformWidget::getFileHandlingMode()
    {
        return this->currentFileHandlingMode;
    }
    
    void WaveformWidget::recalculatePeaks()
    {
        /*calculate scale factor*/
        vector<double> normPeak = srcAudioFile->calculateNormalizedPeaks();
        double peak = MathUtil::getVMax(normPeak);
        this->scaleFactor = 1.0/peak;
        this->scaleFactor = scaleFactor - scaleFactor * this->padding;
    
        /*calculate frame-grab increments*/
        int totalFrames = srcAudioFile->getTotalFrames();
        int frameIncrement = totalFrames/this->width();
            if(srcAudioFile->getNumChannels() == 2)
            {
                this->peakVector.clear();
    
                vector<double> regionMax;
    
                /*
                  Populate the peakVector with peak values for each region of the source audio
                  file to be represented by a single pixel of the widget.
                */
    
                for(int i = 0; i < totalFrames; i += frameIncrement)
                {
                    regionMax = srcAudioFile->peakForRegion(i, i+frameIncrement);
                    double frameAbsL = fabs(regionMax[0]);
                    double frameAbsR = fabs(regionMax[1]);
    
                    this->peakVector.push_back(frameAbsL);
                    this->peakVector.push_back(frameAbsR);
                }
            }
    
            if(this->srcAudioFile->getNumChannels() == 1)
            {
    
                this->peakVector.clear();
                vector<double> regionMax;
    
                /*
                  Populate the peakVector with peak values for each region of the source audio
                  file to be represented by a single pixel of the widget.
                */
    
                for(int i = 0; i < totalFrames; i += frameIncrement)
                {
                    regionMax = srcAudioFile->peakForRegion(i, i+frameIncrement);
                    double frameAbs = fabs(regionMax[0]);
    
                    this->peakVector.push_back(frameAbs);
                }
            }
    }
    
    void WaveformWidget::paintEvent( QPaintEvent * event )
    {
    
    #ifdef DEBUG
        char m[200];
        sprintf(m, "widget width: %d\naudio file size in frames:%d\n", this->width(), this->srcAudioFile->getTotalFrames());
        qDebug()<<m;
    #endif
    
        if (srcAudioFile->getSndFIleNotEmpty())
        {
            this->recalculatePeaks();
            this->overviewDraw(event);
            this->lastSize = this->size();
        }
    }
    
    /*
        The overview drawing function works with the peakVector, which contains the peak value
        for every region (and each channel) of the source audio file to be represented by a single
        pixel of the widget.  The function steps through this vector and draws two vertical bars
        for each such value -- one above the Y-axis midpoint for the channel, and one below.
    */
    void WaveformWidget::overviewDraw(QPaintEvent *event)
    {
        QPainter painter(this);
    
        int minX = event->region().boundingRect().x();
        int maxX = event->region().boundingRect().x() + event->region().boundingRect().width();
    
        /*grab peak values for each region to be represented by a pixel in the visible
        portion of the widget, scale them, and draw: */
        if(this->srcAudioFile->getNumChannels() == 2)
        {
    
                int startIndex = 2*minX;
                int endIndex = 2*maxX;
    
                int yMidpoint = this->height()/2;
                int counter = minX;
    
                for(int i = startIndex;  i < endIndex; i+=2)
                {
                    if (counter < (qreal)value() / maximum() * width())
                        painter.setPen(QPen(this->m_progressColor, 1, Qt::SolidLine, Qt::RoundCap));
                    else
                        painter.setPen(QPen(this->m_waveformColor, 1, Qt::SolidLine, Qt::RoundCap));
    
                    int chan1YMidpoint = yMidpoint - this->height()/4;
                    int chan2YMidpoint = yMidpoint + this->height()/4;
    
    
                    painter.drawLine(counter, chan1YMidpoint, counter, chan1YMidpoint+((this->height()/4)*this->peakVector.at(i)*scaleFactor));
                    painter.drawLine(counter, chan1YMidpoint, counter, chan1YMidpoint -((this->height()/4)*this->peakVector.at(i)*scaleFactor));
    
                    painter.drawLine(counter, chan2YMidpoint, counter, chan2YMidpoint+((this->height()/4)*this->peakVector.at(i+1)*scaleFactor)   );
                    painter.drawLine(counter, chan2YMidpoint, counter, chan2YMidpoint -((this->height()/4)*this->peakVector.at(i+1)*scaleFactor)   );
    
                    counter++;
                }
    
        }
    
        if(srcAudioFile->getNumChannels() == 1)
        {
               int curIndex = minX;
               int yMidpoint = this->height()/2;
    
    
               for(unsigned int i = 0; i < peakVector.size(); i++)
               {
                   if (curIndex < (qreal)value() / maximum() * width())
                       painter.setPen(QPen(this->m_progressColor, 1, Qt::SolidLine, Qt::RoundCap));
                   else
                       painter.setPen(QPen(this->m_waveformColor, 1, Qt::SolidLine, Qt::RoundCap));
                   painter.drawLine(curIndex, yMidpoint, curIndex, yMidpoint+((this->height()/4)*this->peakVector.at(i)*scaleFactor)   );
                   painter.drawLine(curIndex, yMidpoint, curIndex, yMidpoint -((this->height()/4)*this->peakVector.at(i)*scaleFactor)   );
    
                   curIndex++;
               }
        }
    
    }
    
    /*!
        \brief Mutator for waveform color.
    
        @param The desired color for the waveform visualization.
    */
    void WaveformWidget::setColor(QColor color)
    {
        this->m_waveformColor = color;
    }
    
    void WaveformWidget::setFfmpegConvertToMono(bool convert)
    {
        this->ffmpegConvertToMono = convert;
    }
    
    /**
     * @brief sets the path to ffmpeg binary to use for processing non wav files
     * @param path to ffmpeg binary
     */
    void WaveformWidget::setFfmpegPath(QString path)
    {
        this->ffmpeg_path = path;
    }
    
    void WaveformWidget::convertAudio(QFileInfo *fileName)
    {
        QString newFileName = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + fileName->completeBaseName() + QString(".wav");
        QStringList params;
        if (this->ffmpegConvertToMono)
            params << "-y"
                   << "-i"
                   << fileName->canonicalFilePath()
                   << "-ac"
                   << "1"
                   << "-acodec"
                   << "pcm_u8"
                   << newFileName;
        else
            params << "-y"
                   << "-i"
                   << fileName->canonicalFilePath()
                   << "-acodec"
                   << "pcm_u8"
                   << newFileName;
    
        convert_process->start(this->ffmpeg_path, params);
    }
    
    
    void WaveformWidget::resizeEvent(QResizeEvent *)
    {
    
    }
    
    
    
    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi and welcome to devnet,

      Did you profile your code ?
      Are you redoing the calculations every time the widget is painted ?
      Did you consider creating a QPixmap once and update it when needed rather than repaint everything every time ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      1 Reply Last reply
      2

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt
      • Unsolved