diff --git a/src/AvTranscoder/filter/FilterGraph.cpp b/src/AvTranscoder/filter/FilterGraph.cpp index 11b7346e..8b960ef9 100644 --- a/src/AvTranscoder/filter/FilterGraph.cpp +++ b/src/AvTranscoder/filter/FilterGraph.cpp @@ -1,7 +1,6 @@ #include "FilterGraph.hpp" #include -#include #include extern "C" { @@ -16,6 +15,123 @@ extern "C" { namespace avtranscoder { + +/****************** + + AudioFrameBuffer + + ******************/ + +AudioFrameBuffer::AudioFrameBuffer(const AudioFrameDesc& audioFrameDesc) + : _audioFrameDesc(audioFrameDesc) + , _frameQueue() + , _totalDataSize(0) + , _positionInFrontFrame(0) +{ +} + +AudioFrameBuffer::~AudioFrameBuffer() +{ + for(size_t i = 0; i < _frameQueue.size(); ++i) + popFrame(); +} + +size_t AudioFrameBuffer::getBytesPerSample() +{ + return av_get_bytes_per_sample(_audioFrameDesc._sampleFormat); +} + +void AudioFrameBuffer::addFrame(IFrame* frame) +{ + LOG_DEBUG("Add a new " << frame->getDataSize() << " bytes frame to frame buffer. New buffer size: " << _frameQueue.size() + 1); + // Copy the input frame to store it into the queue + AudioFrame* newAudioFrame = new AudioFrame(_audioFrameDesc, false); + const size_t expectedNbSamples = frame->getDataSize() / (newAudioFrame->getNbChannels() * newAudioFrame->getBytesPerSample()); + newAudioFrame->setNbSamplesPerChannel(expectedNbSamples); + newAudioFrame->allocateData(); + newAudioFrame->copyData(*frame); + + _totalDataSize += newAudioFrame->getDataSize(); + _frameQueue.push(newAudioFrame); +} + +void AudioFrameBuffer::popFrame() +{ + _frameQueue.pop(); + LOG_DEBUG("Pop frame from buffer. Remaining frames in buffer: " << _frameQueue.size()); +} + +IFrame* AudioFrameBuffer::getFrame(const size_t size) +{ + LOG_DEBUG("Get a " << size << " bytes frame from a " << _totalDataSize << " bytes frame buffer"); + IFrame* next = _frameQueue.front(); + const size_t nextFrameSize = next->getDataSize(); + + // If no expected size, or if the expected size equals the front frame of the queue (with no offset) + if(size == 0 || (size == nextFrameSize && _positionInFrontFrame == 0)) + { + // Directly return the front frame of the queue + _totalDataSize -= nextFrameSize; + popFrame(); + return next; + } + + // Create a new frame + AudioFrame* newAudioFrame = new AudioFrame(_audioFrameDesc, false); + const size_t expectedNbSamples = size / (newAudioFrame->getNbChannels() * newAudioFrame->getBytesPerSample()); + newAudioFrame->setNbSamplesPerChannel(expectedNbSamples); + newAudioFrame->allocateData(); + + // Concatenate frames data + size_t extractedDataSize = 0; + unsigned char* outputData = new unsigned char[size]; + while(extractedDataSize != size && _frameQueue.size() != 0) + { + // Get the front frame from queue + next = _frameQueue.front(); + size_t remainingDataInFrontFrame = next->getDataSize() - _positionInFrontFrame; + + // Compute the data size to get from the frame + size_t dataToGet = size - extractedDataSize; + if(dataToGet > remainingDataInFrontFrame) + dataToGet = remainingDataInFrontFrame; + + // Copy the data from the frame to temporal buffer + for(size_t i = 0; i < dataToGet; i++) + outputData[extractedDataSize++] = next->getData()[0][_positionInFrontFrame + i]; + + if(dataToGet < remainingDataInFrontFrame) + { + // Set new position into front frame + _positionInFrontFrame += dataToGet; + } + else + { + // The whole front frame has been read, so pop it from queue + popFrame(); + _positionInFrontFrame = 0; + } + } + + _totalDataSize -= extractedDataSize; + newAudioFrame->assignBuffer(outputData); + return newAudioFrame; +} + +IFrame* AudioFrameBuffer::getFrameSampleNb(const size_t sampleNb) +{ + const size_t expectedSize = sampleNb * getBytesPerSample(); + return getFrame(expectedSize); +} + + + +/****************** + + FilterGraph + + ******************/ + FilterGraph::FilterGraph(const ICodec& codec) : _graph(avfilter_graph_alloc()) , _filters() @@ -28,6 +144,7 @@ FilterGraph::FilterGraph(const ICodec& codec) FilterGraph::~FilterGraph() { + _inputAudioFrameBuffers.clear(); for(std::vector::iterator it = _filters.begin(); it < _filters.end(); ++it) { delete(*it); @@ -35,16 +152,131 @@ FilterGraph::~FilterGraph() avfilter_graph_free(&_graph); } +size_t FilterGraph::getAvailableFrameSize(const std::vector& inputs, const size_t& index) +{ + size_t frameSize = inputs.at(index)->getDataSize(); + if(frameSize == 0) + frameSize = _inputAudioFrameBuffers.at(index).getDataSize(); + return frameSize; +} + +size_t FilterGraph::getAvailableFrameSamplesNb(const std::vector& inputs, const size_t& index) +{ + if(_inputAudioFrameBuffers.empty()) + throw std::runtime_error("Cannot compute filter graph input samples number for non-audio frames."); + + const size_t bytesPerSample = _inputAudioFrameBuffers.at(index).getBytesPerSample(); + const size_t availableSamplesNb = getAvailableFrameSize(inputs, index) / bytesPerSample; + return availableSamplesNb; +} + +size_t FilterGraph::getMinInputFrameSamplesNb(const std::vector& inputs) +{ + if(!inputs.size()) + return 0; + + size_t minFrameSamplesNb = getAvailableFrameSamplesNb(inputs, 0); + for(size_t index = 1; index < inputs.size(); ++index) + { + const size_t availableFrameSampleNb = getAvailableFrameSamplesNb(inputs, index); + if(minFrameSamplesNb > availableFrameSampleNb) + minFrameSamplesNb = availableFrameSampleNb; + } + return minFrameSamplesNb; +} + +bool FilterGraph::hasBufferedFrames() +{ + if(!_inputAudioFrameBuffers.size()) + return false; + + for(std::vector::iterator it = _inputAudioFrameBuffers.begin(); it != _inputAudioFrameBuffers.end(); ++it) + { + if(it->isEmpty()) + return false; + } + return true; +} + +bool FilterGraph::hasBufferedFrames(const size_t index) +{ + if(index >= _inputAudioFrameBuffers.size()) + return false; + + return !_inputAudioFrameBuffers.at(index).isEmpty(); +} + +bool FilterGraph::areInputFrameSizesEqual(const std::vector& inputs) +{ + if(!inputs.size() || inputs.size() == 1) + return true; + + const int frameSize = inputs.at(0)->getDataSize(); + for(size_t index = 1; index < inputs.size(); ++index) + { + if(frameSize != inputs.at(index)->getDataSize()) + { + if(_inputAudioFrameBuffers.empty()) + return false; + else + { + const size_t refSampleNb = frameSize / _inputAudioFrameBuffers.at(0).getBytesPerSample(); + const size_t sampleNb = inputs.at(index)->getDataSize() / _inputAudioFrameBuffers.at(index).getBytesPerSample(); + return (refSampleNb == sampleNb); + } + } + } + return true; +} + +bool FilterGraph::areFrameBuffersEmpty() +{ + if(!_inputAudioFrameBuffers.size()) + return true; + + for(std::vector::iterator it = _inputAudioFrameBuffers.begin(); it != _inputAudioFrameBuffers.end(); ++it) + { + if(!it->isEmpty()) + return false; + } + return true; +} + void FilterGraph::process(const std::vector& inputs, IFrame& output) { - // init filter graph + // Init the filter graph if(!_isInit) init(inputs, output); - // setup input frames + // Check whether we can bypass the input audio buffers + const bool bypassBuffers = _inputAudioFrameBuffers.empty() || (areInputFrameSizesEqual(inputs) && areFrameBuffersEmpty()); + size_t minInputFrameSamplesNb = 0; + + if(!bypassBuffers) + { + // Fill the frame buffer with inputs + for(size_t index = 0; index < inputs.size(); ++index) + { + if(!inputs.at(index)->getDataSize()) + { + LOG_DEBUG("Empty frame from filter graph input " << index << ". Remaining audio frames in buffer: " << _inputAudioFrameBuffers.at(index).getBufferSize()); + continue; + } + _inputAudioFrameBuffers.at(index).addFrame(inputs.at(index)); + } + + // Get the minimum input frames size + minInputFrameSamplesNb = getMinInputFrameSamplesNb(inputs); + } + + + // Setup input frames into the filter graph for(size_t index = 0; index < inputs.size(); ++index) { - const int ret = av_buffersrc_write_frame(_filters.at(index)->getAVFilterContext(), &inputs.at(index)->getAVFrame()); + // Retrieve frame from buffer or directly from input + IFrame* inputFrame = (bypassBuffers)? inputs.at(index) : _inputAudioFrameBuffers.at(index).getFrameSampleNb(minInputFrameSamplesNb); + const int ret = av_buffersrc_add_frame_flags(_filters.at(index)->getAVFilterContext(), &inputFrame->getAVFrame(), AV_BUFFERSRC_FLAG_PUSH); + if(ret < 0) { throw std::runtime_error("Error when adding a frame to the source buffer used to start to process filters: " + @@ -52,7 +284,7 @@ void FilterGraph::process(const std::vector& inputs, IFrame& output) } } - // pull filtered data from the filter graph + // Pull filtered data from the filter graph for(;;) { const int ret = av_buffersink_get_frame(_filters.at(_filters.size() - 1)->getAVFilterContext(), &output.getAVFrame()); @@ -150,6 +382,11 @@ void FilterGraph::addInBuffer(const std::vector& inputs) filterOptions << "sample_rate=" << audioFrame->getSampleRate() << ":"; filterOptions << "sample_fmt=" << getSampleFormatName(audioFrame->getSampleFormat()) << ":"; filterOptions << "channel_layout=0x" << std::hex << audioFrame->getChannelLayout(); + + const AudioFrameDesc audioFrameDesc(audioFrame->getSampleRate(), + audioFrame->getNbChannels(), + getSampleFormatName(audioFrame->getSampleFormat())); + _inputAudioFrameBuffers.insert(_inputAudioFrameBuffers.begin(), AudioFrameBuffer(audioFrameDesc)); } // video frame else if((*it)->isVideoFrame()) diff --git a/src/AvTranscoder/filter/FilterGraph.hpp b/src/AvTranscoder/filter/FilterGraph.hpp index 287f6e9f..524a8357 100644 --- a/src/AvTranscoder/filter/FilterGraph.hpp +++ b/src/AvTranscoder/filter/FilterGraph.hpp @@ -5,14 +5,68 @@ #include #include #include +#include #include +#include struct AVFilterGraph; namespace avtranscoder { +/** + * @brief Filter graph input audio frame buffer. + * This FIFO buffer contains IFrame pointers and can deliver specific size audio frames. + * It makes no sense to use such buffers for video, since video frames are spatially consistent, + * so can not be divided nor concatenated. + **/ +class AvExport AudioFrameBuffer +{ +public: + AudioFrameBuffer(const AudioFrameDesc& audioFrameDesc); + ~AudioFrameBuffer(); + + /** + * @brief Return whether the buffer is empty or not. + */ + bool isEmpty() const { return _frameQueue.empty() && _totalDataSize == 0; } + /** + * @brief Return the total amount of available data contained in the frames of the buffer. + */ + size_t getDataSize() const { return _totalDataSize; } + /** + * @brief Return the number of frames contained in the buffer. + */ + size_t getBufferSize() const { return _frameQueue.size(); } + /** + * @brief Return the number of bytes by sample from the internal AudioFrameDesc. + */ + size_t getBytesPerSample(); + + /** + * @brief Push a frame at the end of the buffer. + */ + void addFrame(IFrame* frame); + + /** + * @brief Retrieve a IFrame pointer of the specified size, from the beginning of the buffer. + * If no size is specified, the whole first IFrame pointer is returned. + */ + IFrame* getFrame(const size_t size = 0); + IFrame* getFrameSampleNb(const size_t sampleNb); + +private: + void popFrame(); + + AudioFrameDesc _audioFrameDesc; + + std::queue _frameQueue; + size_t _totalDataSize; + size_t _positionInFrontFrame; + +}; + /** * @brief Manager of filters. **/ @@ -62,6 +116,9 @@ class AvExport FilterGraph */ bool hasFilters() const { return !_filters.empty(); } + bool hasBufferedFrames(); + bool hasBufferedFrames(const size_t index); + private: /** * @brief Initialize the graph of filters to process. @@ -82,11 +139,26 @@ class AvExport FilterGraph void addOutBuffer(const IFrame& output); //@} + /** + * @brief Return the input frame size if not null, or the available size into the corresponding frame buffer + */ + size_t getAvailableFrameSize(const std::vector& inputs, const size_t& index); + size_t getAvailableFrameSamplesNb(const std::vector& inputs, const size_t& index); + /** + * @brief Get the minimum samples number between input frames, or available frame buffers + */ + size_t getMinInputFrameSamplesNb(const std::vector& inputs); + + bool areInputFrameSizesEqual(const std::vector& inputs); + bool areFrameBuffersEmpty(); + private: AVFilterGraph* _graph; ///< The graph which holds the filters. std::vector _filters; ///< List of filters to process. const ICodec& _codec; ///< Codec of the stream on which the filters will be applied. + std::vector _inputAudioFrameBuffers; + /** * @brief Is the FilterGraph initialized. * @see init diff --git a/src/AvTranscoder/transcoder/StreamTranscoder.cpp b/src/AvTranscoder/transcoder/StreamTranscoder.cpp index e3133342..6c79e4d5 100644 --- a/src/AvTranscoder/transcoder/StreamTranscoder.cpp +++ b/src/AvTranscoder/transcoder/StreamTranscoder.cpp @@ -533,7 +533,7 @@ bool StreamTranscoder::processTranscode() // Decode LOG_DEBUG("Decode next frame") - bool decodingStatus = true; + std::vector decodingStatus(_generators.size(), true); for(size_t index = 0; index < _generators.size(); ++index) { if(getProcessCase() == eProcessCaseTranscode) @@ -542,15 +542,17 @@ bool StreamTranscoder::processTranscode() _currentDecoder = _generators.at(index); if(! _inputStreamDesc.empty() && _inputStreamDesc.at(index).demultiplexing()) - decodingStatus = decodingStatus && _currentDecoder->decodeNextFrame(*_decodedData.at(index), _inputStreamDesc.at(index)._channelIndexArray); + decodingStatus.at(index) = decodingStatus.at(index) && _currentDecoder->decodeNextFrame(*_decodedData.at(index), _inputStreamDesc.at(index)._channelIndexArray); else - decodingStatus = decodingStatus && _currentDecoder->decodeNextFrame(*_decodedData.at(index)); + decodingStatus.at(index) = decodingStatus.at(index) && _currentDecoder->decodeNextFrame(*_decodedData.at(index)); } // check the next data buffers in case of audio frames if(_decodedData.at(0)->isAudioFrame()) { const int nbInputSamplesPerChannel = _decodedData.at(0)->getAVFrame().nb_samples; + + // Reallocate output frame if(nbInputSamplesPerChannel > _filteredData->getAVFrame().nb_samples) { LOG_WARN("The buffer of filtered data corresponds to a frame of " << _filteredData->getAVFrame().nb_samples << " samples. The decoded buffer contains " << nbInputSamplesPerChannel << " samples. Reallocate it.") @@ -569,7 +571,26 @@ bool StreamTranscoder::processTranscode() // Transform CodedData data; - if(decodingStatus) + bool continueProcess = true; + for(size_t index = 0; index < decodingStatus.size(); ++index) + { + if(!decodingStatus.at(index)) + { + if(!_filterGraph->hasFilters() || !_filterGraph->hasBufferedFrames(index)) + { + continueProcess = false; + break; + } + LOG_DEBUG("Some frames remain into filter graph buffer " << index); + + // Reset the non-decoded data as an empty frame + _decodedData.at(index)->freeData(); + _decodedData.at(index)->getAVFrame().format = -1; + _decodedData.at(index)->getAVFrame().nb_samples = 0; + } + } + + if(continueProcess) { IFrame* dataToTransform = NULL; if(_filterGraph->hasFilters()) diff --git a/test/pyTest/testMuxAudioChannels.py b/test/pyTest/testMuxAudioChannels.py new file mode 100644 index 00000000..01ab6720 --- /dev/null +++ b/test/pyTest/testMuxAudioChannels.py @@ -0,0 +1,109 @@ +import os + + # Check if environment is setup to run the tests +if os.environ.get('AVTRANSCODER_TEST_AUDIO_WAVE_FILE') is None or \ + os.environ.get('AVTRANSCODER_TEST_AUDIO_MOV_FILE') is None: + from nose.plugins.skip import SkipTest + raise SkipTest("Need to define environment variables " + "AVTRANSCODER_TEST_AUDIO_WAVE_FILE and " + "AVTRANSCODER_TEST_AUDIO_MOV_FILE") + +from nose.tools import * + +from pyAvTranscoder import avtranscoder as av + +def testMuxAudioChannelsFromDifferentFormatInputs_20(): + """ + Mux audio channels from different formats files, and generate one audio stereo 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(inputFileName1, 1, 1)) + inputs.append(av.InputStreamDesc(inputFileName2, 0, 2)) + + # output + outputFileName = "testMuxAudioChannelsFromDifferentFormatInputs_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) + + 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_fileProperties.getAudioProperties() + assert_equals(1, len(dst_audioProperties)) + assert_equals(2, dst_audioProperties[0].getNbChannels()) + +def testMuxAudioChannelsFromDifferentFormatInputs_51(): + """ + Mux audio channels from different formats files, and generate one audio stereo 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(inputFileName1, 1, 1)) + inputs.append(av.InputStreamDesc(inputFileName1, 1, 0)) + inputs.append(av.InputStreamDesc(inputFileName2, 0, 2)) + inputs.append(av.InputStreamDesc(inputFileName2, 0, 5)) + inputs.append(av.InputStreamDesc(inputFileName2, 0, 1)) + inputs.append(av.InputStreamDesc(inputFileName2, 0, 3)) + + # output + outputFileName = "testMuxAudioChannelsFromDifferentFormatInputs_51.wav" + ouputFile = av.OutputFile(outputFileName) + + transcoder = av.Transcoder(ouputFile) + transcoder.addStream(inputs, "wave24b48k5_1") + + 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()) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy