diff --git a/CMakeLists.txt b/CMakeLists.txt index 660b3dfed..2693ac56a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ option(LWS_WITH_HTTP_STREAM_COMPRESSION "Support HTTP stream compression" OFF) option(LWS_WITH_HTTP_BROTLI "Also offer brotli http stream compression (requires LWS_WITH_HTTP_STREAM_COMPRESSION)" OFF) option(LWS_WITH_ACME "Enable support for ACME automatic cert acquisition + maintenance (letsencrypt etc)" OFF) option(LWS_WITH_HUBBUB "Enable libhubbub rewriting support" OFF) +option(LWS_WITH_ALSA "Enable alsa audio example" OFF) option(LWS_WITH_FTS "Full Text Search support" OFF) option(LWS_WITH_SYS_ASYNC_DNS "Nonblocking internal IPv4 + IPv6 DNS resolver" OFF) option(LWS_WITH_SYS_NTPCLIENT "Build in tiny ntpclient good for tls date validation and run via lws_system" OFF) diff --git a/minimal-examples/raw/minimal-raw-audio/CMakeLists.txt b/minimal-examples/raw/minimal-raw-audio/CMakeLists.txt new file mode 100644 index 000000000..d495893bd --- /dev/null +++ b/minimal-examples/raw/minimal-raw-audio/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-raw-audio) +set(SRCS audio.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + +set(requirements 1) +require_lws_config(LWS_WITH_ALSA 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared asound) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets asound) + endif() +endif() diff --git a/minimal-examples/raw/minimal-raw-audio/README.md b/minimal-examples/raw/minimal-raw-audio/README.md new file mode 100644 index 000000000..8d54b8f2c --- /dev/null +++ b/minimal-examples/raw/minimal-raw-audio/README.md @@ -0,0 +1,37 @@ +# lws minimal raw audio + +This demonstrates operating ALSA playback and capture using the lws event loop +via raw file descriptors. + +You need the lws cmake option `-DLWS_WITH_ALSA=1` + +This example opens the default ALSA playback and capture devices and pipes the +capture data into the playback with something over 1s delay via a ringbuffer. + +ALSA doesn't really lend itself to direct use with event loops... this example +uses the capture channel which does create POLLIN normally as the timesource +for the playback as well; they're both set to 16000Hz sample rate. + +## build + +``` + $ cmake . && make +``` + +## usage + +``` + $ ./lws-minimal-raw-audio +[2019/10/14 18:58:49:3288] U: LWS minimal raw audio +[2019/10/14 18:58:50:3438] N: LWS_CALLBACK_RAW_ADOPT_FILE +[2019/10/14 18:58:50:3455] N: LWS_CALLBACK_RAW_ADOPT_FILE +[2019/10/14 18:58:50:4764] N: LWS_CALLBACK_RAW_RX_FILE: 2062 samples +[2019/10/14 18:58:50:6132] N: LWS_CALLBACK_RAW_RX_FILE: 2205 samples +[2019/10/14 18:58:50:7592] N: LWS_CALLBACK_RAW_RX_FILE: 2328 samples +... +^C[2019/10/14 18:58:56:8460] N: LWS_CALLBACK_RAW_CLOSE_FILE +[2019/10/14 18:58:56:8461] N: LWS_CALLBACK_RAW_CLOSE_FILE +[2019/10/14 18:58:56:8461] N: LWS_CALLBACK_PROTOCOL_DESTROY +$ + +``` diff --git a/minimal-examples/raw/minimal-raw-audio/audio.c b/minimal-examples/raw/minimal-raw-audio/audio.c new file mode 100644 index 000000000..639f34ebb --- /dev/null +++ b/minimal-examples/raw/minimal-raw-audio/audio.c @@ -0,0 +1,211 @@ +/* + * lws-minimal-raw-audio + * + * Written in 2010-2019 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates adopting and managing audio device file descriptors in the + * event loop. + */ + +#include +#include +#include +#include +#include +#include + +#include + +static unsigned int sample_rate = 16000; + +struct raw_vhd { + uint8_t simplebuf[32768 * 2]; + snd_pcm_t *pcm_capture; + snd_pcm_t *pcm_playback; + snd_pcm_hw_params_t *params; + snd_pcm_uframes_t frames; + int filefd; + int rpos; + int wpos; + int times; +}; + +static int +set_hw_params(struct lws_vhost *vh, snd_pcm_t **pcm, int type) +{ + unsigned int rate = sample_rate; + snd_pcm_hw_params_t *params; + lws_sock_file_fd_type u; + struct pollfd pfd; + struct lws *wsi1; + int n; + + n = snd_pcm_open(pcm, "default", type, SND_PCM_NONBLOCK); + if (n < 0) { + lwsl_err("%s: Can't open default for playback: %s\n", + __func__, snd_strerror(n)); + + return -1; + } + + if (snd_pcm_poll_descriptors(*pcm, &pfd, 1) != 1) { + lwsl_err("%s: failed to get playback desc\n", __func__); + return -1; + } + + u.filefd = (lws_filefd_type)(long long)pfd.fd; + wsi1 = lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, u, + "lws-audio-test", NULL); + if (!wsi1) { + lwsl_err("%s: Failed to adopt playback desc\n", __func__); + goto bail; + } + if (type == SND_PCM_STREAM_PLAYBACK) + lws_rx_flow_control(wsi1, 0); /* no POLLIN */ + + snd_pcm_hw_params_malloc(¶ms); + snd_pcm_hw_params_any(*pcm, params); + + n = snd_pcm_hw_params_set_access(*pcm, params, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (n < 0) + goto bail1; + + n = snd_pcm_hw_params_set_format(*pcm, params, SND_PCM_FORMAT_S16_LE); + if (n < 0) + goto bail1; + + n = snd_pcm_hw_params_set_channels(*pcm, params, 1); + if (n < 0) + goto bail1; + + n = snd_pcm_hw_params_set_rate_near(*pcm, params, &rate, 0); + if (n < 0) + goto bail1; + + n = snd_pcm_hw_params(*pcm, params); + snd_pcm_hw_params_free(params); + if (n < 0) + goto bail; + + return 0; + +bail1: + snd_pcm_hw_params_free(params); +bail: + lwsl_err("%s: Set hw params failed: %s\n", __func__, snd_strerror(n)); + + return -1; +} + +static int +callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get( + lws_get_vhost(wsi), lws_get_protocol(wsi)); + int n; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), sizeof(struct raw_vhd)); + + if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_playback, + SND_PCM_STREAM_PLAYBACK)) { + lwsl_err("%s: Can't open default for playback\n", + __func__); + + return -1; + } + + if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_capture, + SND_PCM_STREAM_CAPTURE)) { + lwsl_err("%s: Can't open default for capture\n", + __func__); + + return -1; + } + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + lwsl_notice("LWS_CALLBACK_PROTOCOL_DESTROY\n"); + if (vhd && vhd->pcm_playback) { + snd_pcm_drain(vhd->pcm_playback); + snd_pcm_close(vhd->pcm_playback); + vhd->pcm_playback = NULL; + } + if (vhd && vhd->pcm_capture) { + snd_pcm_close(vhd->pcm_capture); + vhd->pcm_capture = NULL; + } + break; + + case LWS_CALLBACK_RAW_RX_FILE: + if (vhd->times >= 6) { /* delay amount decided by this */ + n = snd_pcm_writei(vhd->pcm_playback, + &vhd->simplebuf[vhd->rpos], + ((vhd->wpos - vhd->rpos) & + (sizeof(vhd->simplebuf) - 1)) / 2); + vhd->rpos = (vhd->rpos + (n * 2)) & + (sizeof(vhd->simplebuf) - 1); + } + + n = snd_pcm_readi(vhd->pcm_capture, &vhd->simplebuf[vhd->wpos], + (sizeof(vhd->simplebuf) - vhd->wpos) / 2); + lwsl_notice("LWS_CALLBACK_RAW_RX_FILE: %d samples\n", n); + vhd->times++; + + vhd->wpos = (vhd->wpos + (n * 2)) & (sizeof(vhd->simplebuf) - 1); + break; + + default: + break; + } + + return 0; +} + +static struct lws_protocols protocols[] = { + { "lws-audio-test", callback_raw_test, 0, 0 }, + { NULL, NULL, 0, 0 } /* terminator */ +}; + +static int interrupted; + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0; + + signal(SIGINT, sigint_handler); + memset(&info, 0, sizeof info); + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS minimal raw audio\n"); + + info.port = CONTEXT_PORT_NO_LISTEN_SERVER; + info.protocols = protocols; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + + return 0; +}