Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 82567) +++ channels/chan_sip.c (working copy) @@ -6810,7 +6810,10 @@ } else if (codec == AST_FORMAT_G723_1) { /* Indicate that we don't support VAD (G.723.1 annex A) */ ast_str_append(a_buf, 0, "a=fmtp:%d annexa=no\r\n", rtp_code); - } else if (codec == AST_FORMAT_ILBC) { + } else if (codec == AST_FORMAT_AMRNB) { + /* Indicate that we only support octed-aligned mode */ + ast_str_append(a_buf, 0, "a=fmtp:%d octed-aligned=1\r\n", rtp_code); + } else if (codec == AST_FORMAT_ILBC) { /* Add information about us using only 20/30 ms packetization */ ast_str_append(a_buf, 0, "a=fmtp:%d mode=%d\r\n", rtp_code, fmt.cur_ms); } Index: include/asterisk/frame.h =================================================================== --- include/asterisk/frame.h (revision 82567) +++ include/asterisk/frame.h (working copy) @@ -243,6 +243,8 @@ #define AST_FORMAT_G726 (1 << 11) /*! G.722 */ #define AST_FORMAT_G722 (1 << 12) +/*! AMR NB (narrow band) codec */ +#define AST_FORMAT_AMRNB (1 << 13) /*! Maximum audio format */ #define AST_FORMAT_MAX_AUDIO (1 << 15) /*! Maximum audio mask */ Index: main/channel.c =================================================================== --- main/channel.c (revision 82567) +++ main/channel.c (working copy) @@ -578,6 +578,8 @@ AST_FORMAT_G726_AAL2, /*! ADPCM has great sound quality and is still pretty easy to translate */ AST_FORMAT_ADPCM, + /*! AMR NB is better then GSM */ + AST_FORMAT_AMRNB, /*! Okay, we're down to vocoders now, so pick GSM because it's small and easier to translate and sounds pretty good */ AST_FORMAT_GSM, Index: main/translate.c =================================================================== --- main/translate.c (revision 82567) +++ main/translate.c (working copy) @@ -491,7 +491,7 @@ static int show_translation(int fd, int argc, char *argv[]) { -#define SHOW_TRANS 13 +#define SHOW_TRANS 14 int x, y, z; int curlen = 0, longest = 0; Index: main/rtp.c =================================================================== --- main/rtp.c (revision 82567) +++ main/rtp.c (working copy) @@ -1638,6 +1638,7 @@ {{1, AST_FORMAT_ILBC}, "audio", "iLBC"}, {{1, AST_FORMAT_G722}, "audio", "G722"}, {{1, AST_FORMAT_G726_AAL2}, "audio", "AAL2-G726-32"}, + {{1, AST_FORMAT_AMRNB}, "audio", "AMR"}, {{0, AST_RTP_DTMF}, "audio", "telephone-event"}, {{0, AST_RTP_CISCO_DTMF}, "audio", "cisco-telephone-event"}, {{0, AST_RTP_CN}, "audio", "CN"}, @@ -1683,6 +1684,8 @@ [26] = {1, AST_FORMAT_JPEG}, [31] = {1, AST_FORMAT_H261}, [34] = {1, AST_FORMAT_H263}, + /* from 96 to 127 are the dynamic payload types */ + [96] = {1, AST_FORMAT_AMRNB}, [97] = {1, AST_FORMAT_ILBC}, [98] = {1, AST_FORMAT_H263_PLUS}, [99] = {1, AST_FORMAT_H264}, Index: main/frame.c =================================================================== --- main/frame.c (revision 82567) +++ main/frame.c (working copy) @@ -117,6 +117,7 @@ { 1, AST_FORMAT_ILBC, "ilbc", "iLBC", 50, 30, 30, 30, 30 }, /*!< codec_ilbc.c */ /* inc=30ms - workaround */ { 1, AST_FORMAT_G726_AAL2, "g726aal2", "G.726 AAL2", 40, 10, 300, 10, 20 }, /*!< codec_g726.c */ { 1, AST_FORMAT_G722, "g722", "G722"}, /*!< G722 Passthrough */ + { 1, AST_FORMAT_AMRNB, "amr", "AMR NB", 33, 20, 200, 0, 20}, /*!< AMR NB Passthrough */ { 0, AST_FORMAT_MAX_AUDIO, "maxaudio", "Maximum audio format" }, { 1, AST_FORMAT_JPEG, "jpeg", "JPEG image"}, /*!< See format_jpeg.c */ { 1, AST_FORMAT_PNG, "png", "PNG image"}, /*!< PNG Image format */ @@ -627,7 +628,7 @@ ast_cli(fd, "--------------------------------------------------------------------------------\n"); if ((argc == 3) || (!strcasecmp(argv[3],"audio"))) { found = 1; - for (i=0;i<13;i++) { + for (i=0;i<14;i++) { snprintf(hex,25,"(0x%x)",1< 0) { + /* enocder mode */ + enc_mode = (data[0] >> 3) & 0x0F; + frame_size = AmrFrameSize[enc_mode]; + + /* AMR frames always have 20ms of speech = 160 samples */ + if (frame_size) { + samples = samples + 160; + } + + /* first bit in ToC tells as if this is the last ToC byte */ + if ( !(data[0] & 0x80) ) { + /* this was the last ToC byte */ + return samples; + } + data++; + len++; + } + /* todo: how can we signal an error while decoding the frame structure? */ + return samples; +} + int ast_codec_get_samples(struct ast_frame *f) { int samples=0; @@ -1384,6 +1435,9 @@ case AST_FORMAT_G726_AAL2: samples = f->datalen * 2; break; + case AST_FORMAT_AMRNB: + samples = amr_samples(f->data, f->datalen); + break; default: ast_log(LOG_WARNING, "Unable to calculate samples for format %s\n", ast_getformatname(f->subclass)); } @@ -1394,7 +1448,7 @@ { int len = 0; - /* XXX Still need speex, g723, and lpc10 XXX */ + /* XXX Still need speex, g723, AMR and lpc10 XXX */ switch(format) { case AST_FORMAT_G723_1: len = (samples / 240) * 20; Index: formats/format_amrnb.c =================================================================== --- formats/format_amrnb.c (revision 0) +++ formats/format_amrnb.c (revision 0) @@ -0,0 +1,214 @@ +/* + * 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 AMR samples into AMR format as specified in RFC 4867 + * \note This is not an encoder/decoder. It only reads/writes the AMR frames + * to/from a file - thus, an AMR license is not required. + * \arg Extensions: amr + * \ingroup formats + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 0 $") + +#include +#include +#include +#include +#include +#include +#include +#include + +#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" + +#define BUF_SIZE 33 /* 1 AMR frames mode 7 + ToC + CMR */ +#define AMR_SAMPLES 160 + +#define AMRNB_MAGIC_NUMBER "#!AMR\n" + +/* taken from RFC 4867 */ +static int AmrFrameSize[] = { 12, /* mode 0 = 95 bits */ + 13, /* mode 1 = 103 bits */ + 15, /* mode 2 = 118 bits */ + 17, /* mode 3 = 134 bits */ + 19, /* mode 4 = 148 bits */ + 20, /* mode 5 = 159 bits */ + 26, /* mode 6 = 204 bits */ + 31, /* mode 7 = 244 bits */ + 5, /* mode 8 = 39 bits */ + 0, /* mode 9 = not defined */ + 0, /* mode 10 = not defined */ + 0, /* mode 11 = not defined */ + 0, /* mode 12 = not defined */ + 0, /* mode 13 = not defined */ + 0, /* mode 14 = not defined */ + 0};/* mode 15 = not defined */ + +static struct ast_frame *amrnb_read(struct ast_filestream *fs, int *whennext) +{ + int res, frame_size, enc_mode; + off_t offset = ftello(fs->f); + char file_header[sizeof(AMRNB_MAGIC_NUMBER)]; + + if (offset == 0) { + /* check file header */ + if ((res = fread(file_header, 1, sizeof(AMRNB_MAGIC_NUMBER), fs->f)) != sizeof(AMRNB_MAGIC_NUMBER)) { + ast_log(LOG_ERROR, "amrnb_read: Can't read file header (%s)!\n", strerror(errno)); + return NULL; + } + if (strncmp(file_header, AMRNB_MAGIC_NUMBER, sizeof(AMRNB_MAGIC_NUMBER))) { + ast_log(LOG_ERROR, "amrnb_read: AMR file misses AMR header (magic number)!\n"); + return NULL; + } + } + + /* Send a frame from the file to the appropriate channel */ + fs->fr.frametype = AST_FRAME_VOICE; + fs->fr.subclass = AST_FORMAT_AMRNB; + fs->fr.mallocd = 0; + fs->fr.samples = AMR_SAMPLES; + AST_FRAME_SET_BUFFER(&fs->fr, fs->buf, AST_FRIENDLY_OFFSET, BUF_SIZE); + /* read ToC byte */ + if ((res = fread(fs->fr.data, 1, 1, fs->f)) != 1) { + ast_log(LOG_ERROR, "amrnb_read: Can't read ToC byte (%s)!\n", strerror(errno)); + return NULL; + } + enc_mode = ( ((char*)(fs->fr.data))[0] >> 3) & 0x0F; + frame_size = AmrFrameSize[enc_mode]; + if ((res = fread(fs->fr.data+1, 1, frame_size, fs->f)) != frame_size) { + ast_log(LOG_ERROR, "amrnb_read: Short read (%d) instead of %d (%s)!\n", res, frame_size, strerror(errno)); + return NULL; + } + fs->fr.datalen = frame_size+1; + *whennext = fs->fr.samples; + return &fs->fr; +} + +static int amrnb_write(struct ast_filestream *fs, struct ast_frame *f) +{ + int res; + unsigned char *data = f->data; + int len = f->datalen; + int enc_mode, frame_size, frame_offset=0, frames=0,i; + off_t offset = ftello(fs->f); + + if (offset == 0) { + /* write file header */ + if ( (res = fwrite(AMRNB_MAGIC_NUMBER, 1, sizeof(AMRNB_MAGIC_NUMBER), fs->f)) ) { + ast_log(LOG_ERROR, "amrnb_write: Can't write file header (%s)!\n", strerror(errno)); + return -1; + } + } + + if (f->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "amrnb_write: Asked to write non-voice frame!\n"); + return -1; + } + if (f->subclass != AST_FORMAT_AMRNB) { + ast_log(LOG_WARNING, "amrnb_write: Asked to write non-AMRNB frame (%d)!\n", f->subclass); + return -1; + } + if (!data || !(len>=6)) { /* at least 6 bytes is the VAD frame */ + ast_log(LOG_WARNING, "amrnb_write: empty buffer!\n"); + return -1; + } + /* we asume RTP octed aligned format (RFC 4867 section 4.4). + Thus, skip first octed (CMR) */ + data++; + len--; + + /* get the number of frames in buffer */ + while (len > 0) { + frames++; + /* first bit in ToC tells as if this is the last ToC byte */ + if ( !(data[0] & 0x80) ) { + /* this was the last ToC byte */ + break; + } + len--; + data++; + } + + /* reset pointer */ + data = f->data + 1; /* data always points to beginning of ToC */ + len = f->datalen - 1; /* len is always the size of ToC+frames */ + + /* convert from RTP octed aligned format to AMR file format */ + frame_offset = frames; + for (i=0; i> 3) & 0x0F; + frame_size = AmrFrameSize[enc_mode]; + + if (frame_offset + frame_size > len) { + ast_log(LOG_ERROR, "amrnb_write: error decoding frame, exceeding buffer!\n"); + return -1; + } + + /* write ToC byte frame to file */ + if ((res = fwrite(data+i, 1, 1, fs->f)) != 1) { + ast_log(LOG_ERROR, "amrnb_write: error writing ToC byte to file: %s\n", strerror(errno)); + return -1; + } + /* write frame to file */ + if ((res = fwrite(data+frame_offset, 1, frame_size, fs->f)) != frame_size) { + ast_log(LOG_WARNING, "amrnb_write: Bad write, only %d of %d bytes: %s\n", res, frame_size, strerror(errno)); + return -1; + } + frame_offset += frame_size; + } + return 0; +} + +static const struct ast_format amr_f = { + .name = "amr nb", + .exts = "amr", + .format = AST_FORMAT_AMRNB, + .open = NULL, /* check if file starts with magic number */ + .rewrite = NULL, + .write = amrnb_write, /* write to file */ + .seek = NULL, /* seek number of smaples into file */ + .trunc = NULL, /* truncate file at current position */ + .tell = NULL, /* tell current position */ + .read = amrnb_read, /* read from file and report when to get the next frame (in samples) */ + .close = NULL, /* close the file */ + .buf_size = BUF_SIZE + AST_FRIENDLY_OFFSET, +}; + +static int load_module(void) +{ + return ast_format_register(&amr_f); +} + +static int unload_module(void) +{ + return ast_format_unregister(amr_f.name); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "AMR NB RFC 4867 file format");