过去的一个月,在找了若干代码和请教了若干编程高手,求助于各大论坛,qq群之后,终于实现了我需要的功能 ——实时录音并播放,录音到circular buffer再从circular buffer的同一个位置中播放出来。主要参考的代码有spectrum analyzer example和 qt multimedia example (audio input, audio output )
问题主要集中在从buffer 播放,为解决之,我前后试了 4种方案
能够工作的方案是
1,当播放到buffer最尾时,QAudioOutput 的对象将从active状态转换成idle状态,检测状态转换信号——作为判断buffer播放到最后一个内存位置,调用slot,将QAudioOutput对象close,再重新指定buffer,指定完成将QAudioOutput对象重新start。下面是关键部分代码,全部代码请将之替换我最后面的另一个方案的完整代码的对应部分得到。这个方案的缺点是每个10秒钟(我设定了buffer的长度是能够容纳10秒音频数据,根据设定的格式)能够听到嘎嘣嘎嘣的stop和start时候的脉冲杂音。
void bufferPlayback::startPlayback()
{
if (m_audioOutput) {
m_mode = QAudio::AudioOutput;
m_dataLengthPlay = 0;
m_audioOutputIODevice.close();
m_audioOutputIODevice.setBuffer(&m_buffer);
m_audioOutputIODevice.open(QIODevice::ReadOnly);
qDebug() << "audio play start";
m_audioOutput->start(&m_audioOutputIODevice);
connect(m_audioOutput,SIGNAL(stateChanged(QAudio::State)),SLOT(rpb(QAudio::State)));
}
}
void bufferPlayback::rpb (QAudio::State state)
{
qDebug()<< "resetPlayBuffer";
if (state == QAudio::IdleState) {
m_audioOutput->stop();
m_audioOutputIODevice.close();
m_audioOutputIODevice.setBuffer(&m_buffer);
m_audioOutputIODevice.open(QIODevice::ReadOnly);
m_audioOutput->start(&m_audioOutputIODevice);
}
}
2,从spectrum analyzer example的音频输入获得灵感,仿照同样方法做的输出。需要注意的地方是因为输出没有readyRead信号,所以需要自己建立信号,并在合适的时机——录音被保存到buffer中——发射。该project完整代码将在最后奉上,希望能够帮助程序员们节省开发时间。
3,QAudioOutput的对象start后面参数是一个继承的类对象。在类对象中重新实现readData函数,这部分主要参考的是qt multimedia audio output,但是很不幸,这种方法不工作,现象为——我推测,当放音开始的时候,写内存的线程不能继续进行,因为没有用多线程并发机制,所以10秒的内存只被写了QTime设置的那么长时间,其他秒内为空白。所以运行时候程序听到的声音是一段录音,然后一段空白,循环播放。
Generator类的头文件:
class Generator: public QIODevice
{
Q_OBJECT
public:
Generator(QByteArray buffer);
~Generator();
qint64 writeData(const char *data, qint64 len);
qint64 readData(char *data, qint64 maxlen); // maybe this readData would affect the Mic to Buffer?
QByteArray m_GeneratorBuffer;
void start();
void stop();
// by affecting the behavior of m_audioInputIODevice
qint64 bytesAvailable() const;
private:
// void generateData(const QAudioFormat &format, qint64 durationUs, int frequency);
private:
qint64 m_pos;
};cpp文件:
Generator::Generator(QByteArray buffer)
{
m_GeneratorBuffer = buffer;
m_pos=0;
}
Generator::~Generator()
{
}
void Generator::start()
{
open(QIODevice::ReadOnly);
}
void Generator::stop()
{
m_pos = 0;
close();
}
qint64 Generator::readData(char *data, qint64 len)
{
qDebug() << "len is " << len;
qint64 total = 0;
while (len - total > 0) {
const qint64 chunk = qMin((m_GeneratorBuffer.size() - m_pos), len - total);
memcpy(data + total, m_GeneratorBuffer.constData() + m_pos, chunk);
m_pos = (m_pos + chunk) % m_GeneratorBuffer.size();
total += chunk;
qDebug() << "total is "<}
return total;}qint64 Generator::writeData(const char *data, qint64 len){Q_UNUSED(data);Q_UNUSED(len);return 0;}qint64 Generator::bytesAvailable() const{return m_GeneratorBuffer.size() + QIODevice::bytesAvailable();}void bufferPlayback::startPlayback(){if (m_audioOutput) {m_mode = QAudio::AudioOutput;m_dataLengthPlay = 0;m_generator->start();m_audioOutput->start(m_generator);}
}
4,因为上述方法我认为是不能并发的原因造成的,所以我在这种方案中用了并发。但是该方案也实现不了执行结果同方案3,我分析是可能Qt有自动锁的机制即在读buffer的时候写buffer的thread自动被block。但是不是这样,因为我对qt也刚接触,并且我学C++编程始于3个月前。所以也没有办法去验证,并且没有查到任何这方面的资料——qt 有读写保护即mutex,所以我对我的猜测也很怀疑
PlaybackThread::PlaybackThread(QByteArray buffer, QAudioOutput *audioOutput){m_generator = 0;m_audioOutput = audioOutput;m_buffer = buffer;m_generator = new Generator(m_buffer, this);}PlaybackThread::~PlaybackThread(){}void PlaybackThread::run(){m_generator->start();m_audioOutput->start(m_generator);}Generator::Generator(QByteArray buffer, QObject *parent): QIODevice(parent){qDebug() << "Generator has thread " << thread()->currentThreadId();m_GeneratorBuffer = buffer;m_pos=0;}void bufferPlayback::startPlayback(){if (m_audioOutput != 0) {m_mode = QAudio::AudioOutput;qDebug() << "in startPlayback";m_playbackThread = new PlaybackThread(m_buffer, m_audioOutput);m_playbackThread->start();qDebug() << "in startPlayback";m_playbackThread->wait();}
}
在分析了所有输出的方案后(事实上,除了第二种方案是我根据spectrum analyzer example的输入获得的灵感和第四种方案是我为了解决第三个方案的不能同步外,其他三个例子都有qt 文档中的example原封不动,一模一样的例子)
方案3的代码:
bufferPlayback_buffercircular.pro
######################################################################
# Automatically generated by qmake (2.01a) to 26. huhti 12:54:01 2012######################################################################
QT += core multimediaTEMPLATE = appTARGET =DEPENDPATH += .INCLUDEPATH += .# InputHEADERS += bufferPlayback.h \bufferPlayback_buffercircular.h
FORMS += mainwindow.uiSOURCES += main.cpp \bufferPlayback_buffercircular.cpp
main.cpp
#include#include "bufferPlayback_buffercircular.h"int main(int argc, char *argv[]){QApplication a(argc, argv);bufferPlayback w;return a.exec();}
bufferPlayback_buffercircular.h
#ifndef AUDIOPLAYBACK_H#define AUDIOPLAYBACK_H#include#include#include#include#include#include#include#include#include#includeclass bufferPlayback: public QObject{Q_OBJECTpublic:
~bufferPlayback();bufferPlayback();qint64 bufferLength() const;public slots:void startRecording();void startPlayback();void captureDataFromDevice();void captureDataIntoDevice();signals:
void mysignal();private:qint64 audioLength(const QAudioFormat &format, qint64 microSeconds);QAudio::Mode m_mode;void selectFormat();void stopPlayback();void initialize();void stopRecording();qint64 m_dataLengthRecord;qint64 m_dataLengthPlay;qint64 m_bufferLength;qint64 m_bytesReady;QAudioFormat format;QAudioInput* m_audioInput;QAudioDeviceInfo m_audioInputDevice;QIODevice* m_temp;QIODevice* m_audioInputIODevice;QAudioDeviceInfo m_audioOutputDevice;QAudioOutput* m_audioOutput;qint64 m_playPosition;QBuffer m_audioOutputIODevice;QByteArray m_buffer;bool isOpen;};#endif // AUDIOPLAYBACK_H
bufferPlayback_buffercircular.cpp
#include "bufferPlayback_buffercircular.h"// The buffer length is assigned in audioLength function// now it is set the audio duraton length and get the audio length thereafterconst qint64 BufferDurationUs = 10 * 1000000; // 10secondbufferPlayback::bufferPlayback(): m_mode(QAudio::AudioInput), m_audioInput(0), m_audioInputDevice(QAudioDeviceInfo::defaultInputDevice()), m_audioInputIODevice(0), m_audioOutputDevice(QAudioDeviceInfo::defaultOutputDevice()), m_audioOutput(0), m_bufferLength(0), m_dataLengthPlay(0), m_dataLengthRecord(0), isOpen(true){selectFormat();
initialize();
// I don't think the following device assignment statements are necessary// but in order to avoid the echo pulse noise, I have to do this:QList<QAudioDeviceInfo> inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);m_audioInputDevice = inputDevices.at(0);QList<QAudioDeviceInfo> outputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);m_audioOutputDevice = outputDevices.at(0);QTimer::singleShot(1000, reinterpret_cast<QObject*>(this), SLOT(startPlayback()));startRecording();
// default is shown I guess it is because the value is initialized as defaultInputDevice()// used for debug - show the device//qDebug() << "Device name: " << m_audioInputDevice.deviceName();//qDebug() << "Device name: " << m_audioOutputDevice.deviceName();// test first and then make it compatible// if (devices.size()>1) {// for(int i = 0; i < devices.size(); ++i) {// qDebug() << "Device name: " << devices.at(i).deviceName();// }// }}bufferPlayback::~bufferPlayback(){stopRecording();
stopPlayback();
delete m_audioInput;delete m_audioOutput;}// return the audio data length (the length that buffer needs)//
qint64 bufferPlayback::audioLength(const QAudioFormat &format, qint64 microSeconds){qint64 result = (format.frequency() * format.channels() * (format.sampleSize() / 8))* microSeconds / 1000000;result -= result % (format.channelCount() * format.sampleSize());return result;}qint64 bufferPlayback::bufferLength() const{return m_bufferLength;}void bufferPlayback::initialize(){m_bufferLength = audioLength(format, BufferDurationUs);m_buffer.resize(m_bufferLength);m_buffer.fill(0);m_audioInput = new QAudioInput(m_audioInputDevice, format, this);m_audioOutput = new QAudioOutput(m_audioOutputDevice, format, this);}void bufferPlayback::startRecording(){if (m_audioInput) {m_buffer.fill(0);m_mode = QAudio::AudioInput;m_dataLengthRecord = 0;m_audioInputIODevice = m_audioInput->start();connect(m_audioInputIODevice, SIGNAL(readyRead()),this,SLOT(captureDataFromDevice()));}
}void bufferPlayback::startPlayback(){if (m_audioOutput) {m_mode = QAudio::AudioOutput;m_dataLengthPlay = 0;m_temp = m_audioOutput->start(/*&m_audioOutputIODevice*/);connect (this, SIGNAL(mysignal()),SLOT(captureDataIntoDevice()));}
}void bufferPlayback::stopRecording(){if (m_audioInput) {m_audioInput->stop();QCoreApplication::instance()->processEvents();m_audioInput->disconnect();}
m_audioInputIODevice = 0;}void bufferPlayback::stopPlayback(){if (m_audioOutput) {m_audioOutput->stop();QCoreApplication::instance()->processEvents();m_audioOutput->disconnect();}
}void bufferPlayback::selectFormat(){format.setFrequency(8000);format.setChannels(2);format.setSampleSize(8);format.setCodec("audio/pcm");format.setByteOrder(QAudioFormat::LittleEndian);format.setSampleType(QAudioFormat::UnSignedInt);QAudioDeviceInfo input_info(QAudioDeviceInfo::defaultInputDevice());if (!input_info.isFormatSupported(format)) {qWarning()<<"default format not supported try to use nearest";format = input_info.nearestFormat(format);}
QAudioDeviceInfo output_info(QAudioDeviceInfo::defaultOutputDevice());if (!output_info.isFormatSupported(format)) {qWarning()<<"raw audio format not supported by backend, cannot play audio.";}
}// push data from buffer into speakervoid bufferPlayback::captureDataIntoDevice(){if (isOpen) {m_temp->open(QIODevice::WriteOnly);isOpen = false;}
qint64 bytesWrite = m_temp->write(m_buffer.data()+m_dataLengthPlay, m_bytesReady);if (bytesWrite) {m_dataLengthPlay += bytesWrite;}
if (m_buffer.size() == m_dataLengthPlay) {m_dataLengthPlay = 0;}
}// push data from Mic into buffervoid bufferPlayback::captureDataFromDevice(){const qint64 bytesReady = m_audioInput->bytesReady();const qint64 bytesSpace = m_buffer.size() - m_dataLengthRecord; // what is m_dataLength?const qint64 bytesToRead = qMin(bytesReady, bytesSpace);const qint64 bytesRead = m_audioInputIODevice->read(m_buffer.data()+m_dataLengthRecord,bytesToRead);if (bytesRead) {m_dataLengthRecord += bytesRead;}
if (m_buffer.size() == m_dataLengthRecord) {m_dataLengthRecord = 0;qDebug() << "in capture Data buffer is full";}
emit mysignal();}
如果您想要打包的其他方案的代码,请留下联系方式我会发邮件给您
2014年10月16日编辑:其他方案代码上传至 http://download.csdn.net/detail/kakadiablo/8044021 不会再发送至您的邮箱