diff options
-rw-r--r-- | audio.c | 110 | ||||
-rw-r--r-- | audio.h | 6 | ||||
-rw-r--r-- | main.c | 38 |
3 files changed, 126 insertions, 28 deletions
@@ -36,6 +36,7 @@ #define STATUS_ERR 1 struct AudioContext { + size_t one_second_size; const struct AudioContextCallbacks* callbacks; void* user; @@ -45,6 +46,83 @@ struct AudioContext { struct pw_stream* pw_stream; }; +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, + const char** out_channel_map, + 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_channel_map = channel_map; + *out_audio_info = audio_info; + return true; +} + static void WakeClient(const struct AudioContext* audio_context, char status) { if (write(audio_context->waker[1], &status, sizeof(status)) != sizeof(status)) { @@ -128,7 +206,15 @@ failure: } struct AudioContext* AudioContextCreate( - const struct AudioContextCallbacks* callbacks, void* user) { + const char* audio_config, const struct AudioContextCallbacks* callbacks, + void* user) { + const char* channel_map; + struct spa_audio_info_raw audio_info; + if (!ParseAudioConfig(audio_config, &channel_map, &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) { @@ -136,6 +222,8 @@ struct AudioContext* AudioContextCreate( return NULL; } *audio_context = (struct AudioContext){ + .one_second_size = + audio_info.channels * audio_info.rate * sizeof(int16_t), .callbacks = callbacks, .user = user, }; @@ -165,11 +253,9 @@ struct AudioContext* AudioContextCreate( goto rollback_thread_loop; } - // TOOD(mburakov): Read these from the commandline? struct pw_properties* pw_properties = pw_properties_new( #define _(...) __VA_ARGS__ - _(PW_KEY_AUDIO_FORMAT, "S16LE"), _(PW_KEY_AUDIO_RATE, "48000"), - _(PW_KEY_AUDIO_CHANNELS, "2"), _(SPA_KEY_AUDIO_POSITION, "FL,FR"), + _(PW_KEY_AUDIO_FORMAT, "S16LE"), _(SPA_KEY_AUDIO_POSITION, channel_map), _(PW_KEY_NODE_NAME, "streamer-sink"), _(PW_KEY_NODE_VIRTUAL, "true"), _(PW_KEY_MEDIA_CLASS, "Audio/Sink"), NULL #undef _ @@ -180,6 +266,9 @@ struct AudioContext* AudioContextCreate( goto rollback_thread_loop; } + pw_properties_setf(pw_properties, PW_KEY_AUDIO_RATE, "%du", audio_info.rate); + pw_properties_setf(pw_properties, PW_KEY_AUDIO_CHANNELS, "%du", + audio_info.channels); static const struct pw_stream_events kPwStreamEvents = { .version = PW_VERSION_STREAM_EVENTS, .state_changed = OnStreamStateChanged, @@ -198,14 +287,8 @@ struct AudioContext* AudioContextCreate( 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; @@ -263,7 +346,8 @@ bool AudioContextProcessEvents(struct AudioContext* audio_context) { } if (!buffer_queue_item) return true; audio_context->callbacks->OnAudioReady( - audio_context->user, buffer_queue_item->data, buffer_queue_item->size); + audio_context->user, buffer_queue_item->data, buffer_queue_item->size, + buffer_queue_item->size * 1000000 / audio_context->one_second_size); BufferQueueItemDestroy(buffer_queue_item); } } @@ -24,12 +24,14 @@ struct AudioContext; struct AudioContextCallbacks { - void (*OnAudioReady)(void* user, const void* buffer, size_t size); + void (*OnAudioReady)(void* user, const void* buffer, size_t size, + size_t latency); }; #ifdef USE_PIPEWIRE struct AudioContext* AudioContextCreate( - const struct AudioContextCallbacks* callbacks, void* user); + const char* audio_config, const struct AudioContextCallbacks* callbacks, + void* user); int AudioContextGetEventsFd(struct AudioContext* audio_context); bool AudioContextProcessEvents(struct AudioContext* audio_context); void AudioContextDestroy(struct AudioContext* audio_context); @@ -49,6 +49,7 @@ static void OnSignal(int status) { g_signal = status; } struct Contexts { bool disable_uhid; + const char* audio_config; struct AudioContext* audio_context; struct GpuContext* gpu_context; struct IoMuxer io_muxer; @@ -120,20 +121,15 @@ static void MaybeDropClient(struct Contexts* contexts) { } static void OnAudioContextAudioReady(void* user, const void* buffer, - size_t size) { + size_t size, size_t latency) { struct Contexts* contexts = user; if (contexts->client_fd == -1) return; - // TODO(mburakov): Stride must be calculated from commandline arguments! - static const size_t stride = sizeof(int16_t) * 2; - size_t micros = size * 1000000 / stride / 48000; - size_t latency = MIN(micros, UINT16_MAX); - struct Proto proto = { .size = (uint32_t)size, .type = PROTO_TYPE_AUDIO, .flags = 0, - .latency = (uint16_t)latency, + .latency = (uint16_t)MIN(latency, UINT16_MAX), }; if (!WriteProto(contexts->client_fd, &proto, buffer)) { LOG("Failed to write audio frame"); @@ -308,6 +304,17 @@ static void OnClientConnecting(void* user) { LOG("Failed to schedule capture events reading (%s)", strerror(errno)); goto drop_client; } + + struct Proto proto = { + .size = (uint32_t)strlen(contexts->audio_config) + 1, + .type = PROTO_TYPE_AUDIO, + .flags = PROTO_FLAG_KEYFRAME, + .latency = 0, + }; + if (!WriteProto(contexts->client_fd, &proto, contexts->audio_config)) { + LOG("Failed to write audio configuration"); + goto drop_client; + } return; drop_client: @@ -316,7 +323,7 @@ drop_client: int main(int argc, char* argv[]) { if (argc < 2) { - LOG("Usage: %s <port> [--disable-uhid] [--disable-audio]", argv[0]); + LOG("Usage: %s <port> [--disable-uhid] [--audio <rate:channels>]", argv[0]); return EXIT_FAILURE; } if (signal(SIGINT, OnSignal) == SIG_ERR || @@ -330,21 +337,26 @@ int main(int argc, char* argv[]) { .server_fd = -1, .client_fd = -1, }; - bool disable_audio = false; + const char* audio_config = NULL; for (int i = 2; i < argc; i++) { if (!strcmp(argv[i], "--disable-uhid")) { contexts.disable_uhid = true; - } else if (!strcmp(argv[i], "--disable-audio")) { - disable_audio = true; + } else if (!strcmp(argv[i], "--audio")) { + audio_config = argv[++i]; + if (i == argc) { + LOG("Audio argument requires a value"); + return EXIT_FAILURE; + } } } static struct AudioContextCallbacks kAudioContextCallbacks = { .OnAudioReady = OnAudioContextAudioReady, }; - if (!disable_audio) { + if (audio_config) { + contexts.audio_config = audio_config; contexts.audio_context = - AudioContextCreate(&kAudioContextCallbacks, &contexts); + AudioContextCreate(audio_config, &kAudioContextCallbacks, &contexts); if (!contexts.audio_context) { LOG("Failed to create audio context"); return EXIT_FAILURE; |