diff options
author | Monroe Williams <monroe@lindenlab.com> | 2009-08-27 19:00:18 +0000 |
---|---|---|
committer | Monroe Williams <monroe@lindenlab.com> | 2009-08-27 19:00:18 +0000 |
commit | 745845f79987e4b4ab7f5728746a0eda8898930f (patch) | |
tree | f10efd4a638a6a7eda92a960cdb97e5256ff736a /indra/llaudio/llvorbisencode.cpp | |
parent | 71344b233d5ae3d5262a492b636af04544952611 (diff) |
svn merge -r 129841:129910 svn+ssh://svn.lindenlab.com/svn/linden/branches/moss/pluginapi_05-merge@129910
svn merge -r 129913:131718 svn+ssh://svn.lindenlab.com/svn/linden/branches/pluginapi/pluginapi_05
Some branch shenannigans in the pluginapi_05 branch caused this to become a two-part merge.
Diffstat (limited to 'indra/llaudio/llvorbisencode.cpp')
-rw-r--r-- | indra/llaudio/llvorbisencode.cpp | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/indra/llaudio/llvorbisencode.cpp b/indra/llaudio/llvorbisencode.cpp new file mode 100644 index 0000000000..8ee082a245 --- /dev/null +++ b/indra/llaudio/llvorbisencode.cpp @@ -0,0 +1,505 @@ +/** + * @file vorbisencode.cpp + * @brief Vorbis encoding routine routine for Indra. + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "vorbis/vorbisenc.h" + +#include "llvorbisencode.h" +#include "llerror.h" +#include "llrand.h" +#include "llmath.h" +#include "llapr.h" + +//#if LL_DARWIN +// MBW -- XXX -- Getting rid of SecondLifeVorbis for now -- no fmod means no name collisions. +#if 0 +#include "VorbisFramework.h" + +#define vorbis_analysis mac_vorbis_analysis +#define vorbis_analysis_headerout mac_vorbis_analysis_headerout +#define vorbis_analysis_init mac_vorbis_analysis_init +#define vorbis_encode_ctl mac_vorbis_encode_ctl +#define vorbis_encode_setup_init mac_vorbis_encode_setup_init +#define vorbis_encode_setup_managed mac_vorbis_encode_setup_managed + +#define vorbis_info_init mac_vorbis_info_init +#define vorbis_info_clear mac_vorbis_info_clear +#define vorbis_comment_init mac_vorbis_comment_init +#define vorbis_comment_clear mac_vorbis_comment_clear +#define vorbis_block_init mac_vorbis_block_init +#define vorbis_block_clear mac_vorbis_block_clear +#define vorbis_dsp_clear mac_vorbis_dsp_clear +#define vorbis_analysis_buffer mac_vorbis_analysis_buffer +#define vorbis_analysis_wrote mac_vorbis_analysis_wrote +#define vorbis_analysis_blockout mac_vorbis_analysis_blockout + +#define ogg_stream_packetin mac_ogg_stream_packetin +#define ogg_stream_init mac_ogg_stream_init +#define ogg_stream_flush mac_ogg_stream_flush +#define ogg_stream_pageout mac_ogg_stream_pageout +#define ogg_page_eos mac_ogg_page_eos +#define ogg_stream_clear mac_ogg_stream_clear + +#endif + +S32 check_for_invalid_wav_formats(const std::string& in_fname, std::string& error_msg) +{ + U16 num_channels = 0; + U32 sample_rate = 0; + U32 bits_per_sample = 0; + U32 physical_file_size = 0; + U32 chunk_length = 0; + U32 raw_data_length = 0; + U32 bytes_per_sec = 0; + BOOL uncompressed_pcm = FALSE; + + unsigned char wav_header[44]; /*Flawfinder: ignore*/ + + error_msg.clear(); + + //******************************** + LLAPRFile infile ; + infile.open(in_fname,LL_APR_RB); + //******************************** + if (!infile.getFileHandle()) + { + error_msg = "CannotUploadSoundFile"; + return(LLVORBISENC_SOURCE_OPEN_ERR); + } + + infile.read(wav_header, 44); + physical_file_size = infile.seek(APR_END,0); + + if (strncmp((char *)&(wav_header[0]),"RIFF",4)) + { + error_msg = "SoundFileNotRIFF"; + return(LLVORBISENC_WAV_FORMAT_ERR); + } + + if (strncmp((char *)&(wav_header[8]),"WAVE",4)) + { + error_msg = "SoundFileNotRIFF"; + return(LLVORBISENC_WAV_FORMAT_ERR); + } + + // parse the chunks + + U32 file_pos = 12; // start at the first chunk (usually fmt but not always) + + while ((file_pos + 8)< physical_file_size) + { + infile.seek(APR_SET,file_pos); + infile.read(wav_header, 44); + + chunk_length = ((U32) wav_header[7] << 24) + + ((U32) wav_header[6] << 16) + + ((U32) wav_header[5] << 8) + + wav_header[4]; + +// llinfos << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << llendl; + + if (!(strncmp((char *)&(wav_header[0]),"fmt ",4))) + { + if ((wav_header[8] == 0x01) && (wav_header[9] == 0x00)) + { + uncompressed_pcm = TRUE; + } + num_channels = ((U16) wav_header[11] << 8) + wav_header[10]; + sample_rate = ((U32) wav_header[15] << 24) + + ((U32) wav_header[14] << 16) + + ((U32) wav_header[13] << 8) + + wav_header[12]; + bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22]; + bytes_per_sec = ((U32) wav_header[19] << 24) + + ((U32) wav_header[18] << 16) + + ((U32) wav_header[17] << 8) + + wav_header[16]; + } + else if (!(strncmp((char *)&(wav_header[0]),"data",4))) + { + raw_data_length = chunk_length; + } + file_pos += (chunk_length + 8); + chunk_length = 0; + } + //**************** + infile.close(); + //**************** + + if (!uncompressed_pcm) + { + error_msg = "SoundFileNotPCM"; + return(LLVORBISENC_PCM_FORMAT_ERR); + } + + if ((num_channels < 1) || (num_channels > 2)) + { + error_msg = "SoundFileInvalidChannelCount"; + return(LLVORBISENC_MULTICHANNEL_ERR); + } + + if (sample_rate != 44100) + { + error_msg = "SoundFileInvalidSampleRate"; + return(LLVORBISENC_UNSUPPORTED_SAMPLE_RATE); + } + + if ((bits_per_sample != 16) && (bits_per_sample != 8)) + { + error_msg = "SoundFileInvalidWordSize"; + return(LLVORBISENC_UNSUPPORTED_WORD_SIZE); + } + + if (!raw_data_length) + { + error_msg = "SoundFileInvalidHeader"; + return(LLVORBISENC_CLIP_TOO_LONG); + } + + F32 clip_length = (F32)raw_data_length/(F32)bytes_per_sec; + + if (clip_length > 10.0f) + { + error_msg = "SoundFileInvalidTooLong"; + return(LLVORBISENC_CLIP_TOO_LONG); + } + + return(LLVORBISENC_NOERR); +} + +S32 encode_vorbis_file(const std::string& in_fname, const std::string& out_fname) +{ +#define READ_BUFFER 1024 + unsigned char readbuffer[READ_BUFFER*4+44]; /* out of the data segment, not the stack */ /*Flawfinder: ignore*/ + + ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */ + ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */ + ogg_packet op; /* one raw packet of data for decode */ + + vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */ + vorbis_comment vc; /* struct that stores all the user comments */ + + vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */ + vorbis_block vb; /* local working space for packet->PCM decode */ + + int eos=0; + int result; + + U16 num_channels = 0; + U32 sample_rate = 0; + U32 bits_per_sample = 0; + + S32 format_error = 0; + std::string error_msg; + if ((format_error = check_for_invalid_wav_formats(in_fname, error_msg))) + { + llwarns << error_msg << ": " << in_fname << llendl; + return(format_error); + } + +#if 1 + unsigned char wav_header[44]; /*Flawfinder: ignore*/ + + S32 data_left = 0; + + LLAPRFile infile ; + infile.open(in_fname,LL_APR_RB); + if (!infile.getFileHandle()) + { + llwarns << "Couldn't open temporary ogg file for writing: " << in_fname + << llendl; + return(LLVORBISENC_SOURCE_OPEN_ERR); + } + + LLAPRFile outfile ; + outfile.open(out_fname,LL_APR_WPB); + if (!outfile.getFileHandle()) + { + llwarns << "Couldn't open upload sound file for reading: " << in_fname + << llendl; + return(LLVORBISENC_DEST_OPEN_ERR); + } + + // parse the chunks + U32 chunk_length = 0; + U32 file_pos = 12; // start at the first chunk (usually fmt but not always) + + while (infile.eof() != APR_EOF) + { + infile.seek(APR_SET,file_pos); + infile.read(wav_header, 44); + + chunk_length = ((U32) wav_header[7] << 24) + + ((U32) wav_header[6] << 16) + + ((U32) wav_header[5] << 8) + + wav_header[4]; + +// llinfos << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << llendl; + + if (!(strncmp((char *)&(wav_header[0]),"fmt ",4))) + { + num_channels = ((U16) wav_header[11] << 8) + wav_header[10]; + sample_rate = ((U32) wav_header[15] << 24) + + ((U32) wav_header[14] << 16) + + ((U32) wav_header[13] << 8) + + wav_header[12]; + bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22]; + } + else if (!(strncmp((char *)&(wav_header[0]),"data",4))) + { + infile.seek(APR_SET,file_pos+8); + // leave the file pointer at the beginning of the data chunk data + data_left = chunk_length; + break; + } + file_pos += (chunk_length + 8); + chunk_length = 0; + } + + + /********** Encode setup ************/ + + /* choose an encoding mode */ + /* (mode 0: 44kHz stereo uncoupled, roughly 128kbps VBR) */ + vorbis_info_init(&vi); + + // always encode to mono + + // SL-52913 & SL-53779 determined this quality level to be our 'good + // enough' general-purpose quality level with a nice low bitrate. + // Equivalent to oggenc -q0.5 + F32 quality = 0.05f; +// quality = (bitrate==128000 ? 0.4f : 0.1); + +// if (vorbis_encode_init(&vi, /* num_channels */ 1 ,sample_rate, -1, bitrate, -1)) + if (vorbis_encode_init_vbr(&vi, /* num_channels */ 1 ,sample_rate, quality)) +// if (vorbis_encode_setup_managed(&vi,1,sample_rate,-1,bitrate,-1) || +// vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE_AVG,NULL) || +// vorbis_encode_setup_init(&vi)) + { + llwarns << "unable to initialize vorbis codec at quality " << quality << llendl; + // llwarns << "unable to initialize vorbis codec at bitrate " << bitrate << llendl; + return(LLVORBISENC_DEST_OPEN_ERR); + } + + /* add a comment */ + vorbis_comment_init(&vc); +// vorbis_comment_add(&vc,"Linden"); + + /* set up the analysis state and auxiliary encoding storage */ + vorbis_analysis_init(&vd,&vi); + vorbis_block_init(&vd,&vb); + + /* set up our packet->stream encoder */ + /* pick a random serial number; that way we can more likely build + chained streams just by concatenation */ + ogg_stream_init(&os, ll_rand()); + + /* Vorbis streams begin with three headers; the initial header (with + most of the codec setup parameters) which is mandated by the Ogg + bitstream spec. The second header holds any comment fields. The + third header holds the bitstream codebook. We merely need to + make the headers, then pass them to libvorbis one at a time; + libvorbis handles the additional Ogg bitstream constraints */ + + { + ogg_packet header; + ogg_packet header_comm; + ogg_packet header_code; + + vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code); + ogg_stream_packetin(&os,&header); /* automatically placed in its own + page */ + ogg_stream_packetin(&os,&header_comm); + ogg_stream_packetin(&os,&header_code); + + /* We don't have to write out here, but doing so makes streaming + * much easier, so we do, flushing ALL pages. This ensures the actual + * audio data will start on a new page + */ + while(!eos){ + int result=ogg_stream_flush(&os,&og); + if(result==0)break; + outfile.write(og.header, og.header_len); + outfile.write(og.body, og.body_len); + } + + } + + + while(!eos) + { + long bytes_per_sample = bits_per_sample/8; + + long bytes=(long)infile.read(readbuffer,llclamp((S32)(READ_BUFFER*num_channels*bytes_per_sample),0,data_left)); /* stereo hardwired here */ + + if (bytes==0) + { + /* end of file. this can be done implicitly in the mainline, + but it's easier to see here in non-clever fashion. + Tell the library we're at end of stream so that it can handle + the last frame and mark end of stream in the output properly */ + + vorbis_analysis_wrote(&vd,0); +// eos = 1; + + } + else + { + long i; + long samples; + int temp; + + data_left -= bytes; + /* data to encode */ + + /* expose the buffer to submit data */ + float **buffer=vorbis_analysis_buffer(&vd,READ_BUFFER); + + i = 0; + samples = bytes / (num_channels * bytes_per_sample); + + if (num_channels == 2) + { + if (bytes_per_sample == 2) + { + /* uninterleave samples */ + for(i=0; i<samples ;i++) + { + temp = ((signed char *)readbuffer)[i*4+1]; /*Flawfinder: ignore*/ + temp += ((signed char *)readbuffer)[i*4+3]; /*Flawfinder: ignore*/ + temp <<= 8; + temp += readbuffer[i*4]; + temp += readbuffer[i*4+2]; + + buffer[0][i] = ((float)temp) / 65536.f; + } + } + else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard") + { + /* uninterleave samples */ + for(i=0; i<samples ;i++) + { + temp = readbuffer[i*2+0]; + temp += readbuffer[i*2+1]; + temp -= 256; + buffer[0][i] = ((float)temp) / 256.f; + } + } + } + else if (num_channels == 1) + { + if (bytes_per_sample == 2) + { + for(i=0; i < samples ;i++) + { + temp = ((signed char*)readbuffer)[i*2+1]; + temp <<= 8; + temp += readbuffer[i*2]; + buffer[0][i] = ((float)temp) / 32768.f; + } + } + else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard") + { + for(i=0; i < samples ;i++) + { + temp = readbuffer[i]; + temp -= 128; + buffer[0][i] = ((float)temp) / 128.f; + } + } + } + + /* tell the library how much we actually submitted */ + vorbis_analysis_wrote(&vd,i); + } + + /* vorbis does some data preanalysis, then divvies up blocks for + more involved (potentially parallel) processing. Get a single + block for encoding now */ + while(vorbis_analysis_blockout(&vd,&vb)==1) + { + + /* analysis */ + /* Do the main analysis, creating a packet */ + vorbis_analysis(&vb, NULL); + vorbis_bitrate_addblock(&vb); + + while(vorbis_bitrate_flushpacket(&vd, &op)) + { + + /* weld the packet into the bitstream */ + ogg_stream_packetin(&os,&op); + + /* write out pages (if any) */ + while(!eos) + { + result = ogg_stream_pageout(&os,&og); + + if(result==0) + break; + + outfile.write(og.header, og.header_len); + outfile.write(og.body, og.body_len); + + /* this could be set above, but for illustrative purposes, I do + it here (to show that vorbis does know where the stream ends) */ + + if(ogg_page_eos(&og)) + eos=1; + + } + } + } + } + + + + /* clean up and exit. vorbis_info_clear() must be called last */ + + ogg_stream_clear(&os); + vorbis_block_clear(&vb); + vorbis_dsp_clear(&vd); + vorbis_comment_clear(&vc); + vorbis_info_clear(&vi); + + /* ogg_page and ogg_packet structs always point to storage in + libvorbis. They're never freed or manipulated directly */ + +// fprintf(stderr,"Vorbis encoding: Done.\n"); + llinfos << "Vorbis encoding: Done." << llendl; + +#endif + return(LLVORBISENC_NOERR); + +} |