diff --git a/src/AvTranscoder/data/decoded/Frame.hpp b/src/AvTranscoder/data/decoded/Frame.hpp index 8d014891..58a72a3f 100644 --- a/src/AvTranscoder/data/decoded/Frame.hpp +++ b/src/AvTranscoder/data/decoded/Frame.hpp @@ -43,8 +43,10 @@ class AvExport Frame int* getLineSize() const { return _frame->linesize; } /** - * @brief Copy the data of the given Frame. - */ + * @brief Copy the 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 the given frame, to be ready for memcpy instructions. + */ void copyData(const Frame& frameToRef); /** diff --git a/src/AvTranscoder/decoder/AudioGenerator.cpp b/src/AvTranscoder/decoder/AudioGenerator.cpp index 33309f10..c67831e9 100644 --- a/src/AvTranscoder/decoder/AudioGenerator.cpp +++ b/src/AvTranscoder/decoder/AudioGenerator.cpp @@ -1,5 +1,9 @@ #include "AudioGenerator.hpp" +#include + +#include + namespace avtranscoder { @@ -42,13 +46,26 @@ bool AudioGenerator::decodeNextFrame(Frame& frameBuffer) { AudioFrame& audioBuffer = static_cast(frameBuffer); _silent = new AudioFrame(audioBuffer.desc()); + + std::stringstream msg; + msg << "Generate a silence with the following features:" << std::endl; + msg << "sample rate = " << _silent->getSampleRate() << std::endl; + msg << "number of channels = " << _silent->getNbChannels() << std::endl; + msg << "sample format = " << getSampleFormatName(_silent->getSampleFormat()) << std::endl; + msg << "number of samples per channel = " << _silent->getNbSamplesPerChannel() << std::endl; + LOG_INFO(msg.str()) + + // Set the number of samples of silence to the number of samples of the given frame + // (which was allocated to expect this number of samples). + _silent->setNbSamplesPerChannel(frameBuffer.getAVFrame().nb_samples); } - frameBuffer.getAVFrame().nb_samples = _silent->getAVFrame().nb_samples; + LOG_DEBUG("Copy data of the silence when decode next frame") frameBuffer.copyData(*_silent); } // Take audio frame from _inputFrame else { + LOG_DEBUG("Copy data of the audio specified when decode next frame") frameBuffer.copyData(*_inputFrame); } return true; diff --git a/src/AvTranscoder/decoder/VideoGenerator.cpp b/src/AvTranscoder/decoder/VideoGenerator.cpp index 74f16a6f..32092077 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,11 +77,13 @@ bool VideoGenerator::decodeNextFrame(Frame& frameBuffer) VideoTransform videoTransform; videoTransform.convert(intermediateBuffer, *_blackImage); } + LOG_DEBUG("Copy data of the black image when decode next frame") frameBuffer.copyData(*_blackImage); } // Take image from _inputFrame else { + LOG_DEBUG("Copy data of the image specified when decode next frame") frameBuffer.copyData(*_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..76bed7ff 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 NULL + else + { + return NULL; + } } // transform _transform->convert(*_srcFrame, *_dstFrame); diff --git a/src/AvTranscoder/reader/IReader.hpp b/src/AvTranscoder/reader/IReader.hpp index 51e41083..8d53b647 100644 --- a/src/AvTranscoder/reader/IReader.hpp +++ b/src/AvTranscoder/reader/IReader.hpp @@ -35,16 +35,20 @@ class AvExport IReader /** * @return Get next frame after decoding + * @see readFrameAt */ Frame* readNextFrame(); /** * @return Get previous frame after decoding + * @see readFrameAt */ Frame* readPrevFrame(); /** * @return Get indicated frame after decoding + * @warn Returns NULL if there is no more frame to read. + * @see continueWithGenerator */ Frame* readFrameAt(const size_t frame); @@ -53,10 +57,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 +80,7 @@ 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 }; } 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..7c347c46 100644 --- a/test/pyTest/testReader.py +++ b/test/pyTest/testReader.py @@ -24,11 +24,12 @@ 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()) - assert_equals( frame.getSize(), 0 ) + # check if there is no next frame + frame = reader.readNextFrame() + assert_equals( reader.readNextFrame(), None ) def testVideoReaderReferenceInputFile(): @@ -43,11 +44,11 @@ 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()) - assert_equals( frame.getSize(), 0 ) + # check if there is no next frame + assert_equals( reader.readNextFrame(), None ) def testAudioReaderChannelsExtraction(): @@ -74,3 +75,59 @@ 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 there is no next frame + assert_equals( reader.readNextFrame(), None ) + + # 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 = reader.readNextFrame() + if not frame: + break + frame = av.AudioFrame(frame) + nbSamplesPerChannel = frame.getNbSamplesPerChannel() + bytesPerSample = 2 + assert_equals( frame.getSize(), reader.getOutputNbChannels() * nbSamplesPerChannel * bytesPerSample ) + + # check if there is no next frame + assert_equals( reader.readNextFrame(), None ) + + # 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 ) diff --git a/test/pyTest/testTranscoderTranscodeAudioWave.py b/test/pyTest/testTranscoderTranscodeAudioWave.py index 9aceeb30..36a44b99 100644 --- a/test/pyTest/testTranscoderTranscodeAudioWave.py +++ b/test/pyTest/testTranscoderTranscodeAudioWave.py @@ -43,6 +43,7 @@ def testTranscodeWave24b48k5_1(): assert_equals( 48000, dst_audioStream.getSampleRate() ) assert_equals( 6, dst_audioStream.getNbChannels() ) + def testTranscodeWave24b48kstereo(): """ Transcode one audio stream (profile wave24b48kstereo). @@ -76,6 +77,7 @@ def testTranscodeWave24b48kstereo(): assert_equals( 48000, dst_audioStream.getSampleRate() ) assert_equals( 2, dst_audioStream.getNbChannels() ) + def testTranscodeWave24b48kmono(): """ Transcode one audio stream (profile wave24b48kmono). @@ -109,6 +111,7 @@ def testTranscodeWave24b48kmono(): assert_equals( 48000, dst_audioStream.getSampleRate() ) assert_equals( 1, dst_audioStream.getNbChannels() ) + def testTranscodeWave16b48kmono(): """ Transcode one audio stream (profile wave16b48kmono). @@ -141,3 +144,40 @@ def testTranscodeWave16b48kmono(): assert_equals( "signed 16 bits", dst_audioStream.getSampleFormatLongName() ) assert_equals( 48000, dst_audioStream.getSampleRate() ) assert_equals( 1, dst_audioStream.getNbChannels() ) + + +def testTranscodeWave16b48kmonoWithSilence(): + """ + Transcode one audio stream (profile wave16b48kmono). + Complete with silence. + """ + inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] + outputFileName = "testTranscodeWave16b48kmonoWithSilence.wav" + outputDuration = 50 + + ouputFile = av.OutputFile( outputFileName ) + transcoder = av.Transcoder( ouputFile ) + transcoder.setProcessMethod( av.eProcessMethodBasedOnDuration, 0, outputDuration ) + + inputFile = av.InputFile( inputFileName ) + src_audioStream = inputFile.getProperties().getAudioProperties()[0] + audioStreamIndex = src_audioStream.getStreamIndex() + transcoder.add( inputFileName, audioStreamIndex, "wave16b48kmono" ) + + progress = av.ConsoleProgress() + processStat = transcoder.process( progress ) + + # check process stat returned + audioStat = processStat.getAudioStat(0) + assert_almost_equals( audioStat.getDuration(), outputDuration, delta=0.01 ) + + # get dst file of transcode + dst_inputFile = av.InputFile( outputFileName ) + dst_properties = dst_inputFile.getProperties() + dst_audioStream = dst_properties.getAudioProperties()[0] + + assert_equals( "pcm_s16le", dst_audioStream.getCodecName() ) + assert_equals( "s16", dst_audioStream.getSampleFormatName() ) + assert_equals( "signed 16 bits", dst_audioStream.getSampleFormatLongName() ) + assert_equals( 48000, dst_audioStream.getSampleRate() ) + assert_equals( 1, dst_audioStream.getNbChannels() ) diff --git a/tools/travis/python.nosetests.sh b/tools/travis/python.nosetests.sh index d145612e..234f1c23 100755 --- a/tools/travis/python.nosetests.sh +++ b/tools/travis/python.nosetests.sh @@ -18,4 +18,5 @@ export AVTRANSCODER_TEST_IMAGE_PNG_FILE=`pwd`/avTranscoder-data/image/BigBuckBun export AVTRANSCODER_TEST_IMAGE_JPG_FILE=`pwd`/avTranscoder-data/image/BigBuckBunny/title_anouncement.thumbnail.jpg # Launch tests -nosetests ${TRAVIS_BUILD_DIR}/test/pyTest --with-coverage +nosetests ${TRAVIS_BUILD_DIR}/test/pyTest --with-coverage > progress.txt +