2011-10-15 52 views
13

我在Qt論壇發佈了這個問題,但沒有得到答案。這就是我在這裏發佈的原因。Qt - 如何同時錄製和播放聲音

我想知道有沒有什麼方法可以在Qt中同時錄製和播放聲音。我想錄制來自麥克風的聲音,同時我想在揚聲器/耳機中播放它。

有什麼辦法可以在Qt中做到這一點?或者我需要使用任何其他庫?

如果解決方案是跨平臺的(我需要覆蓋windows,linux和mac),那將會很棒。如果這是不可能的,那麼一個linux解決方案就可以做到。

我正在使用Qt 4.7。

編輯

我最近實施給出here。我創建了QIODevice的子類並重新實現了其writeDatareadData方法,以便可以使用循環緩衝區完成讀取和寫入操作。我已按照this suggestion完成此操作。此代碼也不起作用,因爲QAudioOutput實例面Underrun Error,根據this documentation這意味着 -

音頻數據不被饋送到音頻設備以足夠快的速率

我已經申請暫時解決這個問題的黑客。在outputStateChanged方法中,我檢查輸出的狀態是否已更改爲IDLE,如果有,我再次調用start()方法,指定公用緩衝區。我不想將此作爲一個永久的解決方案,因爲它感覺非常不好,因爲我沒有正確地調查它的原因就吞嚥了一個錯誤。

我該怎麼做才能解決這個問題?

我也嘗試使用Phonon來解決這個問題,但是失敗了,因爲我對這個模塊沒有足夠的瞭解。

+0

@BrianRoach:我還沒有嘗試過任何東西,因爲我找不到一種方法開始。我知道我可以使用聲音輸入usinq QAudioInput並播放聲音,我可以使用QAudioOutput,但這兩種聲音都可以在文件上運行,即QAudioInput將輸入存儲在文件中,然後QAudioOutput播放該文件中的聲音。這種方法肯定不能在全雙工場景中運行,是嗎?我發現了一些以前的答案,但他們都很老,他們建議使用其他庫,如openAL,portAudio等。我想知道是否有任何使用Qt庫的解決方案。 –

回答

9

我不是很有經驗使用Qt,但我與處理媒體,所以請原諒我如果我的答案不是很具體,而是從更一般的觀點來解決你的問題。

我看着你的代碼,我認爲你的想法應該可行。我看到一些問題,但:

  • writeData方法似乎不準備處理緩衝區滿條件。當循環緩衝區填滿時,它只會覆蓋舊數據,並錯誤地繼續增加currentBufferLength變量。我認爲這裏要做的正確的事情是更新readPosition以跳過丟失的數據,並防止currentBufferLength超過緩衝區大小。

  • 你幾乎在同一時間開始了作家和讀者。相反,您應該啓動作者並填充循環緩衝區,然後啓動讀取器。請記住,您永遠無法記錄和播放零延遲。至少你的延遲將是單個緩衝區寫入的大小,但實際上你可能需要寫一些緩衝區來避免打嗝。

  • 您應該分別調試讀寫器和寫入器。只設置寫入程序並驗證循環緩衝區是否定期寫入(首先按照上面的建議修復溢出情況)。爲了進行調試,您可以將緩衝區轉儲到文件中,然後在音頻播放器(例如Audacity)中檢查文件,或者可以使用printf調試來確保不斷獲取數據。然後只做一個類似的讀者。

  • 最後的想法。調用您的readDatawriteData方法的代碼可能在其他線程上運行,可能是兩個不同的線程,一個用於讀者,另一個用於寫入器。如果我的猜測是正確的,那麼你的圓形結構有一個很大的問題。您必須保護對決定讀取和寫入位置和大小的變量的訪問權限,否則您將遇到競爭狀況。

祝你好運。

+0

謝謝米格爾。你的回答非常豐富。我會牢記這些:-)。 –

1

您將QIOStream從啓動QAudioInput中獲取並用於創建Phonon :: MediaSource。然後在該Phonon :: MediaSource和Phonon :: AudioOutput對象之間創建一條路徑。有關Phonon::AudioOutputPhonon::MediaSource的更多詳細信息結帳文檔。

+0

不,我沒有嘗試過,實際上我不知道這種方式存在(我是Qt的初學者)。讓我試試這種方法。 –

+0

我不知道如何在這兩個類之間創建路徑,因爲它們都不是Phonon的一部分。 –

+0

@SayemAhmed好點。我編輯了我的回覆以解決您的問題。 –

2

我不明白爲什麼使用您在評論中提到的類會出現問題。也不限於僅使用文件。

採取從QAudioInputstart()方法返回的QIODevice,並把它交給start()方法QAudioOutput

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

我試過你的方法。起初它似乎工作,但一段時間後,輸出狀態變爲空閒狀態。這可能是因爲同步問題或其他原因,我不知道。我在編輯中發佈我的代碼,以便您可以看看。 –

+0

我已經想清楚發生了什麼。 audioOutput對象正面臨欠載運行錯誤。 –

+0

我又遇到了一些麻煩。請參閱編輯。 –

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