mod_mp4 -- Supports playback of MP4 files.

* 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>
This commit is contained in:
Paulo R. Panhoto 2010-11-10 16:27:43 -02:00
parent 42edb9cfe9
commit f331303404
5 changed files with 882 additions and 0 deletions

View File

@ -0,0 +1,6 @@
LOCAL_LDFLAGS=-lmp4v2
LOCAL_SOURCES=mp4_helper.cpp
LOCAL_OBJS=mp4_helper.o
BASE=../../../..
include $(BASE)/build/modmake.rules

View File

@ -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

View File

@ -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:
*/

View File

@ -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;
}
}

View File

@ -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