Qt - how to record and play sound simultaneously

谁说胖子不能爱 提交于 2020-02-26 07:12:55

问题


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.


回答1:


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.




回答2:


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 ); 



回答3:


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




回答4:


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;}



回答5:


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.



来源:https://stackoverflow.com/questions/7776022/qt-how-to-record-and-play-sound-simultaneously

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!