1 /******************************************************************** 2 * * 3 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * 4 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * 5 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * 6 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * 7 * * 8 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2010 * 9 * by the Xiph.Org Foundation http://www.xiph.org/ * 10 * * 11 ******************************************************************** 12 13 function: maintain the info structure, info <-> header packets 14 last mod: $Id: info.c 17080 2010-03-26 06:59:58Z xiphmont $ 15 16 ********************************************************************/ 17 18 /* general handling of the header and the vorbis_info structure (and 19 substructures) */ 20 21 #include <stdlib.h> 22 #include <string.h> 23 #include <ctype.h> 24 #include <ogg/ogg.h> 25 #include "vorbis/codec.h" 26 #include "codec_internal.h" 27 #include "codebook.h" 28 #include "registry.h" 29 #include "window.h" 30 #include "psy.h" 31 #include "misc.h" 32 #include "os.h" 33 34 #define GENERAL_VENDOR_STRING "Xiph.Org libVorbis 1.3.1" 35 #define ENCODE_VENDOR_STRING "Xiph.Org libVorbis I 20100325 (Everywhere)" 36 37 /* helpers */ 38 static int ilog2(unsigned int v){ 39 int ret=0; 40 if(v)--v; 41 while(v){ 42 ret++; 43 v>>=1; 44 } 45 return(ret); 46 } 47 48 static void _v_writestring(oggpack_buffer *o,const char *s, int bytes){ 49 50 while(bytes--){ 51 oggpack_write(o,*s++,8); 52 } 53 } 54 55 static void _v_readstring(oggpack_buffer *o,char *buf,int bytes){ 56 while(bytes--){ 57 *buf++=oggpack_read(o,8); 58 } 59 } 60 61 void vorbis_comment_init(vorbis_comment *vc){ 62 memset(vc,0,sizeof(*vc)); 63 } 64 65 void vorbis_comment_add(vorbis_comment *vc,const char *comment){ 66 vc->user_comments=_ogg_realloc(vc->user_comments, 67 (vc->comments+2)*sizeof(*vc->user_comments)); 68 vc->comment_lengths=_ogg_realloc(vc->comment_lengths, 69 (vc->comments+2)*sizeof(*vc->comment_lengths)); 70 vc->comment_lengths[vc->comments]=strlen(comment); 71 vc->user_comments[vc->comments]=_ogg_malloc(vc->comment_lengths[vc->comments]+1); 72 strcpy(vc->user_comments[vc->comments], comment); 73 vc->comments++; 74 vc->user_comments[vc->comments]=NULL; 75 } 76 77 void vorbis_comment_add_tag(vorbis_comment *vc, const char *tag, const char *contents){ 78 char *comment=alloca(strlen(tag)+strlen(contents)+2); /* +2 for = and \0 */ 79 strcpy(comment, tag); 80 strcat(comment, "="); 81 strcat(comment, contents); 82 vorbis_comment_add(vc, comment); 83 } 84 85 /* This is more or less the same as strncasecmp - but that doesn't exist 86 * everywhere, and this is a fairly trivial function, so we include it */ 87 static int tagcompare(const char *s1, const char *s2, int n){ 88 int c=0; 89 while(c < n){ 90 if(toupper(s1[c]) != toupper(s2[c])) 91 return !0; 92 c++; 93 } 94 return 0; 95 } 96 97 char *vorbis_comment_query(vorbis_comment *vc, const char *tag, int count){ 98 long i; 99 int found = 0; 100 int taglen = strlen(tag)+1; /* +1 for the = we append */ 101 char *fulltag = alloca(taglen+ 1); 102 103 strcpy(fulltag, tag); 104 strcat(fulltag, "="); 105 106 for(i=0;i<vc->comments;i++){ 107 if(!tagcompare(vc->user_comments[i], fulltag, taglen)){ 108 if(count == found) 109 /* We return a pointer to the data, not a copy */ 110 return vc->user_comments[i] + taglen; 111 else 112 found++; 113 } 114 } 115 return NULL; /* didn't find anything */ 116 } 117 118 int vorbis_comment_query_count(vorbis_comment *vc, const char *tag){ 119 int i,count=0; 120 int taglen = strlen(tag)+1; /* +1 for the = we append */ 121 char *fulltag = alloca(taglen+1); 122 strcpy(fulltag,tag); 123 strcat(fulltag, "="); 124 125 for(i=0;i<vc->comments;i++){ 126 if(!tagcompare(vc->user_comments[i], fulltag, taglen)) 127 count++; 128 } 129 130 return count; 131 } 132 133 void vorbis_comment_clear(vorbis_comment *vc){ 134 if(vc){ 135 long i; 136 if(vc->user_comments){ 137 for(i=0;i<vc->comments;i++) 138 if(vc->user_comments[i])_ogg_free(vc->user_comments[i]); 139 _ogg_free(vc->user_comments); 140 } 141 if(vc->comment_lengths)_ogg_free(vc->comment_lengths); 142 if(vc->vendor)_ogg_free(vc->vendor); 143 memset(vc,0,sizeof(*vc)); 144 } 145 } 146 147 /* blocksize 0 is guaranteed to be short, 1 is guaranteed to be long. 148 They may be equal, but short will never ge greater than long */ 149 int vorbis_info_blocksize(vorbis_info *vi,int zo){ 150 codec_setup_info *ci = vi->codec_setup; 151 return ci ? ci->blocksizes[zo] : -1; 152 } 153 154 /* used by synthesis, which has a full, alloced vi */ 155 void vorbis_info_init(vorbis_info *vi){ 156 memset(vi,0,sizeof(*vi)); 157 vi->codec_setup=_ogg_calloc(1,sizeof(codec_setup_info)); 158 } 159 160 void vorbis_info_clear(vorbis_info *vi){ 161 codec_setup_info *ci=vi->codec_setup; 162 int i; 163 164 if(ci){ 165 166 for(i=0;i<ci->modes;i++) 167 if(ci->mode_param[i])_ogg_free(ci->mode_param[i]); 168 169 for(i=0;i<ci->maps;i++) /* unpack does the range checking */ 170 if(ci->map_param[i]) /* this may be cleaning up an aborted 171 unpack, in which case the below type 172 cannot be trusted */ 173 _mapping_P[ci->map_type[i]]->free_info(ci->map_param[i]); 174 175 for(i=0;i<ci->floors;i++) /* unpack does the range checking */ 176 if(ci->floor_param[i]) /* this may be cleaning up an aborted 177 unpack, in which case the below type 178 cannot be trusted */ 179 _floor_P[ci->floor_type[i]]->free_info(ci->floor_param[i]); 180 181 for(i=0;i<ci->residues;i++) /* unpack does the range checking */ 182 if(ci->residue_param[i]) /* this may be cleaning up an aborted 183 unpack, in which case the below type 184 cannot be trusted */ 185 _residue_P[ci->residue_type[i]]->free_info(ci->residue_param[i]); 186 187 for(i=0;i<ci->books;i++){ 188 if(ci->book_param[i]){ 189 /* knows if the book was not alloced */ 190 vorbis_staticbook_destroy(ci->book_param[i]); 191 } 192 if(ci->fullbooks) 193 vorbis_book_clear(ci->fullbooks+i); 194 } 195 if(ci->fullbooks) 196 _ogg_free(ci->fullbooks); 197 198 for(i=0;i<ci->psys;i++) 199 _vi_psy_free(ci->psy_param[i]); 200 201 _ogg_free(ci); 202 } 203 204 memset(vi,0,sizeof(*vi)); 205 } 206 207 /* Header packing/unpacking ********************************************/ 208 209 static int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb){ 210 codec_setup_info *ci=vi->codec_setup; 211 if(!ci)return(OV_EFAULT); 212 213 vi->version=oggpack_read(opb,32); 214 if(vi->version!=0)return(OV_EVERSION); 215 216 vi->channels=oggpack_read(opb,8); 217 vi->rate=oggpack_read(opb,32); 218 219 vi->bitrate_upper=oggpack_read(opb,32); 220 vi->bitrate_nominal=oggpack_read(opb,32); 221 vi->bitrate_lower=oggpack_read(opb,32); 222 223 ci->blocksizes[0]=1<<oggpack_read(opb,4); 224 ci->blocksizes[1]=1<<oggpack_read(opb,4); 225 226 if(vi->rate<1)goto err_out; 227 if(vi->channels<1)goto err_out; 228 if(ci->blocksizes[0]<64)goto err_out; 229 if(ci->blocksizes[1]<ci->blocksizes[0])goto err_out; 230 if(ci->blocksizes[1]>8192)goto err_out; 231 232 if(oggpack_read(opb,1)!=1)goto err_out; /* EOP check */ 233 234 return(0); 235 err_out: 236 vorbis_info_clear(vi); 237 return(OV_EBADHEADER); 238 } 239 240 static int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb){ 241 int i; 242 int vendorlen=oggpack_read(opb,32); 243 if(vendorlen<0)goto err_out; 244 if(vendorlen>opb->storage-8)goto err_out; 245 vc->vendor=_ogg_calloc(vendorlen+1,1); 246 _v_readstring(opb,vc->vendor,vendorlen); 247 i=oggpack_read(opb,32); 248 if(i<0)goto err_out; 249 if(i>((opb->storage-oggpack_bytes(opb))>>2))goto err_out; 250 vc->comments=i; 251 vc->user_comments=_ogg_calloc(vc->comments+1,sizeof(*vc->user_comments)); 252 vc->comment_lengths=_ogg_calloc(vc->comments+1, sizeof(*vc->comment_lengths)); 253 254 for(i=0;i<vc->comments;i++){ 255 int len=oggpack_read(opb,32); 256 if(len<0)goto err_out; 257 if(len>opb->storage-oggpack_bytes(opb))goto err_out; 258 vc->comment_lengths[i]=len; 259 vc->user_comments[i]=_ogg_calloc(len+1,1); 260 _v_readstring(opb,vc->user_comments[i],len); 261 } 262 if(oggpack_read(opb,1)!=1)goto err_out; /* EOP check */ 263 264 return(0); 265 err_out: 266 vorbis_comment_clear(vc); 267 return(OV_EBADHEADER); 268 } 269 270 /* all of the real encoding details are here. The modes, books, 271 everything */ 272 static int _vorbis_unpack_books(vorbis_info *vi,oggpack_buffer *opb){ 273 codec_setup_info *ci=vi->codec_setup; 274 int i; 275 if(!ci)return(OV_EFAULT); 276 277 /* codebooks */ 278 ci->books=oggpack_read(opb,8)+1; 279 if(ci->books<=0)goto err_out; 280 for(i=0;i<ci->books;i++){ 281 ci->book_param[i]=vorbis_staticbook_unpack(opb); 282 if(!ci->book_param[i])goto err_out; 283 } 284 285 /* time backend settings; hooks are unused */ 286 { 287 int times=oggpack_read(opb,6)+1; 288 if(times<=0)goto err_out; 289 for(i=0;i<times;i++){ 290 int test=oggpack_read(opb,16); 291 if(test<0 || test>=VI_TIMEB)goto err_out; 292 } 293 } 294 295 /* floor backend settings */ 296 ci->floors=oggpack_read(opb,6)+1; 297 if(ci->floors<=0)goto err_out; 298 for(i=0;i<ci->floors;i++){ 299 ci->floor_type[i]=oggpack_read(opb,16); 300 if(ci->floor_type[i]<0 || ci->floor_type[i]>=VI_FLOORB)goto err_out; 301 ci->floor_param[i]=_floor_P[ci->floor_type[i]]->unpack(vi,opb); 302 if(!ci->floor_param[i])goto err_out; 303 } 304 305 /* residue backend settings */ 306 ci->residues=oggpack_read(opb,6)+1; 307 if(ci->residues<=0)goto err_out; 308 for(i=0;i<ci->residues;i++){ 309 ci->residue_type[i]=oggpack_read(opb,16); 310 if(ci->residue_type[i]<0 || ci->residue_type[i]>=VI_RESB)goto err_out; 311 ci->residue_param[i]=_residue_P[ci->residue_type[i]]->unpack(vi,opb); 312 if(!ci->residue_param[i])goto err_out; 313 } 314 315 /* map backend settings */ 316 ci->maps=oggpack_read(opb,6)+1; 317 if(ci->maps<=0)goto err_out; 318 for(i=0;i<ci->maps;i++){ 319 ci->map_type[i]=oggpack_read(opb,16); 320 if(ci->map_type[i]<0 || ci->map_type[i]>=VI_MAPB)goto err_out; 321 ci->map_param[i]=_mapping_P[ci->map_type[i]]->unpack(vi,opb); 322 if(!ci->map_param[i])goto err_out; 323 } 324 325 /* mode settings */ 326 ci->modes=oggpack_read(opb,6)+1; 327 if(ci->modes<=0)goto err_out; 328 for(i=0;i<ci->modes;i++){ 329 ci->mode_param[i]=_ogg_calloc(1,sizeof(*ci->mode_param[i])); 330 ci->mode_param[i]->blockflag=oggpack_read(opb,1); 331 ci->mode_param[i]->windowtype=oggpack_read(opb,16); 332 ci->mode_param[i]->transformtype=oggpack_read(opb,16); 333 ci->mode_param[i]->mapping=oggpack_read(opb,8); 334 335 if(ci->mode_param[i]->windowtype>=VI_WINDOWB)goto err_out; 336 if(ci->mode_param[i]->transformtype>=VI_WINDOWB)goto err_out; 337 if(ci->mode_param[i]->mapping>=ci->maps)goto err_out; 338 if(ci->mode_param[i]->mapping<0)goto err_out; 339 } 340 341 if(oggpack_read(opb,1)!=1)goto err_out; /* top level EOP check */ 342 343 return(0); 344 err_out: 345 vorbis_info_clear(vi); 346 return(OV_EBADHEADER); 347 } 348 349 /* Is this packet a vorbis ID header? */ 350 int vorbis_synthesis_idheader(ogg_packet *op){ 351 oggpack_buffer opb; 352 char buffer[6]; 353 354 if(op){ 355 oggpack_readinit(&opb,op->packet,op->bytes); 356 357 if(!op->b_o_s) 358 return(0); /* Not the initial packet */ 359 360 if(oggpack_read(&opb,8) != 1) 361 return 0; /* not an ID header */ 362 363 memset(buffer,0,6); 364 _v_readstring(&opb,buffer,6); 365 if(memcmp(buffer,"vorbis",6)) 366 return 0; /* not vorbis */ 367 368 return 1; 369 } 370 371 return 0; 372 } 373 374 /* The Vorbis header is in three packets; the initial small packet in 375 the first page that identifies basic parameters, a second packet 376 with bitstream comments and a third packet that holds the 377 codebook. */ 378 379 int vorbis_synthesis_headerin(vorbis_info *vi,vorbis_comment *vc,ogg_packet *op){ 380 oggpack_buffer opb; 381 382 if(op){ 383 oggpack_readinit(&opb,op->packet,op->bytes); 384 385 /* Which of the three types of header is this? */ 386 /* Also verify header-ness, vorbis */ 387 { 388 char buffer[6]; 389 int packtype=oggpack_read(&opb,8); 390 memset(buffer,0,6); 391 _v_readstring(&opb,buffer,6); 392 if(memcmp(buffer,"vorbis",6)){ 393 /* not a vorbis header */ 394 return(OV_ENOTVORBIS); 395 } 396 switch(packtype){ 397 case 0x01: /* least significant *bit* is read first */ 398 if(!op->b_o_s){ 399 /* Not the initial packet */ 400 return(OV_EBADHEADER); 401 } 402 if(vi->rate!=0){ 403 /* previously initialized info header */ 404 return(OV_EBADHEADER); 405 } 406 407 return(_vorbis_unpack_info(vi,&opb)); 408 409 case 0x03: /* least significant *bit* is read first */ 410 if(vi->rate==0){ 411 /* um... we didn't get the initial header */ 412 return(OV_EBADHEADER); 413 } 414 415 return(_vorbis_unpack_comment(vc,&opb)); 416 417 case 0x05: /* least significant *bit* is read first */ 418 if(vi->rate==0 || vc->vendor==NULL){ 419 /* um... we didn;t get the initial header or comments yet */ 420 return(OV_EBADHEADER); 421 } 422 423 return(_vorbis_unpack_books(vi,&opb)); 424 425 default: 426 /* Not a valid vorbis header type */ 427 return(OV_EBADHEADER); 428 break; 429 } 430 } 431 } 432 return(OV_EBADHEADER); 433 } 434 435 /* pack side **********************************************************/ 436 437 static int _vorbis_pack_info(oggpack_buffer *opb,vorbis_info *vi){ 438 codec_setup_info *ci=vi->codec_setup; 439 if(!ci)return(OV_EFAULT); 440 441 /* preamble */ 442 oggpack_write(opb,0x01,8); 443 _v_writestring(opb,"vorbis", 6); 444 445 /* basic information about the stream */ 446 oggpack_write(opb,0x00,32); 447 oggpack_write(opb,vi->channels,8); 448 oggpack_write(opb,vi->rate,32); 449 450 oggpack_write(opb,vi->bitrate_upper,32); 451 oggpack_write(opb,vi->bitrate_nominal,32); 452 oggpack_write(opb,vi->bitrate_lower,32); 453 454 oggpack_write(opb,ilog2(ci->blocksizes[0]),4); 455 oggpack_write(opb,ilog2(ci->blocksizes[1]),4); 456 oggpack_write(opb,1,1); 457 458 return(0); 459 } 460 461 static int _vorbis_pack_comment(oggpack_buffer *opb,vorbis_comment *vc){ 462 int bytes = strlen(ENCODE_VENDOR_STRING); 463 464 /* preamble */ 465 oggpack_write(opb,0x03,8); 466 _v_writestring(opb,"vorbis", 6); 467 468 /* vendor */ 469 oggpack_write(opb,bytes,32); 470 _v_writestring(opb,ENCODE_VENDOR_STRING, bytes); 471 472 /* comments */ 473 474 oggpack_write(opb,vc->comments,32); 475 if(vc->comments){ 476 int i; 477 for(i=0;i<vc->comments;i++){ 478 if(vc->user_comments[i]){ 479 oggpack_write(opb,vc->comment_lengths[i],32); 480 _v_writestring(opb,vc->user_comments[i], vc->comment_lengths[i]); 481 }else{ 482 oggpack_write(opb,0,32); 483 } 484 } 485 } 486 oggpack_write(opb,1,1); 487 488 return(0); 489 } 490 491 static int _vorbis_pack_books(oggpack_buffer *opb,vorbis_info *vi){ 492 codec_setup_info *ci=vi->codec_setup; 493 int i; 494 if(!ci)return(OV_EFAULT); 495 496 oggpack_write(opb,0x05,8); 497 _v_writestring(opb,"vorbis", 6); 498 499 /* books */ 500 oggpack_write(opb,ci->books-1,8); 501 for(i=0;i<ci->books;i++) 502 if(vorbis_staticbook_pack(ci->book_param[i],opb))goto err_out; 503 504 /* times; hook placeholders */ 505 oggpack_write(opb,0,6); 506 oggpack_write(opb,0,16); 507 508 /* floors */ 509 oggpack_write(opb,ci->floors-1,6); 510 for(i=0;i<ci->floors;i++){ 511 oggpack_write(opb,ci->floor_type[i],16); 512 if(_floor_P[ci->floor_type[i]]->pack) 513 _floor_P[ci->floor_type[i]]->pack(ci->floor_param[i],opb); 514 else 515 goto err_out; 516 } 517 518 /* residues */ 519 oggpack_write(opb,ci->residues-1,6); 520 for(i=0;i<ci->residues;i++){ 521 oggpack_write(opb,ci->residue_type[i],16); 522 _residue_P[ci->residue_type[i]]->pack(ci->residue_param[i],opb); 523 } 524 525 /* maps */ 526 oggpack_write(opb,ci->maps-1,6); 527 for(i=0;i<ci->maps;i++){ 528 oggpack_write(opb,ci->map_type[i],16); 529 _mapping_P[ci->map_type[i]]->pack(vi,ci->map_param[i],opb); 530 } 531 532 /* modes */ 533 oggpack_write(opb,ci->modes-1,6); 534 for(i=0;i<ci->modes;i++){ 535 oggpack_write(opb,ci->mode_param[i]->blockflag,1); 536 oggpack_write(opb,ci->mode_param[i]->windowtype,16); 537 oggpack_write(opb,ci->mode_param[i]->transformtype,16); 538 oggpack_write(opb,ci->mode_param[i]->mapping,8); 539 } 540 oggpack_write(opb,1,1); 541 542 return(0); 543 err_out: 544 return(-1); 545 } 546 547 int vorbis_commentheader_out(vorbis_comment *vc, 548 ogg_packet *op){ 549 550 oggpack_buffer opb; 551 552 oggpack_writeinit(&opb); 553 if(_vorbis_pack_comment(&opb,vc)) return OV_EIMPL; 554 555 op->packet = _ogg_malloc(oggpack_bytes(&opb)); 556 memcpy(op->packet, opb.buffer, oggpack_bytes(&opb)); 557 558 op->bytes=oggpack_bytes(&opb); 559 op->b_o_s=0; 560 op->e_o_s=0; 561 op->granulepos=0; 562 op->packetno=1; 563 564 return 0; 565 } 566 567 int vorbis_analysis_headerout(vorbis_dsp_state *v, 568 vorbis_comment *vc, 569 ogg_packet *op, 570 ogg_packet *op_comm, 571 ogg_packet *op_code){ 572 int ret=OV_EIMPL; 573 vorbis_info *vi=v->vi; 574 oggpack_buffer opb; 575 private_state *b=v->backend_state; 576 577 if(!b){ 578 ret=OV_EFAULT; 579 goto err_out; 580 } 581 582 /* first header packet **********************************************/ 583 584 oggpack_writeinit(&opb); 585 if(_vorbis_pack_info(&opb,vi))goto err_out; 586 587 /* build the packet */ 588 if(b->header)_ogg_free(b->header); 589 b->header=_ogg_malloc(oggpack_bytes(&opb)); 590 memcpy(b->header,opb.buffer,oggpack_bytes(&opb)); 591 op->packet=b->header; 592 op->bytes=oggpack_bytes(&opb); 593 op->b_o_s=1; 594 op->e_o_s=0; 595 op->granulepos=0; 596 op->packetno=0; 597 598 /* second header packet (comments) **********************************/ 599 600 oggpack_reset(&opb); 601 if(_vorbis_pack_comment(&opb,vc))goto err_out; 602 603 if(b->header1)_ogg_free(b->header1); 604 b->header1=_ogg_malloc(oggpack_bytes(&opb)); 605 memcpy(b->header1,opb.buffer,oggpack_bytes(&opb)); 606 op_comm->packet=b->header1; 607 op_comm->bytes=oggpack_bytes(&opb); 608 op_comm->b_o_s=0; 609 op_comm->e_o_s=0; 610 op_comm->granulepos=0; 611 op_comm->packetno=1; 612 613 /* third header packet (modes/codebooks) ****************************/ 614 615 oggpack_reset(&opb); 616 if(_vorbis_pack_books(&opb,vi))goto err_out; 617 618 if(b->header2)_ogg_free(b->header2); 619 b->header2=_ogg_malloc(oggpack_bytes(&opb)); 620 memcpy(b->header2,opb.buffer,oggpack_bytes(&opb)); 621 op_code->packet=b->header2; 622 op_code->bytes=oggpack_bytes(&opb); 623 op_code->b_o_s=0; 624 op_code->e_o_s=0; 625 op_code->granulepos=0; 626 op_code->packetno=2; 627 628 oggpack_writeclear(&opb); 629 return(0); 630 err_out: 631 memset(op,0,sizeof(*op)); 632 memset(op_comm,0,sizeof(*op_comm)); 633 memset(op_code,0,sizeof(*op_code)); 634 635 if(b){ 636 oggpack_writeclear(&opb); 637 if(b->header)_ogg_free(b->header); 638 if(b->header1)_ogg_free(b->header1); 639 if(b->header2)_ogg_free(b->header2); 640 b->header=NULL; 641 b->header1=NULL; 642 b->header2=NULL; 643 } 644 return(ret); 645 } 646 647 double vorbis_granule_time(vorbis_dsp_state *v,ogg_int64_t granulepos){ 648 if(granulepos>=0) 649 return((double)granulepos/v->vi->rate); 650 return(-1); 651 } 652 653 const char *vorbis_version_string(void){ 654 return GENERAL_VENDOR_STRING; 655 } 656