上两篇文章介绍了如何使用FFMpeg获取到音频信息和解码音频
使用FFMpeg 获取MP3文件中的信息和图片
使用FFMpeg 解码音频文件
本篇文章介绍一下如何使用Qt播放解码后的PCM数据。
可以使用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 播放音频的方法如下:
这里我使用了一个线程去播放音频,在线程中不断的向设备中写入音频数据,从而达到音频连续不卡顿的播放。代码如下:
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;
}