From ecab16ed88e702a909700157948c19183aab119f Mon Sep 17 00:00:00 2001 From: Seven Du Date: Fri, 10 Apr 2015 20:40:24 +0800 Subject: [PATCH] FS-7516: add mod_imagick use the magick-core API We have pdfs and gifs auto play gif and png yeah, you can play video files with imgk video only requires ffmpeg binary need autoplay=(1000/FPS) --- build/modules.conf.in | 1 + configure.ac | 5 + src/mod/formats/mod_imagick/Makefile.am | 18 + src/mod/formats/mod_imagick/Makefile.hint | 9 + src/mod/formats/mod_imagick/mod_imagick.c | 421 ++++++++++++++++++++++ 5 files changed, 454 insertions(+) create mode 100644 src/mod/formats/mod_imagick/Makefile.am create mode 100644 src/mod/formats/mod_imagick/Makefile.hint create mode 100644 src/mod/formats/mod_imagick/mod_imagick.c diff --git a/build/modules.conf.in b/build/modules.conf.in index 041bfb6dc7..6c57666255 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -111,6 +111,7 @@ event_handlers/mod_event_socket #event_handlers/mod_odbc_cdr #event_handlers/mod_rayo #event_handlers/mod_snmp +#formats/mod_imagick formats/mod_local_stream formats/mod_native_file #formats/mod_portaudio_stream diff --git a/configure.ac b/configure.ac index 296ef27c81..daf206b28d 100644 --- a/configure.ac +++ b/configure.ac @@ -1256,6 +1256,10 @@ PKG_CHECK_MODULES([OPENCV], [opencv >= 2.4.9.1],[ AM_CONDITIONAL([HAVE_OPENCV],[true])],[ AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_OPENCV],[false])]) +PKG_CHECK_MODULES([MAGICK], [ImageMagick >= 6.0.0],[ + AM_CONDITIONAL([HAVE_MAGICK],[true])],[ + AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_MAGICK],[false])]) + PKG_CHECK_MODULES([MEMCACHED], [libmemcached >= 0.31],[ AM_CONDITIONAL([HAVE_MEMCACHED],[true]) MEMCACHED_LIBS="${MEMCACHED_LIBS} -lpthread" @@ -1643,6 +1647,7 @@ AC_CONFIG_FILES([Makefile src/mod/event_handlers/mod_snmp/Makefile src/mod/event_handlers/mod_event_zmq/Makefile src/mod/formats/mod_avformat/Makefile + src/mod/formats/mod_imagick/Makefile src/mod/formats/mod_local_stream/Makefile src/mod/formats/mod_native_file/Makefile src/mod/formats/mod_shell_stream/Makefile diff --git a/src/mod/formats/mod_imagick/Makefile.am b/src/mod/formats/mod_imagick/Makefile.am new file mode 100644 index 0000000000..0181c6d3cd --- /dev/null +++ b/src/mod/formats/mod_imagick/Makefile.am @@ -0,0 +1,18 @@ +include $(top_srcdir)/build/modmake.rulesam +MODNAME=mod_imagick + +if HAVE_MAGICK + +mod_LTLIBRARIES = mod_imagick.la +mod_imagick_la_SOURCES = mod_imagick.c +mod_imagick_la_CFLAGS = $(AM_CFLAGS) $(MAGICK_CFLAGS) +mod_imagick_la_LIBADD = $(switch_builddir)/libfreeswitch.la $(MAGICK_LIBS) +mod_imagick_la_LDFLAGS = -avoid-version -module -no-undefined -shared + +else +install: error +all: error +error: + $(error You must install libmagickcore-dev to build mod_imagick) +endif + diff --git a/src/mod/formats/mod_imagick/Makefile.hint b/src/mod/formats/mod_imagick/Makefile.hint new file mode 100644 index 0000000000..889550d998 --- /dev/null +++ b/src/mod/formats/mod_imagick/Makefile.hint @@ -0,0 +1,9 @@ +#Mac +#LOCAL_CFLAGS=-I/usr/local/Cellar/imagemagick/6.8.9-8/include/ImageMagick-6 +#LOCAL_LDFLAGS=-L/usr/local/Cellar/imagemagick/6.8.9-8/lib -lMagickCore-6.Q16 + +LOCAL_CFLAGS=-I/usr/include/ImageMagick +LOCAL_LDFLAGS=-L/usr/lib/x86_64-linux-gnu -lMagickCore + +BASE=../../../.. +include $(BASE)/build/modmake.rules diff --git a/src/mod/formats/mod_imagick/mod_imagick.c b/src/mod/formats/mod_imagick/mod_imagick.c new file mode 100644 index 0000000000..346e5633d2 --- /dev/null +++ b/src/mod/formats/mod_imagick/mod_imagick.c @@ -0,0 +1,421 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2015, Anthony Minessale II + * + * 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 + * Seven Du + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Seven Du + * + * mod_imagick -- play pdf/gif as video + * + * use the magick-core API since the magick-wand API is more different on different versions + * http://www.imagemagick.org/script/magick-core.php + * + */ + + +#include +#include + + +#if defined(__clang__) +/* the imagemagick header files are very badly broken on clang. They really should be fixing this, in the mean time, this dirty hack works */ +# define __attribute__(x) /*nothing*/ +#define restrict restrict +#endif + +#ifndef MAGICKCORE_QUANTUM_DEPTH +#define MAGICKCORE_QUANTUM_DEPTH 8 +#endif + +#ifndef MAGICKCORE_HDRI_ENABLE +#define MAGICKCORE_HDRI_ENABLE 0 +#endif + +#include + + +#ifdef _MSC_VER +// Disable MSVC warnings that suggest making code non-portable. +#pragma warning(disable : 4996) +#endif + +SWITCH_MODULE_LOAD_FUNCTION(mod_imagick_load); +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_imagick_shutdown); +SWITCH_MODULE_DEFINITION(mod_imagick, mod_imagick_load, mod_imagick_shutdown, NULL); + +struct pdf_file_context { + switch_memory_pool_t *pool; + switch_image_t *img; + int reads; + int sent; + int max; + int samples; + int same_page; + int pagenumber; + int pagecount; + ImageInfo *image_info; + Image *images; + ExceptionInfo *exception; + int autoplay; + switch_time_t next_play_time; +}; + +typedef struct pdf_file_context pdf_file_context_t; + +static switch_status_t imagick_file_open(switch_file_handle_t *handle, const char *path) +{ + pdf_file_context_t *context; + char *ext; + unsigned int flags = 0; + + if ((ext = strrchr((char *)path, '.')) == 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid Format\n"); + return SWITCH_STATUS_GENERR; + } + + ext++; + /* + Prevents playing files to a conference like a slide show using conference_play api. + if (!switch_test_flag(handle, SWITCH_FILE_FLAG_VIDEO)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Video only\n"); + return SWITCH_STATUS_GENERR; + } + */ + if ((context = (pdf_file_context_t *)switch_core_alloc(handle->memory_pool, sizeof(pdf_file_context_t))) == 0) { + return SWITCH_STATUS_MEMERR; + } + + memset(context, 0, sizeof(pdf_file_context_t)); + + if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) { + return SWITCH_STATUS_GENERR; + } + + if (switch_test_flag(handle, SWITCH_FILE_FLAG_READ)) { + flags |= SWITCH_FOPEN_READ; + } + + if (ext && !strcmp(ext, "gif")) { + context->autoplay = 1; + } + + context->max = 10000; + + context->exception = AcquireExceptionInfo(); + context->image_info = AcquireImageInfo(); + switch_set_string(context->image_info->filename, path); + + context->images = ReadImages(context->image_info, context->exception); + if (context->exception->severity != UndefinedException) { + CatchException(context->exception); + } + + if (context->images == (Image *)NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Fail to read file: %s\n", path); + return SWITCH_STATUS_GENERR; + } + + context->pagecount = GetImageListLength(context->images); + + if (handle->params) { + const char *max = switch_event_get_header(handle->params, "img_ms"); + const char *autoplay = switch_event_get_header(handle->params, "autoplay"); + int tmp; + + if (max) { + tmp = atol(max); + context->max = tmp; + } + + if (autoplay) { + context->autoplay = atoi(autoplay); + } + } + + if (context->max) { + context->samples = (handle->samplerate / 1000) * context->max; + } + + handle->format = 0; + handle->sections = 0; + handle->seekable = 1; + handle->speed = 0; + handle->pos = 0; + handle->private_info = context; + context->pool = handle->memory_pool; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Opening File [%s], pagecount: %d\n", path, context->pagecount); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t imagick_file_close(switch_file_handle_t *handle) +{ + pdf_file_context_t *context = (pdf_file_context_t *)handle->private_info; + + switch_img_free(&context->img); + + if (context->images) DestroyImageList(context->images); + if (context->exception) DestroyExceptionInfo(context->exception); + if (context->image_info) DestroyImageInfo(context->image_info); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t imagick_file_read(switch_file_handle_t *handle, void *data, size_t *len) +{ + pdf_file_context_t *context = (pdf_file_context_t *)handle->private_info; + + if (!context->images || !context->samples) { + return SWITCH_STATUS_FALSE; + } + + if (context->samples > 0) { + if (*len >= context->samples) { + *len = context->samples; + } + + context->samples -= *len; + } + + if (!context->samples) { + return SWITCH_STATUS_FALSE; + } + + memset(data, 0, *len * 2 * handle->channels); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t read_page(pdf_file_context_t *context) +{ + switch_status_t status = SWITCH_STATUS_SUCCESS; + Image *image = GetImageFromList(context->images, context->pagenumber); + int W, H, w, h, x, y; + MagickBooleanType ret; + uint8_t *storage; + + if (!image) return SWITCH_STATUS_FALSE; + + W = image->page.width; + H = image->page.height; + w = image->columns; + h = image->rows; + x = image->page.x; + y = image->page.y; + + switch_assert(W > 0 && H > 0); + +#if 0 + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "page: %dx%d image: %dx%d pos: (%d,%d) pagenumber: %d," + " delay: %" SWITCH_SIZE_T_FMT " ticks_per_second: %" SWITCH_SSIZE_T_FMT + " autoplay: %d\n", + W, H, w, h, x, y, + context->pagenumber, image->delay, image->ticks_per_second, context->autoplay); +#endif + + if (context->autoplay) { + if (image->delay && image->ticks_per_second) { + context->next_play_time = switch_micro_time_now() / 1000 + image->delay * (1000 / image->ticks_per_second); + } else { + context->next_play_time = switch_micro_time_now() / 1000 + context->autoplay; + } + } + + if (context->img && (context->img->d_w != W || context->img->d_h != H)) { + switch_img_free(&context->img); + } + + if (!context->img) { + context->img = switch_img_alloc(NULL, SWITCH_IMG_FMT_I420, W, H, 0); + switch_assert(context->img); + } + + if (W == w && H == h) { + storage = malloc(w * h * 3); switch_assert(storage); + + ret = ExportImagePixels(image, 0, 0, w, h, "RGB", CharPixel, storage, context->exception); + + if (ret == MagickFalse && context->exception->severity != UndefinedException) { + CatchException(context->exception); + free(storage); + return SWITCH_STATUS_FALSE; + } + + RAWToI420(storage, w * 3, + context->img->planes[0], context->img->stride[0], + context->img->planes[1], context->img->stride[1], + context->img->planes[2], context->img->stride[2], + context->img->d_w, context->img->d_h); + + free(storage); + } else { + switch_image_t *img = switch_img_alloc(NULL, SWITCH_IMG_FMT_ARGB, image->columns, image->rows, 0); + switch_assert(img); + + ret = ExportImagePixels(image, 0, 0, w, h, "ARGB", CharPixel, img->planes[SWITCH_PLANE_PACKED], context->exception); + + if (ret == MagickFalse && context->exception->severity != UndefinedException) { + CatchException(context->exception); + return SWITCH_STATUS_FALSE; + } + + switch_img_patch(context->img, img, x, y); + switch_img_free(&img); + } + + return status; +} + +static switch_status_t imagick_file_read_video(switch_file_handle_t *handle, switch_frame_t *frame, switch_video_read_flag_t flags) +{ + pdf_file_context_t *context = (pdf_file_context_t *)handle->private_info; + switch_image_t *dup = NULL; + switch_status_t status; + + if (!context->images || !context->samples) { + return SWITCH_STATUS_FALSE; + } + + if (context->autoplay && context->next_play_time && (switch_micro_time_now() / 1000 > context->next_play_time)) { + context->pagenumber++; + if (context->pagenumber >= context->pagecount) context->pagenumber = 0; + context->same_page = 0; + } + + if (!context->same_page) { + status = read_page(context); + if (status != SWITCH_STATUS_SUCCESS) return status; + context->same_page = 1; + } + + if (!context->img) return SWITCH_STATUS_FALSE; + + if ((context->reads++ % 20) == 0) { + switch_img_copy(context->img, &dup); + frame->img = dup; + context->sent++; + } else { + if ((flags && SVR_BLOCK)) { + switch_yield(5000); + } + return SWITCH_STATUS_BREAK; + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t imagick_file_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence) +{ + pdf_file_context_t *context = (pdf_file_context_t *)handle->private_info; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + int page = samples / (handle->samplerate / 1000); + + if (whence == SEEK_SET) { + // page = page; + } else if (whence == SEEK_CUR) { + page += context->pagenumber; + } else if (whence == SEEK_END) { + page = context->pagecount - page; + } + + if (page < 0) page = 0; + if (page > context->pagecount - 1) page = context->pagecount - 1; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "seeking to sample=%" SWITCH_UINT64_T_FMT " cur_sample=%d where:=%d page=%d\n", samples, *cur_sample, whence, page); + + if (page != context->pagenumber) { + context->pagenumber = page; + context->same_page = 0; + *cur_sample = page; + } + + return status; +} + +static void myErrorHandler(const ExceptionType t, const char *reason, const char *description) +{ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: %s\n", reason, description); +} + +static void myFatalErrorHandler(const ExceptionType t, const char *reason, const char *description) +{ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "%s: %s\n", reason, description); +} + +static void myWarningHandler(const ExceptionType t, const char *reason, const char *description) +{ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "%s: %s\n", reason, description); +} + +static char *supported_formats[SWITCH_MAX_CODECS] = { 0 }; + +SWITCH_MODULE_LOAD_FUNCTION(mod_imagick_load) +{ + switch_file_interface_t *file_interface; + int i = 0; + + supported_formats[i++] = (char *)"imgk"; + supported_formats[i++] = (char *)"pdf"; + supported_formats[i++] = (char *)"gif"; + + MagickCoreGenesis(NULL, MagickFalse); + + SetErrorHandler(myErrorHandler); + SetWarningHandler(myWarningHandler); + SetFatalErrorHandler(myFatalErrorHandler); + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + file_interface = (switch_file_interface_t *)switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE); + file_interface->interface_name = modname; + file_interface->extens = supported_formats; + file_interface->file_open = imagick_file_open; + file_interface->file_close = imagick_file_close; + file_interface->file_read = imagick_file_read; + file_interface->file_read_video = imagick_file_read_video; + file_interface->file_seek = imagick_file_seek; + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_imagick_shutdown) +{ + MagickCoreTerminus(); + 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 noet: + */