Files
GTASource/rage/base/tools/audio/audwaveencoder/waveencoder_psn.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

580 lines
17 KiB
C++

//
// 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; i<g_NumValidSampleRates; i++)
{
if(inputSampleRate <= g_ValidSampleRates[i])
{
targetSampleRate = g_ValidSampleRates[i];
break;
}
}
if(!Resample(waveMetadataIn, targetSampleRate))
{
fprintf(stderr, "Failed to resample wave data to a valid MPEG sample rate\n");
return false;
}
if((waveBaseMetadataIn->loopStartOffsetSamples >= 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; inputSampleOffset<waveMetadata->lengthSamples; 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<waveSampleDataOutLengthBytes; byteOffset++)
{
frame = waveSampleDataOut + byteOffset;
//Find the frame sync.
if((frame[0] == 0xFF) && ((frame[1] & 0xE0) == 0xE0))
{
/*unsigned int privateBit = frame[2] & 0x1;
unsigned int channelMode = (frame[3] >> 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; i<startFrame; i++)
{
startOffsetBytes += (unsigned int)audPlatformSpecific::FixEndian(seekTable[i]);
}
for(unsigned int i=startFrame; i<endFrame; i++)
{
sizeBytes += (unsigned int)audPlatformSpecific::FixEndian(seekTable[i]);
}
unsigned char *extractedLoop = new unsigned char[sizeBytes];
memcpy(extractedLoop, (unsigned char *)(*waveSampleDataOut) + startOffsetBytes, sizeBytes);
delete[] *waveSampleDataOut;
*waveSampleDataOut = extractedLoop;
*trimmedSeekTable = seekTable + startFrame;
numFrames = endFrame - startFrame;
waveSampleDataOutLengthBytes = sizeBytes;
}
else if(waveMetadata->loopStartOffsetSamples > 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<numFrames; i++)
{
waveSampleDataOutLengthBytes += (unsigned int)audPlatformSpecific::FixEndian(seekTable[i]);
}
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--;
}
else if(numFrames > 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