diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index b9a1cb3..008a646 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -35,6 +35,11 @@ jobs: copy build\windows\x64\release\alt-voice.lib upload\lib\release copy build\windows\x64\debug\alt-voice.lib upload\lib\debug copy include\alt-voice.h upload\include + copy include\IAudioFilter.h upload\include + copy include\IOpusDecoder.h upload\include + copy include\IOpusEncoder.h upload\include + copy include\ISoundIO.h upload\include + copy include\VoiceError.h upload\include - uses: actions/upload-artifact@v3 with: @@ -97,7 +102,7 @@ jobs: - name: Zip artifacts run: | - zip -r alt-voice-windows dist-windows/* + cd dist-windows && zip -r ../alt-voice-windows.zip . # zip -r alt-voice-linux dist-linux/* - name: Set outputs diff --git a/examples/devicetests.cpp b/examples/devicetests.cpp index 7b17150..ff78f4d 100644 --- a/examples/devicetests.cpp +++ b/examples/devicetests.cpp @@ -13,19 +13,9 @@ void ApplyFilter(void* buffer, uint32_t length) RadioFilter->Process(buffer, length); } -void AudioThread() +void AudioThread(void* buffer, uint32_t size) { - for(;;) - { - if (soundInput) - { - int16_t buffer[FRAME_SIZE_BYTES / 10]; - if (const auto len = soundInput->Read(buffer, FRAME_SIZE_BYTES / 10)) - { - soundOutput->Write(buffer, len, ApplyFilter); - } - } - } + soundOutput->Write(buffer, size, ApplyFilter); } int main() @@ -34,9 +24,9 @@ int main() AltVoiceError filterCreateError = AV_CreateAudioFilter(&RadioFilter); - /*auto bqLowEffect = RadioFilter->ApplyBqfEffect(0, 1400, 0, 0.86f, 0, 0, 1); + auto bqLowEffect = RadioFilter->ApplyBqfEffect(0, 1400, 0, 0.86f, 0, 0, 1); auto bqHighEffect = RadioFilter->ApplyBqfEffect(1, 900, 0, 0.83f, 0, 0, 2); - auto distortionEffect = RadioFilter->ApplyDistortionEffect(0.0f, -2.95f, -0.05f, -0.18f, 1.f, 3);*/ + auto distortionEffect = RadioFilter->ApplyDistortionEffect(0.0f, -2.95f, -0.05f, -0.18f, 1.f, 3); AV_CreateSoundOutput(64000, &soundOutput); @@ -62,11 +52,14 @@ int main() printf("%d: %s\n", i, soundInput->GetDeviceName(i)); } - soundInput->SetVolume(1.f); + soundInput->SetVolume(1.0f); soundInput->SelectDevice(2); soundInput->SetStreamEnabled(true); soundInput->SetNoiseSuppressionEnabled(true); + soundInput->RegisterCallback(AudioThread); - std::thread audioThread(AudioThread); - audioThread.join(); + for(;;) + { + Sleep(1000); + } } \ No newline at end of file diff --git a/include/ISoundIO.h b/include/ISoundIO.h index 1445b08..a10b10e 100644 --- a/include/ISoundIO.h +++ b/include/ISoundIO.h @@ -24,6 +24,8 @@ class ISoundIO virtual AltVoiceError SelectDevice(int id) = 0; virtual int GetDevice() = 0; + virtual void RegisterCallback(OnVoiceCallback callback) {} + virtual void SetStreamEnabled(bool enabled) {} virtual void SetNoiseSuppressionEnabled(const bool enabled) {} }; \ No newline at end of file diff --git a/src/CSoundInput.cpp b/src/CSoundInput.cpp index 425700e..33f136c 100644 --- a/src/CSoundInput.cpp +++ b/src/CSoundInput.cpp @@ -7,14 +7,16 @@ #include -CSoundInput::CSoundInput(int _bitRate) : encoder(new COpusEncoder(SAMPLE_RATE, AUDIO_CHANNELS, _bitRate)) +CSoundInput::CSoundInput(int _bitRate) : encoder(new COpusEncoder(SAMPLE_RATE, AUDIO_CHANNELS, _bitRate)), bitrate(_bitRate) { denoiser = rnnoise_create(nullptr); + opusBuffer = new char[bitrate]; } CSoundInput::~CSoundInput() { delete encoder; + delete opusBuffer; rnnoise_destroy(denoiser); BASS_RecordFree(); } @@ -33,22 +35,7 @@ float CSoundInput::GetVolume() int CSoundInput::Read(void* data, size_t size) { - const int available = BASS_ChannelGetData(recordChannel, nullptr, BASS_DATA_AVAILABLE); - if (available < FRAME_SIZE_BYTES) - return 0; - - int16_t inputData[FRAME_SIZE_SAMPLES]; - if (BASS_ChannelGetData(recordChannel, inputData, FRAME_SIZE_BYTES) != FRAME_SIZE_BYTES) - return 0; - - NoiseSuppressionProcess(inputData, FRAME_SIZE_SAMPLES); - - const DWORD currentMicLevel = BASS_ChannelGetLevel(recordChannel); - - const uint16_t leftChannelLevel = LOWORD(currentMicLevel); - micLevel = static_cast(leftChannelLevel) / MaxShortFloatValue; - - return encoder->EncodeShort(inputData, FRAME_SIZE_SAMPLES, data, size); + return 0; } float CSoundInput::GetLevel() @@ -113,7 +100,8 @@ AltVoiceError CSoundInput::SelectDevice(int id) if (!BASS_RecordInit(deviceId)) return AltVoiceError::DeviceInit; - recordChannel = BASS_RecordStart(SAMPLE_RATE, AUDIO_CHANNELS, 0, nullptr, this); + recordChannel = BASS_RecordStart(SAMPLE_RATE, AUDIO_CHANNELS, 0, OnSoundFrame, this); + BASS_ChannelSetAttribute(recordChannel, BASS_ATTRIB_GRANULE, FRAME_SIZE_SAMPLES); // Change input volume VolumeChangeFX = BASS_ChannelSetFX(recordChannel, BASS_FX_BFX_VOLUME, 0); @@ -131,11 +119,27 @@ int CSoundInput::GetDevice() return BASS_RecordGetDevice(); } +void CSoundInput::RegisterCallback(OnVoiceCallback callback) +{ + VoiceCallback = callback; +} + void CSoundInput::SetNoiseSuppressionEnabled(const bool enabled) { noiseSuppressionEnabled = enabled; } +BOOL CSoundInput::OnSoundFrame(HRECORD handle, const void* buffer, DWORD length, void* user) +{ + const auto self = static_cast(user); + + for (int i = 0; i < length; i += (FRAME_SIZE_SAMPLES * sizeof(short))) + { + self->SoundFrameCaptured(handle, (char*)buffer + i, FRAME_SIZE_SAMPLES * sizeof(short)); + } + return true; +} + void CSoundInput::NoiseSuppressionProcess(void* buffer, DWORD length) { if (noiseSuppressionEnabled) @@ -161,3 +165,27 @@ void CSoundInput::NoiseSuppressionProcess(void* buffer, DWORD length) } } } + +void CSoundInput::SoundFrameCaptured(HRECORD handle, const void* buffer, DWORD length) +{ + // Create new buffer on stack because buffer was marked as const in API + memcpy_s(writableBuffer, FRAME_SIZE_SAMPLES * sizeof(short), buffer, length); + + // Apply noise suppression + NoiseSuppressionProcess(writableBuffer, FRAME_SIZE_SAMPLES); + + // Get current microphone noise level + const DWORD currentMicLevel = BASS_ChannelGetLevel(handle); + + // Get left channel noise level from it (because it's mono so right = left) + const uint16_t leftChannelLevel = LOWORD(currentMicLevel); + + // Convert to float from 0.f to 1.f + micLevel = static_cast(leftChannelLevel) / MaxShortFloatValue; + + if (VoiceCallback) + { + const int opusBufferSize = encoder->EncodeShort(writableBuffer, FRAME_SIZE_SAMPLES, opusBuffer, bitrate); + VoiceCallback(opusBuffer, opusBufferSize); + } +} diff --git a/src/CSoundInput.h b/src/CSoundInput.h index d2c1d9c..11324b1 100644 --- a/src/CSoundInput.h +++ b/src/CSoundInput.h @@ -14,6 +14,7 @@ class CSoundInput : public ISoundIO HRECORD recordChannel = 0; COpusEncoder* encoder = nullptr; + int bitrate; float volume = 1.f; float micLevel = 0; @@ -22,7 +23,11 @@ class CSoundInput : public ISoundIO HFX VolumeChangeFX; DenoiseState* denoiser; + short writableBuffer[FRAME_SIZE_SAMPLES]; float floatBuffer[FRAME_SIZE_SAMPLES]; + char* opusBuffer = nullptr; + + OnVoiceCallback VoiceCallback = nullptr; public: CSoundInput(int _bitRate); @@ -41,8 +46,12 @@ class CSoundInput : public ISoundIO AltVoiceError SelectDevice(int id) override; int GetDevice() override; - void SetNoiseSuppressionEnabled(const bool enabled) override; + void RegisterCallback(OnVoiceCallback callback) override; + void SetNoiseSuppressionEnabled(const bool enabled) override; void NoiseSuppressionProcess(void* buffer, DWORD length); + void SoundFrameCaptured(HRECORD handle, const void* buffer, DWORD length); + + static BOOL OnSoundFrame(HRECORD handle, const void* buffer, DWORD length, void* user); }; diff --git a/src/CSoundOutput.cpp b/src/CSoundOutput.cpp index a2272f5..4cb75c2 100644 --- a/src/CSoundOutput.cpp +++ b/src/CSoundOutput.cpp @@ -16,7 +16,10 @@ void CSoundOutput::Write(void* data, size_t size, OnVoiceCallback filterCallback int16_t outputBuffer[FRAME_SIZE_SAMPLES]; int len = decoder->DecodeShort(data, size, outputBuffer, FRAME_SIZE_SAMPLES, true, false); - filterCallback(outputBuffer, FRAME_SIZE_BYTES); + if (filterCallback) + { + filterCallback(outputBuffer, FRAME_SIZE_BYTES); + } BASS_StreamPutData(outputStream, outputBuffer, FRAME_SIZE_BYTES); } @@ -70,7 +73,7 @@ AltVoiceError CSoundOutput::SelectDevice(int id) } } - if (!BASS_Init(deviceId, SAMPLE_RATE, 0, 0, nullptr)) + if (!BASS_Init(deviceId, SAMPLE_RATE, 0, nullptr, nullptr)) { if(BASS_ErrorGetCode() != BASS_ERROR_ALREADY) return AltVoiceError::DeviceInit;