static void noise_gate_update(void *data, obs_data_t *s) { struct noise_gate_data *ng = data; float open_threshold_db; float close_threshold_db; float sample_rate; int attack_time_ms; int hold_time_ms; int release_time_ms; open_threshold_db = (float)obs_data_get_double(s, S_OPEN_THRESHOLD); close_threshold_db = (float)obs_data_get_double(s, S_CLOSE_THRESHOLD); attack_time_ms = (int)obs_data_get_int(s, S_ATTACK_TIME); hold_time_ms = (int)obs_data_get_int(s, S_HOLD_TIME); release_time_ms = (int)obs_data_get_int(s, S_RELEASE_TIME); sample_rate = (float)audio_output_get_sample_rate(obs_get_audio()); ng->sample_rate_i = 1.0f / sample_rate; ng->channels = audio_output_get_channels(obs_get_audio()); ng->open_threshold = db_to_mul(open_threshold_db); ng->close_threshold = db_to_mul(close_threshold_db); ng->attack_rate = 1.0f / (ms_to_secf(attack_time_ms) * sample_rate); ng->release_rate = 1.0f / (ms_to_secf(release_time_ms) * sample_rate); const float threshold_diff = ng->open_threshold - ng->close_threshold; const float min_decay_period = (1.0f / 75.0f) * sample_rate; ng->decay_rate = threshold_diff / min_decay_period; ng->hold_time = ms_to_secf(hold_time_ms); ng->is_open = false; ng->attenuation = 0.0f; ng->level = 0.0f; ng->held_time = 0.0f; }
static void expander_update(void *data, obs_data_t *s) { struct expander_data *cd = data; const char *presets = obs_data_get_string(s, S_PRESETS); if (strcmp(presets, "expander") == 0 && cd->is_gate) { obs_data_clear(s); obs_data_set_string(s, S_PRESETS, "expander"); expander_defaults(s); cd->is_gate = false; } if (strcmp(presets, "gate") == 0 && !cd->is_gate) { obs_data_clear(s); obs_data_set_string(s, S_PRESETS, "gate"); expander_defaults(s); cd->is_gate = true; } const uint32_t sample_rate = audio_output_get_sample_rate(obs_get_audio()); const size_t num_channels = audio_output_get_channels(obs_get_audio()); const float attack_time_ms = (float)obs_data_get_int(s, S_ATTACK_TIME); const float release_time_ms = (float)obs_data_get_int(s, S_RELEASE_TIME); const float output_gain_db = (float)obs_data_get_double(s, S_OUTPUT_GAIN); cd->ratio = (float)obs_data_get_double(s, S_RATIO); cd->threshold = (float)obs_data_get_double(s, S_THRESHOLD); cd->attack_gain = gain_coefficient(sample_rate, attack_time_ms / MS_IN_S_F); cd->release_gain = gain_coefficient(sample_rate, release_time_ms / MS_IN_S_F); cd->output_gain = db_to_mul(output_gain_db); cd->num_channels = num_channels; cd->sample_rate = sample_rate; cd->slope = 1.0f - cd->ratio; const char *detect_mode = obs_data_get_string(s, S_DETECTOR); if (strcmp(detect_mode, "RMS") == 0) cd->detector = RMS_DETECT; if (strcmp(detect_mode, "peak") == 0) cd->detector = PEAK_DETECT; if (strcmp(detect_mode, "none") == 0) cd->detector = NO_DETECT; size_t sample_len = sample_rate * DEFAULT_AUDIO_BUF_MS / MS_IN_S; if (cd->envelope_buf_len == 0) resize_env_buffer(cd, sample_len); if (cd->runaverage_len == 0) resize_runaverage_buffer(cd, sample_len); if (cd->maxspl_len == 0) resize_maxspl_buffer(cd, sample_len); if (cd->env_in_len == 0) resize_env_in_buffer(cd, sample_len); }
void obs_encoder_set_audio(obs_encoder_t *encoder, audio_t *audio) { if (!audio || !encoder || encoder->info.type != OBS_ENCODER_AUDIO) return; encoder->media = audio; encoder->timebase_num = 1; encoder->timebase_den = audio_output_get_sample_rate(audio); }
uint32_t obs_encoder_get_sample_rate(const obs_encoder_t *encoder) { if (!encoder || !encoder->media || encoder->info.type != OBS_ENCODER_AUDIO) return 0; return encoder->samplerate != 0 ? encoder->samplerate : audio_output_get_sample_rate(encoder->media); }
void obs_encoder_set_audio(obs_encoder_t *encoder, audio_t *audio) { if (!obs_encoder_valid(encoder, "obs_encoder_set_audio")) return; if (encoder->info.type != OBS_ENCODER_AUDIO) { blog(LOG_WARNING, "obs_encoder_set_audio: " "encoder '%s' is not an audio encoder", obs_encoder_get_name(encoder)); return; } if (!audio) return; encoder->media = audio; encoder->timebase_num = 1; encoder->timebase_den = audio_output_get_sample_rate(audio); }
uint32_t obs_encoder_get_sample_rate(const obs_encoder_t *encoder) { if (!obs_encoder_valid(encoder, "obs_encoder_get_sample_rate")) return 0; if (encoder->info.type != OBS_ENCODER_AUDIO) { blog(LOG_WARNING, "obs_encoder_get_sample_rate: " "encoder '%s' is not an audio encoder", obs_encoder_get_name(encoder)); return 0; } if (!encoder->media) return 0; return encoder->samplerate != 0 ? encoder->samplerate : audio_output_get_sample_rate(encoder->media); }
mssapi_captions::mssapi_captions( captions_cb callback, const std::string &lang) try : captions_handler(callback, AUDIO_FORMAT_16BIT, 16000) { HRESULT hr; std::wstring wlang; wlang.resize(lang.size()); for (size_t i = 0; i < lang.size(); i++) wlang[i] = (wchar_t)lang[i]; LCID lang_id = LocaleNameToLCID(wlang.c_str(), 0); wchar_t lang_str[32]; _snwprintf(lang_str, 31, L"language=%x", (int)lang_id); stop = CreateEvent(nullptr, false, false, nullptr); if (!stop.Valid()) throw "Failed to create event"; hr = SpFindBestToken(SPCAT_RECOGNIZERS, lang_str, nullptr, &token); if (FAILED(hr)) throw HRError("SpFindBestToken failed", hr); hr = CoCreateInstance(CLSID_SpInprocRecognizer, nullptr, CLSCTX_ALL, __uuidof(ISpRecognizer), (void**)&recognizer); if (FAILED(hr)) throw HRError("CoCreateInstance for recognizer failed", hr); hr = recognizer->SetRecognizer(token); if (FAILED(hr)) throw HRError("SetRecognizer failed", hr); hr = recognizer->SetRecoState(SPRST_INACTIVE); if (FAILED(hr)) throw HRError("SetRecoState(SPRST_INACTIVE) failed", hr); hr = recognizer->CreateRecoContext(&context); if (FAILED(hr)) throw HRError("CreateRecoContext failed", hr); ULONGLONG interest = SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_END_SR_STREAM); hr = context->SetInterest(interest, interest); if (FAILED(hr)) throw HRError("SetInterest failed", hr); hr = context->SetNotifyWin32Event(); if (FAILED(hr)) throw HRError("SetNotifyWin32Event", hr); notify = context->GetNotifyEventHandle(); if (notify == INVALID_HANDLE_VALUE) throw HRError("GetNotifyEventHandle failed", E_NOINTERFACE); size_t sample_rate = audio_output_get_sample_rate(obs_get_audio()); audio = new CaptionStream((DWORD)sample_rate, this); audio->Release(); hr = recognizer->SetInput(audio, false); if (FAILED(hr)) throw HRError("SetInput failed", hr); hr = context->CreateGrammar(1, &grammar); if (FAILED(hr)) throw HRError("CreateGrammar failed", hr); hr = grammar->LoadDictation(nullptr, SPLO_STATIC); if (FAILED(hr)) throw HRError("LoadDictation failed", hr); try { t = std::thread([this] () {main_thread();}); } catch (...) { throw "Failed to create thread"; } } catch (const char *err) { blog(LOG_WARNING, "%s: %s", __FUNCTION__, err); throw CAPTIONS_ERROR_GENERIC_FAIL; } catch (HRError err) { blog(LOG_WARNING, "%s: %s (%lX)", __FUNCTION__, err.str, err.hr); throw CAPTIONS_ERROR_GENERIC_FAIL; }
static void *aac_create(obs_data_t *settings, obs_encoder_t *encoder) { struct aac_encoder *enc; int bitrate = (int)obs_data_get_int(settings, "bitrate"); audio_t *audio = obs_encoder_audio(encoder); avcodec_register_all(); enc = bzalloc(sizeof(struct aac_encoder)); enc->encoder = encoder; enc->aac = avcodec_find_encoder(AV_CODEC_ID_AAC); blog(LOG_INFO, "---------------------------------"); if (!enc->aac) { warn("Couldn't find encoder"); goto fail; } if (!bitrate) { warn("Invalid bitrate specified"); return NULL; } enc->context = avcodec_alloc_context3(enc->aac); if (!enc->context) { warn("Failed to create codec context"); goto fail; } enc->context->bit_rate = bitrate * 1000; enc->context->channels = (int)audio_output_get_channels(audio); enc->context->sample_rate = audio_output_get_sample_rate(audio); enc->context->sample_fmt = enc->aac->sample_fmts ? enc->aac->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; /* if using FFmpeg's AAC encoder, at least set a cutoff value * (recommended by konverter) */ if (strcmp(enc->aac->name, "aac") == 0) { int cutoff1 = 4000 + (int)enc->context->bit_rate / 8; int cutoff2 = 12000 + (int)enc->context->bit_rate / 8; int cutoff3 = enc->context->sample_rate / 2; int cutoff; cutoff = MIN(cutoff1, cutoff2); cutoff = MIN(cutoff, cutoff3); enc->context->cutoff = cutoff; } info("bitrate: %" PRId64 ", channels: %d", enc->context->bit_rate / 1000, enc->context->channels); init_sizes(enc, audio); /* enable experimental FFmpeg encoder if the only one available */ enc->context->strict_std_compliance = -2; enc->context->flags = CODEC_FLAG_GLOBAL_HEADER; if (initialize_codec(enc)) return enc; fail: aac_destroy(enc); return NULL; }
static void *libfdk_create(obs_data_t settings, obs_encoder_t encoder) { bool hasFdkHandle = false; libfdk_encoder_t *enc = 0; int bitrate = (int)obs_data_get_int(settings, "bitrate") * 1000; int afterburner = obs_data_get_bool(settings, "afterburner") ? 1 : 0; audio_t audio = obs_encoder_audio(encoder); int mode = 0; AACENC_ERROR err; if (!bitrate) { blog(LOG_ERROR, "Invalid bitrate"); return NULL; } enc = bzalloc(sizeof(libfdk_encoder_t)); enc->encoder = encoder; enc->channels = (int)audio_output_get_channels(audio); enc->sample_rate = audio_output_get_sample_rate(audio); switch(enc->channels) { case 1: mode = MODE_1; break; case 2: mode = MODE_2; break; case 3: mode = MODE_1_2; break; case 4: mode = MODE_1_2_1; break; case 5: mode = MODE_1_2_2; break; case 6: mode = MODE_1_2_2_1; break; default: blog(LOG_ERROR, "Invalid channel count"); goto fail; } CHECK_LIBFDK(aacEncOpen(&enc->fdkhandle, 0, enc->channels)); hasFdkHandle = true; CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_AOT, 2)); // MPEG-4 AAC-LC CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_SAMPLERATE, enc->sample_rate)); CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_CHANNELMODE, mode)); CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_CHANNELORDER, 1)); CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_BITRATEMODE, 0)); CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_BITRATE, bitrate)); CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_TRANSMUX, 0)); CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_AFTERBURNER, afterburner)); CHECK_LIBFDK(aacEncEncode(enc->fdkhandle, NULL, NULL, NULL, NULL)); CHECK_LIBFDK(aacEncInfo(enc->fdkhandle, &enc->info)); enc->frame_size_bytes = enc->info.frameLength * 2 * enc->channels; enc->packet_buffer_size = enc->channels * 768; if(enc->packet_buffer_size < 8192) enc->packet_buffer_size = 8192; enc->packet_buffer = bmalloc(enc->packet_buffer_size); blog(LOG_INFO, "libfdk_aac encoder created"); blog(LOG_INFO, "libfdk_aac bitrate: %d, channels: %d", bitrate / 1000, enc->channels); return enc; fail: if(hasFdkHandle) aacEncClose(&enc->fdkhandle); if(enc->packet_buffer) bfree(enc->packet_buffer); if(enc) bfree(enc); blog(LOG_WARNING, "libfdk_aac encoder creation failed"); return 0; }
static void *enc_create(obs_data_t *settings, obs_encoder_t *encoder, const char *type, const char *alt) { struct enc_encoder *enc; int bitrate = (int)obs_data_get_int(settings, "bitrate"); audio_t *audio = obs_encoder_audio(encoder); avcodec_register_all(); enc = bzalloc(sizeof(struct enc_encoder)); enc->encoder = encoder; enc->codec = avcodec_find_encoder_by_name(type); enc->type = type; if (!enc->codec && alt) { enc->codec = avcodec_find_encoder_by_name(alt); enc->type = alt; } blog(LOG_INFO, "---------------------------------"); if (!enc->codec) { warn("Couldn't find encoder"); goto fail; } if (!bitrate) { warn("Invalid bitrate specified"); return NULL; } enc->context = avcodec_alloc_context3(enc->codec); if (!enc->context) { warn("Failed to create codec context"); goto fail; } enc->context->bit_rate = bitrate * 1000; enc->context->channels = (int)audio_output_get_channels(audio); enc->context->sample_rate = audio_output_get_sample_rate(audio); enc->context->sample_fmt = enc->codec->sample_fmts ? enc->codec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; /* check to make sure sample rate is supported */ if (enc->codec->supported_samplerates) { const int *rate = enc->codec->supported_samplerates; int cur_rate = enc->context->sample_rate; int closest = 0; while (*rate) { int dist = abs(cur_rate - *rate); int closest_dist = abs(cur_rate - closest); if (dist < closest_dist) closest = *rate; rate++; } if (closest) enc->context->sample_rate = closest; } /* if using FFmpeg's AAC encoder, at least set a cutoff value * (recommended by konverter) */ if (strcmp(enc->codec->name, "aac") == 0) { int cutoff1 = 4000 + (int)enc->context->bit_rate / 8; int cutoff2 = 12000 + (int)enc->context->bit_rate / 8; int cutoff3 = enc->context->sample_rate / 2; int cutoff; cutoff = MIN(cutoff1, cutoff2); cutoff = MIN(cutoff, cutoff3); enc->context->cutoff = cutoff; } info("bitrate: %" PRId64 ", channels: %d", enc->context->bit_rate / 1000, enc->context->channels); init_sizes(enc, audio); /* enable experimental FFmpeg encoder if the only one available */ enc->context->strict_std_compliance = -2; enc->context->flags = CODEC_FLAG_GLOBAL_HEADER; if (initialize_codec(enc)) return enc; fail: enc_destroy(enc); return NULL; }
bool audio_callback(void *param, uint64_t start_ts_in, uint64_t end_ts_in, uint64_t *out_ts, uint32_t mixers, struct audio_output_data *mixes) { struct obs_core_data *data = &obs->data; struct obs_core_audio *audio = &obs->audio; struct obs_source *source; size_t sample_rate = audio_output_get_sample_rate(audio->audio); size_t channels = audio_output_get_channels(audio->audio); struct ts_info ts = {start_ts_in, end_ts_in}; size_t audio_size; uint64_t min_ts; da_resize(audio->render_order, 0); da_resize(audio->root_nodes, 0); circlebuf_push_back(&audio->buffered_timestamps, &ts, sizeof(ts)); circlebuf_peek_front(&audio->buffered_timestamps, &ts, sizeof(ts)); min_ts = ts.start; audio_size = AUDIO_OUTPUT_FRAMES * sizeof(float); #if DEBUG_AUDIO == 1 blog(LOG_DEBUG, "ts %llu-%llu", ts.start, ts.end); #endif /* ------------------------------------------------ */ /* build audio render order * NOTE: these are source channels, not audio channels */ for (uint32_t i = 0; i < MAX_CHANNELS; i++) { obs_source_t *source = obs_get_output_source(i); if (source) { obs_source_enum_active_tree(source, push_audio_tree, audio); push_audio_tree(NULL, source, audio); da_push_back(audio->root_nodes, &source); obs_source_release(source); } } pthread_mutex_lock(&data->audio_sources_mutex); source = data->first_audio_source; while (source) { push_audio_tree(NULL, source, audio); source = (struct obs_source*)source->next_audio_source; } pthread_mutex_unlock(&data->audio_sources_mutex); /* ------------------------------------------------ */ /* render audio data */ for (size_t i = 0; i < audio->render_order.num; i++) { obs_source_t *source = audio->render_order.array[i]; obs_source_audio_render(source, mixers, channels, sample_rate, audio_size); } /* ------------------------------------------------ */ /* get minimum audio timestamp */ pthread_mutex_lock(&data->audio_sources_mutex); calc_min_ts(data, sample_rate, &min_ts); pthread_mutex_unlock(&data->audio_sources_mutex); /* ------------------------------------------------ */ /* if a source has gone backward in time, buffer */ if (min_ts < ts.start) add_audio_buffering(audio, sample_rate, &ts, min_ts); /* ------------------------------------------------ */ /* mix audio */ if (!audio->buffering_wait_ticks) { for (size_t i = 0; i < audio->root_nodes.num; i++) { obs_source_t *source = audio->root_nodes.array[i]; if (source->audio_pending) continue; pthread_mutex_lock(&source->audio_buf_mutex); if (source->audio_output_buf[0][0] && source->audio_ts) mix_audio(mixes, source, channels, sample_rate, &ts); pthread_mutex_unlock(&source->audio_buf_mutex); } } /* ------------------------------------------------ */ /* discard audio */ pthread_mutex_lock(&data->audio_sources_mutex); source = data->first_audio_source; while (source) { pthread_mutex_lock(&source->audio_buf_mutex); discard_audio(audio, source, channels, sample_rate, &ts); pthread_mutex_unlock(&source->audio_buf_mutex); source = (struct obs_source*)source->next_audio_source; } pthread_mutex_unlock(&data->audio_sources_mutex); /* ------------------------------------------------ */ /* release audio sources */ release_audio_sources(audio); circlebuf_pop_front(&audio->buffered_timestamps, NULL, sizeof(ts)); *out_ts = ts.start; if (audio->buffering_wait_ticks) { audio->buffering_wait_ticks--; return false; } UNUSED_PARAMETER(param); return true; }