diff --git a/app/mediaAnalyser/AvCorrector.cpp b/app/mediaAnalyser/AvCorrector.cpp new file mode 100644 index 0000000..da47bf2 --- /dev/null +++ b/app/mediaAnalyser/AvCorrector.cpp @@ -0,0 +1,224 @@ +#include "AvCorrector.hpp" + +#include +#include +#include +#include +#include + +AvCorrector::AvCorrector(const std::vector& arrayToCorrect, const std::string& correctionOutputName) + : _totalNbSamplesToCorrect(0) + , _cumulOfSamplesCorrected(0) + , _inputNbChannels() + , _inputSampleRate() + , _currentReaderSamplesMaxValue() + , _audioReader() + , _correctedStreamDescs() + , _outputStream(&std::cout) + , _progressionFileName() +{ + for (int i = 0; i < arrayToCorrect.size(); ++i) + { + const avtranscoder::InputStreamDesc inputStreamDesc = arrayToCorrect.at(i); + // Create reader to convert to float planar + avtranscoder::AudioReader* reader = new avtranscoder::AudioReader(inputStreamDesc); + reader->continueWithGenerator(); + _audioReader.push_back(reader); + + // Get data from audio stream + const avtranscoder::AudioProperties* audioProperties = reader->getSourceAudioProperties(); + const int nbChannels = inputStreamDesc._channelIndexArray.empty() ? + audioProperties->getNbChannels() : inputStreamDesc._channelIndexArray.size(); + _inputNbChannels.push_back(nbChannels); + const size_t sampleRate = audioProperties->getSampleRate(); + _inputSampleRate.push_back(sampleRate); + + const int nbSamples = (audioProperties->getNbSamples() / audioProperties->getNbChannels()) * nbChannels; + _nbSamplesToCorrect.push_back(nbSamples); + _totalNbSamplesToCorrect += nbSamples; + + reader->updateOutput(sampleRate, nbChannels, audioProperties->getSampleFormatName()); + + std::stringstream outputStreamFileName; + if(!correctionOutputName.empty()) + { + outputStreamFileName << correctionOutputName << "_" << i << ".wav"; + } + else + { + outputStreamFileName << inputStreamDesc._filename.substr(0, inputStreamDesc._filename.size() - 4) + << "_corrected_" << i << ".wav"; + } + avtranscoder::InputStreamDesc outputStreamDesc(outputStreamFileName.str(), 0); + _correctedStreamDescs.push_back(outputStreamDesc); + } +} + +AvCorrector::~AvCorrector() +{ + for(std::vector::iterator it = _audioReader.begin(); it != _audioReader.end(); ++it) + { + delete(*it); + } +} + +void AvCorrector::correct(const float gain) +{ + // open file to print duration + std::ofstream outputFile; + if(! _progressionFileName.empty()) + { + outputFile.open(_progressionFileName.c_str()); + _outputStream = &outputFile; + } + + for (int i = 0; i < _audioReader.size(); ++i) + { + avtranscoder::AudioReader* reader = _audioReader.at(i); + const avtranscoder::AudioProperties* inputAudioProperties = reader->getSourceAudioProperties(); + + avtranscoder::AudioFrameDesc audioFrameDesc(inputAudioProperties->getSampleRate(), + _inputNbChannels.at(i), + inputAudioProperties->getSampleFormatName()); + + avtranscoder::AudioEncoder* encoder = new avtranscoder::AudioEncoder(inputAudioProperties->getCodecName()); + encoder->setupAudioEncoder(audioFrameDesc); + + avtranscoder::OutputFile* outputFile = new avtranscoder::OutputFile(_correctedStreamDescs.at(i)._filename); + outputFile->addAudioStream(encoder->getAudioCodec()); + outputFile->beginWrap(); + + bool emptyFrameDecoded = false; + + _currentReaderSamplesMaxValue = getSampleMaxValue(inputAudioProperties->getSampleFormatName()); + + // Decode audio streams + int count = 0; + while(count < _nbSamplesToCorrect.at(i)) + { + if(isEndOfCorrection()) + { + break; + } + + avtranscoder::AudioFrame* srcFrame = (avtranscoder::AudioFrame*) _audioReader.at(i)->readNextFrame(); + + // empty frame: go to the end of process + if(srcFrame == NULL) + { + emptyFrameDecoded = true; + break; + } + + const size_t nbChannels = _inputNbChannels.at(i); + const size_t nbSamples = srcFrame->getNbSamplesPerChannel(); + const size_t bytesPerSample = srcFrame->getBytesPerSample(); + + avtranscoder::AudioFrame* audioFrame = new avtranscoder::AudioFrame(audioFrameDesc, false); + audioFrame->setNbSamplesPerChannel(nbSamples); + audioFrame->allocateData(); + + unsigned char* srcData = srcFrame->getData()[0]; + unsigned char* dstData = audioFrame->getData()[0]; + + // Correct input + const int correctedSamples = correctFrame(srcData, dstData, nbChannels, nbSamples, bytesPerSample, gain); + + avtranscoder::CodedData data; + encoder->encodeFrame(*audioFrame, data); + outputFile->wrap(data, 0); + + delete audioFrame; + + count += correctedSamples; + _cumulOfSamplesCorrected += correctedSamples; + printProgress(); + } + + outputFile->endWrap(); + delete encoder; + delete outputFile; + } +} + +void AvCorrector::printProgress() +{ + const int p = (float)_cumulOfSamplesCorrected / _totalNbSamplesToCorrect * 100; + + // print progression to file + if(!_progressionFileName.empty()) + { + _outputStream->seekp(0); + *_outputStream << p; + } + // print progression to console + else + *_outputStream << "[" << std::setw(3) << p << "%]\r" << std::flush; +} + +int AvCorrector::getSampleMaxValue(const std::string& sampleFormat) +{ + if(sampleFormat == "s8") + return 0xFF000000; + if(sampleFormat == "s16") + return 0xFFFF0000; + if(sampleFormat == "s32") + return 0xFFFFFF00; + throw std::runtime_error("Cannot handle such a sample format: " + sampleFormat); +} + +size_t AvCorrector::correctFrame(unsigned char* srcData, unsigned char* dstData, const size_t nbChannels, const size_t nbSamples, + const size_t bytesPerSample, const float gain) +{ + const int half = _currentReaderSamplesMaxValue / 2; + + size_t correctedSamples = 0; + const size_t channelDataSize = nbSamples * bytesPerSample; + + int byte = 0; + for(size_t c = 0; c < nbChannels; c++) + { + // Get channel data + unsigned char* channelData = &srcData[c*channelDataSize]; + for(size_t s = 0; s < nbSamples; s++) + { + // Correct audio sample + Sample audioSample; + for (size_t b = 0; b < bytesPerSample; ++b) + { + audioSample.bytes[b] = (*channelData); + channelData++; + } + + if(audioSample.value > half) + { + const int value = audioSample.value - _currentReaderSamplesMaxValue; + int gainedValue = value * gain; + if(gainedValue - value == 0) + { + if(gainedValue > 0) + gainedValue++; + else + gainedValue--; + } + audioSample.value = (gainedValue + _currentReaderSamplesMaxValue) & _currentReaderSamplesMaxValue; + } else { + audioSample.value = (int)(audioSample.value * gain) & _currentReaderSamplesMaxValue; + } + + for (size_t b = 0; b < bytesPerSample; ++b) + { + dstData[byte] = audioSample.bytes[b]; + byte++; + } + correctedSamples++; + } + } + return correctedSamples; +} + + +bool AvCorrector::isEndOfCorrection() +{ + return _cumulOfSamplesCorrected >= _totalNbSamplesToCorrect; +} diff --git a/app/mediaAnalyser/AvCorrector.hpp b/app/mediaAnalyser/AvCorrector.hpp new file mode 100644 index 0000000..702cbba --- /dev/null +++ b/app/mediaAnalyser/AvCorrector.hpp @@ -0,0 +1,51 @@ +#ifndef AVCORRECTOR_HPP +#define AVCORRECTOR_HPP + +#include + +union Sample { + int value; + unsigned char bytes[4]; +}; + +class AvCorrector +{ +public: + AvCorrector(const std::vector& arrayToCorrect, const std::string& correctionOutputName = ""); + ~AvCorrector(); + + void correct(const float gain); + std::vector getOutputStreamDescs() { return _correctedStreamDescs; } + void setProgressionFile(const std::string& progressionFileName) { _progressionFileName = progressionFileName; } + +private: + int getSampleMaxValue(const std::string& sampleFormat); + void printProgress(); + + size_t correctFrame(unsigned char* srcData, unsigned char* dstData, const size_t nbChannels, const size_t nbSamples, + const size_t bytesPerSample, const float gain); + + bool isEndOfCorrection(); + +private: + // for progress + size_t _totalNbSamplesToCorrect; + size_t _cumulOfSamplesCorrected; + std::vector _nbSamplesToCorrect; + + // to check audio before correct + std::vector _inputNbChannels; + std::vector _inputSampleRate; + + int _currentReaderSamplesMaxValue; + + // To print the progession to a stream + std::ostream* _outputStream; + std::string _progressionFileName; + + // for io + std::vector _audioReader; + std::vector _correctedStreamDescs; +}; + +#endif diff --git a/app/mediaAnalyser/AvSoundFile.cpp b/app/mediaAnalyser/AvSoundFile.cpp index b720f41..a0da7a6 100644 --- a/app/mediaAnalyser/AvSoundFile.cpp +++ b/app/mediaAnalyser/AvSoundFile.cpp @@ -98,18 +98,23 @@ 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; +} + 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 diff --git a/app/mediaAnalyser/AvSoundFile.hpp b/app/mediaAnalyser/AvSoundFile.hpp index accf49d..81f67b8 100644 --- a/app/mediaAnalyser/AvSoundFile.hpp +++ b/app/mediaAnalyser/AvSoundFile.hpp @@ -47,6 +47,13 @@ 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(); + private: // for loudness analyser size_t _nbChannelsToAnalyse; diff --git a/app/mediaAnalyser/main.cpp b/app/mediaAnalyser/main.cpp index a68eb75..7de1958 100644 --- a/app/mediaAnalyser/main.cpp +++ b/app/mediaAnalyser/main.cpp @@ -1,4 +1,5 @@ #include "AvSoundFile.hpp" +#include "AvCorrector.hpp" #include #include @@ -32,6 +33,12 @@ std::vector parseConfigFile(const std::string& co if(separator == '.') ss >> channelIndex; + if(channelIndex == -1) + { + result.push_back(avtranscoder::InputStreamDesc(filename, streamIndex)); + continue; + } + bool newInputDescAdded = false; // if we already have an input description with the same filename/streamIndex, add only the new channelIndex for(std::vector::iterator it = result.begin(); it != result.end(); ++it) @@ -59,16 +66,22 @@ void printHelp() help += "Usage\n"; help += "\tmedia-analyser CONFIG.TXT [--output XMLReportName][--progressionInFile " "progressionName][--forceDurationToAnalyse durationToAnalyse][--help]\n"; + help += "\tmedia-analyser CONFIG.TXT --correction gain [--correctionOutput outputFilesName]" + "[--progressionInFile progressionName]\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 += "\tANALYSE:\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 += "\tCORRECTION:\n"; + help += "\t--correction: process streams correction with the specified gain, generating one output file for each input file\n"; + help += "\t--correctionOutput: name of the corrected streams output files, formatted as follows: _.wav \n"; std::cout << help << std::endl; } @@ -78,6 +91,10 @@ int main(int argc, char** argv) std::string outputProgressionName; float durationToAnalyse = 0; + bool correction = false; + float gain; + std::string correctionOutputName; + // Check required arguments if(argc < 2) { @@ -135,6 +152,23 @@ int main(int argc, char** argv) return 1; } } + else if(arguments.at(argument) == "--correction") + { + correction = true; + gain = std::atof(arguments.at(++argument).c_str()); + } + else if(arguments.at(argument) == "--correctionOutput") + { + try + { + correctionOutputName = arguments.at(++argument); + } + catch(...) + { + printHelp(); + return 1; + } + } // unknown option continue; } @@ -146,29 +180,41 @@ int main(int argc, char** argv) { // Get list of files / streamIndex to analyse std::vector arrayToAnalyse = parseConfigFile(arguments.at(0)); - AvSoundFile soundFile(arrayToAnalyse); - soundFile.setProgressionFile(outputProgressionName); - soundFile.setDurationToAnalyse(durationToAnalyse); - // Analyse loudness according to EBU R-128 - Loudness::analyser::LoudnessLevels level = Loudness::analyser::LoudnessLevels::Loudness_EBU_R128(); - Loudness::analyser::LoudnessAnalyser analyser(level); - soundFile.analyse(analyser); + if(correction) + { + AvCorrector corrector(arrayToAnalyse, correctionOutputName); + corrector.setProgressionFile(outputProgressionName); + corrector.correct(gain); + std::cout << "Correction with gain " << gain << " done." << std::endl; + } + else + { + AvSoundFile soundFile(arrayToAnalyse); + soundFile.setProgressionFile(outputProgressionName); + soundFile.setDurationToAnalyse(durationToAnalyse); - // Print analyse - analyser.printPloudValues(); + // Analyse loudness according to EBU R-128 + Loudness::analyser::LoudnessLevels level = Loudness::analyser::LoudnessLevels::Loudness_EBU_R128(); + Loudness::analyser::LoudnessAnalyser analyser(level); + soundFile.analyse(analyser); - // Write XML - std::vector mediaFilenames; - for(size_t i = 0; i < arrayToAnalyse.size(); ++i) - { - mediaFilenames.push_back(arrayToAnalyse.at(i)._filename); + // Print analyse + analyser.printPloudValues(); + + // Write XML + std::vector mediaFilenames; + for(size_t i = 0; i < arrayToAnalyse.size(); ++i) + { + mediaFilenames.push_back(arrayToAnalyse.at(i)._filename); + } + Loudness::tools::WriteXml writerXml(outputXMLReportName, mediaFilenames); + std::stringstream ss; + ss << soundFile.getNbChannelsToAnalyse(); + ss << " channels"; + writerXml.writeResults(ss.str(), analyser); } - Loudness::tools::WriteXml writerXml(outputXMLReportName, mediaFilenames); - std::stringstream ss; - ss << soundFile.getNbChannelsToAnalyse(); - ss << " channels"; - writerXml.writeResults(ss.str(), analyser); + } catch(const std::exception& e) { diff --git a/src/loudnessTools/WriteXml.cpp b/src/loudnessTools/WriteXml.cpp index c107e4c..de9dedf 100644 --- a/src/loudnessTools/WriteXml.cpp +++ b/src/loudnessTools/WriteXml.cpp @@ -39,6 +39,7 @@ void WriteXml::writeResults(const std::string& channelType, Loudness::analyser:: xmlFile << "\" " << printStandard(analyser.getStandard()) << " " << convertValid(analyser.isValidProgram()) << " " << "channelsType=\"" << channelType << "\" " + << "correctionGain=\"" << analyser.getCorrectionGain() << "\" " << "date=\"" << getDate() << "\">\n"; xmlFile << "\t" << analyser.getIntegratedLoudness() << "\n";