From d1d90f47fd87ec50db7aa5badcc996854d93c0bf Mon Sep 17 00:00:00 2001 From: markster Date: Sat, 7 Jan 2006 17:54:22 +0000 Subject: Add support for H.264 with SIP and recording git-svn-id: http://svn.digium.com/svn/asterisk/trunk@7855 f38db490-d61c-443f-a65b-d21fe96a405b --- channel.c | 3 + channels/chan_sip.c | 10 +- cli.c | 12 +- file.c | 58 ++++++---- formats/Makefile | 2 +- formats/format_h264.c | 281 +++++++++++++++++++++++++++++++++++++++++++++++ frame.c | 2 +- include/asterisk/frame.h | 5 + rtp.c | 3 + 9 files changed, 341 insertions(+), 35 deletions(-) create mode 100644 formats/format_h264.c diff --git a/channel.c b/channel.c index d35caf53d..3062f6bcc 100644 --- a/channel.c +++ b/channel.c @@ -2305,6 +2305,9 @@ static int set_format(struct ast_channel *chan, int fmt, int *rawformat, int *fo int native; int res; + /* Make sure we only consider audio */ + fmt &= AST_FORMAT_AUDIO_MASK; + native = chan->nativeformats; /* Find a translation path from the native format to one of the desired formats */ if (!direction) diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 55419f163..027f10081 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -2790,7 +2790,7 @@ static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *tit what = i->capability; else what = global_capability; - tmp->nativeformats = ast_codec_choose(&i->prefs, what, 1); + tmp->nativeformats = ast_codec_choose(&i->prefs, what, 1) | (i->jointcapability & AST_FORMAT_VIDEO_MASK); fmt = ast_best_codec(tmp->nativeformats); if (title) @@ -3011,9 +3011,9 @@ static struct ast_frame *sip_rtp_read(struct ast_channel *ast, struct sip_pvt *p if (p->owner) { /* We already hold the channel lock */ if (f->frametype == AST_FRAME_VOICE) { - if (f->subclass != p->owner->nativeformats) { + if (f->subclass != (p->owner->nativeformats & AST_FORMAT_AUDIO_MASK)) { ast_log(LOG_DEBUG, "Oooh, format changed to %d\n", f->subclass); - p->owner->nativeformats = f->subclass; + p->owner->nativeformats = (p->owner->nativeformats & AST_FORMAT_VIDEO_MASK) | f->subclass; ast_set_read_format(p->owner, p->owner->readformat); ast_set_write_format(p->owner, p->owner->writeformat); } @@ -3679,13 +3679,13 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req) if (!p->owner) /* There's no open channel owning us */ return 0; - if (!(p->owner->nativeformats & p->jointcapability)) { + if (!(p->owner->nativeformats & p->jointcapability & AST_FORMAT_AUDIO_MASK)) { const unsigned slen=512; char s1[slen], s2[slen]; ast_log(LOG_DEBUG, "Oooh, we need to change our formats since our peer supports only %s and not %s\n", ast_getformatname_multiple(s1, slen, p->jointcapability), ast_getformatname_multiple(s2, slen, p->owner->nativeformats)); - p->owner->nativeformats = ast_codec_choose(&p->prefs, p->jointcapability, 1); + p->owner->nativeformats = ast_codec_choose(&p->prefs, p->jointcapability, 1) | (p->capability & vpeercapability); ast_set_read_format(p->owner, p->owner->readformat); ast_set_write_format(p->owner, p->owner->writeformat); } diff --git a/cli.c b/cli.c index 7ad4d5ff6..5c0f38fb4 100644 --- a/cli.c +++ b/cli.c @@ -738,6 +738,7 @@ static int handle_showchan(int fd, int argc, char *argv[]) struct timeval now; char buf[2048]; char cdrtime[256]; + char nf[256], wf[256], rf[256]; long elapsed_seconds=0; int hour=0, min=0, sec=0; @@ -767,9 +768,9 @@ static int handle_showchan(int fd, int argc, char *argv[]) " DNID Digits: %s\n" " State: %s (%d)\n" " Rings: %d\n" - " NativeFormat: %d\n" - " WriteFormat: %d\n" - " ReadFormat: %d\n" + " NativeFormats: %s\n" + " WriteFormat: %s\n" + " ReadFormat: %s\n" "1st File Descriptor: %d\n" " Frames in: %d%s\n" " Frames out: %d%s\n" @@ -789,7 +790,10 @@ static int handle_showchan(int fd, int argc, char *argv[]) c->name, c->type, c->uniqueid, (c->cid.cid_num ? c->cid.cid_num : "(N/A)"), (c->cid.cid_name ? c->cid.cid_name : "(N/A)"), - (c->cid.cid_dnid ? c->cid.cid_dnid : "(N/A)" ), ast_state2str(c->_state), c->_state, c->rings, c->nativeformats, c->writeformat, c->readformat, + (c->cid.cid_dnid ? c->cid.cid_dnid : "(N/A)" ), ast_state2str(c->_state), c->_state, c->rings, + ast_getformatname_multiple(nf, sizeof(nf), c->nativeformats), + ast_getformatname_multiple(wf, sizeof(wf), c->writeformat), + ast_getformatname_multiple(rf, sizeof(rf), c->readformat), c->fds[0], c->fin & 0x7fffffff, (c->fin & 0x80000000) ? " (DEBUGGED)" : "", c->fout & 0x7fffffff, (c->fout & 0x80000000) ? " (DEBUGGED)" : "", (long)c->whentohangup, cdrtime, c->_bridge ? c->_bridge->name : "", ast_bridged_channel(c) ? ast_bridged_channel(c)->name : "", diff --git a/file.c b/file.c index 590fb71f0..bce283b83 100644 --- a/file.c +++ b/file.c @@ -203,8 +203,7 @@ int ast_writestream(struct ast_filestream *fs, struct ast_frame *f) if (fs->fmt->format < AST_FORMAT_MAX_AUDIO) { /* This is the audio portion. Call the video one... */ if (!fs->vfs && fs->filename) { - /* XXX Support other video formats XXX */ - const char *type = "h263"; + const char *type = ast_getformatname(f->subclass & ~0x1); fs->vfs = ast_writefile(fs->filename, type, NULL, fs->flags, 0, fs->mode); ast_log(LOG_DEBUG, "Opened video output file\n"); } @@ -495,10 +494,14 @@ struct ast_filestream *ast_openstream_full(struct ast_channel *chan, const char } else snprintf(filename2, sizeof(filename2), "%s/%s", preflang, filename); fmts = ast_fileexists(filename2, NULL, NULL); + if (fmts > 0) + fmts &= AST_FORMAT_AUDIO_MASK; } if (fmts < 1) { ast_copy_string(filename2, filename, sizeof(filename2)); fmts = ast_fileexists(filename2, NULL, NULL); + if (fmts > 0) + fmts &= AST_FORMAT_AUDIO_MASK; } if (fmts < 1) { ast_log(LOG_WARNING, "File %s does not exist in any format\n", filename); @@ -530,30 +533,35 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *fil */ int fd = -1; int fmts = -1; + unsigned int format; char filename2[256]; char lang2[MAX_LANGUAGE]; - /* XXX H.263 only XXX */ - char *fmt = "h263"; - if (!ast_strlen_zero(preflang)) { - snprintf(filename2, sizeof(filename2), "%s/%s", preflang, filename); - fmts = ast_fileexists(filename2, fmt, NULL); + const char *fmt; + for (format = AST_FORMAT_MAX_AUDIO << 1; format <= AST_FORMAT_MAX_VIDEO; format = format << 1) { + if (!(chan->nativeformats & format)) + continue; + fmt = ast_getformatname(format); + if (!ast_strlen_zero(preflang)) { + snprintf(filename2, sizeof(filename2), "%s/%s", preflang, filename); + fmts = ast_fileexists(filename2, fmt, NULL); + if (fmts < 1) { + ast_copy_string(lang2, preflang, sizeof(lang2)); + snprintf(filename2, sizeof(filename2), "%s/%s", lang2, filename); + fmts = ast_fileexists(filename2, fmt, NULL); + } + } if (fmts < 1) { - ast_copy_string(lang2, preflang, sizeof(lang2)); - snprintf(filename2, sizeof(filename2), "%s/%s", lang2, filename); + ast_copy_string(filename2, filename, sizeof(filename2)); fmts = ast_fileexists(filename2, fmt, NULL); } + if (fmts < 1) { + continue; + } + fd = ast_filehelper(filename2, (char *)chan, fmt, ACTION_OPEN); + if (fd >= 0) + return chan->vstream; + ast_log(LOG_WARNING, "File %s has video but couldn't be opened\n", filename); } - if (fmts < 1) { - ast_copy_string(filename2, filename, sizeof(filename2)); - fmts = ast_fileexists(filename2, fmt, NULL); - } - if (fmts < 1) { - return NULL; - } - fd = ast_filehelper(filename2, (char *)chan, fmt, ACTION_OPEN); - if (fd >= 0) - return chan->vstream; - ast_log(LOG_WARNING, "File %s has video but couldn't be opened\n", filename); return NULL; } @@ -792,12 +800,14 @@ int ast_filecopy(const char *filename, const char *filename2, const char *fmt) int ast_streamfile(struct ast_channel *chan, const char *filename, const char *preflang) { struct ast_filestream *fs; - struct ast_filestream *vfs; + struct ast_filestream *vfs=NULL; + char fmt[256]; fs = ast_openstream(chan, filename, preflang); - vfs = ast_openvstream(chan, filename, preflang); + if (fs) + vfs = ast_openvstream(chan, filename, preflang); if (vfs) - ast_log(LOG_DEBUG, "Ooh, found a video stream, too\n"); + ast_log(LOG_DEBUG, "Ooh, found a video stream, too, format %s\n", ast_getformatname(vfs->fmt->format)); if (fs){ if (ast_applystream(chan, fs)) return -1; @@ -813,7 +823,7 @@ int ast_streamfile(struct ast_channel *chan, const char *filename, const char *p #endif return 0; } - ast_log(LOG_WARNING, "Unable to open %s (format %s): %s\n", filename, ast_getformatname(chan->nativeformats), strerror(errno)); + ast_log(LOG_WARNING, "Unable to open %s (format %s): %s\n", filename, ast_getformatname_multiple(fmt, sizeof(fmt), chan->nativeformats), strerror(errno)); return -1; } diff --git a/formats/Makefile b/formats/Makefile index 31f973390..42258242e 100644 --- a/formats/Makefile +++ b/formats/Makefile @@ -14,7 +14,7 @@ FORMAT_LIBS=format_gsm.so format_wav.so \ format_wav_gsm.so format_vox.so format_pcm.so format_g729.so \ format_pcm_alaw.so format_h263.so format_g726.so format_ilbc.so \ - format_sln.so format_au.so + format_sln.so format_au.so format_h264.so FORMAT_LIBS+=format_jpeg.so # diff --git a/formats/format_h264.c b/formats/format_h264.c new file mode 100644 index 000000000..e132e0fe0 --- /dev/null +++ b/formats/format_h264.c @@ -0,0 +1,281 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * 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 Save to raw, headerless h264 data. + * \arg File name extension: h264 + * \ingroup formats + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 7221 $") + +#include "asterisk/lock.h" +#include "asterisk/channel.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/sched.h" +#include "asterisk/module.h" +#include "asterisk/endian.h" + +/* Some Ideas for this code came from makeh264e.c by Jeffrey Chilton */ + +/* Portions of the conversion code are by guido@sienanet.it */ + +struct ast_filestream { + void *reserved[AST_RESERVED_POINTERS]; + /* Believe it or not, we must decode/recode to account for the + weird MS format */ + /* This is what a filestream means to us */ + FILE *f; /* Descriptor */ + unsigned int lastts; + struct ast_frame fr; /* Frame information */ + char waste[AST_FRIENDLY_OFFSET]; /* Buffer for sending frames, etc */ + char empty; /* Empty character */ + unsigned char h264[4096]; /* Two Real h264 Frames */ +}; + + +AST_MUTEX_DEFINE_STATIC(h264_lock); +static int glistcnt = 0; + +static char *name = "h264"; +static char *desc = "Raw h264 data"; +static char *exts = "h264"; + +static struct ast_filestream *h264_open(FILE *f) +{ + /* We don't have any header to read or anything really, but + if we did, it would go here. We also might want to check + and be sure it's a valid file. */ + struct ast_filestream *tmp; + unsigned int ts; + int res; + if ((res = fread(&ts, 1, sizeof(ts), f)) < sizeof(ts)) { + ast_log(LOG_WARNING, "Empty file!\n"); + return NULL; + } + + if ((tmp = malloc(sizeof(struct ast_filestream)))) { + memset(tmp, 0, sizeof(struct ast_filestream)); + if (ast_mutex_lock(&h264_lock)) { + ast_log(LOG_WARNING, "Unable to lock h264 list\n"); + free(tmp); + return NULL; + } + tmp->f = f; + tmp->fr.data = tmp->h264; + tmp->fr.frametype = AST_FRAME_VIDEO; + tmp->fr.subclass = AST_FORMAT_H264; + /* datalen will vary for each frame */ + tmp->fr.src = name; + tmp->fr.mallocd = 0; + glistcnt++; + ast_mutex_unlock(&h264_lock); + ast_update_use_count(); + } + return tmp; +} + +static struct ast_filestream *h264_rewrite(FILE *f, const char *comment) +{ + /* We don't have any header to read or anything really, but + if we did, it would go here. We also might want to check + and be sure it's a valid file. */ + struct ast_filestream *tmp; + if ((tmp = malloc(sizeof(struct ast_filestream)))) { + memset(tmp, 0, sizeof(struct ast_filestream)); + if (ast_mutex_lock(&h264_lock)) { + ast_log(LOG_WARNING, "Unable to lock h264 list\n"); + free(tmp); + return NULL; + } + tmp->f = f; + glistcnt++; + ast_mutex_unlock(&h264_lock); + ast_update_use_count(); + } else + ast_log(LOG_WARNING, "Out of memory\n"); + return tmp; +} + +static void h264_close(struct ast_filestream *s) +{ + if (ast_mutex_lock(&h264_lock)) { + ast_log(LOG_WARNING, "Unable to lock h264 list\n"); + return; + } + glistcnt--; + ast_mutex_unlock(&h264_lock); + ast_update_use_count(); + fclose(s->f); + free(s); + s = NULL; +} + +static struct ast_frame *h264_read(struct ast_filestream *s, int *whennext) +{ + int res; + int mark=0; + unsigned short len; + unsigned int ts; + /* Send a frame from the file to the appropriate channel */ + s->fr.frametype = AST_FRAME_VIDEO; + s->fr.subclass = AST_FORMAT_H264; + s->fr.offset = AST_FRIENDLY_OFFSET; + s->fr.mallocd = 0; + s->fr.data = s->h264; + if ((res = fread(&len, 1, sizeof(len), s->f)) < 1) { + return NULL; + } + len = ntohs(len); + if (len & 0x8000) { + mark = 1; + } + len &= 0x7fff; + if (len > sizeof(s->h264)) { + ast_log(LOG_WARNING, "Length %d is too long\n", len); + } + if ((res = fread(s->h264, 1, len, s->f)) != len) { + if (res) + ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + return NULL; + } + s->fr.samples = s->lastts; + s->fr.datalen = len; + s->fr.subclass |= mark; + s->fr.delivery.tv_sec = 0; + s->fr.delivery.tv_usec = 0; + if ((res = fread(&ts, 1, sizeof(ts), s->f)) == sizeof(ts)) { + s->lastts = ntohl(ts); + *whennext = s->lastts * 4/45; + } else + *whennext = 0; + return &s->fr; +} + +static int h264_write(struct ast_filestream *fs, struct ast_frame *f) +{ + int res; + unsigned int ts; + unsigned short len; + int subclass; + int mark=0; + if (f->frametype != AST_FRAME_VIDEO) { + ast_log(LOG_WARNING, "Asked to write non-video frame!\n"); + return -1; + } + subclass = f->subclass; + if (subclass & 0x1) + mark=0x8000; + subclass &= ~0x1; + if (subclass != AST_FORMAT_H264) { + ast_log(LOG_WARNING, "Asked to write non-h264 frame (%d)!\n", f->subclass); + return -1; + } + ts = htonl(f->samples); + if ((res = fwrite(&ts, 1, sizeof(ts), fs->f)) != sizeof(ts)) { + ast_log(LOG_WARNING, "Bad write (%d/4): %s\n", res, strerror(errno)); + return -1; + } + len = htons(f->datalen | mark); + if ((res = fwrite(&len, 1, sizeof(len), fs->f)) != sizeof(len)) { + ast_log(LOG_WARNING, "Bad write (%d/2): %s\n", res, strerror(errno)); + return -1; + } + if ((res = fwrite(f->data, 1, f->datalen, fs->f)) != f->datalen) { + ast_log(LOG_WARNING, "Bad write (%d/%d): %s\n", res, f->datalen, strerror(errno)); + return -1; + } + return 0; +} + +static char *h264_getcomment(struct ast_filestream *s) +{ + return NULL; +} + +static int h264_seek(struct ast_filestream *fs, long sample_offset, int whence) +{ + /* No way Jose */ + return -1; +} + +static int h264_trunc(struct ast_filestream *fs) +{ + /* Truncate file to current length */ + if (ftruncate(fileno(fs->f), ftell(fs->f)) < 0) + return -1; + return 0; +} + +static long h264_tell(struct ast_filestream *fs) +{ + /* XXX This is totally bogus XXX */ + off_t offset; + offset = ftell(fs->f); + return (offset/20)*160; +} + +int load_module() +{ + return ast_format_register(name, exts, AST_FORMAT_H264, + h264_open, + h264_rewrite, + h264_write, + h264_seek, + h264_trunc, + h264_tell, + h264_read, + h264_close, + h264_getcomment); + + +} + +int unload_module() +{ + return ast_format_unregister(name); +} + +int usecount() +{ + return glistcnt; +} + +char *description() +{ + return desc; +} + + +char *key() +{ + return ASTERISK_GPL_KEY; +} diff --git a/frame.c b/frame.c index 2558ead99..8c86a67f4 100644 --- a/frame.c +++ b/frame.c @@ -101,7 +101,7 @@ static struct ast_format_list AST_FORMAT_LIST[] = { { 1, AST_FORMAT_H261, "h261", "H.261 Video" }, /*!< Passthrough */ { 1, AST_FORMAT_H263, "h263", "H.263 Video" }, /*!< Passthrough support, see format_h263.c */ { 1, AST_FORMAT_H263_PLUS, "h263p", "H.263+ Video" }, /*!< See format_h263.c */ - { 0, 0, "nothing", "undefined" }, + { 1, AST_FORMAT_H264, "h264", "H.264 Video" }, /*!< Passthrough support, see format_h263.c */ { 0, 0, "nothing", "undefined" }, { 0, 0, "nothing", "undefined" }, { 0, 0, "nothing", "undefined" }, diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index c0815732b..ca8397e0f 100644 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -193,6 +193,8 @@ struct ast_frame { #define AST_FORMAT_ILBC (1 << 10) /*! Maximum audio format */ #define AST_FORMAT_MAX_AUDIO (1 << 15) +/*! Maximum audio mask */ +#define AST_FORMAT_AUDIO_MASK ((1 << 16)-1) /*! JPEG Images */ #define AST_FORMAT_JPEG (1 << 16) /*! PNG Images */ @@ -203,8 +205,11 @@ struct ast_frame { #define AST_FORMAT_H263 (1 << 19) /*! H.263+ Video */ #define AST_FORMAT_H263_PLUS (1 << 20) +/*! H.264 Video */ +#define AST_FORMAT_H264 (1 << 21) /*! Maximum video format */ #define AST_FORMAT_MAX_VIDEO (1 << 24) +#define AST_FORMAT_VIDEO_MASK (((1 << 25)-1) & ~(AST_FORMAT_AUDIO_MASK)) /* Control frame types */ /*! Other end has hungup */ diff --git a/rtp.c b/rtp.c index b9361ac86..b88dfacbd 100644 --- a/rtp.c +++ b/rtp.c @@ -655,6 +655,7 @@ static struct { {{1, AST_FORMAT_H261}, "video", "H261"}, {{1, AST_FORMAT_H263}, "video", "H263"}, {{1, AST_FORMAT_H263_PLUS}, "video", "h263-1998"}, + {{1, AST_FORMAT_H264}, "video", "H264"}, }; /* Static (i.e., well-known) RTP payload types for our "AST_FORMAT..."s: @@ -683,6 +684,7 @@ static struct rtpPayloadType static_RTP_PT[MAX_RTP_PT] = { [34] = {1, AST_FORMAT_H263}, [103] = {1, AST_FORMAT_H263_PLUS}, [97] = {1, AST_FORMAT_ILBC}, + [99] = {1, AST_FORMAT_H264}, [101] = {0, AST_RTP_DTMF}, [110] = {1, AST_FORMAT_SPEEX}, [111] = {1, AST_FORMAT_G726}, @@ -1515,6 +1517,7 @@ int ast_rtp_write(struct ast_rtp *rtp, struct ast_frame *_f) case AST_FORMAT_H261: case AST_FORMAT_H263: case AST_FORMAT_H263_PLUS: + case AST_FORMAT_H264: case AST_FORMAT_G723_1: case AST_FORMAT_LPC10: case AST_FORMAT_SPEEX: -- cgit v1.2.3