Rupa Schomaker e9c8418a89 Fix race condition between shout thread starting and shout_file_read
Fix race condition between thread terminating and context being cleaned up
Fix stutter when first 64k is drained from shoutcast streaming buffer
Reduce the amount of padding from around 6s to 1s if the stream goes away
  or runs slow


git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@13218 d0543943-73ff-0310-b7d9-9358b9ac24b2
2009-05-01 16:22:36 +00:00

1594 lines
42 KiB
C

/*
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2005-2009, Anthony Minessale II <anthm@freeswitch.org>
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is
* Anthony Minessale II <anthm@freeswitch.org>
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Anthony Minessale II <anthm@freeswitch.org>
* Rupa Schomaker <rupa@rupa.com>
*
* mod_shout.c -- Icecast Module
*
* example filename: shout://user:pass@host.com/mount.mp3
*
*/
#include "libmpg123/mpg123.h"
#include <switch.h>
#include <shout/shout.h>
#include <lame.h>
#include <curl/curl.h>
#define OUTSCALE 8192 * 2
#define MP3_SCACHE 16384 * 2
#define MP3_DCACHE 8192 * 2
#define MP3_TOOSMALL -1234
SWITCH_MODULE_LOAD_FUNCTION(mod_shout_load);
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_shout_shutdown);
SWITCH_MODULE_DEFINITION(mod_shout, mod_shout_load, mod_shout_shutdown, NULL);
static char *supported_formats[SWITCH_MAX_CODECS] = { 0 };
static struct {
char decoder[256];
float vol;
uint32_t outscale;
} globals;
mpg123_handle *our_mpg123_new(const char* decoder, int *error)
{
mpg123_handle *mh;
const char *arch = "auto";
int x64 = 0;
if (*globals.decoder || globals.outscale || globals.vol) {
if (*globals.decoder) {
arch = globals.decoder;
}
if ((mh = mpg123_new(arch, NULL))) {
if (globals.outscale) {
mpg123_param(mh, MPG123_OUTSCALE, globals.outscale, 0);
}
if (globals.vol) {
mpg123_volume(mh, globals.vol);
}
}
} else {
#ifdef WIN32
x64++;
#else
if (sizeof(void *) == 4) {
arch = "i586";
} else {
x64++;
}
#endif
if ((mh = mpg123_new(arch, NULL))) {
if (x64) {
mpg123_param(mh, MPG123_OUTSCALE, 8192, 0);
}
}
}
return mh;
}
struct shout_context {
shout_t *shout;
lame_global_flags *gfp;
char *stream_url;
switch_mutex_t *audio_mutex;
switch_buffer_t *audio_buffer;
switch_memory_pool_t *memory_pool;
unsigned char decode_buf[MP3_DCACHE];
switch_file_handle_t *handle;
mpg123_handle *mh;
int err;
int mp3err;
int dlen;
switch_file_t *fd;
FILE *fp;
int samplerate;
uint8_t thread_running;
uint8_t shout_init;
uint32_t prebuf;
int lame_ready;
int eof;
int channels;
int16_t *l;
switch_size_t llen;
int16_t *r;
switch_size_t rlen;
unsigned char *mp3buf;
switch_size_t mp3buflen;
switch_thread_rwlock_t *rwlock;
};
typedef struct shout_context shout_context_t;
static size_t decode_fd(shout_context_t *context, void *data, size_t bytes);
static inline void free_context(shout_context_t *context)
{
int ret;
if (context) {
context->err++;
switch_thread_rwlock_wrlock(context->rwlock);
if (context->fd) {
switch_file_close(context->fd);
context->fd = NULL;
}
if (context->fp) {
unsigned char mp3buffer[8192];
int len;
int16_t blank[2048] = {0}, *r = NULL;
if (context->channels == 2) {
r = blank;
}
len = lame_encode_buffer(context->gfp, blank, r, sizeof(blank) / 2, mp3buffer, sizeof(mp3buffer));
if (len) {
ret = fwrite(mp3buffer, 1, len, context->fp);
}
while ((len = lame_encode_flush(context->gfp, mp3buffer, sizeof(mp3buffer))) > 0) {
ret = fwrite(mp3buffer, 1, len, context->fp);
if (ret < 0) {
break;
}
}
lame_mp3_tags_fid(context->gfp, context->fp);
fclose(context->fp);
context->fp = NULL;
}
if (context->audio_buffer) {
switch_mutex_lock(context->audio_mutex);
switch_buffer_destroy(&context->audio_buffer);
switch_mutex_unlock(context->audio_mutex);
}
if (context->shout) {
shout_close(context->shout);
context->shout = NULL;
}
if (context->gfp) {
lame_close(context->gfp);
context->gfp = NULL;
}
if (context->stream_url) {
int sanity = 0;
while (context->thread_running) {
switch_yield(500000);
if (++sanity > 10) {
break;
}
}
}
if (context->mh) {
mpg123_delete(context->mh);
}
switch_thread_rwlock_unlock(context->rwlock);
switch_thread_rwlock_destroy(context->rwlock);
}
}
static void log_error(char const *fmt, va_list ap)
{
char *data = NULL;
if (fmt) {
#ifdef HAVE_VASPRINTF
int ret;
ret = vasprintf(&data, fmt, ap);
if ((ret == -1) || !data) {
return;
}
#else
data = (char *) malloc(2048);
if (data) {
vsnprintf(data, 2048, fmt, ap);
} else {
return;
}
#endif
}
if (data) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, (char *) "%s", data);
free(data);
}
}
static void log_debug(char const *fmt, va_list ap)
{
char *data = NULL;
if (fmt) {
#ifdef HAVE_VASPRINTF
int ret;
ret = vasprintf(&data, fmt, ap);
if ((ret == -1) || !data) {
return;
}
#else
data = (char *) malloc(2048);
if (data) {
vsnprintf(data, 2048, fmt, ap);
} else {
return;
}
#endif
}
if (data) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, (char *) "%s", data);
free(data);
}
}
static void log_msg(char const *fmt, va_list ap)
{
char *data = NULL;
if (fmt) {
#ifdef HAVE_VASPRINTF
int ret;
ret = vasprintf(&data, fmt, ap);
if ((ret == -1) || !data) {
return;
}
#else
data = (char *) malloc(2048);
if (data) {
vsnprintf(data, 2048, fmt, ap);
} else {
return;
}
#endif
}
if (data) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, (char *) "%s", data);
free(data);
}
}
static size_t decode_fd(shout_context_t *context, void *data, size_t bytes)
{
int decode_status = 0;
size_t dlen = 0;
int x = 0;
unsigned char *in;
int inlen = 0;
unsigned char *out;
int outlen;
int usedlen;
unsigned char inbuf[MP3_SCACHE];
int done = 0;
size_t used;
size_t lp;
size_t rb = 0;
while (context->eof < 2 && switch_buffer_inuse(context->audio_buffer) < bytes) {
lp = sizeof(inbuf);
if (!context->eof && ((switch_file_read(context->fd, inbuf, &lp) != SWITCH_STATUS_SUCCESS) || lp == 0)) {
context->eof++;
}
inlen = (int) lp;
in = inbuf;
out = context->decode_buf;
outlen = (int) sizeof(context->decode_buf);
usedlen = 0;
x = 0;
if (lp < bytes) {
done = 1;
}
do {
if (context->eof) {
decode_status = mpg123_read(context->mh, out, outlen, &dlen);
} else {
decode_status = mpg123_decode(context->mh, in, inlen, out, outlen, &dlen);
}
if (context->err) {
goto error;
}
if (!x) {
in = NULL;
inlen = 0;
x++;
}
if (decode_status == MPG123_NEW_FORMAT) {
continue;
} else if (decode_status == MPG123_OK) {
usedlen = dlen;
break;
} else if (decode_status == MPG123_DONE || (context->eof && decode_status == MPG123_NEED_MORE)) {
context->eof++;
goto end;
} else if (decode_status == MPG123_ERR || decode_status > 0) {
if (++context->mp3err >= 5) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Decoder Error!\n");
context->eof++;
goto end;
}
continue;
}
context->mp3err = 0;
usedlen += dlen;
out += dlen;
outlen -= dlen;
dlen = 0;
} while (decode_status != MPG123_NEED_MORE);
if (context->audio_buffer) {
switch_buffer_write(context->audio_buffer, context->decode_buf, usedlen);
} else {
goto error;
}
if (done) {
break;
}
}
end:
used = switch_buffer_inuse(context->audio_buffer);
if (context->eof || done || used >= bytes) {
if (!(rb = switch_buffer_read(context->audio_buffer, data, bytes))) {
goto error;
}
return rb;
}
return 0;
error:
switch_mutex_lock(context->audio_mutex);
context->err++;
switch_mutex_unlock(context->audio_mutex);
return 0;
}
#define error_check() if (context->err) goto error;
static size_t stream_callback(void *ptr, size_t size, size_t nmemb, void *data)
{
register unsigned int realsize = (unsigned int) (size * nmemb);
shout_context_t *context = data;
int decode_status = 0;
size_t dlen = 0;
int x = 0;
unsigned char *in;
int inlen;
unsigned char *out;
int outlen;
int usedlen;
uint32_t used, buf_size = 1024 * 128; /* do not make this 64 or less, stutter will ensue after
first 64k buffer is dry */
in = ptr;
inlen = realsize;
out = context->decode_buf;
outlen = sizeof(context->decode_buf);
usedlen = 0;
error_check();
if (context->prebuf) {
buf_size = context->prebuf;
}
/* make sure we aren't over zealous by slowing down the stream when the buffer is too full */
for (;;) {
error_check();
switch_mutex_lock(context->audio_mutex);
if (!context->audio_buffer) {
context->err++;
break;
}
used = switch_buffer_inuse(context->audio_buffer);
switch_mutex_unlock(context->audio_mutex);
if (used < buf_size) {
/* switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Buffered %u/%u!\n", used, buf_size); */
break;
}
switch_yield(500000);
}
error_check();
do {
decode_status = mpg123_decode(context->mh, in, inlen, out, outlen, &dlen);
error_check();
if (!x) {
in = NULL;
inlen = 0;
x++;
}
if (decode_status == MPG123_NEW_FORMAT) {
continue;
} else if (decode_status == MPG123_OK) {
usedlen = dlen;
break;
} else if (decode_status == MPG123_ERR) {
if (++context->mp3err >= 20) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Decoder Error!\n");
goto error;
}
mpg123_delete(context->mh);
context->mh = our_mpg123_new(NULL, NULL);
mpg123_open_feed(context->mh);
mpg123_param(context->mh, MPG123_FORCE_RATE, context->samplerate, 0);
mpg123_param(context->mh, MPG123_FLAGS, MPG123_MONO_MIX, 0);
mpg123_param(context->mh, MPG123_FLAGS, MPG123_SEEKBUFFER|MPG123_MONO_MIX, 0);
return realsize;
}
context->mp3err = 0;
usedlen += dlen;
out += dlen;
outlen -= dlen;
dlen = 0;
} while (decode_status != MPG123_NEED_MORE);
switch_mutex_lock(context->audio_mutex);
if (context->audio_buffer) {
switch_buffer_write(context->audio_buffer, context->decode_buf, usedlen);
} else {
goto error;
}
switch_mutex_unlock(context->audio_mutex);
return realsize;
error:
switch_mutex_lock(context->audio_mutex);
context->err++;
switch_mutex_unlock(context->audio_mutex);
return 0;
}
#define MY_BUF_LEN 1024 * 32
#define MY_BLOCK_SIZE MY_BUF_LEN
static void *SWITCH_THREAD_FUNC read_stream_thread(switch_thread_t *thread, void *obj)
{
CURL *curl_handle = NULL;
shout_context_t *context = (shout_context_t *) obj;
switch_thread_rwlock_rdlock(context->rwlock);
curl_handle = curl_easy_init();
curl_easy_setopt(curl_handle, CURLOPT_URL, context->stream_url);
curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10);
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, stream_callback);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) context);
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "FreeSWITCH(mod_shout)/1.0");
curl_easy_perform(curl_handle);
curl_easy_cleanup(curl_handle);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Read Thread Done\n");
switch_mutex_lock(context->audio_mutex);
context->err++;
switch_mutex_unlock(context->audio_mutex);
context->thread_running = 0;
switch_thread_rwlock_unlock(context->rwlock);
return NULL;
}
static void launch_read_stream_thread(shout_context_t *context)
{
switch_thread_t *thread;
switch_threadattr_t *thd_attr = NULL;
if (context->err) {
return;
}
context->thread_running = 1;
switch_threadattr_create(&thd_attr, context->memory_pool);
switch_threadattr_detach_set(thd_attr, 1);
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
switch_thread_create(&thread, thd_attr, read_stream_thread, context, context->memory_pool);
}
static void *SWITCH_THREAD_FUNC write_stream_thread(switch_thread_t *thread, void *obj)
{
shout_context_t *context = (shout_context_t *) obj;
switch_thread_rwlock_rdlock(context->rwlock);
if (context->thread_running) {
context->thread_running++;
} else {
switch_thread_rwlock_unlock(context->rwlock);
return NULL;
}
if (!context->lame_ready) {
lame_init_params(context->gfp);
lame_print_config(context->gfp);
context->lame_ready = 1;
}
while (!context->err && context->thread_running) {
unsigned char mp3buf[8192] = "";
int16_t audio[9600] = { 0 };
switch_size_t audio_read = 0;
int rlen = 0;
long ret = 0;
switch_mutex_lock(context->audio_mutex);
if (context->audio_buffer) {
audio_read = switch_buffer_read(context->audio_buffer, audio, sizeof(audio));
} else {
context->err++;
}
switch_mutex_unlock(context->audio_mutex);
error_check();
if (!audio_read) {
audio_read = sizeof(audio);
memset(audio, 255, sizeof(audio));
}
if (context->channels == 2) {
int16_t l[4800] = { 0 };
int16_t r[4800] = { 0 };
int j = 0;
switch_size_t i;
for (i = 0; i < audio_read / 4; i++) {
l[i] = audio[j++];
r[i] = audio[j++];
}
if ((rlen = lame_encode_buffer(context->gfp, l, r, audio_read / 4, mp3buf, sizeof(mp3buf))) < 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MP3 encode error %d!\n", rlen);
goto error;
}
} else if (context->channels == 1) {
if ((rlen = lame_encode_buffer(context->gfp, (void *) audio, NULL, audio_read / sizeof(int16_t), mp3buf, sizeof(mp3buf))) < 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MP3 encode error %d!\n", rlen);
goto error;
}
}
if (rlen) {
ret = shout_send(context->shout, mp3buf, rlen);
if (ret != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Send error: %s\n", shout_get_error(context->shout));
goto error;
}
} else {
memset(mp3buf, 0, 128);
ret = shout_send(context->shout, mp3buf, 128);
}
shout_sync(context->shout);
switch_yield(100000);
}
error:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Write Thread Done\n");
switch_thread_rwlock_unlock(context->rwlock);
context->thread_running = 0;
return NULL;
}
static void launch_write_stream_thread(shout_context_t *context)
{
switch_thread_t *thread;
switch_threadattr_t *thd_attr = NULL;
int sanity = 10;
if (context->err) {
return;
}
context->thread_running = 1;
switch_threadattr_create(&thd_attr, context->memory_pool);
switch_threadattr_detach_set(thd_attr, 1);
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
switch_thread_create(&thread, thd_attr, write_stream_thread, context, context->memory_pool);
while (context->thread_running && context->thread_running != 2) {
switch_yield(100000);
if (!--sanity) break;
}
}
#define TC_BUFFER_SIZE 1024 * 32
static switch_status_t shout_file_open(switch_file_handle_t *handle, const char *path)
{
shout_context_t *context;
char *host, *file;
char *username, *password, *port;
char *err = NULL;
int portno = 0;
int sanity = 0;
if ((context = switch_core_alloc(handle->memory_pool, sizeof(*context))) == 0) {
return SWITCH_STATUS_MEMERR;
}
if (!handle->samplerate) {
handle->samplerate = 8000;
}
context->memory_pool = handle->memory_pool;
context->samplerate = handle->samplerate;
context->handle = handle;
switch_thread_rwlock_create(&(context->rwlock), context->memory_pool);
switch_thread_rwlock_rdlock(context->rwlock);
if (switch_test_flag(handle, SWITCH_FILE_FLAG_READ)) {
if (switch_buffer_create_dynamic(&context->audio_buffer, TC_BUFFER_SIZE, TC_BUFFER_SIZE * 2, 0) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error!\n");
goto error;
}
switch_mutex_init(&context->audio_mutex, SWITCH_MUTEX_NESTED, context->memory_pool);
context->mh = our_mpg123_new(NULL, NULL);
mpg123_open_feed(context->mh);
mpg123_format_all(context->mh);
mpg123_param(context->mh, MPG123_FORCE_RATE, context->samplerate, 0);
if (handle->handler) {
mpg123_param(context->mh, MPG123_FLAGS, MPG123_SEEKBUFFER|MPG123_MONO_MIX, 0);
context->stream_url = switch_core_sprintf(context->memory_pool, "http://%s", path);
context->prebuf = handle->prebuf;
launch_read_stream_thread(context);
while((switch_buffer_inuse(context->audio_buffer) < 1*2*context->samplerate) && ++sanity < 10) {
/* at least 1s of audio and up to 5s initialize */
switch_yield(500000);
}
} else {
mpg123_param(context->mh, MPG123_FLAGS, MPG123_MONO_MIX, 0);
if (switch_file_open(&context->fd, path, SWITCH_FOPEN_READ, SWITCH_FPROT_UREAD | SWITCH_FPROT_UWRITE, handle->memory_pool) !=
SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening %s\n", path);
goto error;
}
}
} else if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
if (!(context->gfp = lame_init())) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not allocate lame\n");
goto error;
}
if (!handle->handler) {
id3tag_init(context->gfp);
id3tag_v2_only(context->gfp);
id3tag_pad_v2(context->gfp);
}
context->channels = handle->channels;
lame_set_brate(context->gfp, 16 * (handle->samplerate / 8000) * handle->channels);
lame_set_num_channels(context->gfp, handle->channels);
lame_set_in_samplerate(context->gfp, handle->samplerate);
lame_set_out_samplerate(context->gfp, handle->samplerate);
if (handle->channels == 2) {
lame_set_mode(context->gfp, STEREO);
} else {
lame_set_mode(context->gfp, MONO);
}
lame_set_quality(context->gfp, 2); /* 2=high 5 = medium 7=low */
lame_set_errorf(context->gfp, log_error);
lame_set_debugf(context->gfp, log_debug);
lame_set_msgf(context->gfp, log_msg);
if (handle->handler) {
if (switch_buffer_create_dynamic(&context->audio_buffer, MY_BLOCK_SIZE, MY_BUF_LEN, 0) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error!\n");
goto error;
}
switch_mutex_init(&context->audio_mutex, SWITCH_MUTEX_NESTED, context->memory_pool);
lame_set_bWriteVbrTag(context->gfp, 0);
lame_mp3_tags_fid(context->gfp, NULL);
username = switch_core_strdup(handle->memory_pool, path);
if (!(password = strchr(username, ':'))) {
err = "invalid url";
goto error;
}
*password++ = '\0';
if (!(host = strchr(password, '@'))) {
err = "invalid url";
goto error;
}
*host++ = '\0';
if ((file = strchr(host, '/'))) {
*file++ = '\0';
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid URL: %s\n", path);
goto error;
}
if ((port = strchr(host, ':'))) {
*port++ = '\0';
if (port) {
portno = atoi(port);
}
}
if (!portno) {
portno = 8000;
}
if (!(context->shout = shout_new())) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not allocate shout_t\n");
goto error;
}
if (shout_set_host(context->shout, host) != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting hostname: %s\n", shout_get_error(context->shout));
goto error;
}
if (shout_set_protocol(context->shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting protocol: %s\n", shout_get_error(context->shout));
goto error;
}
if (shout_set_port(context->shout, portno) != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting port: %s\n", shout_get_error(context->shout));
goto error;
}
if (shout_set_password(context->shout, password) != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting password: %s\n", shout_get_error(context->shout));
goto error;
}
if (shout_set_mount(context->shout, file) != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting mount: %s\n", shout_get_error(context->shout));
goto error;
}
if (shout_set_user(context->shout, username) != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting user: %s\n", shout_get_error(context->shout));
goto error;
}
if (shout_set_url(context->shout, "http://www.freeswitch.org") != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting name: %s\n", shout_get_error(context->shout));
goto error;
}
if (shout_set_description(context->shout, "FreeSWITCH mod_shout Broadcasting Module") != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting name: %s\n", shout_get_error(context->shout));
goto error;
}
if (shout_set_audio_info(context->shout, "bitrate", "24000") != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting user: %s\n", shout_get_error(context->shout));
goto error;
}
if (shout_set_format(context->shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting user: %s\n", shout_get_error(context->shout));
goto error;
}
} else {
handle->seekable = 1;
/* lame being lame and all has FILE * coded into it's API for some functions so we gotta use it */
if (!(context->fp = fopen(path, "wb+"))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening %s\n", path);
goto error;
}
}
}
handle->samples = 0;
handle->format = 0;
handle->sections = 0;
handle->speed = 0;
handle->private_info = context;
switch_thread_rwlock_unlock(context->rwlock);
return SWITCH_STATUS_SUCCESS;
error:
switch_thread_rwlock_unlock(context->rwlock);
if (err) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error: %s\n", err);
}
free_context(context);
return SWITCH_STATUS_GENERR;
}
static switch_status_t shout_file_close(switch_file_handle_t *handle)
{
shout_context_t *context = handle->private_info;
free_context(context);
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t shout_file_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence)
{
shout_context_t *context = handle->private_info;
if (handle->handler) {
return SWITCH_STATUS_FALSE;
} else {
switch_mutex_lock(context->audio_mutex);
if (context->audio_buffer) {
if (context->fd) {
switch_file_seek(context->fd, whence, &samples);
} else if (context->fp) {
*cur_sample = fseek(context->fp, *cur_sample, whence);
}
mpg123_delete(context->mh);
context->mh = our_mpg123_new(NULL, NULL);
mpg123_open_feed(context->mh);
mpg123_param(context->mh, MPG123_FORCE_RATE, context->samplerate, 0);
mpg123_param(context->mh, MPG123_FLAGS, MPG123_MONO_MIX, 0);
switch_buffer_zero(context->audio_buffer);
} else {
context->err++;
}
switch_mutex_unlock(context->audio_mutex);
return SWITCH_STATUS_SUCCESS;
}
}
static switch_status_t shout_file_read(switch_file_handle_t *handle, void *data, size_t *len)
{
shout_context_t *context = handle->private_info;
size_t rb = 0, bytes = *len * sizeof(int16_t), newbytes = 0;
*len = 0;
if (!context || context->err) {
return SWITCH_STATUS_FALSE;
}
if (context->fd) {
rb = decode_fd(context, data, bytes);
} else {
switch_mutex_lock(context->audio_mutex);
if (context->audio_buffer) {
rb = switch_buffer_read(context->audio_buffer, data, bytes);
} else {
switch_mutex_lock(context->audio_mutex);
context->err++;
switch_mutex_unlock(context->audio_mutex);
}
switch_mutex_unlock(context->audio_mutex);
}
if (context->err) {
return SWITCH_STATUS_FALSE;
}
/* switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "rb: %d, bytes: %d\n", (int) rb, (int) bytes); */
if (rb) {
*len = rb / sizeof(int16_t);
} else {
/* no data, so insert 1 second of silence */
newbytes = 2 * handle->samplerate;
if(newbytes < bytes) {
bytes = newbytes;
}
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Padding mp3 stream with 1s of empty audio. (%s)\n", context->stream_url);
memset(data, 255, bytes);
*len = bytes / sizeof(int16_t);
}
handle->sample_count += *len;
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t shout_file_write(switch_file_handle_t *handle, void *data, size_t *len)
{
shout_context_t *context;
int rlen = 0;
int16_t *audio = data;
size_t nsamples = *len;
if (!handle) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error no handle\n");
return SWITCH_STATUS_FALSE;
}
if (!(context = handle->private_info)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error no context\n");
return SWITCH_STATUS_FALSE;
}
if (context->err) {
return SWITCH_STATUS_FALSE;
}
if (context->shout && !context->shout_init) {
if (!context->gfp) {
return SWITCH_STATUS_FALSE;
}
context->shout_init++;
if (shout_open(context->shout) != SHOUTERR_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening stream: %s\n", shout_get_error(context->shout));
context->err++;
return SWITCH_STATUS_FALSE;
}
launch_write_stream_thread(context);
}
if (handle->handler && context->audio_mutex) {
switch_mutex_lock(context->audio_mutex);
if (context->audio_buffer) {
if (!switch_buffer_write(context->audio_buffer, data, (nsamples * sizeof(int16_t) * handle->channels))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Buffer error\n");
context->err++;
}
} else {
context->err++;
}
switch_mutex_unlock(context->audio_mutex);
if (context->err) {
return SWITCH_STATUS_FALSE;
}
handle->sample_count += *len;
return SWITCH_STATUS_SUCCESS;
}
if (!context->lame_ready) {
lame_init_params(context->gfp);
lame_print_config(context->gfp);
context->lame_ready = 1;
}
if (context->mp3buflen < nsamples * 4) {
context->mp3buflen = nsamples * 4;
context->mp3buf = switch_core_alloc(context->memory_pool, context->mp3buflen);
}
if (handle->channels == 2) {
switch_size_t i, j = 0;
if (context->llen < nsamples) {
context->l = switch_core_alloc(context->memory_pool, nsamples * 2);
context->r = switch_core_alloc(context->memory_pool, nsamples * 2);
context->llen = context->rlen = nsamples;
}
for (i = 0; i < nsamples; i++) {
context->l[i] = audio[j++];
context->r[i] = audio[j++];
}
if ((rlen = lame_encode_buffer(context->gfp, context->l, context->r, nsamples, context->mp3buf, context->mp3buflen)) < 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MP3 encode error %d!\n", rlen);
return SWITCH_STATUS_FALSE;
}
} else if (handle->channels == 1) {
if ((rlen = lame_encode_buffer(context->gfp, audio, NULL, nsamples, context->mp3buf, context->mp3buflen)) < 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MP3 encode error %d!\n", rlen);
return SWITCH_STATUS_FALSE;
}
} else {
rlen = 0;
}
if (rlen) {
int ret = fwrite(context->mp3buf, 1, rlen, context->fp);
if (ret < 0) {
return SWITCH_STATUS_FALSE;
}
}
handle->sample_count += *len;
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t shout_file_set_string(switch_file_handle_t *handle, switch_audio_col_t col, const char *string)
{
shout_context_t *context = handle->private_info;
switch_status_t status = SWITCH_STATUS_FALSE;
if (!context->shout) {
switch (col) {
case SWITCH_AUDIO_COL_STR_TITLE:
id3tag_set_title(context->gfp, string);
break;
case SWITCH_AUDIO_COL_STR_COMMENT:
id3tag_set_comment(context->gfp, string);
break;
case SWITCH_AUDIO_COL_STR_ARTIST:
id3tag_set_artist(context->gfp, string);
break;
case SWITCH_AUDIO_COL_STR_DATE:
id3tag_set_year(context->gfp, string);
break;
case SWITCH_AUDIO_COL_STR_SOFTWARE:
break;
id3tag_set_album(context->gfp, string);
case SWITCH_AUDIO_COL_STR_COPYRIGHT:
id3tag_set_genre(context->gfp, string);
break;
default:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Value Ignored %d, %s\n", col, string);
break;
}
return status;
}
switch (col) {
case SWITCH_AUDIO_COL_STR_TITLE:
if (shout_set_name(context->shout, string) == SHOUTERR_SUCCESS) {
status = SWITCH_STATUS_SUCCESS;
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting name: %s\n", shout_get_error(context->shout));
}
break;
case SWITCH_AUDIO_COL_STR_COMMENT:
if (shout_set_url(context->shout, string) == SHOUTERR_SUCCESS) {
status = SWITCH_STATUS_SUCCESS;
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting name: %s\n", shout_get_error(context->shout));
}
break;
case SWITCH_AUDIO_COL_STR_ARTIST:
if (shout_set_description(context->shout, string) == SHOUTERR_SUCCESS) {
status = SWITCH_STATUS_SUCCESS;
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error setting name: %s\n", shout_get_error(context->shout));
}
break;
default:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Value Ignored %d, %s\n", col, string);
break;
}
return status;
}
static switch_status_t shout_file_get_string(switch_file_handle_t *handle, switch_audio_col_t col, const char **string)
{
return SWITCH_STATUS_FALSE;
}
static switch_bool_t telecast_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
switch_buffer_t *buffer = (switch_buffer_t *) user_data;
uint8_t data[SWITCH_RECOMMENDED_BUFFER_SIZE];
switch_frame_t frame = { 0 };
frame.data = data;
frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE;
switch (type) {
case SWITCH_ABC_TYPE_INIT:
break;
case SWITCH_ABC_TYPE_CLOSE:
break;
case SWITCH_ABC_TYPE_READ_PING:
if (buffer) {
if (switch_core_media_bug_read(bug, &frame) == SWITCH_STATUS_SUCCESS) {
switch_buffer_lock(buffer);
switch_buffer_write(buffer, frame.data, frame.datalen);
switch_buffer_unlock(buffer);
}
} else {
return SWITCH_FALSE;
}
break;
case SWITCH_ABC_TYPE_READ:
case SWITCH_ABC_TYPE_WRITE:
default:
break;
}
return SWITCH_TRUE;
}
struct holder {
switch_stream_handle_t *stream;
switch_memory_pool_t *pool;
char *host;
char *port;
char *uri;
};
static int web_callback(void *pArg, int argc, char **argv, char **columnNames)
{
struct holder *holder = (struct holder *) pArg;
char title_b4[128] = "";
char title_aft[128 * 3] = "";
char *mp3, *m3u;
/*
0 uuid VARCHAR(255),
1 created VARCHAR(255),
2 name VARCHAR(255),
3 state VARCHAR(255),
4 cid_name VARCHAR(255),
5 cid_num VARCHAR(255),
6 ip_addr VARCHAR(255),
7 dest VARCHAR(255),
8 application VARCHAR(255),
9 application_data VARCHAR(255),
10 read_codec VARCHAR(255),
11 read_rate VARCHAR(255),
12 write_codec VARCHAR(255),
13 write_rate VARCHAR(255)
*/
holder->stream->write_function(holder->stream,
"<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>",
argv[1], argv[4], argv[5], argv[7], argv[8] ? argv[8] : "N/A", argv[9] ? argv[9] : "N/A", argv[10], argv[11]);
snprintf(title_b4, sizeof(title_b4), "%s <%s>", argv[4], argv[5]);
switch_url_encode(title_b4, title_aft, sizeof(title_aft));
mp3 = switch_mprintf("http://%s:%s%s/mp3/%s/%s.mp3", holder->host, holder->port, holder->uri, argv[0], argv[5]);
m3u = switch_mprintf("http://%s:%s%s/m3u/mp3/%s/%s.mp3.m3u", holder->host, holder->port, holder->uri, argv[0], argv[5]);
holder->stream->write_function(holder->stream, "[<a href=%s>mp3</a>] ", mp3);
holder->stream->write_function(holder->stream, "[<a href=%s>m3u</a>]</td></tr>\n", m3u);
switch_safe_free(mp3);
switch_safe_free(m3u);
return 0;
}
void do_telecast(switch_stream_handle_t *stream)
{
char *path_info = switch_event_get_header(stream->param_event, "http-path-info");
char *uuid = strdup(path_info + 4);
switch_core_session_t *tsession;
char *fname = "stream.mp3";
if ((fname = strchr(uuid, '/'))) {
*fname++ = '\0';
}
if (!(tsession = switch_core_session_locate(uuid))) {
char *ref = switch_event_get_header(stream->param_event, "http-referer");
stream->write_function(stream, "Content-type: text/html\r\n\r\n<h2>Not Found!</h2>\n" "<META http-equiv=\"refresh\" content=\"1;URL=%s\">", ref);
} else {
switch_media_bug_t *bug = NULL;
switch_buffer_t *buffer = NULL;
switch_mutex_t *mutex;
switch_channel_t *channel = switch_core_session_get_channel(tsession);
lame_global_flags *gfp = NULL;
switch_codec_implementation_t read_impl = {0};
switch_core_session_get_read_impl(tsession, &read_impl);
if (!(gfp = lame_init())) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not allocate lame\n");
goto end;
}
lame_set_num_channels(gfp, read_impl.number_of_channels);
lame_set_in_samplerate(gfp, read_impl.actual_samples_per_second);
lame_set_brate(gfp, 16 * (read_impl.actual_samples_per_second / 8000) * read_impl.number_of_channels);
lame_set_mode(gfp, 3);
lame_set_quality(gfp, 2);
lame_set_errorf(gfp, log_error);
lame_set_debugf(gfp, log_debug);
lame_set_msgf(gfp, log_msg);
lame_set_bWriteVbrTag(gfp, 0);
lame_mp3_tags_fid(gfp, NULL);
lame_init_params(gfp);
lame_print_config(gfp);
switch_mutex_init(&mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(tsession));
switch_buffer_create_dynamic(&buffer, 1024, 2048, 0);
switch_buffer_add_mutex(buffer, mutex);
if (switch_core_media_bug_add(tsession, telecast_callback, buffer, 0,
SMBF_READ_STREAM | SMBF_WRITE_STREAM | SMBF_READ_PING, &bug) != SWITCH_STATUS_SUCCESS) {
goto end;
}
stream->write_function(stream, "Content-type: audio/mpeg\r\n" "Content-Disposition: inline; filename=\"%s\"\r\n\r\n", fname);
while (switch_channel_ready(channel)) {
unsigned char mp3buf[TC_BUFFER_SIZE] = "";
int rlen;
uint8_t buf[1024];
switch_size_t bytes = 0;
if (switch_buffer_inuse(buffer) >= 1024) {
switch_buffer_lock(buffer);
bytes = switch_buffer_read(buffer, buf, sizeof(buf));
switch_buffer_unlock(buffer);
} else {
if (!bytes) {
switch_cond_next();
continue;
}
memset(buf, 0, bytes);
}
if ((rlen = lame_encode_buffer(gfp, (void *) buf, NULL, bytes / 2, mp3buf, sizeof(mp3buf))) < 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MP3 encode error %d!\n", rlen);
goto end;
}
if (rlen) {
if (stream->raw_write_function(stream, mp3buf, rlen)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Disconnected\n");
goto end;
}
}
}
end:
switch_safe_free(uuid);
if (gfp) {
lame_close(gfp);
gfp = NULL;
}
if (bug) {
switch_core_media_bug_remove(tsession, &bug);
}
if (buffer) {
switch_buffer_destroy(&buffer);
}
switch_core_session_rwunlock(tsession);
}
}
void do_broadcast(switch_stream_handle_t *stream)
{
char *path_info = switch_event_get_header(stream->param_event, "http-path-info");
char *file;
lame_global_flags *gfp = NULL;
switch_file_handle_t fh = { 0 };
unsigned char mp3buf[TC_BUFFER_SIZE] = "";
uint8_t buf[1024];
int rlen;
int is_local = 0;
uint32_t interval = 20000;
if (strstr(path_info + 7, "://")) {
file = strdup(path_info + 7);
is_local++;
} else {
file = switch_mprintf("%s/streamfiles/%s", SWITCH_GLOBAL_dirs.base_dir, path_info + 7);
}
assert(file);
if (switch_core_file_open(&fh, file, 0, 0, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL) != SWITCH_STATUS_SUCCESS) {
memset(&fh, 0, sizeof(fh));
stream->write_function(stream, "Content-type: text/html\r\n\r\n<h2>File not found</h2>\n");
goto end;
}
if (switch_test_flag((&fh), SWITCH_FILE_NATIVE)) {
stream->write_function(stream, "Content-type: text/html\r\n\r\n<h2>File format not supported</h2>\n");
goto end;
}
if (!(gfp = lame_init())) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not allocate lame\n");
goto end;
}
lame_set_num_channels(gfp, fh.channels);
lame_set_in_samplerate(gfp, fh.samplerate);
lame_set_brate(gfp, 16 * (fh.samplerate / 8000) * fh.channels);
lame_set_mode(gfp, 3);
lame_set_quality(gfp, 2);
lame_set_errorf(gfp, log_error);
lame_set_debugf(gfp, log_debug);
lame_set_msgf(gfp, log_msg);
lame_set_bWriteVbrTag(gfp, 0);
lame_mp3_tags_fid(gfp, NULL);
lame_init_params(gfp);
lame_print_config(gfp);
stream->write_function(stream, "Content-type: audio/mpeg\r\n" "Content-Disposition: inline; filename=\"%s.mp3\"\r\n\r\n", path_info + 7);
if (fh.interval) {
interval = fh.interval * 1000;
}
for (;;) {
switch_size_t samples = sizeof(buf) / 2;
switch_core_file_read(&fh, buf, &samples);
if (is_local) {
switch_yield(interval);
}
if (!samples) {
break;
}
if ((rlen = lame_encode_buffer(gfp, (void *) buf, NULL, samples, mp3buf, sizeof(mp3buf))) < 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MP3 encode error %d!\n", rlen);
goto end;
}
if (rlen) {
if (stream->raw_write_function(stream, mp3buf, rlen)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Disconnected\n");
goto end;
}
}
}
while ((rlen = lame_encode_flush(gfp, mp3buf, sizeof(mp3buf))) > 0) {
if (stream->raw_write_function(stream, mp3buf, rlen)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Disconnected\n");
goto end;
}
}
end:
if (fh.channels) {
switch_core_file_close(&fh);
}
switch_safe_free(file);
if (gfp) {
lame_close(gfp);
gfp = NULL;
}
}
void do_index(switch_stream_handle_t *stream)
{
switch_core_db_t *db = switch_core_db_handle();
const char *sql = "select * from channels";
struct holder holder;
char *errmsg;
holder.host = switch_event_get_header(stream->param_event, "http-host");
holder.port = switch_event_get_header(stream->param_event, "http-port");
holder.uri = switch_event_get_header(stream->param_event, "http-uri");
holder.stream = stream;
stream->write_function(stream, "Content-type: text/html\r\n\r\n");
stream->write_function(stream,
"<table align=center border=1 cellpadding=6 cellspacing=0>"
"<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",
"Created", "CID Name", "CID Num", "Ext", "App", "Data", "Codec", "Rate", "Listen");
switch_core_db_exec(db, sql, web_callback, &holder, &errmsg);
stream->write_function(stream, "</table>");
if (errmsg) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error [%s]\n", errmsg);
switch_safe_free(errmsg);
}
}
#define TELECAST_SYNTAX ""
SWITCH_STANDARD_API(telecast_api_function)
{
char *host = NULL, *port = NULL, *uri = NULL, *path_info = NULL;
if (session) {
return SWITCH_STATUS_FALSE;
}
if (stream->param_event) {
host = switch_event_get_header(stream->param_event, "http-host");
port = switch_event_get_header(stream->param_event, "http-port");
uri = switch_event_get_header(stream->param_event, "http-uri");
path_info = switch_event_get_header(stream->param_event, "http-path-info");
}
if (!path_info) {
return SWITCH_STATUS_FALSE;
} else {
if (!strncmp(path_info, "index", 5)) {
do_index(stream);
return SWITCH_STATUS_SUCCESS;
}
if (!strncmp(path_info, "m3u/", 4)) {
char *p;
if ((p = strstr(path_info, ".m3u"))) {
*p = '\0';
}
stream->write_function(stream, "Content-type: audio/x-mpegurl\r\n\r\nhttp://%s:%s%s/%s\n", host, port, uri, path_info + 4);
return SWITCH_STATUS_SUCCESS;
}
if (!strncmp(path_info, "mp3/", 4)) {
do_telecast(stream);
return SWITCH_STATUS_SUCCESS;
}
if (!strncmp(path_info, "stream/", 7)) {
do_broadcast(stream);
return SWITCH_STATUS_SUCCESS;
}
}
stream->write_function(stream, "Content-type: text/html\r\n\r\n<h2>Invalid URL</h2>\n");
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t load_config(void)
{
char *cf = "shout.conf";
switch_xml_t cfg, xml, settings, param;
memset(&globals, 0, sizeof(globals));
if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", cf);
return SWITCH_STATUS_TERM;
}
if ((settings = switch_xml_child(cfg, "settings"))) {
for (param = switch_xml_child(settings, "param"); param; param = param->next) {
char *var = (char *) switch_xml_attr_soft(param, "name");
char *val = (char *) switch_xml_attr_soft(param, "value");
if (!strcmp(var, "decoder")) {
switch_set_string(globals.decoder, val);
} else if (!strcmp(var, "volume")) {
globals.vol = (float)atof(val);
} else if (!strcmp(var, "outscale")) {
int tmp = atoi(val);
if (tmp > 0) {
globals.outscale = tmp;
}
}
}
}
switch_xml_free(xml);
return SWITCH_STATUS_SUCCESS;
}
SWITCH_MODULE_LOAD_FUNCTION(mod_shout_load)
{
switch_api_interface_t *shout_api_interface;
switch_file_interface_t *file_interface;
supported_formats[0] = "shout";
supported_formats[1] = "mp3";
curl_global_init(CURL_GLOBAL_ALL);
/* connect my internal structure to the blank pointer passed to me */
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
file_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE);
file_interface->interface_name = modname;
file_interface->extens = supported_formats;
file_interface->file_open = shout_file_open;
file_interface->file_close = shout_file_close;
file_interface->file_read = shout_file_read;
file_interface->file_write = shout_file_write;
file_interface->file_seek = shout_file_seek;
file_interface->file_set_string = shout_file_set_string;
file_interface->file_get_string = shout_file_get_string;
shout_init();
mpg123_init();
load_config();
SWITCH_ADD_API(shout_api_interface, "telecast", "telecast", telecast_api_function, TELECAST_SYNTAX);
/* indicate that the module should continue to be loaded */
return SWITCH_STATUS_SUCCESS;
}
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_shout_shutdown)
{
curl_global_cleanup();
mpg123_exit();
return SWITCH_STATUS_SUCCESS;
}
/* For Emacs:
* Local Variables:
* mode:c
* indent-tabs-mode:t
* tab-width:4
* c-basic-offset:4
* End:
* For VIM:
* vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab:
*/