diff --git a/src/AvTranscoder/data/decoded/Frame.cpp b/src/AvTranscoder/data/decoded/Frame.cpp index 68491b24..31b40d54 100644 --- a/src/AvTranscoder/data/decoded/Frame.cpp +++ b/src/AvTranscoder/data/decoded/Frame.cpp @@ -40,7 +40,7 @@ Frame::~Frame() } } -void Frame::copyData(const Frame& frameToRef) +void Frame::refData(const Frame& frameToRef) { const int ret = av_frame_copy(_frame, &frameToRef.getAVFrame()); if(ret < 0) @@ -63,11 +63,6 @@ void Frame::refFrame(const Frame& otherFrame) } } -void Frame::clear() -{ - av_frame_unref(_frame); -} - void Frame::allocateAVFrame() { #if LIBAVCODEC_VERSION_MAJOR > 54 diff --git a/src/AvTranscoder/data/decoded/Frame.hpp b/src/AvTranscoder/data/decoded/Frame.hpp index 8d014891..efc5a20b 100644 --- a/src/AvTranscoder/data/decoded/Frame.hpp +++ b/src/AvTranscoder/data/decoded/Frame.hpp @@ -43,9 +43,11 @@ class AvExport Frame int* getLineSize() const { return _frame->linesize; } /** - * @brief Copy the data of the given Frame. - */ - void copyData(const Frame& frameToRef); + * @brief Ref to data of the given Frame. + * @note This function does not allocate anything. + * The current Frame must be already initialized and allocated with the same parameters as frameToRef. + */ + void refData(const Frame& frameToRef); /** * @brief Copy all the fields that do not affect the data layout in the buffers. @@ -58,11 +60,6 @@ class AvExport Frame */ void refFrame(const Frame& otherFrame); - /** - * @brief Unreference all the buffers referenced by frame and reset the frame fields. - */ - void clear(); - /** * @return If it corresponds to a valid audio frame. * @see AudioFrame diff --git a/src/AvTranscoder/decoder/AudioGenerator.cpp b/src/AvTranscoder/decoder/AudioGenerator.cpp index 33309f10..03a57f14 100644 --- a/src/AvTranscoder/decoder/AudioGenerator.cpp +++ b/src/AvTranscoder/decoder/AudioGenerator.cpp @@ -1,5 +1,9 @@ #include "AudioGenerator.hpp" +#include + +#include + namespace avtranscoder { @@ -41,15 +45,25 @@ bool AudioGenerator::decodeNextFrame(Frame& frameBuffer) if(!_silent) { AudioFrame& audioBuffer = static_cast(frameBuffer); + + std::stringstream msg; + msg << "Generate a silence with the following features:" << std::endl; + msg << "sample rate = " << audioBuffer.desc()._sampleRate << std::endl; + msg << "number of channels = " << audioBuffer.desc()._nbChannels << std::endl; + msg << "sample format = " << getSampleFormatName(audioBuffer.desc()._sampleFormat) << std::endl; + LOG_INFO(msg.str()) + _silent = new AudioFrame(audioBuffer.desc()); } + LOG_INFO("Reference the silence when decode next frame") frameBuffer.getAVFrame().nb_samples = _silent->getAVFrame().nb_samples; - frameBuffer.copyData(*_silent); + dynamic_cast(frameBuffer).assign(_silent->getData()[0]); } // Take audio frame from _inputFrame else { - frameBuffer.copyData(*_inputFrame); + LOG_INFO("Reference the audio data specified when decode next frame") + frameBuffer.refData(*_inputFrame); } return true; } diff --git a/src/AvTranscoder/decoder/VideoGenerator.cpp b/src/AvTranscoder/decoder/VideoGenerator.cpp index 74f16a6f..01ac5e50 100644 --- a/src/AvTranscoder/decoder/VideoGenerator.cpp +++ b/src/AvTranscoder/decoder/VideoGenerator.cpp @@ -3,6 +3,8 @@ #include #include +#include + namespace avtranscoder { @@ -51,6 +53,13 @@ bool VideoGenerator::decodeNextFrame(Frame& frameBuffer) // Generate the black image only once if(!_blackImage) { + std::stringstream msg; + msg << "Generate a black image with the following features:" << std::endl; + msg << "width = " << _frameDesc._width << std::endl; + msg << "height = " << _frameDesc._height << std::endl; + msg << "pixel format = rgb24" << std::endl; + LOG_INFO(msg.str()) + VideoFrame& imageBuffer = static_cast(frameBuffer); // Input of convert @@ -68,12 +77,14 @@ bool VideoGenerator::decodeNextFrame(Frame& frameBuffer) VideoTransform videoTransform; videoTransform.convert(intermediateBuffer, *_blackImage); } - frameBuffer.copyData(*_blackImage); + LOG_INFO("Reference the black image when decode next frame") + frameBuffer.refData(*_blackImage); } // Take image from _inputFrame else { - frameBuffer.copyData(*_inputFrame); + LOG_INFO("Reference the image data specified when decode next frame") + frameBuffer.refData(*_inputFrame); } return true; } diff --git a/src/AvTranscoder/reader/AudioReader.cpp b/src/AvTranscoder/reader/AudioReader.cpp index f47bf919..9495d8b5 100644 --- a/src/AvTranscoder/reader/AudioReader.cpp +++ b/src/AvTranscoder/reader/AudioReader.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -42,6 +43,9 @@ void AudioReader::init() _decoder = new AudioDecoder(_inputFile->getStream(_streamIndex)); _decoder->setupDecoder(); + // generator + _generator = new AudioGenerator(); + // create transform _transform = new AudioTransform(); @@ -57,6 +61,7 @@ void AudioReader::init() AudioReader::~AudioReader() { delete _decoder; + delete _generator; delete _srcFrame; delete _dstFrame; delete _transform; diff --git a/src/AvTranscoder/reader/IReader.cpp b/src/AvTranscoder/reader/IReader.cpp index 8f7e7630..c4eb9b12 100644 --- a/src/AvTranscoder/reader/IReader.cpp +++ b/src/AvTranscoder/reader/IReader.cpp @@ -16,6 +16,7 @@ IReader::IReader(const std::string& filename, const size_t streamIndex, const in , _channelIndex(channelIndex) , _currentFrame(-1) , _inputFileAllocated(true) + , _continueWithGenerator(false) { _inputFile = new InputFile(filename); } @@ -31,6 +32,7 @@ IReader::IReader(InputFile& inputFile, const size_t streamIndex, const int chann , _channelIndex(channelIndex) , _currentFrame(-1) , _inputFileAllocated(false) + , _continueWithGenerator(false) { } @@ -53,6 +55,7 @@ Frame* IReader::readPrevFrame() Frame* IReader::readFrameAt(const size_t frame) { assert(_decoder != NULL); + assert(_generator != NULL); assert(_transform != NULL); assert(_srcFrame != NULL); assert(_dstFrame != NULL); @@ -70,10 +73,19 @@ Frame* IReader::readFrameAt(const size_t frame) decodingStatus = _decoder->decodeNextFrame(*_srcFrame, _channelIndex); else decodingStatus = _decoder->decodeNextFrame(*_srcFrame); + // if decoding failed if(!decodingStatus) { - _dstFrame->clear(); - return _dstFrame; + // generate data (ie silence or black) + if(_continueWithGenerator) + { + _generator->decodeNextFrame(*_srcFrame); + } + // or return an empty frame + else + { + return &_emptyFrame; + } } // transform _transform->convert(*_srcFrame, *_dstFrame); diff --git a/src/AvTranscoder/reader/IReader.hpp b/src/AvTranscoder/reader/IReader.hpp index 51e41083..1d55588e 100644 --- a/src/AvTranscoder/reader/IReader.hpp +++ b/src/AvTranscoder/reader/IReader.hpp @@ -53,10 +53,17 @@ class AvExport IReader */ const StreamProperties* getSourceProperties() const { return _streamProperties; } + /** + * @brief Set the reader state to generate data (ie silence or black) when there is no more data to decode. + * @note By default, the reader returns an empty frame. + */ + void continueWithGenerator(const bool continueWithGenerator = true) { _continueWithGenerator = continueWithGenerator; } + protected: InputFile* _inputFile; const StreamProperties* _streamProperties; IDecoder* _decoder; + IDecoder* _generator; Frame* _srcFrame; Frame* _dstFrame; @@ -69,6 +76,8 @@ class AvExport IReader private: int _currentFrame; ///< The current decoded frame. bool _inputFileAllocated; ///< Does the InputFile is held by the class or not (depends on the constructor called) + bool _continueWithGenerator; ///< If there is no more data to decode, complete with generated data + Frame _emptyFrame; ///< If there is no more data to decode, return an empty frame }; } diff --git a/src/AvTranscoder/reader/VideoReader.cpp b/src/AvTranscoder/reader/VideoReader.cpp index d5ddd5f7..53fc00f1 100644 --- a/src/AvTranscoder/reader/VideoReader.cpp +++ b/src/AvTranscoder/reader/VideoReader.cpp @@ -1,6 +1,7 @@ #include "VideoReader.hpp" #include +#include #include #include #include @@ -41,6 +42,11 @@ void VideoReader::init() _decoder = new VideoDecoder(_inputFile->getStream(_streamIndex)); _decoder->setupDecoder(); + // generator + VideoGenerator* generator = new VideoGenerator(); + generator->setVideoFrameDesc(_inputFile->getStream(_streamIndex).getVideoCodec().getVideoFrameDesc()); + _generator = generator; + // create transform _transform = new VideoTransform(); @@ -56,6 +62,7 @@ void VideoReader::init() VideoReader::~VideoReader() { delete _decoder; + delete _generator; delete _srcFrame; delete _dstFrame; delete _transform; diff --git a/test/pyTest/testReader.py b/test/pyTest/testReader.py index f78a64f8..4187bbeb 100644 --- a/test/pyTest/testReader.py +++ b/test/pyTest/testReader.py @@ -24,7 +24,8 @@ def testVideoReaderCreateNewInputFile(): # read all frames and check their size for i in xrange(0, reader.getSourceVideoProperties().getNbFrames()): frame = av.VideoFrame(reader.readNextFrame()) - assert_equals( frame.getSize(), reader.getOutputWidth() * reader.getOutputHeight() * reader.getOutputNbComponents() ) + bytesPerPixel = reader.getOutputBitDepth() / 8 + assert_equals( frame.getSize(), reader.getOutputWidth() * reader.getOutputHeight() * bytesPerPixel ) # check if the next frame is empty frame = av.VideoFrame(reader.readNextFrame()) @@ -43,7 +44,8 @@ def testVideoReaderReferenceInputFile(): # read all frames and check their size for i in xrange(0, reader.getSourceVideoProperties().getNbFrames()): frame = av.VideoFrame(reader.readNextFrame()) - assert_equals( frame.getSize(), reader.getOutputWidth() * reader.getOutputHeight() * reader.getOutputNbComponents() ) + bytesPerPixel = reader.getOutputBitDepth() / 8 + assert_equals( frame.getSize(), reader.getOutputWidth() * reader.getOutputHeight() * bytesPerPixel ) # check if the next frame is empty frame = av.VideoFrame(reader.readNextFrame()) @@ -74,3 +76,60 @@ def testAudioReaderChannelsExtraction(): sizeOfFrameWithOneChannels = frame.getSize() assert_equals( sizeOfFrameWithAllChannels / nbChannels, sizeOfFrameWithOneChannels ) + + +def testVideoReaderWithGenerator(): + """ + Read a video stream with the VideoReader. + When there is no more data to decode, switch to a generator and process some frames. + """ + inputFileName = os.environ['AVTRANSCODER_TEST_VIDEO_AVI_FILE'] + reader = av.VideoReader(inputFileName) + + # read all frames and check their size + for i in xrange(0, reader.getSourceVideoProperties().getNbFrames()): + frame = av.VideoFrame(reader.readNextFrame()) + bytesPerPixel = reader.getOutputBitDepth() / 8 + assert_equals( frame.getSize(), reader.getOutputWidth() * reader.getOutputHeight() * bytesPerPixel ) + + # check if the next frame is empty + frame = av.VideoFrame(reader.readNextFrame()) + assert_equals( frame.getSize(), 0 ) + + # generate 10 frames of black + reader.continueWithGenerator() + for i in xrange(0, 9): + frame = av.VideoFrame(reader.readNextFrame()) + bytesPerPixel = reader.getOutputBitDepth() / 8 + assert_equals( frame.getSize(), reader.getOutputWidth() * reader.getOutputHeight() * bytesPerPixel ) + + +def testAudioReaderWithGenerator(): + """ + Read an audio stream with the AudioReader. + When there is no more data to decode, switch to a generator and process some frames. + """ + inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] + inputFile = av.InputFile(inputFileName) + reader = av.AudioReader(inputFile) + + # read all frames and check their size + while True: + frame = av.AudioFrame(reader.readNextFrame()) + if frame.getSize() == 0: + break + nbSamplesPerChannel = frame.getNbSamplesPerChannel() + bytesPerSample = 2 + assert_equals( frame.getSize(), reader.getOutputNbChannels() * nbSamplesPerChannel * bytesPerSample ) + + # check if the next frame is empty + frame = av.AudioFrame(reader.readNextFrame()) + assert_equals( frame.getSize(), 0 ) + + # generate 10 frames of silence + reader.continueWithGenerator() + for i in xrange(0, 9): + frame = av.AudioFrame(reader.readNextFrame()) + nbSamplesPerChannel = frame.getNbSamplesPerChannel() + bytesPerSample = 2 + assert_equals( frame.getSize(), reader.getOutputNbChannels() * nbSamplesPerChannel * bytesPerSample )