ref: f14a85c2d88e95ed6fc73bb9fc2b11c28a732e53
dir: /src/vorbis.c/
/* * Ogg Vorbis sound format driver * Copyright 2001, Stan Seibert <indigo@aztec.asu.edu> * * Portions from oggenc, (c) Michael Smith <msmith@labyrinth.net.au>, * ogg123, (c) Kenneth Arnold <kcarnold@yahoo.com>, and * libvorbisfile (c) Xiphophorus Company * * May 9, 2001 - Stan Seibert (indigo@aztec.asu.edu) * Ogg Vorbis driver initially written. * * July 5, 1991 - Skeleton file * Copyright 1991 Lance Norskog And Sundry Contributors * This source code is freely redistributable and may be used for * any purpose. This copyright notice must be maintained. * Lance Norskog And Sundry Contributors are not responsible for * the consequences of using this software. * * TODO: When reading in comments, it doesn't understand how to read * more then one comment and doesn't know how to parse KEY=value. */ #include "sox_i.h" #if defined HAVE_LIBVORBISENC && defined HAVE_LIBVORBISFILE #include <stdio.h> #include <math.h> #include <string.h> #include <errno.h> #include <ogg/ogg.h> #include <vorbis/codec.h> #include <vorbis/vorbisfile.h> #include <vorbis/vorbisenc.h> #define DEF_BUF_LEN 4096 #define BUF_ERROR -1 #define BUF_EOF 0 #define BUF_DATA 1 #define HEADER_ERROR 0 #define HEADER_OK 1 /* Private data for Ogg Vorbis file */ typedef struct vorbis_enc { ogg_stream_state os; ogg_page og; ogg_packet op; vorbis_dsp_state vd; vorbis_block vb; vorbis_info vi; } vorbis_enc_t; typedef struct vorbisstuff { /* Decoding data */ OggVorbis_File *vf; char *buf; sox_size_t buf_len; sox_size_t start; sox_size_t end; /* Unsent data samples in buf[start] through buf[end-1] */ int current_section; int eof; vorbis_enc_t *vorbis_enc_data; } *vorbis_t; /******** Callback functions used in ov_open_callbacks ************/ static int myclose (void *datasource UNUSED) { /* Do nothing so sox can close the file for us */ return 0; } static int _fseeko64_wrap(FILE *f, ogg_int64_t off, int whence) { int ret = fseeko(f, off, whence); if (ret == EBADF) ret = -1; return ret; } /********************* End callbacks *****************************/ /* * Do anything required before you start reading samples. * Read file header. * Find out sampling rate, * size and encoding of samples, * mono/stereo/quad. */ static int sox_vorbisstartread(ft_t ft) { vorbis_t vb = (vorbis_t) ft->priv; vorbis_info *vi; vorbis_comment *vc; sox_size_t comment_size; int i, offset; ov_callbacks callbacks = { (size_t (*)(void *, size_t, size_t, void *)) fread, (int (*)(void *, ogg_int64_t, int)) _fseeko64_wrap, (int (*)(void *)) myclose, (long (*)(void *)) ftello }; /* Allocate space for decoding structure */ vb->vf = (OggVorbis_File *)xmalloc(sizeof(OggVorbis_File)); /* Init the decoder */ if (ov_open_callbacks((void *)ft->fp,vb->vf,NULL,0,callbacks) < 0) { sox_fail_errno(ft,SOX_EHDR, "Input not an Ogg Vorbis audio stream"); return (SOX_EOF); } /* Get info about the Ogg Vorbis stream */ vi = ov_info(vb->vf, -1); vc = ov_comment(vb->vf, -1); /* Record audio info */ ft->signal.rate = vi->rate; ft->signal.size = SOX_SIZE_16BIT; ft->signal.encoding = SOX_ENCODING_VORBIS; ft->signal.channels = vi->channels; /* ov_pcm_total doesn't work on non-seekable files so * skip that step in that case. Also, it reports * "frame"-ish results so we must * channels. */ if (ft->seekable) ft->length = ov_pcm_total(vb->vf, -1) * ft->signal.channels; /* Record comments */ if (vc->comments == 0) ft->comment = NULL; else { comment_size = 0; for (i = 0; i < vc->comments; i++) comment_size += vc->comment_lengths[i] + 1; ft->comment = (char *)xcalloc(comment_size, sizeof(char)); offset = 0; for (i = 0; i < vc->comments; i++) { strncpy(ft->comment + offset, vc->user_comments[i], (size_t)vc->comment_lengths[i]); offset += vc->comment_lengths[i]; ft->comment[offset] = '\n'; offset++; } /* On last comment, end string by overwriting last \n */ if (offset > 0) offset--; ft->comment[offset] = 0; } /* Setup buffer */ vb->buf_len = DEF_BUF_LEN; vb->buf = xcalloc(vb->buf_len, sizeof(char)); vb->start = vb->end = 0; /* Fill in other info */ vb->eof = 0; vb->current_section = -1; return (SOX_SUCCESS); } /* Refill the buffer with samples. Returns BUF_EOF if the end of the vorbis data was reached while the buffer was being filled, BUF_ERROR is something bad happens, and BUF_DATA otherwise */ static int refill_buffer (vorbis_t vb) { int num_read; if (vb->start == vb->end) /* Samples all played */ vb->start = vb->end = 0; while (vb->end < vb->buf_len) { num_read = ov_read(vb->vf, vb->buf + vb->end, (int)(vb->buf_len - vb->end), 0, 2, 1, &vb->current_section); if (num_read == 0) return (BUF_EOF); else if (num_read == OV_HOLE) sox_warn("Warning: hole in stream; probably harmless"); else if (num_read < 0) return (BUF_ERROR); else vb->end += num_read; } return (BUF_DATA); } /* * Read up to len samples from file. * Convert to signed longs. * Place in buf[]. * Return number of samples read. */ static sox_size_t sox_vorbisread(ft_t ft, sox_ssample_t *buf, sox_size_t len) { vorbis_t vb = (vorbis_t) ft->priv; sox_size_t i; int ret; sox_ssample_t l; for(i = 0; i < len; i++) { if (vb->start == vb->end) { if (vb->eof) break; ret = refill_buffer(vb); if (ret == BUF_EOF || ret == BUF_ERROR) { vb->eof = 1; if (vb->end == 0) break; } } l = (vb->buf[vb->start+1]<<24) | (0xffffff & (vb->buf[vb->start]<<16)); *(buf + i) = l; vb->start += 2; } return i; } /* * Do anything required when you stop reading samples. * Don't close input file! */ static int sox_vorbisstopread(ft_t ft) { vorbis_t vb = (vorbis_t) ft->priv; free(vb->buf); ov_clear(vb->vf); return (SOX_SUCCESS); } /* Write a page of ogg data to a file. Taken directly from encode.c in oggenc. Returns the number of bytes written. */ static int oe_write_page(ogg_page *page, ft_t ft) { int written; written = sox_writebuf(ft, page->header,(sox_size_t)page->header_len); written += sox_writebuf(ft, page->body,(sox_size_t)page->body_len); return written; } /* Write out the header packets. Derived mostly from encode.c in oggenc. Returns HEADER_ERROR if the header cannot be written and HEADER_OK otherwise. */ static int write_vorbis_header(ft_t ft, vorbis_enc_t *ve) { ogg_packet header_main; ogg_packet header_comments; ogg_packet header_codebooks; vorbis_comment vc; int result; int ret; char *comment; /* Make the comment structure */ vc.user_comments = (char **)xcalloc(1, sizeof(char *)); vc.comment_lengths = (int *)xcalloc(1, sizeof(int)); vc.comments = 1; /* We check if there is a FIELD=value pair already in the comment * if not, add one */ if (strchr(ft->comment,'=') == NULL) { comment = (char *)xcalloc(1,strlen(ft->comment)+strlen("COMMENT=")+1); strncpy(comment,"COMMENT=",strlen("COMMENT=")); } else comment = (char *)xcalloc(1,strlen(ft->comment)+1); if (!comment) return HEADER_ERROR; strcat(comment,ft->comment); vc.user_comments[0] = comment; vc.comment_lengths[0] = strlen(comment); /* Build the packets */ vorbis_analysis_headerout(&ve->vd,&vc, &header_main, &header_comments, &header_codebooks); /* And stream them out */ ogg_stream_packetin(&ve->os,&header_main); ogg_stream_packetin(&ve->os,&header_comments); ogg_stream_packetin(&ve->os,&header_codebooks); while((result = ogg_stream_flush(&ve->os, &ve->og))) { if (!result) break; ret = oe_write_page(&ve->og, ft); if (!ret) { free(comment); return HEADER_ERROR; } } free(comment); return HEADER_OK; } static int sox_vorbisstartwrite(ft_t ft) { vorbis_t vb = (vorbis_t) ft->priv; vorbis_enc_t *ve; long rate; double quality = 3; /* Default compression quality gives ~112kbps */ ft->signal.size = SOX_SIZE_16BIT; ft->signal.encoding = SOX_ENCODING_VORBIS; /* Allocate memory for all of the structures */ ve = vb->vorbis_enc_data = (vorbis_enc_t *)xmalloc(sizeof(vorbis_enc_t)); vorbis_info_init(&ve->vi); /* TODO */ rate = ft->signal.rate; if (rate) sox_fail_errno(ft, SOX_EHDR, "Error setting up Ogg Vorbis encorder - make sure you've specied a sane rate and number of channels"); /* Use encoding to average bit rate of VBR as specified by the -C option */ if (ft->signal.compression != HUGE_VAL) { if (ft->signal.compression < -1 || ft->signal.compression > 10) { sox_fail_errno(ft,SOX_EINVAL, "Vorbis compression quality nust be between -1 and 10"); return SOX_EOF; } quality = ft->signal.compression; } vorbis_encode_init_vbr(&ve->vi, (int)ft->signal.channels, (int)ft->signal.rate, (float)(quality / 10)); vorbis_analysis_init(&ve->vd, &ve->vi); vorbis_block_init(&ve->vd, &ve->vb); ogg_stream_init(&ve->os, rand()); /* Random serial number */ if (write_vorbis_header(ft, ve) == HEADER_ERROR) { sox_fail_errno(ft,SOX_EHDR, "Error writing header for Ogg Vorbis audio stream"); return (SOX_EOF); } return(SOX_SUCCESS); } static sox_size_t sox_vorbiswrite(ft_t ft, const sox_ssample_t *buf, sox_size_t len) { vorbis_t vb = (vorbis_t) ft->priv; vorbis_enc_t *ve = vb->vorbis_enc_data; sox_size_t samples = len / ft->signal.channels; float **buffer = vorbis_analysis_buffer(&ve->vd, (int)samples); sox_size_t i, j; int ret; int eos = 0; /* Copy samples into vorbis buffer */ for (i = 0; i < samples; i++) for (j = 0; j < ft->signal.channels; j++) buffer[j][i] = buf[i*ft->signal.channels + j] / ((float)SOX_SAMPLE_MAX); vorbis_analysis_wrote(&ve->vd, (int)samples); while(vorbis_analysis_blockout(&ve->vd,&ve->vb)==1) { /* Do the main analysis, creating a packet */ vorbis_analysis(&ve->vb, &ve->op); vorbis_bitrate_addblock(&ve->vb); /* Add packet to bitstream */ while (vorbis_bitrate_flushpacket(&ve->vd, &ve->op)) { ogg_stream_packetin(&ve->os,&ve->op); /* If we've gone over a page boundary, we can do actual * output, so do so (for however many pages are available) */ while(!eos) { int result = ogg_stream_pageout(&ve->os,&ve->og); if(!result) break; ret = oe_write_page(&ve->og, ft); if(!ret) return 0; if(ogg_page_eos(&ve->og)) eos = 1; } } } return (len); } static int sox_vorbisstopwrite(ft_t ft) { vorbis_t vb = (vorbis_t) ft->priv; vorbis_enc_t *ve = vb->vorbis_enc_data; /* Close out the remaining data */ sox_vorbiswrite(ft, NULL, 0); ogg_stream_clear(&ve->os); vorbis_block_clear(&ve->vb); vorbis_dsp_clear(&ve->vd); vorbis_info_clear(&ve->vi); return (SOX_SUCCESS); } static const char *vorbisnames[] = { "vorbis", "ogg", NULL }; static sox_format_t sox_vorbis_format = { vorbisnames, 0, sox_vorbisstartread, sox_vorbisread, sox_vorbisstopread, sox_vorbisstartwrite, sox_vorbiswrite, sox_vorbisstopwrite, sox_format_nothing_seek }; const sox_format_t *sox_vorbis_format_fn(void) { return &sox_vorbis_format; } #endif