其实在写博文http://blog.csdn.net/jklinux/article/details/72355485时,并没有测试放音,原以为就是一件很容易的事. 为了后期写音视频播放器时可以在QT用QAudioOutput放音(基本上所有案例都是调用SDL放音), 确定可行性。实实在在测试了一回,真的发现了问题。
QAudioOutput *aoutput = new QAudioOutput(format); //创建QAudioOutput对象并初始化后
QIODevice *dev = aoutput->start(); //调用start函数后, 返回QIODevice对象的地址.
然后就可以调用dev->write(...)函数进行放音。在播放器里应是边解码边把解码得到的pcm数据存入在内存数组里,再通过dev->write函数提交给声卡发声, 但声卡里的数据缓冲区大小肯定是有限制,不可能一味的write就行。 正常情况下应是写入声音数据后,等到有些数据完成播放后再接着写入部分数据。
QIODevice对象有信号bytesWritten(qint64), 理论上可用个槽函数连接此信号即可得知多少数据已完成播放,再写入等量的声音数据。
但连接此信号的槽函数根本就没有得到触发(不知道这是不是它的bug).
接着再试在线程里搞死循环: dev->write(...)然后dev->waitForBytesWritten(..) 一样无法解决
后来通过QT里带的"Audio Output Example"发现的解决方法。
它里面用的是void QAudioOutput::start(QIODevice *device)来启动, 而当声卡需要数据来播放时会自动调用device->readData(..)函数.
并且readData是个虚函数,也就是可以通过继承QIODevice,重新实现readData函数。这样当声卡需要数据时,就可以取到我们自己备好的数据了。
实现代码:
mydevice.h
#ifndef MYDEVICE_H
#define MYDEVICE_H
#include <QIODevice>
class MyDevice : public QIODevice
{
private:
QByteArray data_pcm; //存放pcm数据
int len_written; //记录已写入多少字节
public:
MyDevice(QByteArray pcm); //创建对象传递pcm数据
~MyDevice();
qint64 readData(char *data, qint64 maxlen); //重新实现的虚函数
qint64 writeData(const char *data, qint64 len); //它是个纯虚函数, 不得不实现
};
#endif // MYDEVICE_H
mydevice.cpp
#include "mydevice.h"
#include <QDebug>
MyDevice::MyDevice(QByteArray pcm) : data_pcm(pcm)
{
this->open(QIODevice::ReadOnly); // 为了解决QIODevice::read (QIODevice): device not open
len_written = 0;
}
MyDevice::~MyDevice()
{
this->close();
}
qint64 MyDevice::readData(char *data, qint64 maxlen) // data为声卡的数据缓冲区地址, maxlen为声卡缓冲区最大能存放的字节数
{
if (len_written >= data_pcm.size())
return 0;
int len;
//计算未播放的数据的长度
len = (len_written+maxlen) > data_pcm.size() ? (data_pcm.size() - len_written) : maxlen;
memcpy(data, data_pcm.data()+len_written, len); //把要播放的pcm数据存入声卡缓冲区里
len_written += len; //更新已播放的数据长度
return len;
}
qint64 MyDevice::writeData(const char *data, qint64 len)
{
}
调用代码:
QAudioFormat fmt;
fmt.setSampleRate(8000);
fmt.setChannelCount(1);
fmt.setSampleSize(8);
fmt.setCodec("audio/pcm");
out = new QAudioOutput(fmt, this); //创建声音输出对象并初始化
//先把文件的pcm数据弄到内存数组里
QByteArray ba;
QFile f("/my.raw"); // my.raw是用arecod录制的
if (!f.open(QIODevice::ReadOnly))
exit(0);
ba = f.readAll();
f.close();
MyDevice *dev = new MyDevice(ba); //创建自定义的IO设备
out->start(dev);