Qt - 如何同时录制和播放声音

时间:2011-10-15 05:35:47

标签: c++ qt4

我在Qt论坛上发布了这个问题,但没有得到答案。这就是我在这里发布它的原因。

我想知道有没有办法在Qt同时录制和播放声音。我想录制麦克风的声音,同时我想在扬声器/耳机中播放声音。

有没有办法在Qt中这样做?或者我是否需要使用任何其他库?

如果解决方案是跨平台的(我需要覆盖windows,linux和mac),那将是很棒的。如果不可能,那么Linux解决方案就可以了。

我顺便使用Qt 4.7。

修改

我的最新实施是here。我创建了QIODevice的子类并重新实现了它的writeDatareadData方法,以便可以使用循环缓冲区完成读写操作。我按照this suggestion完成了这项工作。此代码也不起作用,因为QAudioOutput实例面临Underrun Error,根据this documentation表示 -

  

音频数据没有以足够快的速度馈送到音频设备

我已经应用了一个黑客来暂时解决这个问题。在outputStateChanged方法中,我正在检查输出的状态是否已更改为IDLE,如果有,我再次调用start()方法,指定公共缓冲区。我不想将此作为一种永久的解决方案,因为它感觉真的很hacky,因为我在没有正确调查其原因的情况下吞下了一个错误。

我该怎么做才能解决这个问题?

我也尝试使用Phonon来解决这个问题,但是因为我对这个模块没有足够的了解而失败了。

5 个答案:

答案 0 :(得分:9)

我对Qt不是很有经验,但我处理的是媒体,所以请原谅我,如果我的答案不是很具体,而是从更一般的角度来解决你的问题。

我查看了你的代码,我认为一般来说你的想法应该有效。我看到了一些问题:

  • writeData方法似乎没有准备好处理缓冲区满状态。当循环缓冲区填充它时,它只会覆盖旧数据,并错误地继续递增currentBufferLength变量。我认为正确的做法是更新readPosition以跳过丢失的数据,并防止currentBufferLength超过缓冲区大小。

  • 你几乎同时开始作家和读者。相反,您应该启动编写器并填充循环缓冲区,然后启动阅读器。请记住,您将永远无法以零延迟进行录制和播放。至少你的延迟将是单个缓冲区写入的大小,但实际上你可能需要编写器提前几个缓冲区来避免打嗝。

  • 您应该分别调试阅读器和编写器。仅设置编写器并验证是否定期写入循环缓冲区(首先按照我的建议修复溢出条件)。要进行调试,可以将缓冲区转储到文件中,然后在音频播放器(例如Audacity)中检查文件,或者可以使用printf调试来确保始终获取数据。然后只用读者做类似的事情。

  • 最后的想法。调用readDatawriteData方法的代码可能正在其他线程上运行,可能是两个不同的线程,一个用于读取器,另一个用于写入器。如果我的猜测是正确的,那么你的圆形结构就会出现很大的问题。您必须保护对确定读写位置和大小的变量的访问,否则您将有竞争条件。

祝你好运。

答案 1 :(得分:2)

我不明白为什么使用您在评论中提到的课程会出现问题。两者都不仅限于使用文件。

QIODevice start()方法返回QAudioInput,并将其转换为start() QAudioOutput方法:

QIODevice *myDevice = myQAudioInput->start();
myQAudioOutput->start( myDevice ); 

答案 2 :(得分:2)

像这样启动输入和输出设备

m_output= m_audioOutput->start();
    m_input = m_audioInput->start();
    connect(m_input, SIGNAL(readyRead()), SLOT(readMore()));

并将输入样本写入readMore()

中的输出
m_output->write(outdata, len);

请查看此文章了解更多信息 此示例应用程序在Qt中创建,将从麦克风录制并同时播放音频 http://www.codeproject.com/Articles/421287/Cross-Platform-Microphone-Audio-Processing-Utility

答案 3 :(得分:2)

下面是用QT5编写的代码,用于读取音频输入,麦克风,并将其放入64K循环缓冲区。一旦缓冲区有数据,它就会将其写入音频输出,PC上的扬声器。这是简单的代码,应该是熟悉声音设备的良好起点。请注意,此处声音输入和输出位于一个对象中,这可能会导致缓冲区问题。为此,它为输入和输出创建了一个单独的对象。 该程序有两个文件,第一个是qt配置文件(.pro),第二个是main.cpp文件。

#AudioEcho.pro file for QT5.2.1

QT       += core
QT       -= gui
QT += multimedia widgets
TARGET = AudioEcho
CONFIG   += console
CONFIG   -= app_bundle
TEMPLATE = app
SOURCES += main.cpp


//main.cpp file
#include <QDebug>
#include <QIODevice>
#include <QAudioInput>
#include <QAudioOutput>
#include <QCoreApplication>

class myAudio :public QIODevice
{
  // Q_OBJECT

public:
     QAudioOutput *audioOut;
     QAudioInput  *audioIn;

     myAudio();
    ~myAudio(){}
    void fillBuffer();
     QAudioFormat formatIn,formatOut;
     QByteArray buff;
     char *pbuff;
     quint64 RXbuff;
     quint64 buffPtr;
protected:
     qint64 readData(char *data, qint64 maxlen);
     qint64 writeData(const char *data, qint64 len);
     qint64 bytesAvailable() const;
};

#define SAMPLE_RATE 22050
#define CHANNELS 1
#define SAMPLE_SIZE 16
#define SAMPLE_TYPE SignedInt

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    myAudio *m= new myAudio();
    return a.exec();
}
myAudio::myAudio()
    {
    formatIn.setSampleRate(SAMPLE_RATE);
    formatIn.setChannelCount(CHANNELS);
    formatIn.setSampleSize(SAMPLE_SIZE);
    formatIn.setCodec("audio/pcm");
    formatIn.setByteOrder(QAudioFormat::LittleEndian);
    formatIn.setSampleType(QAudioFormat::SAMPLE_TYPE);

    formatOut.setSampleRate(SAMPLE_RATE);
    formatOut.setChannelCount(CHANNELS);
    formatOut.setSampleSize(SAMPLE_SIZE);
    formatOut.setCodec("audio/pcm");
    formatOut.setByteOrder(QAudioFormat::LittleEndian);
    formatOut.setSampleType(QAudioFormat::SAMPLE_TYPE);

//print out the output device setup parameters
     QAudioDeviceInfo          deviceOut(QAudioDeviceInfo::availableDevices(QAudio::AudioOutput).at(0));     //select output device 0
     qDebug()<<"Selected Output device ="<<deviceOut.deviceName();

//print out the input device setup parameters
     QAudioDeviceInfo     deviceIn(QAudioDeviceInfo::availableDevices(QAudio::AudioInput).at(0));     //select output device 0
     qDebug()<<"Selected input device ="<<deviceIn.deviceName();

//configure device
     audioOut = new QAudioOutput(deviceOut,formatOut,0);
     audioIn  = new QAudioInput (deviceIn, formatIn,0);

//print out the device specifications
     foreach(const QAudioDeviceInfo &deviceInfo,     QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
          {
          qDebug() << "\nSuported Input devices";
          qDebug() << "\nDevice name: "             << deviceInfo.deviceName();
          qDebug() << "Supported channel count: "   << deviceInfo.supportedChannelCounts();
          qDebug() << "Supported Codec: "           << deviceInfo.supportedCodecs();
          qDebug() << "Supported byte order: "      << deviceInfo.supportedByteOrders();
          qDebug() << "Supported Sample Rate: "     << deviceInfo.supportedSampleRates();
          qDebug() << "Supported Sample Size: "     << deviceInfo.supportedSampleSizes();
          qDebug() << "Supported Sample Type: "     << deviceInfo.supportedSampleTypes();
          qDebug() << "Preferred Device settings:"  << deviceInfo.preferredFormat();
          }
     foreach(const QAudioDeviceInfo &deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
         {
         qDebug() << "\nSuported output devices";
         qDebug() << "Device name: "             << deviceInfo.deviceName();
         qDebug() << "Supported channel count: "   << deviceInfo.supportedChannelCounts();
         qDebug() << "Supported Codec: "           << deviceInfo.supportedCodecs();
         qDebug() << "Supported byte order: "      << deviceInfo.supportedByteOrders();
         qDebug() << "Supported Sample Rate: "     << deviceInfo.supportedSampleRates();
         qDebug() << "Supported Sample Size: "     << deviceInfo.supportedSampleSizes();
         qDebug() << "Supported Sample Type: "     << deviceInfo.supportedSampleTypes();
         qDebug() << "Preferred Device settings:"  << deviceInfo.preferredFormat();
         }

      buff.resize(0x10000);   //create a rx buffer

      pbuff=buff.data();       //get the buff address;
      RXbuff=0;                //set RX buffer pointer

      qDebug()<<"File open"<<open(QIODevice::ReadWrite);
      qDebug()<<"is device Sequential="<<isSequential();
      audioIn->start(this); //start reading device

      audioOut->setVolume(0.5);  //volume 0 to 1.0
      audioOut->start(this);    //start writing to device
}

//QIODevice Class (Protected Functions)This function is called by QIODevice.
//send to output(Speaker)
qint64 myAudio::readData(char *data, qint64 len)
{
static quint64 TXbuff=0;
qint64 total = 0;
while (len > total  && RXbuff>TXbuff)//write and synchonise buffers
       {
         //write data to speaker
        memcpy(&data[total],&pbuff[TXbuff%0x10000],2);    //copy 2 Bytes
        TXbuff+=2; //point to next buffer 16 bit location
        total+=2;
       }
return total;  //the reset interval
}


//audio input (from Microphone)
qint64 myAudio::writeData(const char *data, qint64 len)
{
int total=0;
while (len > total)
       {
        memcpy(&pbuff[RXbuff%0x10000],&data[total], 2); //write 2Bytes into circular buffer(64K)
        RXbuff+=2; //next 16bit buffer location
        total+=2;  //next data location
      }
return (total); //return total number of bytes received
}

qint64 myAudio::bytesAvailable() const{return 0;}

答案 4 :(得分:1)

您从启动QAudioInput获取QIOStream并使用它来创建Phonon :: MediaSource。然后在Phonon :: MediaSource和Phonon :: AudioOutput对象之间创建一条路径。有关Phonon::AudioOutputPhonon::MediaSource的详细信息,请参阅结帐文档。