使用Qt播放音频

上两篇文章介绍了如何使用FFMpeg获取到音频信息和解码音频
使用FFMpeg 获取MP3文件中的信息和图片
使用FFMpeg 解码音频文件

本篇文章介绍一下如何使用Qt播放解码后的PCM数据。


1. 使用类QAudioFormat、QAudioOutput

可以使用QAudioFormat 设置音频的格式相关信息,如采样率、采样位数、通道数等信息。

代码如下:

QAudioFormat nFormat;
nFormat.setSampleRate(sampleRate);
nFormat.setSampleSize(sampleSize);
nFormat.setChannelCount(channelCount);
nFormat.setCodec("audio/pcm");
nFormat.setByteOrder(QAudioFormat::LittleEndian);
nFormat.setSampleType(QAudioFormat::UnSignedInt);

QAudioOutput类构造时需要设置Format,QAudioOutput可以控制音频的播放、暂停、设置和获取音量等控制。

使用QAudioOutput 播放音频的方法如下:

  1. QAudioOutput *m_OutPut = new QAudioOutput(nFormat); 创建一个QAudioOutput对象。
  2. QIODevice *m_AudioIo = m_OutPut->start(); 获取QIODevice的指针对象。
  3. m_AudioIo ->write() 函数向音频设备中写入数据。

2. 播放音频

这里我使用了一个线程去播放音频,在线程中不断的向设备中写入音频数据,从而达到音频连续不卡顿的播放。代码如下:

void AudioPlayThread::run(void)
{
    while (!this->isInterruptionRequested())
    {
        if (!m_IsPlaying)
        {
            continue;
            QThread::msleep(10);
        }

        QMutexLocker locker(&m_Mutex);

        if (m_PCMDataBuffer.count() <= 0 || m_CurrentPlayIndex >= m_PCMDataBuffer.count())
        {
            QThread::msleep(10);
            continue;
        }

        if (m_OutPut->bytesFree() >= m_OutPut->periodSize())
        {
            char *writeData = new char[m_OutPut->periodSize()];

            // 获取将要播放的数据
            int size = m_OutPut->periodSize();
            size = qMin(size, m_PCMDataBuffer.size() - m_CurrentPlayIndex);
            memcpy(writeData, &m_PCMDataBuffer.data()[m_CurrentPlayIndex], size);

            // 写入音频数据
            m_AudioIo->write(writeData, size);
            m_CurrentPlayIndex += size;

            emit updatePlayStatus();
            delete []writeData;
            QThread::msleep(10);
        }
    }
}

将音频的播放数据放到了 QByteArray m_PCMDataBuffer; 中。每次写入periodSize 个数据, 设备的音频缓冲区中一旦已经释放了>=periodSize的内存就写入一次。从而保证缓冲区中一直有播放的数据,也不会覆盖原有的数据。直到暂停或播放完毕。

下面时完整的代码:
AudioPlayThread.h

#ifndef AUDIO_PLAY_THREAD_H
#define AUDIO_PLAY_THREAD_H

#include <QThread>
#include <QObject>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QMutex>
#include <QByteArray>
#define g_AudioPlayThread AudioPlayThread::getInstance()
class AudioPlayThread : public QThread
{
    Q_OBJECT

public:
    static AudioPlayThread *getInstance(void);

public:
    AudioPlayThread(QObject *parent = nullptr);
    ~AudioPlayThread();

    // ----------- 添加数据相关 ----------------------------------------
    // 设置当前的PCM Buffer
    void setCurrentBuffer(QByteArray buffer);
    // 添加数据
    void addAudioBuffer(char* pData, int len);
    // 清空当前的数据
    void cleanAllAudioBuffer(void);
    // ------------- End ----------------------------------------------

    // 设置当前的采样率、采样位数、通道数目
    void setCurrentSampleInfo(int sampleRate, int sampleSize, int channelCount);

    virtual void run(void) override;

    // 获取当前的所在位置大小
    int getCurrentBuffIndex(void);
    // 获取当前的时间
    int getCurrentTime(void);

    // 切换播放状态
    void playMusic(bool status);
    // 获取当前的播放状态
    bool getPlayMusicStatus(void);
    // 设置音量
    void setCurrentVolumn(qreal volumn);
    // 获取当前音量
    qreal getCurrentVolumn(void);

private:
    QAudioOutput *m_OutPut = nullptr;
    QIODevice *m_AudioIo = nullptr;

    QByteArray m_PCMDataBuffer;
    int m_CurrentPlayIndex = 0;

    QMutex m_Mutex;
    // 播放状态
    bool m_IsPlaying = true;

signals:
    void updatePlayStatus(void);
};

#endif

AudioPlayThread.cpp

#include "AudioPlayThread.h"
#include <QMutexLocker>
#include <QDebug>

AudioPlayThread::AudioPlayThread(QObject *parent)
    :QThread(parent)
{
    m_PCMDataBuffer.clear();
}

AudioPlayThread::~AudioPlayThread()
{

}

AudioPlayThread *AudioPlayThread::getInstance(void)
{
    static AudioPlayThread instance;
    return &instance;
}

void AudioPlayThread::setCurrentBuffer(QByteArray buffer)
{
    QMutexLocker locker(&m_Mutex);

    m_PCMDataBuffer.clear();
    m_PCMDataBuffer = buffer;
    m_IsPlaying = true;
}

void AudioPlayThread::setCurrentSampleInfo(int sampleRate, int sampleSize, int channelCount)
{
    QMutexLocker locker(&m_Mutex);
    //this->requestInterruption();

    // Format
    QAudioFormat nFormat;
    nFormat.setSampleRate(sampleRate);
    nFormat.setSampleSize(sampleSize);
    nFormat.setChannelCount(channelCount);
    nFormat.setCodec("audio/pcm");
    nFormat.setByteOrder(QAudioFormat::LittleEndian);
    nFormat.setSampleType(QAudioFormat::UnSignedInt);

    if (m_OutPut != nullptr)
        delete m_OutPut;
    m_OutPut = new QAudioOutput(nFormat);
    m_AudioIo = m_OutPut->start();
    //this->start();
}

void AudioPlayThread::run(void)
{
    while (!this->isInterruptionRequested())
    {
        if (!m_IsPlaying)
        {
            continue;
            QThread::msleep(10);
        }

        QMutexLocker locker(&m_Mutex);

        if (m_PCMDataBuffer.count() <= 0 || m_CurrentPlayIndex >= m_PCMDataBuffer.count())
        {
            QThread::msleep(10);
            continue;
        }

        if (m_OutPut->bytesFree() >= m_OutPut->periodSize())
        {
            char *writeData = new char[m_OutPut->periodSize()];

            // 获取将要播放的数据
            int size = m_OutPut->periodSize();
            size = qMin(size, m_PCMDataBuffer.size() - m_CurrentPlayIndex);
            memcpy(writeData, &m_PCMDataBuffer.data()[m_CurrentPlayIndex], size);

            // 写入音频数据
            m_AudioIo->write(writeData, size);
            m_CurrentPlayIndex += size;

            emit updatePlayStatus();
            delete []writeData;
            QThread::msleep(10);
        }
    }
}

// 添加数据
void AudioPlayThread::addAudioBuffer(char* pData, int len)
{
    QMutexLocker locker(&m_Mutex);

    m_PCMDataBuffer.append(pData, len);
    m_IsPlaying = true;
}

void AudioPlayThread::cleanAllAudioBuffer(void)
{
    QMutexLocker locker(&m_Mutex);
    m_CurrentPlayIndex = 0;
    m_PCMDataBuffer.clear();
    m_IsPlaying = false;
}

void AudioPlayThread::playMusic(bool status)
{
    m_IsPlaying = status;
}

bool AudioPlayThread::getPlayMusicStatus(void)
{
    return m_IsPlaying;
}

void AudioPlayThread::setCurrentVolumn(qreal volumn)
{
    if (m_OutPut)
        m_OutPut->setVolume(volumn);
}

qreal AudioPlayThread::getCurrentVolumn(void)
{
    if (!m_OutPut)
        return 0;

    return m_OutPut->volume();
}

// 获取当前的所在位置大小
int AudioPlayThread::getCurrentBuffIndex(void)
{
    return m_CurrentPlayIndex;
}

int AudioPlayThread::getCurrentTime(void)
{
    QMutexLocker locker(&m_Mutex);

    qreal sec = m_CurrentPlayIndex * 1.0 / 4 * (1 * 1.0 / m_OutPut->format().sampleRate());
    return sec * 1000;
}
不会飞的纸飞机
扫一扫二维码,了解我的更多动态。

下一篇文章:使用FFMpeg 提取MKV文件中的字幕