]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
mod_mp4 -- Supports playback of MP4 files.
authorPaulo R. Panhoto <paulo@voicetechnology.com.br>
Wed, 10 Nov 2010 18:27:43 +0000 (16:27 -0200)
committerPaulo R. Panhoto <paulo@voicetechnology.com.br>
Wed, 10 Nov 2010 18:27:43 +0000 (16:27 -0200)
* depends on libmp4v2 <http://code.google.com/p/mp4v2/>
(originally compiled against v1.6.1)

* File format details:
- Files must be hinted (mpeg4ip can be used to create the hint
  tracks).
- Video track encoding must be supported by FS (e.g. H.263)
- Audio track encoding must be PCMU/8000/Mono. The audio track
  can be created with this tool:
<https://asteriskvideo.svn.sourceforge.net/svnroot/asteriskvideo/tools/pcm2mp4.cpp>

src/mod/applications/mod_mp4/Makefile [new file with mode: 0644]
src/mod/applications/mod_mp4/exception.hpp [new file with mode: 0644]
src/mod/applications/mod_mp4/mod_mp4.cpp [new file with mode: 0644]
src/mod/applications/mod_mp4/mp4_helper.cpp [new file with mode: 0644]
src/mod/applications/mod_mp4/mp4_helper.hpp [new file with mode: 0644]

diff --git a/src/mod/applications/mod_mp4/Makefile b/src/mod/applications/mod_mp4/Makefile
new file mode 100644 (file)
index 0000000..8c7932a
--- /dev/null
@@ -0,0 +1,6 @@
+LOCAL_LDFLAGS=-lmp4v2
+LOCAL_SOURCES=mp4_helper.cpp
+LOCAL_OBJS=mp4_helper.o
+
+BASE=../../../..
+include $(BASE)/build/modmake.rules
diff --git a/src/mod/applications/mod_mp4/exception.hpp b/src/mod/applications/mod_mp4/exception.hpp
new file mode 100644 (file)
index 0000000..75f8498
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+
+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 MP4 Helper Library to Freeswitch MP4 module.
+
+The Initial Developer of the Original Code is 
+Paulo Rogério Panhoto <paulo@voicetechnology.com.br>.
+Portions created by the Initial Developer are Copyright (C)
+the Initial Developer. All Rights Reserved.
+
+*/
+
+#ifndef EXCEPTION_HPP_
+#define EXCEPTION_HPP_
+
+#include <exception>
+#include <string>
+
+class Exception: public std::exception {
+  public:
+       Exception()
+       {
+       }
+       
+       Exception(const std::string & message): message_(message)
+       {
+       }
+       
+       Exception(const std::exception & e): message_(e.what())
+       {
+       }
+       
+       Exception(const Exception & e): message_(e.message_)
+       {
+       }
+       
+       virtual ~Exception() throw()
+       {
+       }
+       
+       const char * what() const throw()
+       {
+               return message_.c_str();
+       }
+       
+  private:
+       std::string message_;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/mod/applications/mod_mp4/mod_mp4.cpp b/src/mod/applications/mod_mp4/mod_mp4.cpp
new file mode 100644 (file)
index 0000000..a1114d1
--- /dev/null
@@ -0,0 +1,547 @@
+/* 
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2005-2010, 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
+ * Paulo Rogério Panhoto <paulo@voicetechnology.com.br>
+ * Portions created by the Initial Developer are Copyright (C)
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * mod_mp4 -- MP4 File Format support for video apps.
+ *
+ */
+
+#include <switch.h>
+#include "mp4_helper.hpp"
+#include "exception.hpp"
+
+
+#ifndef min
+#define min(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+SWITCH_MODULE_LOAD_FUNCTION(mod_mp4_load);
+SWITCH_MODULE_DEFINITION(mod_mp4, mod_mp4_load, NULL, NULL);
+
+#define VID_BIT (1 << 31)
+#define VERSION 4201
+
+/*
+struct file_header {
+       int32_t version;
+       char video_codec_name[32];
+       char video_fmtp[128];
+       uint32_t audio_rate;
+       uint32_t audio_ptime;
+       switch_time_t created;
+};
+
+struct record_helper {
+       switch_core_session_t *session;
+       switch_mutex_t *mutex;
+       int fd;
+       int up;
+};
+*/
+
+struct AVParams {
+       switch_core_session_t * session;
+       switch_channel_t * channel;
+       switch_timer_t * timer;
+       switch_frame_t * frame;
+       switch_mutex_t * mutex;
+       bool video;
+       switch_payload_t pt;
+       MP4::Context * vc;
+       bool done;
+       bool * quit;
+};
+
+static void *SWITCH_THREAD_FUNC record_video_thread(switch_thread_t *thread, void *obj)
+{
+/*
+       record_helper *eh = reinterpret_cast<record_helper *>(obj);
+       switch_core_session_t *session = eh->session;
+       switch_channel_t *channel = switch_core_session_get_channel(session);
+       switch_status_t status;
+       switch_frame_t *read_frame;
+       int bytes;
+
+       eh->up = 1;
+       while (switch_channel_ready(channel)) {
+               status = switch_core_session_read_video_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0);
+
+               if (!SWITCH_READ_ACCEPTABLE(status)) {
+                       break;
+               }
+
+               if (switch_test_flag(read_frame, SFF_CNG)) {
+                       continue;
+               }
+
+               bytes = read_frame->packetlen | VID_BIT;
+
+               switch_mutex_lock(eh->mutex);
+
+               if (write(eh->fd, &bytes, sizeof(bytes)) != (int) sizeof(bytes)) {
+                       switch_mutex_unlock(eh->mutex);
+                       break;
+               }
+
+               if (write(eh->fd, read_frame->packet, read_frame->packetlen) != (int) read_frame->packetlen) {
+                       switch_mutex_unlock(eh->mutex);
+                       break;
+               }
+
+               switch_mutex_unlock(eh->mutex);
+
+               switch_core_session_write_video_frame(session, read_frame, SWITCH_IO_FLAG_NONE, 0);
+       }
+       eh->up = 0;
+*/
+       return NULL;
+}
+
+SWITCH_STANDARD_APP(record_mp4_function)
+{
+/*
+       switch_status_t status;
+       switch_frame_t *read_frame;
+       switch_channel_t *channel = switch_core_session_get_channel(session);
+       struct record_helper eh = { 0 };
+       switch_thread_t *thread;
+       switch_threadattr_t *thd_attr = NULL;
+       int fd;
+       switch_mutex_t *mutex = NULL;
+       switch_codec_t codec, *vid_codec;
+       switch_codec_implementation_t read_impl = { };
+       int count = 0, sanity = 30;
+
+       switch_core_session_get_read_impl(session, &read_impl);
+       switch_channel_answer(channel);
+
+
+       while (switch_channel_up(channel) && !switch_channel_test_flag(channel, CF_VIDEO)) {
+               switch_yield(10000);
+
+               if (count) count--;
+
+               if (count == 0) {
+                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s waiting for video.\n", switch_channel_get_name(channel));
+                       count = 100;
+                       if (!--sanity) {
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s timeout waiting for video.\n", 
+                                                                 switch_channel_get_name(channel));
+                               return;
+                       }
+               }
+       }
+       
+       if (!switch_channel_ready(channel)) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "%s not ready.\n", switch_channel_get_name(channel));
+               return;
+       }
+
+/*
+       if ((fd = open((char *) data, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Error opening file %s\n", (char *) data);
+               return;
+       }
+**
+
+       MP4::Context ctx(reinterpret_cast<char*>(data), true);
+
+       if (switch_core_codec_init(&codec,
+                       "L16",
+                       NULL,
+                       read_impl.samples_per_second,
+                       read_impl.microseconds_per_packet / 1000,
+                       1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
+                       NULL, switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS)
+       {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Audio Codec Activation Success\n");
+       } else {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Audio Codec Activation Fail\n");
+               goto end;
+       }
+
+       switch_core_session_set_read_codec(session, &codec);
+
+       if (switch_channel_test_flag(channel, CF_VIDEO)) {
+               struct file_header h;
+               memset(&h, 0, sizeof(h));
+               vid_codec = switch_core_session_get_video_read_codec(session);
+
+               h.version = VERSION;
+               h.created = switch_micro_time_now();
+               switch_set_string(h.video_codec_name, vid_codec->implementation->iananame);
+               if (vid_codec->fmtp_in) {
+                       switch_set_string(h.video_fmtp, vid_codec->fmtp_in);
+               }
+               h.audio_rate = read_impl.samples_per_second;
+               h.audio_ptime = read_impl.microseconds_per_packet / 1000;
+
+               if (write(fd, &h, sizeof(h)) != sizeof(h)) {
+                       goto end;
+               }
+
+               switch_mutex_init(&mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
+               eh.mutex = mutex;
+               eh.fd = fd;
+               eh.session = session;
+               switch_threadattr_create(&thd_attr, switch_core_session_get_pool(session));
+               switch_threadattr_detach_set(thd_attr, 1);
+               switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
+               switch_thread_create(&thread, thd_attr, record_video_thread, &eh, switch_core_session_get_pool(session));
+       }
+
+
+       while (switch_channel_ready(channel)) {
+
+               status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0);
+
+               if (!SWITCH_READ_ACCEPTABLE(status)) {
+                       break;
+               }
+
+               if (switch_test_flag(read_frame, SFF_CNG)) {
+                       continue;
+               }
+
+               if (mutex) {
+                       switch_mutex_lock(mutex);
+               }
+
+               if (write(fd, &read_frame->datalen, sizeof(read_frame->datalen)) != sizeof(read_frame->datalen)) {
+                       if (mutex) {
+                               switch_mutex_unlock(mutex);
+                       }
+                       break;
+               }
+
+               if (write(fd, read_frame->data, read_frame->datalen) != (int) read_frame->datalen) {
+                       if (mutex) {
+                               switch_mutex_unlock(mutex);
+                       }
+                       break;
+               }
+
+               if (mutex) {
+                       switch_mutex_unlock(mutex);
+               }
+       }
+
+
+  end:
+
+       if (eh.up) {
+               while (eh.up) {
+                       switch_cond_next();
+               }
+       }
+
+       switch_core_session_set_read_codec(session, NULL);
+       switch_core_codec_destroy(&codec);
+*/
+}
+
+static void *SWITCH_THREAD_FUNC play_video_function(switch_thread_t *thread, void *obj)
+{
+       AVParams * pt = reinterpret_cast<AVParams*>(obj);
+       u_int next = 0, first = 0xffffffff;
+       u_int64_t ts = 0, control = 0;
+
+       bool ok;
+       bool sent = true;
+       pt->done = false;
+       switch_time_t start = switch_time_now();
+       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pt->session), SWITCH_LOG_DEBUG, "Video thread Started\n");
+       while (!*pt->quit && switch_channel_ready(pt->channel)) {
+               if (pt->video) {
+                       if (sent) {
+                               switch_mutex_lock(pt->mutex);
+                               pt->frame->packetlen = pt->frame->buflen;
+                               ok = pt->vc->getVideoPacket(pt->frame->packet, pt->frame->packetlen, next);
+                               switch_mutex_unlock(pt->mutex);
+                               sent = false;
+                               if (ok) {
+                                       switch_rtp_hdr_t *hdr = reinterpret_cast<switch_rtp_hdr_t *>(pt->frame->packet);
+                                       if(first == 0xffffffff) first = next;
+                                       next -= first;
+                                       control = next * 90000LL / pt->vc->videoTrack().track.clock;
+                                       control -= first;
+                                       hdr->ts = htonl(control);
+                                       control = control * 1000 / 90;
+                                       if (pt->pt)
+                                               hdr->pt = pt->pt;
+                               } else break;
+                       }
+
+                       ts = switch_time_now() - start;
+                       int64_t wait = control > ts ? (control - ts) : 0;
+
+                       if (wait > 0) {
+                               switch_cond_next();
+                               // wait the time for the next Video frame
+                               switch_sleep(wait);
+                       }
+
+                       if (switch_channel_test_flag(pt->channel, CF_VIDEO)) {
+                               switch_byte_t *data = (switch_byte_t *) pt->frame->packet;
+
+                               pt->frame->data = data + 12;
+                               pt->frame->datalen = pt->frame->packetlen - 12;
+                               switch_core_session_write_video_frame(pt->session, pt->frame, SWITCH_IO_FLAG_NONE, 0);
+                               sent = true;
+                       }
+
+               } 
+       }
+       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pt->session), SWITCH_LOG_DEBUG, "Video thread ended\n");
+       pt->done = true;
+       return NULL;
+}
+
+static void *SWITCH_THREAD_FUNC play_audio_function(switch_thread_t *thread, void *obj)
+{
+       AVParams * pt = reinterpret_cast<AVParams*>(obj);
+       u_int next = 0, first = 0xffffffff;
+       u_int64_t ts = 0, control = 0;
+
+       bool ok;
+       bool sent = true;
+       switch_dtmf_t dtmf = {0};
+       pt->done = false;
+       switch_frame_t * read_frame;
+       
+       while (!*pt->quit && switch_channel_ready(pt->channel)) {
+               // event processing.
+               // -- SEE switch_ivr_play_say.c:1231 && mod_dptools.c:1428 && mod_dptools.c:1919
+               switch_core_session_read_frame(pt->session, &read_frame, SWITCH_IO_FLAG_SINGLE_READ, 0);
+
+               if (switch_channel_test_flag(pt->channel, CF_BREAK)) {
+                       switch_channel_clear_flag(pt->channel, CF_BREAK);
+                       break;
+               }
+
+               switch_ivr_parse_all_events(pt->session);
+
+               if (switch_channel_has_dtmf(pt->channel)) {
+                       switch_channel_dequeue_dtmf(pt->channel, &dtmf);
+                       const char * terminators = switch_channel_get_variable(pt->channel, SWITCH_PLAYBACK_TERMINATORS_VARIABLE);
+                       if (terminators && !strcasecmp(terminators, "none")) terminators = NULL;
+                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pt->session), SWITCH_LOG_DEBUG, "Digit %c\n", dtmf.digit);
+                       if (terminators && strchr(terminators, dtmf.digit)) {
+                               std::string digit(&dtmf.digit, 0, 1);
+                               switch_channel_set_variable(pt->channel, SWITCH_PLAYBACK_TERMINATOR_USED, digit.c_str());
+                               break;
+                       }
+               }
+               
+               switch_mutex_lock(pt->mutex);
+               pt->frame->datalen = pt->frame->buflen;
+               ok = pt->vc->getAudioPacket(pt->frame->data, pt->frame->datalen, next);
+               switch_mutex_unlock(pt->mutex);
+
+               if (ok) {
+                 if (pt->frame->datalen > (int) pt->frame->buflen)
+                               pt->frame->datalen = pt->frame->buflen;
+
+                       switch_core_session_write_frame(pt->session, pt->frame, SWITCH_IO_FLAG_NONE, 0);
+                       switch_core_timer_next(pt->timer);
+               }
+               else break;
+       }
+       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pt->session), SWITCH_LOG_DEBUG, "Audio done\n");
+       *pt->quit = pt->done = true;
+       return NULL;
+}
+
+SWITCH_STANDARD_APP(play_mp4_function)
+{
+       switch_channel_t *channel = switch_core_session_get_channel(session);
+       switch_frame_t write_frame = { 0 }, vid_frame = {0};
+       switch_codec_t codec = { 0 }, vid_codec = {0}, *read_vid_codec;
+       unsigned char *aud_buffer;
+       unsigned char *vid_buffer;
+       switch_timer_t timer = { 0 };
+       switch_codec_implementation_t read_impl = {};
+       bool done = false;
+
+       try {
+               MP4::Context vc((char *) data);
+
+               switch_payload_t pt = 0;
+
+               switch_core_session_get_read_impl(session, &read_impl);
+
+               aud_buffer = (unsigned char *) switch_core_session_alloc(session, SWITCH_RECOMMENDED_BUFFER_SIZE);
+               vid_buffer = (unsigned char *) switch_core_session_alloc(session, SWITCH_RECOMMENDED_BUFFER_SIZE);
+
+               /*
+               if (!vc.isOpen())
+               {
+                       char msgbuf[1024];
+                       sprintf(msgbuf, "PLAYBACK ERROR (%s): FILE NOT FOUND.", (char*) data);
+                       switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, msgbuf);
+                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Error opening file %s\n", (char *) data);
+                       return;
+               }
+               
+               if(!vc.isSupported())
+               {
+                       char msgbuf[1024];
+                       sprintf(msgbuf, "PLAYBACK ERROR (%s): UNSUPPORTED FORMAT OR FILE NOT HINTED.", (char*) data);
+                       switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, msgbuf);
+                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, 
+                               "Error reading track info. Maybe this file is not hinted.\n");
+                       throw 1;
+               }
+               */
+
+               switch_channel_set_variable(channel, "sip_force_video_fmtp", vc.videoTrack().fmtp.c_str());
+               switch_channel_answer(channel);
+
+               if ((read_vid_codec = switch_core_session_get_video_read_codec(session))) {
+                       pt = read_vid_codec->agreed_pt;
+               }
+
+               write_frame.codec = &codec;
+               write_frame.data = aud_buffer;
+               write_frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE;
+
+               vid_frame.codec = &vid_codec;
+               vid_frame.packet = vid_buffer;
+               vid_frame.data = vid_buffer + 12;
+               vid_frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE - 12;
+               switch_set_flag((&vid_frame), SFF_RAW_RTP);
+               switch_set_flag((&vid_frame), SFF_PROXY_PACKET);
+
+               if (switch_core_timer_init(&timer, "soft", read_impl.microseconds_per_packet / 1000,
+                       read_impl.samples_per_packet, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
+                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Timer Activation Fail\n");
+                       throw 2;
+               }
+
+               if (switch_core_codec_init(&codec,
+                                                                       vc.audioTrack().codecName,
+                                                                       NULL,
+                                                                       vc.audioTrack().clock,
+                                                                       vc.audioTrack().packetLength,
+                                                                       1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
+                                                                       NULL, switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS) {
+                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Audio Codec Activation Success\n");
+               } else {
+                       throw Exception("Audio Codec Activation Fail");
+               }
+
+               if (switch_core_codec_init(&vid_codec,
+                                                                       vc.videoTrack().track.codecName,
+                                                                       NULL,
+                                                                       0,
+                                                                       0,
+                                                                       1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
+                                                                       NULL, switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS) {
+                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Video Codec Activation Success\n");
+               } else
+               {
+                       throw Exception("Video Codec Activation Fail");
+               }
+               switch_core_session_set_read_codec(session, &codec);
+
+               AVParams vpt;
+               vpt.session = session;
+               vpt.channel = channel;
+               vpt.frame = &vid_frame;
+               vpt.timer = &timer;
+               vpt.video = true;
+               vpt.pt = pt;
+               vpt.vc = &vc;
+               switch_mutex_init(&vpt.mutex, SWITCH_MUTEX_DEFAULT, switch_core_session_get_pool(session));
+               vpt.quit = &done;
+               
+               switch_threadattr_t * thd_attr;
+               switch_threadattr_create(&thd_attr, switch_core_session_get_pool(session));
+               switch_threadattr_detach_set(thd_attr, 1);
+               switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
+               switch_thread_t *thread;
+               switch_thread_create(&thread, thd_attr, play_video_function, (void*)&vpt, switch_core_session_get_pool(session));
+
+               AVParams apt;
+               apt.session = session;
+               apt.channel = channel;
+               apt.frame = &write_frame;
+               apt.timer = &timer;
+               apt.video = false;
+               apt.vc = &vc;
+               apt.mutex = vpt.mutex;
+               apt.quit = &done;
+               play_audio_function(NULL, &apt);
+
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Waiting for video thread to join.\n");
+               while (!vpt.done) {
+                       switch_cond_next();
+               }
+
+               switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "FILE PLAYED");
+       } catch(const std::exception & e) 
+       {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "%s\n", e.what());
+               switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE,
+                       (std::string("PLAYBACK_FAILED - ") + e.what()).c_str());
+       }catch(...)
+       {
+               switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "PLAYBACK_FAILED - See FS logs for detail.");
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Exception caught.\n");
+       }
+
+       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "All done.\n");
+       if (timer.interval)  switch_core_timer_destroy(&timer);
+
+       switch_core_session_set_read_codec(session, NULL);
+
+       if (switch_core_codec_ready(&codec)) switch_core_codec_destroy(&codec);
+
+       if (switch_core_codec_ready(&vid_codec)) switch_core_codec_destroy(&vid_codec);
+}
+
+SWITCH_MODULE_LOAD_FUNCTION(mod_mp4_load)
+{
+       switch_application_interface_t *app_interface;
+
+       /* connect my internal structure to the blank pointer passed to me */
+       *module_interface = switch_loadable_module_create_module_interface(pool, modname);
+
+       SWITCH_ADD_APP(app_interface, "play_mp4", "play an MP4 file", "play an MP4 file", play_mp4_function, "<file>", SAF_NONE);
+       //SWITCH_ADD_APP(app_interface, "record_mp4", "record an MP4 file", "record an MP4 file", record_mp4_function, "<file>", SAF_NONE);
+
+       /* indicate that the module should continue to be loaded */
+       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:
+ */
diff --git a/src/mod/applications/mod_mp4/mp4_helper.cpp b/src/mod/applications/mod_mp4/mp4_helper.cpp
new file mode 100644 (file)
index 0000000..a55f5db
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+
+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 MP4 Helper Library to the Freeswitch MP4 Module.
+
+The Initial Developer of the Original Code is 
+Paulo Rogério Panhoto <paulo@voicetechnology.com.br>.
+Portions created by the Initial Developer are Copyright (C)
+the Initial Developer. All Rights Reserved.
+
+
+*/
+
+#include "mp4_helper.hpp"
+
+namespace MP4
+{
+       
+       Context::Context(const char * file, bool newFile)
+       {
+               if(newFile) create(file);
+               else open(file);
+       }
+       
+       Context::~Context()
+       {
+               close();
+       }
+
+       void Context::open(const char * file)
+       {
+               fh = MP4Read(file, 0);
+               if (fh == MP4_INVALID_FILE_HANDLE) throw Exception(file, "Open failed");
+               getTracks(file);
+       }
+       
+       void Context::create(const char * file)
+       {
+               fh = MP4Create(file);
+               if (fh == MP4_INVALID_FILE_HANDLE) throw Exception(file, "Create file failed");
+       }
+
+       void Context::close()
+       {
+               if (!isOpen()) return;
+               MP4Close(fh);
+       }
+
+       void Context::getTracks(const char * file)
+       {
+               int i = 0;
+               bool audioTrack = false, videoTrack = false;
+
+               if (!isOpen()) throw Exception(file, "File is closed.");
+
+               for (;;)
+               {
+                       TrackProperties track;
+                       if((track.hint = MP4FindTrackId(fh, i++, MP4_HINT_TRACK_TYPE, 0)) == MP4_INVALID_TRACK_ID) break;
+
+                       MP4GetHintTrackRtpPayload(fh, track.hint, &track.codecName, &track.payload, NULL, NULL);
+
+                       track.track = MP4GetHintTrackReferenceTrackId(fh, track.hint);
+                       if(track.track == MP4_INVALID_TRACK_ID) continue;
+                       track.clock = MP4GetTrackTimeScale(fh, track.hint);
+
+                       if (!strcmp(MP4GetTrackType(fh, track.track), MP4_AUDIO_TRACK_TYPE)) {
+                               audioTrack = true;
+
+                               if(!strncmp(track.codecName, "PCM", 3))
+                                       track.packetLength = 20;
+                               else
+                                       track.packetLength = track.clock = 0;
+
+                               audio = track;
+                       } else if (!strcmp(MP4GetTrackType(fh, track.track), MP4_VIDEO_TRACK_TYPE)) {
+                               videoTrack = true;
+
+                               const char * sdp = MP4GetHintTrackSdp(fh, track.hint);
+                               const char * fmtp = strstr(sdp, "fmtp");
+
+                               if (fmtp) {
+                                       // finds beginning of 'fmtp' value;
+                                       for(fmtp += 5; *fmtp != ' '; ++fmtp);
+                                       ++fmtp;
+
+                                       const char * eol = fmtp;
+                                       for(;*eol != '\r' && *eol != '\n'; ++eol);
+                                       video.fmtp = std::string(fmtp, eol);
+                               }
+                               video.track = track;
+                       }
+               }
+
+               if (!audioTrack || !videoTrack) throw Exception(file, "Missing audio/video track.");
+       }
+
+       bool Context::getVideoPacket(void * buffer, u_int & size, u_int & ts)
+       {
+               return getPacket(video.track.hint, video.track.runtime, true, buffer, size, ts);
+       }
+
+       bool Context::getAudioPacket(void * buffer, u_int & size, u_int & ts)
+       {
+               return getPacket(audio.hint, audio.runtime, false, buffer, size, ts);
+       }
+
+       bool Context::getPacket(MP4TrackId hint, RuntimeProperties & rt,
+                               bool header, void * buffer, u_int & size, u_int & ts)
+       {
+               if (rt.frame == 0 || rt.packet == rt.packetsPerFrame) {
+                       ++rt.frame;
+                       if(!MP4ReadRtpHint(fh, hint, rt.frame, &rt.packetsPerFrame))
+                               return false;
+                       rt.packet = 0;
+                       rt.last_frame = MP4GetSampleTime(fh, hint, rt.frame);
+               }
+
+               ts = rt.last_frame;
+               if (!MP4ReadRtpPacket(fh, hint, rt.packet, (u_int8_t **) &buffer, &size, 0, header, true)) return false;
+               ++rt.packet;
+               return true;
+       }
+}
\ No newline at end of file
diff --git a/src/mod/applications/mod_mp4/mp4_helper.hpp b/src/mod/applications/mod_mp4/mp4_helper.hpp
new file mode 100644 (file)
index 0000000..e2da307
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+
+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 MP4 Helper Library to Freeswitch MP4 module.
+
+The Initial Developer of the Original Code is 
+Paulo Rogério Panhoto <paulo@voicetechnology.com.br>.
+Portions created by the Initial Developer are Copyright (C)
+the Initial Developer. All Rights Reserved.
+
+*/
+
+#ifndef MP4_HELPER_HPP_
+#define MP4_HELPER_HPP_
+
+#include <mp4.h>
+#include <string>
+#include <exception>
+#include <string>
+
+namespace MP4
+{
+       class Exception: public std::exception {
+               public:
+                       Exception(const std::string & file, const std::string & error)
+                       : description_(file + ':' + error)
+                       {
+                       }
+                       
+                       const char * what() const throw()
+                       {
+                               return description_.c_str();
+                       }
+                       
+                       ~Exception() throw()
+                       {
+                       }
+                       
+               private:
+                       std::string description_;
+       };
+       
+       struct RuntimeProperties {
+               u_int32_t frame; // sampleID
+               u_int16_t packetsPerFrame;
+               u_int16_t packet; // packetID
+               u_int32_t last_frame; // timestamp
+
+               RuntimeProperties(): frame(0), packetsPerFrame(0), packet(0)
+               {
+               }
+       };
+
+
+       struct TrackProperties {
+               MP4TrackId hint;
+               MP4TrackId track;
+
+               char * codecName;
+               u_int8_t payload;
+               u_int32_t clock;
+               u_int32_t packetLength; // packet Length in time (ms)
+               
+               RuntimeProperties runtime;
+
+               TrackProperties(): hint(MP4_INVALID_TRACK_ID), track(MP4_INVALID_TRACK_ID), 
+                       codecName(NULL), payload(0), clock(0), packetLength(0)
+               {
+               }
+       };
+
+       typedef TrackProperties AudioProperties;
+
+       struct VideoProperties {
+               TrackProperties track;
+               std::string fmtp;
+
+               VideoProperties()
+               {
+               }
+
+               VideoProperties(const TrackProperties & rhs): track(rhs)
+               {
+               }
+       };
+
+       class Context {
+       public:
+
+               Context(const char * file, bool create = false);
+               ~Context();
+
+               void open(const char * file);
+               
+               void create(const char * file);
+
+               void close();
+
+               // returns: TRUE = has more data, FALSE = end-of-stream or failure
+               bool getVideoPacket(void * buffer, u_int & size, u_int & ts);
+
+               // returns: TRUE = has more data, FALSE = end-of-stream or failure
+               bool getAudioPacket(void * buffer, u_int & size, u_int & ts);
+
+               bool isOpen() const { return fh != MP4_INVALID_FILE_HANDLE; }
+
+               bool isSupported() const { return audio.track != MP4_INVALID_TRACK_ID && video.track.track != MP4_INVALID_TRACK_ID; }
+
+               const AudioProperties & audioTrack() const { return audio; }
+
+               const VideoProperties & videoTrack() const { return video; }
+
+       private:
+               MP4FileHandle fh;
+               AudioProperties audio;
+
+               VideoProperties video;
+
+               // Prevent copy construction.
+               Context(const Context &);
+
+               bool getPacket(MP4TrackId hint, RuntimeProperties & rt,
+                               bool header, void * buffer, u_int & size, u_int & ts);
+
+               void getTracks(const char * file);
+       };
+}
+#endif