Files
freeswitch-modules/mod_dub/mod_dub.c
Dave Horton b606255206 add mod_dub (#16)
* add mod_dub

Signed-off-by: Dave Horton <daveh@beachdognet.com>

* remove some locks

---------

Signed-off-by: Dave Horton <daveh@beachdognet.com>
2024-03-12 09:56:49 -04:00

384 lines
14 KiB
C

/*
*
* mod_dub.c
*
*/
#include "mod_dub.h"
#include <stdlib.h>
#include <switch.h>
#include <switch_curl.h>
#include "dub_glue.h"
/* Prototypes */
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_dub_shutdown);
SWITCH_MODULE_LOAD_FUNCTION(mod_dub_load);
SWITCH_MODULE_DEFINITION(mod_dub, mod_dub_load, mod_dub_shutdown, NULL);
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_bool_t ret = SWITCH_TRUE;
switch (type) {
case SWITCH_ABC_TYPE_INIT:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Got SWITCH_ABC_TYPE_INIT.\n");
break;
case SWITCH_ABC_TYPE_CLOSE:
{
dub_session_cleanup(session, 1, bug);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Finished SWITCH_ABC_TYPE_CLOSE.\n");
}
break;
case SWITCH_ABC_TYPE_WRITE_REPLACE:
ret = dub_speech_frame(bug, user_data);
break;
default:
break;
}
return ret;
}
static switch_status_t dub_set_gain(switch_core_session_t *session, int gain) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = NULL;
struct cap_cb *cb = NULL;
if (switch_channel_pre_answer(channel) != SWITCH_STATUS_SUCCESS) {
return SWITCH_STATUS_FALSE;
}
if (!(bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME))) {
cb =(struct cap_cb *) switch_core_session_alloc(session, sizeof(struct cap_cb));
memset(cb, 0, sizeof(struct cap_cb));
switch_mutex_init(&cb->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
}
else {
cb = (struct cap_cb *) switch_core_media_bug_get_user_data(bug);
}
cb->gain = gain;
/* check again under lock */
switch_mutex_lock(cb->mutex);
if (!(bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "dub_set_gain: adding bug so we can set gain on main channel\n");
if (switch_core_media_bug_add(session, MY_BUG_NAME, NULL, capture_callback, (void *) cb, 0, SMBF_WRITE_REPLACE, &bug) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "dub_set_gain: error adding bug!\n");
switch_mutex_unlock(cb->mutex);
return SWITCH_STATUS_FALSE;
}
switch_channel_set_private(channel, MY_BUG_NAME, bug);
}
else {
cb = (struct cap_cb *) switch_core_media_bug_get_user_data(bug);
cb->gain = gain;
}
switch_mutex_unlock(cb->mutex);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "dub_set_gain: setting gain to %d\n", gain);
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t dub_add_track(switch_core_session_t *session, char* trackName) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = NULL;
struct cap_cb *cb = NULL;
int offset = 0;
int samples_per_second;
switch_codec_implementation_t write_impl = { 0 };
if (switch_channel_pre_answer(channel) != SWITCH_STATUS_SUCCESS) {
return SWITCH_STATUS_FALSE;
}
if (!(bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME))) {
/* allocate per-session memory and use track 0 */
cb =(struct cap_cb *) switch_core_session_alloc(session, sizeof(struct cap_cb));
memset(cb, 0, sizeof(struct cap_cb));
switch_mutex_init(&cb->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
offset = 0;
}
else {
/* retrieve the bug and search for an empty track */
cb = (struct cap_cb *) switch_core_media_bug_get_user_data(bug);
while (offset < MAX_DUB_TRACKS && cb->tracks[offset].state != DUB_TRACK_STATE_INACTIVE) {
offset++;
}
if (offset == MAX_DUB_TRACKS) {
/* all tracks are in use */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "dub_add_track: no available tracks\n");
return SWITCH_STATUS_FALSE;
}
}
switch_core_session_get_write_impl(session, &write_impl);
samples_per_second = !strcasecmp(write_impl.iananame, "g722") ? write_impl.actual_samples_per_second : write_impl.samples_per_second;
init_dub_track(&cb->tracks[offset], trackName, samples_per_second);
if (!bug) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "dub_add_track: adding bug for track %s\n", trackName);
if (switch_core_media_bug_add(session, MY_BUG_NAME, NULL, capture_callback, (void *) cb, 0, SMBF_WRITE_REPLACE, &bug) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "dub_add_track: error adding bug!\n");
return SWITCH_STATUS_FALSE;
}
switch_channel_set_private(channel, MY_BUG_NAME, bug);
}
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "dub_add_track: added track %s at offset %d\n", trackName, offset);
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t dub_remove_track(switch_core_session_t *session, char* trackName) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
switch_status_t status = SWITCH_STATUS_FALSE;
dub_track_t *track = NULL;
if (bug) {
struct cap_cb *cb =(struct cap_cb *) switch_core_media_bug_get_user_data(bug);
switch_mutex_lock(cb->mutex);
for (int i = 0; i < MAX_DUB_TRACKS && track == NULL; i++) {
if (cb->tracks[i].state != DUB_TRACK_STATE_INACTIVE && strcmp(cb->tracks[i].trackName, trackName) == 0) {
track = &cb->tracks[i];
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "dub_remove_track: removing track %s at offset %d\n", trackName, i);
break;
}
}
if (track) {
int count = 0;
remove_dub_track(track);
/* check if this is the last bug */
for (int i = 0; i < MAX_DUB_TRACKS; i++) {
if (cb->tracks[i].state != DUB_TRACK_STATE_INACTIVE) count++;
}
if (count == 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "dub_remove_track: removing bug after removing last track\n");
dub_session_cleanup(session, 0, bug);
}
status = SWITCH_STATUS_SUCCESS;
}
else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "dub_remove_track: track %s not found\n", trackName);
}
switch_mutex_unlock(cb->mutex);
}
else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "dub_remove_track: bug not found\n");
}
return status;
}
static switch_status_t dub_silence_track(switch_core_session_t *session, char* trackName) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
switch_status_t status = SWITCH_STATUS_FALSE;
dub_track_t *track = NULL;
if (bug) {
struct cap_cb *cb =(struct cap_cb *) switch_core_media_bug_get_user_data(bug);
switch_mutex_lock(cb->mutex);
for (int i = 0; i < MAX_DUB_TRACKS; i++) {
if (cb->tracks[i].state != DUB_TRACK_STATE_INACTIVE && strcmp(cb->tracks[i].trackName, trackName) == 0) {
track = &cb->tracks[i];
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "dub_silence_track: silencing track %s at offset %d\n", trackName, i);
break;
}
}
if (track) {
silence_dub_track(track);
status = SWITCH_STATUS_SUCCESS;
}
switch_mutex_unlock(cb->mutex);
}
else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "dub_silence_track: bug not found\n");
}
return status;
}
static switch_status_t dub_play_on_track(switch_core_session_t *session, char* trackName, char* url, int loop, int gain) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
switch_status_t status = SWITCH_STATUS_FALSE;
dub_track_t *track = NULL;
if (bug) {
struct cap_cb *cb =(struct cap_cb *) switch_core_media_bug_get_user_data(bug);
switch_mutex_lock(cb->mutex);
for (int i = 0; i < MAX_DUB_TRACKS; i++) {
if (cb->tracks[i].state != DUB_TRACK_STATE_INACTIVE && strcmp(cb->tracks[i].trackName, trackName) == 0) {
track = &cb->tracks[i];
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,
"dub_play_on_track: playing %s on track %s at offset %d with gain %d\n", url, trackName, i, gain);
break;
}
}
if (track) {
status = play_dub_track(track, cb->mutex, url, loop, gain);
}
switch_mutex_unlock(cb->mutex);
}
return status;
}
static switch_status_t dub_say_on_track(switch_core_session_t *session, char* trackName, char* text, int gain) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
switch_status_t status = SWITCH_STATUS_FALSE;
dub_track_t *track = NULL;
if (bug) {
struct cap_cb *cb =(struct cap_cb *) switch_core_media_bug_get_user_data(bug);
switch_mutex_lock(cb->mutex);
for (int i = 0; i < MAX_DUB_TRACKS; i++) {
if (cb->tracks[i].state != DUB_TRACK_STATE_INACTIVE && strcmp(cb->tracks[i].trackName, trackName) == 0) {
track = &cb->tracks[i];
break;
}
}
if (track) {
status = say_dub_track(track, cb->mutex, text, gain);
}
switch_mutex_unlock(cb->mutex);
}
return status;
}
#define DUB_API_SYNTAX "<uuid> [addTrack|removeTrack|silenceTrack|playOnTrack|sayOnTrack|setGain] track [url|text|gain] [gain] [loop]"
SWITCH_STANDARD_API(dub_function)
{
char *mycmd = NULL, *argv[6] = { 0 };
int argc = 0;
int error_written = 0;
switch_status_t status = SWITCH_STATUS_FALSE;
if (!zstr(cmd) && (mycmd = strdup(cmd))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "dub_function: %s\n", mycmd);
argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
}
if (zstr(cmd) || argc < 3 || zstr(argv[0]) || zstr(argv[1]) || zstr(argv[2])) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error with command %s.\n", cmd);
}
else {
switch_core_session_t *session = NULL;
if ((session = switch_core_session_locate(argv[0]))) {
char* action = argv[1];
char* track = argv[2];
if (0 == strcmp(action, "setGain")) {
int gain = atoi(track);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "setGain %d\n", gain);
status = dub_set_gain(session, gain);
}
else if (0 == strcmp(action, "addTrack")) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "addTrack %s\n", track);
status = dub_add_track(session, track);
}
else if (0 == strcmp(action, "removeTrack")) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "removeTrack %s\n", track);
status = dub_remove_track(session, track);
}
else if (0 == strcmp(action, "silenceTrack")) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "silenceTrack %s\n", track);
status = dub_silence_track(session, track);
}
else if (0 == strcmp(action, "playOnTrack")) {
if (argc < 4) {
stream->write_function(stream, "-USAGE: %s\n", DUB_API_SYNTAX);
error_written = 1;
}
else {
char* url = argv[3];
int loop = argc > 4 && 0 == strcmp(argv[4], "loop");
int gain = argc > 5 ? atoi(argv[5]) : 0;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO,
"playOnTrack %s, %s gain %d\n", url, loop ? "loop" : "once", gain);
status = dub_play_on_track(session, track, url, loop, gain);
}
}
else if (0 == strcmp(action, "sayOnTrack")) {
if (argc < 4) {
stream->write_function(stream, "-USAGE: %s\n", DUB_API_SYNTAX);
error_written = 1;
}
else {
char* text = argv[3];
int gain = argc > 4 ? atoi(argv[4]) : 0;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "sayOnTrack %s gain %d\n", text, gain);
status = dub_say_on_track(session, track, text, gain);
}
}
else {
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", DUB_API_SYNTAX);
error_written = 1;
}
switch_core_session_rwunlock(session);
}
else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error locating session for %s.\n", cmd);
stream->write_function(stream, "-ERR Invalid session\n");
error_written = 1;
}
}
if (status == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "+OK Success\n");
} else if (!error_written){
stream->write_function(stream, "-ERR Operation Failed\n");
}
switch_safe_free(mycmd);
return SWITCH_STATUS_SUCCESS;
}
SWITCH_MODULE_LOAD_FUNCTION(mod_dub_load)
{
switch_api_interface_t *api_interface;
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
dub_init();
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_dub loaded\n");
SWITCH_ADD_API(api_interface, "uuid_dub", "dub mp3 track over channel audio", dub_function, DUB_API_SYNTAX);
switch_console_set_complete("add uuid_dub addTrack <trackname>");
switch_console_set_complete("add uuid_dub removeTrack <trackname>");
switch_console_set_complete("add uuid_dub silenceTrack <trackname>");
switch_console_set_complete("add uuid_dub playOnTrack <trackname> <url|file> [loop|once] [gain]");
switch_console_set_complete("add uuid_dub setGain <gain>");
switch_console_set_complete("add uuid_dub stop ");
/* indicate that the module should continue to be loaded */
return SWITCH_STATUS_SUCCESS;
}
/*
Called when the system shuts down
Macro expands to: switch_status_t mod_dub_shutdown() */
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_dub_shutdown)
{
dub_cleanup();
return SWITCH_STATUS_SUCCESS;
}