1#include <math.h> 2#include <vorbis/codec.h> 3#include <vorbis/vorbisenc.h> 4#include <tcl.h> 5#include "snack.h" 6#include <stdlib.h> 7#include <time.h> 8 9#if defined(__WIN32__) 10# include <io.h> 11# include <fcntl.h> 12# define WIN32_LEAN_AND_MEAN 13# include <windows.h> 14# undef WIN32_LEAN_AND_MEAN 15# define EXPORT(a,b) __declspec(dllexport) a b 16BOOL APIENTRY 17DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved) 18{ 19 return TRUE; 20} 21#else 22# define EXPORT(a,b) a b 23#endif 24 25/* vorbisfile.h */ 26 27/******************************************************************** 28 * * 29 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * 30 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * 31 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * 32 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * 33 * * 34 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * 35 * by the XIPHOPHORUS Company http://www.xiph.org/ * 36 * * 37 ******************************************************************** 38 39 function: stdio-based convenience library for opening/seeking/decoding 40 last mod: $Id: vorbisfile.h,v 1.17 2002/03/07 03:41:03 xiphmont Exp $ 41 42 ********************************************************************/ 43 44#ifndef _OV_FILE_H_ 45#define _OV_FILE_H_ 46 47#ifdef __cplusplus 48extern "C" 49{ 50#endif /* __cplusplus */ 51 52#include <stdio.h> 53#include "vorbis/codec.h" 54 55/* The function prototypes for the callbacks are basically the same as for 56 * the stdio functions fread, fseek, fclose, ftell. 57 * The one difference is that the FILE * arguments have been replaced with 58 * a void * - this is to be used as a pointer to whatever internal data these 59 * functions might need. In the stdio case, it's just a FILE * cast to a void * 60 * 61 * If you use other functions, check the docs for these functions and return 62 * the right values. For seek_func(), you *MUST* return -1 if the stream is 63 * unseekable 64 */ 65typedef struct { 66 size_t (*read_func) (void *ptr, size_t size, size_t nmemb, void *datasource); 67 int (*seek_func) (void *datasource, ogg_int64_t offset, int whence); 68 int (*close_func) (void *datasource); 69 long (*tell_func) (void *datasource); 70} ov_callbacks; 71 72#define NOTOPEN 0 73#define PARTOPEN 1 74#define OPENED 2 75#define STREAMSET 3 76#define INITSET 4 77 78typedef struct OggVorbis_File { 79 Tcl_Channel datasource; /* Pointer to a FILE *, etc. */ 80 int seekable; 81 ogg_int64_t offset; 82 ogg_int64_t end; 83 ogg_sync_state oy; 84 85 /* If the FILE handle isn't seekable (eg, a pipe), only the current 86 stream appears */ 87 int links; 88 ogg_int64_t *offsets; 89 ogg_int64_t *dataoffsets; 90 long *serialnos; 91 ogg_int64_t *pcmlengths; /* overloaded to maintain binary 92 compatability; x2 size, stores both 93 beginning and end values */ 94 vorbis_info *vi; 95 vorbis_comment *vc; 96 97 /* Decoding working state local storage */ 98 ogg_int64_t pcm_offset; 99 int ready_state; 100 long current_serialno; 101 int current_link; 102 103 double bittrack; 104 double samptrack; 105 106 ogg_stream_state os; /* take physical pages, weld into a logical 107 stream of packets */ 108 vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */ 109 vorbis_block vb; /* local working space for packet->PCM decode */ 110 111 ov_callbacks callbacks; 112 113 int maxbitrate; 114 int minbitrate; 115 int nombitrate; 116 double quality; 117 Tcl_Obj *commList; 118 Tcl_Obj *vendor; 119 120} OggVorbis_File; 121 122 /* 123extern int ov_clear(OggVorbis_File *vf); 124extern int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes); 125extern int ov_open_callbacks(void *datasource, OggVorbis_File *vf, 126 char *initial, long ibytes, ov_callbacks callbacks); 127 */ 128extern int ov_clear(Tcl_Interp *interp, OggVorbis_File *vf); 129extern int ov_open(Tcl_Interp *interp, Tcl_Channel *f,OggVorbis_File *vf,char *initial,long ibytes); 130extern int ov_open_callbacks(Tcl_Interp *interp, Tcl_Channel *datasource, OggVorbis_File *vf, 131 char *initial, long ibytes, ov_callbacks callbacks); 132 133extern int ov_test(FILE *f,OggVorbis_File *vf,char *initial,long ibytes); 134extern int ov_test_callbacks(void *datasource, OggVorbis_File *vf, 135 char *initial, long ibytes, ov_callbacks callbacks); 136extern int ov_test_open(OggVorbis_File *vf); 137 138extern long ov_bitrate(OggVorbis_File *vf,int i); 139extern long ov_bitrate_instant(OggVorbis_File *vf); 140extern long ov_streams(OggVorbis_File *vf); 141extern long ov_seekable(OggVorbis_File *vf); 142extern long ov_serialnumber(OggVorbis_File *vf,int i); 143 144extern ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i); 145extern ogg_int64_t ov_pcm_total(OggVorbis_File *vf,int i); 146extern double ov_time_total(OggVorbis_File *vf,int i); 147 148extern int ov_raw_seek(OggVorbis_File *vf,ogg_int64_t pos); 149extern int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos); 150extern int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos); 151extern int ov_time_seek(OggVorbis_File *vf,double pos); 152extern int ov_time_seek_page(OggVorbis_File *vf,double pos); 153 154extern ogg_int64_t ov_raw_tell(OggVorbis_File *vf); 155extern ogg_int64_t ov_pcm_tell(OggVorbis_File *vf); 156extern double ov_time_tell(OggVorbis_File *vf); 157 158extern vorbis_info *ov_info(OggVorbis_File *vf,int link); 159extern vorbis_comment *ov_comment(OggVorbis_File *vf,int link); 160 161extern long ov_read_float(OggVorbis_File *vf,float ***pcm_channels,int samples, 162 int *bitstream); 163extern long ov_read(OggVorbis_File *vf,char *buffer,int length, 164 int bigendianp,int word,int sgned,int *bitstream); 165 166#ifdef __cplusplus 167} 168#endif /* __cplusplus */ 169 170#endif 171 172 173/* vorbisfile.c */ 174 175/******************************************************************** 176 * * 177 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * 178 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * 179 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * 180 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * 181 * * 182 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * 183 * by the XIPHOPHORUS Company http://www.xiph.org/ * 184 * * 185 ******************************************************************** 186 187 function: stdio-based convenience library for opening/seeking/decoding 188 last mod: $Id: vorbisfile.c,v 1.62 2002/07/06 04:20:03 msmith Exp $ 189 190 ********************************************************************/ 191 192#include <stdlib.h> 193#include <stdio.h> 194#include <errno.h> 195#include <string.h> 196#include <math.h> 197 198#include "vorbis/codec.h" 199#include "vorbis/vorbisfile.h" 200 201/* misc.h */ 202#ifndef _V_RANDOM_H_ 203#define _V_RANDOM_H_ 204#include "vorbis/codec.h" 205 206extern int analysis_noisy; 207 208extern void *_vorbis_block_alloc(vorbis_block *vb,long bytes); 209extern void _vorbis_block_ripcord(vorbis_block *vb); 210extern void _analysis_output(char *base,int i,float *v,int n,int bark,int dB, 211 ogg_int64_t off); 212 213#ifdef DEBUG_MALLOC 214 215#define _VDBG_GRAPHFILE "malloc.m" 216extern void *_VDBG_malloc(void *ptr,long bytes,char *file,long line); 217extern void _VDBG_free(void *ptr,char *file,long line); 218 219#ifndef MISC_C 220#undef _ogg_malloc 221#undef _ogg_calloc 222#undef _ogg_realloc 223#undef _ogg_free 224 225#define _ogg_malloc(x) _VDBG_malloc(NULL,(x),__FILE__,__LINE__) 226#define _ogg_calloc(x,y) _VDBG_malloc(NULL,(x)*(y),__FILE__,__LINE__) 227#define _ogg_realloc(x,y) _VDBG_malloc((x),(y),__FILE__,__LINE__) 228#define _ogg_free(x) _VDBG_free((x),__FILE__,__LINE__) 229#endif 230#endif 231 232#endif 233 234/* os.h */ 235#include <ogg/os_types.h> 236 237#ifndef _V_IFDEFJAIL_H_ 238# define _V_IFDEFJAIL_H_ 239 240# ifdef __GNUC__ 241# define STIN static __inline__ 242# elif _WIN32 243# define STIN static __inline 244#else 245# define STIN static 246#endif 247 248#ifndef M_PI 249# define M_PI (3.1415926536f) 250#endif 251 252#ifdef _WIN32 253# include <malloc.h> 254# define rint(x) (floor((x)+0.5f)) 255# define NO_FLOAT_MATH_LIB 256# define FAST_HYPOT(a, b) sqrt((a)*(a) + (b)*(b)) 257#endif 258 259#ifndef FAST_HYPOT 260# define FAST_HYPOT hypot 261#endif 262 263#endif 264 265#ifdef HAVE_ALLOCA_H 266# include <alloca.h> 267#endif 268 269#ifdef USE_MEMORY_H 270# include <memory.h> 271#endif 272 273#ifndef min 274# define min(x,y) ((x)>(y)?(y):(x)) 275#endif 276 277#ifndef max 278# define max(x,y) ((x)<(y)?(y):(x)) 279#endif 280 281#if defined(__i386__) && defined(__GNUC__) && !defined(__BEOS__) 282# define VORBIS_FPU_CONTROL 283/* both GCC and MSVC are kinda stupid about rounding/casting to int. 284 Because of encapsulation constraints (GCC can't see inside the asm 285 block and so we end up doing stupid things like a store/load that 286 is collectively a noop), we do it this way */ 287 288/* we must set up the fpu before this works!! */ 289 290typedef ogg_int16_t vorbis_fpu_control; 291 292static inline void vorbis_fpu_setround(vorbis_fpu_control *fpu){ 293 ogg_int16_t ret; 294 ogg_int16_t temp; 295 __asm__ __volatile__("fnstcw %0\n\t" 296 "movw %0,%%dx\n\t" 297 "orw $62463,%%dx\n\t" 298 "movw %%dx,%1\n\t" 299 "fldcw %1\n\t":"=m"(ret):"m"(temp): "dx"); 300 *fpu=ret; 301} 302 303static inline void vorbis_fpu_restore(vorbis_fpu_control fpu){ 304 __asm__ __volatile__("fldcw %0":: "m"(fpu)); 305} 306 307/* assumes the FPU is in round mode! */ 308static inline int vorbis_ftoi(double f){ /* yes, double! Otherwise, 309 we get extra fst/fld to 310 truncate precision */ 311 int i; 312 __asm__("fistl %0": "=m"(i) : "t"(f)); 313 return(i); 314} 315#endif 316 317 318#if defined(_WIN32) && !defined(__GNUC__) && !defined(__BORLANDC__) 319# define VORBIS_FPU_CONTROL 320 321typedef ogg_int16_t vorbis_fpu_control; 322 323static __inline int vorbis_ftoi(double f){ 324 int i; 325 __asm{ 326 fld f 327 fistp i 328 } 329 return i; 330} 331 332static __inline void vorbis_fpu_setround(vorbis_fpu_control *fpu){ 333} 334 335static __inline void vorbis_fpu_restore(vorbis_fpu_control fpu){ 336} 337 338#endif 339 340 341#ifndef VORBIS_FPU_CONTROL 342 343typedef int vorbis_fpu_control; 344 345static int vorbis_ftoi(double f){ 346 return (int)(f+.5); 347} 348 349/* We don't have special code for this compiler/arch, so do it the slow way */ 350# define vorbis_fpu_setround(vorbis_fpu_control) {} 351# define vorbis_fpu_restore(vorbis_fpu_control) {} 352 353#endif 354 355 356/* A 'chained bitstream' is a Vorbis bitstream that contains more than 357 one logical bitstream arranged end to end (the only form of Ogg 358 multiplexing allowed in a Vorbis bitstream; grouping [parallel 359 multiplexing] is not allowed in Vorbis) */ 360 361/* A Vorbis file can be played beginning to end (streamed) without 362 worrying ahead of time about chaining (see decoder_example.c). If 363 we have the whole file, however, and want random access 364 (seeking/scrubbing) or desire to know the total length/time of a 365 file, we need to account for the possibility of chaining. */ 366 367/* We can handle things a number of ways; we can determine the entire 368 bitstream structure right off the bat, or find pieces on demand. 369 This example determines and caches structure for the entire 370 bitstream, but builds a virtual decoder on the fly when moving 371 between links in the chain. */ 372 373/* There are also different ways to implement seeking. Enough 374 information exists in an Ogg bitstream to seek to 375 sample-granularity positions in the output. Or, one can seek by 376 picking some portion of the stream roughly in the desired area if 377 we only want coarse navigation through the stream. */ 378 379/************************************************************************* 380 * Many, many internal helpers. The intention is not to be confusing; 381 * rampant duplication and monolithic function implementation would be 382 * harder to understand anyway. The high level functions are last. Begin 383 * grokking near the end of the file */ 384 385/* read a little more data from the file/pipe into the ogg_sync framer 386*/ 387#define CHUNKSIZE 8500 /* a shade over 8k; anyone using pages well 388 over 8k gets what they deserve */ 389static long _get_data(OggVorbis_File *vf){ 390 errno=0; 391 if(vf->datasource){ 392 char *buffer=ogg_sync_buffer(&vf->oy,CHUNKSIZE); 393 /*long bytes=(vf->callbacks.read_func)(buffer,1,CHUNKSIZE,vf->datasource);*/ 394 long bytes=Tcl_Read(vf->datasource,buffer,CHUNKSIZE); 395 if(bytes>0)ogg_sync_wrote(&vf->oy,bytes); 396 if(bytes==0 && errno)return(-1); 397 return(bytes); 398 }else 399 return(0); 400} 401 402/* save a tiny smidge of verbosity to make the code more readable */ 403static void _seek_helper(OggVorbis_File *vf,ogg_int64_t offset){ 404 if(vf->datasource){ 405 TCL_SEEK(vf->datasource, offset, SEEK_SET); 406 /*(vf->callbacks.seek_func)(vf->datasource, offset, SEEK_SET);*/ 407 vf->offset=offset; 408 ogg_sync_reset(&vf->oy); 409 }else{ 410 /* shouldn't happen unless someone writes a broken callback */ 411 return; 412 } 413} 414 415/* The read/seek functions track absolute position within the stream */ 416 417/* from the head of the stream, get the next page. boundary specifies 418 if the function is allowed to fetch more data from the stream (and 419 how much) or only use internally buffered data. 420 421 boundary: -1) unbounded search 422 0) read no additional data; use cached only 423 n) search for a new page beginning for n bytes 424 425 return: <0) did not find a page (OV_FALSE, OV_EOF, OV_EREAD) 426 n) found a page at absolute offset n */ 427 428static ogg_int64_t _get_next_page(OggVorbis_File *vf,ogg_page *og, 429 ogg_int64_t boundary){ 430 if(boundary>0)boundary+=vf->offset; 431 while(1){ 432 long more; 433 434 if(boundary>0 && vf->offset>=boundary)return(OV_FALSE); 435 more=ogg_sync_pageseek(&vf->oy,og); 436 437 if(more<0){ 438 /* skipped n bytes */ 439 vf->offset-=more; 440 }else{ 441 if(more==0){ 442 /* send more paramedics */ 443 if(!boundary)return(OV_FALSE); 444 { 445 long ret=_get_data(vf); 446 if(ret==0)return(OV_EOF); 447 if(ret<0)return(OV_EREAD); 448 } 449 }else{ 450 /* got a page. Return the offset at the page beginning, 451 advance the internal offset past the page end */ 452 ogg_int64_t ret=vf->offset; 453 vf->offset+=more; 454 return(ret); 455 456 } 457 } 458 } 459} 460 461/* find the latest page beginning before the current stream cursor 462 position. Much dirtier than the above as Ogg doesn't have any 463 backward search linkage. no 'readp' as it will certainly have to 464 read. */ 465/* returns offset or OV_EREAD, OV_FAULT */ 466static ogg_int64_t _get_prev_page(OggVorbis_File *vf,ogg_page *og){ 467 ogg_int64_t begin=vf->offset; 468 ogg_int64_t end=begin; 469 ogg_int64_t ret; 470 ogg_int64_t offset=-1; 471 472 while(offset==-1){ 473 begin-=CHUNKSIZE; 474 if(begin<0) 475 begin=0; 476 _seek_helper(vf,begin); 477 while(vf->offset<end){ 478 ret=_get_next_page(vf,og,end-vf->offset); 479 if(ret==OV_EREAD)return(OV_EREAD); 480 if(ret<0){ 481 break; 482 }else{ 483 offset=ret; 484 } 485 } 486 } 487 488 /* we have the offset. Actually snork and hold the page now */ 489 _seek_helper(vf,offset); 490 ret=_get_next_page(vf,og,CHUNKSIZE); 491 if(ret<0) 492 /* this shouldn't be possible */ 493 return(OV_EFAULT); 494 495 return(offset); 496} 497 498/* finds each bitstream link one at a time using a bisection search 499 (has to begin by knowing the offset of the lb's initial page). 500 Recurses for each link so it can alloc the link storage after 501 finding them all, then unroll and fill the cache at the same time */ 502static int _bisect_forward_serialno(OggVorbis_File *vf, 503 ogg_int64_t begin, 504 ogg_int64_t searched, 505 ogg_int64_t end, 506 long currentno, 507 long m){ 508 ogg_int64_t endsearched=end; 509 ogg_int64_t next=end; 510 ogg_page og; 511 ogg_int64_t ret; 512 513 /* the below guards against garbage seperating the last and 514 first pages of two links. */ 515 while(searched<endsearched){ 516 ogg_int64_t bisect; 517 518 if(endsearched-searched<CHUNKSIZE){ 519 bisect=searched; 520 }else{ 521 bisect=(searched+endsearched)/2; 522 } 523 524 _seek_helper(vf,bisect); 525 ret=_get_next_page(vf,&og,-1); 526 if(ret==OV_EREAD)return(OV_EREAD); 527 if(ret<0 || ogg_page_serialno(&og)!=currentno){ 528 endsearched=bisect; 529 if(ret>=0)next=ret; 530 }else{ 531 searched=ret+og.header_len+og.body_len; 532 } 533 } 534 535 _seek_helper(vf,next); 536 ret=_get_next_page(vf,&og,-1); 537 if(ret==OV_EREAD)return(OV_EREAD); 538 539 if(searched>=end || ret<0){ 540 vf->links=m+1; 541 vf->offsets=_ogg_malloc((vf->links+1)*sizeof(*vf->offsets)); 542 vf->serialnos=_ogg_malloc(vf->links*sizeof(*vf->serialnos)); 543 vf->offsets[m+1]=searched; 544 }else{ 545 ret=_bisect_forward_serialno(vf,next,vf->offset, 546 end,ogg_page_serialno(&og),m+1); 547 if(ret==OV_EREAD)return(OV_EREAD); 548 } 549 550 vf->offsets[m]=begin; 551 vf->serialnos[m]=currentno; 552 return(0); 553} 554 555/* uses the local ogg_stream storage in vf; this is important for 556 non-streaming input sources */ 557static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc, 558 long *serialno,ogg_page *og_ptr){ 559 ogg_page og; 560 ogg_packet op; 561 int i,ret; 562 563 if(!og_ptr){ 564 ogg_int64_t llret=_get_next_page(vf,&og,CHUNKSIZE); 565 if(llret==OV_EREAD)return(OV_EREAD); 566 if(llret<0)return OV_ENOTVORBIS; 567 og_ptr=&og; 568 } 569 570 ogg_stream_reset_serialno(&vf->os,ogg_page_serialno(og_ptr)); 571 if(serialno)*serialno=vf->os.serialno; 572 vf->ready_state=STREAMSET; 573 574 /* extract the initial header from the first page and verify that the 575 Ogg bitstream is in fact Vorbis data */ 576 577 vorbis_info_init(vi); 578 vorbis_comment_init(vc); 579 580 i=0; 581 while(i<3){ 582 ogg_stream_pagein(&vf->os,og_ptr); 583 while(i<3){ 584 int result=ogg_stream_packetout(&vf->os,&op); 585 if(result==0)break; 586 if(result==-1){ 587 ret=OV_EBADHEADER; 588 goto bail_header; 589 } 590 if((ret=vorbis_synthesis_headerin(vi,vc,&op))){ 591 goto bail_header; 592 } 593 i++; 594 } 595 if(i<3) 596 if(_get_next_page(vf,og_ptr,CHUNKSIZE)<0){ 597 ret=OV_EBADHEADER; 598 goto bail_header; 599 } 600 } 601 return 0; 602 603 bail_header: 604 vorbis_info_clear(vi); 605 vorbis_comment_clear(vc); 606 vf->ready_state=OPENED; 607 608 return ret; 609} 610 611/* last step of the OggVorbis_File initialization; get all the 612 vorbis_info structs and PCM positions. Only called by the seekable 613 initialization (local stream storage is hacked slightly; pay 614 attention to how that's done) */ 615 616/* this is void and does not propogate errors up because we want to be 617 able to open and use damaged bitstreams as well as we can. Just 618 watch out for missing information for links in the OggVorbis_File 619 struct */ 620static void _prefetch_all_headers(OggVorbis_File *vf, ogg_int64_t dataoffset){ 621 ogg_page og; 622 int i; 623 ogg_int64_t ret; 624 625 vf->vi=_ogg_realloc(vf->vi,vf->links*sizeof(*vf->vi)); 626 vf->vc=_ogg_realloc(vf->vc,vf->links*sizeof(*vf->vc)); 627 vf->dataoffsets=_ogg_malloc(vf->links*sizeof(*vf->dataoffsets)); 628 vf->pcmlengths=_ogg_malloc(vf->links*2*sizeof(*vf->pcmlengths)); 629 630 for(i=0;i<vf->links;i++){ 631 if(i==0){ 632 /* we already grabbed the initial header earlier. Just set the offset */ 633 vf->dataoffsets[i]=dataoffset; 634 _seek_helper(vf,dataoffset); 635 636 }else{ 637 638 /* seek to the location of the initial header */ 639 640 _seek_helper(vf,vf->offsets[i]); 641 if(_fetch_headers(vf,vf->vi+i,vf->vc+i,NULL,NULL)<0){ 642 vf->dataoffsets[i]=-1; 643 }else{ 644 vf->dataoffsets[i]=vf->offset; 645 } 646 } 647 648 /* fetch beginning PCM offset */ 649 650 if(vf->dataoffsets[i]!=-1){ 651 ogg_int64_t accumulated=0; 652 long lastblock=-1; 653 int result; 654 655 ogg_stream_reset_serialno(&vf->os,vf->serialnos[i]); 656 657 while(1){ 658 ogg_packet op; 659 660 ret=_get_next_page(vf,&og,-1); 661 if(ret<0) 662 /* this should not be possible unless the file is 663 truncated/mangled */ 664 break; 665 666 if(ogg_page_serialno(&og)!=vf->serialnos[i]) 667 break; 668 669 /* count blocksizes of all frames in the page */ 670 ogg_stream_pagein(&vf->os,&og); 671 while((result=ogg_stream_packetout(&vf->os,&op))){ 672 if(result>0){ /* ignore holes */ 673 long thisblock=vorbis_packet_blocksize(vf->vi+i,&op); 674 if(lastblock!=-1) 675 accumulated+=(lastblock+thisblock)>>2; 676 lastblock=thisblock; 677 } 678 } 679 680 if(ogg_page_granulepos(&og)!=-1){ 681 /* pcm offset of last packet on the first audio page */ 682 accumulated= ogg_page_granulepos(&og)-accumulated; 683 break; 684 } 685 } 686 687 /* less than zero? This is a stream with samples trimmed off 688 the beginning, a normal occurrence; set the offset to zero */ 689 if(accumulated<0)accumulated=0; 690 691 vf->pcmlengths[i*2]=accumulated; 692 } 693 694 /* get the PCM length of this link. To do this, 695 get the last page of the stream */ 696 { 697 ogg_int64_t end=vf->offsets[i+1]; 698 _seek_helper(vf,end); 699 700 while(1){ 701 ret=_get_prev_page(vf,&og); 702 if(ret<0){ 703 /* this should not be possible */ 704 vorbis_info_clear(vf->vi+i); 705 vorbis_comment_clear(vf->vc+i); 706 break; 707 } 708 if(ogg_page_granulepos(&og)!=-1){ 709 vf->pcmlengths[i*2+1]=ogg_page_granulepos(&og)-vf->pcmlengths[i*2]; 710 break; 711 } 712 vf->offset=ret; 713 } 714 } 715 } 716} 717 718static void _make_decode_ready(OggVorbis_File *vf){ 719 if(vf->ready_state!=STREAMSET)return; 720 if(vf->seekable){ 721 vorbis_synthesis_init(&vf->vd,vf->vi+vf->current_link); 722 }else{ 723 vorbis_synthesis_init(&vf->vd,vf->vi); 724 } 725 vorbis_block_init(&vf->vd,&vf->vb); 726 vf->ready_state=INITSET; 727 return; 728} 729 730static int _open_seekable2(OggVorbis_File *vf){ 731 long serialno=vf->current_serialno; 732 ogg_int64_t dataoffset=vf->offset, end; 733 ogg_page og; 734 735 /* we're partially open and have a first link header state in 736 storage in vf */ 737 /* we can seek, so set out learning all about this file */ 738 /*(vf->callbacks.seek_func)(vf->datasource,0,SEEK_END);*/ 739 TCL_SEEK(vf->datasource, 0, SEEK_END); 740 /* vf->offset=vf->end=(vf->callbacks.tell_func)(vf->datasource);*/ 741 vf->offset=vf->end=TCL_TELL(vf->datasource); 742 743 /* We get the offset for the last page of the physical bitstream. 744 Most OggVorbis files will contain a single logical bitstream */ 745 end=_get_prev_page(vf,&og); 746 if(end<0)return(end); 747 748 /* more than one logical bitstream? */ 749 if(ogg_page_serialno(&og)!=serialno){ 750 751 /* Chained bitstream. Bisect-search each logical bitstream 752 section. Do so based on serial number only */ 753 if(_bisect_forward_serialno(vf,0,0,end+1,serialno,0)<0)return(OV_EREAD); 754 755 }else{ 756 757 /* Only one logical bitstream */ 758 if(_bisect_forward_serialno(vf,0,end,end+1,serialno,0))return(OV_EREAD); 759 760 } 761 762 /* the initial header memory is referenced by vf after; don't free it */ 763 _prefetch_all_headers(vf,dataoffset); 764 return(ov_raw_seek(vf,0)); 765} 766 767/* clear out the current logical bitstream decoder */ 768static void _decode_clear(OggVorbis_File *vf){ 769 vorbis_dsp_clear(&vf->vd); 770 vorbis_block_clear(&vf->vb); 771 vf->ready_state=OPENED; 772 773 vf->bittrack=0.f; 774 vf->samptrack=0.f; 775} 776 777/* fetch and process a packet. Handles the case where we're at a 778 bitstream boundary and dumps the decoding machine. If the decoding 779 machine is unloaded, it loads it. It also keeps pcm_offset up to 780 date (seek and read both use this. seek uses a special hack with 781 readp). 782 783 return: <0) error, OV_HOLE (lost packet) or OV_EOF 784 0) need more data (only if readp==0) 785 1) got a packet 786*/ 787 788static int _fetch_and_process_packet(OggVorbis_File *vf, 789 ogg_packet *op_in, 790 int readp){ 791 ogg_page og; 792 793 /* handle one packet. Try to fetch it from current stream state */ 794 /* extract packets from page */ 795 while(1){ 796 797 /* process a packet if we can. If the machine isn't loaded, 798 neither is a page */ 799 if(vf->ready_state==INITSET){ 800 while(1) { 801 ogg_packet op; 802 ogg_packet *op_ptr=(op_in?op_in:&op); 803 int result=ogg_stream_packetout(&vf->os,op_ptr); 804 ogg_int64_t granulepos; 805 806 op_in=NULL; 807 if(result==-1)return(OV_HOLE); /* hole in the data. */ 808 if(result>0){ 809 /* got a packet. process it */ 810 granulepos=op_ptr->granulepos; 811 if(!vorbis_synthesis(&vf->vb,op_ptr)){ /* lazy check for lazy 812 header handling. The 813 header packets aren't 814 audio, so if/when we 815 submit them, 816 vorbis_synthesis will 817 reject them */ 818 819 /* suck in the synthesis data and track bitrate */ 820 { 821 int oldsamples=vorbis_synthesis_pcmout(&vf->vd,NULL); 822 /* for proper use of libvorbis within libvorbisfile, 823 oldsamples will always be zero. */ 824 if(oldsamples)return(OV_EFAULT); 825 826 vorbis_synthesis_blockin(&vf->vd,&vf->vb); 827 vf->samptrack+=vorbis_synthesis_pcmout(&vf->vd,NULL)-oldsamples; 828 vf->bittrack+=op_ptr->bytes*8; 829 } 830 831 /* update the pcm offset. */ 832 if(granulepos!=-1 && !op_ptr->e_o_s){ 833 int link=(vf->seekable?vf->current_link:0); 834 int i,samples; 835 836 /* this packet has a pcm_offset on it (the last packet 837 completed on a page carries the offset) After processing 838 (above), we know the pcm position of the *last* sample 839 ready to be returned. Find the offset of the *first* 840 841 As an aside, this trick is inaccurate if we begin 842 reading anew right at the last page; the end-of-stream 843 granulepos declares the last frame in the stream, and the 844 last packet of the last page may be a partial frame. 845 So, we need a previous granulepos from an in-sequence page 846 to have a reference point. Thus the !op_ptr->e_o_s clause 847 above */ 848 849 if(vf->seekable && link>0) 850 granulepos-=vf->pcmlengths[link*2]; 851 if(granulepos<0)granulepos=0; /* actually, this 852 shouldn't be possible 853 here unless the stream 854 is very broken */ 855 856 samples=vorbis_synthesis_pcmout(&vf->vd,NULL); 857 858 granulepos-=samples; 859 for(i=0;i<link;i++) 860 granulepos+=vf->pcmlengths[i*2+1]; 861 vf->pcm_offset=granulepos; 862 } 863 return(1); 864 } 865 } 866 else 867 break; 868 } 869 } 870 871 if(vf->ready_state>=OPENED){ 872 if(!readp)return(0); 873 if(_get_next_page(vf,&og,-1)<0)return(OV_EOF); /* eof. 874 leave unitialized */ 875 /* bitrate tracking; add the header's bytes here, the body bytes 876 are done by packet above */ 877 vf->bittrack+=og.header_len*8; 878 879 /* has our decoding just traversed a bitstream boundary? */ 880 if(vf->ready_state==INITSET){ 881 if(vf->current_serialno!=ogg_page_serialno(&og)){ 882 _decode_clear(vf); 883 884 if(!vf->seekable){ 885 vorbis_info_clear(vf->vi); 886 vorbis_comment_clear(vf->vc); 887 } 888 } 889 } 890 } 891 892 /* Do we need to load a new machine before submitting the page? */ 893 /* This is different in the seekable and non-seekable cases. 894 895 In the seekable case, we already have all the header 896 information loaded and cached; we just initialize the machine 897 with it and continue on our merry way. 898 899 In the non-seekable (streaming) case, we'll only be at a 900 boundary if we just left the previous logical bitstream and 901 we're now nominally at the header of the next bitstream 902 */ 903 904 if(vf->ready_state!=INITSET){ 905 int link; 906 907 if(vf->ready_state<STREAMSET){ 908 if(vf->seekable){ 909 vf->current_serialno=ogg_page_serialno(&og); 910 911 /* match the serialno to bitstream section. We use this rather than 912 offset positions to avoid problems near logical bitstream 913 boundaries */ 914 for(link=0;link<vf->links;link++) 915 if(vf->serialnos[link]==vf->current_serialno)break; 916 if(link==vf->links)return(OV_EBADLINK); /* sign of a bogus 917 stream. error out, 918 leave machine 919 uninitialized */ 920 921 vf->current_link=link; 922 923 ogg_stream_reset_serialno(&vf->os,vf->current_serialno); 924 vf->ready_state=STREAMSET; 925 926 }else{ 927 /* we're streaming */ 928 /* fetch the three header packets, build the info struct */ 929 930 int ret=_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,&og); 931 if(ret)return(ret); 932 vf->current_link++; 933 link=0; 934 } 935 } 936 937 _make_decode_ready(vf); 938 } 939 ogg_stream_pagein(&vf->os,&og); 940 } 941} 942 943/* if, eg, 64 bit stdio is configured by default, this will build with 944 fseek64 */ 945static int _fseek64_wrap(FILE *f,ogg_int64_t off,int whence){ 946 if(f==NULL)return(-1); 947 return fseek(f,off,whence); 948} 949 950static int _ov_open1(Tcl_Interp *interp,Tcl_Channel *f,OggVorbis_File *vf,char *initial, 951 long ibytes, ov_callbacks callbacks){ 952 /*int offsettest=(f?callbacks.seek_func(f,0,SEEK_CUR):-1);*/ 953 int offsettest=(f?TCL_SEEK(*f,0,SEEK_CUR):-1); 954 int ret; 955 956 memset(vf,0,sizeof(*vf)-28); 957 vf->datasource=*f; 958 vf->callbacks = callbacks; 959 960 /* init the framing state */ 961 ogg_sync_init(&vf->oy); 962 963 /* perhaps some data was previously read into a buffer for testing 964 against other stream types. Allow initialization from this 965 previously read data (as we may be reading from a non-seekable 966 stream) */ 967 if(initial){ 968 char *buffer=ogg_sync_buffer(&vf->oy,ibytes); 969 memcpy(buffer,initial,ibytes); 970 ogg_sync_wrote(&vf->oy,ibytes); 971 } 972 973 /* can we seek? Stevens suggests the seek test was portable */ 974 if(offsettest!=-1)vf->seekable=1; 975 976 /* No seeking yet; Set up a 'single' (current) logical bitstream 977 entry for partial open */ 978 vf->links=1; 979 vf->vi=_ogg_calloc(vf->links,sizeof(*vf->vi)); 980 vf->vc=_ogg_calloc(vf->links,sizeof(*vf->vc)); 981 ogg_stream_init(&vf->os,-1); /* fill in the serialno later */ 982 983 /* Try to fetch the headers, maintaining all the storage */ 984 if((ret=_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,NULL))<0){ 985 vf->datasource=NULL; 986 /* ov_clear(vf);*/ 987 ov_clear(interp,vf); 988 }else if(vf->ready_state < PARTOPEN) 989 vf->ready_state=PARTOPEN; 990 return(ret); 991} 992 993static int _ov_open2(Tcl_Interp *interp,OggVorbis_File *vf){ 994 if(vf->ready_state < OPENED) 995 vf->ready_state=OPENED; 996 if(vf->seekable){ 997 int ret=_open_seekable2(vf); 998 if(ret){ 999 vf->datasource=NULL; 1000 /*ov_clear(vf);*/ 1001 ov_clear(interp,vf); 1002 } 1003 return(ret); 1004 } 1005 return 0; 1006} 1007 1008 1009/* clear out the OggVorbis_File struct */ 1010/*int ov_clear(OggVorbis_File *vf){*/ 1011int ov_clear(Tcl_Interp *interp,OggVorbis_File *vf){ 1012 if(vf){ 1013 vorbis_block_clear(&vf->vb); 1014 vorbis_dsp_clear(&vf->vd); 1015 ogg_stream_clear(&vf->os); 1016 1017 if(vf->vi && vf->links){ 1018 int i; 1019 for(i=0;i<vf->links;i++){ 1020 vorbis_info_clear(vf->vi+i); 1021 vorbis_comment_clear(vf->vc+i); 1022 } 1023 _ogg_free(vf->vi); 1024 _ogg_free(vf->vc); 1025 } 1026 if(vf->dataoffsets)_ogg_free(vf->dataoffsets); 1027 if(vf->pcmlengths)_ogg_free(vf->pcmlengths); 1028 if(vf->serialnos)_ogg_free(vf->serialnos); 1029 if(vf->offsets)_ogg_free(vf->offsets); 1030 ogg_sync_clear(&vf->oy); 1031 /* if(vf->datasource)(vf->callbacks.close_func)(vf->datasource);*/ 1032 if(vf->datasource)Tcl_Close(interp,vf->datasource); 1033 memset(vf,0,sizeof(*vf)-28); 1034 } 1035#ifdef DEBUG_LEAKS 1036 _VDBG_dump(); 1037#endif 1038 return(0); 1039} 1040 1041/* inspects the OggVorbis file and finds/documents all the logical 1042 bitstreams contained in it. Tries to be tolerant of logical 1043 bitstream sections that are truncated/woogie. 1044 1045 return: -1) error 1046 0) OK 1047*/ 1048 1049/*int ov_open_callbacks(void *f,OggVorbis_File *vf,char *initial,long ibytes, 1050 ov_callbacks callbacks){*/ 1051int ov_open_callbacks(Tcl_Interp *interp,Tcl_Channel *f,OggVorbis_File *vf,char *initial,long ibytes, 1052 ov_callbacks callbacks){ 1053 int ret=_ov_open1(interp, f,vf,initial,ibytes,callbacks); 1054 if(ret)return ret; 1055 return _ov_open2(interp, vf); 1056} 1057 1058/*int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){*/ 1059int ov_open(Tcl_Interp *interp,Tcl_Channel *f,OggVorbis_File *vf,char *initial,long ibytes){ 1060 ov_callbacks callbacks = { 1061 (size_t (*)(void *, size_t, size_t, void *)) fread, 1062 (int (*)(void *, ogg_int64_t, int)) _fseek64_wrap, 1063 (int (*)(void *)) fclose, 1064 (long (*)(void *)) ftell 1065 }; 1066 1067 return ov_open_callbacks(interp, (void *)f, vf, initial, ibytes, callbacks); 1068} 1069 1070/* Only partially open the vorbis file; test for Vorbisness, and load 1071 the headers for the first chain. Do not seek (although test for 1072 seekability). Use ov_test_open to finish opening the file, else 1073 ov_clear to close/free it. Same return codes as open. */ 1074/* 1075int ov_test_callbacks(void *f,OggVorbis_File *vf,char *initial,long ibytes, 1076 ov_callbacks callbacks) 1077{ 1078 return _ov_open1(f,vf,initial,ibytes,callbacks); 1079} 1080 1081int ov_test(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){ 1082 ov_callbacks callbacks = { 1083 (size_t (*)(void *, size_t, size_t, void *)) fread, 1084 (int (*)(void *, ogg_int64_t, int)) _fseek64_wrap, 1085 (int (*)(void *)) fclose, 1086 (long (*)(void *)) ftell 1087 }; 1088 1089 return ov_test_callbacks((void *)f, vf, initial, ibytes, callbacks); 1090} 1091 1092int ov_test_open(OggVorbis_File *vf){ 1093 if(vf->ready_state!=PARTOPEN)return(OV_EINVAL); 1094 return _ov_open2(vf); 1095} 1096*/ 1097/* How many logical bitstreams in this physical bitstream? */ 1098long ov_streams(OggVorbis_File *vf){ 1099 return vf->links; 1100} 1101 1102/* Is the FILE * associated with vf seekable? */ 1103long ov_seekable(OggVorbis_File *vf){ 1104 return vf->seekable; 1105} 1106 1107/* returns the bitrate for a given logical bitstream or the entire 1108 physical bitstream. If the file is open for random access, it will 1109 find the *actual* average bitrate. If the file is streaming, it 1110 returns the nominal bitrate (if set) else the average of the 1111 upper/lower bounds (if set) else -1 (unset). 1112 1113 If you want the actual bitrate field settings, get them from the 1114 vorbis_info structs */ 1115 1116long ov_bitrate(OggVorbis_File *vf,int i){ 1117 if(vf->ready_state<OPENED)return(OV_EINVAL); 1118 if(i>=vf->links)return(OV_EINVAL); 1119 if(!vf->seekable && i!=0)return(ov_bitrate(vf,0)); 1120 if(i<0){ 1121 ogg_int64_t bits=0; 1122 int i; 1123 for(i=0;i<vf->links;i++) 1124 bits+=(vf->offsets[i+1]-vf->dataoffsets[i])*8; 1125 return(rint(bits/ov_time_total(vf,-1))); 1126 }else{ 1127 if(vf->seekable){ 1128 /* return the actual bitrate */ 1129 return(rint((vf->offsets[i+1]-vf->dataoffsets[i])*8/ov_time_total(vf,i))); 1130 }else{ 1131 /* return nominal if set */ 1132 if(vf->vi[i].bitrate_nominal>0){ 1133 return vf->vi[i].bitrate_nominal; 1134 }else{ 1135 if(vf->vi[i].bitrate_upper>0){ 1136 if(vf->vi[i].bitrate_lower>0){ 1137 return (vf->vi[i].bitrate_upper+vf->vi[i].bitrate_lower)/2; 1138 }else{ 1139 return vf->vi[i].bitrate_upper; 1140 } 1141 } 1142 return(OV_FALSE); 1143 } 1144 } 1145 } 1146} 1147 1148/* returns the actual bitrate since last call. returns -1 if no 1149 additional data to offer since last call (or at beginning of stream), 1150 EINVAL if stream is only partially open 1151*/ 1152long ov_bitrate_instant(OggVorbis_File *vf){ 1153 int link=(vf->seekable?vf->current_link:0); 1154 long ret; 1155 if(vf->ready_state<OPENED)return(OV_EINVAL); 1156 if(vf->samptrack==0)return(OV_FALSE); 1157 ret=vf->bittrack/vf->samptrack*vf->vi[link].rate+.5; 1158 vf->bittrack=0.f; 1159 vf->samptrack=0.f; 1160 return(ret); 1161} 1162 1163/* Guess */ 1164long ov_serialnumber(OggVorbis_File *vf,int i){ 1165 if(i>=vf->links)return(ov_serialnumber(vf,vf->links-1)); 1166 if(!vf->seekable && i>=0)return(ov_serialnumber(vf,-1)); 1167 if(i<0){ 1168 return(vf->current_serialno); 1169 }else{ 1170 return(vf->serialnos[i]); 1171 } 1172} 1173 1174/* returns: total raw (compressed) length of content if i==-1 1175 raw (compressed) length of that logical bitstream for i==0 to n 1176 OV_EINVAL if the stream is not seekable (we can't know the length) 1177 or if stream is only partially open 1178*/ 1179ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i){ 1180 if(vf->ready_state<OPENED)return(OV_EINVAL); 1181 if(!vf->seekable || i>=vf->links)return(OV_EINVAL); 1182 if(i<0){ 1183 ogg_int64_t acc=0; 1184 int i; 1185 for(i=0;i<vf->links;i++) 1186 acc+=ov_raw_total(vf,i); 1187 return(acc); 1188 }else{ 1189 return(vf->offsets[i+1]-vf->offsets[i]); 1190 } 1191} 1192 1193/* returns: total PCM length (samples) of content if i==-1 PCM length 1194 (samples) of that logical bitstream for i==0 to n 1195 OV_EINVAL if the stream is not seekable (we can't know the 1196 length) or only partially open 1197*/ 1198ogg_int64_t ov_pcm_total(OggVorbis_File *vf,int i){ 1199 if(vf->ready_state<OPENED)return(OV_EINVAL); 1200 if(!vf->seekable || i>=vf->links)return(OV_EINVAL); 1201 if(i<0){ 1202 ogg_int64_t acc=0; 1203 int i; 1204 for(i=0;i<vf->links;i++) 1205 acc+=ov_pcm_total(vf,i); 1206 return(acc); 1207 }else{ 1208 return(vf->pcmlengths[i*2+1]); 1209 } 1210} 1211 1212/* returns: total seconds of content if i==-1 1213 seconds in that logical bitstream for i==0 to n 1214 OV_EINVAL if the stream is not seekable (we can't know the 1215 length) or only partially open 1216*/ 1217double ov_time_total(OggVorbis_File *vf,int i){ 1218 if(vf->ready_state<OPENED)return(OV_EINVAL); 1219 if(!vf->seekable || i>=vf->links)return(OV_EINVAL); 1220 if(i<0){ 1221 double acc=0; 1222 int i; 1223 for(i=0;i<vf->links;i++) 1224 acc+=ov_time_total(vf,i); 1225 return(acc); 1226 }else{ 1227 return((double)(vf->pcmlengths[i*2+1])/vf->vi[i].rate); 1228 } 1229} 1230 1231/* seek to an offset relative to the *compressed* data. This also 1232 scans packets to update the PCM cursor. It will cross a logical 1233 bitstream boundary, but only if it can't get any packets out of the 1234 tail of the bitstream we seek to (so no surprises). 1235 1236 returns zero on success, nonzero on failure */ 1237 1238int ov_raw_seek(OggVorbis_File *vf,ogg_int64_t pos){ 1239 ogg_stream_state work_os; 1240 1241 if(vf->ready_state<OPENED)return(OV_EINVAL); 1242 if(!vf->seekable) 1243 return(OV_ENOSEEK); /* don't dump machine if we can't seek */ 1244 1245 if(pos<0 || pos>vf->end)return(OV_EINVAL); 1246 1247 /* clear out decoding machine state */ 1248 vf->pcm_offset=-1; 1249 _decode_clear(vf); 1250 1251 _seek_helper(vf,pos); 1252 1253 /* we need to make sure the pcm_offset is set, but we don't want to 1254 advance the raw cursor past good packets just to get to the first 1255 with a granulepos. That's not equivalent behavior to beginning 1256 decoding as immediately after the seek position as possible. 1257 1258 So, a hack. We use two stream states; a local scratch state and 1259 a the shared vf->os stream state. We use the local state to 1260 scan, and the shared state as a buffer for later decode. 1261 1262 Unfortuantely, on the last page we still advance to last packet 1263 because the granulepos on the last page is not necessarily on a 1264 packet boundary, and we need to make sure the granpos is 1265 correct. 1266 */ 1267 1268 { 1269 ogg_page og; 1270 ogg_packet op; 1271 int lastblock=0; 1272 int accblock=0; 1273 int thisblock; 1274 int eosflag; 1275 1276 ogg_stream_init(&work_os,-1); /* get the memory ready */ 1277 1278 while(1){ 1279 if(vf->ready_state==STREAMSET){ 1280 /* snarf/scan a packet if we can */ 1281 int result=ogg_stream_packetout(&work_os,&op); 1282 1283 if(result>0){ 1284 1285 if(vf->vi[vf->current_link].codec_setup) 1286 thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); 1287 if(eosflag) 1288 ogg_stream_packetout(&vf->os,NULL); 1289 else 1290 if(lastblock)accblock+=(lastblock+thisblock)>>2; 1291 1292 if(op.granulepos!=-1){ 1293 int i,link=vf->current_link; 1294 ogg_int64_t granulepos=op.granulepos-vf->pcmlengths[link*2]; 1295 if(granulepos<0)granulepos=0; 1296 1297 for(i=0;i<link;i++) 1298 granulepos+=vf->pcmlengths[i*2+1]; 1299 vf->pcm_offset=granulepos-accblock; 1300 break; 1301 } 1302 lastblock=thisblock; 1303 continue; 1304 } 1305 } 1306 1307 if(!lastblock){ 1308 if(_get_next_page(vf,&og,-1)<0){ 1309 vf->pcm_offset=ov_pcm_total(vf,-1); 1310 break; 1311 } 1312 }else{ 1313 /* huh? Bogus stream with packets but no granulepos */ 1314 vf->pcm_offset=-1; 1315 break; 1316 } 1317 1318 /* has our decoding just traversed a bitstream boundary? */ 1319 if(vf->ready_state==STREAMSET) 1320 if(vf->current_serialno!=ogg_page_serialno(&og)){ 1321 _decode_clear(vf); /* clear out stream state */ 1322 ogg_stream_clear(&work_os); 1323 } 1324 1325 if(vf->ready_state<STREAMSET){ 1326 int link; 1327 1328 vf->current_serialno=ogg_page_serialno(&og); 1329 for(link=0;link<vf->links;link++) 1330 if(vf->serialnos[link]==vf->current_serialno)break; 1331 if(link==vf->links)goto seek_error; /* sign of a bogus stream. 1332 error out, leave 1333 machine uninitialized */ 1334 vf->current_link=link; 1335 1336 ogg_stream_reset_serialno(&vf->os,vf->current_serialno); 1337 ogg_stream_reset_serialno(&work_os,vf->current_serialno); 1338 vf->ready_state=STREAMSET; 1339 1340 } 1341 1342 ogg_stream_pagein(&vf->os,&og); 1343 ogg_stream_pagein(&work_os,&og); 1344 eosflag=ogg_page_eos(&og); 1345 } 1346 } 1347 1348 ogg_stream_clear(&work_os); 1349 return(0); 1350 1351 seek_error: 1352 /* dump the machine so we're in a known state */ 1353 vf->pcm_offset=-1; 1354 ogg_stream_clear(&work_os); 1355 _decode_clear(vf); 1356 return OV_EBADLINK; 1357} 1358 1359/* Page granularity seek (faster than sample granularity because we 1360 don't do the last bit of decode to find a specific sample). 1361 1362 Seek to the last [granule marked] page preceeding the specified pos 1363 location, such that decoding past the returned point will quickly 1364 arrive at the requested position. */ 1365int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos){ 1366 int link=-1; 1367 ogg_int64_t result=0; 1368 ogg_int64_t total=ov_pcm_total(vf,-1); 1369 1370 if(vf->ready_state<OPENED)return(OV_EINVAL); 1371 if(!vf->seekable)return(OV_ENOSEEK); 1372 1373 if(pos<0 || pos>total)return(OV_EINVAL); 1374 1375 /* which bitstream section does this pcm offset occur in? */ 1376 for(link=vf->links-1;link>=0;link--){ 1377 total-=vf->pcmlengths[link*2+1]; 1378 if(pos>=total)break; 1379 } 1380 1381 /* search within the logical bitstream for the page with the highest 1382 pcm_pos preceeding (or equal to) pos. There is a danger here; 1383 missing pages or incorrect frame number information in the 1384 bitstream could make our task impossible. Account for that (it 1385 would be an error condition) */ 1386 1387 /* new search algorithm by HB (Nicholas Vinen) */ 1388 { 1389 ogg_int64_t end=vf->offsets[link+1]; 1390 ogg_int64_t begin=vf->offsets[link]; 1391 ogg_int64_t begintime = vf->pcmlengths[link*2]; 1392 ogg_int64_t endtime = vf->pcmlengths[link*2+1]+begintime; 1393 ogg_int64_t target=pos-total+begintime; 1394 ogg_int64_t best=begin; 1395 1396 ogg_page og; 1397 while(begin<end){ 1398 ogg_int64_t bisect; 1399 1400 if(end-begin<CHUNKSIZE){ 1401 bisect=begin; 1402 }else{ 1403 /* take a (pretty decent) guess. */ 1404 bisect=begin + 1405 (target-begintime)*(end-begin)/(endtime-begintime) - CHUNKSIZE; 1406 if(bisect<=begin) 1407 bisect=begin+1; 1408 } 1409 1410 _seek_helper(vf,bisect); 1411 1412 while(begin<end){ 1413 result=_get_next_page(vf,&og,end-vf->offset); 1414 if(result==OV_EREAD) goto seek_error; 1415 if(result<0){ 1416 if(bisect<=begin+1) 1417 end=begin; /* found it */ 1418 else{ 1419 if(bisect==0) goto seek_error; 1420 bisect-=CHUNKSIZE; 1421 if(bisect<=begin)bisect=begin+1; 1422 _seek_helper(vf,bisect); 1423 } 1424 }else{ 1425 ogg_int64_t granulepos=ogg_page_granulepos(&og); 1426 if(granulepos==-1)continue; 1427 if(granulepos<target){ 1428 best=result; /* raw offset of packet with granulepos */ 1429 begin=vf->offset; /* raw offset of next page */ 1430 begintime=granulepos; 1431 1432 if(target-begintime>44100)break; 1433 bisect=begin; /* *not* begin + 1 */ 1434 }else{ 1435 if(bisect<=begin+1) 1436 end=begin; /* found it */ 1437 else{ 1438 if(end==vf->offset){ /* we're pretty close - we'd be stuck in */ 1439 end=result; 1440 bisect-=CHUNKSIZE; /* an endless loop otherwise. */ 1441 if(bisect<=begin)bisect=begin+1; 1442 _seek_helper(vf,bisect); 1443 }else{ 1444 end=result; 1445 endtime=granulepos; 1446 break; 1447 } 1448 } 1449 } 1450 } 1451 } 1452 } 1453 1454 /* found our page. seek to it, update pcm offset. Easier case than 1455 raw_seek, don't keep packets preceeding granulepos. */ 1456 { 1457 ogg_page og; 1458 ogg_packet op; 1459 /* clear out decoding machine state */ 1460 _decode_clear(vf); 1461 /* seek */ 1462 _seek_helper(vf,best); 1463 1464 if(_get_next_page(vf,&og,-1)<0)return(OV_EOF); /* shouldn't happen */ 1465 vf->current_serialno=ogg_page_serialno(&og); 1466 vf->current_link=link; 1467 1468 ogg_stream_reset_serialno(&vf->os,vf->current_serialno); 1469 vf->ready_state=STREAMSET; 1470 ogg_stream_pagein(&vf->os,&og); 1471 1472 /* pull out all but last packet; the one with granulepos */ 1473 while(1){ 1474 result=ogg_stream_packetpeek(&vf->os,&op); 1475 if(result==0){ 1476 /* !!! the packet finishing this page originated on a 1477 preceeding page. Keep fetching previous pages until we 1478 get one with a granulepos or without the 'continued' flag 1479 set. Then just use raw_seek for simplicity. */ 1480 1481 _decode_clear(vf); 1482 _seek_helper(vf,best); 1483 1484 while(1){ 1485 result=_get_prev_page(vf,&og); 1486 if(result<0) goto seek_error; 1487 if(ogg_page_granulepos(&og)>-1 || 1488 !ogg_page_continued(&og)){ 1489 return ov_raw_seek(vf,result); 1490 } 1491 vf->offset=result; 1492 } 1493 } 1494 if(result<0){ 1495 result = OV_EBADPACKET; 1496 goto seek_error; 1497 } 1498 if(op.granulepos!=-1){ 1499 vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2]; 1500 if(vf->pcm_offset<0)vf->pcm_offset=0; 1501 vf->pcm_offset+=total; 1502 break; 1503 }else 1504 result=ogg_stream_packetout(&vf->os,NULL); 1505 } 1506 } 1507 } 1508 1509 /* verify result */ 1510 if(vf->pcm_offset>pos || pos>ov_pcm_total(vf,-1)){ 1511 result=OV_EFAULT; 1512 goto seek_error; 1513 } 1514 return(0); 1515 1516 seek_error: 1517 /* dump machine so we're in a known state */ 1518 vf->pcm_offset=-1; 1519 _decode_clear(vf); 1520 return (int)result; 1521} 1522 1523/* seek to a sample offset relative to the decompressed pcm stream 1524 returns zero on success, nonzero on failure */ 1525 1526int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos){ 1527 int thisblock,lastblock=0; 1528 int ret=ov_pcm_seek_page(vf,pos); 1529 if(ret<0)return(ret); 1530 _make_decode_ready(vf); 1531 1532 /* discard leading packets we don't need for the lapping of the 1533 position we want; don't decode them */ 1534 1535 while(1){ 1536 ogg_packet op; 1537 ogg_page og; 1538 1539 int ret=ogg_stream_packetpeek(&vf->os,&op); 1540 if(ret>0){ 1541 thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); 1542 if(thisblock<0)thisblock=0; /* non audio packet */ 1543 if(lastblock)vf->pcm_offset+=(lastblock+thisblock)>>2; 1544 1545 if(vf->pcm_offset+((thisblock+ 1546 vorbis_info_blocksize(vf->vi,1))>>2)>=pos)break; 1547 1548 /* remove the packet from packet queue and track its granulepos */ 1549 ogg_stream_packetout(&vf->os,NULL); 1550 vorbis_synthesis_trackonly(&vf->vb,&op); /* set up a vb with 1551 only tracking, no 1552 pcm_decode */ 1553 vorbis_synthesis_blockin(&vf->vd,&vf->vb); 1554 1555 /* end of logical stream case is hard, especially with exact 1556 length positioning. */ 1557 1558 if(op.granulepos>-1){ 1559 int i; 1560 /* always believe the stream markers */ 1561 vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2]; 1562 if(vf->pcm_offset<0)vf->pcm_offset=0; 1563 for(i=0;i<vf->current_link;i++) 1564 vf->pcm_offset+=vf->pcmlengths[i*2+1]; 1565 } 1566 1567 lastblock=thisblock; 1568 1569 }else{ 1570 if(ret<0 && ret!=OV_HOLE)break; 1571 1572 /* suck in a new page */ 1573 if(_get_next_page(vf,&og,-1)<0)break; 1574 if(vf->current_serialno!=ogg_page_serialno(&og))_decode_clear(vf); 1575 1576 if(vf->ready_state<STREAMSET){ 1577 int link; 1578 1579 vf->current_serialno=ogg_page_serialno(&og); 1580 for(link=0;link<vf->links;link++) 1581 if(vf->serialnos[link]==vf->current_serialno)break; 1582 if(link==vf->links)return(OV_EBADLINK); 1583 vf->current_link=link; 1584 1585 ogg_stream_reset_serialno(&vf->os,vf->current_serialno); 1586 vf->ready_state=STREAMSET; 1587 _make_decode_ready(vf); 1588 lastblock=0; 1589 } 1590 1591 ogg_stream_pagein(&vf->os,&og); 1592 } 1593 } 1594 1595 /* discard samples until we reach the desired position. Crossing a 1596 logical bitstream boundary with abandon is OK. */ 1597 while(vf->pcm_offset<pos){ 1598 float **pcm; 1599 ogg_int64_t target=pos-vf->pcm_offset; 1600 long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm); 1601 1602 if(samples>target)samples=target; 1603 vorbis_synthesis_read(&vf->vd,samples); 1604 vf->pcm_offset+=samples; 1605 1606 if(samples<target) 1607 if(_fetch_and_process_packet(vf,NULL,1)<=0) 1608 vf->pcm_offset=ov_pcm_total(vf,-1); /* eof */ 1609 } 1610 return 0; 1611} 1612 1613/* seek to a playback time relative to the decompressed pcm stream 1614 returns zero on success, nonzero on failure */ 1615int ov_time_seek(OggVorbis_File *vf,double seconds){ 1616 /* translate time to PCM position and call ov_pcm_seek */ 1617 1618 int link=-1; 1619 ogg_int64_t pcm_total=ov_pcm_total(vf,-1); 1620 double time_total=ov_time_total(vf,-1); 1621 1622 if(vf->ready_state<OPENED)return(OV_EINVAL); 1623 if(!vf->seekable)return(OV_ENOSEEK); 1624 if(seconds<0 || seconds>time_total)return(OV_EINVAL); 1625 1626 /* which bitstream section does this time offset occur in? */ 1627 for(link=vf->links-1;link>=0;link--){ 1628 pcm_total-=vf->pcmlengths[link*2+1]; 1629 time_total-=ov_time_total(vf,link); 1630 if(seconds>=time_total)break; 1631 } 1632 1633 /* enough information to convert time offset to pcm offset */ 1634 { 1635 ogg_int64_t target=pcm_total+(seconds-time_total)*vf->vi[link].rate; 1636 return(ov_pcm_seek(vf,target)); 1637 } 1638} 1639 1640/* page-granularity version of ov_time_seek 1641 returns zero on success, nonzero on failure */ 1642int ov_time_seek_page(OggVorbis_File *vf,double seconds){ 1643 /* translate time to PCM position and call ov_pcm_seek */ 1644 1645 int link=-1; 1646 ogg_int64_t pcm_total=ov_pcm_total(vf,-1); 1647 double time_total=ov_time_total(vf,-1); 1648 1649 if(vf->ready_state<OPENED)return(OV_EINVAL); 1650 if(!vf->seekable)return(OV_ENOSEEK); 1651 if(seconds<0 || seconds>time_total)return(OV_EINVAL); 1652 1653 /* which bitstream section does this time offset occur in? */ 1654 for(link=vf->links-1;link>=0;link--){ 1655 pcm_total-=vf->pcmlengths[link*2+1]; 1656 time_total-=ov_time_total(vf,link); 1657 if(seconds>=time_total)break; 1658 } 1659 1660 /* enough information to convert time offset to pcm offset */ 1661 { 1662 ogg_int64_t target=pcm_total+(seconds-time_total)*vf->vi[link].rate; 1663 return(ov_pcm_seek_page(vf,target)); 1664 } 1665} 1666 1667/* tell the current stream offset cursor. Note that seek followed by 1668 tell will likely not give the set offset due to caching */ 1669ogg_int64_t ov_raw_tell(OggVorbis_File *vf){ 1670 if(vf->ready_state<OPENED)return(OV_EINVAL); 1671 return(vf->offset); 1672} 1673 1674/* return PCM offset (sample) of next PCM sample to be read */ 1675ogg_int64_t ov_pcm_tell(OggVorbis_File *vf){ 1676 if(vf->ready_state<OPENED)return(OV_EINVAL); 1677 return(vf->pcm_offset); 1678} 1679 1680/* return time offset (seconds) of next PCM sample to be read */ 1681double ov_time_tell(OggVorbis_File *vf){ 1682 /* translate time to PCM position and call ov_pcm_seek */ 1683 1684 int link=-1; 1685 ogg_int64_t pcm_total=0; 1686 double time_total=0.f; 1687 1688 if(vf->ready_state<OPENED)return(OV_EINVAL); 1689 if(vf->seekable){ 1690 pcm_total=ov_pcm_total(vf,-1); 1691 time_total=ov_time_total(vf,-1); 1692 1693 /* which bitstream section does this time offset occur in? */ 1694 for(link=vf->links-1;link>=0;link--){ 1695 pcm_total-=vf->pcmlengths[link*2+1]; 1696 time_total-=ov_time_total(vf,link); 1697 if(vf->pcm_offset>=pcm_total)break; 1698 } 1699 } 1700 1701 return((double)time_total+(double)(vf->pcm_offset-pcm_total)/vf->vi[link].rate); 1702} 1703 1704/* link: -1) return the vorbis_info struct for the bitstream section 1705 currently being decoded 1706 0-n) to request information for a specific bitstream section 1707 1708 In the case of a non-seekable bitstream, any call returns the 1709 current bitstream. NULL in the case that the machine is not 1710 initialized */ 1711 1712vorbis_info *ov_info(OggVorbis_File *vf,int link){ 1713 if(vf->seekable){ 1714 if(link<0) 1715 if(vf->ready_state>=STREAMSET) 1716 return vf->vi+vf->current_link; 1717 else 1718 return vf->vi; 1719 else 1720 if(link>=vf->links) 1721 return NULL; 1722 else 1723 return vf->vi+link; 1724 }else{ 1725 return vf->vi; 1726 } 1727} 1728 1729/* grr, strong typing, grr, no templates/inheritence, grr */ 1730vorbis_comment *ov_comment(OggVorbis_File *vf,int link){ 1731 if(vf->seekable){ 1732 if(link<0) 1733 if(vf->ready_state>=STREAMSET) 1734 return vf->vc+vf->current_link; 1735 else 1736 return vf->vc; 1737 else 1738 if(link>=vf->links) 1739 return NULL; 1740 else 1741 return vf->vc+link; 1742 }else{ 1743 return vf->vc; 1744 } 1745} 1746 1747static int host_is_big_endian() { 1748 ogg_int32_t pattern = 0xfeedface; /* deadbeef */ 1749 unsigned char *bytewise = (unsigned char *)&pattern; 1750 if (bytewise[0] == 0xfe) return 1; 1751 return 0; 1752} 1753 1754/* up to this point, everything could more or less hide the multiple 1755 logical bitstream nature of chaining from the toplevel application 1756 if the toplevel application didn't particularly care. However, at 1757 the point that we actually read audio back, the multiple-section 1758 nature must surface: Multiple bitstream sections do not necessarily 1759 have to have the same number of channels or sampling rate. 1760 1761 ov_read returns the sequential logical bitstream number currently 1762 being decoded along with the PCM data in order that the toplevel 1763 application can take action on channel/sample rate changes. This 1764 number will be incremented even for streamed (non-seekable) streams 1765 (for seekable streams, it represents the actual logical bitstream 1766 index within the physical bitstream. Note that the accessor 1767 functions above are aware of this dichotomy). 1768 1769 input values: buffer) a buffer to hold packed PCM data for return 1770 length) the byte length requested to be placed into buffer 1771 bigendianp) should the data be packed LSB first (0) or 1772 MSB first (1) 1773 word) word size for output. currently 1 (byte) or 1774 2 (16 bit short) 1775 1776 return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL) 1777 0) EOF 1778 n) number of bytes of PCM actually returned. The 1779 below works on a packet-by-packet basis, so the 1780 return length is not related to the 'length' passed 1781 in, just guaranteed to fit. 1782 1783 *section) set to the logical bitstream number */ 1784 1785long ov_read(OggVorbis_File *vf,char *buffer,int length, 1786 int bigendianp,int word,int sgned,int *bitstream){ 1787 int i,j; 1788 int host_endian = host_is_big_endian(); 1789 1790 float **pcm; 1791 long samples; 1792 1793 if(vf->ready_state<OPENED)return(OV_EINVAL); 1794 1795 while(1){ 1796 if(vf->ready_state>=STREAMSET){ 1797 samples=vorbis_synthesis_pcmout(&vf->vd,&pcm); 1798 if(samples)break; 1799 } 1800 1801 /* suck in another packet */ 1802 { 1803 int ret=_fetch_and_process_packet(vf,NULL,1); 1804 if(ret==OV_EOF)return(0); 1805 if(ret<=0)return(ret); 1806 } 1807 1808 } 1809 1810 if(samples>0){ 1811 1812 /* yay! proceed to pack data into the byte buffer */ 1813 1814 long channels=ov_info(vf,-1)->channels; 1815 long bytespersample=word * channels; 1816 vorbis_fpu_control fpu; 1817 if(samples>length/bytespersample)samples=length/bytespersample; 1818 1819 if(samples <= 0) 1820 return OV_EINVAL; 1821 1822 /* a tight loop to pack each size */ 1823 { 1824 int val; 1825 if(word==1){ 1826 int off=(sgned?0:128); 1827 vorbis_fpu_setround(&fpu); 1828 for(j=0;j<samples;j++) 1829 for(i=0;i<channels;i++){ 1830 val=vorbis_ftoi(pcm[i][j]*128.f); 1831 if(val>127)val=127; 1832 else if(val<-128)val=-128; 1833 *buffer++=val+off; 1834 } 1835 vorbis_fpu_restore(fpu); 1836 }else{ 1837 int off=(sgned?0:32768); 1838 1839 if(host_endian==bigendianp){ 1840 if(sgned){ 1841 1842 vorbis_fpu_setround(&fpu); 1843 for(i=0;i<channels;i++) { /* It's faster in this order */ 1844 float *src=pcm[i]; 1845 short *dest=((short *)buffer)+i; 1846 for(j=0;j<samples;j++) { 1847 val=vorbis_ftoi(src[j]*32768.f); 1848 if(val>32767)val=32767; 1849 else if(val<-32768)val=-32768; 1850 *dest=val; 1851 dest+=channels; 1852 } 1853 } 1854 vorbis_fpu_restore(fpu); 1855 1856 }else{ 1857 1858 vorbis_fpu_setround(&fpu); 1859 for(i=0;i<channels;i++) { 1860 float *src=pcm[i]; 1861 short *dest=((short *)buffer)+i; 1862 for(j=0;j<samples;j++) { 1863 val=vorbis_ftoi(src[j]*32768.f); 1864 if(val>32767)val=32767; 1865 else if(val<-32768)val=-32768; 1866 *dest=val+off; 1867 dest+=channels; 1868 } 1869 } 1870 vorbis_fpu_restore(fpu); 1871 1872 } 1873 }else if(bigendianp){ 1874 1875 vorbis_fpu_setround(&fpu); 1876 for(j=0;j<samples;j++) 1877 for(i=0;i<channels;i++){ 1878 val=vorbis_ftoi(pcm[i][j]*32768.f); 1879 if(val>32767)val=32767; 1880 else if(val<-32768)val=-32768; 1881 val+=off; 1882 *buffer++=(val>>8); 1883 *buffer++=(val&0xff); 1884 } 1885 vorbis_fpu_restore(fpu); 1886 1887 }else{ 1888 int val; 1889 vorbis_fpu_setround(&fpu); 1890 for(j=0;j<samples;j++) 1891 for(i=0;i<channels;i++){ 1892 val=vorbis_ftoi(pcm[i][j]*32768.f); 1893 if(val>32767)val=32767; 1894 else if(val<-32768)val=-32768; 1895 val+=off; 1896 *buffer++=(val&0xff); 1897 *buffer++=(val>>8); 1898 } 1899 vorbis_fpu_restore(fpu); 1900 1901 } 1902 } 1903 } 1904 1905 vorbis_synthesis_read(&vf->vd,samples); 1906 vf->pcm_offset+=samples; 1907 if(bitstream)*bitstream=vf->current_link; 1908 return(samples*bytespersample); 1909 }else{ 1910 return(samples); 1911 } 1912} 1913 1914/* input values: pcm_channels) a float vector per channel of output 1915 length) the sample length being read by the app 1916 1917 return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL) 1918 0) EOF 1919 n) number of samples of PCM actually returned. The 1920 below works on a packet-by-packet basis, so the 1921 return length is not related to the 'length' passed 1922 in, just guaranteed to fit. 1923 1924 *section) set to the logical bitstream number */ 1925 1926 1927 1928long ov_read_float(OggVorbis_File *vf,float ***pcm_channels,int length, 1929 int *bitstream){ 1930 1931 if(vf->ready_state<OPENED)return(OV_EINVAL); 1932 1933 while(1){ 1934 if(vf->ready_state>=STREAMSET){ 1935 float **pcm; 1936 long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm); 1937 if(samples){ 1938 if(pcm_channels)*pcm_channels=pcm; 1939 if(samples>length)samples=length; 1940 vorbis_synthesis_read(&vf->vd,samples); 1941 vf->pcm_offset+=samples; 1942 if(bitstream)*bitstream=vf->current_link; 1943 return samples; 1944 1945 } 1946 } 1947 1948 /* suck in another packet */ 1949 { 1950 int ret=_fetch_and_process_packet(vf,NULL,1); 1951 if(ret==OV_EOF)return(0); 1952 if(ret<=0)return(ret); 1953 } 1954 1955 } 1956} 1957 1958/* end vorbisfile.c */ 1959 1960 1961#define OGG_PATTERN "OggS" 1962#define OGG_STRING "OGG" 1963 1964static char * 1965GuessOggFile(char *buf, int len) 1966{ 1967 if (len < (int) strlen(OGG_PATTERN)) return(QUE_STRING); 1968 if (strncasecmp(OGG_PATTERN, buf, strlen(OGG_PATTERN)) == 0) { 1969 return(OGG_STRING); 1970 } 1971 return(NULL); 1972} 1973 1974char * 1975ExtOggFile(char *s) 1976{ 1977 int l1 = strlen(".ogg"); 1978 int l2 = strlen(s); 1979 1980 if (strncasecmp(".ogg", &s[l2 - l1], l1) == 0) { 1981 return(OGG_STRING); 1982 } 1983 return(NULL); 1984} 1985 1986static int started = 0; 1987 1988#define READBUFSIZE 1024 1989 1990static ogg_stream_state os; /* take physical pages, weld into a logical 1991 stream of packets */ 1992static ogg_page og; /* one Ogg bitstream page. Vorbis packets are 1993 inside */ 1994static ogg_packet op; /* one raw packet of data for decode */ 1995 1996static vorbis_info vi; /* struct that stores all the static vorbis 1997 bitstream settings */ 1998static vorbis_comment vc; /* struct that stores all the user comments */ 1999 2000static vorbis_dsp_state vd; /* central working state for the 2001 packet->PCM decoder */ 2002static vorbis_block vb; /* local working space for packet->PCM decode */ 2003 2004#define SNACK_OGG_INT 19 2005 2006static int 2007OpenOggFile(Sound *s, Tcl_Interp *interp, Tcl_Channel *ch, char *mode) 2008{ 2009 if (s->debug > 2) Snack_WriteLog(" Enter OpenOggFile\n"); 2010 2011 if ((*ch = Tcl_OpenFileChannel(interp, s->fcname, mode, 420)) == 0) { 2012 return TCL_ERROR; 2013 } 2014 if (*ch == NULL) { 2015 Tcl_AppendResult(interp, "Ogg: unable to open file: ", 2016 Snack_GetSoundFilename(s), NULL); 2017 return TCL_ERROR; 2018 } 2019 Tcl_SetChannelOption(interp, *ch, "-translation", "binary"); 2020#ifdef TCL_81_API 2021 Tcl_SetChannelOption(interp, *ch, "-encoding", "binary"); 2022#endif 2023 2024 if (s->extHead2 != NULL && s->extHead2Type != SNACK_OGG_INT) { 2025 Snack_FileFormat *ff; 2026 2027 for (ff = Snack_GetFileFormats(); ff != NULL; ff = ff->nextPtr) { 2028 if (strcmp(s->fileType, ff->name) == 0) { 2029 if (ff->freeHeaderProc != NULL) { 2030 (ff->freeHeaderProc)(s); 2031 } 2032 } 2033 } 2034 } 2035 2036 if (s->extHead2 == NULL) { 2037 s->extHead2 = (char *) ckalloc(sizeof(OggVorbis_File)); 2038 s->extHead2Type = SNACK_OGG_INT; 2039 ((OggVorbis_File *)s->extHead2)->nombitrate = 128000; 2040 ((OggVorbis_File *)s->extHead2)->maxbitrate = -1; 2041 ((OggVorbis_File *)s->extHead2)->minbitrate = -1; 2042 ((OggVorbis_File *)s->extHead2)->quality = -1.0; 2043 ((OggVorbis_File *)s->extHead2)->commList = NULL; 2044 ((OggVorbis_File *)s->extHead2)->vendor = NULL; 2045 } 2046 2047 if (strcmp(mode,"r") == 0) { 2048 if(ov_open(interp, ch, (OggVorbis_File *)s->extHead2, NULL, 0) < 0) { 2049 Tcl_AppendResult(interp, "Input does not appear to be an Ogg bitstream", 2050 NULL); 2051 return TCL_ERROR; 2052 } 2053 } 2054 2055 if (s->debug > 2) Snack_WriteLog(" Exit OpenOggFile\n"); 2056 2057 return TCL_OK; 2058} 2059 2060static int 2061CloseOggFile(Sound *s, Tcl_Interp *interp, Tcl_Channel *ch) 2062{ 2063 if (s->debug > 2) Snack_WriteLog(" Enter CloseOggFile\n"); 2064 2065 if (started == 0) { 2066 ov_clear(interp, (OggVorbis_File *)s->extHead2); 2067 *ch = NULL; 2068 } else { 2069 2070 /* Tell the library we're at end of stream */ 2071 vorbis_analysis_wrote(&vd, 0); 2072 2073 while (vorbis_analysis_blockout(&vd, &vb) == 1) { 2074 vorbis_analysis(&vb,&op); 2075 ogg_stream_packetin(&os,&op); 2076 2077 while (1) { 2078 int result = ogg_stream_pageout(&os, &og); 2079 if (result == 0) break; 2080 if (Tcl_Write(*ch, (char *) og.header, og.header_len) == -1) 2081 return TCL_ERROR; 2082 if (Tcl_Write(*ch, (char *) og.body, og.body_len) == -1) 2083 return TCL_ERROR; 2084 2085 if (ogg_page_eos(&og)) break; 2086 } 2087 } 2088 2089 /* clean up, vorbis_info_clear() must be called last */ 2090 2091 ogg_stream_clear(&os); 2092 vorbis_block_clear(&vb); 2093 vorbis_dsp_clear(&vd); 2094 vorbis_comment_clear(&vc); 2095 vorbis_info_clear(&vi); 2096 2097 if (ch != NULL) { 2098 Tcl_Close(interp, *ch); 2099 } 2100 started = 0; 2101 } 2102 2103 if (s->debug > 2) Snack_WriteLog(" Exit CloseOggFile\n"); 2104 2105 return TCL_OK; 2106} 2107 2108float pcmout[READBUFSIZE]; 2109 2110static int 2111ReadOggSamples(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, char *ibuf, 2112 float *obuf, int len) 2113{ 2114 int nread = 0, bigendian = Snack_PlatformIsLittleEndian() ? 0 : 1; 2115 float *f = obuf; 2116 int n, i, dummy; 2117 2118 if (s->debug > 2) Snack_WriteLog(" Enter ReadOggSamples\n"); 2119 2120 while (nread < len) { 2121 int size = min(sizeof(pcmout), (len - nread) * s->sampsize); 2122 n = ov_read((OggVorbis_File *)s->extHead2, (char *)pcmout, size, 2123 bigendian, 2, 1, &dummy); 2124 if (n < 0) { 2125 return -1; 2126 } else if (n == 0) { 2127 return(nread); 2128 } else { 2129 short *r = (short *) pcmout; 2130 for (i = 0; i < n / s->sampsize; i++) { 2131 *f++ = (float) *r++; 2132 } 2133 nread += (n / s->sampsize); 2134 } 2135 } 2136 2137 if (s->debug > 2) Snack_WriteLogInt(" Exit ReadOggSamples", nread); 2138 2139 return(nread); 2140} 2141 2142static int 2143SeekOggFile(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, int pos) 2144{ 2145 if (pos == 0) return 0; /* ov_time_seek() does not like seeking to 0 */ 2146 2147 if (ov_pcm_seek((OggVorbis_File *)s->extHead2, (ogg_int64_t) pos)) { 2148 return(-1); 2149 } else { 2150 return(pos); 2151 } 2152} 2153 2154static int 2155GetOggHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, 2156 char *buf) 2157{ 2158 int i; 2159 vorbis_info *vi; 2160 vorbis_comment *vc; 2161 2162 if (s->debug > 2) Snack_WriteLog(" Enter GetOggHeader\n"); 2163 2164 /* For the case when Tcl_Open has been done somewhere else */ 2165 2166 if (s->extHead2 != NULL && s->extHead2Type != SNACK_OGG_INT) { 2167 Snack_FileFormat *ff; 2168 2169 for (ff = Snack_GetFileFormats(); ff != NULL; ff = ff->nextPtr) { 2170 if (strcmp(s->fileType, ff->name) == 0) { 2171 if (ff->freeHeaderProc != NULL) { 2172 (ff->freeHeaderProc)(s); 2173 } 2174 } 2175 } 2176 } 2177 2178 if (s->extHead2 == NULL) { 2179 s->extHead2 = (char *) ckalloc(sizeof(OggVorbis_File)); 2180 s->extHead2Type = SNACK_OGG_INT; 2181 ((OggVorbis_File *)s->extHead2)->maxbitrate = -1; 2182 ((OggVorbis_File *)s->extHead2)->minbitrate = -1; 2183 ((OggVorbis_File *)s->extHead2)->quality = -1.0; 2184 2185 if (ov_open(interp, &s->rwchan, (OggVorbis_File *)s->extHead2, 2186 (char *)s->tmpbuf, s->firstNRead) < 0) { 2187 Tcl_AppendResult(interp, "Input does not appear to be an Ogg bitstream", 2188 NULL); 2189 return TCL_ERROR; 2190 } 2191 } 2192 2193 vi = ov_info((OggVorbis_File *)s->extHead2,-1); 2194 2195 Snack_SetSampleRate(s, vi->rate); 2196 Snack_SetNumChannels(s, vi->channels); 2197 Snack_SetSampleEncoding(s, LIN16); 2198 Snack_SetBytesPerSample(s, 2); 2199 Snack_SetHeaderSize(s, 0); 2200 Snack_SetLength(s, (long)ov_pcm_total((OggVorbis_File *)s->extHead2, -1)); 2201 ((OggVorbis_File *)s->extHead2)->nombitrate = 2202 ov_bitrate((OggVorbis_File *)s->extHead2, -1); 2203 vc = ov_comment((OggVorbis_File *)s->extHead2, -1); 2204 ((OggVorbis_File *)s->extHead2)->commList = Tcl_NewListObj(0, NULL); 2205 Tcl_IncrRefCount(((OggVorbis_File *)s->extHead2)->commList); 2206 for (i = 0; i < vc->comments; i++) { 2207 Tcl_Obj *newObj = Tcl_NewStringObj(vc->user_comments[i], -1); 2208 Tcl_IncrRefCount(newObj); 2209 Tcl_ListObjAppendElement(interp, ((OggVorbis_File *)s->extHead2)->commList, 2210 newObj); 2211 } 2212 ((OggVorbis_File *)s->extHead2)->vendor = Tcl_NewStringObj(vc->vendor, -1); 2213 2214 if (s->debug > 2) Snack_WriteLog(" Exit GetOggHeader\n"); 2215 2216 return TCL_OK; 2217} 2218 2219static int 2220PutOggHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, 2221 int objc, Tcl_Obj *CONST objv[], int len) 2222{ 2223 int arg, n = 0, ret; 2224 OggVorbis_File *of = (OggVorbis_File *)s->extHead2; 2225 Tcl_Obj **listObj; 2226 static char *subOptionStrings[] = { 2227 "-comment", "-maxbitrate", "-minbitrate", "-nominalbitrate", 2228 "-quality", NULL 2229 }; 2230 enum subOptions { 2231 COMMENT, MAX, MIN, NOMINAL, QUALITY 2232 }; 2233 2234 if (s->debug > 2) Snack_WriteLog(" Enter PutOggHeader\n"); 2235 2236 for (arg = 0; arg < objc; arg+=2) { 2237 int index; 2238 2239 if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings, 2240 "option", 0, &index) != TCL_OK) { 2241 return TCL_ERROR; 2242 } 2243 2244 if (arg + 1 == objc) { 2245 Tcl_AppendResult(interp, "No argument given for ", 2246 subOptionStrings[index], " option", (char *) NULL); 2247 return TCL_ERROR; 2248 } 2249 2250 switch ((enum subOptions) index) { 2251 case COMMENT: 2252 { 2253 if (Tcl_ListObjGetElements(interp, objv[arg+1], &n, &listObj) != 2254 TCL_OK) { 2255 return TCL_ERROR; 2256 } 2257 break; 2258 } 2259 case MAX: 2260 { 2261 if (Tcl_GetIntFromObj(interp, objv[arg+1], &of->maxbitrate) != TCL_OK) 2262 return TCL_ERROR; 2263 break; 2264 } 2265 case MIN: 2266 { 2267 if (Tcl_GetIntFromObj(interp, objv[arg+1], &of->minbitrate) != TCL_OK) 2268 return TCL_ERROR; 2269 break; 2270 } 2271 case NOMINAL: 2272 { 2273 if (Tcl_GetIntFromObj(interp, objv[arg+1], &of->nombitrate) != TCL_OK) 2274 return TCL_ERROR; 2275 break; 2276 } 2277 case QUALITY: 2278 { 2279 if (Tcl_GetDoubleFromObj(interp, objv[arg+1], &of->quality) != TCL_OK) 2280 return TCL_ERROR; 2281 break; 2282 } 2283 } 2284 } 2285 2286 /* For the case when Tcl_Open has been done somewhere else */ 2287 2288 if (started == 0) { 2289 ogg_packet header; 2290 ogg_packet header_comm; 2291 ogg_packet header_code; 2292 2293 if (s->extHead2 != NULL && s->extHead2Type != SNACK_OGG_INT) { 2294 Snack_FileFormat *ff; 2295 2296 for (ff = Snack_GetFileFormats(); ff != NULL; ff = ff->nextPtr) { 2297 if (strcmp(s->fileType, ff->name) == 0) { 2298 if (ff->freeHeaderProc != NULL) { 2299 (ff->freeHeaderProc)(s); 2300 } 2301 } 2302 } 2303 } 2304 2305 if (s->extHead2 == NULL) { 2306 s->extHead2 = (char *) ckalloc(sizeof(OggVorbis_File)); 2307 s->extHead2Type = SNACK_OGG_INT; 2308 ((OggVorbis_File *)s->extHead2)->nombitrate = 128000; 2309 ((OggVorbis_File *)s->extHead2)->maxbitrate = -1; 2310 ((OggVorbis_File *)s->extHead2)->minbitrate = -1; 2311 ((OggVorbis_File *)s->extHead2)->quality = -1.0; 2312 ((OggVorbis_File *)s->extHead2)->commList = NULL; 2313 ((OggVorbis_File *)s->extHead2)->vendor = NULL; 2314 of = (OggVorbis_File *)s->extHead2; 2315 } 2316 2317 started = 1; 2318 vorbis_info_init(&vi); 2319 if (((OggVorbis_File *)s->extHead2)->quality == -1.0) { 2320 ret = vorbis_encode_init(&vi, s->nchannels, s->samprate, of->maxbitrate, 2321 of->nombitrate, of->minbitrate); 2322 } else { 2323 ret = vorbis_encode_init_vbr(&vi, s->nchannels, s->samprate, 2324 of->quality); 2325 } 2326 2327 if (ret) { 2328 Tcl_AppendResult(interp, "vorbis_encode_init failed", (char *) NULL); 2329 return TCL_ERROR; 2330 } 2331 if (of->commList != NULL && n == 0) { 2332 Tcl_ListObjGetElements(interp, of->commList, &n, &listObj); 2333 } 2334 2335 if (n > 0) { 2336 int i; 2337 2338 vorbis_comment_init(&vc); 2339 for (i = 0; i < n; i++) { 2340 vorbis_comment_add(&vc, Tcl_GetStringFromObj(listObj[i], NULL)); 2341 } 2342 } 2343 2344 vorbis_analysis_init(&vd, &vi); 2345 vorbis_block_init(&vd, &vb); 2346 2347 srand(time(NULL)); 2348 ogg_stream_init(&os, rand()); 2349 2350 vorbis_analysis_headerout(&vd, &vc, &header, &header_comm, &header_code); 2351 ogg_stream_packetin(&os, &header); 2352 ogg_stream_packetin(&os, &header_comm); 2353 ogg_stream_packetin(&os, &header_code); 2354 2355 while (ogg_stream_flush(&os, &og) != 0) { 2356 if (Tcl_Write(ch, (char *) og.header, og.header_len) == -1) 2357 return TCL_ERROR; 2358 if (Tcl_Write(ch, (char *) og.body, og.body_len) == -1) 2359 return TCL_ERROR; 2360 } 2361 } 2362 s->headSize = 0; 2363 2364 if (s->debug > 2) Snack_WriteLog(" Exit PutOggHeader\n"); 2365 2366 return TCL_OK; 2367} 2368 2369static int 2370WriteOggSamples(Sound *s, Tcl_Channel ch, Tcl_Obj *obj, int start, int length) 2371{ 2372 int eos = 0, pos = start, end = start + length; 2373 long i, j, k; 2374 2375 if (s->debug > 2) Snack_WriteLogInt(" Enter WriteOggSamples", length); 2376 2377 while (pos < end) { 2378 float **buffer = vorbis_analysis_buffer(&vd, READBUFSIZE); 2379 2380 /* uninterleave samples */ 2381 Snack_GetSoundData(s, pos, pcmout, READBUFSIZE); 2382 for (i = 0, k = 0; i < READBUFSIZE / s->nchannels; i++) { 2383 for (j = 0; j < s->nchannels; j++, k++) { 2384 if (s->readStatus == READ) { 2385 buffer[j][i] = FSAMPLE(s, pos) / 32768.0f; 2386 } else { 2387 buffer[j][i] = pcmout[k] / 32768.0f; 2388 } 2389 pos++; 2390 if (pos > end && j == s->nchannels-1) break; 2391 } 2392 if (pos > end && j == s->nchannels-1) break; 2393 } 2394 2395 /* tell the library how much we actually submitted */ 2396 vorbis_analysis_wrote(&vd, i); 2397 } 2398 2399 while(vorbis_analysis_blockout(&vd, &vb)==1){ 2400 vorbis_analysis(&vb,NULL); 2401 vorbis_bitrate_addblock(&vb); 2402 2403 while(vorbis_bitrate_flushpacket(&vd,&op)) { 2404 ogg_stream_packetin(&os,&op); 2405 2406 while(!eos){ 2407 int result = ogg_stream_pageout(&os, &og); 2408 if (result == 0) break; 2409 if (Tcl_Write(ch, (char *) og.header, og.header_len) == -1) 2410 return TCL_ERROR; 2411 if (Tcl_Write(ch, (char *) og.body, og.body_len) == -1) 2412 return TCL_ERROR; 2413 2414 if (ogg_page_eos(&og)) eos=1; 2415 } 2416 } 2417 } 2418 2419 if (s->debug > 2) Snack_WriteLog(" Exit WriteOggSamples\n"); 2420 2421 return(length); 2422} 2423 2424void 2425FreeOggHeader(Sound *s) 2426{ 2427 if (s->debug > 2) Snack_WriteLog(" Enter FreeOggHeader\n"); 2428 2429 if (s->extHead2 != NULL) { 2430 /* To be cleared commList */ 2431 ckfree((char *)s->extHead2); 2432 s->extHead2 = NULL; 2433 s->extHead2Type = 0; 2434 } 2435 2436 if (s->debug > 2) Snack_WriteLog(" Exit FreeOggHeader\n"); 2437} 2438 2439int 2440ConfigOgg(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) 2441{ 2442 int arg, index; 2443 OggVorbis_File *of = (OggVorbis_File *)s->extHead2; 2444 static char *optionStrings[] = { 2445 "-comment", "-vendor", "-maxbitrate", "-minbitrate", "-nominalbitrate", 2446 "-quality", NULL 2447 }; 2448 enum options { 2449 COMMENT, VENDOR, MAX, MIN, NOMINAL, QUALITY 2450 }; 2451 2452 if (s->debug > 2) Snack_WriteLog(" Enter ConfigOgg\n"); 2453 2454 if (s->extHead2 != NULL && s->extHead2Type != SNACK_OGG_INT) { 2455 Snack_FileFormat *ff; 2456 2457 for (ff = Snack_GetFileFormats(); ff != NULL; ff = ff->nextPtr) { 2458 if (strcmp(s->fileType, ff->name) == 0) { 2459 if (ff->freeHeaderProc != NULL) { 2460 (ff->freeHeaderProc)(s); 2461 } 2462 } 2463 } 2464 } 2465 2466 if (s->extHead2 == NULL) { 2467 s->extHead2 = (char *) ckalloc(sizeof(OggVorbis_File)); 2468 s->extHead2Type = SNACK_OGG_INT; 2469 ((OggVorbis_File *)s->extHead2)->nombitrate = 128000; 2470 ((OggVorbis_File *)s->extHead2)->maxbitrate = -1; 2471 ((OggVorbis_File *)s->extHead2)->minbitrate = -1; 2472 ((OggVorbis_File *)s->extHead2)->quality = -1.0; 2473 ((OggVorbis_File *)s->extHead2)->commList = NULL; 2474 ((OggVorbis_File *)s->extHead2)->vendor = NULL; 2475 of = (OggVorbis_File *)s->extHead2; 2476 } 2477 2478 if (objc < 3) return 0; 2479 2480 2481 if (objc == 3) { /* get option */ 2482 if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, "option", 0, 2483 &index) != TCL_OK) { 2484 Tcl_AppendResult(interp, ", or\n", NULL); 2485 return 0; 2486 } 2487 2488 switch ((enum options) index) { 2489 case COMMENT: 2490 { 2491 Tcl_SetObjResult(interp, of->commList); 2492 break; 2493 } 2494 case VENDOR: 2495 { 2496 Tcl_SetObjResult(interp, of->vendor); 2497 break; 2498 } 2499 case MAX: 2500 { 2501 Tcl_SetObjResult(interp, Tcl_NewIntObj(of->maxbitrate)); 2502 break; 2503 } 2504 case MIN: 2505 { 2506 Tcl_SetObjResult(interp, Tcl_NewIntObj(of->minbitrate)); 2507 break; 2508 } 2509 case NOMINAL: 2510 { 2511 Tcl_SetObjResult(interp, Tcl_NewIntObj(of->nombitrate)); 2512 break; 2513 } 2514 case QUALITY: 2515 { 2516 Tcl_SetObjResult(interp, Tcl_NewDoubleObj(of->quality)); 2517 break; 2518 } 2519 } 2520 } else { /* set option */ 2521 for (arg = 2; arg < objc; arg+=2) { 2522 int index; 2523 2524 if (Tcl_GetIndexFromObj(interp, objv[arg], optionStrings, "option", 0, 2525 &index) != TCL_OK) { 2526 return 0; 2527 } 2528 2529 if (arg + 1 == objc) { 2530 Tcl_AppendResult(interp, "No argument given for ", 2531 optionStrings[index], " option\n", (char *) NULL); 2532 return 0; 2533 } 2534 2535 switch ((enum options) index) { 2536 case COMMENT: 2537 { 2538 int i, n; 2539 Tcl_Obj **listObj; 2540 2541 if (Tcl_ListObjGetElements(interp, objv[arg+1], &n, &listObj) != 2542 TCL_OK) { 2543 return 0; 2544 } 2545 /* To be cleared commList */ 2546 of->commList = Tcl_NewListObj(0, NULL); 2547 for (i = 0; i < n; i++) { 2548 Tcl_ListObjAppendElement(interp, of->commList, listObj[i]); 2549 } 2550 break; 2551 } 2552 case MAX: 2553 { 2554 if (Tcl_GetIntFromObj(interp,objv[arg+1], &of->maxbitrate) != TCL_OK) 2555 return 0; 2556 break; 2557 } 2558 case MIN: 2559 { 2560 if (Tcl_GetIntFromObj(interp,objv[arg+1], &of->minbitrate) != TCL_OK) 2561 return 0; 2562 break; 2563 } 2564 case NOMINAL: 2565 { 2566 if (Tcl_GetIntFromObj(interp,objv[arg+1], &of->nombitrate) != TCL_OK) 2567 return 0; 2568 break; 2569 } 2570 case QUALITY: 2571 { 2572 if (Tcl_GetDoubleFromObj(interp, objv[arg+1], &of->quality) !=TCL_OK) 2573 return 0; 2574 break; 2575 } 2576 } 2577 } 2578 } 2579 2580 if (s->debug > 2) Snack_WriteLog(" Exit ConfigOgg\n"); 2581 2582 return 1; 2583} 2584 2585#define OGGFILE_VERSION "1.3" 2586 2587Snack_FileFormat snackOggFormat = { 2588 OGG_STRING, 2589 GuessOggFile, 2590 GetOggHeader, 2591 ExtOggFile, 2592 PutOggHeader, 2593 OpenOggFile, 2594 CloseOggFile, 2595 ReadOggSamples, 2596 WriteOggSamples, 2597 SeekOggFile, 2598 FreeOggHeader, 2599 ConfigOgg, 2600 (Snack_FileFormat *) NULL 2601}; 2602 2603/* Called by "load libsnackogg" */ 2604EXPORT(int, Snackogg_Init) _ANSI_ARGS_((Tcl_Interp *interp)) 2605{ 2606 int res; 2607 2608#ifdef USE_TCL_STUBS 2609 if (Tcl_InitStubs(interp, "8", 0) == NULL) { 2610 return TCL_ERROR; 2611 } 2612#endif 2613 2614#ifdef USE_SNACK_STUBS 2615 if (Snack_InitStubs(interp, "2", 0) == NULL) { 2616 return TCL_ERROR; 2617 } 2618#endif 2619 2620 res = Tcl_PkgProvide(interp, "snackogg", OGGFILE_VERSION); 2621 2622 if (res != TCL_OK) return res; 2623 2624 Tcl_SetVar(interp, "snack::snackogg", OGGFILE_VERSION, TCL_GLOBAL_ONLY); 2625 2626 Snack_CreateFileFormat(&snackOggFormat); 2627 2628 return TCL_OK; 2629} 2630 2631EXPORT(int, Snackogg_SafeInit)(Tcl_Interp *interp) 2632{ 2633 return Snackogg_Init(interp); 2634} 2635