Kuinka saada puhutun äänen kovuus selville (mikrofoni kaappaus, Unreal Engine).

Viestiketju alueella 'Ohjelmointi, pelikehitys ja muu sovelluskehitys' , aloittaja Jean_Sipulius, 18.06.2018.

  1. Jean_Sipulius

    Jean_Sipulius

    Viestejä:
    218
    Rekisteröitynyt:
    17.10.2016
    Moro! Olisi kysymys ja en oikein tietä miten sen tähän taiteilisi, että se olisi mahdollisimman selvä. Olen saanut äänikaappauksen toimimaan eräässä Unreal Engine projektissa ja ääni olisi tarkoitus lähettää verkon ylitse. Tällä hetkellä ääni toimii hyvin Push-To-Talkissa sekä myös jatkuvassa puheessa, mutta ongelma on se, että jatkuvan puheen ollessa päällä, ääni vie turhaa kaistaa, koska verkon yli lähtee jatkuvasti taulukko, joka on täynnä tavuja.

    Pääluokat ovat seuraavat. Windowssin oma legacy kirjasto, jolla windowssin äänikaappausta on tehty. .CPP koodi
    Koodi:
    #include "AudioCapturePrivatePCH.h"
    #include "LambdaRunnable.h"
    #include "FWindowsAudioCapture.h"
    
    #include "AllowWindowsPlatformTypes.h"
    #include <iostream>
    #include <MMSystem.h>
    #include <Windows.h>
    using namespace std;
    
    #pragma comment(lib, "winmm.lib")
    
    FWindowsAudioCapture::FWindowsAudioCapture()
    {
        bRunLoopActive = false;
    }
    
    void FWindowsAudioCapture::StartCapture(TFunction<void(const TArray<uint8>&)> OnAudioData /*= nullptr*/, TFunction<void(const TArray<uint8>&)> OnCaptureFinished /*= nullptr*/)
    {
        //Only attempt to start capture once. If it's active return.
        if (bRunLoopActive)
        {
            return;
        }
    
        bRunLoopActive = true;
        FThreadSafeBool* bShouldRunPtr = &bRunLoopActive;
    
        FLambdaRunnable::RunLambdaOnBackGroundThread([this, OnAudioData, OnCaptureFinished, bShouldRunPtr]()
        {
            HWAVEIN hWaveIn;
            MMRESULT result;
            WAVEFORMATEX pFormat;
    
            pFormat.wFormatTag = WAVE_FORMAT_PCM;
            pFormat.nChannels = Options.Channels;                                    //typically 1 or 2
            pFormat.nSamplesPerSec = Options.SampleRate;
            pFormat.wBitsPerSample = Options.BitsPerSample;                            // 16 for high quality, 8 for telephone-grade
            pFormat.nBlockAlign = pFormat.nChannels * pFormat.wBitsPerSample / 8;    // = n.Channels * wBitsPerSample/8
            pFormat.nAvgBytesPerSec = Options.SampleRate * pFormat.nBlockAlign;        // = nSamplesPerSec * n.Channels * wBitsPerSample/8
    
            pFormat.cbSize = 0;
    
            result = waveInOpen(&hWaveIn, WAVE_MAPPER, &pFormat, 0L, 0L, WAVE_FORMAT_DIRECT);
    
            WAVEHDR hWaveInHdr;
            TArray<uint8> AudioBuffer;
            AudioBuffer.SetNum(pFormat.nAvgBytesPerSec / 2);        //half a second
    
            hWaveInHdr.lpData = (LPSTR)AudioBuffer.GetData();
            hWaveInHdr.dwBufferLength = AudioBuffer.Num();
            hWaveInHdr.dwBytesRecorded = 0;
            hWaveInHdr.dwUser = 0L;
            hWaveInHdr.dwFlags = 0L;
            hWaveInHdr.dwLoops = 0L;
    
            waveInPrepareHeader(hWaveIn, &hWaveInHdr, sizeof(WAVEHDR));
    
            // Insert a wave input buffer
            result = waveInAddBuffer(hWaveIn, &hWaveInHdr, sizeof(WAVEHDR));
    
            result = waveInStart(hWaveIn);
    
            //The headers will now get filled and we should check them periodically for new data
            while (*bShouldRunPtr)
            {
                if (hWaveInHdr.dwFlags & WHDR_DONE)
                {
                    TArray<uint8> OutData;
                    OutData.SetNum(AudioBuffer.Num());              
                    FMemory::Memcpy(OutData.GetData(), AudioBuffer.GetData(), AudioBuffer.Num());
    
                  
    
    
                    if (OnAudioData != nullptr)
                    {
                        OnAudioData(OutData);
                    }
    
                    //Clear flags
                    hWaveInHdr.dwFlags = 0;
                    hWaveInHdr.dwBytesRecorded = 0;
                  
                    //Re-prep
                    waveInPrepareHeader(hWaveIn, &hWaveInHdr, sizeof(WAVEHDR));
                    waveInAddBuffer(hWaveIn, &hWaveInHdr, sizeof(WAVEHDR));
                }
            }
    
            waveInStop(hWaveIn);
            waveInUnprepareHeader(hWaveIn, &hWaveInHdr, sizeof(WAVEHDR));
            waveInClose(hWaveIn);
    
            if (OnCaptureFinished != nullptr)
            {
                //flush whatever is left of the buffer
                OnCaptureFinished(AudioBuffer);
            }
        });
    }
    
    void FWindowsAudioCapture::StopCapture()
    {
        bRunLoopActive = false;
    }
    
    void FWindowsAudioCapture::SetOptions(const FAudioCaptureOptions& InOptions)
    {
        Options = InOptions;
    }
    
    #include "HideWindowsPlatformTypes.h"
    
    Toinen luokka, joka ottaa äänen vastaan ja lähettää sen pelimoottorissa olevalle äänikomponentille:
    Koodi:
    #include "AudioCapturePrivatePCH.h"
    #include "ITFAudioCapture.h"
    #include "TFAudioCaptureComponent.h"
    #include "LambdaRunnable.h"
    #include "FWindowsAudioCapture.h"
    
    class FTFAudioCapture : public ITFAudioCapture
    {
    public:
    
        virtual void StartCapture(TFunction<void(const TArray<uint8>&)> OnAudioData = nullptr, TFunction<void(const TArray<uint8>&)> OnCaptureFinished = nullptr) override;
        virtual void StopCapture() override;
        virtual void SetOptions(const FAudioCaptureOptions& Options) override;
    
        virtual void AddAudioComponent(const UTFAudioCaptureComponent* Component) override;
        virtual void RemoveAudioComponent(const UTFAudioCaptureComponent* Component) override;
    
        /** IModuleInterface implementation */
        virtual void StartupModule() override;
        virtual void ShutdownModule() override;
    
    private:
        TSharedPtr<FWindowsAudioCapture> WindowsCapture;
        TArray<UTFAudioCaptureComponent*> Components;
    };
    
    void FTFAudioCapture::StartupModule()
    {
        if (!WindowsCapture.IsValid())
        {
            WindowsCapture = MakeShareable(new FWindowsAudioCapture);
        }
    }
    
    void FTFAudioCapture::ShutdownModule()
    {
    
    }
    
    void FTFAudioCapture::StartCapture(TFunction<void(const TArray<uint8>&)> OnAudioData, TFunction<void(const TArray<uint8>&)> OnCaptureFinished)
    {
        TFunction<void(const TArray<uint8>&)> OnDataDelegate = [this, OnAudioData] (const TArray<uint8>& AudioData)
        {
            //Call each added component function inside game thread
            FLambdaRunnable::RunShortLambdaOnGameThread([this, AudioData, OnAudioData]
            {
                for (auto Component : Components)
                {
                    Component->OnAudioData.Broadcast(AudioData);
                }
    
                //Also if valid pass it to the new delegate
                if (OnAudioData != nullptr)
                {
                    OnAudioData(AudioData);
                }
            });
        };
    
        TFunction<void(const TArray<uint8>&)> OnFinishedDelegate = [this, OnCaptureFinished](const TArray<uint8>& AudioData)
        {
            //Call each added component function inside game thread
            FLambdaRunnable::RunShortLambdaOnGameThread([this, AudioData, OnCaptureFinished]
            {
                for (auto Component : Components)
                {
                    Component->OnCaptureFinished.Broadcast(AudioData);
                }
    
                //Also if valid pass it to the new delegate
                if (OnCaptureFinished != nullptr)
                {
                    OnCaptureFinished(AudioData);
                }
    
            });
        };
    
        WindowsCapture->StartCapture(OnDataDelegate, OnFinishedDelegate);
    }
    
    void FTFAudioCapture::StopCapture()
    {
        if (WindowsCapture.IsValid())
        {
            WindowsCapture->StopCapture();
        }
    }
    
    void FTFAudioCapture::SetOptions(const FAudioCaptureOptions& Options)
    {
        WindowsCapture->SetOptions(Options);
    }
    
    void FTFAudioCapture::AddAudioComponent(const UTFAudioCaptureComponent* Component)
    {
        Components.Add((UTFAudioCaptureComponent*)Component);
    }
    
    void FTFAudioCapture::RemoveAudioComponent(const UTFAudioCaptureComponent* Component)
    {
        Components.Remove((UTFAudioCaptureComponent*)Component);
    }
    
    IMPLEMENT_MODULE(FTFAudioCapture, TFAudioCapture)
    
    Olen yrittänyt saada selville puhutaanko mikrofoniin katselemalla AudioData taulukkoa. AudioData taulukko lähetetään eteenpäin kohdassa jossa sanotaan:
    Koodi:
                for (auto Component : Components)
                {
                    Component->OnCaptureFinished.Broadcast(AudioData);
                }
    
    Vaikka miten katson niin tuo tavujen taulukko on täysin satunnainen. Vaikka minkäänlaista ääntä ei menisi niin siellä saattaa olla jotain 0 ja usean sadan välistä. Vaikka aiheutan ääntä niin tuo tavujen taulukko ei näytä siitä olevan moksistaan. Kuitenkin tuo nimenomainen taulukko on se, josta pelimoottorissa ääni parsitaan ja toistetaan.

    Onko mitään hajua miten noita kahta luokkaa tutkimalla voisi saada selville sen, että kuuleeko mikrofoni ääntä?
     
  2. zepi

    zepi

    Viestejä:
    2 156
    Rekisteröitynyt:
    17.10.2016
    Jos käytät 16-bittisiä sampleja, niin taulukossa sataset eivät ole paljon, vaan luokkaa 1% maksimista (16bit maksimiarvo 32768).

    Jos sanot mikrofoniin tasaisella voimakkaalla äänellä "AAAAAAAAAAAAA" jotakuinkin niin matalalla äänellä kuin "normaalisti" pystyt, tallennat lukuarvot taulukkoon ja tarkastelet (nSamplesPerSec/20) mittaista pätkää tuosta ääninäytteestä, niin mitä sieltä löytyy?

    Normaalisti miehen äänialan bassopää on jossain 80-160Hz välimaastossa, joten sieltä pitäisi löytyä about tuolla taajuudella oskillointia. Jos siis sample ratesi on vaikka 44kHz, niin 2200 peräkkäistä samplea excelissä (tai pelienginessä) piirtämällä pitäisi löytyä ainakin jonkin sorttinen järki siihen nauhoitetun signaalin voimakkuuteen ja sitten voitkin ruveta miettimään, että mikä triggeröintiehto on sopiva ja millä aikajaksolla se muutos pitäisi sieltä havaita.

    Jos haluat valmiita koodeja, niin github on täynnä voice activation libraryitä. Kriittinen hakusana on "VAD". Minulla ei ole mitään käryä, että mikä noista olisi hyvä.

    Jos haluat hypätä syvään päätyyn, niin lähde vaikka tästä eteenpäin:
    https://ieeexplore.ieee.org/document/8309294/
    jtkim-kaist/VAD
     
  3. Jean_Sipulius

    Jean_Sipulius

    Viestejä:
    218
    Rekisteröitynyt:
    17.10.2016
    Kiitos. Minäpä huomenna tarkastelen noita töissä. Mistä tuo luku 20 tulee? nSamplesPerSec/20?
     
  4. Xiyng

    Xiyng

    Viestejä:
    942
    Rekisteröitynyt:
    19.10.2016
    Asia ei minulle kuulu enkä asiasta mitään tiedä, mutta pakko heittää silti arvaus. :D Arvaan siis, että se on mielivaltainen luku mutta käytännössä sillä saa jossain mielessä sopivan mittaisen tarkastelujakson.
     
  5. zepi

    zepi

    Viestejä:
    2 156
    Rekisteröitynyt:
    17.10.2016
    20 tulee siitä, että saat sellaisen määrän sampleja joissa tuollainen 100hz ääni näkyy kivana aaltona. Käytännössä siis 0.05sek pituinen pätkä ääntä.