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

发布于 2024-12-10 00:29:13 字数 1463 浏览 1 评论 0原文

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

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

Qt 有什么办法可以做到这一点吗?或者我需要使用其他库吗?

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

顺便说一句,我正在使用 Qt 4.7。

编辑

此处给出了我的最新实现。我创建了 QIODevice 的子类并重新实现了其 < a href="https://doc.qt.io/qt-5/qiodevice.html#writeData" rel="nofollow noreferrer">writeData 和 readData 方法,以便可以使用循环缓冲区进行读写。我已按照此建议执行此操作。此代码也不起作用,因为 QAudioOutput 实例面临 Underrun错误,根据此文档表示-

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

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

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

我还尝试使用 Phonon 解决这个问题,但失败了,因为我不对这个模块有足够的了解。

I posted this question on the Qt forum, but got no answers. That's why I am posting it here.

I wanted to know is there any way to record and play sound at the same time in Qt. I want to record sound from a microphone and at the same time I want to play it in the speaker/headphone.

Is there any way to do this in Qt? Or do I need to use any other library?

It would be great if the solution is cross-platform (I need to cover windows, linux and mac). If it isn't possible, then a linux solution will do.

I am using Qt 4.7 by the way.

Edit

My latest implementation is given here. I have created a sub-class of the QIODevice and re-implemented its writeData and readData method so that reading and writing can be done with a circular buffer. I have done this as per this suggestion. This code also doesn't work because the QAudioOutput instance faces Underrun Error, which according to this documentation means -

Audio data is not being fed to the audio device at a fast enough rate

I have applied a hack to solve this problem temporarily. In the outputStateChanged method, I am checking to see if the state of the output has changed to IDLE and if it has, I am again calling start() method, specifying the common buffer. I don't want to use this as a permanent solution because it feels really hacky and because I am swallowing an error without properly investigating its reasons.

What should I do to solve this problem?

I also tried to solve this using Phonon but failed because I do not have sufficient knowledge of this module.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

只为守护你 2024-12-17 00:29:13

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

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

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

  • 您几乎同时启动了作者和读者。相反,您应该启动写入器并准备循环缓冲区,然后启动读取器。请记住,您永远无法以零延迟进行录制和播放。至少您的延迟将是单个缓冲区写入的大小,但实际上您可能需要写入器领先几个缓冲区以避免出现问题。

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

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

祝你好运。

I'm not very experienced with Qt, but I am with handling media, so forgive me if my answer isn't very specific but instead addresses your problem from a more general point of view.

I looked at your code, and I think in general your idea should work. I see some problems though:

  • the writeData method doesn't seem to be prepared to handle a buffer full condition. When the circular buffer fills it'll just overwrite old data, and incorrectly continue to increment the currentBufferLength variable. I think the correct thing to do here is to update the readPosition to skip over the data that was lost, and to prevent currentBufferLength from ever growing past the buffer size.

  • You are starting both the writer and the reader at pretty much the same time. Instead, you should start the writer and prime the circular buffer, then start the reader. Keep in mind that you will never be able to record and play with zero latency. At the very least your latency will be the size of an individual buffer write, but in practice you'll probably need the writer to be ahead by a few buffers to avoid hiccups.

  • You should debug the reader and the writer separately. Set up only the writer and verify that the circular buffer is getting written to at regular intervals (first fix the overflow condition as I suggested above). To debug, you can dump the buffers to a file and then check the file in an audio player (Audacity, for example), or you can use printf debugging to ensure you are constantly getting data. Then do something similar with only a reader.

  • Final thought. The code that calls your readData and writeData methods is probably running on other threads, likely two different threads, one for the reader and another one for the writer. If my guess is correct, then you have a big problem with your circular structure. You have to protect access to the variables that determine the read and write positions and sizes, if not you will have race conditions.

Good luck.

国产ˉ祖宗 2024-12-17 00:29:13

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

获取从 QAudioInputstart() 方法返回的 QIODevice 并将其传递给QAudioOutput

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

I don't see why there would be a problem using the classes you mention in your comment. Neither are restricted to just using files.

Take the QIODevice returned from the start() method of QAudioInput and give it to the start() method of QAudioOutput:

QIODevice *myDevice = myQAudioInput->start();
myQAudioOutput->start( myDevice ); 
后知后觉 2024-12-17 00:29:13

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

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 /跨平台麦克风音频处理实用程序

Start the input and output device like this

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

and write the input sample to output in readMore()

m_output->write(outdata, len);

Please look at this article for more.
This sample application is created in Qt will record from microphone and play audio simultaneously
http://www.codeproject.com/Articles/421287/Cross-Platform-Microphone-Audio-Processing-Utility

千纸鹤 2024-12-17 00:29:13

下面是用 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;}

Below is the code written in QT5 to read the audio input, the microphone, and places it into a 64K circular buffer. Once the buffer has data it writes it to audio output, the speaker on the PC. This is the bare bones code that should be a good starting point for getting familiar with the sound device. Note, that here the sound input and output are in one object this may cause buffer problems. To over come this create a separate objects for the input and output.
The program is in two file, the first being qt profile (.pro) and the second being the main.cpp file.

#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;}
A君 2024-12-17 00:29:13

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

You take the QIOStream that you get from starting the QAudioInput and use it to create a Phonon::MediaSource. Then you create a path between that Phonon::MediaSource and a Phonon::AudioOutput object. For more details checkout documentation for Phonon::AudioOutput and Phonon::MediaSource.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文