From ec13902565c78b91475c061164477ea7b1219da5 Mon Sep 17 00:00:00 2001 From: Exeldro Date: Sun, 31 Jan 2021 14:44:33 +0100 Subject: [PATCH] add pulse --- .github/workflows/build.yml | 92 +++++ CMakeLists.txt | 2 +- audio-monitor-filter.c | 3 +- audio-monitor-filter.h | 2 +- audio-monitor-mac.c | 2 +- audio-monitor-pulse.c | 715 ++++++++++++++++++++++++++++++++++++ audio-monitor-win.c | 3 +- audio-output-control.cpp | 12 +- 8 files changed, 824 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5220992..ea743ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,6 +105,98 @@ jobs: with: name: '${{ env.FILE_NAME }}' path: ./nightly/*.pkg + ubuntu64: + name: 'Linux/Ubuntu 64-bit' + runs-on: [ubuntu-latest] + steps: + - name: Checkout + uses: actions/checkout@v2.3.3 + with: + repository: obsproject/obs-studio + submodules: 'recursive' + - name: "Checkout plugin" + uses: actions/checkout@v2.3.3 + with: + path: UI/frontend-plugins/${{ env.PLUGIN_NAME }} + - name: Add plugin to obs cmake + shell: bash + run: echo "add_subdirectory(${{ env.PLUGIN_NAME }})" >> UI/frontend-plugins/CMakeLists.txt + - name: Fetch Git Tags + run: git fetch --prune --tags --unshallow + - name: Install prerequisites (Apt) + shell: bash + run: | + sudo dpkg --add-architecture amd64 + sudo apt-get -qq update + sudo apt-get install -y \ + build-essential \ + checkinstall \ + cmake \ + libasound2-dev \ + libavcodec-dev \ + libavdevice-dev \ + libavfilter-dev \ + libavformat-dev \ + libavutil-dev \ + libcurl4-openssl-dev \ + libfdk-aac-dev \ + libfontconfig-dev \ + libfreetype6-dev \ + libgl1-mesa-dev \ + libjack-jackd2-dev \ + libjansson-dev \ + libluajit-5.1-dev \ + libpulse-dev \ + libqt5x11extras5-dev \ + libsndio-dev \ + libspeexdsp-dev \ + libswresample-dev \ + libswscale-dev \ + libudev-dev \ + libv4l-dev \ + libva-dev \ + libvlc-dev \ + libx11-dev \ + libx11-xcb-dev \ + libx264-dev \ + libxcb-randr0-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb-xinerama0-dev \ + libxcomposite-dev \ + libxinerama-dev \ + libmbedtls-dev \ + pkg-config \ + python3-dev \ + qtbase5-dev \ + libqt5svg5-dev \ + swig \ + linux-generic + - name: 'Configure' + shell: bash + run: | + mkdir ./build + cd ./build + cmake -DUNIX_STRUCTURE=0 -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/obs-studio-portable" -DBUILD_CAPTIONS=OFF -DWITH_RTMPS=OFF -DBUILD_BROWSER=OFF .. + - name: 'Build' + shell: bash + working-directory: ${{ github.workspace }}/build + run: make -j4 + - name: 'Package' + shell: bash + run: | + FILE_DATE=$(date +%Y-%m-%d) + FILE_NAME=${{ env.PLUGIN_NAME }}-$FILE_DATE-${{ github.sha }}-linux64.tar.gz + echo "FILE_NAME=${FILE_NAME}" >> $GITHUB_ENV + mkdir -p ./${{ env.PLUGIN_NAME }}/bin/64bit/ + mv ./build/UI/frontend-plugins/${{ env.PLUGIN_NAME }}/${{ env.PLUGIN_NAME }}.so ./${{ env.PLUGIN_NAME }}/bin/64bit/${{ env.PLUGIN_NAME }}.so + mv ./UI/frontend-plugins/${{ env.PLUGIN_NAME }}/data ./${{ env.PLUGIN_NAME }}/data + tar -cvzf "${FILE_NAME}" ${{ env.PLUGIN_NAME }} + - name: 'Publish' + uses: actions/upload-artifact@v2.2.0 + with: + name: '${{ env.FILE_NAME }}' + path: '*.tar.gz' windows: name: Windows runs-on: [windows-latest] diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e7199d..d9cfab0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -project(audio-monitor VERSION 0.3.2) +project(audio-monitor VERSION 0.3.3) set(PROJECT_FULL_NAME "Audio Monitor") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/version.h) diff --git a/audio-monitor-filter.c b/audio-monitor-filter.c index 1dffa0c..b7ed278 100644 --- a/audio-monitor-filter.c +++ b/audio-monitor-filter.c @@ -53,7 +53,8 @@ static void audio_monitor_update(void *data, obs_data_t *settings) d.device_name); } audio_monitor_destroy(audio_monitor->monitor); - audio_monitor->monitor = audio_monitor_create(device_id); + audio_monitor->monitor = audio_monitor_create( + device_id, obs_source_get_name(audio_monitor->source)); audio_monitor_start(audio_monitor->monitor); } float def = (float)obs_data_get_double(settings, "volume") / 100.0f; diff --git a/audio-monitor-filter.h b/audio-monitor-filter.h index 8c6995a..73c1954 100644 --- a/audio-monitor-filter.h +++ b/audio-monitor-filter.h @@ -9,7 +9,7 @@ void audio_monitor_start(struct audio_monitor *audio_monitor); void audio_monitor_audio(void *data, struct obs_audio_data *audio); void audio_monitor_set_volume(struct audio_monitor *audio_monitor, float volume); -struct audio_monitor *audio_monitor_create(const char *device_id); +struct audio_monitor *audio_monitor_create(const char *device_id, const char *source_name); void audio_monitor_destroy(struct audio_monitor *audio_monitor); const char *audio_monitor_get_device_id(struct audio_monitor *audio_monitor); diff --git a/audio-monitor-mac.c b/audio-monitor-mac.c index a8d9877..d3e7410 100644 --- a/audio-monitor-mac.c +++ b/audio-monitor-mac.c @@ -249,7 +249,7 @@ void audio_monitor_set_volume(struct audio_monitor *audio_monitor, float volume) audio_monitor->volume = volume; } -struct audio_monitor *audio_monitor_create(const char *device_id){ +struct audio_monitor *audio_monitor_create(const char *device_id, const char *source_name){ struct audio_monitor *audio_monitor = bzalloc(sizeof(struct audio_monitor)); audio_monitor->device_id = bstrdup(device_id); pthread_mutex_init(&audio_monitor->mutex, NULL); diff --git a/audio-monitor-pulse.c b/audio-monitor-pulse.c index e69de29..6455ad0 100644 --- a/audio-monitor-pulse.c +++ b/audio-monitor-pulse.c @@ -0,0 +1,715 @@ +#include "audio-monitor-pulse.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint_fast32_t pulseaudio_refs = 0; +static pthread_mutex_t pulseaudio_mutex = PTHREAD_MUTEX_INITIALIZER; +static pa_threaded_mainloop *pulseaudio_mainloop = NULL; +static pa_context *pulseaudio_context = NULL; + +struct audio_monitor { + pa_stream *stream; + pa_buffer_attr attr; + enum speaker_layout speakers; + pa_sample_format_t format; + uint_fast32_t samples_per_sec; + uint_fast32_t bytes_per_frame; + + uint_fast8_t channels; + + uint_fast32_t packets; + uint_fast64_t frames; + + struct circlebuf new_data; + size_t buffer_size; + size_t bytesRemaining; + size_t bytes_per_channel; + + audio_resampler_t *resampler; + float volume; + pthread_mutex_t mutex; + char *device_id; + char* source_name; +}; + +struct pulseaudio_default_output { + char *default_sink_name; +}; + +void pulseaudio_lock() +{ + pa_threaded_mainloop_lock(pulseaudio_mainloop); +} + +void pulseaudio_unlock() +{ + pa_threaded_mainloop_unlock(pulseaudio_mainloop); +} + +void pulseaudio_wait() +{ + pa_threaded_mainloop_wait(pulseaudio_mainloop); +} + +void pulseaudio_signal(int wait_for_accept) +{ + pa_threaded_mainloop_signal(pulseaudio_mainloop, wait_for_accept); +} + +void pulseaudio_accept() +{ + pa_threaded_mainloop_accept(pulseaudio_mainloop); +} + +static void pulseaudio_context_state_changed(pa_context *c, void *userdata) +{ + UNUSED_PARAMETER(userdata); + UNUSED_PARAMETER(c); + + pulseaudio_signal(0); +} + +static pa_proplist *pulseaudio_properties() +{ + pa_proplist *p = pa_proplist_new(); + + pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, "OBS"); + pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, "obs"); + pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, "production"); + + return p; +} +static void pulseaudio_init_context() +{ + pulseaudio_lock(); + + pa_proplist *p = pulseaudio_properties(); + pulseaudio_context = pa_context_new_with_proplist( + pa_threaded_mainloop_get_api(pulseaudio_mainloop), + "OBS-Monitor", p); + + pa_context_set_state_callback(pulseaudio_context, + pulseaudio_context_state_changed, NULL); + + pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOAUTOSPAWN, + NULL); + pa_proplist_free(p); + + pulseaudio_unlock(); +} + +int_fast32_t pulseaudio_init() +{ + pthread_mutex_lock(&pulseaudio_mutex); + + if (pulseaudio_refs == 0) { + pulseaudio_mainloop = pa_threaded_mainloop_new(); + pa_threaded_mainloop_start(pulseaudio_mainloop); + + pulseaudio_init_context(); + } + + pulseaudio_refs++; + + pthread_mutex_unlock(&pulseaudio_mutex); + + return 0; +} + +static int_fast32_t pulseaudio_context_ready() +{ + pulseaudio_lock(); + + if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(pulseaudio_context))) { + pulseaudio_unlock(); + return -1; + } + + while (pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) + pulseaudio_wait(); + + pulseaudio_unlock(); + return 0; +} + +int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata) +{ + if (pulseaudio_context_ready() < 0) + return -1; + + pulseaudio_lock(); + + pa_operation *op = + pa_context_get_server_info(pulseaudio_context, cb, userdata); + if (!op) { + pulseaudio_unlock(); + return -1; + } + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) + pulseaudio_wait(); + pa_operation_unref(op); + + pulseaudio_unlock(); + return 0; +} + +pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss, + const pa_channel_map *map) +{ + if (pulseaudio_context_ready() < 0) + return NULL; + + pulseaudio_lock(); + + pa_proplist *p = pulseaudio_properties(); + pa_stream *s = pa_stream_new_with_proplist(pulseaudio_context, name, ss, + map, p); + pa_proplist_free(p); + + pulseaudio_unlock(); + return s; +} + +int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name, + const pa_buffer_attr *attr, + pa_stream_flags_t flags) +{ + if (pulseaudio_context_ready() < 0) + return -1; + + size_t dev_len = strlen(name) - 8; + char *device = bzalloc(dev_len + 1); + memcpy(device, name, dev_len); + + pulseaudio_lock(); + int_fast32_t ret = + pa_stream_connect_playback(s, device, attr, flags, NULL, NULL); + pulseaudio_unlock(); + + bfree(device); + return ret; +} + +static void pulseaudio_default_devices(pa_context *c, const pa_server_info *i, + void *userdata) +{ + UNUSED_PARAMETER(c); + struct pulseaudio_default_output *d = + (struct pulseaudio_default_output *)userdata; + d->default_sink_name = bstrdup(i->default_sink_name); + pulseaudio_signal(0); +} + +void pulseaudio_unref() +{ + pthread_mutex_lock(&pulseaudio_mutex); + + if (--pulseaudio_refs == 0) { + pulseaudio_lock(); + if (pulseaudio_context != NULL) { + pa_context_disconnect(pulseaudio_context); + pa_context_unref(pulseaudio_context); + pulseaudio_context = NULL; + } + pulseaudio_unlock(); + + if (pulseaudio_mainloop != NULL) { + pa_threaded_mainloop_stop(pulseaudio_mainloop); + pa_threaded_mainloop_free(pulseaudio_mainloop); + pulseaudio_mainloop = NULL; + } + } + + pthread_mutex_unlock(&pulseaudio_mutex); +} + +void get_default_id(char **id) +{ + pulseaudio_init(); + struct pulseaudio_default_output *pdo = + bzalloc(sizeof(struct pulseaudio_default_output)); + pulseaudio_get_server_info( + (pa_server_info_cb_t)pulseaudio_default_devices, (void *)pdo); + + if (!pdo->default_sink_name || !*pdo->default_sink_name) { + *id = NULL; + } else { + *id = bzalloc(strlen(pdo->default_sink_name) + 9); + strcat(*id, pdo->default_sink_name); + strcat(*id, ".monitor"); + bfree(pdo->default_sink_name); + } + + bfree(pdo); + pulseaudio_unref(); +} + +static void pulseaudio_server_info(pa_context *c, const pa_server_info *i, + void *userdata) +{ + UNUSED_PARAMETER(c); + UNUSED_PARAMETER(userdata); + + blog(LOG_INFO, "Server name: '%s %s'", i->server_name, + i->server_version); + + pulseaudio_signal(0); +} + + + +int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb, + const char *name, void *userdata) +{ + if (pulseaudio_context_ready() < 0) + return -1; + + pulseaudio_lock(); + + pa_operation *op = pa_context_get_source_info_by_name( + pulseaudio_context, name, cb, userdata); + if (!op) { + pulseaudio_unlock(); + return -1; + } + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) + pulseaudio_wait(); + pa_operation_unref(op); + + pulseaudio_unlock(); + + return 0; +} + +static enum audio_format +pulseaudio_to_obs_audio_format(pa_sample_format_t format) +{ + switch (format) { + case PA_SAMPLE_U8: + return AUDIO_FORMAT_U8BIT; + case PA_SAMPLE_S16LE: + return AUDIO_FORMAT_16BIT; + case PA_SAMPLE_S32LE: + return AUDIO_FORMAT_32BIT; + case PA_SAMPLE_FLOAT32LE: + return AUDIO_FORMAT_FLOAT; + default: + return AUDIO_FORMAT_UNKNOWN; + } +} + +static pa_channel_map pulseaudio_channel_map(enum speaker_layout layout) +{ + pa_channel_map ret; + + ret.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + ret.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + ret.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + ret.map[3] = PA_CHANNEL_POSITION_LFE; + ret.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; + ret.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; + ret.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; + ret.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; + + switch (layout) { + case SPEAKERS_MONO: + ret.channels = 1; + ret.map[0] = PA_CHANNEL_POSITION_MONO; + break; + + case SPEAKERS_STEREO: + ret.channels = 2; + break; + + case SPEAKERS_2POINT1: + ret.channels = 3; + ret.map[2] = PA_CHANNEL_POSITION_LFE; + break; + + case SPEAKERS_4POINT0: + ret.channels = 4; + ret.map[3] = PA_CHANNEL_POSITION_REAR_CENTER; + break; + + case SPEAKERS_4POINT1: + ret.channels = 5; + ret.map[4] = PA_CHANNEL_POSITION_REAR_CENTER; + break; + + case SPEAKERS_5POINT1: + ret.channels = 6; + break; + + case SPEAKERS_7POINT1: + ret.channels = 8; + break; + + case SPEAKERS_UNKNOWN: + default: + ret.channels = 0; + break; + } + + return ret; +} + +static enum speaker_layout +pulseaudio_channels_to_obs_speakers(uint_fast32_t channels) +{ + switch (channels) { + case 0: + return SPEAKERS_UNKNOWN; + case 1: + return SPEAKERS_MONO; + case 2: + return SPEAKERS_STEREO; + case 3: + return SPEAKERS_2POINT1; + case 4: + return SPEAKERS_4POINT0; + case 5: + return SPEAKERS_4POINT1; + case 6: + return SPEAKERS_5POINT1; + case 8: + return SPEAKERS_7POINT1; + default: + return SPEAKERS_UNKNOWN; + } +} + +static void pulseaudio_source_info(pa_context *c, const pa_source_info *i, + int eol, void *userdata) +{ + UNUSED_PARAMETER(c); + struct audio_monitor *data = userdata; + // An error occurred + if (eol < 0) { + data->format = PA_SAMPLE_INVALID; + goto skip; + } + // Terminating call for multi instance callbacks + if (eol > 0) + goto skip; + + blog(LOG_INFO, "Audio format: %s, %" PRIu32 " Hz, %" PRIu8 " channels", + pa_sample_format_to_string(i->sample_spec.format), + i->sample_spec.rate, i->sample_spec.channels); + + pa_sample_format_t format = i->sample_spec.format; + if (pulseaudio_to_obs_audio_format(format) == AUDIO_FORMAT_UNKNOWN) { + format = PA_SAMPLE_FLOAT32LE; + + blog(LOG_INFO, + "Sample format %s not supported by OBS," + "using %s instead for recording", + pa_sample_format_to_string(i->sample_spec.format), + pa_sample_format_to_string(format)); + } + + uint8_t channels = i->sample_spec.channels; + if (pulseaudio_channels_to_obs_speakers(channels) == SPEAKERS_UNKNOWN) { + channels = 2; + + blog(LOG_INFO, + "%c channels not supported by OBS," + "using %c instead for recording", + i->sample_spec.channels, channels); + } + + data->format = format; + data->samples_per_sec = i->sample_spec.rate; + data->channels = channels; +skip: + pulseaudio_signal(0); +} + +void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb, + void *userdata) +{ + if (pulseaudio_context_ready() < 0) + return; + + pulseaudio_lock(); + pa_stream_set_write_callback(p, cb, userdata); + pulseaudio_unlock(); +} + +void pulseaudio_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, + void *userdata) +{ + if (pulseaudio_context_ready() < 0) + return; + + pulseaudio_lock(); + pa_stream_set_underflow_callback(p, cb, userdata); + pulseaudio_unlock(); +} + +static void pulseaudio_stream_write(pa_stream *p, size_t nbytes, void *userdata) +{ + UNUSED_PARAMETER(p); + struct audio_monitor *data = userdata; + + pthread_mutex_lock(&data->mutex); + data->bytesRemaining += nbytes; + pthread_mutex_unlock(&data->mutex); + + pulseaudio_signal(0); +} + +static void pulseaudio_underflow(pa_stream *p, void *userdata) +{ + UNUSED_PARAMETER(p); + struct audio_monitor *data = userdata; + + pthread_mutex_lock(&data->mutex); + //if (obs_source_active(data->source)) + data->attr.tlength = (data->attr.tlength * 3) / 2; + + pa_stream_set_buffer_attr(data->stream, &data->attr, NULL, NULL); + pthread_mutex_unlock(&data->mutex); + + pulseaudio_signal(0); +} + +void audio_monitor_stop(struct audio_monitor *audio_monitor){ + if (audio_monitor->stream) { + /* Stop the stream */ + pulseaudio_lock(); + pa_stream_disconnect(audio_monitor->stream); + pulseaudio_unlock(); + + /* Remove the callbacks, to ensure we no longer try to do anything + * with this stream object */ + pulseaudio_write_callback(audio_monitor->stream, NULL, NULL); + pulseaudio_set_underflow_callback(audio_monitor->stream, NULL, NULL); + + /* Unreference the stream and drop it. PA will free it when it can. */ + pulseaudio_lock(); + pa_stream_unref(audio_monitor->stream); + pulseaudio_unlock(); + + audio_monitor->stream = NULL; + } + + //blog(LOG_INFO, "Stopped Monitoring in '%s'", audio_monitor->device_id); + blog(LOG_INFO, + "Got %" PRIuFAST32 " packets with %" PRIuFAST64 " frames", + audio_monitor->packets, audio_monitor->frames); + + audio_monitor->packets = 0; + audio_monitor->frames = 0; + audio_resampler_destroy(audio_monitor->resampler); + audio_monitor->resampler = NULL; +} + +void audio_monitor_start(struct audio_monitor *audio_monitor){ + pulseaudio_init(); + char *device = NULL; + if (strcmp(audio_monitor->device_id, "default") == 0) { + get_default_id(&device); + }else { + device = bstrdup(audio_monitor->device_id); + } + if (!device) + return; + if (pulseaudio_get_server_info(pulseaudio_server_info, + (void *)audio_monitor) < 0) { + blog(LOG_ERROR, "Unable to get server info !"); + bfree(device); + return; + } + if (pulseaudio_get_source_info(pulseaudio_source_info, + device, + (void *)audio_monitor) < 0) { + blog(LOG_ERROR, "Unable to get source info !"); + bfree(device); + return; + } + bfree(device); + + if (audio_monitor->format == PA_SAMPLE_INVALID) { + blog(LOG_ERROR, + "An error occurred while getting the source info!"); + return; + } + + pa_sample_spec spec; + spec.format = audio_monitor->format; + spec.rate = (uint32_t)audio_monitor->samples_per_sec; + spec.channels = audio_monitor->channels; + + if (!pa_sample_spec_valid(&spec)) { + blog(LOG_ERROR, "Sample spec is not valid"); + return false; + } + + const struct audio_output_info *info = + audio_output_get_info(obs_get_audio()); + + struct resample_info from = {.samples_per_sec = info->samples_per_sec, + .speakers = info->speakers, + .format = AUDIO_FORMAT_FLOAT_PLANAR}; + struct resample_info to = { + .samples_per_sec = (uint32_t)audio_monitor->samples_per_sec, + .speakers = + pulseaudio_channels_to_obs_speakers(audio_monitor->channels), + .format = pulseaudio_to_obs_audio_format(audio_monitor->format)}; + + audio_monitor->resampler = audio_resampler_create(&to, &from); + + if (!audio_monitor->resampler) { + blog(LOG_WARNING, "%s: %s", __FUNCTION__, + "Failed to create resampler"); + return; + } + + audio_monitor->bytes_per_channel = get_audio_bytes_per_channel( + pulseaudio_to_obs_audio_format(audio_monitor->format)); + audio_monitor->speakers = pulseaudio_channels_to_obs_speakers(spec.channels); + audio_monitor->bytes_per_frame = pa_frame_size(&spec); + + pa_channel_map channel_map = pulseaudio_channel_map(audio_monitor->speakers); + + audio_monitor->stream = pulseaudio_stream_new( + audio_monitor->source_name, &spec, &channel_map); + if (!audio_monitor->stream) { + blog(LOG_ERROR, "Unable to create stream"); + return; + } + + audio_monitor->attr.fragsize = (uint32_t)-1; + audio_monitor->attr.maxlength = (uint32_t)-1; + audio_monitor->attr.minreq = (uint32_t)-1; + audio_monitor->attr.prebuf = (uint32_t)-1; + audio_monitor->attr.tlength = pa_usec_to_bytes(25000, &spec); + + audio_monitor->buffer_size = + audio_monitor->bytes_per_frame * pa_usec_to_bytes(5000, &spec); + + pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_AUTO_TIMING_UPDATE; + + int_fast32_t ret = pulseaudio_connect_playback( + audio_monitor->stream, audio_monitor->device_id, &audio_monitor->attr, flags); + if (ret < 0) { + audio_monitor_stop(audio_monitor); + blog(LOG_ERROR, "Unable to connect to stream"); + return; + } + + blog(LOG_INFO, "Started Monitoring in '%s'", audio_monitor->device_id); + + pulseaudio_write_callback(audio_monitor->stream, + pulseaudio_stream_write, + (void *)audio_monitor); + + pulseaudio_set_underflow_callback(audio_monitor->stream, + pulseaudio_underflow, + (void *)audio_monitor); +} + +static void do_stream_write(void *param) +{ + struct audio_monitor *data = param; + uint8_t *buffer = NULL; + + while (data->new_data.size >= data->buffer_size && + data->bytesRemaining > 0) { + size_t bytesToFill = data->buffer_size; + + if (bytesToFill > data->bytesRemaining) + bytesToFill = data->bytesRemaining; + + pulseaudio_lock(); + pa_stream_begin_write(data->stream, (void **)&buffer, + &bytesToFill); + pulseaudio_unlock(); + + circlebuf_pop_front(&data->new_data, buffer, bytesToFill); + + pulseaudio_lock(); + pa_stream_write(data->stream, buffer, bytesToFill, NULL, 0LL, + PA_SEEK_RELATIVE); + pulseaudio_unlock(); + + data->bytesRemaining -= bytesToFill; + } +} + +void audio_monitor_audio(void *data, struct obs_audio_data *audio){ + struct audio_monitor *audio_monitor = data; + if (!audio_monitor->resampler && audio_monitor->device_id && + strlen(audio_monitor->device_id) && + pthread_mutex_trylock(&audio_monitor->mutex) == 0) { + audio_monitor_start(audio_monitor); + pthread_mutex_unlock(&audio_monitor->mutex); + } + if (!audio_monitor->resampler || + pthread_mutex_trylock(&audio_monitor->mutex) != 0) + return; + + uint8_t *resample_data[MAX_AV_PLANES]; + uint32_t resample_frames; + uint64_t ts_offset; + bool success = audio_resampler_resample( + audio_monitor->resampler, resample_data, &resample_frames, + &ts_offset, (const uint8_t *const *)audio->data, + (uint32_t)audio->frames); + if (!success) { + pthread_mutex_unlock(&audio_monitor->mutex); + return; + } + /* apply volume */ + if (!close_float(audio_monitor->volume, 1.0f, EPSILON)) { + register float *cur = (float *)resample_data[0]; + register float *end = + cur + resample_frames * audio_monitor->channels; + + while (cur < end) + *(cur++) *= audio_monitor->volume; + } + size_t bytes = audio_monitor->bytes_per_frame * resample_frames; + + circlebuf_push_back(&audio_monitor->new_data, resample_data[0], bytes); + audio_monitor->packets++; + audio_monitor->frames += resample_frames; + pthread_mutex_unlock(&audio_monitor->mutex); + do_stream_write(data); +} + +void audio_monitor_set_volume(struct audio_monitor *audio_monitor, + float volume){ + audio_monitor->volume = volume; +} + +struct audio_monitor *audio_monitor_create(const char *device_id, const char *source_name){ + struct audio_monitor *audio_monitor = + bzalloc(sizeof(struct audio_monitor)); + audio_monitor->device_id = bstrdup(device_id); + audio_monitor->source_name = bstrdup(source_name); + pthread_mutex_init(&audio_monitor->mutex, NULL); + return audio_monitor; +} + +void audio_monitor_destroy(struct audio_monitor *audio_monitor){ + if (!audio_monitor) + return; + audio_monitor_stop(audio_monitor); + bfree(audio_monitor->source_name); + bfree(audio_monitor->device_id); + bfree(audio_monitor); +} + +const char *audio_monitor_get_device_id(struct audio_monitor *audio_monitor){ + return audio_monitor->device_id; +} \ No newline at end of file diff --git a/audio-monitor-win.c b/audio-monitor-win.c index 35891eb..6966713 100644 --- a/audio-monitor-win.c +++ b/audio-monitor-win.c @@ -236,7 +236,8 @@ void audio_monitor_set_volume(struct audio_monitor *audio_monitor, float volume) audio_monitor->volume = volume; } -struct audio_monitor *audio_monitor_create(const char *device_id) +struct audio_monitor *audio_monitor_create(const char *device_id, + const char *source_name) { struct audio_monitor *audio_monitor = bzalloc(sizeof(struct audio_monitor)); diff --git a/audio-output-control.cpp b/audio-output-control.cpp index 71ea5e2..2465e12 100644 --- a/audio-output-control.cpp +++ b/audio-output-control.cpp @@ -75,9 +75,14 @@ AudioOutputControl::AudioOutputControl(int track, obs_data_t *settings) obs_data_get_string(device, "id")); auto it = audioDevices.find(device_id); if (it == audioDevices.end()) { + QString name = QT_UTF8( + obs_module_text("Track")); + name += " "; + name += QString::number(track + 1); audio_monitor *monitor = audio_monitor_create( - QT_TO_UTF8(device_id)); + QT_TO_UTF8(device_id), + QT_TO_UTF8(name)); audio_monitor_set_volume(monitor, 1.0f); audio_monitor_start(monitor); audioDevices[device_id] = monitor; @@ -366,8 +371,11 @@ void AudioOutputControl::AddDevice(QString device_id, QString device_name) { auto it = audioDevices.find(device_id); if (it == audioDevices.end()) { + QString name = QT_UTF8(obs_module_text("Track")); + name += " "; + name += QString::number(track + 1); audio_monitor *monitor = - audio_monitor_create(QT_TO_UTF8(device_id)); + audio_monitor_create(QT_TO_UTF8(device_id), QT_TO_UTF8(name)); audio_monitor_set_volume(monitor, 1.0f); audio_monitor_start(monitor); audioDevices[device_id] = monitor;