/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Audio source using \ref pw_stream "pw_stream" and ringbuffer. [title] */ #include #include #include #include #include #include #include #define M_PI_M2f (float)(M_PI+M_PI) #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 #define DEFAULT_VOLUME 0.7f #define BUFFER_SIZE (16*1024) struct data { struct pw_main_loop *main_loop; struct pw_loop *loop; struct pw_stream *stream; float accumulator; struct spa_source *refill_event; struct spa_ringbuffer ring; float buffer[BUFFER_SIZE * DEFAULT_CHANNELS]; }; static void fill_f32(struct data *d, uint32_t offset, int n_frames) { float val; int i, c; for (i = 0; i < n_frames; i++) { d->accumulator += M_PI_M2f * 440 / DEFAULT_RATE; if (d->accumulator >= M_PI_M2f) d->accumulator -= M_PI_M2f; val = sinf(d->accumulator) * DEFAULT_VOLUME; for (c = 0; c < DEFAULT_CHANNELS; c++) d->buffer[((offset + i) % BUFFER_SIZE) * DEFAULT_CHANNELS + c] = val; } } static void do_refill(void *userdata, uint64_t count) { struct data *data = userdata; int32_t filled; uint32_t index, avail; filled = spa_ringbuffer_get_write_index(&data->ring, &index); /* we xrun, this can not happen because we never read more * than what there is in the ringbuffer and we never write more than * what is left */ spa_assert(filled >= 0); spa_assert(filled <= BUFFER_SIZE); /* this is how much samples we can write */ avail = BUFFER_SIZE - filled; /* write new samples to the ringbuffer from the given index */ fill_f32(data, index, avail); /* and advance the ringbuffer */ spa_ringbuffer_write_update(&data->ring, index + avail); } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. generate stuff in the buffer ... * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; int n_frames, stride; uint8_t *p; uint32_t index; int32_t avail; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) return; /* the amount of space in the ringbuffer and the read index */ avail = spa_ringbuffer_get_read_index(&data->ring, &index); stride = sizeof(float) * DEFAULT_CHANNELS; n_frames = buf->datas[0].maxsize / stride; if (b->requested) n_frames = SPA_MIN((int)b->requested, n_frames); if (avail < (int32_t)n_frames) { /* there is not enough data available in the ringbuffer, * fill with silence and hope it will be filled next time */ memset(p, 0, n_frames * stride); pw_log_warn("underrun"); } else { /* enough data in the ringbuffer, copy it into the buffer data. * We use the number of samples as the read/write counters so we * need to multiply with the stride to get the byte offsets. */ spa_ringbuffer_read_data(&data->ring, data->buffer, BUFFER_SIZE * stride, (index % BUFFER_SIZE) * stride, p, n_frames * stride); /* update the read pointer */ spa_ringbuffer_read_update(&data->ring, index + n_frames); } buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->stride = stride; buf->datas[0].chunk->size = n_frames * stride; pw_stream_queue_buffer(data->stream, b); /* signal the main thread to fill the ringbuffer */ pw_loop_signal_event(data->loop, data->refill_event); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .process = on_process, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->main_loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); data.main_loop = pw_main_loop_new(NULL); data.loop = pw_main_loop_get_loop(data.main_loop); pw_loop_add_signal(data.loop, SIGINT, do_quit, &data); pw_loop_add_signal(data.loop, SIGTERM, do_quit, &data); /* we're going to refill a ringbuffer from the main loop. Make an * event for this. */ spa_ringbuffer_init(&data.ring); data.refill_event = pw_loop_add_event(data.loop, do_refill, &data); /* prefill the ringbuffer */ do_refill(&data, 0); props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Music", NULL); if (argc > 1) /* Set stream target if given on command line */ pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); data.stream = pw_stream_new_simple( data.loop, "audio-src-ring", props, &stream_events, &data); /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat * id means that this is a format enumeration (of 1 value). */ params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .channels = DEFAULT_CHANNELS, .rate = DEFAULT_RATE )); /* Now connect this stream. We ask that our process function is * called in a realtime thread. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, 1); /* and wait while we let things run */ pw_main_loop_run(data.main_loop); pw_stream_destroy(data.stream); pw_loop_destroy_source(data.loop, data.refill_event); pw_main_loop_destroy(data.main_loop); pw_deinit(); return 0; }