QAudioInput - błędy przy braku wyzwalania timera

0

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ść:

  1. Ustawienie parametrów dźwięku (próbkowanie, rozdzielczość, mono/stereo)
  2. Rozpoczęcie nagrywania
  3. Zakończenie nagrywania
  4. 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
  5. 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;
    }
}
0

Opcje Wydajności -> Efekty wizualne -> Pokaż zawartość okna podczas przeciągania (zaznacz to).

Timer jest dobry do "napędzania" grafiki, ale nie nadaje się do obsługi rzeczy, które mają sztywne ramy czasowe.

0

możesz ustawić dużą wielkość bufora, ale to jest rozwiązanie bezsensu, bo zawsze znajdzie się user, który przytrzyma za długo myszkę.
Najbezpieczniej i najskuteczniej użyć wątków.
Jest to bardzo proste, zamień większość metod na sloty połącz je z odpowiednimi sygnałami. Ogólnie komunikuj się z tą klasą tylko i wyłącznie za pomocą sygnałów i slotów a Qt załatwi resztę.
Wystarczy odpowiednio użyć to z QThread i nawet nie musisz martwić się o synchronizację:

WlascicelAudioRecorder::WlascicelAudioRecorder()
{
    m_audioRecorder = new AudioRecorder(); // nie wolno nadawać wartości parent, żeby dało się obiekt przenieść od innego wątku
    … … … // konfiguruj
    m_thread = new QThread(this);
    m_audioRecorder->moveToThread(m_thread);

    connect(this, &WlascicelAudioRecorder::RecordStop,
            m_audioRecorder, &AudioRecorder::RecordStop);
    connect(this, &WlascicelAudioRecorder::RecordStart,
            m_audioRecorder, &AudioRecorder::RecordStart);
    connect(m_audioRecorder, &AudioRecorder::EmitujWynik,
            m_this, &WlascicelAudioRecorder::PrezentujWynik);
    … … …
    m_thread->start();
}

WlascicelAudioRecorder::~WlascicelAudioRecorder()
{
     m_audioRecorder->deleteLater();
     m_thread->quit();
     m_thread->wait(5000);
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1