/** * @brief Called on the captureTimer events to capture audio */ void OpenAL2::doAudio() { QMutexLocker lock(&audioLock); // output section if (echoCancelSupported && outputInitialized && !peerSources.isEmpty()) { doOutput(); } else { kill_filter_audio(filterer); filterer = nullptr; } // input section if (alInDev && inSubscriptions) { doInput(); } }
void Cyanide::audio_thread() { return; const char *device_list, *output_device = NULL; //void *audio_device = NULL; bool call[MAX_CALLS] = {0}, preview = 0; bool audio_filtering_enabled; // bool groups_audio[MAX_NUM_GROUPS] = {0}; int perframe = (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate) / 1000; uint8_t buf[perframe * 2 * av_DefaultSettings.audio_channels], dest[perframe * 2 * av_DefaultSettings.audio_channels]; memset(buf, 0, sizeof(buf)); uint8_t audio_count = 0; bool record_on = 0; #ifdef AUDIO_FILTERING qDebug() << "Audio Filtering enabled"; #ifdef ALC_LOOPBACK_CAPTURE_SAMPLES qDebug() << "Echo cancellation enabled"; #endif #endif qDebug() << "frame size:" << perframe; device_list = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); if(device_list) { output_device = device_list; qDebug() << "Output Device List:"; while(*device_list) { qDebug() << device_list; //postmessage(NEW_AUDIO_OUT_DEVICE, 0, 0, (void*)device_list); device_list += strlen(device_list) + 1; } } device_out = alcOpenDevice(output_device); if(!device_out) { qDebug() << "alcOpenDevice() failed"; return; } int attrlist[] = { ALC_FREQUENCY, av_DefaultSettings.audio_sample_rate, ALC_INVALID }; context = alcCreateContext(device_out, attrlist); if(!alcMakeContextCurrent(context)) { qDebug() << "alcMakeContextCurrent() failed"; alcCloseDevice(device_out); return; } alGenSources(countof(source), source); static ALuint ringSrc[MAX_CALLS]; alGenSources(MAX_CALLS, ringSrc); /* Create buffer to store samples */ ALuint RingBuffer; alGenBuffers(1, &RingBuffer); { float frequency1 = 441.f; float frequency2 = 882.f; int seconds = 4; unsigned sample_rate = 22050; size_t buf_size = seconds * sample_rate * 2; //16 bit (2 bytes per sample) int16_t *samples = (int16_t*)malloc(buf_size * sizeof(int16_t)); if (!samples) return; /*Generate an electronic ringer sound that quickly alternates between two frequencies*/ int index = 0; for(index = 0; index < buf_size; ++index) { if ((index / (sample_rate)) % 4 < 2 ) {//4 second ring cycle, first 2 secondsring, the rest(2 seconds) is silence if((index / 1000) % 2 == 1) { samples[index] = 5000 * sin((2.0 * 3.1415926 * frequency1) / sample_rate * index); //5000=amplitude(volume level). It can be from zero to 32700 } else { samples[index] = 5000 * sin((2.0 * 3.1415926 * frequency2) / sample_rate * index); } } else { samples[index] = 0; } } alBufferData(RingBuffer, AL_FORMAT_MONO16, samples, buf_size, sample_rate); free(samples); } { unsigned int i; for (i = 0; i < MAX_CALLS; ++i) { alSourcei(ringSrc[i], AL_LOOPING, AL_TRUE); alSourcei(ringSrc[i], AL_BUFFER, RingBuffer); } } #ifdef AUDIO_FILTERING Filter_Audio *f_a = NULL; #endif while(loop == LOOP_RUN || loop == LOOP_SUSPEND) { #ifdef AUDIO_FILTERING if (!f_a && audio_filtering_enabled) { f_a = new_filter_audio(av_DefaultSettings.audio_sample_rate); if (!f_a) { audio_filtering_enabled = 0; qDebug() << "filter audio failed"; } else { qDebug() << "filter audio on"; } } else if (f_a && !audio_filtering_enabled) { kill_filter_audio(f_a); f_a = NULL; qDebug() << "filter audio off"; } #else if (audio_filtering_enabled) audio_filtering_enabled = 0; #endif bool sleep = 1; if(record_on) { ALint samples; alcGetIntegerv(device_in, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= perframe) { alcCaptureSamples(device_in, buf, perframe); if (samples >= perframe * 2) { sleep = 0; } } } #ifdef AUDIO_FILTERING #ifdef ALC_LOOPBACK_CAPTURE_SAMPLES if (f_a && audio_filtering_enabled) { ALint samples; alcGetIntegerv(device_out, ALC_LOOPBACK_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= perframe) { int16_t buffer[perframe]; alcCaptureSamplesLoopback(device_out, buffer, perframe); pass_audio_output(f_a, buffer, perframe); set_echo_delay_ms(f_a, 5); if (samples >= perframe * 2) { sleep = 0; } } } #endif #endif #ifdef AUDIO_FILTERING if (f_a && filter_audio(f_a, (int16_t*)buf, perframe) == -1) { qDebug() << "filter audio error"; } #endif if(preview) { audio_play(0, (int16_t*)buf, perframe, av_DefaultSettings.audio_channels, av_DefaultSettings.audio_sample_rate); } int i; for(i = 0; i < MAX_CALLS; i++) { if(call[i]) { int r; if((r = toxav_prepare_audio_frame(toxav, i, dest, sizeof(dest), (const int16_t*)buf, perframe)) < 0) { qDebug() << "toxav_prepare_audio_frame error" << r; continue; } if((r = toxav_send_audio(toxav, i, dest, r)) < 0) { qDebug() << "toxav_send_audio error" << r; } } } if (sleep) { usleep(5000); } } #ifdef AUDIO_FILTERING kill_filter_audio(f_a); #endif //missing some cleanup ? alDeleteSources(MAX_CALLS, ringSrc); alDeleteSources(countof(source), source); alDeleteBuffers(1, &RingBuffer); if(device_in) { if(record_on) { alcCaptureStop(device_in); } alcCaptureCloseDevice(device_in); } alcMakeContextCurrent(NULL); alcDestroyContext(context); alcCloseDevice(device_out); }
static void utox_filter_audio_kill(Filter_Audio *filter_audio_handle) { #ifdef AUDIO_FILTERING kill_filter_audio(filter_audio_handle); #endif }
void utox_audio_thread(void *args) { ToxAV *av = args; const char *device_list, *output_device = NULL; void *audio_device = NULL; _Bool call[MAX_CALLS] = {0}, preview = 0; _Bool groups_audio[MAX_NUM_GROUPS] = {0}; int perframe = (UTOX_DEFAULT_FRAME_A * UTOX_DEFAULT_SAMPLE_RATE_A) / 1000; uint8_t buf[perframe * 2 * UTOX_DEFAULT_AUDIO_CHANNELS]; //, dest[perframe * 2 * UTOX_DEFAULT_AUDIO_CHANNELS]; memset(buf, 0, sizeof(buf)); uint8_t audio_count = 0; _Bool record_on = 0; #ifdef AUDIO_FILTERING debug("Audio Filtering"); #ifdef ALC_LOOPBACK_CAPTURE_SAMPLES debug(" and Echo cancellation"); #endif debug(" enabled in this build\n"); #endif debug("frame size: %u\n", perframe); device_list = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); if (device_list) { audio_device = (void*)device_list; debug("uTox audio input device list:\n"); while(*device_list) { debug("\t%s\n", device_list); postmessage(AUDIO_IN_DEVICE, UI_STRING_ID_INVALID, 0, (void*)device_list); device_list += strlen(device_list) + 1; } } postmessage(AUDIO_IN_DEVICE, STR_AUDIO_IN_NONE, 0, NULL); audio_detect(); if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT")) { device_list = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); } else { device_list = alcGetString(NULL, ALC_DEVICE_SPECIFIER); } if(device_list) { output_device = device_list; debug("uTox audio output device list:\n"); while(*device_list) { debug("\t%s\n", device_list); postmessage(AUDIO_OUT_DEVICE, 0, 0, (void*)device_list); device_list += strlen(device_list) + 1; } } device_out = alcOpenDevice(output_device); if(!device_out) { debug("alcOpenDevice() failed\n"); return; } int attrlist[] = { ALC_FREQUENCY, UTOX_DEFAULT_SAMPLE_RATE_A, ALC_INVALID }; context = alcCreateContext(device_out, attrlist); if(!alcMakeContextCurrent(context)) { debug("alcMakeContextCurrent() failed\n"); alcCloseDevice(device_out); return; } alGenSources(countof(source), source); /* TODO hacky fix. This source list should be a VLA with a way to link sources to friends. * NO SRSLY don't leave this like this! */ static ALuint ringSrc[UTOX_MAX_NUM_FRIENDS]; alGenSources(UTOX_MAX_NUM_FRIENDS, ringSrc); /* Create buffer to store samples */ ALuint RingBuffer; alGenBuffers(1, &RingBuffer); { /* wrapped to keep this data on the stack... I think... */ float frequency1 = 441.f; float frequency2 = 882.f; int seconds = 4; unsigned sample_rate = 22050; size_t buf_size = seconds * sample_rate * 2; //16 bit (2 bytes per sample) int16_t *samples = malloc(buf_size * sizeof(int16_t)); if (!samples) return; /*Generate an electronic ringer sound that quickly alternates between two frequencies*/ int index = 0; for(index = 0; index < buf_size; ++index) { if ((index / (sample_rate)) % 4 < 2 ) {//4 second ring cycle, first 2 secondsring, the rest(2 seconds) is silence if((index / 1000) % 2 == 1) { samples[index] = 5000 * sin((2.0 * 3.1415926 * frequency1) / sample_rate * index); //5000=amplitude(volume level). It can be from zero to 32700 } else { samples[index] = 5000 * sin((2.0 * 3.1415926 * frequency2) / sample_rate * index); } } else { samples[index] = 0; } } alBufferData(RingBuffer, AL_FORMAT_MONO16, samples, buf_size, sample_rate); free(samples); } { unsigned int i; for (i = 0; i < UTOX_MAX_NUM_FRIENDS; ++i) { alSourcei(ringSrc[i], AL_LOOPING, AL_TRUE); alSourcei(ringSrc[i], AL_BUFFER, RingBuffer); } } Filter_Audio *f_a = NULL; audio_thread_init = 1; int16_t *preview_buffer = NULL; unsigned int preview_buffer_index = 0; #define PREVIEW_BUFFER_SIZE (UTOX_DEFAULT_SAMPLE_RATE_A / 2) while(1) { if(audio_thread_msg) { TOX_MSG *m = &audio_msg; if(!m->msg) { break; } switch(m->msg) { case AUDIO_SET_INPUT: { audio_device = m->data; if(record_on) { alccapturestop(device_in); alccaptureclose(device_in); } if(audio_count) { device_in = alcopencapture(audio_device); if(!device_in) { record_on = 0; } else { alccapturestart(device_in); record_on = 1; } } debug("set audio in\n"); break; } case AUDIO_SET_OUTPUT: { output_device = m->data; ALCdevice *device = alcOpenDevice(output_device); if(!device) { debug("alcOpenDevice() failed\n"); break; } ALCcontext *con = alcCreateContext(device, NULL); if(!alcMakeContextCurrent(con)) { debug("alcMakeContextCurrent() failed\n"); alcCloseDevice(device); break; } alcDestroyContext(context); alcCloseDevice(device_out); context = con; device_out = device; alGenSources(countof(source), source); alGenSources(MAX_CALLS, ringSrc); Tox *tox = toxav_get_tox(av); uint32_t num_chats = tox_count_chatlist(tox); if (num_chats != 0) { int32_t chats[num_chats]; uint32_t max = tox_get_chatlist(tox, chats, num_chats); unsigned int i; for (i = 0; i < max; ++i) { if (tox_group_get_type(tox, chats[i]) == TOX_GROUPCHAT_TYPE_AV) { GROUPCHAT *g = &group[chats[i]]; alGenSources(g->peers, g->source); } } } debug("set audio out\n"); break; } case AUDIO_PREVIEW_START: { preview = 1; audio_count++; preview_buffer = calloc(PREVIEW_BUFFER_SIZE, 2); preview_buffer_index = 0; if(!record_on) { device_in = alcopencapture(audio_device); if(device_in) { alccapturestart(device_in); record_on = 1; debug("Starting Audio Preview\n"); } } break; } case AUDIO_START: { audio_count++; if(!record_on) { device_in = alcopencapture(audio_device); if(device_in) { alccapturestart(device_in); record_on = 1; debug("Listening to audio\n"); yieldcpu(20); } } break; } case GROUP_AUDIO_CALL_START: { break; // TODO, new groups API audio_count++; groups_audio[m->param1] = 1; if(!record_on) { device_in = alcopencapture(audio_device); if(device_in) { alccapturestart(device_in); record_on = 1; debug("Starting Audio GroupCall\n"); } } break; } case AUDIO_PREVIEW_END: { preview = 0; audio_count--; free(preview_buffer); preview_buffer = NULL; if(!audio_count && record_on) { alccapturestop(device_in); alccaptureclose(device_in); record_on = 0; debug("Audio Preview Stopped\n"); } break; } case AUDIO_END: { if(!call[m->param1]) { break; } call[m->param1] = 0; audio_count--; if(!audio_count && record_on) { alccapturestop(device_in); alccaptureclose(device_in); record_on = 0; debug("stop\n"); } break; } case GROUP_AUDIO_CALL_END: { break; // TODO, new groups API if(!groups_audio[m->param1]) { break; } audio_count--; groups_audio[m->param1] = 0; if(!audio_count && record_on) { alccapturestop(device_in); alccaptureclose(device_in); record_on = 0; debug("stop\n"); } break; } case AUDIO_PLAY_RINGTONE: { if(!audible_notifications_enabled) { break; } alSourcePlay(ringSrc[m->param1]); break; } case AUDIO_STOP_RINGTONE: { ALint state; alGetSourcei(ringSrc[m->param1], AL_SOURCE_STATE, &state); if(state == AL_PLAYING) { alSourceStop(ringSrc[m->param1]); } break; } } audio_thread_msg = 0; } // TODO move this code to filter_audio.c #ifdef AUDIO_FILTERING if (!f_a && audio_filtering_enabled) { f_a = new_filter_audio(UTOX_DEFAULT_SAMPLE_RATE_A); if (!f_a) { audio_filtering_enabled = 0; debug("filter audio failed\n"); } else { debug("filter audio on\n"); } } else if (f_a && !audio_filtering_enabled) { kill_filter_audio(f_a); f_a = NULL; debug("filter audio off\n"); } #else if (audio_filtering_enabled) { audio_filtering_enabled = 0; } #endif _Bool sleep = 1; if(record_on) { ALint samples; _Bool frame = 0; /* If we have a device_in we're on linux so we can just call OpenAL, otherwise we're on something else so * we'll need to call audio_frame() to add to the buffer for us. */ if (device_in == (void*)1) { frame = audio_frame((void*)buf); if (frame) { /* We have an audio frame to use, continue without sleeping. */ sleep = 0; } } else { alcGetIntegerv(device_in, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= perframe) { alcCaptureSamples(device_in, buf, perframe); frame = 1; if (samples >= perframe * 2) { sleep = 0; } } } #ifdef AUDIO_FILTERING #ifdef ALC_LOOPBACK_CAPTURE_SAMPLES if (f_a && audio_filtering_enabled) { alcGetIntegerv(device_out, ALC_LOOPBACK_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= perframe) { int16_t buffer[perframe]; alcCaptureSamplesLoopback(device_out, buffer, perframe); pass_audio_output(f_a, buffer, perframe); set_echo_delay_ms(f_a, UTOX_DEFAULT_FRAME_A); if (samples >= perframe * 2) { sleep = 0; } } } #endif #endif if (frame) { _Bool voice = 1; #ifdef AUDIO_FILTERING if (f_a) { int ret = filter_audio(f_a, (int16_t*)buf, perframe); if (ret == -1) { debug("filter audio error\n"); } if (ret == 0) { voice = 0; } } #endif /* If push to talk, we don't have to do anything */ if (!check_ptt_key()) { voice = 0; //PTT is up, send nothing. } if (preview) { if (preview_buffer_index + perframe > PREVIEW_BUFFER_SIZE) { preview_buffer_index = 0; } sourceplaybuffer(0, preview_buffer + preview_buffer_index, perframe, UTOX_DEFAULT_AUDIO_CHANNELS, UTOX_DEFAULT_SAMPLE_RATE_A); if (voice) { memcpy(preview_buffer + preview_buffer_index, buf, perframe * sizeof(int16_t)); } else { memset(preview_buffer + preview_buffer_index, 0, perframe * sizeof(int16_t)); } preview_buffer_index += perframe; } if (voice) { int i, active_call_count = 0; for(i = 0; i < UTOX_MAX_NUM_FRIENDS; i++) { if( UTOX_SEND_AUDIO(i) ) { active_call_count++; TOXAV_ERR_SEND_FRAME error = 0; toxav_audio_send_frame(av, friend[i].number, (const int16_t *)buf, perframe, UTOX_DEFAULT_AUDIO_CHANNELS, UTOX_DEFAULT_SAMPLE_RATE_A, &error); if (error) { debug("toxav_send_audio error friend == %i, error == %i\n", i, error); } else { // debug("Send a frame to friend %i\n",i); if (active_call_count >= UTOX_MAX_CALLS) { debug("We're calling more peers than allowed by UTOX_MAX_CALLS, This is a bug\n"); break; } } } } // TODO REMOVED until new groups api can be implemented. /*Tox *tox = toxav_get_tox(av); uint32_t num_chats = tox_count_chatlist(tox); if (num_chats != 0) { int32_t chats[num_chats]; uint32_t max = tox_get_chatlist(tox, chats, num_chats); for (i = 0; i < max; ++i) { if (groups_audio[chats[i]]) { toxav_group_send_audio(tox, chats[i], (int16_t *)buf, perframe, UTOX_DEFAULT_AUDIO_CHANNELS, UTOX_DEFAULT_SAMPLE_RATE_A); } } }*/ } } } if (sleep) { yieldcpu(5); } } utox_filter_audio_kill(f_a); //missing some cleanup ? alDeleteSources(MAX_CALLS, ringSrc); alDeleteSources(countof(source), source); alDeleteBuffers(1, &RingBuffer); if(device_in) { if(record_on) { alcCaptureStop(device_in); } alcCaptureCloseDevice(device_in); } alcMakeContextCurrent(NULL); alcDestroyContext(context); alcCloseDevice(device_out); audio_thread_msg = 0; audio_thread_init = 0; debug("UTOX AUDIO:\tClean thread exit!\n"); }
void AudioFilterer::closeFilter() { if (filter) kill_filter_audio(filter); filter = nullptr; }
Filter_Audio *new_filter_audio(uint32_t fs) { if (fs == 0) { return NULL; } Filter_Audio *f_a = calloc(sizeof(Filter_Audio), 1); if (!f_a) { return NULL; } f_a->fs = fs; if (fs != 16000) fs = 32000; init_highpass_filter_zam(&f_a->hpfa, 100, (float) f_a->fs); init_highpass_filter_zam(&f_a->hpfb, 100, (float) f_a->fs); unsigned int lowpass_filter_frequency = 12000; if (f_a->fs > (lowpass_filter_frequency * 2)) { init_lowpass_filter_zam(&f_a->lpfa, lowpass_filter_frequency, (float) f_a->fs); init_lowpass_filter_zam(&f_a->lpfb, lowpass_filter_frequency, (float) f_a->fs); f_a->lowpass_enabled = 1; } if (WebRtcAgc_Create(&f_a->gain_control) == -1) { free(f_a); return NULL; } if (WebRtcNsx_Create(&f_a->noise_sup_x) == -1) { WebRtcAgc_Free(f_a->gain_control); free(f_a); return NULL; } if (WebRtcAec_Create(&f_a->echo_cancellation) == -1) { WebRtcAgc_Free(f_a->gain_control); WebRtcNsx_Free(f_a->noise_sup_x); free(f_a); return NULL; } if (WebRtcVad_Create(&f_a->Vad_handle) == -1){ WebRtcAec_Free(f_a->echo_cancellation); WebRtcAgc_Free(f_a->gain_control); WebRtcNsx_Free(f_a->noise_sup_x); free(f_a); return NULL; } WebRtcAgc_config_t gain_config; gain_config.targetLevelDbfs = 1; gain_config.compressionGaindB = 20; gain_config.limiterEnable = kAgcTrue; if (WebRtcAgc_Init(f_a->gain_control, 0, 255, kAgcModeAdaptiveDigital, fs) == -1 || WebRtcAgc_set_config(f_a->gain_control, gain_config) == -1) { kill_filter_audio(f_a); return NULL; } if (WebRtcNsx_Init(f_a->noise_sup_x, fs) == -1 || WebRtcNsx_set_policy(f_a->noise_sup_x, 2) == -1) { kill_filter_audio(f_a); return NULL; } AecConfig echo_config; echo_config.nlpMode = kAecNlpAggressive; echo_config.skewMode = kAecFalse; echo_config.metricsMode = kAecFalse; echo_config.delay_logging = kAecFalse; if (WebRtcAec_Init(f_a->echo_cancellation, fs, f_a->fs) == -1 || WebRtcAec_set_config(f_a->echo_cancellation, echo_config) == -1) { kill_filter_audio(f_a); return NULL; } int vad_mode = 1; //Aggressiveness mode (0, 1, 2, or 3). if (WebRtcVad_Init(f_a->Vad_handle) == -1 || WebRtcVad_set_mode(f_a->Vad_handle,vad_mode) == -1){ kill_filter_audio(f_a); return NULL; } f_a->echo_enabled = 1; f_a->gain_enabled = 1; f_a->noise_enabled = 1; f_a->vad_enabled = 1; int quality = 4; if (f_a->fs != 16000) { f_a->downsampler = speex_resampler_init(1, f_a->fs, 32000, quality, 0); f_a->upsampler = speex_resampler_init(1, 32000, f_a->fs, quality, 0); /* quality doesn't need to be high for this one. */ quality = 0; f_a->downsampler_echo = speex_resampler_init(1, f_a->fs, 16000, quality, 0); if (!f_a->upsampler || !f_a->downsampler || !f_a->downsampler_echo) { kill_filter_audio(f_a); return NULL; } } return f_a; }
/** * @brief Handle audio output */ void OpenAL2::doOutput() { if (!echoCancelSupported) { kill_filter_audio(filterer); filterer = nullptr; } alcMakeContextCurrent(alOutContext); ALuint bufids[PROXY_BUFFER_COUNT]; ALint processed = 0, queued = 0; alGetSourcei(alProxySource, AL_BUFFERS_PROCESSED, &processed); alGetSourcei(alProxySource, AL_BUFFERS_QUEUED, &queued); if (processed > 0) { // unqueue all processed buffers alSourceUnqueueBuffers(alProxySource, processed, bufids); // delete all but the first buffer, reuse first for new data alDeleteBuffers(processed - 1, bufids + 1); } else if (queued < static_cast<ALint>(PROXY_BUFFER_COUNT)) { // create new buffer until the maximum is reached alGenBuffers(1, bufids); } else { alcMakeContextCurrent(alProxyContext); return; } ALdouble latency[2] = {0}; if (echoCancelSupported) { alGetSourcedvSOFT(alProxySource, AL_SEC_OFFSET_LATENCY_SOFT, latency); } checkAlError(); ALshort outBuf[AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL] = {0}; if (echoCancelSupported) { alcMakeContextCurrent(alProxyContext); alcRenderSamplesSOFT(alProxyDev, outBuf, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL); checkAlcError(alProxyDev); alcMakeContextCurrent(alOutContext); } alBufferData(bufids[0], AL_FORMAT_MONO16, outBuf, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL * 2, AUDIO_SAMPLE_RATE); alSourceQueueBuffers(alProxySource, 1, bufids); // initialize echo canceler if supported if (echoCancelSupported && !filterer) { filterer = new_filter_audio(AUDIO_SAMPLE_RATE); int16_t filterLatency = latency[1] * 1000 * 2 + AUDIO_FRAME_DURATION; qDebug() << "Setting filter delay to: " << filterLatency << "ms"; set_echo_delay_ms(filterer, filterLatency); enable_disable_filters(filterer, 1, 1, 1, 0); } // do echo cancel pass_audio_output(filterer, outBuf, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL); ALint state; alGetSourcei(alProxySource, AL_SOURCE_STATE, &state); if (state != AL_PLAYING) { qDebug() << "Proxy source underflow detected"; alSourcePlay(alProxySource); } alcMakeContextCurrent(alProxyContext); }