diff --git a/src/AvTranscoder/transcoder/InputStreamDesc.hpp b/src/AvTranscoder/transcoder/InputStreamDesc.hpp index a3083e5d..e50d0aed 100644 --- a/src/AvTranscoder/transcoder/InputStreamDesc.hpp +++ b/src/AvTranscoder/transcoder/InputStreamDesc.hpp @@ -63,7 +63,7 @@ struct InputStreamDesc bool demultiplexing() const { return !_channelIndexArray.empty(); } public: - std::string _filename; ///< Source file path. + std::string _filename; ///< Source file path. If empty, a generator is used as source. size_t _streamIndex; ///< Source stream to extract. std::vector _channelIndexArray; ///< List of source channels to extract from the stream }; diff --git a/src/AvTranscoder/transcoder/StreamTranscoder.cpp b/src/AvTranscoder/transcoder/StreamTranscoder.cpp index 6c79e4d5..391f5ddd 100644 --- a/src/AvTranscoder/transcoder/StreamTranscoder.cpp +++ b/src/AvTranscoder/transcoder/StreamTranscoder.cpp @@ -33,6 +33,7 @@ StreamTranscoder::StreamTranscoder(IInputStream& inputStream, IOutputFile& outpu , _outputEncoder(NULL) , _transform(NULL) , _filterGraph(NULL) + , _firstInputStreamIndex(0) , _offset(offset) , _needToSwitchToGenerator(false) { @@ -140,6 +141,7 @@ StreamTranscoder::StreamTranscoder(const std::vector& inputStre , _outputEncoder(NULL) , _transform(NULL) , _filterGraph(NULL) + , _firstInputStreamIndex(std::numeric_limits::max()) , _offset(offset) , _needToSwitchToGenerator(false) { @@ -147,12 +149,27 @@ StreamTranscoder::StreamTranscoder(const std::vector& inputStre size_t nbOutputChannels = 0; for(size_t index = 0; index < inputStreams.size(); ++index) { - addDecoder(_inputStreamDesc.at(index), *_inputStreams.at(index)); - nbOutputChannels += _inputStreamDesc.at(index)._channelIndexArray.size(); + if(_inputStreams.at(index) != NULL) + { + LOG_INFO("add decoder for input stream " << index); + addDecoder(_inputStreamDesc.at(index), *_inputStreams.at(index)); + nbOutputChannels += _inputStreamDesc.at(index)._channelIndexArray.size(); + if(_firstInputStreamIndex == std::numeric_limits::max()) + _firstInputStreamIndex = index; + } + else + { + LOG_INFO("add generator for empty input " << index); + addGenerator(_inputStreamDesc.at(index), profile); + nbOutputChannels++; + } } - IInputStream& inputStream = *_inputStreams.at(0); - const InputStreamDesc& inputStreamDesc = inputStreamsDesc.at(0); + if(_firstInputStreamIndex == std::numeric_limits::max()) + throw std::runtime_error("Cannot handle empty only input streams"); + + IInputStream& inputStream = *_inputStreams.at(_firstInputStreamIndex); + const InputStreamDesc& inputStreamDesc = inputStreamsDesc.at(_firstInputStreamIndex); // create a transcode case switch(inputStream.getProperties().getStreamType()) @@ -278,6 +295,53 @@ void StreamTranscoder::addDecoder(const InputStreamDesc& inputStreamDesc, IInput } } +void StreamTranscoder::addGenerator(const InputStreamDesc& inputStreamDesc, const ProfileLoader::Profile& profile) +{ + // create a transcode case + if(profile.find(constants::avProfileType)->second == constants::avProfileTypeVideo) + { + VideoCodec inputVideoCodec(eCodecTypeEncoder, profile.find(constants::avProfileCodec)->second); + VideoFrameDesc inputFrameDesc(profile); + inputVideoCodec.setImageParameters(inputFrameDesc); + + // generator decoder + VideoGenerator* generator = new VideoGenerator(inputFrameDesc); + _generators.push_back(generator); + _currentDecoder = generator; + + // buffers to process + VideoFrameDesc outputFrameDesc = inputFrameDesc; + outputFrameDesc.setParameters(profile); + _decodedData.push_back(new VideoFrame(inputFrameDesc)); + + // no decoder for this input + _inputDecoders.push_back(NULL); + + } + else if(profile.find(constants::avProfileType)->second == constants::avProfileTypeAudio) + { + // corresponding input codec + AudioCodec inputAudioCodec(eCodecTypeEncoder, profile.find(constants::avProfileCodec)->second); + AudioFrameDesc inputFrameDesc(profile); + inputFrameDesc._nbChannels = 1; + inputAudioCodec.setAudioParameters(inputFrameDesc); + + // generator decoder + AudioGenerator* generator = new AudioGenerator(inputFrameDesc); + _generators.push_back(generator); + _currentDecoder = generator; + // buffers to get the decoded data + _decodedData.push_back(new AudioFrame(inputFrameDesc)); + + // no decoder for this input + _inputDecoders.push_back(NULL); + } + else + { + throw std::runtime_error("unupported stream type"); + } +} + StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader::Profile& profile) : _inputStreamDesc() , _inputStreams() @@ -291,6 +355,7 @@ StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader: , _outputEncoder(NULL) , _transform(NULL) , _filterGraph(NULL) + , _firstInputStreamIndex(0) , _offset(0) , _needToSwitchToGenerator(false) { @@ -375,7 +440,8 @@ StreamTranscoder::~StreamTranscoder() for(std::vector::iterator it = _inputDecoders.begin(); it != _inputDecoders.end(); ++it) { - delete(*it); + if(*it != NULL) + delete(*it); } for(std::vector::iterator it = _generators.begin(); it != _generators.end(); ++it) { @@ -536,7 +602,8 @@ bool StreamTranscoder::processTranscode() std::vector decodingStatus(_generators.size(), true); for(size_t index = 0; index < _generators.size(); ++index) { - if(getProcessCase() == eProcessCaseTranscode) + EProcessCase processCase = getProcessCase(index); + if(processCase == eProcessCaseTranscode) _currentDecoder = _inputDecoders.at(index); else _currentDecoder = _generators.at(index); @@ -548,9 +615,9 @@ bool StreamTranscoder::processTranscode() } // check the next data buffers in case of audio frames - if(_decodedData.at(0)->isAudioFrame()) + if(_decodedData.at(_firstInputStreamIndex)->isAudioFrame()) { - const int nbInputSamplesPerChannel = _decodedData.at(0)->getAVFrame().nb_samples; + const int nbInputSamplesPerChannel = _decodedData.at(_firstInputStreamIndex)->getAVFrame().nb_samples; // Reallocate output frame if(nbInputSamplesPerChannel > _filteredData->getAVFrame().nb_samples) @@ -670,7 +737,11 @@ float StreamTranscoder::getDuration() const float minStreamDuration = -1; for(size_t index = 0; index < _inputStreams.size(); ++index) { - const StreamProperties& streamProperties = _inputStreams.at(index)->getProperties(); + IInputStream* inputStream = _inputStreams.at(index); + if(inputStream == NULL) + continue; + + const StreamProperties& streamProperties = inputStream->getProperties(); if(minStreamDuration == -1 || streamProperties.getDuration() < minStreamDuration) minStreamDuration = streamProperties.getDuration(); } @@ -722,13 +793,30 @@ void StreamTranscoder::setOffset(const float offset) } } -StreamTranscoder::EProcessCase StreamTranscoder::getProcessCase() const +StreamTranscoder::EProcessCase StreamTranscoder::getProcessCase(const size_t decoderIndex) const { - if(! _inputStreams.empty() && ! _inputDecoders.empty() && std::find(_inputDecoders.begin(), _inputDecoders.end(), _currentDecoder) != _inputDecoders.end() ) - return eProcessCaseTranscode; - else if(! _inputStreams.empty() && _inputDecoders.empty() && !_currentDecoder) - return eProcessCaseRewrap; + if(_inputStreamDesc.size() <= 1) + { + if(! _inputStreams.empty() && ! _inputDecoders.empty() && std::find(_inputDecoders.begin(), _inputDecoders.end(), _currentDecoder) != _inputDecoders.end() ) + return eProcessCaseTranscode; + else if(! _inputStreams.empty() && _inputDecoders.empty() && !_currentDecoder) + return eProcessCaseRewrap; + else + return eProcessCaseGenerator; + } else + { + if(! _inputStreams.empty() && _currentDecoder != NULL) + { + if( _inputStreams.at(decoderIndex) != NULL) + return eProcessCaseTranscode; + return eProcessCaseGenerator; + } + else if(! _inputStreams.empty() && _inputDecoders.empty() && !_currentDecoder) + { + return eProcessCaseRewrap; + } return eProcessCaseGenerator; + } } } diff --git a/src/AvTranscoder/transcoder/StreamTranscoder.hpp b/src/AvTranscoder/transcoder/StreamTranscoder.hpp index c916b8cc..c72db0ea 100644 --- a/src/AvTranscoder/transcoder/StreamTranscoder.hpp +++ b/src/AvTranscoder/transcoder/StreamTranscoder.hpp @@ -115,7 +115,7 @@ class AvExport StreamTranscoder eProcessCaseRewrap, eProcessCaseGenerator }; - EProcessCase getProcessCase() const; + EProcessCase getProcessCase(const size_t decoderIndex = 0) const; //@} private: @@ -125,6 +125,7 @@ class AvExport StreamTranscoder * @param inputStream */ void addDecoder(const InputStreamDesc& inputStreamDesc, IInputStream& inputStream); + void addGenerator(const InputStreamDesc& inputStreamDesc, const ProfileLoader::Profile& profile); bool processRewrap(); bool processTranscode(); @@ -147,6 +148,8 @@ class AvExport StreamTranscoder FilterGraph* _filterGraph; ///< Filter graph (has ownership) + size_t _firstInputStreamIndex; ///< Index of the first non-null input stream. + float _offset; ///< Offset, in seconds, at the beginning of the StreamTranscoder. bool _needToSwitchToGenerator; ///< Set if need to switch to a generator during the process (because, of other streams diff --git a/src/AvTranscoder/transcoder/Transcoder.cpp b/src/AvTranscoder/transcoder/Transcoder.cpp index 3e35ad4e..5249f330 100644 --- a/src/AvTranscoder/transcoder/Transcoder.cpp +++ b/src/AvTranscoder/transcoder/Transcoder.cpp @@ -260,6 +260,12 @@ void Transcoder::addTranscodeStream(const std::vector& inputStr AVMediaType commonStreamType = AVMEDIA_TYPE_UNKNOWN; for(std::vector::const_iterator it = inputStreamDescArray.begin(); it != inputStreamDescArray.end(); ++it) { + if(it->_filename.empty()) + { + inputStreams.push_back(NULL); + continue; + } + InputFile* referenceFile = addInputFile(it->_filename, it->_streamIndex, offset); inputStreams.push_back(&referenceFile->getStream(it->_streamIndex)); @@ -329,7 +335,19 @@ ProfileLoader::Profile Transcoder::getProfileFromInputs(const std::vector= 1); // Get properties from the first input - const InputStreamDesc& inputStreamDesc = inputStreamDescArray.at(0); + size_t nonEmptyFileName = std::numeric_limits::max(); + for(size_t i = 0; i < inputStreamDescArray.size(); ++i) + { + if(!inputStreamDescArray.at(i)._filename.empty()) + { + nonEmptyFileName = i; + break; + } + } + if(nonEmptyFileName == std::numeric_limits::max()) + throw std::runtime_error("Cannot get profile from empty input streams"); + + const InputStreamDesc& inputStreamDesc = inputStreamDescArray.at(nonEmptyFileName); InputFile inputFile(inputStreamDesc._filename); const StreamProperties* streamProperties = &inputFile.getProperties().getStreamPropertiesWithIndex(inputStreamDesc._streamIndex); @@ -519,7 +537,19 @@ void Transcoder::fillProcessStat(ProcessStat& processStat) LOG_WARN("Cannot process statistics of generated stream.") continue; } - const IInputStream* inputStream = _streamTranscoders.at(streamIndex)->getInputStreams().at(0); + + size_t nonNullInputStreamIndex = 0; + std::vector inputStreams = _streamTranscoders.at(streamIndex)->getInputStreams(); + for(size_t i = 0; i < inputStreams.size(); ++i) + { + if(inputStreams.at(i) != NULL) + { + nonNullInputStreamIndex = i; + break; + } + } + + const IInputStream* inputStream = inputStreams.at(nonNullInputStreamIndex); const AVMediaType mediaType = inputStream->getProperties().getStreamType(); switch(mediaType) { diff --git a/test/pyTest/testMuxAudioChannels.py b/test/pyTest/testMuxAudioChannels.py index 01ab6720..6850e934 100644 --- a/test/pyTest/testMuxAudioChannels.py +++ b/test/pyTest/testMuxAudioChannels.py @@ -60,7 +60,7 @@ def testMuxAudioChannelsFromDifferentFormatInputs_20(): def testMuxAudioChannelsFromDifferentFormatInputs_51(): """ - Mux audio channels from different formats files, and generate one audio stereo stream + Mux audio channels from different formats files, and generate one audio 5.1 stream """ # inputs inputFileName1 = os.environ['AVTRANSCODER_TEST_AUDIO_MOV_FILE'] @@ -107,3 +107,155 @@ def testMuxAudioChannelsFromDifferentFormatInputs_51(): dst_audioProperties = dst_inputFile.getProperties().getAudioProperties() assert_equals(1, len(dst_audioProperties)) assert_equals(6, dst_audioProperties[0].getNbChannels()) + + +def testMuxAudioChannelsWithSilence_20(): + """ + Mux audio channels with generated silence, and generate one audio stereo stream + """ + # input + inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] + + inputs = av.InputStreamDescVector() + inputs.append(av.InputStreamDesc(inputFileName, 0, 0)) + inputs.append(av.InputStreamDesc("", 0, 0)) # empty filename to generate silence + + # output + outputFileName = "testMuxAudioChannelsWithSilence_20.wav" + ouputFile = av.OutputFile(outputFileName) + + transcoder = av.Transcoder(ouputFile) + transcoder.addStream(inputs, "wave24b48kstereo") + + progress = av.ConsoleProgress() + processStat = transcoder.process( progress ) + + # check process stat returned + audioStat = processStat.getAudioStat(0) + + inputFile = av.InputFile(inputFileName) + + src_audioStream = inputFile.getProperties().getAudioProperties()[0] + + assert_equals(src_audioStream.getDuration(), audioStat.getDuration()) + + # check dst file properties + dst_inputFile = av.InputFile(outputFileName) + dst_fileProperties = dst_inputFile.getProperties() + assert_equals(src_audioStream.getDuration(), dst_fileProperties.getDuration()) + + # check dst audio streams + dst_audioProperties = dst_fileProperties.getAudioProperties() + assert_equals(1, len(dst_audioProperties)) + assert_equals(2, dst_audioProperties[0].getNbChannels()) + +def testMuxAudioChannelsWithSilenceProfileFromInput_20(): + """ + Mux audio channels with generated silence, and generate one audio stereo stream + """ + # input + inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] + + inputs = av.InputStreamDescVector() + inputs.append(av.InputStreamDesc(inputFileName, 0, 0)) + inputs.append(av.InputStreamDesc("", 0, 0)) # empty filename to generate silence + + # output + outputFileName = "testMuxAudioChannelsWithSilenceNoProfile_20.wav" + ouputFile = av.OutputFile(outputFileName) + + transcoder = av.Transcoder(ouputFile) + transcoder.addStream(inputs) + + progress = av.ConsoleProgress() + processStat = transcoder.process( progress ) + + # check process stat returned + audioStat = processStat.getAudioStat(0) + + inputFile = av.InputFile(inputFileName) + + src_audioStream = inputFile.getProperties().getAudioProperties()[0] + + assert_equals(src_audioStream.getDuration(), audioStat.getDuration()) + + # check dst file properties + dst_inputFile = av.InputFile(outputFileName) + dst_fileProperties = dst_inputFile.getProperties() + assert_equals(src_audioStream.getDuration(), dst_fileProperties.getDuration()) + + # check dst audio streams + dst_audioProperties = dst_fileProperties.getAudioProperties() + assert_equals(1, len(dst_audioProperties)) + assert_equals(2, dst_audioProperties[0].getNbChannels()) + +def testMuxAudioChannelsWithSilenceProfileFromInput_51(): + """ + Mux audio channels with generated silence, and generate one audio 5.1 stream + """ + # inputs + inputFileName1 = os.environ['AVTRANSCODER_TEST_AUDIO_MOV_FILE'] + inputFileName2 = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] + assert_not_equals(inputFileName1, inputFileName2) + + inputs = av.InputStreamDescVector() + inputs.append(av.InputStreamDesc("", 0, 0)) # empty filename to generate silence + inputs.append(av.InputStreamDesc(inputFileName1, 1, 0)) + inputs.append(av.InputStreamDesc(inputFileName2, 0, 2)) + inputs.append(av.InputStreamDesc("", 0, 0)) # empty filename to generate silence + inputs.append(av.InputStreamDesc(inputFileName2, 0, 1)) + inputs.append(av.InputStreamDesc("", 0, 0)) # empty filename to generate silence + + # output + outputFileName = "testMuxAudioChannelsWithSilenceProfileFromInput_51.wav" + ouputFile = av.OutputFile(outputFileName) + + transcoder = av.Transcoder(ouputFile) + transcoder.addStream(inputs) + + progress = av.ConsoleProgress() + processStat = transcoder.process( progress ) + + # check process stat returned + audioStat = processStat.getAudioStat(0) + + inputFile1 = av.InputFile(inputFileName1) + inputFile2 = av.InputFile(inputFileName2) + + src_audioStream1 = inputFile1.getProperties().getAudioProperties()[0] + src_audioStream2 = inputFile2.getProperties().getAudioProperties()[0] + + min_src_duration = min(src_audioStream1.getDuration(), src_audioStream2.getDuration()) + + assert_equals(min_src_duration, audioStat.getDuration()) + + # check dst file properties + dst_inputFile = av.InputFile(outputFileName) + dst_fileProperties = dst_inputFile.getProperties() + assert_equals(min_src_duration, dst_fileProperties.getDuration()) + + # check dst audio streams + dst_audioProperties = dst_inputFile.getProperties().getAudioProperties() + assert_equals(1, len(dst_audioProperties)) + assert_equals(6, dst_audioProperties[0].getNbChannels()) + +@raises(RuntimeError) +def testMuxAudioChannelsWithSilenceOnly_20(): + """ + Mux audio channels with generated silence, and generate one audio stereo stream + """ + # input + inputs = av.InputStreamDescVector() + inputs.append(av.InputStreamDesc("", 0, 0)) # empty filename to generate silence + inputs.append(av.InputStreamDesc("", 0, 0)) # empty filename to generate silence + + # output + outputFileName = "testMuxAudioChannelsWithSilenceOnly_20.wav" + ouputFile = av.OutputFile(outputFileName) + + transcoder = av.Transcoder(ouputFile) + transcoder.addStream(inputs, "wave24b48kstereo") + + progress = av.ConsoleProgress() + processStat = transcoder.process( progress ) +