mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
minimal-raw-audio
Shows how to bring alsa's libasound into the lws event loop
This commit is contained in:
parent
fe1d3def00
commit
7a84ca4c83
4 changed files with 326 additions and 0 deletions
|
@ -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)
|
||||
|
|
77
minimal-examples/raw/minimal-raw-audio/CMakeLists.txt
Normal file
77
minimal-examples/raw/minimal-raw-audio/CMakeLists.txt
Normal file
|
@ -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 <libwebsockets.h>\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()
|
37
minimal-examples/raw/minimal-raw-audio/README.md
Normal file
37
minimal-examples/raw/minimal-raw-audio/README.md
Normal file
|
@ -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
|
||||
$
|
||||
|
||||
```
|
211
minimal-examples/raw/minimal-raw-audio/audio.c
Normal file
211
minimal-examples/raw/minimal-raw-audio/audio.c
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* lws-minimal-raw-audio
|
||||
*
|
||||
* Written in 2010-2019 by Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* 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 <libwebsockets.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
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;
|
||||
}
|
Loading…
Add table
Reference in a new issue