Obecnie tworzę, a właściwie prawie utworzyłem program rysujący spectrogram dźwięku, który się słyszy. Jakiś czas temu szukałem i próbowałem obsłużyć samo nagrywanie dźwięku. Postanowiłem napisać klasę, która zdecydowanie ułatwi pracę. Można ją wykorzystać w każdym programie w Qt wykorzystującym nagrywanie dźwięku w czasie rzeczywistym.
Obiekt klasy powinien mieć następującą funkcjonalność:
- Ustawienie parametrów dźwięku (próbkowanie, rozdzielczość, mono/stereo)
- Rozpoczęcie nagrywania
- Zakończenie nagrywania
- Podczas nagrywania jest wywoływane zdarzenie timera, który w parametrze podaje liczbę próbek znajdujących się w buforze i procedura timera służy do wywołania dalszego przetwarzania, częstotliwość timera nie musi być stabilna, można pozwolić sobie na opuszczenie pojedynczych wywołań timera
- Możliwie małe opóźnienie i jednocześnie dowolnie długi programowy bufor przechowujący zarejestrowane próbki.
Teoretycznie nie mam żadnego problemu, wszystko działa tak, jak chciałem, ale w Windows 8 dostrzegłem pewien problem, który polega na tym, że jak się trzyma myszką pasek tytułu okna lub jeden z przycisków na tym pasku, to timer nie jest wywoływany. Wyczytałem, że to nie jest problem Qt, tylko jest to problem WinApi i że pewnym sposobem obejścia tego problemu jest wielowątkowość. Przez ten problem, wystarczy przez sekundę przytrzymać myszkę, żeby program pracował nieprawidlowo a czasem nawet przestał działać.
Czy da się ten problem rozwiązać w klasie do obsługi nagrywania tak, że jak timery są zablokowane, to wszystko, co leci jest buforowane i przy najbliższym wywołaniu jest więcej próbek do przetworzenia? Jak widać, QAudioInput ma w sobie timer, pod który jest podczepiona procedura wydobywająca wszystko z bufora sprzętowego i wprowadzająca do bufora programowego, który ja zaimplementowałem. Zdarzenie (sygnał) to "notify", a procedura (slot) to "FillAudioBuffer".
Tworzenie i nieniszczenie tablicy w procedurach GetFromBuffer8 i GetFromBuffer16 jest zamierzone. W miejscu wywołania procedury, która zwróci wskaźnik do tej tablicy, po wykorzystaniu tych danych należy zniszczyć tablice instrukcja "delete[]".
#ifndef AUDIORECORDER_H
#define AUDIORECORDER_H
#include <QtGui>
#include <QIODevice>
#include <QAudioInput>
#include <QAudioFormat>
#include <QTime>
#include <stdio.h>
#include <queue>
#include "eden.h"
namespace EdenClass
{
class AudioRecorder : public QObject
{
Q_OBJECT
public:
AudioRecorder();
~AudioRecorder();
void SetParams(int SampleRate, int SampleSize, int Channels);
void SetParams(int SampleRate, int SampleSize, int Channels, int AudioBufSize, int AudioBufInterval);
void RecordStart();
void RecordStop();
void BufferFlush();
int GetAudioRemaining();
char *GetFromBuffer8(int BufLen);
short *GetFromBuffer16(int BufLen);
private:
int SizeFactor;
queue<char*> BufQueue;
queue<int> BufLenQueue;
QAudioInput* mAudioInput;
QIODevice* mIOInput;
bool Working;
int BufSize;
int NotifyInterval;
int AudioPointer;
int TotalLength;
void RemoveHeapObjects(bool Aud);
signals:
void TimerTick(int BufferRemain);
private slots:
void FillAudioBuffer();
};
}
#endif // AUDIORECORDER_H
#include "audiorecorder.h"
namespace EdenClass
{
AudioRecorder::AudioRecorder()
{
mAudioInput = 0;
mIOInput = 0;
Working = false;
TotalLength = 0;
}
AudioRecorder::~AudioRecorder()
{
RemoveHeapObjects(true);
}
// Zniszczenie istniejacych obiektow na stercie
void AudioRecorder::RemoveHeapObjects(bool Aud)
{
Working = false;
if (Aud)
{
if (mAudioInput != 0)
{
delete mAudioInput;
mAudioInput = 0;
}
}
if (mIOInput != 0)
{
delete mIOInput;
mIOInput = 0;
}
}
// Okreslanie parametrow nagrywania
void AudioRecorder::SetParams(int SampleRate, int SampleSize, int Channels)
{
int XXX = SampleRate * SampleSize * Channels;
SetParams(SampleRate, SampleSize, Channels, XXX / 2, 100);
}
// Okreslanie parametrow nagrywania z okresleniem wielkosci bufora i interwalu czasowego zdarzen
void AudioRecorder::SetParams(int SampleRate, int SampleSize, int Channels, int AudioBufSize, int AudioBufInterval)
{
RemoveHeapObjects(true);
SizeFactor = Channels * SampleSize;
BufSize = AudioBufSize;
NotifyInterval = AudioBufInterval;
QAudioFormat QAF;
QAF.setChannelCount(Channels);
QAF.setSampleRate(SampleRate);
QAF.setSampleSize(SampleSize * 8);
QAF.setCodec("audio/pcm");
QAF.setByteOrder(QAudioFormat::LittleEndian);
QAF.setSampleType(QAudioFormat::SignedInt);
QAudioDeviceInfo QAFI(QAudioDeviceInfo::defaultInputDevice());
if (!QAFI.isFormatSupported(QAF))
{
QAF = QAFI.nearestFormat(QAF);
}
mAudioInput = new QAudioInput(QAFI, QAF, this);
mAudioInput->setNotifyInterval(NotifyInterval);
mAudioInput->blockSignals(false);
mAudioInput->setBufferSize(BufSize);
QObject::connect(mAudioInput, SIGNAL(notify()), this, SLOT(FillAudioBuffer()));
}
// Rozpoczecie nagrywania
void AudioRecorder::RecordStart()
{
while (!BufQueue.empty())
{
delete[] BufQueue.front();
BufQueue.pop();
}
while (!BufLenQueue.empty())
{
BufLenQueue.pop();
}
TotalLength = 0;
AudioPointer = 0;
mIOInput = mAudioInput->start();
Working = true;
}
// Zakonczenie nagrywania
void AudioRecorder::RecordStop()
{
Working = false;
mAudioInput->stop();
mIOInput->readAll();
mIOInput = 0;
RemoveHeapObjects(false);
}
// Odczyt danych z urzadzenia dzwiekowego do bufora
void AudioRecorder::FillAudioBuffer()
{
if (Working)
{
char* TempBuf;
QByteArray TempBufQ = mIOInput->readAll();
int TempBufL = TempBufQ.size();
TempBuf = new char[TempBufL];
for (int I = 0; I < TempBufL; I++)
{
TempBuf[I] = TempBufQ[I];
}
BufQueue.push(TempBuf);
BufLenQueue.push(TempBufL);
TotalLength += TempBufL;
}
emit TimerTick(GetAudioRemaining() / SizeFactor);
}
// Liczba nieodczytanych bajtow w buforze
int AudioRecorder::GetAudioRemaining()
{
return TotalLength - AudioPointer;
}
// Pobranie dzwieku 8-bit z bufora
char *AudioRecorder::GetFromBuffer8(int BufLen)
{
if (TotalLength <= 0)
{
TotalLength = 0;
}
BufLen = BufLen * SizeFactor;
char *TempBuf = new char[BufLen];
if (BufLen > (TotalLength - AudioPointer))
{
for (int I = (TotalLength - AudioPointer); I < BufLen; I++)
{
TempBuf[I] = 0;
}
BufLen = TotalLength - AudioPointer;
}
if (TotalLength == 0)
{
return TempBuf;
}
for (int I = 0; I<BufLen; I++)
{
TempBuf[I] = BufQueue.front()[AudioPointer];
AudioPointer++;
if (AudioPointer == BufLenQueue.front())
{
AudioPointer = 0;
TotalLength -= BufLenQueue.front();
delete[] BufQueue.front();
BufQueue.pop();
BufLenQueue.pop();
if (TotalLength == 0)
{
BufLen = I - 1;
}
}
}
return TempBuf;
}
// Pobranie dzwieku 16-bit z bufora
short *AudioRecorder::GetFromBuffer16(int BufLen)
{
char *TempBuf8 = GetFromBuffer8(BufLen);
BufLen = BufLen * SizeFactor / 2;
short *TempBuf = new short[BufLen];
for (int I = 0; I < BufLen; I++)
{
TempBuf[I] = (TempBuf8[(I << 1) + 1] << 8) + ((unsigned char)TempBuf8[(I << 1)]);
}
delete[] TempBuf8;
return TempBuf;
}
// Czyszczenie bufora
void AudioRecorder::BufferFlush()
{
mIOInput->readAll();
Working = false;
while (!BufQueue.empty())
{
delete[] BufQueue.front();
BufQueue.pop();
}
while (!BufLenQueue.empty())
{
BufLenQueue.pop();
}
TotalLength = 0;
AudioPointer = 0;
mIOInput->readAll();
Working = true;
}
}