diff options
author | Mikhail Burakov <mburakov@mailbox.org> | 2024-05-20 08:33:47 +0200 |
---|---|---|
committer | Mikhail Burakov <mburakov@mailbox.org> | 2024-05-20 08:33:47 +0200 |
commit | 8cebffea22ae0f636f91fd1851538aa3c9b10708 (patch) | |
tree | e5393e6469610501aee2f3f53a3982399bdf1995 | |
parent | 3a4e9a0818e2e352eb87b13a250c9ad6e67524b0 (diff) |
Receive audio config from server
-rw-r--r-- | audio.c | 120 | ||||
-rw-r--r-- | audio.h | 3 | ||||
-rw-r--r-- | main.c | 36 |
3 files changed, 124 insertions, 35 deletions
@@ -29,6 +29,8 @@ #include "toolbox/utils.h" struct AudioContext { + size_t sample_rate; + size_t audio_stride; struct AtomicQueue queue; struct pw_thread_loop* pw_thread_loop; struct pw_stream* pw_stream; @@ -37,6 +39,81 @@ struct AudioContext { size_t queue_samples_count; }; +static bool LookupChannel(const char* name, uint32_t* value) { + struct { + const char* name; + enum spa_audio_channel value; + } static const kChannelMap[] = { +#define _(op) {.name = #op, .value = SPA_AUDIO_CHANNEL_##op} + _(FL), _(FR), _(FC), _(LFE), _(SL), _(SR), _(FLC), + _(FRC), _(RC), _(RL), _(RR), _(TC), _(TFL), _(TFC), + _(TFR), _(TRL), _(TRC), _(TRR), _(RLC), _(RRC), _(FLW), + _(FRW), _(LFE2), _(FLH), _(FCH), _(FRH), _(TFLC), _(TFRC), + _(TSL), _(TSR), _(LLFE), _(RLFE), _(BC), _(BLC), _(BRC), +#undef _ + }; + for (size_t i = 0; i < LENGTH(kChannelMap); i++) { + if (!strcmp(kChannelMap[i].name, name)) { + if (value) *value = kChannelMap[i].value; + return true; + } + } + return false; +} + +static size_t ParseChannelMap( + const char* channel_map, + uint32_t channel_positions[SPA_AUDIO_MAX_CHANNELS]) { + char minibuf[5]; + size_t channels_counter = 0; + for (size_t i = 0, j = 0;; i++) { + switch (channel_map[i]) { + case 0: + case ',': + minibuf[j] = 0; + if (channels_counter == SPA_AUDIO_MAX_CHANNELS || + !LookupChannel(minibuf, &channel_positions[channels_counter++])) + return 0; + if (!channel_map[i]) return channels_counter; + j = 0; + break; + default: + if (j == 4) return 0; + minibuf[j++] = channel_map[i]; + break; + } + } +} + +static bool ParseAudioConfig(const char* audio_config, + struct spa_audio_info_raw* out_audio_info) { + int sample_rate = atoi(audio_config); + if (sample_rate != 44100 && sample_rate != 48000) { + LOG("Invalid sample rate requested"); + return false; + } + const char* channel_map = strchr(audio_config, ':'); + if (!channel_map) { + LOG("Invalid audio config requested"); + return false; + } + + channel_map++; + struct spa_audio_info_raw audio_info = { + .format = SPA_AUDIO_FORMAT_S16_LE, + .rate = (uint32_t)sample_rate, + }; + audio_info.channels = + (uint32_t)ParseChannelMap(channel_map, audio_info.position); + if (!audio_info.channels) { + LOG("Invalid channel map requested"); + return false; + } + + *out_audio_info = audio_info; + return true; +} + static void OnStreamProcess(void* data) { struct AudioContext* audio_context = data; struct pw_buffer* pw_buffer = @@ -46,10 +123,10 @@ static void OnStreamProcess(void* data) { return; } - static const size_t stride = sizeof(int16_t) * 2; struct spa_data* spa_data = &pw_buffer->buffer->datas[0]; - size_t requested = - MIN(pw_buffer->requested, spa_data->maxsize / stride) * stride; + size_t requested = MIN(pw_buffer->requested, + spa_data->maxsize / audio_context->audio_stride) * + audio_context->audio_stride; size_t available = AtomicQueueRead(&audio_context->queue, spa_data->data, requested); @@ -59,13 +136,22 @@ static void OnStreamProcess(void* data) { } spa_data->chunk->offset = 0; - spa_data->chunk->stride = stride; + spa_data->chunk->stride = (int32_t)audio_context->audio_stride; spa_data->chunk->size = (uint32_t)requested; pw_stream_queue_buffer(audio_context->pw_stream, pw_buffer); return; } -struct AudioContext* AudioContextCreate(size_t queue_size) { +struct AudioContext* AudioContextCreate(size_t queue_size, + const char* audio_config) { + LOG("Audio config is \"%s\"", audio_config); + + struct spa_audio_info_raw audio_info; + if (!ParseAudioConfig(audio_config, &audio_info)) { + LOG("Failed to parse audio config argument"); + return NULL; + } + pw_init(0, NULL); struct AudioContext* audio_context = malloc(sizeof(struct AudioContext)); if (!audio_context) { @@ -73,8 +159,10 @@ struct AudioContext* AudioContextCreate(size_t queue_size) { return NULL; } + audio_context->sample_rate = audio_info.rate; + audio_context->audio_stride = audio_info.channels * sizeof(int16_t); if (!AtomicQueueCreate(&audio_context->queue, - queue_size * sizeof(int16_t) * 2)) { + queue_size * audio_context->audio_stride)) { LOG("Failed to create buffer queue (%s)", strerror(errno)); goto rollback_audio_context; } @@ -93,11 +181,10 @@ struct AudioContext* AudioContextCreate(size_t queue_size) { goto rollback_thread_loop; } - // TOOD(mburakov): Read these from the commandline? struct pw_properties* pw_properties = pw_properties_new( #define _(...) __VA_ARGS__ _(PW_KEY_MEDIA_TYPE, "Audio"), _(PW_KEY_MEDIA_CATEGORY, "Playback"), - _(PW_KEY_MEDIA_ROLE, "Game"), _(PW_KEY_NODE_LATENCY, "128/48000"), NULL + _(PW_KEY_MEDIA_ROLE, "Game"), NULL #undef _ ); if (!pw_properties) { @@ -106,6 +193,8 @@ struct AudioContext* AudioContextCreate(size_t queue_size) { goto rollback_thread_loop; } + pw_properties_setf(pw_properties, PW_KEY_NODE_LATENCY, "128/%du", + audio_info.rate); static const struct pw_stream_events kPwStreamEvents = { .version = PW_VERSION_STREAM_EVENTS, .process = OnStreamProcess, @@ -122,14 +211,8 @@ struct AudioContext* AudioContextCreate(size_t queue_size) { uint8_t buffer[1024]; struct spa_pod_builder spa_pod_builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - const struct spa_pod* params[] = { - spa_format_audio_raw_build( - &spa_pod_builder, SPA_PARAM_EnumFormat, - &SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_S16_LE, - .rate = 48000, .channels = 2, - .position = {SPA_AUDIO_CHANNEL_FL, - SPA_AUDIO_CHANNEL_FR})), - }; + const struct spa_pod* params[] = {spa_format_audio_raw_build( + &spa_pod_builder, SPA_PARAM_EnumFormat, &audio_info)}; static const enum pw_stream_flags kPwStreamFlags = PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS; @@ -162,8 +245,7 @@ bool AudioContextDecode(struct AudioContext* audio_context, const void* buffer, LOG("Audio queue overflow!"); size_t queue_size = atomic_load_explicit(&audio_context->queue.size, memory_order_relaxed); - static const size_t stride = sizeof(int16_t) * 2; - audio_context->queue_samples_sum += queue_size / stride; + audio_context->queue_samples_sum += queue_size / audio_context->audio_stride; audio_context->queue_samples_count++; return true; } @@ -178,7 +260,7 @@ uint64_t AudioContextGetLatency(struct AudioContext* audio_context) { } // TODO(mburakov): This number is extremely optimistic, i.e. Bluetooth delays // are not accounted for. Is it anyhow possible to get this information? - return (128 + queue_latency) * 1000000 / 48000; + return (128 + queue_latency) * 1000000 / audio_context->sample_rate; } void AudioContextDestroy(struct AudioContext* audio_context) { @@ -24,7 +24,8 @@ struct AudioContext; -struct AudioContext* AudioContextCreate(size_t queue_size); +struct AudioContext* AudioContextCreate(size_t queue_size, + const char* audio_config); bool AudioContextDecode(struct AudioContext* audio_context, const void* buffer, size_t size); uint64_t AudioContextGetLatency(struct AudioContext* audio_context); @@ -45,6 +45,7 @@ static volatile sig_atomic_t g_signal; static void OnSignal(int status) { g_signal = status; } struct Context { + size_t audio_buffer_size; struct InputStream* input_stream; struct Window* window; size_t overlay_width; @@ -150,12 +151,22 @@ static void GetMaxOverlaySize(size_t* width, size_t* height) { static struct Context* ContextCreate(int sock, bool no_input, bool stats, const char* audio_buffer) { + int audio_buffer_size = 0; + if (audio_buffer) { + audio_buffer_size = atoi(audio_buffer); + if (audio_buffer_size <= 0) { + LOG("Invalid audio buffer size"); + return NULL; + } + } + struct Context* context = calloc(1, sizeof(struct Context)); if (!context) { LOG("Failed to allocate context (%s)", strerror(errno)); return NULL; } + context->audio_buffer_size = (size_t)audio_buffer_size; const struct WindowEventHandlers* maybe_window_event_handlers = NULL; if (!no_input) { context->input_stream = InputStreamCreate(sock); @@ -197,23 +208,8 @@ static struct Context* ContextCreate(int sock, bool no_input, bool stats, LOG("Failed to create decode context"); goto rollback_overlay; } - - if (audio_buffer) { - int buffer_size = atoi(audio_buffer); - if (buffer_size <= 0) { - LOG("Invalid audio buffer size"); - goto rollback_decode_context; - } - context->audio_context = AudioContextCreate((size_t)buffer_size); - if (!context->audio_context) { - LOG("Failed to create audio context"); - goto rollback_decode_context; - } - } return context; -rollback_decode_context: - DecodeContextDestroy(context->decode_context); rollback_overlay: if (context->overlay) OverlayDestroy(context->overlay); rollback_window: @@ -358,6 +354,16 @@ static bool HandleVideoStream(struct Context* context) { static bool HandleAudioStream(struct Context* context) { const struct Proto* proto = context->buffer.data; + + if (proto->flags & PROTO_FLAG_KEYFRAME) { + // TODO(mburakov): Dynamic reconfiguration is unsupported. + if (context->audio_context || !context->audio_buffer_size) return true; + context->audio_context = AudioContextCreate(context->audio_buffer_size, + (const char*)proto->data); + if (!context->audio_context) LOG("Failed to create audio context"); + return !!context->audio_context; + } + if (!context->audio_context) return true; if (!AudioContextDecode(context->audio_context, proto->data, proto->size)) { LOG("Failed to decode incoming audio data"); |