最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

QT6實現(xiàn)音頻可視化的示例代碼

 更新時間:2024年12月23日 09:12:14   作者:Beyond欣  
這篇文章主要為大家詳細介紹了一個基于QT6的音頻可視化Demo,文中的示例代碼,具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學習一下

QT6音頻可視化

需要做一個簡易的音頻可視化,網(wǎng)上找的案例都是基于QT5的。很多API和類都棄用了,分享一個基于QT6的音頻可視化Demo。

1 獲取QAudioBuffer

QAudioBuffer 存儲一系列音頻幀,每個幀包含一個時間點上所有通道的采樣值。采樣格式通過 QAudioFormat 指定,包括采樣率、通道數(shù)、樣本大小等。在多通道(例如立體聲)音頻中,采樣數(shù)據(jù)通常是交錯存儲的,即每個幀包含各個通道的采樣值依次排列。

QT5 通過 QAudioProbe 來獲取幀數(shù)據(jù),QT6 提供了 QAudioBufferOutput 來獲取幀數(shù)據(jù)。

    QMediaPlayer * player = new QMediaPlayer(this);
    QAudioOutput * audioOutput = new QAudioOutput;
    player->setAudioOutput(audioOutput);
    player->setSource(QUrl::fromLocalFile("./111.flac"));

    QAudioFormat formatAudio;
    formatAudio.setSampleRate(44100);
    formatAudio.setChannelCount(1);
    formatAudio.setSampleFormat(QAudioFormat::UInt8);
    QAudioBufferOutput * buffer = new QAudioBufferOutput(formatAudio, this);
    player->setAudioBufferOutput(buffer);
    player->play();
    connect(buffer, &QAudioBufferOutput::audioBufferReceived, 
            this, &Widget::OnAudioBufferReceived);

2 時域轉頻域

Qt 自身未集成 FFT 計算,其自帶的 Demo 里使用的 FFTReal。FFTW 、FFTReal、KISSFFT 都試了下,最后用的 KISSFFT。

    int N = audioData.size();
    kiss_fft_cfg cfg = kiss_fft_alloc(N, 0, NULL, NULL);

    // 準備輸入和輸出
    std::vector<kiss_fft_cpx> input(N);
    std::vector<kiss_fft_cpx> output(N);
    for (int i = 0; i < N; ++i) {
        input[i].r = audioData[i];
        input[i].i = 0.0f;
    }

    // FFT
    kiss_fft(cfg, input.data(), output.data());
    free(cfg);

    // 計算 FFT 結果的幅值
    float frameMaxMagnitude = 0.0f;
    std::vector<float> magnitude(N / 2);
    for (int i = 0; i < N / 2; ++i) {
        magnitude[i] = std::sqrt(output[i].r * output[i].r + output[i].i * output[i].i);
        frameMaxMagnitude = qMax(frameMaxMagnitude, magnitude[i]);
    }

    // 更新全局最大值(帶平滑)
    GlobalMaxMagnitude = qMax(GlobalMaxMagnitude * 0.9f, frameMaxMagnitude);

    // 計算動態(tài)范圍的有效最大值
    float effectiveMaxMagnitude = qMax(frameMaxMagnitude, GlobalMaxMagnitude * DynamicThreshold);

    // 歸一化
    for (int i = 0; i < N / 2; ++i) {
        magnitude[i] = (magnitude[i] / effectiveMaxMagnitude) * 100.0f;
    }

3 頻域結果分組

簡單區(qū)分下高中低頻率,不同頻率占比不同

    // 分組頻段范圍
    int sampleRate = 44100;

    int lowEnd = 400;
    int midEnd = 4000;
    int highEnd = 20000;

    int lowBins = lowEnd * N / sampleRate;
    int midBins = midEnd * N / sampleRate;
    int highBins = highEnd * N / sampleRate;

    auto addBands = [&](int startBin, int endBin, int bands) {
        int binRange = (endBin - startBin) / bands; // 每個小頻段包含的 bin 數(shù)量
        for (int i = 0; i < bands; ++i) {
            float bandSum = 0;
            int bandStart = startBin + i * binRange;
            int bandEnd = bandStart + binRange;

            for (int j = bandStart; j < bandEnd && j < magnitude.size(); ++j) {
                bandSum += magnitude[j];
            }

            freqData << bandSum;
        }
    };

    addBands(0, lowBins, 5);
    addBands(lowBins, midBins, 10);
    addBands(midBins, highBins, 5);

4 QChart可視化

QChart 可以同時繪制 柱狀圖和曲線圖,使用 QStackedBarSeries 和 QSplineSeries

constexpr int FreqAxisRange = 512;
constexpr int TimeAxisRange = 1024;
constexpr int FreqDomainSum = 20;

constexpr int FPS = 30;

startTimer(FPS);

void TimeWaveformWidget::SetSeriesData(const QList<int> & freqData, const QList<int> & timeData)
{
    m_FreqData = std::move(freqData);
    m_TimeData = std::move(timeData);
}

void TimeWaveformWidget::timerEvent(QTimerEvent * event)
{
    // 更新頻域數(shù)據(jù)
    if (m_FreqData.size() == FreqDomainSum) {
        for (int i = 0; i < m_FreqData.size(); ++i) {
            m_FreqBarSet->replace(i, m_FreqData.at(i));
        }
    }

    // 更新時域數(shù)據(jù)
    QVector<QPointF> points;
    int sampleCount = m_TimeData.size();
    QVector<QPointF> originalPoints;
    for (int i = 0; i < sampleCount; ++i) {
        originalPoints.emplace_back(i, m_TimeData.at(i));
    }
    InterpolateData(originalPoints, points, TimeAxisRange);
    m_TimeSeries->replace(points);

    m_Chart->update();
}

void TimeWaveformWidget::InitFreqSeries()
{
    for (int i = 0; i < FreqDomainSum; i++) {
        *m_FreqBarSet << 0;
    }

    m_FreqSeries->append(m_FreqBarSet);
    m_Chart->addSeries(m_FreqSeries);
    m_Chart->addAxis(m_FreqAxisX, Qt::AlignBottom);
    m_FreqAxisY->setRange(0, FreqAxisRange);
    m_Chart->addAxis(m_FreqAxisY, Qt::AlignLeft);
    m_FreqSeries->attachAxis(m_FreqAxisX);
    m_FreqSeries->attachAxis(m_FreqAxisY);

    m_FreqAxisX->setLineVisible(false);
    m_FreqAxisX->setLabelsVisible(false);
    m_FreqAxisX->setGridLineVisible(false);
    m_FreqAxisY->setLineVisible(false);
    m_FreqAxisY->setLabelsVisible(false);
    m_FreqAxisY->setGridLineVisible(false);
}

void TimeWaveformWidget::InitTimeSeries()
{
    m_Chart->addSeries(m_TimeSeries);

    auto axisX = new QValueAxis;
    axisX->setRange(0, TimeAxisRange);
    auto axisY = new QValueAxis;
    axisY->setRange(0, 256);
    m_Chart->addAxis(axisX, Qt::AlignBottom);
    m_TimeSeries->attachAxis(axisX);
    m_Chart->addAxis(axisY, Qt::AlignLeft);
    m_TimeSeries->attachAxis(axisY);

    axisX->setLineVisible(false);
    axisX->setLabelsVisible(false);
    axisX->setGridLineVisible(false);
    axisY->setLineVisible(false);
    axisY->setLabelsVisible(false);
    axisY->setGridLineVisible(false);
}

float TimeWaveformWidget::CatmullRomInterpolate(
  float p0, float p1, float p2, float p3, float t)
{
    float t2 = t * t;
    float t3 = t2 * t;
    float result = 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
    return result;
}

void TimeWaveformWidget::InterpolateData(
  const QVector<QPointF> & originalPoints,
  QVector<QPointF> & targetPoints, int targetCount)
{
    int n = originalPoints.size();
    if (n < 2) {
        return;
    }

    targetPoints.reserve(targetCount);
    float step = static_cast<float>(n - 1) / (targetCount - 1);
    for (int i = 0; i < targetCount; ++i) {
        float t = i * step;
        int index = static_cast<int>(t);
        float frac = t - index;

        int p0 = (index > 0) ? (index - 1) : 0;
        int p1 = index;
        int p2 = (index + 1 < n) ? (index + 1) : (n - 1);
        int p3 = (index + 2 < n) ? (index + 2) : (n - 1);

        float value = CatmullRomInterpolate(
          originalPoints[p0].y(), originalPoints[p1].y(),
          originalPoints[p2].y(), originalPoints[p3].y(), frac);

        targetPoints.append(QPointF(i, value));
    }
}

5 QPaint可視化

照著別人的效果,用 QPaint 畫一下。

constexpr int Rows = 12;
constexpr int Cols = 20;
constexpr int GridGap = 4;
constexpr int Falling = 50;
constexpr bool EnableAntialiasing = true;

constexpr int MaxFrequency = 512; // 頻率數(shù)據(jù)最大值
// 色相范圍
constexpr int MaxHue = 120;
constexpr int MaxSaturation = 180;
constexpr int MaxValue = 180;

void FrequencySpectrumWidget::resizeEvent(QResizeEvent * event)
{
    QWidget::resizeEvent(event);

    if (m_BackgroundPixmap.size() != QSize(width(), height())) {
        GetGridSize(m_GridCache.gridSize, m_GridCache.gap, m_GridCache.startX, m_GridCache.startY);
        PreDrawBackground();
    }
}

void FrequencySpectrumWidget::timerEvent(QTimerEvent * event)
{
    UpdateFallingColors();
    update();
}

void FrequencySpectrumWidget::paintEvent(QPaintEvent * event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, EnableAntialiasing);

    // 繪制緩存的背景圖像
    painter.drawPixmap(0, 0, m_BackgroundPixmap);

    const int gridSize = m_GridCache.gridSize;
    const int gap = m_GridCache.gap;
    const int startX = m_GridCache.startX;
    const int startY = m_GridCache.startY;

    // 繪制頻域數(shù)據(jù)相關的格子
    for (int col = 0; col < Cols; ++col) {
        int volumeLevel = m_FrequencyData[col];
        for (int i = 0; i < volumeLevel; ++i) {
            int row = Rows - 1 - i;
            int hue = MaxHue - (i * MaxHue / Rows);
            int saturation = MaxSaturation;
            int value = MaxValue;
            QColor color = QColor::fromHsv(hue, saturation, value);
            QRect rect(startX + col * (gridSize + gap),
                       startY + row * (gridSize + gap),
                       gridSize, gridSize);
            painter.setBrush(QBrush(color));
            painter.drawRect(rect);
        }

        int heightRow = Rows - 1 - m_FallingColorHeight[col];
        QColor color(200, 200, 200);
        QRect rect(
          startX + col * (gridSize + gap),
          startY + heightRow * (gridSize + gap) + gridSize / 2,
          gridSize, gridSize / 2);
        painter.setBrush(QBrush(color));
        painter.drawRect(rect);
    }
}

void FrequencySpectrumWidget::PreDrawBackground()
{
    m_BackgroundPixmap = QPixmap(width(), height());
    m_BackgroundPixmap.fill(Qt::black); // 填充背景為黑色

    QPainter painter(&m_BackgroundPixmap);
    painter.setRenderHint(QPainter::Antialiasing, EnableAntialiasing);

    const int gridSize = m_GridCache.gridSize;
    const int gap = m_GridCache.gap;
    const int startX = m_GridCache.startX;
    const int startY = m_GridCache.startY;

    QVector<QRect> gridRects;

    for (int col = 0; col < Cols; ++col) {
        for (int row = 0; row < Rows; ++row) {
            gridRects.append(QRect(
              startX + col * (gridSize + gap),
              startY + row * (gridSize + gap),
              gridSize, gridSize));
        }
    }

    QColor color(22, 23, 21);
    painter.setBrush(QBrush(color));
    painter.drawRects(gridRects.data(), gridRects.size());
}

void FrequencySpectrumWidget::UpdateFallingColors()
{
    for (int col = 0; col < Cols; ++col) {
        // m_FallingColorHeight[col] = qMax(m_FrequencyData[col], m_FallingColorHeight[col] - 1);
        m_FallingColorHeight[col] = qMax(
          m_FrequencyData[col],
          m_FallingColorHeight[col] - qCeil((m_FallingColorHeight[col] - m_FrequencyData[col]) * 0.1));
    }
}

void FrequencySpectrumWidget::GetGridSize(int & gridSize, int & gap, int & startX, int & startY)
{
    gap = GridGap; // 網(wǎng)格間隙(1:1比例)

    int availableWidth = width() - (Cols + 1) * gap; // 可用寬度(減去左右間隙)
    int availableHeight = height() - (Rows + 1) * gap; // 可用高度(減去上下間隙)
    gridSize = std::min(availableWidth / Cols, availableHeight / Rows); // 格子邊長
    if (gridSize < gap * 2) { // 尺寸太小,保證至少有兩個格子寬度的間隙
        gridSize = gap * 2;
    }

    // 計算總的網(wǎng)格寬度和高度
    int totalWidth = (Cols * gridSize) + (Cols + 1) * gap;
    int totalHeight = (Rows * gridSize) + (Rows + 1) * gap;

    // 計算繪制起始位置,使網(wǎng)格居中
    startX = (width() - totalWidth) / 2;
    startY = (height() - totalHeight) / 2;
}

以上就是QT6實現(xiàn)音頻可視化的示例代碼的詳細內容,更多關于QT6音頻可視化的資料請關注腳本之家其它相關文章!

相關文章

  • C++實現(xiàn)馬踏棋盤(騎士周游)

    C++實現(xiàn)馬踏棋盤(騎士周游)

    這篇文章主要為大家詳細介紹了C++實現(xiàn)馬踏棋盤,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • C++中pair的用法總結

    C++中pair的用法總結

    pair是C++STL(標準模板庫)中的一個現(xiàn)有容器,它將2個數(shù)據(jù)整合成一組數(shù)據(jù),當我們類似需求的時候就可以使用到pair,pair的本質其實就是個結構體,本文將詳細的給大家介紹pair用法,感興趣的同學可以參考閱讀
    2023-05-05
  • c語言定時器示例分享

    c語言定時器示例分享

    在linux下開發(fā),使用的是C語言。適用于需要定時的軟件開發(fā),以系統(tǒng)真實的時間來計算,它送出SIGALRM信號。每隔一秒定時一次
    2014-04-04
  • C++結構體與類指針知識點總結

    C++結構體與類指針知識點總結

    在本篇文章里小編給大家整理了關于C++結構體與類指針知識點以及相關內容,有興趣的朋友們參考學習下。
    2019-09-09
  • C++核心編程之類和對象詳解

    C++核心編程之類和對象詳解

    這篇文章主要介紹了C++編程進階之類和對象用法等相關使用技巧,需要的朋友可以參考下,希望能夠給你帶來幫助
    2021-09-09
  • C++11、C++14、C++17、C++20常用新特性

    C++11、C++14、C++17、C++20常用新特性

    本文主要介紹了C++11、C++14、C++17、C++20常用新特性,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-03-03
  • C語言學習之柔性數(shù)組詳解

    C語言學習之柔性數(shù)組詳解

    結構體的最后一個元素允許是未知大小的數(shù)組,這就叫柔性數(shù)組。這篇文中主要為大家詳細介紹了C語言中柔性數(shù)組的相關知識,需要的可以了解一下
    2023-03-03
  • C++中類模板的應用你了解多少

    C++中類模板的應用你了解多少

    這篇文章主要為大家詳細介紹了C++中類模板的應用,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • c/c++中opencv實現(xiàn)腐蝕的示例代碼

    c/c++中opencv實現(xiàn)腐蝕的示例代碼

    圖像腐蝕是形態(tài)學圖像處理中的一種基本操作,OpenCV的cv::erode函數(shù)實現(xiàn)該功能,具有一定的參考價值,感興趣的了解一下
    2025-06-06
  • 有關C++中類類型轉換操作符總結(必看篇)

    有關C++中類類型轉換操作符總結(必看篇)

    下面小編就為大家?guī)硪黄嘘PC++中類類型轉換操作符總結(必看篇)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01

最新評論

宁陵县| 鄂温| 怀仁县| 准格尔旗| 岗巴县| 宁国市| 新源县| 宿州市| 东兰县| 甘肃省| 浦东新区| 淄博市| 洱源县| 图木舒克市| 杭州市| 通山县| 渑池县| 云和县| 漠河县| 锡林浩特市| 定远县| 土默特右旗| 介休市| 堆龙德庆县| 景宁| 井研县| 修武县| 绥中县| 临泽县| 永福县| 元氏县| 福泉市| 繁昌县| 竹北市| 蕲春县| 肥东县| 健康| 云安县| 台安县| 乃东县| 集贤县|