Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 171 additions & 30 deletions app/mediaAnalyser/AvSoundFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
#include <algorithm>
#include <stdexcept>

#include <AvTranscoder/encoder/AudioEncoder.hpp>
#include <AvTranscoder/file/OutputFile.hpp>
#include <AvTranscoder/transform/AudioTransform.hpp>

const size_t NB_OF_BYTES_24_BITS = 3;
const float MAX_VALUE_24_BITS = 8388607.0;
const std::string CODEC_NAME_24_BITS = "pcm_s24le";
const std::string SAMPLE_FORMAT_24_BITS = "s32";

void AvSoundFile::printProgress()
{
const int p = (float)_cumulOfSamplesAnalysed / _totalNbSamplesToAnalyse * 100;
Expand Down Expand Up @@ -98,18 +107,48 @@ AvSoundFile::~AvSoundFile()
}
}

size_t AvSoundFile::getTotalNbSamplesToAnalyse()
{
size_t newTotalNbSamplesToAnalyse = 0;
for(size_t i = 0; i < _inputSampleRate.size(); i++)
{
newTotalNbSamplesToAnalyse += _forceDurationToAnalyse * _inputSampleRate.at(i) * _inputNbChannels.at(i);
}
return newTotalNbSamplesToAnalyse;
}

bool AvSoundFile::fillAudioBuffer(float** audioBuffer, size_t& nbSamplesRead, size_t& nbInputChannelAdded)
{
for(size_t fileIndex = 0; fileIndex < _audioReader.size(); ++fileIndex)
{
avtranscoder::IFrame* dstFrame = _audioReader.at(fileIndex)->readNextFrame();

// empty frame: go to the end of process
if(dstFrame == NULL)
{
return false;
}

size_t inputChannel = 0;
for(size_t channelToAdd = nbInputChannelAdded;
channelToAdd < nbInputChannelAdded + _inputNbChannels.at(fileIndex); ++channelToAdd)
{
audioBuffer[channelToAdd] = (float*)(dstFrame->getData()[inputChannel]);
++inputChannel;
nbSamplesRead += dstFrame->getAVFrame().nb_samples;
}
nbInputChannelAdded += _inputNbChannels.at(fileIndex);
}
return true;
}

void AvSoundFile::analyse(Loudness::analyser::LoudnessAnalyser& analyser)
{
// update number of samples to analyse
if(_forceDurationToAnalyse)
{
size_t newTotalNbSamplesToAnalyse = 0;
for(size_t i = 0; i < _inputSampleRate.size(); i++)
{
newTotalNbSamplesToAnalyse += _forceDurationToAnalyse * _inputSampleRate.at(i) * _inputNbChannels.at(i);
}
// set total number of samples to analyse
_totalNbSamplesToAnalyse = newTotalNbSamplesToAnalyse;
_totalNbSamplesToAnalyse = getTotalNbSamplesToAnalyse();
}

// open file to print duration
Expand All @@ -126,34 +165,13 @@ void AvSoundFile::analyse(Loudness::analyser::LoudnessAnalyser& analyser)
// Create planar buffer of float data
float** audioBuffer = new float*[_nbChannelsToAnalyse];

bool emptyFrameDecoded = false;
// Decode audio streams
// Analyze audio streams
while(!isEndOfAnalysis())
{
// Decode audio streams
size_t nbSamplesRead = 0;
size_t nbInputChannelAdded = 0;
for(size_t fileIndex = 0; fileIndex < _audioReader.size(); ++fileIndex)
{
avtranscoder::IFrame* dstFrame = _audioReader.at(fileIndex)->readNextFrame();

// empty frame: go to the end of process
if(dstFrame == NULL)
{
emptyFrameDecoded = true;
break;
}

size_t inputChannel = 0;
for(size_t channelToAdd = nbInputChannelAdded;
channelToAdd < nbInputChannelAdded + _inputNbChannels.at(fileIndex); ++channelToAdd)
{
audioBuffer[channelToAdd] = (float*)(dstFrame->getData()[inputChannel]);
++inputChannel;
nbSamplesRead += dstFrame->getAVFrame().nb_samples;
}
nbInputChannelAdded += _inputNbChannels.at(fileIndex);
}
if(emptyFrameDecoded)
if(!fillAudioBuffer(audioBuffer, nbSamplesRead, nbInputChannelAdded))
break;

// Analyse loudness
Expand All @@ -173,6 +191,129 @@ void AvSoundFile::analyse(Loudness::analyser::LoudnessAnalyser& analyser)
delete[] audioBuffer;
}

void AvSoundFile::correct(Loudness::analyser::LoudnessAnalyser& analyser, const std::string& outputFilePath, const float gain)
{
// update number of samples to analyse
if(_forceDurationToAnalyse)
{
// set total number of samples to analyse
_totalNbSamplesToAnalyse = getTotalNbSamplesToAnalyse();
}

// open file to print duration
std::ofstream progressOutputFile;
if(! _progressionFileName.empty())
{
progressOutputFile.open(_progressionFileName.c_str());
_outputStream = &progressOutputFile;
}

// init
analyser.initAndStart(_nbChannelsToAnalyse, _inputSampleRate.at(0));

// Create planar buffer of float data
float** audioBuffer = new float*[_nbChannelsToAnalyse];

// Initialize output encoder
int totalInputNbChannels = 0;
for(size_t i = 0; i < _inputNbChannels.size(); ++i)
totalInputNbChannels += _inputNbChannels.at(i);

avtranscoder::AudioFrameDesc outputAudioFrameDesc(_audioReader.at(0)->getOutputSampleRate(), totalInputNbChannels, SAMPLE_FORMAT_24_BITS);
avtranscoder::AudioEncoder* encoder = new avtranscoder::AudioEncoder(CODEC_NAME_24_BITS);
encoder->setupAudioEncoder(outputAudioFrameDesc);

avtranscoder::OutputFile* outputFile = new avtranscoder::OutputFile(outputFilePath);
outputFile->addAudioStream(encoder->getAudioCodec());
outputFile->beginWrap();

// reset counters
_cumulOfSamplesAnalysed = 0;

while(!isEndOfAnalysis())
{
// Decode audio streams
size_t nbSamplesRead = 0;
size_t nbInputChannelAdded = 0;
if(!fillAudioBuffer(audioBuffer, nbSamplesRead, nbInputChannelAdded))
break;

// Apply gain
const size_t nbSamplesInOneFrame = nbSamplesRead / nbInputChannelAdded;
applyGain(audioBuffer, nbSamplesInOneFrame, gain);

// Convert corrected frame
const size_t rawDataSize = nbSamplesRead * NB_OF_BYTES_24_BITS;
unsigned char* rawData = new unsigned char[rawDataSize];
encodePlanarSamplesToInterlacedPcm(audioBuffer, rawData, nbSamplesInOneFrame);

// Write corrected frame
avtranscoder::CodedData data;
data.copyData(rawData, rawDataSize);
outputFile->wrap(data, 0);

// Analyse loudness
analyser.processSamples(audioBuffer, nbSamplesInOneFrame);

// Progress
_cumulOfSamplesAnalysed += nbSamplesRead;
printProgress();

delete rawData;
}

outputFile->endWrap();

delete encoder;
delete outputFile;

// Close progression file
if(! _progressionFileName.empty())
progressOutputFile.close();

// free audio buffer
delete[] audioBuffer;
}

float AvSoundFile::clipSample(const float value)
{
if (value > 1.f) {
return 1.f;
}
if (value < -1.f) {
return -1.f;
}
return value;
}

void AvSoundFile::applyGain(float** audioBuffer, const size_t numberOfSamplesPerChannel, const float gain)
{
for(size_t channel = 0; channel < _nbChannelsToAnalyse; channel++)
{
for(size_t sample = 0; sample < numberOfSamplesPerChannel; sample++)
{
audioBuffer[channel][sample] = audioBuffer[channel][sample] * gain;
}
}
}

void AvSoundFile::encodePlanarSamplesToInterlacedPcm(float** planarBuffer, unsigned char* interlacedBuffer, const size_t numberOfSamplesPerChannel)
{
size_t sampleCounter = 0;
for(size_t sample = 0; sample < numberOfSamplesPerChannel; sample++)
{
for(size_t channel = 0; channel < _nbChannelsToAnalyse; channel++)
{
const size_t offset = sampleCounter++ * NB_OF_BYTES_24_BITS;
const float sampleValueClipped = clipSample(planarBuffer[channel][sample]);
const int sampleValue = (int)(sampleValueClipped * MAX_VALUE_24_BITS);
interlacedBuffer[offset] = sampleValue & 0xff;
interlacedBuffer[offset + 1] = (sampleValue >> 8) & 0xff;
interlacedBuffer[offset + 2] = (sampleValue >> 16) & 0xff;
}
}
}

void AvSoundFile::setDurationToAnalyse(const float durationToAnalyse)
{
if(durationToAnalyse > 0)
Expand Down
31 changes: 31 additions & 0 deletions app/mediaAnalyser/AvSoundFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class AvSoundFile
~AvSoundFile();

void analyse(Loudness::analyser::LoudnessAnalyser& analyser);
void correct(Loudness::analyser::LoudnessAnalyser& analyser, const std::string& outputFilePath, const float gain);

/**
* @brief Set the output filename of the progress file.
Expand Down Expand Up @@ -47,6 +48,36 @@ class AvSoundFile
*/
bool isEndOfAnalysis();

/**
* @brief Compute the total number of samples to analyse from the several inputs
* sample rate, the number of channels and the expected duration.
* @return the number of samples to analyse
*/
size_t getTotalNbSamplesToAnalyse();

/**
* @brief Fill input audio buffer with data from audio readers, and increment the
* number of read channels and the total number of samples read (every read channels).
* @return whether the audiobuffer could be filled
*/
bool fillAudioBuffer(float** audioBuffer, size_t& nbSamplesRead, size_t& nbInputChannelAdded);

/**
* @brief Apply gain to specified planar audio buffer samples.
*/
void applyGain(float** audioBuffer, const size_t numberOfSamplesPerChannel, const float gain);

/**
* @brief Clip audio sample value to normalized values [-1.0, 1.0].
* @return the clipped audio sample value
*/
float clipSample(const float value);

/**
* @brief Convert planar audio samples buffer into interlaced PCM values buffer.
*/
void encodePlanarSamplesToInterlacedPcm(float** planarBuffer, unsigned char* interlacedBuffer, const size_t numberOfSamplesPerChannel);

private:
// for loudness analyser
size_t _nbChannelsToAnalyse;
Expand Down
36 changes: 33 additions & 3 deletions app/mediaAnalyser/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <vector>
#include <utility>
#include <fstream>
#include <cmath>

std::vector<avtranscoder::InputStreamDesc> parseConfigFile(const std::string& configFilename)
{
Expand Down Expand Up @@ -58,17 +59,18 @@ void printHelp()
std::string help;
help += "Usage\n";
help += "\tmedia-analyser CONFIG.TXT [--output XMLReportName][--progressionInFile "
"progressionName][--forceDurationToAnalyse durationToAnalyse][--help]\n";
"progressionName][--forceDurationToAnalyse durationToAnalyse][--correction outputFile][--help]\n";
help += "CONFIG.TXT\n";
help += "\tEach line will be one audio stream analysed by the loudness library.\n";
help += "\tPattern of each line is:\n";
help += "\t[inputFile]=STREAM_INDEX.CHANNEL_INDEX\n";
help += "Command line options\n";
help += "\t--help: display this help\n";
help += "\t--output: filename of the XML report\n";
help += "\t--progressionInFile: to print the progression in a file instead of in console\n";
help += "\t--output: filename of the XML report\n";
help += "\t--forceDurationToAnalyse: to force loudness analysis on a specific duration (in seconds). By default this is "
"the duration of the input.\n";
help += "\t--correction: enable loudness correction, and write the corrected streams to the specified output file\n";
std::cout << help << std::endl;
}

Expand All @@ -78,6 +80,9 @@ int main(int argc, char** argv)
std::string outputProgressionName;
float durationToAnalyse = 0;

bool correction = false;
std::string correctionOutputFile;

// Check required arguments
if(argc < 2)
{
Expand Down Expand Up @@ -135,6 +140,19 @@ int main(int argc, char** argv)
return 1;
}
}
else if(arguments.at(argument) == "--correction")
{
correction = true;
try
{
correctionOutputFile = arguments.at(++argument);
}
catch(...)
{
printHelp();
return 1;
}
}
// unknown option
continue;
}
Expand All @@ -157,6 +175,18 @@ int main(int argc, char** argv)

// Print analyse
analyser.printPloudValues();
const float gain = analyser.getCorrectionGain();

if(correction && std::fabs(1.0 - gain) > 0.001)
{
std::cout << "Correction with gain " << gain << std::endl;
AvSoundFile correctedSoundFile(arrayToAnalyse);
correctedSoundFile.setProgressionFile(outputProgressionName);
correctedSoundFile.setDurationToAnalyse(durationToAnalyse);
correctedSoundFile.correct(analyser, correctionOutputFile, gain);

analyser.printPloudValues();
}

// Write XML
std::vector<std::string> mediaFilenames;
Expand All @@ -168,7 +198,7 @@ int main(int argc, char** argv)
std::stringstream ss;
ss << soundFile.getNbChannelsToAnalyse();
ss << " channels";
writerXml.writeResults(ss.str(), analyser);
writerXml.writeResults(ss.str(), analyser, (correction)? gain : 1.0);
}
catch(const std::exception& e)
{
Expand Down
Loading