diff --git a/src/AvTranscoder/data/decoded/Frame.cpp b/src/AvTranscoder/data/decoded/Frame.cpp index 655776c9..69b15ff4 100644 --- a/src/AvTranscoder/data/decoded/Frame.cpp +++ b/src/AvTranscoder/data/decoded/Frame.cpp @@ -23,6 +23,15 @@ Frame::Frame(const Frame& otherFrame) 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) diff --git a/src/AvTranscoder/data/decoded/Frame.hpp b/src/AvTranscoder/data/decoded/Frame.hpp index 482c92ff..53d5ce00 100644 --- a/src/AvTranscoder/data/decoded/Frame.hpp +++ b/src/AvTranscoder/data/decoded/Frame.hpp @@ -22,10 +22,11 @@ class AvExport Frame */ Frame(); - /** - * @brief Copy properties and reference data of the other frame - */ + //@{ + // @brief Copy properties and reference data of the other frame. Frame(const Frame& otherFrame); + void operator=(const Frame& otherFrame); + //@} virtual ~Frame(); @@ -63,7 +64,8 @@ class AvExport Frame /** * @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 only by calling the destructor of the referenced 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); diff --git a/src/AvTranscoder/decoder/AudioDecoder.cpp b/src/AvTranscoder/decoder/AudioDecoder.cpp index 99d6a8a6..8d0490e7 100644 --- a/src/AvTranscoder/decoder/AudioDecoder.cpp +++ b/src/AvTranscoder/decoder/AudioDecoder.cpp @@ -78,6 +78,7 @@ void AudioDecoder::setupDecoder(const ProfileLoader::Profile& profile) bool AudioDecoder::decodeNextFrame(Frame& frameBuffer) { bool decodeNextFrame = false; + const size_t channelLayout = frameBuffer.getAVFrame().channel_layout; if(!_isSetup) setupDecoder(); @@ -100,6 +101,9 @@ bool AudioDecoder::decodeNextFrame(Frame& frameBuffer) throw std::runtime_error("An error occurred during audio decoding: " + getDescriptionFromErrorCode(ret)); } + // fixed channel layout value after decoding + frameBuffer.getAVFrame().channel_layout = channelLayout; + // if no frame could be decompressed if(!nextPacketRead && ret == 0 && got_frame == 0) decodeNextFrame = false; diff --git a/src/AvTranscoder/decoder/AudioGenerator.cpp b/src/AvTranscoder/decoder/AudioGenerator.cpp index f13b880f..29ec5dd8 100644 --- a/src/AvTranscoder/decoder/AudioGenerator.cpp +++ b/src/AvTranscoder/decoder/AudioGenerator.cpp @@ -22,31 +22,20 @@ AudioGenerator::~AudioGenerator() bool AudioGenerator::decodeNextFrame(Frame& frameBuffer) { // check the given frame - if(!frameBuffer.isAudioFrame()) + if(! frameBuffer.isAudioFrame()) { - LOG_WARN("The given frame is not a valid audio frame: allocate a new AVSample to put generated data into it."); + 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); } - // Check channel layout of the given frame to be able to copy audio data to it. - // @see Frame.copyData method - if(frameBuffer.getAVFrame().channel_layout == 0) - { - const size_t channelLayout = av_get_default_channel_layout(frameBuffer.getAVFrame().channels); - LOG_WARN("Channel layout en the audio frame is not set. Set it to '" << channelLayout - << "' to be able to copy silence data.") - av_frame_set_channel_layout(&frameBuffer.getAVFrame(), channelLayout); - } - // Generate silent if(!_inputFrame) { // Generate the silent only once if(!_silent) { - AudioFrame& audioBuffer = static_cast(frameBuffer); - _silent = new AudioFrame(audioBuffer.desc()); + _silent = new AudioFrame(_frameDesc); std::stringstream msg; msg << "Generate a silence with the following features:" << std::endl; @@ -61,7 +50,7 @@ bool AudioGenerator::decodeNextFrame(Frame& frameBuffer) _silent->setNbSamplesPerChannel(frameBuffer.getAVFrame().nb_samples); } LOG_DEBUG("Copy data of the silence when decode next frame") - frameBuffer.copyData(*_silent); + frameBuffer.refFrame(*_silent); } // Take audio frame from _inputFrame else diff --git a/src/AvTranscoder/decoder/AudioGenerator.hpp b/src/AvTranscoder/decoder/AudioGenerator.hpp index ba80ab69..0b1d51ec 100644 --- a/src/AvTranscoder/decoder/AudioGenerator.hpp +++ b/src/AvTranscoder/decoder/AudioGenerator.hpp @@ -26,7 +26,7 @@ class AvExport AudioGenerator : public IDecoder private: Frame* _inputFrame; ///< Has link (no ownership) AudioFrame* _silent; ///< The generated silent (has ownership) - const AudioFrameDesc _frameDesc; ///< The description of the silence (sampleRate, channels...) + const AudioFrameDesc _frameDesc; ///< The description of the given frame buffer when decoding. }; } diff --git a/src/AvTranscoder/decoder/VideoGenerator.cpp b/src/AvTranscoder/decoder/VideoGenerator.cpp index bdaf645f..d55db6df 100644 --- a/src/AvTranscoder/decoder/VideoGenerator.cpp +++ b/src/AvTranscoder/decoder/VideoGenerator.cpp @@ -1,7 +1,6 @@ #include "VideoGenerator.hpp" #include -#include #include @@ -12,6 +11,7 @@ VideoGenerator::VideoGenerator(const VideoFrameDesc& frameDesc) : _inputFrame(NULL) , _blackImage(NULL) , _frameDesc(frameDesc) + , _videoTransform() { } @@ -23,9 +23,9 @@ VideoGenerator::~VideoGenerator() bool VideoGenerator::decodeNextFrame(Frame& frameBuffer) { // check the given frame - if(!frameBuffer.isVideoFrame()) + if(! frameBuffer.isVideoFrame()) { - LOG_WARN("The given frame is not a valid video frame: allocate a new AVPicture to put generated data into it."); + 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); } @@ -36,32 +36,23 @@ bool VideoGenerator::decodeNextFrame(Frame& frameBuffer) // Generate the black image only once if(!_blackImage) { + // Create the black RGB image + VideoFrameDesc blackDesc(_frameDesc._width, _frameDesc._height, "rgb24"); + _blackImage = new VideoFrame(blackDesc); + const unsigned char fillChar = 0; + _blackImage->assign(fillChar); + std::stringstream msg; msg << "Generate a black image with the following features:" << std::endl; - msg << "width = " << _frameDesc._width << std::endl; - msg << "height = " << _frameDesc._height << std::endl; - msg << "pixel format = rgb24" << std::endl; + msg << "width = " << _blackImage->getWidth() << std::endl; + msg << "height = " << _blackImage->getHeight() << std::endl; + msg << "pixel format = " << getPixelFormatName(_blackImage->getPixelFormat()) << std::endl; LOG_INFO(msg.str()) - VideoFrame& imageBuffer = static_cast(frameBuffer); - - // Input of convert - // @todo support PAL (0 to 255) and NTFS (16 to 235) - VideoFrameDesc desc(_frameDesc); - desc._pixelFormat = getAVPixelFormat("rgb24"); - VideoFrame intermediateBuffer(desc); - const unsigned char fillChar = 0; - intermediateBuffer.assign(fillChar); - - // Output of convert - _blackImage = new VideoFrame(imageBuffer.desc()); - - // Convert and store the black image - VideoTransform videoTransform; - videoTransform.convert(intermediateBuffer, *_blackImage); } LOG_DEBUG("Copy data of the black image when decode next frame") - frameBuffer.copyData(*_blackImage); + // Convert the black image to the configuration of the given frame + _videoTransform.convert(*_blackImage, frameBuffer); } // Take image from _inputFrame else diff --git a/src/AvTranscoder/decoder/VideoGenerator.hpp b/src/AvTranscoder/decoder/VideoGenerator.hpp index c9053047..47796daa 100644 --- a/src/AvTranscoder/decoder/VideoGenerator.hpp +++ b/src/AvTranscoder/decoder/VideoGenerator.hpp @@ -3,6 +3,7 @@ #include "IDecoder.hpp" #include +#include namespace avtranscoder { @@ -15,18 +16,22 @@ class AvExport VideoGenerator : public IDecoder public: VideoGenerator(const VideoFrameDesc& frameDesc); - ~VideoGenerator(); bool decodeNextFrame(Frame& frameBuffer); bool decodeNextFrame(Frame& frameBuffer, const std::vector channelIndexArray); + /** + * @brief Force to return this frame when calling the decoding methods. + * @param inputFrame: should have the same properties as the given frames when decoding. + */ void setNextFrame(Frame& inputFrame) { _inputFrame = &inputFrame; } private: Frame* _inputFrame; ///< A frame given from outside (has link, no ownership) - VideoFrame* _blackImage; ///< The generated black image (has ownership) - const VideoFrameDesc _frameDesc; ///< The description of the black image (width, height...) + 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/filter/FilterGraph.cpp b/src/AvTranscoder/filter/FilterGraph.cpp index 8cd40d5d..c629c81d 100644 --- a/src/AvTranscoder/filter/FilterGraph.cpp +++ b/src/AvTranscoder/filter/FilterGraph.cpp @@ -35,30 +35,35 @@ FilterGraph::~FilterGraph() avfilter_graph_free(&_graph); } -void FilterGraph::process(Frame& frame) +void FilterGraph::process(const std::vector& inputs, Frame& output) { if(!hasFilters()) { - LOG_DEBUG("No filter to process.") + 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(frame); + init(inputs, output); - // setup source frame - int ret = av_buffersrc_write_frame(_filters.at(0)->getAVFilterContext(), &frame.getAVFrame()); - if(ret < 0) + // setup input frames + for(size_t index = 0; index < inputs.size(); ++index) { - throw std::runtime_error("Error when adding a frame to the source buffer used to start to process filters: " + - getDescriptionFromErrorCode(ret)); + const int ret = av_buffersrc_write_frame(_filters.at(index)->getAVFilterContext(), &inputs.at(index)->getAVFrame()); + if(ret < 0) + { + throw std::runtime_error("Error when adding a frame to the source buffer used to start to process filters: " + + getDescriptionFromErrorCode(ret)); + } } // pull filtered data from the filter graph for(;;) { - ret = av_buffersink_get_frame(_filters.at(_filters.size() - 1)->getAVFilterContext(), &frame.getAVFrame()); + const int ret = av_buffersink_get_frame(_filters.at(_filters.size() - 1)->getAVFilterContext(), &output.getAVFrame()); if(ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) break; if(ret < 0) @@ -77,22 +82,31 @@ Filter& FilterGraph::addFilter(const std::string& filterName, const std::string& return *_filters.back(); } -void FilterGraph::init(const Frame& frame) +void FilterGraph::init(const std::vector& inputs, Frame& output) { // push filters to the graph - pushInBuffer(frame); - for(size_t i = 1; i < _filters.size(); ++i) + addInBuffer(inputs); + addOutBuffer(output); + for(size_t i = 0; i < _filters.size(); ++i) { pushFilter(*_filters.at(i)); } - pushOutBuffer(frame); // connect filters for(size_t index = 0; index < _filters.size() - 1; ++index) { - LOG_INFO("Connect filter " << _filters.at(index)->getName() << " to filter " << _filters.at(index + 1)->getName()) + size_t indexOfOutputFilterToConnect = index + 1; + size_t indexOfInputPadOfDestinationFilter = 0; + // handle cases with several inputs + if(index < inputs.size()) + { + indexOfOutputFilterToConnect = inputs.size(); + indexOfInputPadOfDestinationFilter = index; + } + + LOG_INFO("Connect filter " << _filters.at(index)->getName() << " to filter " << _filters.at(indexOfOutputFilterToConnect)->getName()) const int err = - avfilter_link(_filters.at(index)->getAVFilterContext(), 0, _filters.at(index + 1)->getAVFilterContext(), 0); + avfilter_link(_filters.at(index)->getAVFilterContext(), 0, _filters.at(indexOfOutputFilterToConnect)->getAVFilterContext(), indexOfInputPadOfDestinationFilter); if(err < 0) { throw std::runtime_error("Error when connecting filters."); @@ -128,57 +142,58 @@ void FilterGraph::pushFilter(Filter& filter) } } -void FilterGraph::pushInBuffer(const Frame& frame) +void FilterGraph::addInBuffer(const std::vector& inputs) { - std::string filterName; - std::stringstream filterOptions; - // audio frame - if(frame.isAudioFrame()) + for(std::vector::const_reverse_iterator it = inputs.rbegin(); it != inputs.rend(); ++it) { - filterName = "abuffer"; - const AudioFrame& audioFrame = dynamic_cast(frame); - filterOptions << "time_base=" << _codec.getAVCodecContext().time_base.num << "/" - << _codec.getAVCodecContext().time_base.den << ":"; - filterOptions << "sample_rate=" << audioFrame.getSampleRate() << ":"; - filterOptions << "sample_fmt=" << getSampleFormatName(audioFrame.getSampleFormat()) << ":"; - filterOptions << "channel_layout=0x" << std::hex << audioFrame.getChannelLayout(); - } - // video frame - else if(frame.isVideoFrame()) - { - filterName = "buffer"; - const VideoFrame& videoFrame = dynamic_cast(frame); - filterOptions << "video_size=" << videoFrame.getWidth() << "x" << videoFrame.getHeight() << ":"; - filterOptions << "pix_fmt=" << getPixelFormatName(videoFrame.getPixelFormat()) << ":"; - filterOptions << "time_base=" << _codec.getAVCodecContext().time_base.num << "/" - << _codec.getAVCodecContext().time_base.den << ":"; - filterOptions << "pixel_aspect=" << _codec.getAVCodecContext().sample_aspect_ratio.num << "/" - << _codec.getAVCodecContext().sample_aspect_ratio.den; + std::string filterName; + std::stringstream filterOptions; + // audio frame + if((*it)->isAudioFrame()) + { + filterName = "abuffer"; + const AudioFrame* audioFrame = dynamic_cast(*it); + filterOptions << "time_base=" << _codec.getAVCodecContext().time_base.num << "/" + << _codec.getAVCodecContext().time_base.den << ":"; + filterOptions << "sample_rate=" << audioFrame->getSampleRate() << ":"; + filterOptions << "sample_fmt=" << getSampleFormatName(audioFrame->getSampleFormat()) << ":"; + filterOptions << "channel_layout=0x" << std::hex << audioFrame->getChannelLayout(); + } + // video frame + else if((*it)->isVideoFrame()) + { + filterName = "buffer"; + const VideoFrame* videoFrame = dynamic_cast(*it); + filterOptions << "video_size=" << videoFrame->getWidth() << "x" << videoFrame->getHeight() << ":"; + filterOptions << "pix_fmt=" << getPixelFormatName(videoFrame->getPixelFormat()) << ":"; + filterOptions << "time_base=" << _codec.getAVCodecContext().time_base.num << "/" + << _codec.getAVCodecContext().time_base.den << ":"; + filterOptions << "pixel_aspect=" << _codec.getAVCodecContext().sample_aspect_ratio.num << "/" + << _codec.getAVCodecContext().sample_aspect_ratio.den; + } + // invalid frame + else + 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); } - // invalid frame - else - 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); - pushFilter(*in); } -void FilterGraph::pushOutBuffer(const Frame& frame) +void FilterGraph::addOutBuffer(const Frame& output) { std::string filterName; - if(frame.isAudioFrame()) + if(output.isAudioFrame()) filterName = "abuffersink"; - else if(frame.isVideoFrame()) + else if(output.isVideoFrame()) filterName = "buffersink"; else throw std::runtime_error("Cannot create output buffer of filter graph: the given frame is invalid."); // add out buffer - Filter& out = addFilter(filterName, "", "out"); - pushFilter(out); + addFilter(filterName, "", "out"); } } diff --git a/src/AvTranscoder/filter/FilterGraph.hpp b/src/AvTranscoder/filter/FilterGraph.hpp index bd42f979..4ae9274c 100644 --- a/src/AvTranscoder/filter/FilterGraph.hpp +++ b/src/AvTranscoder/filter/FilterGraph.hpp @@ -15,8 +15,6 @@ namespace avtranscoder /** * @brief Manager of filters. - * @warning Currently, the class manages only filters which has one input and one output. - * @note See 'complex graph' definition in ffmpeg documentation. **/ class AvExport FilterGraph { @@ -44,10 +42,18 @@ class AvExport FilterGraph /** * @brief Pull filtered data from the filter graph, and put result to the given frame. - * @param frame: both input and output data of the method. + * @param inputs: input data buffers (at least one). + * @param output: output data buffer. * @note Do nothing if there was no filter added. + * If there is one input buffer, the filter graph is a chain of effects: input -> filter 1 -> filter 2 -> output. + * If there is several input buffers, the filter graph is like this: + * input 1 ---| + * | + * filter 1 -> filter 2 -> output + * | + * input 2 ---| */ - void process(Frame& frame); + void process(const std::vector& inputs, Frame& output); private: /** @@ -61,7 +67,7 @@ class AvExport FilterGraph * @see pushInBuffer * @see pushOutBuffer */ - void init(const Frame& frame); + void init(const std::vector& inputs, Frame& output); /** * @brief Push the given Filter to the graph. @@ -69,9 +75,9 @@ class AvExport FilterGraph void pushFilter(Filter& filter); ///@{ - /// @brief Push the input and output buffer at the beginning and the end of the graph. - void pushInBuffer(const Frame& frame); - void pushOutBuffer(const Frame& frame); + /// @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); //@} private: diff --git a/src/AvTranscoder/profile/ProfileLoader.cpp b/src/AvTranscoder/profile/ProfileLoader.cpp index d73fe252..c9d7e2d3 100644 --- a/src/AvTranscoder/profile/ProfileLoader.cpp +++ b/src/AvTranscoder/profile/ProfileLoader.cpp @@ -12,10 +12,10 @@ namespace avtranscoder ProfileLoader::ProfileLoader(const bool autoload) { if(autoload) - loadProfiles(); + addProfiles(); } -void ProfileLoader::loadProfile(const std::string& avProfileFileName) +void ProfileLoader::addProfile(const std::string& avProfileFileName) { std::ifstream infile; infile.open(avProfileFileName.c_str(), std::ifstream::in); @@ -30,10 +30,10 @@ void ProfileLoader::loadProfile(const std::string& avProfileFileName) if(keyValue.size() == 2) customProfile[keyValue.at(0)] = keyValue.at(1); } - loadProfile(customProfile); + addProfile(customProfile); } -void ProfileLoader::loadProfiles(const std::string& avProfilesPath) +void ProfileLoader::addProfiles(const std::string& avProfilesPath) { std::string realAvProfilesPath = avProfilesPath; if(realAvProfilesPath.empty()) @@ -59,7 +59,7 @@ void ProfileLoader::loadProfiles(const std::string& avProfilesPath) const std::string absPath = (*dirIt) + "/" + (*fileIt); try { - loadProfile(absPath); + addProfile(absPath); } catch(const std::exception& e) { @@ -69,7 +69,7 @@ void ProfileLoader::loadProfiles(const std::string& avProfilesPath) } } -void ProfileLoader::loadProfile(const Profile& profile) +void ProfileLoader::addProfile(const Profile& profile) { // check profile identificator if(!profile.count(constants::avProfileIdentificator)) diff --git a/src/AvTranscoder/profile/ProfileLoader.hpp b/src/AvTranscoder/profile/ProfileLoader.hpp index 6670e69e..fb87ca87 100644 --- a/src/AvTranscoder/profile/ProfileLoader.hpp +++ b/src/AvTranscoder/profile/ProfileLoader.hpp @@ -45,21 +45,21 @@ class AvExport ProfileLoader ProfileLoader(const bool autoload = true); /** - * @brief Load profiles from files in avProfilesPath directory + * @brief Add profiles from files in avProfilesPath directory * @param avProfilesPath: if empty, the path is replaced by value of AVPROFILES environment variable */ - void loadProfiles(const std::string& avProfilesPath = ""); + void addProfiles(const std::string& avProfilesPath = ""); /** - * @brief Load the profile defines in the given file + * @brief Add the profile defines in the given file */ - void loadProfile(const std::string& avProfileFileName); + void addProfile(const std::string& avProfileFileName); /** - * @brief Load the given profile + * @brief Add the given profile * @exception throw std::runtime_error if the profile is invalid */ - void loadProfile(const Profile& profile); + void addProfile(const Profile& profile); bool hasProfile(const Profile& profile) const; diff --git a/src/AvTranscoder/properties/AudioProperties.cpp b/src/AvTranscoder/properties/AudioProperties.cpp index 5fbb9daf..2ec52828 100644 --- a/src/AvTranscoder/properties/AudioProperties.cpp +++ b/src/AvTranscoder/properties/AudioProperties.cpp @@ -73,7 +73,7 @@ std::string AudioProperties::getChannelLayout() const throw std::runtime_error("unknown codec context"); char buf1[512]; - av_get_channel_layout_string(buf1, sizeof(buf1), -1, _codecContext->channel_layout); + av_get_channel_layout_string(buf1, sizeof(buf1), getNbChannels(), _codecContext->channel_layout); return std::string(buf1); } diff --git a/src/AvTranscoder/properties/PixelProperties.cpp b/src/AvTranscoder/properties/PixelProperties.cpp index bef52cd4..90428d5d 100644 --- a/src/AvTranscoder/properties/PixelProperties.cpp +++ b/src/AvTranscoder/properties/PixelProperties.cpp @@ -39,7 +39,7 @@ std::string PixelProperties::getPixelName() const std::string PixelProperties::getPixelFormatName() const { - if(!_pixelFormat) + if(_pixelFormat == AV_PIX_FMT_NONE) throw std::runtime_error("unable to find pixel format."); const char* formatName = av_get_pix_fmt_name(_pixelFormat); diff --git a/src/AvTranscoder/reader/AudioReader.cpp b/src/AvTranscoder/reader/AudioReader.cpp index b7b487c4..ea154447 100644 --- a/src/AvTranscoder/reader/AudioReader.cpp +++ b/src/AvTranscoder/reader/AudioReader.cpp @@ -44,12 +44,6 @@ void AudioReader::init() _decoder->setupDecoder(); _currentDecoder = _decoder; - // generator - _generator = new AudioGenerator(_inputFile->getStream(_streamIndex).getAudioCodec().getAudioFrameDesc()); - - // create transform - _transform = new AudioTransform(); - // create src frame _srcFrame = new AudioFrame(_inputFile->getStream(_streamIndex).getAudioCodec().getAudioFrameDesc()); AudioFrame* srcFrame = static_cast(_srcFrame); @@ -57,6 +51,12 @@ void AudioReader::init() _outputSampleRate = srcFrame->getSampleRate(); _outputNbChannels = (_channelIndex == -1) ? srcFrame->getNbChannels() : 1; _dstFrame = new AudioFrame(AudioFrameDesc(_outputSampleRate, _outputNbChannels, _outputSampleFormat)); + + // generator + _generator = new AudioGenerator(srcFrame->desc()); + + // create transform + _transform = new AudioTransform(); } AudioReader::~AudioReader() diff --git a/src/AvTranscoder/reader/VideoReader.cpp b/src/AvTranscoder/reader/VideoReader.cpp index 304a5d22..98feff89 100644 --- a/src/AvTranscoder/reader/VideoReader.cpp +++ b/src/AvTranscoder/reader/VideoReader.cpp @@ -43,12 +43,6 @@ void VideoReader::init() _decoder->setupDecoder(); _currentDecoder = _decoder; - // generator - _generator = new VideoGenerator(_inputFile->getStream(_streamIndex).getVideoCodec().getVideoFrameDesc()); - - // create transform - _transform = new VideoTransform(); - // create src frame _srcFrame = new VideoFrame(_inputFile->getStream(_streamIndex).getVideoCodec().getVideoFrameDesc()); VideoFrame* srcFrame = static_cast(_srcFrame); @@ -56,6 +50,12 @@ void VideoReader::init() _outputWidth = srcFrame->getWidth(); _outputHeight = srcFrame->getHeight(); _dstFrame = new VideoFrame(VideoFrameDesc(_outputWidth, _outputHeight, getOutputPixelFormat())); + + // generator + _generator = new VideoGenerator(srcFrame->desc()); + + // create transform + _transform = new VideoTransform(); } VideoReader::~VideoReader() diff --git a/src/AvTranscoder/transcoder/StreamTranscoder.cpp b/src/AvTranscoder/transcoder/StreamTranscoder.cpp index a6878bc3..9b72ec49 100644 --- a/src/AvTranscoder/transcoder/StreamTranscoder.cpp +++ b/src/AvTranscoder/transcoder/StreamTranscoder.cpp @@ -15,58 +15,63 @@ #include #include #include +#include namespace avtranscoder { StreamTranscoder::StreamTranscoder(IInputStream& inputStream, IOutputFile& outputFile, const float offset) - : _inputStream(&inputStream) + : _inputStreamDesc() + , _inputStreams() , _outputStream(NULL) - , _sourceBuffer(NULL) - , _frameBuffer(NULL) - , _inputDecoder(NULL) - , _generator(NULL) + , _decodedData() + , _filteredData(NULL) + , _transformedData(NULL) + , _inputDecoders() + , _generators() , _currentDecoder(NULL) , _outputEncoder(NULL) , _transform(NULL) , _filterGraph(NULL) - , _inputStreamDesc() , _offset(offset) , _needToSwitchToGenerator(false) { + _inputStreams.push_back(&inputStream); + // create a re-wrapping case - switch(_inputStream->getProperties().getStreamType()) + switch(inputStream.getProperties().getStreamType()) { case AVMEDIA_TYPE_VIDEO: { // output stream - _outputStream = &outputFile.addVideoStream(_inputStream->getVideoCodec()); + _outputStream = &outputFile.addVideoStream(inputStream.getVideoCodec()); try { // filter - _filterGraph = new FilterGraph(_inputStream->getVideoCodec()); + _filterGraph = new FilterGraph(inputStream.getVideoCodec()); - VideoFrameDesc inputFrameDesc(_inputStream->getVideoCodec().getVideoFrameDesc()); + VideoFrameDesc inputFrameDesc(inputStream.getVideoCodec().getVideoFrameDesc()); // generator decoder - _generator = new VideoGenerator(inputFrameDesc); + _generators.push_back(new VideoGenerator(inputFrameDesc)); // buffers to process - _sourceBuffer = new VideoFrame(inputFrameDesc); - _frameBuffer = new VideoFrame(inputFrameDesc); + _decodedData.push_back(new VideoFrame(inputFrameDesc)); + _filteredData = new VideoFrame(inputFrameDesc); + _transformedData = new VideoFrame(inputFrameDesc); // transform _transform = new VideoTransform(); // output encoder - VideoEncoder* outputVideo = new VideoEncoder(_inputStream->getVideoCodec().getCodecName()); + VideoEncoder* outputVideo = new VideoEncoder(inputStream.getVideoCodec().getCodecName()); outputVideo->setupVideoEncoder(inputFrameDesc); _outputEncoder = outputVideo; } catch(std::runtime_error& e) { - LOG_WARN("Cannot create the video encoder for stream " << _inputStream->getStreamIndex() << " if needed. " + LOG_WARN("Cannot create the video encoder for stream " << inputStream.getStreamIndex() << " if needed. " << e.what()) } @@ -75,34 +80,35 @@ StreamTranscoder::StreamTranscoder(IInputStream& inputStream, IOutputFile& outpu case AVMEDIA_TYPE_AUDIO: { // output stream - _outputStream = &outputFile.addAudioStream(_inputStream->getAudioCodec()); + _outputStream = &outputFile.addAudioStream(inputStream.getAudioCodec()); try { // filter - _filterGraph = new FilterGraph(_inputStream->getAudioCodec()); + _filterGraph = new FilterGraph(inputStream.getAudioCodec()); - AudioFrameDesc inputFrameDesc(_inputStream->getAudioCodec().getAudioFrameDesc()); + AudioFrameDesc inputFrameDesc(inputStream.getAudioCodec().getAudioFrameDesc()); // generator decoder - _generator = new AudioGenerator(inputFrameDesc); + _generators.push_back(new AudioGenerator(inputFrameDesc)); // buffers to process - _sourceBuffer = new AudioFrame(inputFrameDesc); - _frameBuffer = new AudioFrame(inputFrameDesc); + _decodedData.push_back(new AudioFrame(inputFrameDesc)); + _filteredData = new AudioFrame(inputFrameDesc); + _transformedData = new AudioFrame(inputFrameDesc); // transform _transform = new AudioTransform(); // output encoder - AudioEncoder* outputAudio = new AudioEncoder(_inputStream->getAudioCodec().getCodecName()); + AudioEncoder* outputAudio = new AudioEncoder(inputStream.getAudioCodec().getCodecName()); outputAudio->setupAudioEncoder(inputFrameDesc); _outputEncoder = outputAudio; } catch(std::runtime_error& e) { - LOG_WARN("Cannot create the audio encoder for stream " << _inputStream->getStreamIndex() << " if needed. " + LOG_WARN("Cannot create the audio encoder for stream " << inputStream.getStreamIndex() << " if needed. " << e.what()) } @@ -111,7 +117,7 @@ StreamTranscoder::StreamTranscoder(IInputStream& inputStream, IOutputFile& outpu case AVMEDIA_TYPE_DATA: { // @warning: rewrap a data stream can't be lengthen by a generator (end of rewrapping will end the all process) - _outputStream = &outputFile.addDataStream(_inputStream->getDataCodec()); + _outputStream = &outputFile.addDataStream(inputStream.getDataCodec()); break; } default: @@ -120,41 +126,47 @@ StreamTranscoder::StreamTranscoder(IInputStream& inputStream, IOutputFile& outpu setOffset(offset); } -StreamTranscoder::StreamTranscoder(const InputStreamDesc& inputStreamDesc, IInputStream& inputStream, IOutputFile& outputFile, +StreamTranscoder::StreamTranscoder(const std::vector& inputStreamsDesc, std::vector& inputStreams, IOutputFile& outputFile, const ProfileLoader::Profile& profile, const float offset) - : _inputStream(&inputStream) + : _inputStreamDesc(inputStreamsDesc) + , _inputStreams(inputStreams) , _outputStream(NULL) - , _sourceBuffer(NULL) - , _frameBuffer(NULL) - , _inputDecoder(NULL) - , _generator(NULL) + , _decodedData() + , _filteredData(NULL) + , _transformedData(NULL) + , _inputDecoders() + , _generators() , _currentDecoder(NULL) , _outputEncoder(NULL) , _transform(NULL) , _filterGraph(NULL) - , _inputStreamDesc(inputStreamDesc) , _offset(offset) , _needToSwitchToGenerator(false) { + // add as many decoders as input streams + size_t nbOutputChannels = 0; + for(size_t index = 0; index < inputStreams.size(); ++index) + { + addDecoder(_inputStreamDesc.at(index), *_inputStreams.at(index)); + nbOutputChannels += _inputStreamDesc.at(index)._channelIndexArray.size(); + } + + IInputStream& inputStream = *_inputStreams.at(0); + const InputStreamDesc& inputStreamDesc = inputStreamsDesc.at(0); + // create a transcode case - switch(_inputStream->getProperties().getStreamType()) + switch(inputStream.getProperties().getStreamType()) { case AVMEDIA_TYPE_VIDEO: { // filter - _filterGraph = new FilterGraph(_inputStream->getVideoCodec()); - - // input decoder - VideoDecoder* inputVideo = new VideoDecoder(*static_cast(_inputStream)); - inputVideo->setupDecoder(); - _inputDecoder = inputVideo; - _currentDecoder = _inputDecoder; + _filterGraph = new FilterGraph(inputStream.getVideoCodec()); // output encoder VideoEncoder* outputVideo = new VideoEncoder(profile.at(constants::avProfileCodec)); _outputEncoder = outputVideo; - VideoFrameDesc outputFrameDesc = _inputStream->getVideoCodec().getVideoFrameDesc(); + VideoFrameDesc outputFrameDesc = inputStream.getVideoCodec().getVideoFrameDesc(); outputFrameDesc.setParameters(profile); outputVideo->setupVideoEncoder(outputFrameDesc, profile); @@ -162,55 +174,50 @@ StreamTranscoder::StreamTranscoder(const InputStreamDesc& inputStreamDesc, IInpu _outputStream = &outputFile.addVideoStream(outputVideo->getVideoCodec()); // buffers to process - _sourceBuffer = new VideoFrame(_inputStream->getVideoCodec().getVideoFrameDesc()); - _frameBuffer = new VideoFrame(outputVideo->getVideoCodec().getVideoFrameDesc()); + _filteredData = new VideoFrame(outputVideo->getVideoCodec().getVideoFrameDesc()); + _transformedData = new VideoFrame(outputVideo->getVideoCodec().getVideoFrameDesc()); // transform _transform = new VideoTransform(); - // generator decoder - _generator = new VideoGenerator(outputVideo->getVideoCodec().getVideoFrameDesc()); - break; } case AVMEDIA_TYPE_AUDIO: { // filter - _filterGraph = new FilterGraph(_inputStream->getAudioCodec()); - - // input decoder - AudioDecoder* inputAudio = new AudioDecoder(*static_cast(_inputStream)); - inputAudio->setupDecoder(); - _inputDecoder = inputAudio; - _currentDecoder = _inputDecoder; + _filterGraph = new FilterGraph(inputStream.getAudioCodec()); + // merge two or more audio streams into a single multi-channel stream. + if(inputStreams.size() > 1) + { + std::stringstream mergeOptions; + mergeOptions << "inputs=" << inputStreams.size(); + _filterGraph->addFilter("amerge", mergeOptions.str()); + } // output encoder AudioEncoder* outputAudio = new AudioEncoder(profile.at(constants::avProfileCodec)); _outputEncoder = outputAudio; - AudioFrameDesc outputFrameDesc(_inputStream->getAudioCodec().getAudioFrameDesc()); + AudioFrameDesc outputFrameDesc(inputStream.getAudioCodec().getAudioFrameDesc()); outputFrameDesc.setParameters(profile); - if(_inputStreamDesc.demultiplexing()) - outputFrameDesc._nbChannels = _inputStreamDesc._channelIndexArray.size(); + if(inputStreamDesc.demultiplexing()) + outputFrameDesc._nbChannels = nbOutputChannels; outputAudio->setupAudioEncoder(outputFrameDesc, profile); // output stream _outputStream = &outputFile.addAudioStream(outputAudio->getAudioCodec()); // buffers to process - AudioFrameDesc inputFrameDesc(_inputStream->getAudioCodec().getAudioFrameDesc()); - if(_inputStreamDesc.demultiplexing()) - inputFrameDesc._nbChannels = _inputStreamDesc._channelIndexArray.size(); + AudioFrameDesc inputFrameDesc(inputStream.getAudioCodec().getAudioFrameDesc()); + if(inputStreamDesc.demultiplexing()) + inputFrameDesc._nbChannels = nbOutputChannels; - _sourceBuffer = new AudioFrame(inputFrameDesc); - _frameBuffer = new AudioFrame(outputAudio->getAudioCodec().getAudioFrameDesc()); + _filteredData = new AudioFrame(inputFrameDesc); + _transformedData = new AudioFrame(outputAudio->getAudioCodec().getAudioFrameDesc()); // transform _transform = new AudioTransform(); - // generator decoder - _generator = new AudioGenerator(outputFrameDesc); - break; } default: @@ -222,18 +229,68 @@ StreamTranscoder::StreamTranscoder(const InputStreamDesc& inputStreamDesc, IInpu setOffset(offset); } +void StreamTranscoder::addDecoder(const InputStreamDesc& inputStreamDesc, IInputStream& inputStream) +{ + // create a transcode case + switch(inputStream.getProperties().getStreamType()) + { + case AVMEDIA_TYPE_VIDEO: + { + // corresponding input decoder + VideoDecoder* inputVideo = new VideoDecoder(static_cast(inputStream)); + inputVideo->setupDecoder(); + _inputDecoders.push_back(inputVideo); + _currentDecoder = inputVideo; + + // buffers to get the decoded data + VideoFrame* inputFrame = new VideoFrame(inputStream.getVideoCodec().getVideoFrameDesc()); + _decodedData.push_back(inputFrame); + + // generator decoder + _generators.push_back(new VideoGenerator(inputFrame->desc())); + + break; + } + case AVMEDIA_TYPE_AUDIO: + { + // corresponding input decoder + AudioDecoder* inputAudio = new AudioDecoder(static_cast(inputStream)); + inputAudio->setupDecoder(); + _inputDecoders.push_back(inputAudio); + _currentDecoder = inputAudio; + + // buffers to get the decoded data + AudioFrameDesc inputFrameDesc(inputStream.getAudioCodec().getAudioFrameDesc()); + if(inputStreamDesc.demultiplexing()) + inputFrameDesc._nbChannels = inputStreamDesc._channelIndexArray.size(); + _decodedData.push_back(new AudioFrame(inputFrameDesc)); + + // generator decoder + _generators.push_back(new AudioGenerator(inputFrameDesc)); + + break; + } + default: + { + throw std::runtime_error("Unupported stream type"); + break; + } + } +} + StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader::Profile& profile) - : _inputStream(NULL) + : _inputStreamDesc() + , _inputStreams() , _outputStream(NULL) - , _sourceBuffer(NULL) - , _frameBuffer(NULL) - , _inputDecoder(NULL) - , _generator(NULL) + , _decodedData() + , _filteredData(NULL) + , _transformedData(NULL) + , _inputDecoders() + , _generators() , _currentDecoder(NULL) , _outputEncoder(NULL) , _transform(NULL) , _filterGraph(NULL) - , _inputStreamDesc() , _offset(0) , _needToSwitchToGenerator(false) { @@ -245,8 +302,9 @@ StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader: inputVideoCodec.setImageParameters(inputFrameDesc); // generator decoder - _generator = new VideoGenerator(inputVideoCodec.getVideoFrameDesc()); - _currentDecoder = _generator; + VideoGenerator* generator = new VideoGenerator(inputFrameDesc); + _generators.push_back(generator); + _currentDecoder = generator; // filter _filterGraph = new FilterGraph(inputVideoCodec); @@ -254,8 +312,9 @@ StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader: // buffers to process VideoFrameDesc outputFrameDesc = inputFrameDesc; outputFrameDesc.setParameters(profile); - _sourceBuffer = new VideoFrame(inputFrameDesc); - _frameBuffer = new VideoFrame(outputFrameDesc); + _decodedData.push_back(new VideoFrame(inputFrameDesc)); + _filteredData = new VideoFrame(inputFrameDesc); + _transformedData = new VideoFrame(outputFrameDesc); // transform _transform = new VideoTransform(); @@ -276,8 +335,9 @@ StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader: inputAudioCodec.setAudioParameters(inputFrameDesc); // generator decoder - _generator = new AudioGenerator(inputAudioCodec.getAudioFrameDesc()); - _currentDecoder = _generator; + AudioGenerator* generator = new AudioGenerator(inputFrameDesc); + _generators.push_back(generator); + _currentDecoder = generator; // filter _filterGraph = new FilterGraph(inputAudioCodec); @@ -285,8 +345,9 @@ StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader: // buffers to process AudioFrameDesc outputFrameDesc = inputFrameDesc; outputFrameDesc.setParameters(profile); - _sourceBuffer = new AudioFrame(inputFrameDesc); - _frameBuffer = new AudioFrame(outputFrameDesc); + _decodedData.push_back(new AudioFrame(inputFrameDesc)); + _filteredData = new AudioFrame(inputFrameDesc); + _transformedData = new AudioFrame(outputFrameDesc); // transform _transform = new AudioTransform(); @@ -307,13 +368,25 @@ StreamTranscoder::StreamTranscoder(IOutputFile& outputFile, const ProfileLoader: StreamTranscoder::~StreamTranscoder() { - delete _sourceBuffer; - delete _frameBuffer; - delete _generator; + for(std::vector::iterator it = _decodedData.begin(); it != _decodedData.end(); ++it) + { + delete(*it); + } + delete _filteredData; + delete _transformedData; + + for(std::vector::iterator it = _inputDecoders.begin(); it != _inputDecoders.end(); ++it) + { + delete(*it); + } + for(std::vector::iterator it = _generators.begin(); it != _generators.end(); ++it) + { + delete(*it); + } + delete _outputEncoder; delete _transform; delete _filterGraph; - delete _inputDecoder; } void StreamTranscoder::preProcessCodecLatency() @@ -321,13 +394,8 @@ void StreamTranscoder::preProcessCodecLatency() if(!_outputEncoder) { std::stringstream msg; - msg << "No encoder found for input stream "; - if(getProcessCase() == eProcessCaseGenerator) - msg << "generator"; - else - msg << _inputStream->getStreamIndex(); - msg << ": will not preProcessCodecLatency."; - LOG_WARN(msg.str()) + msg << "No encoder found: will not preProcessCodecLatency."; + LOG_INFO(msg.str()) return; } @@ -380,7 +448,7 @@ bool StreamTranscoder::processFrame() { LOG_INFO("End of positive offset") - if(_inputDecoder) + if(! _inputDecoders.empty()) switchToInputDecoder(); else _currentDecoder = NULL; @@ -389,14 +457,17 @@ bool StreamTranscoder::processFrame() else { // process generator - if(_currentDecoder != _generator) + if(_currentDecoder != _generators.at(0)) switchToGeneratorDecoder(); } } else if(_offset < 0) { - const bool endOfStream = - _outputStream->getStreamDuration() >= (_inputStream->getProperties().getDuration() + _offset); + bool endOfStream = false; + for(size_t index = 0; index < _inputStreams.size(); ++index) + { + endOfStream = endOfStream && _outputStream->getStreamDuration() >= (_inputStreams.at(index)->getProperties().getDuration() + _offset); + } if(endOfStream) { LOG_INFO("End of negative offset") @@ -414,20 +485,13 @@ bool StreamTranscoder::processFrame() bool StreamTranscoder::processRewrap() { - assert(_inputStream != NULL); + assert(_inputStreams.size() == 1); assert(_outputStream != NULL); - assert(_inputDecoder == NULL); LOG_DEBUG("StreamTranscoder::processRewrap") - // if switched to generator, process frame - if(_currentDecoder && _currentDecoder == _generator) - { - return processTranscode(); - } - CodedData data; - if(!_inputStream->readNextPacket(data)) + if(! _inputStreams.at(0)->readNextPacket(data)) { if(_needToSwitchToGenerator) { @@ -457,30 +521,37 @@ bool StreamTranscoder::processTranscode() assert(_outputStream != NULL); assert(_currentDecoder != NULL); assert(_outputEncoder != NULL); - assert(_sourceBuffer != NULL); - assert(_frameBuffer != NULL); + assert(! _decodedData.empty()); assert(_transform != NULL); LOG_DEBUG("StreamTranscoder::processTranscode") LOG_DEBUG("Decode next frame") - bool decodingStatus = false; - if(_inputStreamDesc.demultiplexing()) - decodingStatus = _currentDecoder->decodeNextFrame(*_sourceBuffer, _inputStreamDesc._channelIndexArray); - else - decodingStatus = _currentDecoder->decodeNextFrame(*_sourceBuffer); + bool decodingStatus = true; + for(size_t index = 0; index < _generators.size(); ++index) + { + if(getProcessCase() == eProcessCaseTranscode) + _currentDecoder = _inputDecoders.at(index); + else + _currentDecoder = _generators.at(index); + + if(! _inputStreamDesc.empty() && _inputStreamDesc.at(index).demultiplexing()) + decodingStatus = decodingStatus && _currentDecoder->decodeNextFrame(*_decodedData.at(index), _inputStreamDesc.at(index)._channelIndexArray); + else + decodingStatus = decodingStatus && _currentDecoder->decodeNextFrame(*_decodedData.at(index)); + } CodedData data; if(decodingStatus) { LOG_DEBUG("Filtering") - _filterGraph->process(*_sourceBuffer); + _filterGraph->process(_decodedData, *_filteredData); LOG_DEBUG("Convert") - _transform->convert(*_sourceBuffer, *_frameBuffer); + _transform->convert(*_filteredData, *_transformedData); LOG_DEBUG("Encode") - _outputEncoder->encodeFrame(*_frameBuffer, data); + _outputEncoder->encodeFrame(*_transformedData, data); } else { @@ -516,7 +587,7 @@ void StreamTranscoder::switchToGeneratorDecoder() { LOG_INFO("Switch to generator decoder") - _currentDecoder = _generator; + _currentDecoder = _generators.at(0); assert(_currentDecoder != NULL); } @@ -524,19 +595,25 @@ void StreamTranscoder::switchToInputDecoder() { LOG_INFO("Switch to input decoder") - _currentDecoder = _inputDecoder; + _currentDecoder = _inputDecoders.at(0); assert(_currentDecoder != NULL); } float StreamTranscoder::getDuration() const { - if(_inputStream) + if(! _inputStreams.empty()) { - const StreamProperties& streamProperties = _inputStream->getProperties(); - const float totalDuration = streamProperties.getDuration() + _offset; + float minStreamDuration = -1; + for(size_t index = 0; index < _inputStreams.size(); ++index) + { + const StreamProperties& streamProperties = _inputStreams.at(index)->getProperties(); + if(minStreamDuration == -1 || streamProperties.getDuration() < minStreamDuration) + minStreamDuration = streamProperties.getDuration(); + } + const float totalDuration = minStreamDuration + _offset; if(totalDuration < 0) { - LOG_WARN("Offset of " << _offset << "s applied to a stream with a duration of " << streamProperties.getDuration() + LOG_WARN("Offset of " << _offset << "s applied to a stream with a duration of " << minStreamDuration << "s. Set its duration to 0s.") return 0.; } @@ -549,7 +626,7 @@ float StreamTranscoder::getDuration() const bool StreamTranscoder::canSwitchToGenerator() { - if(_sourceBuffer && _frameBuffer && _generator && _outputEncoder && _transform) + if(! _decodedData.empty() && ! _generators.empty() && _outputEncoder && _transform) return true; return false; } @@ -559,7 +636,7 @@ void StreamTranscoder::needToSwitchToGenerator(const bool needToSwitch) if(needToSwitch && !canSwitchToGenerator()) { std::stringstream os; - os << "The stream at index " << _inputStream->getStreamIndex() << " has a duration of " << getDuration() << "s."; + os << "The stream has a duration of " << getDuration() << "s."; os << " It needs to switch to a generator during the process, but it cannot. "; throw std::runtime_error(os.str()); } @@ -575,9 +652,9 @@ void StreamTranscoder::setOffset(const float offset) StreamTranscoder::EProcessCase StreamTranscoder::getProcessCase() const { - if(_inputStream && _inputDecoder && _currentDecoder == _inputDecoder) + if(! _inputStreams.empty() && ! _inputDecoders.empty() && std::find(_inputDecoders.begin(), _inputDecoders.end(), _currentDecoder) != _inputDecoders.end() ) return eProcessCaseTranscode; - else if(_inputStream && !_inputDecoder && !_currentDecoder) + else if(! _inputStreams.empty() && _inputDecoders.empty() && !_currentDecoder) return eProcessCaseRewrap; else return eProcessCaseGenerator; diff --git a/src/AvTranscoder/transcoder/StreamTranscoder.hpp b/src/AvTranscoder/transcoder/StreamTranscoder.hpp index bfc1fad3..4dceff17 100644 --- a/src/AvTranscoder/transcoder/StreamTranscoder.hpp +++ b/src/AvTranscoder/transcoder/StreamTranscoder.hpp @@ -33,9 +33,10 @@ class AvExport StreamTranscoder StreamTranscoder(IInputStream& inputStream, IOutputFile& outputFile, const float offset = 0); /** - * @brief Transcode the given stream. + * @brief Transcode the given streams. + * @note The data are wrapped to one output stream. **/ - StreamTranscoder(const InputStreamDesc& inputStreamDesc, IInputStream& inputStream, IOutputFile& outputFile, + StreamTranscoder(const std::vector& inputStreamsDesc, std::vector& inputStreams, IOutputFile& outputFile, const ProfileLoader::Profile& profile, const float offset = 0); /** @@ -59,13 +60,13 @@ class AvExport StreamTranscoder bool processFrame(); //@{ - /** Switch decoder */ + // Switch current decoder. void switchToGeneratorDecoder(); void switchToInputDecoder(); //@} /** - * @brief Get the total duration (in seconds), ie. duration of the stream and the offset applies + * @brief Get the total duration (in seconds), ie. duration of the shortest input stream and the offset applies * @note if it's a generated stream, return limit of double. * @note if offset > duration of the stream, return 0 */ @@ -83,7 +84,7 @@ class AvExport StreamTranscoder FilterGraph* getFilterGraph() const { return _filterGraph; } /// Returns a pointer to the stream which unwraps data - IInputStream* getInputStream() const { return _inputStream; } + std::vector getInputStreams() const { return _inputStreams; } /// Returns a reference to the stream which wraps data IOutputStream& getOutputStream() const { return *_outputStream; } @@ -118,18 +119,27 @@ class AvExport StreamTranscoder //@} private: + /** + * @brief Create the decoder (and the other related objects needed) which decodes the given input stream. + * @param inputStreamDesc + * @param inputStream + */ + void addDecoder(const InputStreamDesc& inputStreamDesc, IInputStream& inputStream); + bool processRewrap(); bool processTranscode(); private: - IInputStream* _inputStream; ///< Input stream to read next packet (has link, no ownership) + std::vector _inputStreamDesc; ///< Description of the data to extract from the input stream. + 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) - Frame* _sourceBuffer; ///< Has ownership - Frame* _frameBuffer; ///< Has 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). - IDecoder* _inputDecoder; ///< Decoder of packets read from _inputStream (has ownership) - IDecoder* _generator; ///< Generator of audio or video packets (has ownership) + std::vector _inputDecoders; ///< Decoders of packets read from _inputStream (has ownership) + std::vector _generators; ///< Generators of audio or video packets (has ownership) IDecoder* _currentDecoder; ///< Link to _inputDecoder or _generator IEncoder* _outputEncoder; ///< Encoder of packets which will be wrapped by _outputStream (has ownership) @@ -137,8 +147,6 @@ class AvExport StreamTranscoder FilterGraph* _filterGraph; ///< Filter graph (has ownership) - const InputStreamDesc _inputStreamDesc; ///< Description of the data to extract from the input stream. - float _offset; ///< Offset, in seconds, at the beginning of the StreamTranscoder. bool _needToSwitchToGenerator; ///< Set if need to switch to a generator during the process (because, of other streams diff --git a/src/AvTranscoder/transcoder/Transcoder.cpp b/src/AvTranscoder/transcoder/Transcoder.cpp index f07ef225..fe9d1829 100644 --- a/src/AvTranscoder/transcoder/Transcoder.cpp +++ b/src/AvTranscoder/transcoder/Transcoder.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -38,17 +39,20 @@ Transcoder::~Transcoder() void Transcoder::addStream(const InputStreamDesc& inputStreamDesc, const std::string& profileName, const float offset) { // Check filename - if(inputStreamDesc._filename.length() == 0) + if(inputStreamDesc._filename.empty()) throw std::runtime_error("Can't process a stream without a filename indicated."); - if(profileName.length() == 0) + if(profileName.empty()) { // Re-wrap if(!inputStreamDesc.demultiplexing()) addRewrapStream(inputStreamDesc, offset); // Transcode (transparent for the user) else - addTranscodeStream(inputStreamDesc, offset); + { + const ProfileLoader::Profile profile = getProfileFromInput(inputStreamDesc); + addStream(inputStreamDesc, profile, offset); + } } // Transcode else @@ -64,7 +68,41 @@ void Transcoder::addStream(const InputStreamDesc& inputStreamDesc, const Profile if(!inputStreamDesc._filename.length()) throw std::runtime_error("Can't transcode a stream without a filename indicated."); - addTranscodeStream(inputStreamDesc, profile, offset); + std::vector inputStreamDescArray; + inputStreamDescArray.push_back(inputStreamDesc); + addStream(inputStreamDescArray, profile, offset); +} + +void Transcoder::addStream(const std::vector& inputStreamDescArray, const std::string& profileName, const float offset) +{ + // Check number of inputs + if(inputStreamDescArray.empty()) + throw std::runtime_error("Need a description of at least one input stream to start the process."); + + // If there is one input, switch to an easier case + if(inputStreamDescArray.size() == 1) + { + addStream(inputStreamDescArray.at(0), profileName, offset); + return; + } + + // Get encoding profile + ProfileLoader::Profile encodingProfile; + if(profileName.empty()) + encodingProfile = getProfileFromInputs(inputStreamDescArray); + else + encodingProfile = _profileLoader.getProfile(profileName); + + addStream(inputStreamDescArray, encodingProfile, offset); +} + +void Transcoder::addStream(const std::vector& inputStreamDescArray, const ProfileLoader::Profile& profile, const float offset) +{ + // Check number of inputs + if(inputStreamDescArray.empty()) + throw std::runtime_error("Need a description of at least one input stream to start the process."); + + addTranscodeStream(inputStreamDescArray, profile, offset); } void Transcoder::addGenerateStream(const std::string& encodingProfileName) @@ -77,7 +115,7 @@ void Transcoder::addGenerateStream(const ProfileLoader::Profile& encodingProfile { // Add profile if(!_profileLoader.hasProfile(encodingProfile)) - _profileLoader.loadProfile(encodingProfile); + _profileLoader.addProfile(encodingProfile); LOG_INFO("Add generated stream with encodingProfile=" << encodingProfile.at(constants::avProfileIdentificatorHuman)) @@ -112,7 +150,7 @@ bool Transcoder::processFrame() // if a stream failed to process if(!_streamTranscoders.at(streamIndex)->processFrame()) { - LOG_WARN("Failed to process stream at index " << streamIndex) + LOG_WARN("Failed to process the stream transcoder at index " << streamIndex) // if this is the end of the main stream if(streamIndex == _mainStreamIndex) { @@ -203,57 +241,40 @@ void Transcoder::addRewrapStream(const InputStreamDesc& inputStreamDesc, const f _streamTranscoders.push_back(_streamTranscodersAllocated.back()); } -void Transcoder::addTranscodeStream(const InputStreamDesc& inputStreamDesc, const float offset) -{ - // Get profile from input file - InputFile inputFile(inputStreamDesc._filename); - ProfileLoader::Profile profile = getProfileFromFile(inputFile, inputStreamDesc._streamIndex); - - // override number of channels parameters to manage demultiplexing - if(inputStreamDesc.demultiplexing()) - { - // number of channels - std::stringstream ss; - ss << inputStreamDesc._channelIndexArray.size(); - profile[constants::avProfileChannel] = ss.str(); - } - - addTranscodeStream(inputStreamDesc, profile, offset); -} - -void Transcoder::addTranscodeStream(const InputStreamDesc& inputStreamDesc, const ProfileLoader::Profile& profile, +void Transcoder::addTranscodeStream(const std::vector& inputStreamDescArray, const ProfileLoader::Profile& profile, const float offset) { // Add profile if(!_profileLoader.hasProfile(profile)) - _profileLoader.loadProfile(profile); + _profileLoader.addProfile(profile); - LOG_INFO("Add transcode stream from " << inputStreamDesc << "with encodingProfile=" - << profile.at(constants::avProfileIdentificatorHuman) << std::endl + std::stringstream sources; + for(size_t index = 0; index < inputStreamDescArray.size(); ++index) + sources << inputStreamDescArray.at(index); + LOG_INFO("Add transcode stream from the following inputs:" << std::endl << sources.str() + << "with encodingProfile=" << profile.at(constants::avProfileIdentificatorHuman) << std::endl << "and offset=" << offset << "s") - // Add input file - InputFile* referenceFile = addInputFile(inputStreamDesc._filename, inputStreamDesc._streamIndex, offset); - IInputStream& inputStream = referenceFile->getStream(inputStreamDesc._streamIndex); - - switch(inputStream.getProperties().getStreamType()) + // Create all streams from the given inputs + std::vector inputStreams; + AVMediaType commonStreamType = AVMEDIA_TYPE_UNKNOWN; + for(std::vector::const_iterator it = inputStreamDescArray.begin(); it != inputStreamDescArray.end(); ++it) { - case AVMEDIA_TYPE_VIDEO: - case AVMEDIA_TYPE_AUDIO: - { - _streamTranscodersAllocated.push_back( - new StreamTranscoder(inputStreamDesc, inputStream, _outputFile, profile, offset)); - _streamTranscoders.push_back(_streamTranscodersAllocated.back()); - break; - } - case AVMEDIA_TYPE_DATA: - case AVMEDIA_TYPE_SUBTITLE: - case AVMEDIA_TYPE_ATTACHMENT: - default: - { - throw std::runtime_error("unsupported media type in transcode setup"); - } + InputFile* referenceFile = addInputFile(it->_filename, it->_streamIndex, offset); + inputStreams.push_back(&referenceFile->getStream(it->_streamIndex)); + + // Check stream type + const AVMediaType currentStreamType = referenceFile->getProperties().getStreamPropertiesWithIndex(it->_streamIndex).getStreamType(); + if(commonStreamType == AVMEDIA_TYPE_UNKNOWN) + commonStreamType = currentStreamType; + else if(currentStreamType != commonStreamType) + throw std::runtime_error("All the given inputs should be of the same type (video, audio...)."); + } + + _streamTranscodersAllocated.push_back( + new StreamTranscoder(inputStreamDescArray, inputStreams, _outputFile, profile, offset)); + _streamTranscoders.push_back(_streamTranscodersAllocated.back()); } InputFile* Transcoder::addInputFile(const std::string& filename, const int streamIndex, const float offset) @@ -296,12 +317,25 @@ InputFile* Transcoder::addInputFile(const std::string& filename, const int strea return referenceFile; } -ProfileLoader::Profile Transcoder::getProfileFromFile(InputFile& inputFile, const size_t streamIndex) +ProfileLoader::Profile Transcoder::getProfileFromInput(const InputStreamDesc& inputStreamDesc) +{ + std::vector inputStreamDescArray; + inputStreamDescArray.push_back(inputStreamDesc); + return getProfileFromInputs(inputStreamDescArray); +} + +ProfileLoader::Profile Transcoder::getProfileFromInputs(const std::vector& inputStreamDescArray) { - const StreamProperties* streamProperties = &inputFile.getProperties().getStreamPropertiesWithIndex(streamIndex); + assert(inputStreamDescArray.size() >= 1); + + // Get properties from the first input + const InputStreamDesc& inputStreamDesc = inputStreamDescArray.at(0); + InputFile inputFile(inputStreamDesc._filename); + + const StreamProperties* streamProperties = &inputFile.getProperties().getStreamPropertiesWithIndex(inputStreamDesc._streamIndex); const VideoProperties* videoProperties = NULL; const AudioProperties* audioProperties = NULL; - switch(inputFile.getStream(streamIndex).getProperties().getStreamType()) + switch(inputFile.getStream(inputStreamDesc._streamIndex).getProperties().getStreamType()) { case AVMEDIA_TYPE_VIDEO: { @@ -331,8 +365,10 @@ ProfileLoader::Profile Transcoder::getProfileFromFile(InputFile& inputFile, cons std::stringstream ss; ss << videoProperties->getFps(); profile[constants::avProfileFrameRate] = ss.str(); - profile[constants::avProfileWidth] = videoProperties->getWidth(); - profile[constants::avProfileHeight] = videoProperties->getHeight(); + ss.str(""); ss << videoProperties->getWidth(); + profile[constants::avProfileWidth] = ss.str(); + ss.str(""); ss << videoProperties->getHeight(); + profile[constants::avProfileHeight] = ss.str(); } // audio else if(audioProperties != NULL) @@ -344,7 +380,22 @@ ProfileLoader::Profile Transcoder::getProfileFromFile(InputFile& inputFile, cons ss << audioProperties->getSampleRate(); profile[constants::avProfileSampleRate] = ss.str(); ss.str(""); - ss << audioProperties->getNbChannels(); + // override number of channels parameters to manage demultiplexing + size_t nbChannels = 0; + for(std::vector::const_iterator it = inputStreamDescArray.begin(); it != inputStreamDescArray.end(); ++it) + { + if(inputStreamDesc.demultiplexing()) + nbChannels += it->_channelIndexArray.size(); + else + { + InputFile inputFile(it->_filename); + const StreamProperties& currentStream = inputFile.getProperties().getStreamPropertiesWithIndex(inputStreamDesc._streamIndex); + if(currentStream.getStreamType() != AVMEDIA_TYPE_AUDIO) + throw std::runtime_error("All the given inputs should be audio streams."); + nbChannels += dynamic_cast(currentStream).getNbChannels(); + } + } + ss << nbChannels; profile[constants::avProfileChannel] = ss.str(); } @@ -463,12 +514,12 @@ void Transcoder::fillProcessStat(ProcessStat& processStat) for(size_t streamIndex = 0; streamIndex < _streamTranscoders.size(); ++streamIndex) { IOutputStream& stream = _streamTranscoders.at(streamIndex)->getOutputStream(); - const IInputStream* inputStream = _streamTranscoders.at(streamIndex)->getInputStream(); - if(inputStream == NULL) + if(_streamTranscoders.at(streamIndex)->getInputStreams().empty()) { LOG_WARN("Cannot process statistics of generated stream.") continue; } + const IInputStream* inputStream = _streamTranscoders.at(streamIndex)->getInputStreams().at(0); const AVMediaType mediaType = inputStream->getProperties().getStreamType(); switch(mediaType) { diff --git a/src/AvTranscoder/transcoder/Transcoder.hpp b/src/AvTranscoder/transcoder/Transcoder.hpp index 9c4ee023..986ce45c 100644 --- a/src/AvTranscoder/transcoder/Transcoder.hpp +++ b/src/AvTranscoder/transcoder/Transcoder.hpp @@ -65,6 +65,14 @@ class AvExport Transcoder void addStream(const InputStreamDesc& inputStreamDesc, const ProfileLoader::Profile& profile, const float offset = 0); //@} + //@{ + // @brief Add a new stream to the output file, created from the given input description to process. + // @param inputStreamDescArray: the type of the described streams should be of the same type. + // @param profile: if empty, get the profile from the inputs. + void addStream(const std::vector& inputStreamDescArray, const std::string& profileName = "", float offset = 0); + void addStream(const std::vector& inputStreamDescArray, const ProfileLoader::Profile& profile, const float offset = 0); + //@} + //@{ // @brief Add a new generated stream to the output file, created from the given encoding profile. void addGenerateStream(const std::string& encodingProfileName); @@ -129,9 +137,7 @@ class AvExport Transcoder private: void addRewrapStream(const InputStreamDesc& inputStreamDesc, const float offset); - - void addTranscodeStream(const InputStreamDesc& inputStreamDesc, const float offset); - void addTranscodeStream(const InputStreamDesc& inputStreamDesc, const ProfileLoader::Profile& profile, + void addTranscodeStream(const std::vector& inputStreamDescArray, const ProfileLoader::Profile& profile, const float offset = 0); /** @@ -139,8 +145,11 @@ class AvExport Transcoder */ InputFile* addInputFile(const std::string& filename, const int streamIndex, const float offset); - ProfileLoader::Profile getProfileFromFile(InputFile& inputFile, - const size_t streamIndex); ///< The function analyses the inputFile + /** + * @return The profile from the given inputs. + */ + ProfileLoader::Profile getProfileFromInput(const InputStreamDesc& inputStreamDesc); + ProfileLoader::Profile getProfileFromInputs(const std::vector& inputStreamDescArray); /** * @brief Get the duration of the stream, in seconds diff --git a/src/AvTranscoder/transcoder/transcoder.i b/src/AvTranscoder/transcoder/transcoder.i index 229cd227..5288dcad 100644 --- a/src/AvTranscoder/transcoder/transcoder.i +++ b/src/AvTranscoder/transcoder/transcoder.i @@ -5,6 +5,7 @@ %} %template(StreamTranscoderVector) std::vector< avtranscoder::StreamTranscoder* >; +%template(InputStreamDescVector) std::vector< avtranscoder::InputStreamDesc >; %include %include diff --git a/test/pyTest/testTranscoderAdd.py b/test/pyTest/testTranscoderAdd.py index 2c359aa4..bb0efbc5 100644 --- a/test/pyTest/testTranscoderAdd.py +++ b/test/pyTest/testTranscoderAdd.py @@ -35,7 +35,7 @@ def testAddStreamTranscoder(): @raises(IOError) -def testAddAllStreamsOfFileWhichDoesNotExist(): +def testAddAStreamFromAFileWhichDoesNotExist(): """ Add all streams from a given file. """ @@ -43,7 +43,7 @@ def testAddAllStreamsOfFileWhichDoesNotExist(): inputFileName = "fileWhichDoesNotExist.mov" # output - outputFileName = "testAddAllStreamsOfFileWhichDoesNotExist.mov" + outputFileName = "testAddAStreamFromAFileWhichDoesNotExist.mov" ouputFile = av.OutputFile( outputFileName ) transcoder = av.Transcoder( ouputFile ) @@ -51,3 +51,77 @@ def testAddAllStreamsOfFileWhichDoesNotExist(): # process transcoder.process() + + +@raises(RuntimeError) +def testEmptyListOfInputs(): + """ + Add an empty list of inputs. + """ + # inputs + inputs = av.InputStreamDescVector() + + # output + outputFileName = "testEmptyListOfInputs.mov" + ouputFile = av.OutputFile(outputFileName) + + transcoder = av.Transcoder(ouputFile) + transcoder.addStream(inputs) + + +@raises(RuntimeError) +def testAllSeveralInputsWithDifferentType(): + """ + Add one video and one audio to create one output stream. + """ + # inputs + inputs = av.InputStreamDescVector() + inputs.append(av.InputStreamDesc(os.environ['AVTRANSCODER_TEST_AUDIO_MOV_FILE'], 0)) + inputs.append(av.InputStreamDesc(os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'], 0)) + + # output + outputFileName = "testAllSeveralInputsWithDifferentType.mov" + ouputFile = av.OutputFile(outputFileName) + + transcoder = av.Transcoder(ouputFile) + transcoder.addStream(inputs) + + +def testAddSeveralInputsToCreateOneOutput(): + """ + Add several audio inputs and create one output stream. + """ + # inputs + inputs = av.InputStreamDescVector() + inputFileName = os.environ['AVTRANSCODER_TEST_AUDIO_WAVE_FILE'] + inputFile = av.InputFile(inputFileName) + src_audioStream = inputFile.getProperties().getAudioProperties()[0] + src_audioStreamIndex = src_audioStream.getStreamIndex() + inputs.append(av.InputStreamDesc(inputFileName, src_audioStreamIndex, (0,1))) + inputs.append(av.InputStreamDesc(inputFileName, src_audioStreamIndex, (2,3))) + inputs.append(av.InputStreamDesc(inputFileName, src_audioStreamIndex, (4,5))) + + # output + outputFileName = "testAddSeveralInputsToCreateOneOutput.mov" + ouputFile = av.OutputFile(outputFileName) + + transcoder = av.Transcoder(ouputFile) + transcoder.addStream(inputs) + + # process + 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( src_audioStream.getCodecName(), dst_audioStream.getCodecName() ) + assert_equals( src_audioStream.getSampleFormatName(), dst_audioStream.getSampleFormatName() ) + assert_equals( src_audioStream.getSampleFormatLongName(), dst_audioStream.getSampleFormatLongName() ) + assert_equals( src_audioStream.getSampleRate(), dst_audioStream.getSampleRate() ) + assert_equals( src_audioStream.getNbChannels(), dst_audioStream.getNbChannels() ) diff --git a/test/pyTest/testTranscoderGenerateStream.py b/test/pyTest/testTranscoderGenerateStream.py index 9f55f78c..191aa722 100644 --- a/test/pyTest/testTranscoderGenerateStream.py +++ b/test/pyTest/testTranscoderGenerateStream.py @@ -13,8 +13,7 @@ def testTranscodeNoStream(): ouputFile = av.OutputFile( outputFileName ) transcoder = av.Transcoder( ouputFile ) - progress = av.NoDisplayProgress() - transcoder.process( progress ) + transcoder.process() @raises(RuntimeError)