[Asterisk-video] app_mp4 and h324m
Denis Smirnov
ds at seiros.ru
Fri Jan 12 05:44:17 MST 2007
On Fri, Jan 12, 2007 at 01:10:53PM +0100, Sergio Garcia Murillo wrote:
SGM> Hi everyone, I have just left my former company and I'm going to start working on some video related projects.
SGM> I have just upgrade the app_mp4 application to handle dtmfs and h263-1998, 2000 and fix some bugs.
SGM> You can download it and a small example from here http://sip.fontventa.com
SGM> Also I'm going to re-start the development of the h324m library, I plan to upload the current source to the svn
SGM> repository next week.
SGM> If anyone is still interested or want to collaborate will be wellcome.
I cleanup your code for compiling with -Werror, and do some small
modifications.
Attached.
--
JID: ds at im.seiros.ru
ICQ: 58417635 (please, use jabber, if you can)
http://freesource.info/
-------------- next part --------------
/*
* Asterisk -- An open source telephony toolkit.
*
* Sergio Garcia Murillo <sergio.garcia at ydilo.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief MP4 application -- save and play mp4 files
*
* \ingroup applications
*/
#include <asterisk.h>
#include <mp4.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <asterisk/lock.h>
#include <asterisk/file.h>
#include <asterisk/logger.h>
#include <asterisk/channel.h>
#include <asterisk/pbx.h>
#include <asterisk/module.h>
static char *app_play = "mp4play";
static char *syn_play = "MP4 file playblack";
static char *des_play = " mp4play(): Play mp4 file to user. \n";
static char *app_save = "mp4save";
static char *syn_save = "MP4 file record";
static char *des_save = " mp4save(): Record mp4 file. \n";
struct mp4track {
MP4FileHandle mp4;
MP4TrackId track;
MP4TrackId hint;
bool first;
unsigned char frame[65535];
int length;
int sampleId;
};
static int mp4_rtp_write_audio(struct mp4track *t, struct ast_frame *f)
{
/* Next sample */
t->sampleId++;
ast_log(LOG_DEBUG, "Saving #%d:%d:%d %d samples %d size of audio\n", t->sampleId, t->track, t->hint, f->samples, f->datalen);
/* Add hint */
MP4AddRtpHint(t->mp4, t->hint);
/* Add rtp packet to hint track */
MP4AddRtpPacket(t->mp4, t->hint, 0, 0);
/* Set which part of sample audio goes to this rtp packet */
MP4AddRtpSampleData(t->mp4, t->hint, t->sampleId, 0, f->datalen);
/* Write rtp hint */
MP4WriteRtpHint(t->mp4, t->hint, f->datalen, 1); //ast_codec_get_samples(f),1);
/* Write audio */
MP4WriteSample(t->mp4, t->track, f->data, f->datalen, f->datalen, 0, 0); //ast_codec_get_samples(f),0,1);
return 0;
}
static void mp4_rtp_write_video_frame(struct mp4track *t, int samples, bool intra)
{
ast_log(LOG_DEBUG, "Saving #%d:%d:%d %d samples %d size of video\n", t->sampleId, t->track, t->hint, samples, t->length);
/* Save rtp hint */
MP4WriteRtpHint(t->mp4, t->hint, samples, intra);
/* Save video frame */
MP4WriteSample(t->mp4, t->track, t->frame, t->length, samples, 0, intra);
}
static int mp4_rtp_write_video(struct mp4track *t, struct ast_frame *f, int payload, bool intra)
{
/* rtp mark */
bool mBit = f->subclass & 0x1;
/* If it's the first packet of a new frame */
if (t->first) {
/* If we hava a sample */
if (t->sampleId > 0) {
/* Save frame */
mp4_rtp_write_video_frame(t, f->samples, intra);
/* Reset buffer length */
t->length = 0;
}
/* Reset first mark */
t->first = 0;
/* Next frame */
t->sampleId++;
ast_log(LOG_DEBUG, "New video hint\n");
/* Add hint */
MP4AddRtpHint(t->mp4, t->hint);
}
/* Add rtp packet to hint track */
MP4AddRtpPacket(t->mp4, t->hint, mBit, 0);
/* Save rtp specific payload header to hint */
if (payload > 0)
MP4AddRtpImmediateData(t->mp4, t->hint, f->data, payload);
/* Set hint reference to video data */
MP4AddRtpSampleData(t->mp4, t->hint, t->sampleId, (u_int32_t) t->length, f->datalen - payload);
/* Copy the video data to buffer */
memcpy(t->frame + t->length, (char*)f->data + payload, f->datalen - payload);
/* Increase stored buffer length */
t->length += f->datalen - payload;
/* If it's the las packet in a frame */
if (mBit)
/* Set first mark */
t->first = 1;
return 0;
}
struct mp4rtp {
struct ast_channel *chan;
MP4FileHandle mp4;
MP4TrackId hint;
MP4TrackId track;
unsigned int timeScale;
unsigned int sampleId;
unsigned short numHintSamples;
unsigned short packetIndex;
int frameSamples;
int frameSize;
int frameTime;
int frameType;
int frameSubClass;
char *name;
unsigned char type;
};
static int mp4_rtp_read(struct mp4rtp *p)
{
struct ast_frame *f;
int next = 0;
int last = 0;
ast_log(LOG_DEBUG, "mp4_rtp_read\n");
/* If it's first packet of a frame */
if (!p->numHintSamples) {
ast_log(LOG_DEBUG, "New sample for %s\n", p->name);
/* Get number of rtp packets for this sample */
if (!MP4ReadRtpHint(p->mp4, p->hint, p->sampleId, &p->numHintSamples)) {
ast_log(LOG_DEBUG, "MP4ReadRtpHint failed\n");
return -1;
}
/* Get number of samples for this sample */
p->frameSamples = MP4GetSampleDuration(p->mp4, p->hint, p->sampleId);
/* Get size of sample */
p->frameSize = MP4GetSampleSize(p->mp4, p->hint, p->sampleId);
/* Get sample timestamp */
p->frameTime = MP4GetSampleTime(p->mp4, p->hint, p->sampleId);
ast_log(LOG_DEBUG, "[%d,%d,%d]\n", p->frameSamples, p->frameSize, p->frameTime);
}
/* if it's the last */
if (p->packetIndex + 1 == p->numHintSamples)
last = 1;
/* malloc frame & data */
f = (struct ast_frame *) malloc(sizeof(struct ast_frame) + 65000);
/* Let mp4 lib allocate memory */
f->data = f + AST_FRIENDLY_OFFSET;
f->datalen = 65000;
f->src = 0;
/* Set type */
f->frametype = p->frameType;
f->subclass = p->frameSubClass;
/* Set number of samples */
f->samples = p->frameSamples;
f->delivery.tv_usec = 0;
f->delivery.tv_sec = 0;
f->mallocd = 0;
/* If it's video set the mark of last rtp packet */
if (f->frametype == AST_FRAME_VIDEO)
f->subclass |= last;
/* Read next rtp packet */
if (!MP4ReadRtpPacket(p->mp4, /* MP4FileHandle hFile */
p->hint, /* MP4TrackId hintTrackId */
p->packetIndex++, /* u_int16_t packetIndex */
(u_int8_t **) &f->data, /* u_int8_t** ppBytes */
(u_int32_t *) &f->datalen, /* u_int32_t* pNumBytes */
0, /* u_int32_t ssrc DEFAULT(0) */
0, /* bool includeHeader DEFAULT(true) */
1 /* bool includePayload DEFAULT(true) */
)) {
ast_log(LOG_DEBUG, "Error reading packet [%d,%d]\n", p->hint, p->track);
return -1;
}
/* Write frame */
ast_write(p->chan, f);
/* Are we the last packet in a hint? */
if (last) {
/* The first hint */
p->packetIndex = 0;
/* Go for next sample */
p->sampleId++;
p->numHintSamples = 0;
}
/* Set next send time */
if (!last && f->frametype == AST_FRAME_VIDEO)
next = 0;
else if (p->timeScale)
next = (p->frameSamples * 1000) / p->timeScale;
else
next = -1;
/* exit next send time */
return next;
}
static int mp4_play(struct ast_channel *chan, void *data)
{
struct mp4rtp audio = { chan, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
struct mp4rtp video = { chan, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
MP4FileHandle mp4;
MP4TrackId hintId;
MP4TrackId trackId;
const char *type = NULL;
int audioNext = -1;
int videoNext = -1;
int t = 0;
int i = 0;
struct ast_frame *f;
/* Check for data */
if (!data)
return -1;
ast_log(LOG_DEBUG, "mp4play %s\n", (char *)data);
/* Open mp4 file */
mp4 = MP4Read((char *) data, 9);
/* If not valid */
if (mp4 == MP4_INVALID_FILE_HANDLE)
return -1;
/* Get the first hint track */
hintId = MP4FindTrackId(mp4, i++, MP4_HINT_TRACK_TYPE, 0);
/* Iterate hint tracks */
while (hintId != MP4_INVALID_TRACK_ID) {
ast_log(LOG_DEBUG, "found hint track %d\n", hintId);
/* Get asociated track */
trackId = MP4GetHintTrackReferenceTrackId(mp4, hintId);
/* Check it's good */
if (trackId != MP4_INVALID_TRACK_ID) {
/* Get type */
type = MP4GetTrackType(mp4, trackId);
ast_log(LOG_DEBUG, "track %d %s\n", trackId, type);
/* Check track type */
if (strcmp(type, MP4_AUDIO_TRACK_TYPE) == 0) {
/* it's audio */
audio.mp4 = mp4;
audio.hint = hintId;
audio.track = trackId;
audio.sampleId = 1;
audio.packetIndex = 0;
audio.frameType = AST_FRAME_VOICE;
/* Get audio type */
MP4GetHintTrackRtpPayload(mp4, hintId, &audio.name, &audio.type, NULL, NULL);
/* Get time scale */
audio.timeScale = MP4GetTrackTimeScale(mp4, hintId);
/* Depending on the name */
if (strcmp("PCMU", audio.name) == 0)
audio.frameSubClass = AST_FORMAT_ULAW;
else if (strcmp("PCMA", audio.name) == 0)
audio.frameSubClass = AST_FORMAT_ALAW;
} else if (strcmp(type, MP4_VIDEO_TRACK_TYPE) == 0) {
/* it's video */
video.mp4 = mp4;
video.hint = hintId;
video.track = trackId;
video.sampleId = 1;
video.packetIndex = 0;
video.frameType = AST_FRAME_VIDEO;
/* Get video type */
MP4GetHintTrackRtpPayload(mp4, hintId, &video.name, &video.type, NULL, NULL);
/* Get time scale */
video.timeScale = MP4GetTrackTimeScale(mp4, hintId);
/* Depending on the name */
if (strcmp("H263", video.name) == 0)
video.frameSubClass = AST_FORMAT_H263;
else if (strcmp("H263-1998", video.name) == 0)
video.frameSubClass = AST_FORMAT_H263_PLUS;
else if (strcmp("H263-2000", video.name) == 0)
video.frameSubClass = AST_FORMAT_H263_PLUS;
}
}
/* Get the next hint track */
hintId = MP4FindTrackId(mp4, i++, MP4_HINT_TRACK_TYPE, 0);
}
/* If we have audio */
if (audio.name)
/* Send audio */
audioNext = mp4_rtp_read(&audio);
/* If we have video */
if (video.name)
/* Send video */
videoNext = mp4_rtp_read(&video);
/* Wait control messages or finish of both streams */
while (!(audioNext < 0 && videoNext < 0)) {
/* Get next time */
if (audioNext < 0)
t = videoNext;
else if (videoNext < 0)
t = audioNext;
else if (audioNext < videoNext)
t = audioNext;
else
t = videoNext;
/* Sleep until next time or control arrives */
/*if(ast_safe_sleep(chan,t)!=0)
return 0; */
/* Wait time */
int ms = t;
/* Read from channel and wait timeout */
while (ms > 0) {
/* Wait */
ms = ast_waitfor(chan, ms);
/* if we have been hang up */
if (ms < 0)
return -1;
/* if we have received something on the channel */
if (ms > 0) {
/* Read frame */
f = ast_read(chan);
/* If failed */
if (!f)
return -1;
/* If it's a dtmf */
if (f->frametype == AST_FRAME_DTMF) {
char dtmf[2];
int res;
/* Get dtmf number */
res = f->subclass;
dtmf[0] = res;
dtmf[1] = 0;
/* Check for dtmf extension in context */
if (ast_exists_extension(chan, chan->context, dtmf, 1, NULL)) {
/* Set extension */
//set_ext_pri(dtmf,1);
/* Free frame */
ast_frfree(f);
/* exit */
return res;
}
}
/* Free frame */
ast_frfree(f);
}
}
/* Remove time */
if (audioNext > 0)
audioNext -= t;
if (videoNext > 0)
videoNext -= t;
/* if we have to send audio */
if (!audioNext > 0)
audioNext = mp4_rtp_read(&audio);
/* or video */
if (!videoNext > 0)
videoNext = mp4_rtp_read(&video);
}
ast_log(LOG_DEBUG, "exit");
//Exit
return 0;
}
static int mp4_save(struct ast_channel *chan, void *data)
{
struct ast_module_user *u;
struct ast_frame *f;
struct mp4track audioTrack;
struct mp4track videoTrack;
MP4FileHandle mp4;
MP4TrackId audio = -1;
MP4TrackId video = -1;
MP4TrackId hintAudio = -1;
MP4TrackId hintVideo = -1;
unsigned char type = 0;
bool intra;
int payload;
u = ast_module_user_add(chan);
/* Check for file */
if (!data)
return -1;
/* Create mp4 file */
mp4 = MP4CreateEx((char *) data, 9, 0, 1, 1, 0, 0, 0, 0);
/* If failed */
if (mp4 == MP4_INVALID_FILE_HANDLE)
return -1;
/* Wait for data avaiable on channel */
while (ast_waitfor(chan, -1) > -1) {
/* Read frame from channel */
f = ast_read(chan);
/* if it's null */
if (f == NULL)
break;
/* Check frame type */
if (f->frametype == AST_FRAME_VOICE) {
/* Check if we have the audio track */
if (audio == -1) {
/* Create audio track */
audio = MP4AddAudioTrack(mp4, 8000, 0, MP4_ULAW_AUDIO_TYPE);
/* Create audio hint track */
hintAudio = MP4AddHintTrack(mp4, audio);
/* Set payload type for hint track */
type = 0;
MP4SetHintTrackRtpPayload(mp4, hintAudio, "PCMU", &type, 0, NULL, 1, 0);
/* Set struct info */
audioTrack.mp4 = mp4;
audioTrack.track = audio;
audioTrack.hint = hintAudio;
audioTrack.length = 0;
audioTrack.sampleId = 0;
audioTrack.first = 1;
}
/* Save audio rtp packet */
mp4_rtp_write_audio(&audioTrack, f);
} else if (f->frametype == AST_FRAME_VIDEO) {
/* Check if we have the video track */
if (video == -1) {
/* Check codec */
if (f->subclass & AST_FORMAT_H263) {
/* Create video track */
video = MP4AddH263VideoTrack(mp4, 90000, 0, 176, 144, 0, 0, 0, 0);
/* Create video hint track */
hintVideo = MP4AddHintTrack(mp4, video);
/* Set payload type for hint track */
type = 34;
MP4SetHintTrackRtpPayload(mp4, hintVideo, "H263", &type, 0, NULL, 1, 0);
} else if (f->subclass & AST_FORMAT_H263_PLUS) {
/* Create video track */
video = MP4AddH263VideoTrack(mp4, 90000, 0, 176, 144, 0, 0, 0, 0);
/* Create video hint track */
hintVideo = MP4AddHintTrack(mp4, video);
/* Set payload type for hint track */
type = 96;
MP4SetHintTrackRtpPayload(mp4, hintVideo, "H263-1998", &type, 0, NULL, 1, 0);
} else
/* Unknown codec nothing to do */
break;
/* Set struct info */
videoTrack.mp4 = mp4;
videoTrack.track = video;
videoTrack.hint = hintVideo;
videoTrack.length = 0;
videoTrack.sampleId = 0;
videoTrack.first = 1;
}
/* Check codec */
if (f->subclass & AST_FORMAT_H263) {
/* Check if it's an intra frame */
intra = (((unsigned char *) (f->data))[1] & 0x10) != 0;
/* payload length */
payload = 4;
} else if (f->subclass & AST_FORMAT_H263_PLUS) {
/* Check if it's an intra frame */
intra = (((unsigned char *) (f->data))[0] & 0x04) != 0;
/* payload length */
payload = 2;
} else
/* Unknown codec nothing to do */
break;
/* Write rtp video packet */
mp4_rtp_write_video(&videoTrack, f, payload, intra);
} else if (f->frametype == AST_FRAME_DTMF) {
}
/* free frame */
ast_frfree(f);
}
/* Save last video frame if needed */
if (videoTrack.sampleId > 0)
/* Save frame */
mp4_rtp_write_video_frame(&videoTrack, 0, intra);
/* Close file */
MP4Close(mp4);
ast_module_user_remove(u);
//Success
return 0;
}
static int unload_module(void)
{
int res;
res = ast_unregister_application(app_play);
res &= ast_unregister_application(app_save);
ast_module_user_hangup_all();
return res;
}
static int load_module(void)
{
int res;
res = ast_register_application(app_save, mp4_save, syn_save, des_save);
res &= ast_register_application(app_play, mp4_play, syn_play, des_play);
return 0;
}
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "MP4 applications");
More information about the asterisk-video
mailing list