diff --git a/app/avProcessor/avProcessor.cpp b/app/avProcessor/avProcessor.cpp index 2c367dcb..cf046635 100644 --- a/app/avProcessor/avProcessor.cpp +++ b/app/avProcessor/avProcessor.cpp @@ -28,7 +28,7 @@ void parseConfigFile(const std::string& configFilename, avtranscoder::Transcoder std::stringstream ss(streamId); size_t streamIndex = 0; - char separator; + char separator = 'x'; std::vector channelIndexArray; ss >> streamIndex; ss >> separator; diff --git a/src/AvTranscoder/codec/AudioCodec.cpp b/src/AvTranscoder/codec/AudioCodec.cpp index f5460f5e..ba4a349f 100644 --- a/src/AvTranscoder/codec/AudioCodec.cpp +++ b/src/AvTranscoder/codec/AudioCodec.cpp @@ -1,5 +1,7 @@ #include "AudioCodec.hpp" +#include + #include #include @@ -24,7 +26,7 @@ AudioCodec::AudioCodec(const ECodecType type, AVCodecContext& avCodecContext) AudioFrameDesc AudioCodec::getAudioFrameDesc() const { assert(_avCodecContext != NULL); - return AudioFrameDesc(_avCodecContext->sample_rate, _avCodecContext->channels, _avCodecContext->sample_fmt); + return AudioFrameDesc(_avCodecContext->sample_rate, _avCodecContext->channels, getSampleFormatName(_avCodecContext->sample_fmt)); } void AudioCodec::setAudioParameters(const AudioFrameDesc& audioFrameDesc) diff --git a/src/AvTranscoder/codec/ICodec.cpp b/src/AvTranscoder/codec/ICodec.cpp index a7aaae1a..3e64cd76 100644 --- a/src/AvTranscoder/codec/ICodec.cpp +++ b/src/AvTranscoder/codec/ICodec.cpp @@ -49,7 +49,7 @@ ICodec::~ICodec() if(!_isCodecContextAllocated) return; - av_free(_avCodecContext); + avcodec_free_context(&_avCodecContext); _avCodecContext = NULL; } @@ -148,7 +148,8 @@ void ICodec::allocateContext() _avCodecContext = avcodec_alloc_context3(_avCodec); if(!_avCodecContext) { - throw std::runtime_error("Unable to allocate the codecContext and set its fields to default values"); + LOG_ERROR("Unable to allocate the codecContext and set its fields to default values.") + throw std::bad_alloc(); } _avCodecContext->codec = _avCodec; } diff --git a/src/AvTranscoder/codec/VideoCodec.cpp b/src/AvTranscoder/codec/VideoCodec.cpp index 081aba82..f12e7fb9 100644 --- a/src/AvTranscoder/codec/VideoCodec.cpp +++ b/src/AvTranscoder/codec/VideoCodec.cpp @@ -1,5 +1,7 @@ #include "VideoCodec.hpp" +#include + #include #include @@ -24,7 +26,7 @@ VideoCodec::VideoCodec(const ECodecType type, AVCodecContext& avCodecContext) VideoFrameDesc VideoCodec::getVideoFrameDesc() const { assert(_avCodecContext != NULL); - VideoFrameDesc videoFrameDesc(_avCodecContext->width, _avCodecContext->height, _avCodecContext->pix_fmt); + VideoFrameDesc videoFrameDesc(_avCodecContext->width, _avCodecContext->height, getPixelFormatName(_avCodecContext->pix_fmt)); double fps = 1.0 * _avCodecContext->time_base.den / (_avCodecContext->time_base.num * _avCodecContext->ticks_per_frame); if(!std::isinf(fps)) videoFrameDesc._fps = fps; diff --git a/src/AvTranscoder/data/coded/CodedData.cpp b/src/AvTranscoder/data/coded/CodedData.cpp index 44eeaa6a..8c556b69 100644 --- a/src/AvTranscoder/data/coded/CodedData.cpp +++ b/src/AvTranscoder/data/coded/CodedData.cpp @@ -14,7 +14,12 @@ CodedData::CodedData() CodedData::CodedData(const size_t dataSize) : _avStream(NULL) { - av_new_packet(&_packet, dataSize); + const int err = av_new_packet(&_packet, dataSize); + if(err != 0) + { + LOG_ERROR("Unable to allocate the payload of a packet and initialize its fields with default values: " << getDescriptionFromErrorCode(err)) + throw std::bad_alloc(); + } } CodedData::CodedData(const AVPacket& avPacket) diff --git a/src/AvTranscoder/data/data.i b/src/AvTranscoder/data/data.i index 117c1f74..ceaf92cb 100644 --- a/src/AvTranscoder/data/data.i +++ b/src/AvTranscoder/data/data.i @@ -2,12 +2,12 @@ %{ #include -#include +#include #include #include %} %include -%include +%include %include %include diff --git a/src/AvTranscoder/data/decoded/AudioFrame.cpp b/src/AvTranscoder/data/decoded/AudioFrame.cpp index baffbbee..8dfd3bd8 100644 --- a/src/AvTranscoder/data/decoded/AudioFrame.cpp +++ b/src/AvTranscoder/data/decoded/AudioFrame.cpp @@ -12,18 +12,19 @@ extern "C" { namespace avtranscoder { -AudioFrameDesc::AudioFrameDesc(const size_t sampleRate, const size_t nbChannels, const AVSampleFormat sampleFormat) +AudioFrameDesc::AudioFrameDesc(const size_t sampleRate, const size_t nbChannels, const std::string& sampleFormatName) : _sampleRate(sampleRate) , _nbChannels(nbChannels) - , _sampleFormat(sampleFormat) + , _sampleFormat(getAVSampleFormat(sampleFormatName)) { } -AudioFrameDesc::AudioFrameDesc(const size_t sampleRate, const size_t nbChannels, const std::string& sampleFormatName) - : _sampleRate(sampleRate) - , _nbChannels(nbChannels) - , _sampleFormat(getAVSampleFormat(sampleFormatName)) +AudioFrameDesc::AudioFrameDesc(const ProfileLoader::Profile& profile) + : _sampleRate(0) + , _nbChannels(0) + , _sampleFormat(AV_SAMPLE_FMT_NONE) { + setParameters(profile); } void AudioFrameDesc::setParameters(const ProfileLoader::Profile& profile) @@ -39,15 +40,19 @@ void AudioFrameDesc::setParameters(const ProfileLoader::Profile& profile) _sampleFormat = getAVSampleFormat(profile.find(constants::avProfileSampleFormat)->second.c_str()); } -AudioFrame::AudioFrame(const AudioFrameDesc& ref) - : Frame() +AudioFrame::AudioFrame(const AudioFrameDesc& desc, const bool forceDataAllocation) + : IFrame() + , _desc(desc) { - allocateAVSample(ref); -} + // Set Frame properties + av_frame_set_sample_rate(_frame, desc._sampleRate); + av_frame_set_channels(_frame, desc._nbChannels); + av_frame_set_channel_layout(_frame, av_get_default_channel_layout(desc._nbChannels)); + _frame->format = desc._sampleFormat; + _frame->nb_samples = getDefaultNbSamples(); -AudioFrame::AudioFrame(const Frame& otherFrame) - : Frame(otherFrame) -{ + if(forceDataAllocation) + allocateData(); } std::string AudioFrame::getChannelLayoutDesc() const @@ -57,7 +62,20 @@ std::string AudioFrame::getChannelLayoutDesc() const return std::string(buf); } -size_t AudioFrame::getSize() const +AudioFrame::~AudioFrame() +{ + if(_frame->buf[0]) + av_frame_unref(_frame); + if(_dataAllocated) + freeData(); +} + +size_t AudioFrame::getBytesPerSample() const +{ + return av_get_bytes_per_sample(getSampleFormat()); +} + +size_t AudioFrame::getDataSize() const { if(getSampleFormat() == AV_SAMPLE_FMT_NONE) { @@ -65,67 +83,74 @@ size_t AudioFrame::getSize() const return 0; } - const size_t size = getNbSamplesPerChannel() * getNbChannels() * av_get_bytes_per_sample(getSampleFormat()); + const size_t size = getNbSamplesPerChannel() * getNbChannels() * getBytesPerSample(); if(size == 0) { std::stringstream msg; msg << "Unable to determine audio buffer size:" << std::endl; msg << "nb sample per channel = " << getNbSamplesPerChannel() << std::endl; msg << "channels = " << getNbChannels() << std::endl; - msg << "bytes per sample = " << av_get_bytes_per_sample(getSampleFormat()) << std::endl; + msg << "bytes per sample = " << getBytesPerSample() << std::endl; throw std::runtime_error(msg.str()); } return size; } -void AudioFrame::allocateAVSample(const AudioFrameDesc& desc) +void AudioFrame::allocateData() { + if(_dataAllocated) + LOG_WARN("The AudioFrame seems to already have allocated data. This could lead to memory leaks.") + // Set Frame properties - av_frame_set_sample_rate(_frame, desc._sampleRate); - av_frame_set_channels(_frame, desc._nbChannels); - av_frame_set_channel_layout(_frame, av_get_default_channel_layout(desc._nbChannels)); - _frame->format = desc._sampleFormat; - _frame->nb_samples = desc._sampleRate / 25.; // cannot be known before calling avcodec_decode_audio4 + av_frame_set_sample_rate(_frame, _desc._sampleRate); + av_frame_set_channels(_frame, _desc._nbChannels); + av_frame_set_channel_layout(_frame, av_get_default_channel_layout(_desc._nbChannels)); + _frame->format = _desc._sampleFormat; + if(_frame->nb_samples == 0) + _frame->nb_samples = getDefaultNbSamples(); // Allocate data const int align = 0; const int ret = - av_samples_alloc(_frame->data, _frame->linesize, _frame->channels, _frame->nb_samples, desc._sampleFormat, align); + av_samples_alloc(_frame->data, _frame->linesize, _frame->channels, _frame->nb_samples, _desc._sampleFormat, align); if(ret < 0) { - std::stringstream os; - os << "Unable to allocate an audio frame of "; - os << "sample rate = " << _frame->sample_rate << ", "; - os << "nb channels = " << _frame->channels << ", "; - os << "channel layout = " << av_get_channel_name(_frame->channels) << ", "; - os << "nb samples = " << _frame->nb_samples << ", "; - os << "sample format = " << getSampleFormatName(desc._sampleFormat); - throw std::runtime_error(os.str()); + const std::string formatName = getSampleFormatName(_desc._sampleFormat); + std::stringstream msg; + msg << "Unable to allocate an audio frame of "; + msg << "sample rate = " << _frame->sample_rate << ", "; + msg << "nb channels = " << _frame->channels << ", "; + msg << "channel layout = " << av_get_channel_name(_frame->channels) << ", "; + msg << "nb samples = " << _frame->nb_samples << ", "; + msg << "sample format = " << (formatName.empty() ? "none" : formatName); + LOG_ERROR(msg.str()) + throw std::bad_alloc(); } + _dataAllocated = true; } -void AudioFrame::assign(const unsigned char value) +void AudioFrame::freeData() { - // Create the audio buffer - // The buffer will be freed in destructor of based class - const int audioSize = getSize(); - unsigned char* audioBuffer = new unsigned char[audioSize]; - memset(audioBuffer, value, audioSize); - - // Fill the picture - assign(audioBuffer); + av_freep(&_frame->data[0]); + _dataAllocated = false; } -void AudioFrame::assign(const unsigned char* ptrValue) +void AudioFrame::assignBuffer(const unsigned char* ptrValue) { const int align = 0; const int ret = av_samples_fill_arrays(_frame->data, _frame->linesize, ptrValue, getNbChannels(), getNbSamplesPerChannel(), getSampleFormat(), align); if(ret < 0) { - std::stringstream os; - os << "Unable to assign an audio buffer: " << getDescriptionFromErrorCode(ret); - throw std::runtime_error(os.str()); + std::stringstream msg; + msg << "Unable to assign an audio buffer: " << getDescriptionFromErrorCode(ret); + throw std::runtime_error(msg.str()); } } + +size_t AudioFrame::getDefaultNbSamples() const +{ + return _desc._sampleRate / 25.; +} + } diff --git a/src/AvTranscoder/data/decoded/AudioFrame.hpp b/src/AvTranscoder/data/decoded/AudioFrame.hpp index ea644498..6bf00175 100644 --- a/src/AvTranscoder/data/decoded/AudioFrame.hpp +++ b/src/AvTranscoder/data/decoded/AudioFrame.hpp @@ -1,7 +1,7 @@ #ifndef _AV_TRANSCODER_FRAME_AUDIO_FRAME_HPP_ #define _AV_TRANSCODER_FRAME_AUDIO_FRAME_HPP_ -#include "Frame.hpp" +#include "IFrame.hpp" #include namespace avtranscoder @@ -14,9 +14,8 @@ namespace avtranscoder struct AvExport AudioFrameDesc { public: - AudioFrameDesc(const size_t sampleRate = 0, const size_t channels = 0, - const AVSampleFormat sampleFormat = AV_SAMPLE_FMT_NONE); AudioFrameDesc(const size_t sampleRate, const size_t channels, const std::string& sampleFormatName); + AudioFrameDesc(const ProfileLoader::Profile& profile); /** * @brief Set the attributes from the given profile. @@ -33,50 +32,59 @@ struct AvExport AudioFrameDesc /** * @brief This class describes decoded audio data. */ -class AvExport AudioFrame : public Frame +class AvExport AudioFrame : public IFrame { +private: + AudioFrame(const AudioFrame& otherFrame); + AudioFrame& operator=(const AudioFrame& otherFrame); + public: + AudioFrame(const AudioFrameDesc& desc, const bool forceDataAllocation = true); + ~AudioFrame(); + /** - * @note Allocated data will be initialized to silence. + * @brief Allocated data will be initialized to silence. + * @warning The allocated data should be freed by the caller. + * @see freeData */ - AudioFrame(const AudioFrameDesc& ref); - AudioFrame(const Frame& otherFrame); + void allocateData(); + void freeData(); + size_t getDataSize() const; size_t getSampleRate() const { return av_frame_get_sample_rate(_frame); } size_t getNbChannels() const { return av_frame_get_channels(_frame); } size_t getChannelLayout() const { return av_frame_get_channel_layout(_frame); } std::string getChannelLayoutDesc() const; ///< Get a description of a channel layout (example: '5.1'). AVSampleFormat getSampleFormat() const { return static_cast(_frame->format); } + size_t getBytesPerSample() const; ///< 0 if unknown sample format size_t getNbSamplesPerChannel() const { return _frame->nb_samples; } - AudioFrameDesc desc() const { return AudioFrameDesc(getSampleRate(), getNbChannels(), getSampleFormat()); } - - size_t getSize() const; ///< in bytes - - void setNbSamplesPerChannel(const size_t nbSamples) { _frame->nb_samples = nbSamples; } /** - * @brief Assign the given value to all the data of the audio frame. + * @brief This methods dynamically updates the size that the data buffer would occupy if allocated. + * @warning If the data buffer is already allocated, this could lead to memory leaks or seg fault. */ - void assign(const unsigned char value); + void setNbSamplesPerChannel(const size_t nbSamples) { _frame->nb_samples = nbSamples; } - /** - * @brief Assign the given ptr of data to the data of the audio frame. - * @warning the given ptr should have the size of the audio frame.. - * @see getSize - */ - void assign(const unsigned char* ptrValue); + void assignBuffer(const unsigned char* ptrValue); private: /** - * @brief Allocate the audio buffer of the frame. + * @brief The number of samples of a frame cannot be known before calling avcodec_decode_audio4, + * and can be variable in a same stream. Because we need to allocate some frames without knowing this parameter, + * we access here a default number of samples. + * @note This value depends on the sample rate (example: 1920 samples at 48kHz). + * @return the number of samples of our default AudioFrame. + * @see setNbSamplesPerChannel */ - void allocateAVSample(const AudioFrameDesc& ref); + size_t getDefaultNbSamples() const; +private: /** - * @note To allocate new audio buffer if needed. - * @see allocateAVSample + * @brief Description of the frame to allocate. + * @warning This description could be different from the current frame (a decoder could have reseted it). + * We need to keep this description to allocate again the frame even if it was reseted. */ - friend class AudioGenerator; + const AudioFrameDesc _desc; }; } diff --git a/src/AvTranscoder/data/decoded/Frame.cpp b/src/AvTranscoder/data/decoded/Frame.cpp deleted file mode 100644 index 69b15ff4..00000000 --- a/src/AvTranscoder/data/decoded/Frame.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "Frame.hpp" - -#include - -namespace avtranscoder -{ - -Frame::Frame() - : _frame(NULL) -{ - allocateAVFrame(); -} - -Frame::Frame(const Frame& otherFrame) - : _frame(NULL) -{ - // allocate frame - allocateAVFrame(); - // check if the frame could be a valid video/audio frame - if(otherFrame.getAVFrame().format == -1) - return; - // reference the other frame - refFrame(otherFrame); -} - -void Frame::operator=(const Frame& otherFrame) -{ - // check if the frame could be a valid video/audio frame - if(otherFrame.getAVFrame().format == -1) - return; - // reference the other frame - refFrame(otherFrame); -} - -Frame::~Frame() -{ - if(_frame != NULL) - { -#if LIBAVCODEC_VERSION_MAJOR > 54 - av_frame_free(&_frame); -#else -#if LIBAVCODEC_VERSION_MAJOR > 53 - avcodec_free_frame(&_frame); -#else - av_free(_frame); -#endif -#endif - _frame = NULL; - } -} - -int Frame::getEncodedSize() const -{ - return av_frame_get_pkt_size(_frame); -} - -void Frame::copyData(const Frame& frameToRef) -{ - const int ret = av_frame_copy(_frame, &frameToRef.getAVFrame()); - if(ret < 0) - { - throw std::ios_base::failure("Unable to copy data of other frame: " + getDescriptionFromErrorCode(ret)); - } -} - -void Frame::copyProperties(const Frame& otherFrame) -{ - av_frame_copy_props(_frame, &otherFrame.getAVFrame()); -} - -void Frame::refFrame(const Frame& otherFrame) -{ - const int ret = av_frame_ref(_frame, &otherFrame.getAVFrame()); - if(ret < 0) - { - throw std::ios_base::failure("Unable to reference other frame: " + getDescriptionFromErrorCode(ret)); - } -} - -void Frame::clear() -{ - av_frame_unref(_frame); -} - -void Frame::allocateAVFrame() -{ -#if LIBAVCODEC_VERSION_MAJOR > 54 - _frame = av_frame_alloc(); -#else - _frame = avcodec_alloc_frame(); -#endif - if(_frame == NULL) - { - throw std::runtime_error("Unable to allocate an empty Frame."); - } -} - -bool Frame::isAudioFrame() const -{ - if(_frame->sample_rate && _frame->channels && _frame->channel_layout && _frame->format != -1) - return true; - return false; -} - -bool Frame::isVideoFrame() const -{ - if(_frame->width && _frame->height && _frame->format != -1) - return true; - return false; -} -} diff --git a/src/AvTranscoder/data/decoded/Frame.hpp b/src/AvTranscoder/data/decoded/Frame.hpp deleted file mode 100644 index 53d5ce00..00000000 --- a/src/AvTranscoder/data/decoded/Frame.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef _AV_TRANSCODER_FRAME_FRAME_HPP_ -#define _AV_TRANSCODER_FRAME_FRAME_HPP_ - -#include - -extern "C" { -#include -} - -namespace avtranscoder -{ - -/** - * @brief This class describes decoded (raw) audio or video data. - */ -class AvExport Frame -{ -public: - /** - * @brief Allocate an empty frame. - * @warn This only allocates the AVFrame itself, not the data buffers. - */ - Frame(); - - //@{ - // @brief Copy properties and reference data of the other frame. - Frame(const Frame& otherFrame); - void operator=(const Frame& otherFrame); - //@} - - virtual ~Frame(); - - /** - * @brief Get all the data of the frame. - */ - unsigned char** getData() { return _frame->data; } - - /** - * @brief Returns the size in byte. - * For video, size in bytes of each picture line. - * For audio, size in bytes of each plane. - * @note For audio, only linesize[0] may be set. For planar audio, each channel - * plane must be the same size. - */ - int* getLineSize() const { return _frame->linesize; } - - /** - * @return Size of the corresponding packet containing the compressed frame (in bytes) - * @warning returns a negative value if the size is unknown - */ - int getEncodedSize() const; - - /** - * @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); - - /** - * @brief Copy all the fields that do not affect the data layout in the buffers. - */ - void copyProperties(const Frame& otherFrame); - - /** - * @brief Copy frame properties and create a new reference to data of the given frame. - * @warning This method allocates new data that will be freed by calling clear method or the destructor of the referenced frame. - * @see clear - */ - 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 - */ - bool isAudioFrame() const; - - /** - * @return If it corresponds to a valid video frame. - * @see VideoFrame - */ - bool isVideoFrame() const; - -#ifndef SWIG - AVFrame& getAVFrame() { return *_frame; } - const AVFrame& getAVFrame() const { return *_frame; } - const unsigned char** getData() const { return const_cast(_frame->data); } -#endif - -private: - void allocateAVFrame(); - -protected: - AVFrame* _frame; -}; -} - -#endif diff --git a/src/AvTranscoder/data/decoded/IFrame.cpp b/src/AvTranscoder/data/decoded/IFrame.cpp new file mode 100644 index 00000000..608f90e4 --- /dev/null +++ b/src/AvTranscoder/data/decoded/IFrame.cpp @@ -0,0 +1,95 @@ +#include "IFrame.hpp" + +#include + +namespace avtranscoder +{ + +IFrame::IFrame() + : _frame(NULL) + , _dataAllocated(false) +{ + allocateAVFrame(); +} + +IFrame::~IFrame() +{ + freeAVFrame(); +} + +void IFrame::copyData(const IFrame& frameToRef) +{ + const int ret = av_frame_copy(_frame, &frameToRef.getAVFrame()); + if(ret < 0) + { + throw std::ios_base::failure("Unable to copy data of other frame: " + getDescriptionFromErrorCode(ret)); + } +} + +void IFrame::copyProperties(const IFrame& otherFrame) +{ + av_frame_copy_props(_frame, &otherFrame.getAVFrame()); +} + +void IFrame::allocateAVFrame() +{ +#if LIBAVCODEC_VERSION_MAJOR > 54 + _frame = av_frame_alloc(); +#else + _frame = avcodec_alloc_frame(); +#endif + if(_frame == NULL) + { + LOG_ERROR("Unable to allocate an empty Frame.") + throw std::bad_alloc(); + } +} + +void IFrame::freeAVFrame() +{ + if(_frame != NULL) + { +#if LIBAVCODEC_VERSION_MAJOR > 54 + av_frame_free(&_frame); +#else +#if LIBAVCODEC_VERSION_MAJOR > 53 + avcodec_free_frame(&_frame); +#else + av_free(_frame); +#endif +#endif + _frame = NULL; + } +} + +void IFrame::assignValue(const unsigned char value) +{ + // Free the existing data + freeData(); + + // Create the buffer + const int bufferSize = getDataSize(); + unsigned char* dataBuffer = static_cast(malloc(bufferSize * sizeof(unsigned char))); + memset(dataBuffer, value, bufferSize); + + // Fill the frame + assignBuffer(dataBuffer); + + // Prepare to free the data + _dataAllocated = true; +} + +bool IFrame::isAudioFrame() const +{ + if(_frame->sample_rate && _frame->channels && _frame->channel_layout && _frame->format != -1) + return true; + return false; +} + +bool IFrame::isVideoFrame() const +{ + if(_frame->width && _frame->height && _frame->format != -1) + return true; + return false; +} +} diff --git a/src/AvTranscoder/data/decoded/IFrame.hpp b/src/AvTranscoder/data/decoded/IFrame.hpp new file mode 100644 index 00000000..95394974 --- /dev/null +++ b/src/AvTranscoder/data/decoded/IFrame.hpp @@ -0,0 +1,126 @@ +#ifndef _AV_TRANSCODER_FRAME_FRAME_HPP_ +#define _AV_TRANSCODER_FRAME_FRAME_HPP_ + +#include + +extern "C" { +#include +} + +namespace avtranscoder +{ + +/** + * @brief This class describes decoded (raw) audio or video data. + * This class is abstract. + * @see VideoFrame + * @see AudioFrame + */ +class AvExport IFrame +{ +private: + IFrame(const IFrame& otherFrame); + IFrame& operator=(const IFrame& otherFrame); + +public: + /** + * @brief Allocate an empty frame. + * @warning This only allocates the AVFrame itself, not the data buffers. + * Depending on the case, we could manipulate frame with data allocated elsewhere. + * For example, an empty frame is given to a decoder, which is responsible to allocate and free the data buffers. + */ + IFrame(); + + virtual ~IFrame(); + + /** + * @brief Allocate the buffer of the frame. + */ + virtual void allocateData() = 0; + + /** + * @brief Free the buffer of the frame. + */ + virtual void freeData() = 0; + + /** + * @brief Get the size in bytes that a video/audio buffer of the given frame properties would occupy if allocated. + * @warning This methods does not guarantee that the buffer is actually allocated. + */ + virtual size_t getDataSize() const = 0; + + /** + * @brief Assign the given ptr of data to the data of the frame. + * @warning the given ptr should have the size of the frame.. + * @see getSize + */ + virtual void assignBuffer(const unsigned char* ptrValue) = 0; + + /** + * @brief Assign the given value to all the data of the frame. + */ + void assignValue(const unsigned char value); + + /** + * @brief Get all the data of the frame. + */ + unsigned char** getData() { return _frame->data; } + + /** + * @brief Returns the size in byte. + * For video, size in bytes of each picture line. + * For audio, size in bytes of each plane. + * @note For audio, only linesize[0] may be set. For planar audio, each channel + * plane must be the same size. + */ + int* getLineSize() const { return _frame->linesize; } + + /** + * @brief Copy the data of the given Frame. + * 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. + * @note It copies the frame data (i.e. the contents of the data / extended data arrays), not any other properties. + * @see copyProperties + */ + void copyData(const IFrame& frameToRef); + + /** + * @brief Copy all the fields that do not affect the data layout in the buffers. + * @warning The info checked when copying data of an other frame (width/height, channels...) are not copied. + */ + void copyProperties(const IFrame& otherFrame); + + /** + * @return If it corresponds to a valid audio frame. + * @see AudioFrame + */ + bool isAudioFrame() const; + + /** + * @return If it corresponds to a valid video frame. + * @see VideoFrame + */ + bool isVideoFrame() const; + + /** + * @return If the data buffer is allocated and hold by the frame. + */ + bool isDataAllocated() const { return _dataAllocated; } + +#ifndef SWIG + AVFrame& getAVFrame() { return *_frame; } + const AVFrame& getAVFrame() const { return *_frame; } + const unsigned char** getData() const { return const_cast(_frame->data); } +#endif + +private: + void allocateAVFrame(); + void freeAVFrame(); + +protected: + AVFrame* _frame; + bool _dataAllocated; +}; +} + +#endif diff --git a/src/AvTranscoder/data/decoded/VideoFrame.cpp b/src/AvTranscoder/data/decoded/VideoFrame.cpp index c7f8dcf8..4da08b00 100644 --- a/src/AvTranscoder/data/decoded/VideoFrame.cpp +++ b/src/AvTranscoder/data/decoded/VideoFrame.cpp @@ -13,22 +13,24 @@ extern "C" { namespace avtranscoder { -VideoFrameDesc::VideoFrameDesc(const size_t width, const size_t height, const AVPixelFormat pixelFormat) +VideoFrameDesc::VideoFrameDesc(const size_t width, const size_t height, const std::string& pixelFormatName) : _width(width) , _height(height) - , _pixelFormat(pixelFormat) + , _pixelFormat(getAVPixelFormat(pixelFormatName)) , _fps(1.0) { } -VideoFrameDesc::VideoFrameDesc(const size_t width, const size_t height, const std::string& pixelFormatName) - : _width(width) - , _height(height) - , _pixelFormat(getAVPixelFormat(pixelFormatName)) +VideoFrameDesc::VideoFrameDesc(const ProfileLoader::Profile& profile) + : _width(0) + , _height(0) + , _pixelFormat(AV_PIX_FMT_NONE) , _fps(1.0) { + setParameters(profile); } + void VideoFrameDesc::setParameters(const ProfileLoader::Profile& profile) { // width @@ -45,18 +47,27 @@ void VideoFrameDesc::setParameters(const ProfileLoader::Profile& profile) _fps = atof(profile.find(constants::avProfileFrameRate)->second.c_str()); } -VideoFrame::VideoFrame(const VideoFrameDesc& ref) - : Frame() +VideoFrame::VideoFrame(const VideoFrameDesc& desc, const bool forceDataAllocation) + : IFrame() + , _desc(desc) { - allocateAVPicture(ref); + _frame->width = desc._width; + _frame->height = desc._height; + _frame->format = desc._pixelFormat; + + if(forceDataAllocation) + allocateData(); } -VideoFrame::VideoFrame(const Frame& otherFrame) - : Frame(otherFrame) +VideoFrame::~VideoFrame() { + if(_frame->buf[0]) + av_frame_unref(_frame); + if(_dataAllocated) + freeData(); } -size_t VideoFrame::getSize() const +size_t VideoFrame::getDataSize() const { if(getPixelFormat() == AV_PIX_FMT_NONE) { @@ -66,48 +77,51 @@ size_t VideoFrame::getSize() const const size_t size = avpicture_get_size(getPixelFormat(), getWidth(), getHeight()); if(size == 0) - throw std::runtime_error("unable to determine image buffer size"); + throw std::runtime_error("Unable to determine image buffer size: " + getDescriptionFromErrorCode(size)); return size; } -void VideoFrame::allocateAVPicture(const VideoFrameDesc& desc) +void VideoFrame::allocateData() { - const int ret = avpicture_alloc(reinterpret_cast(_frame), desc._pixelFormat, desc._width, desc._height); + if(_dataAllocated) + LOG_WARN("The VideoFrame seems to already have allocated data. This could lead to memory leaks.") + + // Set Frame properties + _frame->width = _desc._width; + _frame->height = _desc._height; + _frame->format = _desc._pixelFormat; + + // Allocate data + const int ret = avpicture_alloc(reinterpret_cast(_frame), _desc._pixelFormat, _desc._width, _desc._height); if(ret < 0) { - std::stringstream os; - os << "Unable to allocate an image frame of "; - os << "width = " << desc._width << ", "; - os << "height = " << desc._height << ", "; - os << "pixel format = " << desc._pixelFormat; - throw std::runtime_error(os.str()); + const std::string formatName = getPixelFormatName(_desc._pixelFormat); + std::stringstream msg; + msg << "Unable to allocate an image frame of "; + msg << "width = " << _frame->width << ", "; + msg << "height = " << _frame->height << ", "; + msg << "pixel format = " << (formatName.empty() ? "none" : formatName); + LOG_ERROR(msg.str()) + throw std::bad_alloc(); } - _frame->width = desc._width; - _frame->height = desc._height; - _frame->format = desc._pixelFormat; + _dataAllocated = true; } -void VideoFrame::assign(const unsigned char value) +void VideoFrame::freeData() { - // Create the image buffer - // The buffer will be freed in destructor of based class - const int imageSize = getSize(); - unsigned char* imageBuffer = new unsigned char[imageSize]; - memset(imageBuffer, value, imageSize); - - // Fill the picture - assign(imageBuffer); + avpicture_free(reinterpret_cast(_frame)); + _dataAllocated = false; } -void VideoFrame::assign(const unsigned char* ptrValue) +void VideoFrame::assignBuffer(const unsigned char* ptrValue) { const int ret = avpicture_fill(reinterpret_cast(_frame), ptrValue, getPixelFormat(), getWidth(), getHeight()); if(ret < 0) { - std::stringstream os; - os << "Unable to assign an image buffer of " << getSize() << " bytes: " << getDescriptionFromErrorCode(ret); - throw std::runtime_error(os.str()); + std::stringstream msg; + msg << "Unable to assign an image buffer of " << getDataSize() << " bytes: " << getDescriptionFromErrorCode(ret); + throw std::runtime_error(msg.str()); } } } diff --git a/src/AvTranscoder/data/decoded/VideoFrame.hpp b/src/AvTranscoder/data/decoded/VideoFrame.hpp index eeea4b09..0339e03b 100644 --- a/src/AvTranscoder/data/decoded/VideoFrame.hpp +++ b/src/AvTranscoder/data/decoded/VideoFrame.hpp @@ -1,7 +1,7 @@ #ifndef _AV_TRANSCODER_FRAME_VIDEO_FRAME_HPP_ #define _AV_TRANSCODER_FRAME_VIDEO_FRAME_HPP_ -#include "Frame.hpp" +#include "IFrame.hpp" #include extern "C" { @@ -21,8 +21,8 @@ namespace avtranscoder struct AvExport VideoFrameDesc { public: - VideoFrameDesc(const size_t width = 0, const size_t height = 0, const AVPixelFormat pixelFormat = AV_PIX_FMT_NONE); VideoFrameDesc(const size_t width, const size_t height, const std::string& pixelFormatName); + VideoFrameDesc(const ProfileLoader::Profile& profile); /** * @brief Set the attributes from the given profile. @@ -40,42 +40,38 @@ struct AvExport VideoFrameDesc /** * @brief This class describes decoded video data. */ -class AvExport VideoFrame : public Frame +class AvExport VideoFrame : public IFrame { +private: + VideoFrame(const VideoFrame& otherFrame); + VideoFrame& operator=(const VideoFrame& otherFrame); + public: - VideoFrame(const VideoFrameDesc& ref); - VideoFrame(const Frame& otherFrame); + VideoFrame(const VideoFrameDesc& desc, const bool forceDataAllocation = true); + ~VideoFrame(); + + /** + * @brief Allocate the image buffer of the frame. + * @warning The allocated data should be freed by the caller. + * @see freeData + */ + void allocateData(); + void freeData(); + size_t getDataSize() const; size_t getWidth() const { return _frame->width; } size_t getHeight() const { return _frame->height; } AVPixelFormat getPixelFormat() const { return static_cast(_frame->format); } - VideoFrameDesc desc() const { return VideoFrameDesc(getWidth(), getHeight(), getPixelFormat()); } - - size_t getSize() const; ///< in bytes/** - - /** - * @brief Assign the given value to all the data of the picture. - */ - void assign(const unsigned char value); - /** - * @brief Assign the given ptr of data to the data of the picture. - * @warning the given ptr should have the size of the picture. - * @see getSize - */ - void assign(const unsigned char* ptrValue); + void assignBuffer(const unsigned char* ptrValue); private: /** - * @brief Allocate the image buffer of the frame. - */ - void allocateAVPicture(const VideoFrameDesc& desc); - - /** - * @note To allocate new image buffer if needed. - * @see allocateAVPicture + * @brief Description of the frame to allocate. + * @warning This description could be different from the current frame (a decoder could have reseted it). + * We need to keep this description to allocate again the frame even if it was reseted. */ - friend class VideoGenerator; + const VideoFrameDesc _desc; }; } diff --git a/src/AvTranscoder/decoder/AudioDecoder.cpp b/src/AvTranscoder/decoder/AudioDecoder.cpp index 6ea1afb6..4b7f6101 100644 --- a/src/AvTranscoder/decoder/AudioDecoder.cpp +++ b/src/AvTranscoder/decoder/AudioDecoder.cpp @@ -1,5 +1,6 @@ #include "AudioDecoder.hpp" +#include #include #include #include @@ -76,7 +77,7 @@ void AudioDecoder::setupDecoder(const ProfileLoader::Profile& profile) _isSetup = true; } -bool AudioDecoder::decodeNextFrame(Frame& frameBuffer) +bool AudioDecoder::decodeNextFrame(IFrame& frameBuffer) { bool decodeNextFrame = false; const size_t channelLayout = frameBuffer.getAVFrame().channel_layout; @@ -121,27 +122,10 @@ bool AudioDecoder::decodeNextFrame(Frame& frameBuffer) return decodeNextFrame; } -bool AudioDecoder::decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray) +bool AudioDecoder::decodeNextFrame(IFrame& frameBuffer, const std::vector channelIndexArray) { AVCodecContext& avCodecContext = _inputStream->getAudioCodec().getAVCodecContext(); const size_t srcNbChannels = avCodecContext.channels; - const size_t bytePerSample = av_get_bytes_per_sample((AVSampleFormat)frameBuffer.getAVFrame().format); - - // if all channels of the stream are extracted - if(srcNbChannels == channelIndexArray.size()) - return decodeNextFrame(frameBuffer); - - // else decode all data in an intermediate buffer - AudioFrame allDataOfNextFrame(frameBuffer); - if(!decodeNextFrame(allDataOfNextFrame)) - return false; - - const int dstNbChannels = 1; - const int noAlignment = 0; - const size_t decodedSize = av_samples_get_buffer_size(NULL, dstNbChannels, frameBuffer.getAVFrame().nb_samples, - avCodecContext.sample_fmt, noAlignment); - if(decodedSize == 0) - return false; // check if each expected channel exists for(std::vector::const_iterator channelIndex = channelIndexArray.begin(); @@ -159,12 +143,29 @@ bool AudioDecoder::decodeNextFrame(Frame& frameBuffer, const std::vector } } - // copy frame properties of decoded frame + // if all channels of the stream are extracted + if(srcNbChannels == channelIndexArray.size()) + return decodeNextFrame(frameBuffer); + + // else decode all data in an intermediate buffer AudioFrame& audioBuffer = static_cast(frameBuffer); + AudioFrame allDataOfNextFrame(AudioFrameDesc(audioBuffer.getSampleRate(), srcNbChannels, getSampleFormatName(audioBuffer.getSampleFormat())), false); + if(!decodeNextFrame(allDataOfNextFrame)) + return false; + + const size_t bytePerSample = audioBuffer.getBytesPerSample(); + const int dstNbChannels = channelIndexArray.size(); + const int noAlignment = 0; + const size_t decodedSize = av_samples_get_buffer_size(NULL, dstNbChannels, frameBuffer.getAVFrame().nb_samples, + avCodecContext.sample_fmt, noAlignment); + if(decodedSize == 0) + return false; + + // update the output frame audioBuffer.copyProperties(allDataOfNextFrame); - av_frame_set_channels(&audioBuffer.getAVFrame(), channelIndexArray.size()); - av_frame_set_channel_layout(&audioBuffer.getAVFrame(), av_get_default_channel_layout(channelIndexArray.size())); audioBuffer.setNbSamplesPerChannel(allDataOfNextFrame.getNbSamplesPerChannel()); + if(! audioBuffer.isDataAllocated()) + audioBuffer.allocateData(); // @todo manage cases with data of frame not only on data[0] (use _frame.linesize) unsigned char* src = allDataOfNextFrame.getData()[0]; diff --git a/src/AvTranscoder/decoder/AudioDecoder.hpp b/src/AvTranscoder/decoder/AudioDecoder.hpp index b5e5ac57..92063449 100644 --- a/src/AvTranscoder/decoder/AudioDecoder.hpp +++ b/src/AvTranscoder/decoder/AudioDecoder.hpp @@ -16,8 +16,8 @@ class AvExport AudioDecoder : public IDecoder void setupDecoder(const ProfileLoader::Profile& profile = ProfileLoader::Profile()); - bool decodeNextFrame(Frame& frameBuffer); - bool decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray); + bool decodeNextFrame(IFrame& frameBuffer); + bool decodeNextFrame(IFrame& frameBuffer, const std::vector channelIndexArray); void flushDecoder(); diff --git a/src/AvTranscoder/decoder/AudioGenerator.cpp b/src/AvTranscoder/decoder/AudioGenerator.cpp index 48a0b424..63b1a4b1 100644 --- a/src/AvTranscoder/decoder/AudioGenerator.cpp +++ b/src/AvTranscoder/decoder/AudioGenerator.cpp @@ -19,16 +19,8 @@ AudioGenerator::~AudioGenerator() delete _silent; } -bool AudioGenerator::decodeNextFrame(Frame& frameBuffer) +bool AudioGenerator::decodeNextFrame(IFrame& frameBuffer) { - // check the given frame - if(! frameBuffer.isAudioFrame()) - { - LOG_WARN("The given frame to put data is not a valid audio frame: try to reallocate it.") - frameBuffer.clear(); - static_cast(frameBuffer).allocateAVSample(_frameDesc); - } - // Generate silent if(!_inputFrame) { @@ -50,7 +42,7 @@ bool AudioGenerator::decodeNextFrame(Frame& frameBuffer) _silent->setNbSamplesPerChannel(frameBuffer.getAVFrame().nb_samples); } LOG_DEBUG("Copy data of the silence when decode next frame") - frameBuffer.refFrame(*_silent); + frameBuffer.copyData(*_silent); } // Take audio frame from _inputFrame else @@ -61,7 +53,7 @@ bool AudioGenerator::decodeNextFrame(Frame& frameBuffer) return true; } -bool AudioGenerator::decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray) +bool AudioGenerator::decodeNextFrame(IFrame& frameBuffer, const std::vector channelIndexArray) { return decodeNextFrame(frameBuffer); } diff --git a/src/AvTranscoder/decoder/AudioGenerator.hpp b/src/AvTranscoder/decoder/AudioGenerator.hpp index de8eccd9..9c4a7348 100644 --- a/src/AvTranscoder/decoder/AudioGenerator.hpp +++ b/src/AvTranscoder/decoder/AudioGenerator.hpp @@ -19,18 +19,18 @@ class AvExport AudioGenerator : public IDecoder ~AudioGenerator(); - bool decodeNextFrame(Frame& frameBuffer); - bool decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray); + bool decodeNextFrame(IFrame& frameBuffer); + bool decodeNextFrame(IFrame& frameBuffer, const std::vector channelIndexArray); /** * @brief Force to return this frame when calling the decoding methods. * @param inputFrame: could have other properties than the given frame when decoding (will be converted). * @see decodeNextFrame */ - void setNextFrame(Frame& inputFrame) { _inputFrame = &inputFrame; } + void setNextFrame(IFrame& inputFrame) { _inputFrame = &inputFrame; } private: - Frame* _inputFrame; ///< Has link (no ownership) + IFrame* _inputFrame; ///< Has link (no ownership) AudioFrame* _silent; ///< The generated silent (has ownership) const AudioFrameDesc _frameDesc; ///< The description of the given frame buffer when decoding. AudioTransform _audioTransform; ///< To transform the specified data when decoding. diff --git a/src/AvTranscoder/decoder/IDecoder.hpp b/src/AvTranscoder/decoder/IDecoder.hpp index ca28bf15..d9acf8d1 100644 --- a/src/AvTranscoder/decoder/IDecoder.hpp +++ b/src/AvTranscoder/decoder/IDecoder.hpp @@ -2,7 +2,7 @@ #define _AV_TRANSCODER_ESSENCE_STREAM_IDECODER_HPP_ #include -#include +#include #include namespace avtranscoder @@ -28,7 +28,7 @@ class AvExport IDecoder * decoder. The caller may not write to it. * @return status of decoding */ - virtual bool decodeNextFrame(Frame& frameBuffer) = 0; + virtual bool decodeNextFrame(IFrame& frameBuffer) = 0; /** * @brief Decode substream of next frame @@ -36,14 +36,14 @@ class AvExport IDecoder * @param channelIndexArray: list of channels to extract * @return status of decoding */ - virtual bool decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray) = 0; + virtual bool decodeNextFrame(IFrame& frameBuffer, const std::vector channelIndexArray) = 0; /** * @brief Set the next frame of the input stream (which bypass the work of decoding) * @note Not yet implemented for VideoDecoder and AudioDecoder * @param inputFrame: the new next frame */ - virtual void setNextFrame(Frame& inputFrame) {} + virtual void setNextFrame(IFrame& inputFrame) {} /** * @brief Reset the internal decoder state / flush internal buffers. diff --git a/src/AvTranscoder/decoder/VideoDecoder.cpp b/src/AvTranscoder/decoder/VideoDecoder.cpp index 082d1dc2..02a39f8d 100644 --- a/src/AvTranscoder/decoder/VideoDecoder.cpp +++ b/src/AvTranscoder/decoder/VideoDecoder.cpp @@ -74,7 +74,7 @@ void VideoDecoder::setupDecoder(const ProfileLoader::Profile& profile) _isSetup = true; } -bool VideoDecoder::decodeNextFrame(Frame& frameBuffer) +bool VideoDecoder::decodeNextFrame(IFrame& frameBuffer) { bool decodeNextFrame = false; @@ -115,7 +115,7 @@ bool VideoDecoder::decodeNextFrame(Frame& frameBuffer) return decodeNextFrame; } -bool VideoDecoder::decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray) +bool VideoDecoder::decodeNextFrame(IFrame& frameBuffer, const std::vector channelIndexArray) { return false; } diff --git a/src/AvTranscoder/decoder/VideoDecoder.hpp b/src/AvTranscoder/decoder/VideoDecoder.hpp index 3065f484..88303d00 100644 --- a/src/AvTranscoder/decoder/VideoDecoder.hpp +++ b/src/AvTranscoder/decoder/VideoDecoder.hpp @@ -16,8 +16,8 @@ class AvExport VideoDecoder : public IDecoder void setupDecoder(const ProfileLoader::Profile& profile = ProfileLoader::Profile()); - bool decodeNextFrame(Frame& frameBuffer); - bool decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray); + bool decodeNextFrame(IFrame& frameBuffer); + bool decodeNextFrame(IFrame& frameBuffer, const std::vector channelIndexArray); void flushDecoder(); diff --git a/src/AvTranscoder/decoder/VideoGenerator.cpp b/src/AvTranscoder/decoder/VideoGenerator.cpp index 58ba9509..ad89c9ac 100644 --- a/src/AvTranscoder/decoder/VideoGenerator.cpp +++ b/src/AvTranscoder/decoder/VideoGenerator.cpp @@ -19,16 +19,8 @@ VideoGenerator::~VideoGenerator() delete _blackImage; } -bool VideoGenerator::decodeNextFrame(Frame& frameBuffer) +bool VideoGenerator::decodeNextFrame(IFrame& frameBuffer) { - // check the given frame - if(! frameBuffer.isVideoFrame()) - { - LOG_WARN("The given frame to put data is not a valid video frame: try to reallocate it.") - frameBuffer.clear(); - static_cast(frameBuffer).allocateAVPicture(_frameDesc); - } - // Generate black image if(!_inputFrame) { @@ -39,7 +31,7 @@ bool VideoGenerator::decodeNextFrame(Frame& frameBuffer) VideoFrameDesc blackDesc(_frameDesc._width, _frameDesc._height, "rgb24"); _blackImage = new VideoFrame(blackDesc); const unsigned char fillChar = 0; - _blackImage->assign(fillChar); + _blackImage->assignValue(fillChar); std::stringstream msg; msg << "Generate a black image with the following features:" << std::endl; @@ -62,7 +54,7 @@ bool VideoGenerator::decodeNextFrame(Frame& frameBuffer) return true; } -bool VideoGenerator::decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray) +bool VideoGenerator::decodeNextFrame(IFrame& frameBuffer, const std::vector channelIndexArray) { return false; } diff --git a/src/AvTranscoder/decoder/VideoGenerator.hpp b/src/AvTranscoder/decoder/VideoGenerator.hpp index 6f689e31..f8f03489 100644 --- a/src/AvTranscoder/decoder/VideoGenerator.hpp +++ b/src/AvTranscoder/decoder/VideoGenerator.hpp @@ -18,18 +18,18 @@ class AvExport VideoGenerator : public IDecoder VideoGenerator(const VideoFrameDesc& frameDesc); ~VideoGenerator(); - bool decodeNextFrame(Frame& frameBuffer); - bool decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray); + bool decodeNextFrame(IFrame& frameBuffer); + bool decodeNextFrame(IFrame& frameBuffer, const std::vector channelIndexArray); /** * @brief Force to return this frame when calling the decoding methods. * @param inputFrame: could have other properties than the given frame when decoding (will be converted). * @see decodeNextFrame */ - void setNextFrame(Frame& inputFrame) { _inputFrame = &inputFrame; } + void setNextFrame(IFrame& inputFrame) { _inputFrame = &inputFrame; } private: - Frame* _inputFrame; ///< A frame given from outside (has link, no ownership) + IFrame* _inputFrame; ///< A frame given from outside (has link, no ownership) VideoFrame* _blackImage; ///< The generated RGB black image (has ownership) const VideoFrameDesc _frameDesc; ///< The description of the given frame buffer when decoding. VideoTransform _videoTransform; ///< To transform data of the back image to the given Frame when decoding. diff --git a/src/AvTranscoder/encoder/AudioEncoder.cpp b/src/AvTranscoder/encoder/AudioEncoder.cpp index 66fa14e1..06410299 100644 --- a/src/AvTranscoder/encoder/AudioEncoder.cpp +++ b/src/AvTranscoder/encoder/AudioEncoder.cpp @@ -91,7 +91,7 @@ void AudioEncoder::setupEncoder(const ProfileLoader::Profile& profile) } } -bool AudioEncoder::encodeFrame(const Frame& sourceFrame, CodedData& codedFrame) +bool AudioEncoder::encodeFrame(const IFrame& sourceFrame, CodedData& codedFrame) { AVCodecContext& avCodecContext = _codec.getAVCodecContext(); diff --git a/src/AvTranscoder/encoder/AudioEncoder.hpp b/src/AvTranscoder/encoder/AudioEncoder.hpp index 35ea2bef..0c8e0a55 100644 --- a/src/AvTranscoder/encoder/AudioEncoder.hpp +++ b/src/AvTranscoder/encoder/AudioEncoder.hpp @@ -18,7 +18,7 @@ class AvExport AudioEncoder : public IEncoder const ProfileLoader::Profile& profile = ProfileLoader::Profile()); void setupEncoder(const ProfileLoader::Profile& profile = ProfileLoader::Profile()); - bool encodeFrame(const Frame& sourceFrame, CodedData& codedFrame); + bool encodeFrame(const IFrame& sourceFrame, CodedData& codedFrame); bool encodeFrame(CodedData& codedFrame); ICodec& getCodec() { return _codec; } diff --git a/src/AvTranscoder/encoder/IEncoder.hpp b/src/AvTranscoder/encoder/IEncoder.hpp index 33ef1f38..e9727938 100644 --- a/src/AvTranscoder/encoder/IEncoder.hpp +++ b/src/AvTranscoder/encoder/IEncoder.hpp @@ -1,7 +1,7 @@ #ifndef _AV_TRANSCODER_ESSENCE_STREAM_IENCODER_HPP_ #define _AV_TRANSCODER_ESSENCE_STREAM_IENCODER_HPP_ -#include +#include #include #include #include @@ -28,7 +28,7 @@ class AvExport IEncoder * @return status of encoding * @throw runtime_error if the encoded process failed. */ - virtual bool encodeFrame(const Frame& sourceFrame, CodedData& codedFrame) = 0; + virtual bool encodeFrame(const IFrame& sourceFrame, CodedData& codedFrame) = 0; /** * @brief Get the frames remaining into the encoder diff --git a/src/AvTranscoder/encoder/VideoEncoder.cpp b/src/AvTranscoder/encoder/VideoEncoder.cpp index fef4ab37..c6d43b66 100644 --- a/src/AvTranscoder/encoder/VideoEncoder.cpp +++ b/src/AvTranscoder/encoder/VideoEncoder.cpp @@ -104,7 +104,7 @@ void VideoEncoder::setupEncoder(const ProfileLoader::Profile& profile) } } -bool VideoEncoder::encodeFrame(const Frame& sourceFrame, CodedData& codedFrame) +bool VideoEncoder::encodeFrame(const IFrame& sourceFrame, CodedData& codedFrame) { AVCodecContext& avCodecContext = _codec.getAVCodecContext(); diff --git a/src/AvTranscoder/encoder/VideoEncoder.hpp b/src/AvTranscoder/encoder/VideoEncoder.hpp index 0a45b7e8..d12035e6 100644 --- a/src/AvTranscoder/encoder/VideoEncoder.hpp +++ b/src/AvTranscoder/encoder/VideoEncoder.hpp @@ -18,7 +18,7 @@ class AvExport VideoEncoder : public IEncoder const ProfileLoader::Profile& profile = ProfileLoader::Profile()); void setupEncoder(const ProfileLoader::Profile& profile = ProfileLoader::Profile()); - bool encodeFrame(const Frame& sourceFrame, CodedData& codedFrame); + bool encodeFrame(const IFrame& sourceFrame, CodedData& codedFrame); bool encodeFrame(CodedData& codedFrame); ICodec& getCodec() { return _codec; } diff --git a/src/AvTranscoder/file/FormatContext.cpp b/src/AvTranscoder/file/FormatContext.cpp index efc56bfe..51d78f4e 100644 --- a/src/AvTranscoder/file/FormatContext.cpp +++ b/src/AvTranscoder/file/FormatContext.cpp @@ -8,11 +8,12 @@ namespace avtranscoder FormatContext::FormatContext(const std::string& filename, int req_flags, AVDictionary** options) : _avFormatContext(NULL) + , _avStreamAllocated() , _flags(req_flags) , _options() , _isOpen(false) { - int ret = avformat_open_input(&_avFormatContext, filename.c_str(), NULL, options); + const int ret = avformat_open_input(&_avFormatContext, filename.c_str(), NULL, options); if(ret < 0) { std::string msg = "Unable to open file "; @@ -31,6 +32,7 @@ FormatContext::FormatContext(const std::string& filename, int req_flags, AVDicti FormatContext::FormatContext(int req_flags) : _avFormatContext(NULL) + , _avStreamAllocated() , _flags(req_flags) , _options() , _isOpen(false) @@ -44,10 +46,14 @@ FormatContext::~FormatContext() if(!_avFormatContext) return; + // free the streams added + for(std::vector::iterator it = _avStreamAllocated.begin(); it != _avStreamAllocated.end(); ++it) + avcodec_close((*it)->codec); + + // free the format context if(_isOpen) avformat_close_input(&_avFormatContext); - else - avformat_free_context(_avFormatContext); + avformat_free_context(_avFormatContext); _avFormatContext = NULL; } @@ -139,6 +145,7 @@ AVStream& FormatContext::addAVStream(const AVCodec& avCodec) { throw std::runtime_error("Unable to add new video stream"); } + _avStreamAllocated.push_back(stream); return *stream; } diff --git a/src/AvTranscoder/file/FormatContext.hpp b/src/AvTranscoder/file/FormatContext.hpp index 7e8cce88..0a33bbe1 100644 --- a/src/AvTranscoder/file/FormatContext.hpp +++ b/src/AvTranscoder/file/FormatContext.hpp @@ -23,11 +23,13 @@ class AvExport FormatContext public: /** * @brief Allocate an AVFormatContext by opening an input file + * @see InputFile */ FormatContext(const std::string& filename, int req_flags = 0, AVDictionary** options = NULL); /** * @brief Allocate an AVFormatContext with default values + * @see OutputFile */ FormatContext(int req_flags = 0); @@ -120,6 +122,7 @@ class AvExport FormatContext private: AVFormatContext* _avFormatContext; ///< Has ownership + std::vector _avStreamAllocated; ///< Has link (no ownership) const int _flags; ///< Flags with which the options are loaded (see AV_OPT_FLAG_xxx) OptionMap _options; bool _isOpen; ///< Is the AVFormatContext open (in constructor with a filename) diff --git a/src/AvTranscoder/filter/Filter.hpp b/src/AvTranscoder/filter/Filter.hpp index 680a923a..5b6658e0 100644 --- a/src/AvTranscoder/filter/Filter.hpp +++ b/src/AvTranscoder/filter/Filter.hpp @@ -30,8 +30,8 @@ class AvExport Filter #endif private: - AVFilter* _filter; - AVFilterContext* _context; + AVFilter* _filter; ///< Has ownership + AVFilterContext* _context; ///< Has link (no ownership) std::string _options; std::string _instanceName; }; diff --git a/src/AvTranscoder/filter/FilterGraph.cpp b/src/AvTranscoder/filter/FilterGraph.cpp index c629c81d..11b7346e 100644 --- a/src/AvTranscoder/filter/FilterGraph.cpp +++ b/src/AvTranscoder/filter/FilterGraph.cpp @@ -30,21 +30,13 @@ FilterGraph::~FilterGraph() { for(std::vector::iterator it = _filters.begin(); it < _filters.end(); ++it) { - it = _filters.erase(it); + delete(*it); } avfilter_graph_free(&_graph); } -void FilterGraph::process(const std::vector& inputs, Frame& output) +void FilterGraph::process(const std::vector& inputs, IFrame& output) { - if(!hasFilters()) - { - LOG_DEBUG("No filter to process: reference first input frame to the given output.") - output.clear(); - output.refFrame(*inputs.at(0)); - return; - } - // init filter graph if(!_isInit) init(inputs, output); @@ -82,7 +74,7 @@ Filter& FilterGraph::addFilter(const std::string& filterName, const std::string& return *_filters.back(); } -void FilterGraph::init(const std::vector& inputs, Frame& output) +void FilterGraph::init(const std::vector& inputs, IFrame& output) { // push filters to the graph addInBuffer(inputs); @@ -142,9 +134,9 @@ void FilterGraph::pushFilter(Filter& filter) } } -void FilterGraph::addInBuffer(const std::vector& inputs) +void FilterGraph::addInBuffer(const std::vector& inputs) { - for(std::vector::const_reverse_iterator it = inputs.rbegin(); it != inputs.rend(); ++it) + for(std::vector::const_reverse_iterator it = inputs.rbegin(); it != inputs.rend(); ++it) { std::string filterName; std::stringstream filterOptions; @@ -176,13 +168,12 @@ void FilterGraph::addInBuffer(const std::vector& inputs) throw std::runtime_error("Cannot create input buffer of filter graph: the given frame is invalid."); // add in buffer - Filter* in = new Filter(filterName, filterOptions.str(), "in"); LOG_INFO("Add filter '" << filterName << "' at the beginning of the graph.") - _filters.insert(_filters.begin(), in); + _filters.insert(_filters.begin(), new Filter(filterName, filterOptions.str(), "in")); } } -void FilterGraph::addOutBuffer(const Frame& output) +void FilterGraph::addOutBuffer(const IFrame& output) { std::string filterName; diff --git a/src/AvTranscoder/filter/FilterGraph.hpp b/src/AvTranscoder/filter/FilterGraph.hpp index 86a5ea4c..287f6e9f 100644 --- a/src/AvTranscoder/filter/FilterGraph.hpp +++ b/src/AvTranscoder/filter/FilterGraph.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include @@ -55,21 +55,21 @@ class AvExport FilterGraph * @warning the output frame must be cleared once it has been used * @see the av_buffersink_get_frame function documentation */ - void process(const std::vector& inputs, Frame& output); + void process(const std::vector& inputs, IFrame& output); -private: /** * @return If at least one filter has been added to the filter graph */ bool hasFilters() const { return !_filters.empty(); } +private: /** * @brief Initialize the graph of filters to process. * @see pushFilterToGraph * @see pushInBuffer * @see pushOutBuffer */ - void init(const std::vector& inputs, Frame& output); + void init(const std::vector& inputs, IFrame& output); /** * @brief Push the given Filter to the graph. @@ -78,8 +78,8 @@ class AvExport FilterGraph ///@{ /// @brief Add the input and output buffers at the beginning and the end of the list of filters. - void addInBuffer(const std::vector& inputs); - void addOutBuffer(const Frame& output); + void addInBuffer(const std::vector& inputs); + void addOutBuffer(const IFrame& output); //@} private: diff --git a/src/AvTranscoder/properties/VideoProperties.cpp b/src/AvTranscoder/properties/VideoProperties.cpp index 99b3bde2..a3f83a19 100644 --- a/src/AvTranscoder/properties/VideoProperties.cpp +++ b/src/AvTranscoder/properties/VideoProperties.cpp @@ -1,9 +1,9 @@ #include "VideoProperties.hpp" -#include #include #include #include +#include extern "C" { #include @@ -342,11 +342,14 @@ size_t VideoProperties::getBitRate() const // discard no frame type when decode _codecContext->skip_frame = AVDISCARD_NONE; - Frame frame; AVPacket pkt; av_init_packet(&pkt); + avcodec_open2(_codecContext, _codec, NULL); + VideoFrame frame(VideoFrameDesc(getWidth(), getHeight(), getPixelProperties().getPixelFormatName()), false); + AVFrame& avFrame = frame.getAVFrame(); + int gotFrame = 0; size_t nbDecodedFrames = 0; int gopFramesSize = 0; @@ -357,11 +360,10 @@ size_t VideoProperties::getBitRate() const { if(pkt.stream_index == (int)_streamIndex) { - avcodec_decode_video2(_codecContext, &frame.getAVFrame(), &gotFrame, &pkt); + avcodec_decode_video2(_codecContext, &avFrame, &gotFrame, &pkt); if(gotFrame) { // check distance between key frames - AVFrame& avFrame = frame.getAVFrame(); if(avFrame.pict_type == AV_PICTURE_TYPE_I) { if(positionOfFirstKeyFrame == -1) @@ -375,7 +377,7 @@ size_t VideoProperties::getBitRate() const if(positionOfLastKeyFrame == -1) { #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(54, 7, 100) - gopFramesSize += frame.getEncodedSize(); + gopFramesSize += av_frame_get_pkt_size(&avFrame); #else gopFramesSize += pkt.size; #endif @@ -567,7 +569,9 @@ void VideoProperties::analyseGopStructure(IProgress& progress) // Initialize the AVCodecContext to use the given AVCodec avcodec_open2(_codecContext, _codec, NULL); - Frame frame; + VideoFrame frame(VideoFrameDesc(getWidth(), getHeight(), getPixelProperties().getPixelFormatName()), false); + AVFrame& avFrame = frame.getAVFrame(); + size_t count = 0; int gotFrame = 0; int positionOfFirstKeyFrame = -1; @@ -577,13 +581,11 @@ void VideoProperties::analyseGopStructure(IProgress& progress) { if(pkt.stream_index == (int)_streamIndex) { - avcodec_decode_video2(_codecContext, &frame.getAVFrame(), &gotFrame, &pkt); + avcodec_decode_video2(_codecContext, &avFrame, &gotFrame, &pkt); if(gotFrame) { - AVFrame& avFrame = frame.getAVFrame(); - _gopStructure.push_back( - std::make_pair(av_get_picture_type_char(avFrame.pict_type), frame.getEncodedSize())); + std::make_pair(av_get_picture_type_char(avFrame.pict_type), av_frame_get_pkt_size(&avFrame))); _isInterlaced = avFrame.interlaced_frame; _isTopFieldFirst = avFrame.top_field_first; if(avFrame.pict_type == AV_PICTURE_TYPE_I) diff --git a/src/AvTranscoder/reader/AudioReader.cpp b/src/AvTranscoder/reader/AudioReader.cpp index ea154447..981744b8 100644 --- a/src/AvTranscoder/reader/AudioReader.cpp +++ b/src/AvTranscoder/reader/AudioReader.cpp @@ -45,15 +45,16 @@ void AudioReader::init() _currentDecoder = _decoder; // create src frame - _srcFrame = new AudioFrame(_inputFile->getStream(_streamIndex).getAudioCodec().getAudioFrameDesc()); + const AudioFrameDesc srcFrameDesc = _inputFile->getStream(_streamIndex).getAudioCodec().getAudioFrameDesc(); + _srcFrame = new AudioFrame(srcFrameDesc, false); AudioFrame* srcFrame = static_cast(_srcFrame); // create dst frame _outputSampleRate = srcFrame->getSampleRate(); _outputNbChannels = (_channelIndex == -1) ? srcFrame->getNbChannels() : 1; - _dstFrame = new AudioFrame(AudioFrameDesc(_outputSampleRate, _outputNbChannels, _outputSampleFormat)); + _dstFrame = new AudioFrame(AudioFrameDesc(_outputSampleRate, _outputNbChannels, getSampleFormatName(_outputSampleFormat))); // generator - _generator = new AudioGenerator(srcFrame->desc()); + _generator = new AudioGenerator(srcFrameDesc); // create transform _transform = new AudioTransform(); @@ -75,6 +76,6 @@ void AudioReader::updateOutput(const size_t sampleRate, const size_t nbChannels, _outputSampleFormat = getAVSampleFormat(sampleFormat); // update dst frame delete _dstFrame; - _dstFrame = new AudioFrame(AudioFrameDesc(_outputSampleRate, _outputNbChannels, _outputSampleFormat)); + _dstFrame = new AudioFrame(AudioFrameDesc(_outputSampleRate, _outputNbChannels, getSampleFormatName(_outputSampleFormat))); } } diff --git a/src/AvTranscoder/reader/IReader.cpp b/src/AvTranscoder/reader/IReader.cpp index 3e40fd16..b6f2bf78 100644 --- a/src/AvTranscoder/reader/IReader.cpp +++ b/src/AvTranscoder/reader/IReader.cpp @@ -46,17 +46,17 @@ IReader::~IReader() delete _inputFile; } -Frame* IReader::readNextFrame() +IFrame* IReader::readNextFrame() { return readFrameAt(_currentFrame + 1); } -Frame* IReader::readPrevFrame() +IFrame* IReader::readPrevFrame() { return readFrameAt(_currentFrame - 1); } -Frame* IReader::readFrameAt(const size_t frame) +IFrame* IReader::readFrameAt(const size_t frame) { assert(_currentDecoder != NULL); assert(_transform != NULL); @@ -86,6 +86,9 @@ Frame* IReader::readFrameAt(const size_t frame) // generate data (ie silence or black) if(_continueWithGenerator) { + // allocate the frame since the process will continue with some generated data + if(! _srcFrame->isDataAllocated()) + _srcFrame->allocateData(); _currentDecoder = _generator; return readFrameAt(frame); } diff --git a/src/AvTranscoder/reader/IReader.hpp b/src/AvTranscoder/reader/IReader.hpp index e73e3068..b8a8a9df 100644 --- a/src/AvTranscoder/reader/IReader.hpp +++ b/src/AvTranscoder/reader/IReader.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include namespace avtranscoder @@ -37,20 +37,20 @@ class AvExport IReader * @return Get next frame after decoding * @see readFrameAt */ - Frame* readNextFrame(); + IFrame* readNextFrame(); /** * @return Get previous frame after decoding * @see readFrameAt */ - Frame* readPrevFrame(); + IFrame* 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); + IFrame* readFrameAt(const size_t frame); /** * @brief Get the properties of the source stream read. @@ -70,8 +70,8 @@ class AvExport IReader IDecoder* _generator; IDecoder* _currentDecoder; ///< Link to _inputDecoder or _generator - Frame* _srcFrame; - Frame* _dstFrame; + IFrame* _srcFrame; + IFrame* _dstFrame; ITransform* _transform; diff --git a/src/AvTranscoder/reader/VideoReader.cpp b/src/AvTranscoder/reader/VideoReader.cpp index 98feff89..b6fd245c 100644 --- a/src/AvTranscoder/reader/VideoReader.cpp +++ b/src/AvTranscoder/reader/VideoReader.cpp @@ -1,5 +1,6 @@ #include "VideoReader.hpp" +#include #include #include #include @@ -44,15 +45,16 @@ void VideoReader::init() _currentDecoder = _decoder; // create src frame - _srcFrame = new VideoFrame(_inputFile->getStream(_streamIndex).getVideoCodec().getVideoFrameDesc()); + const VideoFrameDesc srcFrameDesc = _inputFile->getStream(_streamIndex).getVideoCodec().getVideoFrameDesc(); + _srcFrame = new VideoFrame(srcFrameDesc, false); VideoFrame* srcFrame = static_cast(_srcFrame); // create dst frame _outputWidth = srcFrame->getWidth(); _outputHeight = srcFrame->getHeight(); - _dstFrame = new VideoFrame(VideoFrameDesc(_outputWidth, _outputHeight, getOutputPixelFormat())); + _dstFrame = new VideoFrame(VideoFrameDesc(_outputWidth, _outputHeight, getPixelFormatName(getOutputPixelFormat()))); // generator - _generator = new VideoGenerator(srcFrame->desc()); + _generator = new VideoGenerator(srcFrameDesc); // create transform _transform = new VideoTransform(); @@ -74,6 +76,6 @@ void VideoReader::updateOutput(const size_t width, const size_t height, const st _outputPixelProperties = PixelProperties(pixelFormat); // update dst frame delete _dstFrame; - _dstFrame = new VideoFrame(VideoFrameDesc(_outputWidth, _outputHeight, getOutputPixelFormat())); + _dstFrame = new VideoFrame(VideoFrameDesc(_outputWidth, _outputHeight, getPixelFormatName(getOutputPixelFormat()))); } } diff --git a/src/AvTranscoder/transcoder/StreamTranscoder.cpp b/src/AvTranscoder/transcoder/StreamTranscoder.cpp index 25a63d1b..e3133342 100644 --- a/src/AvTranscoder/transcoder/StreamTranscoder.cpp +++ b/src/AvTranscoder/transcoder/StreamTranscoder.cpp @@ -174,7 +174,7 @@ StreamTranscoder::StreamTranscoder(const std::vector& inputStre _outputStream = &outputFile.addVideoStream(outputVideo->getVideoCodec()); // buffers to process - _filteredData = new VideoFrame(outputVideo->getVideoCodec().getVideoFrameDesc()); + _filteredData = new VideoFrame(inputStream.getVideoCodec().getVideoFrameDesc()); _transformedData = new VideoFrame(outputVideo->getVideoCodec().getVideoFrameDesc()); // transform @@ -243,11 +243,11 @@ void StreamTranscoder::addDecoder(const InputStreamDesc& inputStreamDesc, IInput _currentDecoder = inputVideo; // buffers to get the decoded data - VideoFrame* inputFrame = new VideoFrame(inputStream.getVideoCodec().getVideoFrameDesc()); + VideoFrame* inputFrame = new VideoFrame(inputStream.getVideoCodec().getVideoFrameDesc(), false); _decodedData.push_back(inputFrame); // generator decoder - _generators.push_back(new VideoGenerator(inputFrame->desc())); + _generators.push_back(new VideoGenerator(inputStream.getVideoCodec().getVideoFrameDesc())); break; } @@ -263,7 +263,7 @@ void StreamTranscoder::addDecoder(const InputStreamDesc& inputStreamDesc, IInput AudioFrameDesc inputFrameDesc(inputStream.getAudioCodec().getAudioFrameDesc()); if(inputStreamDesc.demultiplexing()) inputFrameDesc._nbChannels = inputStreamDesc._channelIndexArray.size(); - _decodedData.push_back(new AudioFrame(inputFrameDesc)); + _decodedData.push_back(new AudioFrame(inputFrameDesc, false)); // generator decoder _generators.push_back(new AudioGenerator(inputFrameDesc)); @@ -297,8 +297,7 @@ StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader: if(profile.find(constants::avProfileType)->second == constants::avProfileTypeVideo) { VideoCodec inputVideoCodec(eCodecTypeEncoder, profile.find(constants::avProfileCodec)->second); - VideoFrameDesc inputFrameDesc; - inputFrameDesc.setParameters(profile); + VideoFrameDesc inputFrameDesc(profile); inputVideoCodec.setImageParameters(inputFrameDesc); // generator decoder @@ -330,8 +329,7 @@ StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader: else if(profile.find(constants::avProfileType)->second == constants::avProfileTypeAudio) { AudioCodec inputAudioCodec(eCodecTypeEncoder, profile.find(constants::avProfileCodec)->second); - AudioFrameDesc inputFrameDesc; - inputFrameDesc.setParameters(profile); + AudioFrameDesc inputFrameDesc(profile); inputAudioCodec.setAudioParameters(inputFrameDesc); // generator decoder @@ -368,7 +366,7 @@ StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader: StreamTranscoder::~StreamTranscoder() { - for(std::vector::iterator it = _decodedData.begin(); it != _decodedData.end(); ++it) + for(std::vector::iterator it = _decodedData.begin(); it != _decodedData.end(); ++it) { delete(*it); } @@ -448,6 +446,13 @@ bool StreamTranscoder::processFrame() { LOG_INFO("End of positive offset") + // free our frame data since some new buffers will be allocated by the decoders in the next step + for(std::vector::iterator it = _decodedData.begin(); it != _decodedData.end(); ++it) + { + if((*it)->isDataAllocated()) + (*it)->freeData(); + } + // switch the decoder if(! _inputDecoders.empty()) switchToInputDecoder(); else @@ -526,6 +531,7 @@ bool StreamTranscoder::processTranscode() LOG_DEBUG("StreamTranscoder::processTranscode") + // Decode LOG_DEBUG("Decode next frame") bool decodingStatus = true; for(size_t index = 0; index < _generators.size(); ++index) @@ -541,16 +547,44 @@ bool StreamTranscoder::processTranscode() decodingStatus = decodingStatus && _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; + 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.") + _filteredData->freeData(); + _filteredData->getAVFrame().nb_samples = nbInputSamplesPerChannel; + _filteredData->allocateData(); + } + if(nbInputSamplesPerChannel > _transformedData->getAVFrame().nb_samples) + { + LOG_WARN("The buffer of transformed data corresponds to a frame of " << _transformedData->getAVFrame().nb_samples << " samples. The decoded buffer contains " << nbInputSamplesPerChannel << " samples. Reallocate it.") + _transformedData->freeData(); + _transformedData->getAVFrame().nb_samples = nbInputSamplesPerChannel; + _transformedData->allocateData(); + } + } + + // Transform CodedData data; if(decodingStatus) { - LOG_DEBUG("Filtering") - _filterGraph->process(_decodedData, *_filteredData); + IFrame* dataToTransform = NULL; + if(_filterGraph->hasFilters()) + { + LOG_DEBUG("Filtering") + _filterGraph->process(_decodedData, *_filteredData); + dataToTransform = _filteredData; + } + else + { + dataToTransform = _decodedData.at(0); + } LOG_DEBUG("Convert") - _transform->convert(*_filteredData, *_transformedData); - - _filteredData->clear(); + _transform->convert(*dataToTransform, *_transformedData); LOG_DEBUG("Encode") _outputEncoder->encodeFrame(*_transformedData, data); @@ -563,12 +597,19 @@ bool StreamTranscoder::processTranscode() if(_needToSwitchToGenerator) { switchToGeneratorDecoder(); + LOG_INFO("Force reallocation of the decoded data buffers since the decoders could have cleared them.") + for(std::vector::iterator it = _decodedData.begin(); it != _decodedData.end(); ++it) + { + if(! (*it)->isDataAllocated()) + (*it)->allocateData(); + } return processTranscode(); } return false; } } + // Wrap LOG_DEBUG("wrap (" << data.getSize() << " bytes)") const IOutputStream::EWrappingStatus wrappingStatus = _outputStream->wrap(data); switch(wrappingStatus) @@ -649,7 +690,15 @@ void StreamTranscoder::setOffset(const float offset) { _offset = offset; if(_offset > 0) + { needToSwitchToGenerator(); + // allocate the frame since the process will start with some generated data + for(std::vector::iterator it = _decodedData.begin(); it != _decodedData.end(); ++it) + { + if(! (*it)->isDataAllocated()) + (*it)->allocateData(); + } + } } StreamTranscoder::EProcessCase StreamTranscoder::getProcessCase() const diff --git a/src/AvTranscoder/transcoder/StreamTranscoder.hpp b/src/AvTranscoder/transcoder/StreamTranscoder.hpp index 4dceff17..c916b8cc 100644 --- a/src/AvTranscoder/transcoder/StreamTranscoder.hpp +++ b/src/AvTranscoder/transcoder/StreamTranscoder.hpp @@ -134,9 +134,9 @@ class AvExport StreamTranscoder std::vector _inputStreams; ///< List of input stream to read next packet (has link, no ownership) IOutputStream* _outputStream; ///< Output stream to wrap next packet (has link, no ownership) - std::vector _decodedData; ///< List of buffers of decoded data (has ownership). - Frame* _filteredData; ///< Buffer of filtered data (has ownership). - Frame* _transformedData; ///< Buffer of transformed data (has ownership). + std::vector _decodedData; ///< List of buffers of decoded data (has ownership). + IFrame* _filteredData; ///< Buffer of filtered data (has ownership). + IFrame* _transformedData; ///< Buffer of transformed data (has ownership). std::vector _inputDecoders; ///< Decoders of packets read from _inputStream (has ownership) std::vector _generators; ///< Generators of audio or video packets (has ownership) diff --git a/src/AvTranscoder/transform/AudioTransform.cpp b/src/AvTranscoder/transform/AudioTransform.cpp index 41194a06..96ffacf4 100644 --- a/src/AvTranscoder/transform/AudioTransform.cpp +++ b/src/AvTranscoder/transform/AudioTransform.cpp @@ -26,8 +26,9 @@ extern "C" { #endif } -#include #include +#include +#include namespace avtranscoder { @@ -43,7 +44,7 @@ AudioTransform::~AudioTransform() FreeResampleContext(&_audioConvertContext); } -bool AudioTransform::init(const Frame& srcFrame, const Frame& dstFrame) +bool AudioTransform::init(const AudioFrame& src, const AudioFrame& dst) { // Set convert context _audioConvertContext = AllocResampleContext(); @@ -52,9 +53,6 @@ bool AudioTransform::init(const Frame& srcFrame, const Frame& dstFrame) throw std::runtime_error("unable to create audio convert context"); } - const AudioFrame& src = static_cast(srcFrame); - const AudioFrame& dst = static_cast(dstFrame); - av_opt_set_int(_audioConvertContext, "in_channel_layout", src.getChannelLayout(), 0); av_opt_set_int(_audioConvertContext, "out_channel_layout", dst.getChannelLayout(), 0); av_opt_set_int(_audioConvertContext, "in_sample_rate", src.getSampleRate(), 0); @@ -90,10 +88,19 @@ bool AudioTransform::init(const Frame& srcFrame, const Frame& dstFrame) return true; } -void AudioTransform::convert(const Frame& srcFrame, Frame& dstFrame) +void AudioTransform::convert(const IFrame& srcFrame, IFrame& dstFrame) { + const AudioFrame& src = static_cast(srcFrame); + const AudioFrame& dst = static_cast(dstFrame); + + assert(src.getSampleRate() > 0); + assert(src.getNbChannels() > 0); + assert(src.getNbSamplesPerChannel() > 0); + assert(src.getSampleFormat() != AV_SAMPLE_FMT_NONE); + assert(dst.getDataSize() > 0); + if(!_isInit) - _isInit = init(srcFrame, dstFrame); + _isInit = init(src, dst); // if number of samples change from previous frame const size_t nbInputSamplesPerChannel = srcFrame.getAVFrame().nb_samples; @@ -110,13 +117,9 @@ void AudioTransform::convert(const Frame& srcFrame, Frame& dstFrame) swr_convert(_audioConvertContext, dstData, nbInputSamplesPerChannel, srcData, nbInputSamplesPerChannel); #endif + // update the number of samples of the output frame if(nbOutputSamplesPerChannel < 0) - { - throw std::runtime_error("unable to convert audio samples"); - } - else - { - dstFrame.getAVFrame().nb_samples = nbOutputSamplesPerChannel; - } + throw std::runtime_error("Unable to convert audio samples"); + dstFrame.getAVFrame().nb_samples = nbOutputSamplesPerChannel; } } diff --git a/src/AvTranscoder/transform/AudioTransform.hpp b/src/AvTranscoder/transform/AudioTransform.hpp index e670605c..834be433 100644 --- a/src/AvTranscoder/transform/AudioTransform.hpp +++ b/src/AvTranscoder/transform/AudioTransform.hpp @@ -3,8 +3,7 @@ #include "ITransform.hpp" -#include -#include +#include #ifdef AVTRANSCODER_LIBAV_DEPENDENCY #define ResampleContext AVAudioResampleContext @@ -27,10 +26,10 @@ class AvExport AudioTransform : public ITransform AudioTransform(); ~AudioTransform(); - void convert(const Frame& srcFrame, Frame& dstFrame); + void convert(const IFrame& srcFrame, IFrame& dstFrame); private: - bool init(const Frame& srcFrame, const Frame& dstFrame); + bool init(const AudioFrame& src, const AudioFrame& dst); private: ResampleContext* _audioConvertContext; diff --git a/src/AvTranscoder/transform/ITransform.hpp b/src/AvTranscoder/transform/ITransform.hpp index 0dba77d4..e2a637c5 100644 --- a/src/AvTranscoder/transform/ITransform.hpp +++ b/src/AvTranscoder/transform/ITransform.hpp @@ -2,7 +2,7 @@ #define _AV_TRANSCODER_ESSENCE_TRANSFORM_ESSENCE_TRANSFORM_HPP_ #include -#include +#include namespace avtranscoder { @@ -14,10 +14,7 @@ class AvExport ITransform virtual ~ITransform() {} - virtual void convert(const Frame& src, Frame& dst) = 0; - -protected: - virtual bool init(const Frame& src, const Frame& dst) = 0; + virtual void convert(const IFrame& src, IFrame& dst) = 0; }; } diff --git a/src/AvTranscoder/transform/VideoTransform.cpp b/src/AvTranscoder/transform/VideoTransform.cpp index 90131b1d..2ea8eb3d 100644 --- a/src/AvTranscoder/transform/VideoTransform.cpp +++ b/src/AvTranscoder/transform/VideoTransform.cpp @@ -1,7 +1,5 @@ #include "VideoTransform.hpp" -#include - extern "C" { #include #include @@ -31,12 +29,8 @@ VideoTransform::~VideoTransform() sws_freeContext(_imageConvertContext); } -bool VideoTransform::init(const Frame& srcFrame, const Frame& dstFrame) +bool VideoTransform::init(const VideoFrame& src, const VideoFrame& dst) { - // Set convert context - const VideoFrame& src = static_cast(srcFrame); - const VideoFrame& dst = static_cast(dstFrame); - const AVPixelFormat srcPixelFormat = src.getPixelFormat(); const AVPixelFormat dstPixelFormat = dst.getPixelFormat(); @@ -66,17 +60,18 @@ bool VideoTransform::init(const Frame& srcFrame, const Frame& dstFrame) return true; } -void VideoTransform::convert(const Frame& srcFrame, Frame& dstFrame) +void VideoTransform::convert(const IFrame& srcFrame, IFrame& dstFrame) { const VideoFrame& src = static_cast(srcFrame); VideoFrame& dst = static_cast(dstFrame); - assert(src.getWidth() != 0); - assert(src.getHeight() != 0); + assert(src.getWidth() > 0); + assert(src.getHeight() > 0); assert(src.getPixelFormat() != AV_PIX_FMT_NONE); + assert(dst.getDataSize() > 0); if(!_isInit) - _isInit = init(srcFrame, dstFrame); + _isInit = init(src, dst); if(!_imageConvertContext) { diff --git a/src/AvTranscoder/transform/VideoTransform.hpp b/src/AvTranscoder/transform/VideoTransform.hpp index 7d17d0f7..1b5b63f6 100644 --- a/src/AvTranscoder/transform/VideoTransform.hpp +++ b/src/AvTranscoder/transform/VideoTransform.hpp @@ -1,11 +1,9 @@ #ifndef _AV_TRANSCODER_ESSENCE_TRANSFORM_VIDEO_ESSENCE_TRANSFORM_HPP #define _AV_TRANSCODER_ESSENCE_TRANSFORM_VIDEO_ESSENCE_TRANSFORM_HPP -#include - #include "ITransform.hpp" -#include +#include class SwsContext; @@ -22,10 +20,10 @@ class AvExport VideoTransform : public ITransform VideoTransform(); ~VideoTransform(); - void convert(const Frame& srcFrame, Frame& dstFrame); + void convert(const IFrame& srcFrame, IFrame& dstFrame); private: - bool init(const Frame& srcFrame, const Frame& dstFrame); + bool init(const VideoFrame& src, const VideoFrame& dst); SwsContext* _imageConvertContext; bool _isInit; diff --git a/test/pyTest/testAudioFrame.py b/test/pyTest/testAudioFrame.py new file mode 100644 index 00000000..6028c5f3 --- /dev/null +++ b/test/pyTest/testAudioFrame.py @@ -0,0 +1,52 @@ +from nose.tools import * + +from pyAvTranscoder import avtranscoder as av + + +@raises(MemoryError) +def testInvalidAudioFrameAutoAllocated(): + """ + Try to create an invalid AudioFrame automatically allocated. + """ + desc = av.AudioFrameDesc(4800, 1, "toto") + av.AudioFrame(desc) + + +@raises(MemoryError) +def testInvalidAudioFrameManualAllocated(): + """ + Try to create an invalid AudioFrame manually allocated. + """ + sampleRate = 48000 + nbChannels = 1 + sampleFormat = "titi" + desc = av.AudioFrameDesc(sampleRate, nbChannels, sampleFormat) + frame = av.AudioFrame(desc, False) + + assert_equals(frame.isDataAllocated(), False) + assert_equals(frame.getDataSize(), 0) + assert_equals(frame.getSampleRate(), sampleRate) + assert_equals(frame.getNbChannels(), nbChannels) + assert_equals(frame.getChannelLayoutDesc(), "mono") + assert_equals(frame.getNbSamplesPerChannel(), 1920) + assert_equals(frame.getBytesPerSample(), 0) + assert_equals(av.getSampleFormatName(frame.getSampleFormat()), "") + + frame.allocateData() + +def testAudioFrame(): + """ + Check the size and the data buffer of a AudioFrame. + """ + sampleRate = 48000 + nbChannels = 1 + sampleFormat = "s32" + desc = av.AudioFrameDesc(sampleRate, nbChannels, sampleFormat) + frame = av.AudioFrame(desc) + + assert_equals(frame.isDataAllocated(), True) + assert_equals(frame.isAudioFrame(), True) + assert_equals(frame.getDataSize(), frame.getNbSamplesPerChannel() * frame.getBytesPerSample() * nbChannels) + + frame.freeData() + assert_equals(frame.isDataAllocated(), False) diff --git a/test/pyTest/testAudioReader.py b/test/pyTest/testAudioReader.py new file mode 100644 index 00000000..30a9b080 --- /dev/null +++ b/test/pyTest/testAudioReader.py @@ -0,0 +1,85 @@ +import os + +# Check if environment is setup to run the tests +if os.environ.get('AVTRANSCODER_TEST_AUDIO_WAVE_FILE') is None: + from nose.plugins.skip import SkipTest + raise SkipTest("Need to define environment variables AVTRANSCODER_TEST_AUDIO_WAVE_FILE") + +from nose.tools import * + +from pyAvTranscoder import avtranscoder as av + + +def testAudioReaderCreateNewInputFile(): + """ + Read a audio stream with the AudioReader. + The InputFile is created inside the reader. + """ + inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] + reader = av.AudioReader(inputFileName) + + # read all frames and check their size + while True: + frame = reader.readNextFrame() + if not frame: + break + assert_greater(frame.getDataSize(), 0) + + # check if there is no next frame + frame = reader.readNextFrame() + assert_equals( reader.readNextFrame(), None ) + + +def testAudioReaderChannelsExtraction(): + """ + Read the same audio stream with several AudioReaders. + Compare decoded frames from reader of all channels, and of one channel. + """ + inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] + inputFile = av.InputFile(inputFileName) + streamIndex = inputFile.getProperties().getAudioProperties()[0].getStreamIndex() + channelIndex = 0 + + # create reader to read all channels of the audio stream + readerOfAllChannels = av.AudioReader(inputFile, streamIndex) + nbChannels = readerOfAllChannels.getOutputNbChannels() + # read first frame + frame = readerOfAllChannels.readNextFrame() + sizeOfFrameWithAllChannels = frame.getDataSize() + + # create reader to read one channel of the audio stream + readerOfOneChannel = av.AudioReader(inputFile, streamIndex, channelIndex) + # read first frame + frame = readerOfOneChannel.readNextFrame() + sizeOfFrameWithOneChannels = frame.getDataSize() + + assert_equals( sizeOfFrameWithAllChannels / nbChannels, sizeOfFrameWithOneChannels ) + + +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 + assert_greater(frame.getDataSize(), 0) + + # 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 = reader.readNextFrame() + # assuming we generate data of 1920 samples of 2 bytes + nbSamplesPerChannel = 1920 + bytesPerSample = 2 + assert_equals(frame.getDataSize(), reader.getOutputNbChannels() * nbSamplesPerChannel * bytesPerSample ) diff --git a/test/pyTest/testReader.py b/test/pyTest/testReader.py deleted file mode 100644 index 7c347c46..00000000 --- a/test/pyTest/testReader.py +++ /dev/null @@ -1,133 +0,0 @@ -import os - -# Check if environment is setup to run the tests -if os.environ.get('AVTRANSCODER_TEST_VIDEO_AVI_FILE') is None or \ - os.environ.get('AVTRANSCODER_TEST_AUDIO_WAVE_FILE') is None: - from nose.plugins.skip import SkipTest - raise SkipTest("Need to define environment variables " - "AVTRANSCODER_TEST_VIDEO_AVI_FILE and " - "AVTRANSCODER_TEST_AUDIO_WAVE_FILE") - -from nose.tools import * - -from pyAvTranscoder import avtranscoder as av - - -def testVideoReaderCreateNewInputFile(): - """ - Read a video stream with the VideoReader. - The InputFile is created inside the reader. - """ - 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 - frame = reader.readNextFrame() - assert_equals( reader.readNextFrame(), None ) - - -def testVideoReaderReferenceInputFile(): - """ - Read a video stream with the VideoReader. - The InputFile is a reference for the reader. - """ - inputFileName = os.environ['AVTRANSCODER_TEST_VIDEO_AVI_FILE'] - inputFile = av.InputFile(inputFileName) - reader = av.VideoReader(inputFile) - - # 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 ) - - -def testAudioReaderChannelsExtraction(): - """ - Read the same audio stream with several AudioReaders. - Compare decoded frames from reader of all channels, and of one channel. - """ - inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] - inputFile = av.InputFile(inputFileName) - streamIndex = inputFile.getProperties().getAudioProperties()[0].getStreamIndex() - channelIndex = 0 - - # create reader to read all channels of the audio stream - readerOfAllChannels = av.AudioReader(inputFile, streamIndex) - nbChannels = readerOfAllChannels.getOutputNbChannels() - # read first frame - frame = av.AudioFrame(readerOfAllChannels.readNextFrame()) - sizeOfFrameWithAllChannels = frame.getSize() - - # create reader to read one channel of the audio stream - readerOfOneChannel = av.AudioReader(inputFile, streamIndex, channelIndex) - # read first frame - frame = av.AudioFrame(readerOfOneChannel.readNextFrame()) - 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/testSetFrame.py b/test/pyTest/testSetFrame.py index ec04f562..b9428a5e 100644 --- a/test/pyTest/testSetFrame.py +++ b/test/pyTest/testSetFrame.py @@ -39,7 +39,7 @@ def testSetVideoFrame(): # set video frame frame = av.VideoFrame(av.VideoFrameDesc(1920, 1080, "rgb24")) - frame.assign(i) + frame.assignValue(i) videoDecoder.setNextFrame( frame ) # end process @@ -85,7 +85,7 @@ def testSetAudioFrame(): # set video frame frame = av.AudioFrame(av.AudioFrameDesc(44100, 1, "s16")) - frame.assign(i) + frame.assignValue(i) audioDecoder.setNextFrame( frame ) # end process diff --git a/test/pyTest/testTranscoderAdd.py b/test/pyTest/testTranscoderAdd.py index bb0efbc5..5edb9e8f 100644 --- a/test/pyTest/testTranscoderAdd.py +++ b/test/pyTest/testTranscoderAdd.py @@ -69,6 +69,25 @@ def testEmptyListOfInputs(): transcoder.addStream(inputs) +@raises(RuntimeError) +def testAddOneChannelWhichDoesNotExist(): + """ + Extract one audio channel from an input stream. + """ + inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] + outputFileName = "testAddOneChannelWhichDoesNotExist.wav" + + ouputFile = av.OutputFile(outputFileName) + transcoder = av.Transcoder(ouputFile) + + inputFile = av.InputFile(inputFileName) + src_audioStream = inputFile.getProperties().getAudioProperties()[0] + audioStreamIndex = src_audioStream.getStreamIndex() + transcoder.addStream(av.InputStreamDesc(inputFileName, audioStreamIndex, 15)) + + transcoder.process() + + @raises(RuntimeError) def testAllSeveralInputsWithDifferentType(): """ diff --git a/test/pyTest/testTranscoderTranscodeAudioWave.py b/test/pyTest/testTranscoderTranscodeAudioWave.py index acc1c108..de640613 100644 --- a/test/pyTest/testTranscoderTranscodeAudioWave.py +++ b/test/pyTest/testTranscoderTranscodeAudioWave.py @@ -1,15 +1,51 @@ import os # Check if environment is setup to run the tests -if os.environ.get('AVTRANSCODER_TEST_AUDIO_WAVE_FILE') is None: +if os.environ.get('AVTRANSCODER_TEST_AUDIO_WAVE_FILE') is None or \ + os.environ.get('AVTRANSCODER_TEST_AUDIO_WAVE_MONO_FILE') is None: from nose.plugins.skip import SkipTest - raise SkipTest("Need to define environment variable AVTRANSCODER_TEST_AUDIO_WAVE_FILE") + raise SkipTest("Need to define environment variables " + "AVTRANSCODER_TEST_AUDIO_WAVE_FILE and" + "AVTRANSCODER_TEST_AUDIO_WAVE_MONO_FILE") from nose.tools import * from pyAvTranscoder import avtranscoder as av +def testTranscodeExtractOneChannelFromMono(): + """ + Extract one audio channel from a stream of one channel. + """ + inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_MONO_FILE'] + outputFileName = "testTranscodeExtractOneChannelFromMono.wav" + + ouputFile = av.OutputFile(outputFileName) + transcoder = av.Transcoder(ouputFile) + + inputFile = av.InputFile(inputFileName) + src_audioStream = inputFile.getProperties().getAudioProperties()[0] + audioStreamIndex = src_audioStream.getStreamIndex() + transcoder.addStream(av.InputStreamDesc(inputFileName, audioStreamIndex, 0)) + + processStat = transcoder.process() + + # check process stat returned + audioStat = processStat.getAudioStat(0) + assert_equals(src_audioStream.getDuration(), audioStat.getDuration()) + + # get dst file of transcode + dst_inputFile = av.InputFile(outputFileName) + dst_properties = dst_inputFile.getProperties() + dst_audioStream = dst_properties.getAudioProperties()[0] + + assert_equals(dst_audioStream.getCodecName(), src_audioStream.getCodecName()) + assert_equals(dst_audioStream.getSampleFormatName(), src_audioStream.getSampleFormatName()) + assert_equals(dst_audioStream.getSampleFormatLongName(), src_audioStream.getSampleFormatLongName()) + assert_equals(dst_audioStream.getSampleRate(), src_audioStream.getSampleRate()) + assert_equals(dst_audioStream.getNbChannels(), 1) + + def testTranscodeWave24b48k5_1(): """ Transcode one audio stream (profile wave24b48k5_1). diff --git a/test/pyTest/testVideoFrame.py b/test/pyTest/testVideoFrame.py new file mode 100644 index 00000000..90e6edea --- /dev/null +++ b/test/pyTest/testVideoFrame.py @@ -0,0 +1,49 @@ +from nose.tools import * + +from pyAvTranscoder import avtranscoder as av + + +@raises(MemoryError) +def testInvalidVideoFrameAutoAllocated(): + """ + Try to create an invalid VideoFrame automatically allocated. + """ + desc = av.VideoFrameDesc(1920, 1080, "toto") + av.VideoFrame(desc) + + +@raises(MemoryError) +def testInvalidVideoFrameManualAllocated(): + """ + Try to create an invalid VideoFrame manually allocated. + """ + width = 1920 + height = 1080 + pixelFormat = "titi" + desc = av.VideoFrameDesc(width, height, pixelFormat) + frame = av.VideoFrame(desc, False) + + assert_equals(frame.isDataAllocated(), False) + assert_equals(frame.getDataSize(), 0) + assert_equals(frame.getWidth(), width) + assert_equals(frame.getHeight(), height) + assert_equals(av.getPixelFormatName(frame.getPixelFormat()), "") + + frame.allocateData() + +def testVideoFrame(): + """ + Check the size and the data buffer of a VideoFrame. + """ + width = 1920 + height = 1080 + pixelFormat = "yuv420p" + desc = av.VideoFrameDesc(width, height, pixelFormat) + frame = av.VideoFrame(desc) + + assert_equals(frame.isDataAllocated(), True) + assert_equals(frame.isVideoFrame(), True) + assert_equals(frame.getDataSize(), width * height * 1.5) + + frame.freeData() + assert_equals(frame.isDataAllocated(), False) diff --git a/test/pyTest/testVideoReader.py b/test/pyTest/testVideoReader.py new file mode 100644 index 00000000..b4e782a7 --- /dev/null +++ b/test/pyTest/testVideoReader.py @@ -0,0 +1,73 @@ +import os + +# Check if environment is setup to run the tests +if os.environ.get('AVTRANSCODER_TEST_VIDEO_AVI_FILE') is None: + from nose.plugins.skip import SkipTest + raise SkipTest("Need to define environment variables AVTRANSCODER_TEST_VIDEO_AVI_FILE") + +from nose.tools import * + +from pyAvTranscoder import avtranscoder as av + + +def testVideoReaderCreateNewInputFile(): + """ + Read a video stream with the VideoReader. + The InputFile is created inside the reader. + """ + 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 = reader.readNextFrame() + bytesPerPixel = reader.getOutputBitDepth() / 8 + assert_equals( frame.getDataSize(), reader.getOutputWidth() * reader.getOutputHeight() * bytesPerPixel ) + + # check if there is no next frame + frame = reader.readNextFrame() + assert_equals( reader.readNextFrame(), None ) + + +def testVideoReaderReferenceInputFile(): + """ + Read a video stream with the VideoReader. + The InputFile is a reference for the reader. + """ + inputFileName = os.environ['AVTRANSCODER_TEST_VIDEO_AVI_FILE'] + inputFile = av.InputFile(inputFileName) + reader = av.VideoReader(inputFile) + + # read all frames and check their size + for i in xrange(0, reader.getSourceVideoProperties().getNbFrames()): + frame = reader.readNextFrame() + bytesPerPixel = reader.getOutputBitDepth() / 8 + assert_equals( frame.getDataSize(), reader.getOutputWidth() * reader.getOutputHeight() * bytesPerPixel ) + + # check if there is no next frame + assert_equals( reader.readNextFrame(), None ) + + +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 = reader.readNextFrame() + bytesPerPixel = reader.getOutputBitDepth() / 8 + assert_equals( frame.getDataSize(), 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 = reader.readNextFrame() + bytesPerPixel = reader.getOutputBitDepth() / 8 + assert_equals( frame.getDataSize(), reader.getOutputWidth() * reader.getOutputHeight() * bytesPerPixel ) diff --git a/tools/appveyor/python.nosetests.bat b/tools/appveyor/python.nosetests.bat index 7deef392..eb2692a9 100755 --- a/tools/appveyor/python.nosetests.bat +++ b/tools/appveyor/python.nosetests.bat @@ -15,6 +15,7 @@ set AVTRANSCODER_TEST_VIDEO_AVI_FILE=%PWD%\avTranscoder-data/video\BigBuckBunny\ set AVTRANSCODER_TEST_VIDEO_MP4_FILE=%PWD%\avTranscoder-data\video\BigBuckBunny\BigBuckBunny_HD.mp4 set AVTRANSCODER_TEST_VIDEO_MOV_FILE=%PWD%\avTranscoder-data\video\BigBuckBunny\BigBuckBunny_640p_h264.mov set AVTRANSCODER_TEST_AUDIO_WAVE_FILE=%PWD%\avTranscoder-data\audio\frequenciesPerChannel.wav +set AVTRANSCODER_TEST_AUDIO_WAVE_MONO_FILE=%PWD%\avTranscoder-data\audio\frequenciesOneChannel.wav set AVTRANSCODER_TEST_AUDIO_MOV_FILE=%PWD%\avTranscoder-data\video\BigBuckBunny\BigBuckBunny_1080p_5_1.mov set AVTRANSCODER_TEST_IMAGE_PNG_FILE=%PWD%\avTranscoder-data\image\BigBuckBunny\bbb-splash.thumbnail.png set AVTRANSCODER_TEST_IMAGE_JPG_FILE=%PWD%\avTranscoder-data\image\BigBuckBunny\title_anouncement.thumbnail.jpg diff --git a/tools/travis/python.nosetests.sh b/tools/travis/python.nosetests.sh index 277a1dc6..d23c402e 100755 --- a/tools/travis/python.nosetests.sh +++ b/tools/travis/python.nosetests.sh @@ -14,6 +14,7 @@ export AVTRANSCODER_TEST_VIDEO_MP4_FILE=`pwd`/avTranscoder-data/video/BigBuckBun export AVTRANSCODER_TEST_VIDEO_MOV_FILE=`pwd`/avTranscoder-data/video/BigBuckBunny/BigBuckBunny_640p_h264.mov export AVTRANSCODER_TEST_VIDEO_RAW_FILE=`pwd`/avTranscoder-data/video/Intro/Intro_raw_1080p.h264 export AVTRANSCODER_TEST_AUDIO_WAVE_FILE=`pwd`/avTranscoder-data/audio/frequenciesPerChannel.wav +export AVTRANSCODER_TEST_AUDIO_WAVE_MONO_FILE=`pwd`/avTranscoder-data/audio/frequenciesOneChannel.wav export AVTRANSCODER_TEST_AUDIO_MOV_FILE=`pwd`/avTranscoder-data/video/BigBuckBunny/BigBuckBunny_1080p_5_1.mov export AVTRANSCODER_TEST_IMAGE_PNG_FILE=`pwd`/avTranscoder-data/image/BigBuckBunny/bbb-splash.thumbnail.png export AVTRANSCODER_TEST_IMAGE_JPG_FILE=`pwd`/avTranscoder-data/image/BigBuckBunny/title_anouncement.thumbnail.jpg