Files
freeswitch-modules/mod_audio_fork/mod_audio_fork.c
2023-12-26 10:57:15 -05:00

360 lines
13 KiB
C

/*
*
* mod_audio_fork.c -- Freeswitch module for forking audio to remote server over websockets
*
*/
#include "mod_audio_fork.h"
#include "lws_glue.h"
//static int mod_running = 0;
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_audio_fork_shutdown);
SWITCH_MODULE_RUNTIME_FUNCTION(mod_audio_fork_runtime);
SWITCH_MODULE_LOAD_FUNCTION(mod_audio_fork_load);
SWITCH_MODULE_DEFINITION(mod_audio_fork, mod_audio_fork_load, mod_audio_fork_shutdown, NULL /*mod_audio_fork_runtime*/);
static void responseHandler(switch_core_session_t* session, const char * eventName, char * json) {
switch_event_t *event;
switch_channel_t *channel = switch_core_session_get_channel(session);
if (json) switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "responseHandler: sending event payload: %s.\n", json);
switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, eventName);
switch_channel_event_set_data(channel, event);
if (json) switch_event_add_body(event, "%s", json);
switch_event_fire(&event);
}
static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
switch_core_session_t *session = switch_core_media_bug_get_session(bug);
switch (type) {
case SWITCH_ABC_TYPE_INIT:
break;
case SWITCH_ABC_TYPE_CLOSE:
{
private_t* tech_pvt = (private_t *) switch_core_media_bug_get_user_data(bug);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Got SWITCH_ABC_TYPE_CLOSE for bug %s\n", tech_pvt->bugname);
fork_session_cleanup(session, tech_pvt->bugname, NULL, 1);
}
break;
case SWITCH_ABC_TYPE_READ:
return fork_frame(session, bug);
break;
case SWITCH_ABC_TYPE_WRITE:
default:
break;
}
return SWITCH_TRUE;
}
static switch_status_t start_capture(switch_core_session_t *session,
switch_media_bug_flag_t flags,
char* host,
unsigned int port,
char* path,
int sampling,
int sslFlags,
char* bugname,
char* metadata)
{
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug;
switch_status_t status;
switch_codec_t* read_codec;
void *pUserData = NULL;
int channels = (flags & SMBF_STEREO) ? 2 : 1;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO,
"mod_audio_fork (%s): streaming %d sampling to %s path %s port %d tls: %s.\n",
bugname, sampling, host, path, port, sslFlags ? "yes" : "no");
if (switch_channel_get_private(channel, bugname)) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "mod_audio_fork: bug %s already attached!\n", bugname);
return SWITCH_STATUS_FALSE;
}
read_codec = switch_core_session_get_read_codec(session);
if (switch_channel_pre_answer(channel) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "mod_audio_fork: channel must have reached pre-answer status before calling start!\n");
return SWITCH_STATUS_FALSE;
}
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "calling fork_session_init.\n");
if (SWITCH_STATUS_FALSE == fork_session_init(session, responseHandler, read_codec->implementation->actual_samples_per_second,
host, port, path, sampling, sslFlags, channels, bugname, metadata, &pUserData)) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error initializing mod_audio_fork session.\n");
return SWITCH_STATUS_FALSE;
}
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "adding bug %s.\n", bugname);
if ((status = switch_core_media_bug_add(session, bugname, NULL, capture_callback, pUserData, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
return status;
}
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "setting bug private data %s.\n", bugname);
switch_channel_set_private(channel, bugname, bug);
if (fork_session_connect(&pUserData) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error mod_audio_fork session cannot connect.\n");
return SWITCH_STATUS_FALSE;
}
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "exiting start_capture.\n");
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t do_stop(switch_core_session_t *session, char* bugname, char* text)
{
switch_status_t status = SWITCH_STATUS_SUCCESS;
if (text) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "mod_audio_fork (%s): stop w/ final text %s\n", bugname, text);
}
else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "mod_audio_fork (%s): stop\n", bugname);
}
status = fork_session_cleanup(session, bugname, text, 0);
return status;
}
static switch_status_t do_pauseresume(switch_core_session_t *session, char* bugname, int pause)
{
switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "mod_audio_fork (%s): %s\n", bugname, pause ? "pause" : "resume");
status = fork_session_pauseresume(session, bugname, pause);
return status;
}
static switch_status_t do_graceful_shutdown(switch_core_session_t *session, char* bugname)
{
switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "mod_audio_fork (%s): do_graceful_shutdown \n", bugname);
status = fork_session_graceful_shutdown(session, bugname);
return status;
}
static switch_status_t send_text(switch_core_session_t *session, char* bugname, char* text) {
switch_status_t status = SWITCH_STATUS_FALSE;
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = switch_channel_get_private(channel, bugname);
if (bug) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "mod_audio_fork (%s): sending text: %s.\n", bugname, text);
status = fork_session_send_text(session, bugname, text);
}
else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "mod_audio_fork (%s): no bug, failed sending text: %s.\n", bugname, text);
}
return status;
}
#define FORK_API_SYNTAX "<uuid> [start | stop | send_text | pause | resume | graceful-shutdown ] [wss-url | path] [mono | mixed | stereo] [8000 | 16000 | 24000 | 32000 | 64000] [bugname] [metadata]"
SWITCH_STANDARD_API(fork_function)
{
char *mycmd = NULL, *argv[7] = { 0 };
int argc = 0;
switch_status_t status = SWITCH_STATUS_FALSE;
char *bugname = MY_BUG_NAME;
if (!zstr(cmd) && (mycmd = strdup(cmd))) {
argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
}
assert(cmd);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "mod_audio_fork cmd: %s\n", cmd);
if (zstr(cmd) || argc < 2 ||
(0 == strcmp(argv[1], "start") && argc < 4)) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error with command %s %s %s.\n", cmd, argv[0], argv[1]);
stream->write_function(stream, "-USAGE: %s\n", FORK_API_SYNTAX);
goto done;
} else {
switch_core_session_t *lsession = NULL;
if ((lsession = switch_core_session_locate(argv[0]))) {
if (!strcasecmp(argv[1], "stop")) {
char * text = NULL;
if (argc > 3) {
bugname = argv[2];
text = argv[3];
}
else if (argc > 2) {
if (argv[2][0] == '{' || argv[2][0] == '[') text = argv[2];
else bugname = argv[2];
}
status = do_stop(lsession, bugname, text);
}
else if (!strcasecmp(argv[1], "pause")) {
if (argc > 2) bugname = argv[2];
status = do_pauseresume(lsession, bugname, 1);
}
else if (!strcasecmp(argv[1], "resume")) {
if (argc > 2) bugname = argv[2];
status = do_pauseresume(lsession, bugname, 0);
}
else if (!strcasecmp(argv[1], "graceful-shutdown")) {
if (argc > 2) bugname = argv[2];
status = do_graceful_shutdown(lsession, bugname);
}
else if (!strcasecmp(argv[1], "send_text")) {
char * text = 0;
if (argc < 3) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "send_text requires an argument specifying text to send\n");
switch_core_session_rwunlock(lsession);
goto done;
}
if (argc > 3) {
bugname = argv[2];
text = argv[3];
}
else {
if (argv[2][0] == '{' || argv[2][0] == '[') text = argv[2];
else bugname = argv[2];
}
status = send_text(lsession, bugname, text);
}
else if (!strcasecmp(argv[1], "start")) {
switch_channel_t *channel = switch_core_session_get_channel(lsession);
char host[MAX_WS_URL_LEN], path[MAX_PATH_LEN];
unsigned int port;
int sslFlags;
int sampling = 8000;
switch_media_bug_flag_t flags = SMBF_READ_STREAM ;
char *metadata = NULL;
if( argc > 6) {
bugname = argv[5];
metadata = argv[6];
}
else if (argc > 5) {
if (argv[5][0] == '{' || argv[5][0] == '[') metadata = argv[5];
else bugname = argv[5];
}
if (0 == strcmp(argv[3], "mixed")) {
flags |= SMBF_WRITE_STREAM ;
}
else if (0 == strcmp(argv[3], "stereo")) {
flags |= SMBF_WRITE_STREAM ;
flags |= SMBF_STEREO;
}
else if(0 != strcmp(argv[3], "mono")) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "invalid mix type: %s, must be mono, mixed, or stereo\n", argv[3]);
switch_core_session_rwunlock(lsession);
goto done;
}
if (0 == strcmp(argv[4], "16k")) {
sampling = 16000;
}
else if (0 == strcmp(argv[4], "8k")) {
sampling = 8000;
}
else {
sampling = atoi(argv[4]);
}
if (!parse_ws_uri(channel, argv[2], &host[0], &path[0], &port, &sslFlags)) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "invalid websocket uri: %s\n", argv[2]);
}
else if (sampling % 8000 != 0) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "invalid sample rate: %s\n", argv[4]);
}
status = start_capture(lsession, flags, host, port, path, sampling, sslFlags, bugname, metadata);
}
else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "unsupported mod_audio_fork cmd: %s\n", argv[1]);
}
switch_core_session_rwunlock(lsession);
}
else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error locating session %s\n", argv[0]);
}
}
if (status == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "+OK Success\n");
} else {
stream->write_function(stream, "-ERR Operation Failed\n");
}
done:
switch_safe_free(mycmd);
return SWITCH_STATUS_SUCCESS;
}
SWITCH_MODULE_LOAD_FUNCTION(mod_audio_fork_load)
{
switch_api_interface_t *api_interface;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_audio_fork API loading..\n");
/* connect my internal structure to the blank pointer passed to me */
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
/* create/register custom event message types */
if (switch_event_reserve_subclass(EVENT_TRANSCRIPTION) != SWITCH_STATUS_SUCCESS ||
switch_event_reserve_subclass(EVENT_TRANSFER) != SWITCH_STATUS_SUCCESS ||
switch_event_reserve_subclass(EVENT_PLAY_AUDIO) != SWITCH_STATUS_SUCCESS ||
switch_event_reserve_subclass(EVENT_KILL_AUDIO) != SWITCH_STATUS_SUCCESS ||
switch_event_reserve_subclass(EVENT_ERROR) != SWITCH_STATUS_SUCCESS ||
switch_event_reserve_subclass(EVENT_DISCONNECT) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register an event subclass for mod_audio_fork API.\n");
return SWITCH_STATUS_TERM;
}
SWITCH_ADD_API(api_interface, "uuid_audio_fork", "audio_fork API", fork_function, FORK_API_SYNTAX);
switch_console_set_complete("add uuid_audio_fork start wss-url metadata");
switch_console_set_complete("add uuid_audio_fork start wss-url");
switch_console_set_complete("add uuid_audio_fork stop");
fork_init();
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_audio_fork API successfully loaded\n");
/* indicate that the module should continue to be loaded */
//mod_running = 1;
return SWITCH_STATUS_SUCCESS;
}
/*
Called when the system shuts down
Macro expands to: switch_status_t mod_audio_fork_shutdown() */
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_audio_fork_shutdown)
{
fork_cleanup();
//mod_running = 0;
switch_event_free_subclass(EVENT_TRANSCRIPTION);
switch_event_free_subclass(EVENT_TRANSFER);
switch_event_free_subclass(EVENT_PLAY_AUDIO);
switch_event_free_subclass(EVENT_KILL_AUDIO);
switch_event_free_subclass(EVENT_DISCONNECT);
switch_event_free_subclass(EVENT_ERROR);
return SWITCH_STATUS_SUCCESS;
}
/*
If it exists, this is called in it's own thread when the module-load completes
If it returns anything but SWITCH_STATUS_TERM it will be called again automatically
Macro expands to: switch_status_t mod_audio_fork_runtime()
*/
/*
SWITCH_MODULE_RUNTIME_FUNCTION(mod_audio_fork_runtime)
{
fork_service_threads(&mod_running);
return SWITCH_STATUS_TERM;
}
*/