// // tools/audwaveencoder/waveencoder_psn.cpp // // Copyright (C) 1999-2006 Rockstar Games. All Rights Reserved. // #include "stdafx.h" #include "forceinclude/win32_release.h" #ifdef __PPU #undef __PPU #endif // __PPU #define __PPU 1 #include "waveencoder_psn.h" typedef float FLOAT; #include "lame.h" #include "lame_global_flags.h" #include "math.h" using namespace audBuildCommon; using namespace Microsoft::Win32; using namespace System; using namespace System::IO; using namespace System::Runtime::InteropServices; namespace audWaveEncoding { const int g_NumValidSampleRates = 9; const int g_ValidSampleRates[g_NumValidSampleRates] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }; enum audMpegVersions { MPEG1, MPEG2, MPEG2_5 }; const int g_MpegVersionTable[] = { MPEG2_5, -1, MPEG2, MPEG1 }; enum audMpegLayers { LAYER1, LAYER2, LAYER3 }; const int g_MpegLayerTable[] = { -1, LAYER3, LAYER2, LAYER1 }; const int g_MpegSlotBytes[] = { 4, //LAYER1 1, //LAYER2 1 //LAYER3 }; const int g_Mpeg1BitrateTable[16][3] = { -1, -1, -1, 32, 32, 32, 64, 48, 40, 96, 56, 48, 128, 64, 56, 160, 80, 64, 192, 96, 80, 224, 112, 96, 256, 128, 112, 288, 160, 128, 320, 192, 160, 352, 224, 192, 384, 256, 224, 416, 320, 256, 448, 384, 320, -1, -1, -1 }; const int g_Mpeg2BitrateTable[16][3] = { -1, -1, -1, 32, 8, 8, 48, 16, 16, 56, 24, 24, 64, 32, 32, 80, 40, 40, 96, 48, 48, 112, 56, 56, 128, 64, 64, 144, 80, 80, 160, 96, 96, 176, 112, 112, 192, 128, 128, 224, 144, 144, 256, 160, 160, -1, -1, -1 }; const int g_MpegSampleRateTable[4][3] = { 44100, 22050, 11025, 48000, 24000, 12000, 32000, 16000, 8000, -1, -1, -1 }; static const unsigned int g_Mp3BlockSamples = 1152; //Align to the largest possible frame size for now. const int g_Mp3FileBufferBytes = 5*1024*1024; audWaveEncoderPSN::audWaveEncoderPSN(String * /*fileFullLocalPath*/) { } bool audWaveEncoderPSN::Encode ( audWaveMetadataBaseWrapper *waveMetadataIn, unsigned int waveSampleDataOffsetBytes, void **waveMetadataOut, unsigned int &waveMetadataOutLengthBytes, void **waveSampleDataOut, unsigned int &waveSampleDataOutLengthBytes, int compression, int &preloopPadding, unsigned int & ) { preloopPadding = 0; audWaveMetadataBase *waveBaseMetadataIn = waveMetadataIn->GetMetadata(); //Round input sample rate up to next valid MPEG setting and resample. int inputSampleRate = (int)waveBaseMetadataIn->sampleRate; int targetSampleRate = g_ValidSampleRates[0]; for(int i=0; iloopStartOffsetSamples >= 0) && !PreProcessLoop(waveBaseMetadataIn, preloopPadding)) { return false; } unsigned int samplesPerFrame; if(!EncodeMp3(waveBaseMetadataIn, waveSampleDataOut, waveSampleDataOutLengthBytes, compression, targetSampleRate, samplesPerFrame)) { return false; } //Generate a seek table by parsing the generated data. unsigned short *seekTable; unsigned int numFrames; if(!GenerateSeekTable((unsigned char *)(*waveSampleDataOut), waveSampleDataOutLengthBytes, samplesPerFrame, &seekTable, numFrames)) { delete[] *waveSampleDataOut; return false; } //Trim sample and seek table frames. unsigned short *trimmedSeekTable; PostProcessMp3Frames(waveBaseMetadataIn, waveSampleDataOut, waveSampleDataOutLengthBytes, samplesPerFrame, seekTable, numFrames, &trimmedSeekTable); //Write out to a test MP3 file. //WriteMp3File(waveBaseMetadataIn, *waveSampleDataOut, waveSampleDataOutLengthBytes); waveBaseMetadataIn->lengthSamples = numFrames * samplesPerFrame; audWaveMetadata encodedWaveMetadata; //Convert metadata to platform endianness. const unsigned int waveFlags = WAVEFLAGS_PSN_MP3; unsigned __int64 waveDataOffsetBytes = (unsigned __int64)waveSampleDataOffsetBytes; encodedWaveMetadata.base.waveDataOffsetBytes = audPlatformSpecific::FixEndian(waveDataOffsetBytes); encodedWaveMetadata.base.lengthBytes = audPlatformSpecific::FixEndian((unsigned int)waveSampleDataOutLengthBytes); encodedWaveMetadata.base.lengthSamples = audPlatformSpecific::FixEndian(waveBaseMetadataIn->lengthSamples); encodedWaveMetadata.base.loopStartOffsetSamples = (int)audPlatformSpecific::FixEndian((unsigned int)waveBaseMetadataIn-> loopStartOffsetSamples); encodedWaveMetadata.base.sampleRate = audPlatformSpecific::FixEndian(waveBaseMetadataIn->sampleRate); encodedWaveMetadata.base.headroom = (short)audPlatformSpecific::FixEndian((unsigned short)waveBaseMetadataIn->headroom); encodedWaveMetadata.base.flags = audPlatformSpecific::FixEndian(waveFlags); encodedWaveMetadata.platform.samplesPerFrame = audPlatformSpecific::FixEndian(samplesPerFrame); encodedWaveMetadata.platform.numFrames = audPlatformSpecific::FixEndian(numFrames); unsigned __int64 seekTableOffsetBytes = (unsigned __int64)sizeof(audWaveMetadata); encodedWaveMetadata.platform.seekTableOffsetBytes = FlipEndian(seekTableOffsetBytes); waveMetadataOutLengthBytes = sizeof(audWaveMetadata) + (numFrames * sizeof(unsigned short)); *waveMetadataOut = new unsigned char[waveMetadataOutLengthBytes]; memcpy(*waveMetadataOut, &encodedWaveMetadata, sizeof(audWaveMetadata)); memcpy(((char *)(*waveMetadataOut)) + sizeof(audWaveMetadata), trimmedSeekTable, numFrames * sizeof(unsigned short)); delete[] seekTable; return true; } bool audWaveEncoderPSN::PreProcessLoop(audWaveMetadataBase *waveMetadata, int &preloopPadding) { //Align pre-loop and loop lengths via resampling and zero-padding. if(!AlignLoop(waveMetadata, g_Mp3BlockSamples, preloopPadding)) { return false; } if(waveMetadata->loopStartOffsetSamples == 0) { //Concatenate 3 full loops to present to the MP3 encoder. short *concatenatedLoops = new short[waveMetadata->lengthSamples * 3]; for(int i=0; i<3; i++) { memcpy(concatenatedLoops + (i * waveMetadata->lengthSamples), waveMetadata->waveData, waveMetadata->lengthSamples * sizeof(short)); } delete[] waveMetadata->waveData; waveMetadata->waveData = concatenatedLoops; waveMetadata->lengthSamples *= 3; } else { //Concatenate 3 full loops onto the preloop to present to the MP3 encoder. // - moving the loop point to the end of the first loop. unsigned int loopLengthSamples = waveMetadata->lengthSamples - (unsigned int)waveMetadata->loopStartOffsetSamples; unsigned int numConcatenatedSamples = (unsigned int)waveMetadata->loopStartOffsetSamples + (loopLengthSamples * 3); short *concatenatedLoops = new short[numConcatenatedSamples]; //Copy preloop. memcpy(concatenatedLoops, waveMetadata->waveData, waveMetadata->loopStartOffsetSamples * sizeof(short)); //Copy loop 3 times. for(unsigned int i=0; i<3; i++) { memcpy(concatenatedLoops + waveMetadata->loopStartOffsetSamples + (i * loopLengthSamples), ((short *)waveMetadata->waveData) + waveMetadata->loopStartOffsetSamples, loopLengthSamples * sizeof(short)); } delete[] waveMetadata->waveData; waveMetadata->waveData = concatenatedLoops; waveMetadata->lengthSamples = numConcatenatedSamples; //Move the loop point to the end of the first loop. waveMetadata->loopStartOffsetSamples += loopLengthSamples; } return true; } bool audWaveEncoderPSN::EncodeMp3(audWaveMetadataBase *waveMetadata, void **waveSampleDataOut, unsigned int &waveSampleDataOutLengthBytes, int compression, int sampleRate, unsigned int &samplesPerFrame) { //Map compression setting to MP3 VBR quality - 0(best) to 9(worst). int vbrQuality = 10 - (int)ceil((float)compression / 10.0f); if(vbrQuality == 10) { vbrQuality = 9; //Compression setting 0 will give the same compression as 1 to 10. } lame_global_flags *globalFlags = lame_init(); lame_set_bWriteVbrTag(globalFlags, 0); //No header. lame_set_num_samples(globalFlags, waveMetadata->lengthSamples); lame_set_in_samplerate(globalFlags, sampleRate); lame_set_num_channels(globalFlags, 1); //We only support mono input. lame_set_out_samplerate(globalFlags, sampleRate); lame_set_quality(globalFlags, 2); //Use a better (but slower) quality compression algorithm. lame_set_VBR(globalFlags, vbr_default); lame_set_VBR_q(globalFlags, vbrQuality); lame_set_lowpassfreq(globalFlags, -1); //Disable low-pass filtering. lame_set_highpassfreq(globalFlags, -1); //Disable high-pass filtering. lame_set_disable_reservoir(globalFlags, 1); //Disable the bit reservoir to allow seeking/looping to an arbitrary frame. lame_init_params(globalFlags); if(0 == lame_get_version(globalFlags)) { // For MPEG-II, only 576 samples per frame per channel samplesPerFrame = 576; } else { // For MPEG-I, 1152 samples per frame per channel samplesPerFrame = 1152; } //Zero-pad the raw wave data (into a new buffer) to ensure it aligns to the frame size. unsigned int paddingSamples = samplesPerFrame - (waveMetadata->lengthSamples % samplesPerFrame); if(paddingSamples == samplesPerFrame) { paddingSamples = 0; } waveMetadata->lengthBytes = waveMetadata->lengthSamples * 2; unsigned int unpaddedLengthBytes = waveMetadata->lengthBytes; waveMetadata->lengthSamples += paddingSamples; waveMetadata->lengthBytes += paddingSamples * 2; unsigned char *paddedBuffer = new unsigned char[waveMetadata->lengthBytes]; memcpy(paddedBuffer, waveMetadata->waveData, unpaddedLengthBytes); if(paddingSamples > 0) { memset(paddedBuffer + unpaddedLengthBytes, 0, paddingSamples * 2); } int encodedBufferBytes = (5 * waveMetadata->lengthSamples / 4) + 7200; unsigned char *encodeBuffer = new unsigned char [encodedBufferBytes]; int bytesEncoded; int totalBytesEncoded = 0; for(unsigned int inputSampleOffset=0; inputSampleOffsetlengthSamples; inputSampleOffset+=samplesPerFrame) { bytesEncoded = lame_encode_buffer(globalFlags, ((const short *)paddedBuffer) + inputSampleOffset, NULL, samplesPerFrame, encodeBuffer + totalBytesEncoded, encodedBufferBytes - totalBytesEncoded); if(bytesEncoded < 0) { fprintf(stderr, "LAME error encoding MP3\n"); lame_close(globalFlags); delete[] paddedBuffer; delete[] encodeBuffer; return false; } totalBytesEncoded += bytesEncoded; } bytesEncoded = lame_encode_flush(globalFlags, encodeBuffer + totalBytesEncoded, encodedBufferBytes - totalBytesEncoded); delete[] paddedBuffer; if(bytesEncoded < 0) { fprintf(stderr, "LAME error encoding MP3\n"); lame_close(globalFlags); delete[] encodeBuffer; return false; } totalBytesEncoded += bytesEncoded; lame_close(globalFlags); *waveSampleDataOut = encodeBuffer; waveSampleDataOutLengthBytes = totalBytesEncoded; return true; } bool audWaveEncoderPSN::GenerateSeekTable(unsigned char *waveSampleDataOut, unsigned int waveSampleDataOutLengthBytes, unsigned int samplesPerFrame, unsigned short **seekTable, unsigned int &numFrames) { //Let's go crazy and allocate enough memory for 1-byte frames - just to be on the safe side... *seekTable = new unsigned short [waveSampleDataOutLengthBytes]; numFrames = 0; unsigned char *frame; for(unsigned int byteOffset=0; byteOffset> 6) & 0x3; unsigned int modeExtension = (frame[3] >> 4) & 0x3; unsigned int copyright = (frame[3] >> 3) & 0x1; unsigned int original = (frame[3] >> 2) & 0x1; unsigned int emphasis = frame[3] & 0x3;*/ unsigned int versionIndex = (frame[1] >> 3) & 0x3; int version = g_MpegVersionTable[versionIndex]; if(version < 0) { fprintf(stderr, "Invalid MPEG version\n"); delete[] seekTable; return false; } unsigned int layerIndex = (frame[1] >> 1) & 0x3; int layer = g_MpegLayerTable[layerIndex]; if(layer < 0) { fprintf(stderr, "Invalid MPEG layer\n"); delete[] seekTable; return false; } unsigned int bitrateIndex = (frame[2] >> 4) & 0xF; int bitrate; if(version == MPEG1) { bitrate = g_Mpeg1BitrateTable[bitrateIndex][layer] * 1000; } else { bitrate = g_Mpeg2BitrateTable[bitrateIndex][layer] * 1000; } if(bitrate < 0) { fprintf(stderr, "Invalid MPEG bitrate\n"); delete[] seekTable; return false; } unsigned int sampleRateIndex = (frame[2] >> 2) & 0x3; int sampleRate = g_MpegSampleRateTable[sampleRateIndex][version]; if(sampleRate < 0) { fprintf(stderr, "Invalid MPEG sample rate\n"); delete[] seekTable; return false; } unsigned int padding = (frame[2] >> 1) & 0x1; int paddingBytes = padding * g_MpegSlotBytes[layer]; int frameBytes = ((samplesPerFrame / 8 * bitrate) / sampleRate) + paddingBytes; unsigned int protection = frame[1] & 0x01; if(protection == 0) { //There should be a 16-bit CRC at the end of the header. frameBytes += 2; } (*seekTable)[numFrames] = audPlatformSpecific::FixEndian((unsigned short)frameBytes); numFrames++; //Jump to the end of this frame. byteOffset += frameBytes - 1; } } return true; } void audWaveEncoderPSN::PostProcessMp3Frames(audWaveMetadataBase *waveMetadata, void **waveSampleDataOut, unsigned int &waveSampleDataOutLengthBytes, unsigned int samplesPerFrame, unsigned short *seekTable, unsigned int &numFrames, unsigned short **trimmedSeekTable) { if(waveMetadata->loopStartOffsetSamples == 0) { //Extract the central loop from the 3 concatenated loops we encoded. unsigned int startFrame = ((numFrames - 2) / 3) + 1; //Trim the frame of silence at the start and end. unsigned int endFrame = ((numFrames - 2) * 2 / 3) + 1; //Trim the frame of silence at the start and end. unsigned int startOffsetBytes = 0; unsigned int sizeBytes = 0; for(unsigned int i=0; iloopStartOffsetSamples > 0) { //Cut the 3rd loop off the end of the preloop and 3 loops we encoded. unsigned int loopLengthSamples = (waveMetadata->lengthSamples - waveMetadata->loopStartOffsetSamples) / 2; unsigned int endSample = waveMetadata->loopStartOffsetSamples + loopLengthSamples; numFrames = (endSample / samplesPerFrame) + 1; //Take the frame of silence at the start into account. waveSampleDataOutLengthBytes = 0; //Strip the first frame of silent MP3 data. for(unsigned int i=1; i 2) { //Strip the first and last frames of silent MP3 data. waveSampleDataOutLengthBytes -= audPlatformSpecific::FixEndian(seekTable[0]) + audPlatformSpecific::FixEndian(seekTable[numFrames - 1]); unsigned char *trimmedEncodedBuffer = new unsigned char[waveSampleDataOutLengthBytes]; memcpy(trimmedEncodedBuffer, (unsigned char *)(*waveSampleDataOut) + audPlatformSpecific::FixEndian(seekTable[0]), waveSampleDataOutLengthBytes); delete[] *waveSampleDataOut; *waveSampleDataOut = trimmedEncodedBuffer; *trimmedSeekTable = seekTable + 1; numFrames -= 2; } else { *trimmedSeekTable = seekTable; } } void audWaveEncoderPSN::WriteMp3File(audWaveMetadataBase *waveMetadata, void *waveSampleDataOut, unsigned int waveSampleDataOutLengthBytes) { BinaryWriter *mp3FileOut = NULL; try { FileStream *mp3FileStream = File::Open(String::Concat(S"C:/Windows/Temp/", __box(waveMetadata->nameHash), S".mp3"), FileMode::Create); BufferedStream *mp3FileBufStream = new BufferedStream(mp3FileStream, g_Mp3FileBufferBytes); mp3FileOut = new BinaryWriter(mp3FileBufStream); //Convert MP3 data to a managed array and write out. Byte managedData __gc[] = new Byte[waveSampleDataOutLengthBytes]; System::Runtime::InteropServices::Marshal::Copy(waveSampleDataOut, managedData, 0, waveSampleDataOutLengthBytes); mp3FileOut->Write(managedData); managedData = NULL; //Flush data to disk. mp3FileOut->Flush(); } __finally { if(mp3FileOut) { mp3FileOut->Close(); } } } } //namespace