diff --git a/src/mod/applications/mod_mp4/Makefile b/src/mod/applications/mod_mp4/Makefile new file mode 100644 index 0000000000..8c7932ae30 --- /dev/null +++ b/src/mod/applications/mod_mp4/Makefile @@ -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 index 0000000000..75f84989a2 --- /dev/null +++ b/src/mod/applications/mod_mp4/exception.hpp @@ -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 . +Portions created by the Initial Developer are Copyright (C) +the Initial Developer. All Rights Reserved. + +*/ + +#ifndef EXCEPTION_HPP_ +#define EXCEPTION_HPP_ + +#include +#include + +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 index 0000000000..a1114d1ffc --- /dev/null +++ b/src/mod/applications/mod_mp4/mod_mp4.cpp @@ -0,0 +1,547 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2010, 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 + * Paulo Rogério Panhoto + * 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 +#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(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(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(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(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(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, "", SAF_NONE); + //SWITCH_ADD_APP(app_interface, "record_mp4", "record an MP4 file", "record an MP4 file", record_mp4_function, "", 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 index 0000000000..a55f5dbeb5 --- /dev/null +++ b/src/mod/applications/mod_mp4/mp4_helper.cpp @@ -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 . +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 index 0000000000..e2da30733f --- /dev/null +++ b/src/mod/applications/mod_mp4/mp4_helper.hpp @@ -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 . +Portions created by the Initial Developer are Copyright (C) +the Initial Developer. All Rights Reserved. + +*/ + +#ifndef MP4_HELPER_HPP_ +#define MP4_HELPER_HPP_ + +#include +#include +#include +#include + +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