/* * format_base64.c -- file format for base64_wav49 * Copyright (c) 2004-2007 Anthony Minessale II * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * (derived from format_wav_gsm.c in the asterisk distribution) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #else #include #endif #include /* Some Ideas for this code came from makewave.c by Jeffrey Chilton */ /* Portions of the conversion code are by guido@sienanet.it */ /* begin binary data: */ char msgsm_silence[] = /* 65 */ {0x48,0x17,0xD6,0x84,0x02,0x80,0x24,0x49,0x92,0x24,0x89,0x02,0x80,0x24,0x49 ,0x92,0x24,0x89,0x02,0x80,0x24,0x49,0x92,0x24,0x89,0x02,0x80,0x24,0x49,0x92 ,0x24,0x09,0x82,0x74,0x61,0x4D,0x28,0x00,0x48,0x92,0x24,0x49,0x92,0x28,0x00 ,0x48,0x92,0x24,0x49,0x92,0x28,0x00,0x48,0x92,0x24,0x49,0x92,0x28,0x00,0x48 ,0x92,0x24,0x49,0x92,0x00}; /* end binary data. size = 65 bytes */ static const char c64[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; #define B64BUFFLEN 1024 static int b64encode(struct ast_filestream *fs); /* borrow at least enuf to see the filename */ struct ast_format { /* Name of format */ char name[80]; /* Extensions (separated by | if more than one) this format can read. First is assumed for writing (e.g. .mp3) */ char exts[80]; }; struct real_ast_filestream { /* Everybody reserves a block of AST_RESERVED_POINTERS pointers for us */ struct ast_format *fmt; int flags; mode_t mode; char *filename; }; #define BASE64_STRING_LEN 256 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 */ int fd; /* Descriptor */ int bytes; struct ast_frame fr; /* Frame information */ char waste[AST_FRIENDLY_OFFSET]; /* Buffer for sending frames, etc */ char empty; /* Empty character */ unsigned char gsm[66]; /* Two Real GSM Frames */ int foffset; int secondhalf; /* Are we on the second half */ struct timeval last; char otherfilename[BASE64_STRING_LEN]; char realfilename[BASE64_STRING_LEN]; int otherfd; int writing; }; AST_MUTEX_DEFINE_STATIC(wav_lock); static int glistcnt = 0; static char *name = "b64"; static char *desc = "Microsoft WAV format (Proprietary GSM) (BASE64 Encoded)"; static char *exts = "b64"; #if __BYTE_ORDER == __LITTLE_ENDIAN #define htoll(b) (b) #define htols(b) (b) #define ltohl(b) (b) #define ltohs(b) (b) #else #if __BYTE_ORDER == __BIG_ENDIAN #define htoll(b) \ (((((b) ) & 0xFF) << 24) | \ ((((b) >> 8) & 0xFF) << 16) | \ ((((b) >> 16) & 0xFF) << 8) | \ ((((b) >> 24) & 0xFF) )) #define htols(b) \ (((((b) ) & 0xFF) << 8) | \ ((((b) >> 8) & 0xFF) )) #define ltohl(b) htoll(b) #define ltohs(b) htols(b) #else #error "Endianess not defined" #endif #endif static int check_header(int fd) { int type, size, formtype; int fmt, hsize, fact; short format, chans; int freq; int data; if (read(fd, &type, 4) != 4) { ast_log(LOG_WARNING, "Read failed (type)\n"); return -1; } if (read(fd, &size, 4) != 4) { ast_log(LOG_WARNING, "Read failed (size)\n"); return -1; } size = ltohl(size); if (read(fd, &formtype, 4) != 4) { ast_log(LOG_WARNING, "Read failed (formtype)\n"); return -1; } if (memcmp(&type, "RIFF", 4)) { ast_log(LOG_WARNING, "Does not begin with RIFF\n"); return -1; } if (memcmp(&formtype, "WAVE", 4)) { ast_log(LOG_WARNING, "Does not contain WAVE\n"); return -1; } if (read(fd, &fmt, 4) != 4) { ast_log(LOG_WARNING, "Read failed (fmt)\n"); return -1; } if (memcmp(&fmt, "fmt ", 4)) { ast_log(LOG_WARNING, "Does not say fmt\n"); return -1; } if (read(fd, &hsize, 4) != 4) { ast_log(LOG_WARNING, "Read failed (formtype)\n"); return -1; } if (ltohl(hsize) != 20) { ast_log(LOG_WARNING, "Unexpected header size %d\n", ltohl(hsize)); return -1; } if (read(fd, &format, 2) != 2) { ast_log(LOG_WARNING, "Read failed (format)\n"); return -1; } if (ltohs(format) != 49) { ast_log(LOG_WARNING, "Not a GSM file %d\n", ltohs(format)); return -1; } if (read(fd, &chans, 2) != 2) { ast_log(LOG_WARNING, "Read failed (format)\n"); return -1; } if (ltohs(chans) != 1) { ast_log(LOG_WARNING, "Not in mono %d\n", ltohs(chans)); return -1; } if (read(fd, &freq, 4) != 4) { ast_log(LOG_WARNING, "Read failed (freq)\n"); return -1; } if (ltohl(freq) != 8000) { ast_log(LOG_WARNING, "Unexpected freqency %d\n", ltohl(freq)); return -1; } /* Ignore the byte frequency */ if (read(fd, &freq, 4) != 4) { ast_log(LOG_WARNING, "Read failed (X_1)\n"); return -1; } /* Ignore the two weird fields */ if (read(fd, &freq, 4) != 4) { ast_log(LOG_WARNING, "Read failed (X_2/X_3)\n"); return -1; } /* Ignore the byte frequency */ if (read(fd, &freq, 4) != 4) { ast_log(LOG_WARNING, "Read failed (Y_1)\n"); return -1; } /* Check for the word fact */ if (read(fd, &fact, 4) != 4) { ast_log(LOG_WARNING, "Read failed (fact)\n"); return -1; } if (memcmp(&fact, "fact", 4)) { ast_log(LOG_WARNING, "Does not say fact\n"); return -1; } /* Ignore the "fact value" */ if (read(fd, &fact, 4) != 4) { ast_log(LOG_WARNING, "Read failed (fact header)\n"); return -1; } if (read(fd, &fact, 4) != 4) { ast_log(LOG_WARNING, "Read failed (fact value)\n"); return -1; } /* Check for the word data */ if (read(fd, &data, 4) != 4) { ast_log(LOG_WARNING, "Read failed (data)\n"); return -1; } if (memcmp(&data, "data", 4)) { ast_log(LOG_WARNING, "Does not say data\n"); return -1; } /* Ignore the data length */ if (read(fd, &data, 4) != 4) { ast_log(LOG_WARNING, "Read failed (data)\n"); return -1; } return 0; } static int update_header(int fd) { off_t cur,end,bytes; int datalen,filelen; cur = lseek(fd, 0, SEEK_CUR); end = lseek(fd, 0, SEEK_END); /* in a gsm WAV, data starts 60 bytes in */ bytes = end - 60; datalen = htoll(bytes); filelen = htoll(52 + ((bytes + 1) & ~0x1)); if (cur < 0) { ast_log(LOG_WARNING, "Unable to find our position\n"); return -1; } if (lseek(fd, 4, SEEK_SET) != 4) { ast_log(LOG_WARNING, "Unable to set our position\n"); return -1; } if (write(fd, &filelen, 4) != 4) { ast_log(LOG_WARNING, "Unable to set write file size\n"); return -1; } if (lseek(fd, 56, SEEK_SET) != 56) { ast_log(LOG_WARNING, "Unable to set our position\n"); return -1; } if (write(fd, &datalen, 4) != 4) { ast_log(LOG_WARNING, "Unable to set write datalen\n"); return -1; } if (lseek(fd, cur, SEEK_SET) != cur) { ast_log(LOG_WARNING, "Unable to return to position\n"); return -1; } return 0; } static int write_header(int fd) { unsigned int hz=htoll(8000); unsigned int bhz = htoll(1625); unsigned int hs = htoll(20); unsigned short fmt = htols(49); unsigned short chans = htols(1); unsigned int fhs = htoll(4); unsigned int x_1 = htoll(65); unsigned short x_2 = htols(2); unsigned short x_3 = htols(320); unsigned int y_1 = htoll(20160); unsigned int size = htoll(0); /* Write a GSM header, ignoring sizes which will be filled in later */ if (write(fd, "RIFF", 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &size, 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, "WAVEfmt ", 8) != 8) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &hs, 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &fmt, 2) != 2) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &chans, 2) != 2) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &hz, 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &bhz, 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &x_1, 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &x_2, 2) != 2) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &x_3, 2) != 2) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, "fact", 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &fhs, 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &y_1, 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, "data", 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } if (write(fd, &size, 4) != 4) { ast_log(LOG_WARNING, "Unable to write header\n"); return -1; } return 0; } static struct ast_filestream *wav_open(int fd) { struct ast_filestream *tmp; if ((tmp = malloc(sizeof(struct ast_filestream)))) { memset(tmp, 0, sizeof(struct ast_filestream)); if (ast_mutex_lock(&wav_lock)) { ast_log(LOG_WARNING, "Unable to lock wav list\n"); free(tmp); return NULL; } tmp->fd = fd; tmp->fr.data = tmp->gsm; tmp->fr.frametype = AST_FRAME_VOICE; tmp->fr.subclass = AST_FORMAT_GSM; /* datalen will vary for each frame */ tmp->fr.src = name; tmp->fr.mallocd = 0; tmp->secondhalf = 0; glistcnt++; ast_mutex_unlock(&wav_lock); ast_update_use_count(); } return tmp; } static struct ast_filestream *wav_rewrite(int fd, 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 (write_header(fd)) { free(tmp); return NULL; } if (ast_mutex_lock(&wav_lock)) { ast_log(LOG_WARNING, "Unable to lock wav list\n"); free(tmp); return NULL; } tmp->fd = fd; glistcnt++; ast_mutex_unlock(&wav_lock); ast_update_use_count(); } else ast_log(LOG_WARNING, "Out of memory\n"); return tmp; } static void wav_close(struct ast_filestream *s) { char zero = 0; ast_mutex_unlock(&wav_lock); glistcnt--; ast_update_use_count(); if(s->writing) { /* Pad to even length */ if (s->bytes & 0x1) write(s->fd, &zero, 1); b64encode(s); } if(s->otherfd) close(s->otherfd); if(!ast_strlen_zero(s->otherfilename)) unlink(s->otherfilename); free(s); s = NULL; } static struct ast_frame *wav_read(struct ast_filestream *fs, int *whennext) { int res; char msdata[66]; unsigned char buf[B64BUFFLEN]; char bound[B64BUFFLEN]; FILE *f,*of; char *bp=NULL,*ep=NULL; char bbegin[128],bend[128]; int header=0; int thisisit=0; char l64[256]; int b=0, c=0, l=0, i=0,x=0; int done=0; if(ast_strlen_zero(fs->otherfilename)) { lseek(fs->fd,0,SEEK_SET); if(!(f=fdopen(fs->fd,"rb"))) { return NULL; } res = 0; /* look for the beginning of the base64 data */ while(!feof(f) && !done) { fscanf(f,"%s\n",buf); if(!bp) { strncpy(bound,buf,B64BUFFLEN); if((bp = strstr(bound,"boundary=")) || (bp = strstr(bound,"BOUNDARY="))) { bp += 9; if(*bp == '"') *bp++; if((ep=strchr(bp,'"'))) *ep = '\0'; snprintf(bbegin,128,"--%s",bp); snprintf(bend,128,"%s--",bbegin); } else continue; } if(!strcmp(buf,bbegin)) { header++; while(!feof(f)) { fscanf(f,"%s\n",buf); if(strstr(buf,"base64") || strstr(buf,"BASE64")) thisisit++; if(thisisit && strlen(buf) == 72) { snprintf(fs->otherfilename,128,"/tmp/%d%d.WAV",getpid(),getppid()); if(!(of=fopen(fs->otherfilename,"w"))) { ast_log(LOG_ERROR,"Error Opening TMP file....\n"); fclose(f); return NULL; } for (i=0; i<256; i++) l64[i] = -1; for (i=0; i<64; i++) l64[(int)c64[i]] = i; while(!feof(f)) { for(x=0;x= 8) fputc((b>>(l-=8))%256,of); } fscanf(f,"%s\n",buf); if(!strcmp(buf,bend)) { done=1; break; printf("DONE\n"); } } fclose(of); fclose(f); close(fs->fd); if(!(fs->fd = open(fs->otherfilename,O_RDONLY))) { return NULL; } if (check_header(fs->fd)) { return NULL; } break; } } } } } /* Send a frame from the file to the appropriate channel */ fs->fr.frametype = AST_FRAME_VOICE; fs->fr.subclass = AST_FORMAT_GSM; fs->fr.offset = AST_FRIENDLY_OFFSET; fs->fr.samples = 160; fs->fr.datalen = 33; fs->fr.mallocd = 0; if (fs->secondhalf) { /* Just return a frame based on the second GSM frame */ fs->fr.data = fs->gsm + 33; } else { if ((res = read(fs->fd, msdata, 65)) != 65) { if (res && (res != 1)) ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); return NULL; } /* Convert from MS format to two real GSM frames */ conv65(msdata, fs->gsm); fs->fr.data = fs->gsm; } fs->secondhalf = !fs->secondhalf; *whennext = 160; return &fs->fr; } static int wav_write(struct ast_filestream *fs, struct ast_frame *f) { int res; char msdata[66]; int len =0; int alreadyms=0; int fd=0; /* little trick to get at the secret data at the top of my fs struct */ struct real_ast_filestream *realfs = (struct real_ast_filestream *) fs; if (f->frametype != AST_FRAME_VOICE) { ast_log(LOG_WARNING, "Asked to write non-voice frame!\n"); return -1; } if (f->subclass != AST_FORMAT_GSM) { ast_log(LOG_WARNING, "Asked to write non-GSM frame (%d)!\n", f->subclass); return -1; } if(!fs->otherfd) { /* this is stupid but we cant keep updating the header cos we want to encode this file and once you start encoding you cant go back without starting the encoding over again so we need to write the file in it's entirity somplace else then when we are 100% done we can go back and write the whole encoded file to the real fd why can't they put the header at the end and call it a footer like zip does sigh.. */ snprintf(fs->realfilename,BASE64_STRING_LEN,"%s.%s",realfs->filename,realfs->fmt->exts); snprintf(fs->otherfilename,BASE64_STRING_LEN,"%s.%s._b64data_",realfs->filename,realfs->fmt->exts); if ((fd = open(fs->otherfilename, O_WRONLY | O_TRUNC | O_CREAT, 0600)) < 0) { ast_log(LOG_WARNING, "I/O Error opening %s\n", fs->otherfilename); return -1; } if (write_header(fd)) { ast_log(LOG_WARNING, "I/O Error opening %s\n", fs->otherfilename); close(fd); unlink(fs->otherfilename); return -1; } fs->otherfd=fs->fd; fs->fd=fd; fs->writing=1; } if (!(f->datalen % 65)) alreadyms = 1; while(len < f->datalen) { if (alreadyms) { fs->secondhalf = 0; if ((res = write(fs->fd, f->data + len, 65)) != 65) { ast_log(LOG_WARNING, "Bad write (%d/65): %s\n", res, strerror(errno)); return -1; } fs->bytes += 65; update_header(fs->fd); len += 65; } else { if (fs->secondhalf) { memcpy(fs->gsm + 33, f->data + len, 33); conv66(fs->gsm, msdata); if ((res = write(fs->fd, msdata, 65)) != 65) { ast_log(LOG_WARNING, "Bad write (%d/65): %s\n", res, strerror(errno)); return -1; } fs->bytes += 65; update_header(fs->fd); } else { /* Copy the data and do nothing */ memcpy(fs->gsm, f->data + len, 33); } fs->secondhalf = !fs->secondhalf; len += 33; } } return 0; } static int wav_seek(struct ast_filestream *fs, long sample_offset, int whence) { off_t offset=0,distance,cur,min,max; min = 60; cur = lseek(fs->fd, 0, SEEK_CUR); max = lseek(fs->fd, 0, SEEK_END); /* I'm getting sloppy here, I'm only going to go to even splits of the 2 * frames, if you want tighter cuts use format_gsm, format_pcm, or format_wav */ distance = (sample_offset/320) * 65; if(whence == SEEK_SET) offset = distance + min; else if(whence == SEEK_CUR || whence == SEEK_FORCECUR) offset = distance + cur; else if(whence == SEEK_END) offset = max - distance; // always protect against seeking past end of header offset = (offset < min)?min:offset; if (whence != SEEK_FORCECUR) { offset = (offset > max)?max:offset; } else if (offset > max) { int i; lseek(fs->fd, 0, SEEK_END); for (i=0; i< (offset - max) / 65; i++) { write(fs->fd, msgsm_silence, 65); } } fs->secondhalf = 0; return lseek(fs->fd, offset, SEEK_SET); } static int wav_trunc(struct ast_filestream *fs) { if(ftruncate(fs->fd, lseek(fs->fd, 0, SEEK_CUR))) return -1; return update_header(fs->fd); } static long wav_tell(struct ast_filestream *fs) { off_t offset; offset = lseek(fs->fd, 0, SEEK_CUR); /* since this will most likely be used later in play or record, lets stick * to that level of resolution, just even frames boundaries */ return (offset - 52)/65*320; } static char *wav_getcomment(struct ast_filestream *s) { return NULL; } static int b64encode(struct ast_filestream *fs) { int x=0,y=0,bytes=0,ilen=0; unsigned int b=0,l=0; unsigned char in[B64BUFFLEN]; unsigned char out[B64BUFFLEN+512]; char header[B64BUFFLEN]; char *filename=NULL,*seekp; int fd=0; char *bound = "XXXX_boundary_XXXX"; if(!(fs->otherfd && fs->fd)) { ast_log(LOG_ERROR,"ERROR\n"); return -1; } /* this is the tmp file that is assumed to be completed close it and reopen it for reading... */ close(fs->fd); if(!(fs->fd=open(fs->otherfilename, O_RDONLY))) { ast_log(LOG_ERROR,"failure %s %d %s\n",name,fd,strerror(errno)); return -1; } seekp = filename = fs->realfilename; while(seekp && (seekp=strchr(seekp,'/'))) { *seekp++; filename = seekp; } /* this is the file asterisk knows about, we will insert the encoded data here */ /* we dont add too many headers so if you send it over email it will obtain them from the mailer itself*/ lseek(fs->otherfd,0,SEEK_SET); snprintf(header,B64BUFFLEN,"MIME-Version: 1.0\nSubject: Sound File %s.wav\nContent-Type: multipart/mixed; boundary=\"%s\"\n\n--%s\nContent-Transfer-Encoding: base64\nContent-Description: Sound attachment.\nContent-Disposition: attachment; filename=\"%s.wav\"\nContent-Type: audio/wav\n\n",filename,bound,bound,filename); write(fs->otherfd,header,strlen(header)); while((ilen=read(fs->fd, in,B64BUFFLEN))) { for(x=0;x= 6) { out[bytes++] = c64[(b>>(l-=6))%64]; if(++y!=72) continue; out[bytes++] = '\n'; y=0; } } if (write(fs->otherfd,&out, bytes) != bytes) { return -1; } else bytes=0; } if (l > 0) out[bytes++] = c64[((b%16)<<(6-l))%64]; if (l != 0) while (l < 6) out[bytes++] = '=', l += 2; if (write(fs->otherfd,&out, bytes) != bytes) { return -1; } snprintf(header,B64BUFFLEN,"\n\n--%s--\n.\n",bound); write(fs->otherfd,header,strlen(header)); return 0; } int load_module() { return ast_format_register(name, exts, AST_FORMAT_GSM, wav_open, wav_rewrite, wav_write, wav_seek, wav_trunc, wav_tell, wav_read, wav_close, wav_getcomment); } int unload_module() { return ast_format_unregister(name); } int usecount() { int res; if (ast_mutex_lock(&wav_lock)) { ast_log(LOG_WARNING, "Unable to lock wav list\n"); return -1; } res = glistcnt; ast_mutex_unlock(&wav_lock); return res; } char *description() { return desc; } char *key() { return ASTERISK_GPL_KEY; }