【问题标题】:Qt - how to record and play sound simultaneouslyQt - 如何同时录制和播放声音
【发布时间】:2011-10-15 05:35:47
【问题描述】:

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

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

有没有办法在 Qt 中做到这一点?还是我需要使用任何其他库?

如果解决方案是跨平台的就好了(我需要涵盖 windows、linux 和 mac)。如果不可能,那么 linux 解决方案就可以了。

顺便说一句,我使用的是 Qt 4.7。

编辑

我的最新实现是here。我创建了QIODevice 的子类并重新实现了它的writeDatareadData 方法,以便可以使用循环缓冲区完成读取和写入。我按照this suggestion 做了这个。这段代码也不起作用,因为QAudioOutput 实例面向Underrun Error,根据this documentation 意味着 -

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

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

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

我也尝试使用Phonon 解决此问题,但失败了,因为我对该模块没有足够的了解。

【问题讨论】:

  • @BrianRoach:我什么都没试过,因为我找不到开始的方法。我知道我可以使用 QAudioInput 进行声音输入并播放声音,我可以使用 QAudioOutput,但这两者都适用于文件,即 QAudioInput 将输入存储在文件中,然后 QAudioOutput 播放该文件中的声音。这种方法在全双工场景中肯定行不通,不是吗?我找到了一些以前的答案,但它们都很旧,他们建议使用其他库,如 openAL、portAudio 等。我想知道是否有任何使用 Qt 库的解决方案。

标签: c++ qt4


【解决方案1】:

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

我查看了您的代码,我认为总的来说您的想法应该可行。不过我看到了一些问题:

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

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

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

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

祝你好运。

【讨论】:

  • 谢谢米格尔。您的回答非常有用。我会记住这些:-)。
【解决方案2】:

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

QAudioInputstart()方法返回的QIODevice交给QAudioOutputstart()方法:

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

【讨论】:

  • 我试过你的方法。起初它似乎工作,但一段时间后,输出状态变为空闲。这可能是因为同步问题或其他原因,我不知道。我在编辑中发布我的代码,以便您查看。
  • 我已经弄清楚发生了什么。 audioOutput 对象面临欠载错误。
  • 我现在用一个程序来做这件事。它可以工作,但会产生带有大量静电的输出。为什么会这样? Here 是我的问题,我的代码是什么样的
【解决方案3】:

这样启动输入输出设备

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

【讨论】:

    【解决方案4】:

    下面是用 QT5 编写的代码,用于读取音频输入、麦克风,并将其放入 64K 循环缓冲区。一旦缓冲区有数据,它就会将其写入音频输出,即 PC 上的扬声器。这是基本的代码,应该是熟悉声音设备的良好起点。请注意,这里的声音输入和输出在一个对象中,这可能会导致缓冲区问题。为了克服这一点,为输入和输出创建一个单独的对象。 该程序在两个文件中,第一个是 qt profile (.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;}
    

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 不,我没有尝试过,实际上我不知道存在这种方式(我是Qt的初学者)。让我试试这个方法。
      • 我不知道如何在这两个类之间创建路径,因为它们都不是 Phonon 的一部分。
      • @SayemAhmed 好点。我编辑了我的回复来解决你的问题。
      • 我也试过你的方法,但我得到了一些例外。请查看编辑。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-12
      相关资源
      最近更新 更多