summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--audio.c110
-rw-r--r--audio.h6
-rw-r--r--main.c38
3 files changed, 126 insertions, 28 deletions
diff --git a/audio.c b/audio.c
index ecc6e39..fa5b23e 100644
--- a/audio.c
+++ b/audio.c
@@ -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);
}
}
diff --git a/audio.h b/audio.h
index 1bb71b2..1fb71d0 100644
--- a/audio.h
+++ b/audio.h
@@ -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);
diff --git a/main.c b/main.c
index cfda341..116bf72 100644
--- a/main.c
+++ b/main.c
@@ -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;