/* The copyright in this software is being made available under the BSD * License, included below. This software may be subject to other third party * and contributor rights, including patent rights, and no such rights are * granted under this license. * * Copyright (c) 2010-2017, ITU/ISO/IEC * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "GuessLambdaModifiers.h" #include #include #include namespace { /// Formatted input for a bitrate vector /// \param left The input stream that contains the bitrate vector /// \param right The vector to be written to /// \pre right must be empty void parseBitrateVector( std::istream& left, std::vector< double >& right ) { assert( right.empty( ) ); for( ; ; ) { assert( left.good( ) ); double bitrate; left >> bitrate; if( left.fail( ) ) { break; } if( bitrate <= ( double )0.0 ) { left.setstate( std::istream::failbit ); } else { right.push_back( bitrate ); } if( !left.good( ) ) { break; } if( left.peek( ) == ' ' ) { left.ignore( ); } else { break; } } } /// Makes a next guess for a single Lambda-modifier based on only one previous guess /// \param initialAdjustmentParameter The proportionality to use between the target bitrate and the previous guess /// \param target The target bitrate value that this Lambda-modifier is trying to reach /// \param previousPoint The previous guess /// \return The Lambda-modifier guess /// \pre The given point must contain only positive non-zero values double incrementLambdaModifier( double initialAdjustmentParameter, double targetBitrate, const Point& previousPoint ) { assert( ( double )0.0 < previousPoint.lambdaModifier ); assert( ( double )0.0 < previousPoint.bitrate ); double extrapolated( previousPoint.lambdaModifier * targetBitrate / previousPoint.bitrate ); return previousPoint.lambdaModifier + initialAdjustmentParameter * ( extrapolated - previousPoint.lambdaModifier ); } } double polateLambdaModifier( double targetBitrate, const Point& point1, const Point& point2 ) { assert( 0.0 < point1.lambdaModifier ); assert( 0.0 < point2.lambdaModifier ); assert( 0.0 < point1.bitrate ); assert( 0.0 < point2.bitrate ); assert( point1.lambdaModifier != point2.lambdaModifier ); assert( point1.bitrate != point2.bitrate ); // Calculate and return the result double denominator( point1.bitrate - point2.bitrate ); double result( point1.lambdaModifier + ( point1.lambdaModifier - point2.lambdaModifier ) / denominator * ( targetBitrate - point1.bitrate ) ); return result; } double guessLambdaModifier( double initialAdjustmentParameter, double targetBitrate, const std::list< Point >& pointList, double interDampeningFactor ) { assert( ( double )0.0 < interDampeningFactor ); assert( interDampeningFactor <= ( double )1.0 ); assert( !pointList.empty( ) ); double preliminaryResult; if( 1 == pointList.size( ) ) // If there is only one prevous point, then we cannot interpolate, so we call incrementLambdaModifier { preliminaryResult = incrementLambdaModifier( initialAdjustmentParameter, targetBitrate, pointList.back( ) ); } else // If there are at least two previous points, then we may be able to interpolate { std::list< Point >::const_reverse_iterator i( pointList.rbegin( ) ); Point point1 = *i; ++i; Point point2 = *i; // If the slope is either horizontal or vertical, we cannot interpolate if( point1.lambdaModifier == point2.lambdaModifier || point1.bitrate == point2.bitrate ) { preliminaryResult = incrementLambdaModifier( initialAdjustmentParameter, targetBitrate, pointList.back( ) ); } else // If the slope is not horizontal and not vertical, we can interpolate { preliminaryResult = polateLambdaModifier( targetBitrate, point1, point2 ); } } double previousResult( pointList.back( ).lambdaModifier ); // Apply "intra dampening" { double intermediate( std::log( ( double )1.0 + std::abs( preliminaryResult - previousResult ) / previousResult ) ); assert( ( double )0.0 <= intermediate ); if( ( preliminaryResult - previousResult ) < 0.0 ) { preliminaryResult = previousResult * ( ( double )1.0 - intermediate ); } else { preliminaryResult = previousResult * ( ( double )1.0 + intermediate ); } } // Apply "inter dampening factor". If necessary, reduce the factor until a positive result is acheived. double result; do { result = previousResult + interDampeningFactor * ( preliminaryResult - previousResult ); interDampeningFactor /= ( double )2.0; } while( result <= ( double )0.0 ); return result; } namespace { /// Extracts a single point at the given index from a full meta-log entry Point pointFromFullMetaLogEntry( unsigned char index, const MetaLogEntry< std::vector< double > >& fullEntry ) { Point result; result.lambdaModifier = fullEntry.lambdaModifiers[ index ]; result.bitrate = fullEntry.bitrateVector[ index ]; return result; } /// Calculates the inter dampening factor based /// \param parameter The inter dampening parameter which determines how severely the inter dampening factor is affected by Lambda-modifier changes at previous temporal layers /// \param cumulativeDelta The sum of the percentage changes of the Lambda-modifiers at the previous temporal layers /// \return The calculated inter dampening factor /// \pre cumulativeDelta must be non-negative /// \pre parameter must be non-negative double interDampeningFactor( double parameter, double cumulativeDelta ) { assert( 0.0 <= cumulativeDelta ); assert( 0.0 <= parameter ); return ( double )1.0 / ( parameter * cumulativeDelta + ( double )1.0 ); } } std::vector< double > guessLambdaModifiers( double initialAdjustmentParameter, const std::vector< double > &targetBitrateVector, const std::list< MetaLogEntry< std::vector< double > > >& metaLogEntryList ) { assert( !targetBitrateVector.empty( ) ); assert( !metaLogEntryList.empty( ) ); double cumulativeDelta( 0.0 ); std::vector< double > resultVector; for( unsigned char i( 0 ); i < targetBitrateVector.size( ); ++i ) { // Populate pointList with up to two of the previous points std::list< Point > pointList; std::list< MetaLogEntry< std::vector< double > > >::const_reverse_iterator j( metaLogEntryList.rbegin( ) ); pointList.push_front( pointFromFullMetaLogEntry( i, *j ) ); ++j; if( j != metaLogEntryList.rend( ) ) { pointList.push_front( pointFromFullMetaLogEntry( i, *j ) ); } // Calculate the new Lambda-modifier guess and add it to the result vector const double newLambdaModifier( guessLambdaModifier( initialAdjustmentParameter, targetBitrateVector[ i ], // target bitrate pointList, interDampeningFactor( 50.0, cumulativeDelta ) ) ); resultVector.push_back( newLambdaModifier ); // Increment the cumulativeDelta const double oldLambdaModifier( pointList.back( ).lambdaModifier ); cumulativeDelta += std::abs( newLambdaModifier - oldLambdaModifier ) / oldLambdaModifier; } return resultVector; } namespace { /// Ignores all of the the characters up to and including a given character /// \param i The active input stream /// \param character The character to ignore up to /// \throw MetaLogParseException if the stream goes bad before character is encountered or just after character is encountered void ignoreUpTo( std::istream& i, char character ) { while( i.good( ) && character != i.get( ) ) ; if( !i.good( ) ) { throw MetaLogParseException( ); } } /// Parses a Lambda-modifier map /// \param right The map to write the output to void parseLambdaModifierMap( std::istream& left, std::map< unsigned char, double >& right ) { for( ; ; ) { assert( left.good( ) ); // Ignore the "-LM" if( '-' != left.get( ) ) { left.setstate( std::istream::failbit ); } if( !left.good( ) ) { break; } if( 'L' != left.get( ) ) { left.setstate( std::istream::failbit ); } if( !left.good( ) ) { break; } if( 'M' != left.get( ) ) { left.setstate( std::istream::failbit ); } if( !left.good( ) ) { break; } // Parse the index long indexLong; left >> indexLong; if( !left.good( ) ) { break; } if( indexLong < std::numeric_limits< unsigned char >::min( ) ) { left.setstate( std::istream::failbit ); } if( std::numeric_limits< unsigned char >::max( ) < indexLong ) { left.setstate( std::istream::failbit ); } if( !left.good( ) ) { break; } unsigned char index( ( unsigned char )indexLong ); if( ' ' != left.get( ) ) { left.setstate( std::istream::failbit ); } if( !left.good( ) ) { break; } // Parse the Lambda-modifier double lambdaModifier; left >> lambdaModifier; if( lambdaModifier <= ( double )0.0 || ( !right.empty( ) && ( right.count( index ) != 0 || index <= right.rbegin( )->first ) ) ) { left.setstate( std::istream::failbit ); } else { right[ index ] = lambdaModifier; } if( !left.good( ) ) { break; } // If we peek and see a space, then there should be more Lambda-modifiers to parse. Otherwise, we are finished. if( left.peek( ) == ' ' ) { left.ignore( ); } else { break; } } } /// Extracts the indexes from the given maps /// \return The set of indexes std::set< unsigned char > indexSetFromMap( const std::map< unsigned char, double >& in ) { std::set< unsigned char > result; for( typename std::map< unsigned char, double >::const_iterator i( in.begin( ) ); i != in.end( ); ++i ) { result.insert( i->first ); } return result; } } void guessLambdaModifiers( std::ostream& o, std::istream& initialAdjustmentParameterIstream, std::istream& targetsIstream, std::istream& metaLogIstream ) { // Parse the initialAdjustmentParameter double initialAdjustmentParameter; initialAdjustmentParameterIstream >> initialAdjustmentParameter; if( initialAdjustmentParameterIstream.fail( ) || initialAdjustmentParameterIstream.good( ) ) { throw InitialAdjustmentParameterParseException( ); } // Parse the targets std::vector< double > targetVector; parseBitrateVector( targetsIstream, targetVector ); if( targetVector.empty( ) || targetsIstream.fail( ) || targetsIstream.good( ) ) { throw TargetsParseException( ); } // Parse the metalog std::list< MetaLogEntry< std::map< unsigned char, double > > > metaLogEntryList; do { // Parse the Lambda-modifiers MetaLogEntry< std::map< unsigned char, double > > entry; parseLambdaModifierMap( metaLogIstream, entry.lambdaModifiers ); if( !metaLogIstream.good( ) ) { throw MetaLogParseException( ); } // Skip the ';' if( ';' != metaLogIstream.get( ) ) { throw MetaLogParseException( ); } if( !metaLogIstream.good( ) ) { throw MetaLogParseException( ); } // Parse the bitrates parseBitrateVector( metaLogIstream, entry.bitrateVector ); if( metaLogIstream.fail( ) ) { throw MetaLogParseException( ); } metaLogEntryList.push_back( entry ); if( !metaLogIstream.good( ) ) { break; } if( metaLogIstream.get( ) != '\n' ) { throw MetaLogParseException( ); } metaLogIstream.peek( ); } while( metaLogIstream.good( ) ); if( metaLogEntryList.empty( ) ) { throw MetaLogParseException( ); // The meta-log should not be empty } // Initialize firstIndexVector and check that the sizes and indexes match std::set< unsigned char > firstIndexSet( indexSetFromMap( metaLogEntryList.front( ).lambdaModifiers ) ); if( firstIndexSet.size( ) != targetVector.size( ) ) { throw MismatchedIndexesException( ); } for( std::list< MetaLogEntry< std::map< unsigned char, double > > >::const_iterator i( metaLogEntryList.begin( ) ); i != metaLogEntryList.end( ); ++i ) { if( indexSetFromMap( i->lambdaModifiers ) != firstIndexSet ) { throw MismatchedIndexesException( ); } if( i->bitrateVector.size( ) != targetVector.size( ) ) { throw MismatchedIndexesException( ); } } // Initialize simplifiedMetaLogEntryList std::list< MetaLogEntry< std::vector< double > > > simplifiedMetaLogEntryList; for( std::list< MetaLogEntry< std::map< unsigned char, double > > >::const_iterator i( metaLogEntryList.begin( ) ); i != metaLogEntryList.end( ); ++i ) { simplifiedMetaLogEntryList.push_back( MetaLogEntry< std::vector< double > >( ) ); for( std::map< unsigned char, double >::const_iterator j( i->lambdaModifiers.begin( ) ); j != i->lambdaModifiers.end( ); ++j ) { simplifiedMetaLogEntryList.back( ).lambdaModifiers.push_back( j->second ); } simplifiedMetaLogEntryList.back( ).bitrateVector = i->bitrateVector; } // Run the calculations std::vector< double > resultVector( guessLambdaModifiers( initialAdjustmentParameter, targetVector, simplifiedMetaLogEntryList ) ); // Output the results std::set< unsigned char >::const_iterator indexIter( firstIndexSet.begin( ) ); std::vector< double >::const_iterator resultIter( resultVector.begin( ) ); do { if( indexIter != firstIndexSet.begin( ) ) { o << " "; } o << "-LM" << ( long )( *indexIter ) << " "; o.setf( std::ostream::fixed, std::ostream::floatfield ); o.precision( 7 ); o << ( *resultIter ); ++indexIter; ++resultIter; } while( indexIter != firstIndexSet.end( ) ); assert( resultIter == resultVector.end( ) ); // The index set and the result vector should be the same size }