@ -14,34 +14,30 @@
* limitations under the License .
*/
# include <algorithm>
# include <set>
# include <string>
# define LOG_TAG "A PM::A udioProfile"
# define LOG_TAG "A udioProfile"
//#define LOG_NDEBUG 0
# include <android-base/stringprintf.h>
# include <media/AudioContainers.h>
# include <media/AudioResamplerPublic.h>
# include <media/AudioProfile.h>
# include <media/TypeConverter.h>
# include <utils/Errors.h>
# include "AudioPort.h"
# include "AudioProfile.h"
# include "HwModule.h"
# include "TypeConverter.h"
namespace android {
bool operator = = ( const AudioProfile & left , const AudioProfile & compareTo )
bool operator = = ( const AudioProfile & left , const AudioProfile & right )
{
return ( left . getFormat ( ) = = compareTo . getFormat ( ) ) & &
( left . getChannels ( ) = = compareTo . getChannels ( ) ) & &
( left . getSampleRates ( ) = = compareTo . getSampleRates ( ) ) ;
return ( left . getFormat ( ) = = right . getFormat ( ) ) & &
( left . getChannels ( ) = = right . getChannels ( ) ) & &
( left . getSampleRates ( ) = = right . getSampleRates ( ) ) ;
}
static AudioProfile * createFullDynamicImpl ( )
// static
sp < AudioProfile > AudioProfile : : createFullDynamic ( audio_format_t dynamicFormat )
{
AudioProfile * dynamicProfile = new AudioProfile ( gD ynamicFormat,
AudioProfile * dynamicProfile = new AudioProfile ( d ynamicFormat,
ChannelMaskSet ( ) , SampleRateSet ( ) ) ;
dynamicProfile - > setDynamicFormat ( true ) ;
dynamicProfile - > setDynamicChannels ( true ) ;
@ -49,17 +45,10 @@ static AudioProfile* createFullDynamicImpl()
return dynamicProfile ;
}
// static
sp < AudioProfile > AudioProfile : : createFullDynamic ( )
{
static sp < AudioProfile > dynamicProfile = createFullDynamicImpl ( ) ;
return dynamicProfile ;
}
AudioProfile : : AudioProfile ( audio_format_t format ,
audio_channel_mask_t channelMasks ,
uint32_t samplingRate ) :
mName ( String8 ( " " ) ) ,
mName ( " " ) ,
mFormat ( format )
{
mChannelMasks . insert ( channelMasks ) ;
@ -69,7 +58,7 @@ AudioProfile::AudioProfile(audio_format_t format,
AudioProfile : : AudioProfile ( audio_format_t format ,
const ChannelMaskSet & channelMasks ,
const SampleRateSet & samplingRateCollection ) :
mName ( String8 ( " " ) ) ,
mName ( " " ) ,
mFormat ( format ) ,
mChannelMasks ( channelMasks ) ,
mSamplingRates ( samplingRateCollection ) { }
@ -98,276 +87,45 @@ void AudioProfile::clear()
}
}
status_t AudioProfile : : checkExact ( uint32_t samplingRate , audio_channel_mask_t channelMask ,
audio_format_t format ) const
{
if ( audio_formats_match ( format , mFormat ) & &
supportsChannels ( channelMask ) & &
supportsRate ( samplingRate ) ) {
return NO_ERROR ;
}
return BAD_VALUE ;
}
status_t AudioProfile : : checkCompatibleSamplingRate ( uint32_t samplingRate ,
uint32_t & updatedSamplingRate ) const
{
ALOG_ASSERT ( samplingRate > 0 ) ;
if ( mSamplingRates . empty ( ) ) {
updatedSamplingRate = samplingRate ;
return NO_ERROR ;
}
// Search for the closest supported sampling rate that is above (preferred)
// or below (acceptable) the desired sampling rate, within a permitted ratio.
// The sampling rates are sorted in ascending order.
auto desiredRate = mSamplingRates . lower_bound ( samplingRate ) ;
// Prefer to down-sample from a higher sampling rate, as we get the desired frequency spectrum.
if ( desiredRate ! = mSamplingRates . end ( ) ) {
if ( * desiredRate / AUDIO_RESAMPLER_DOWN_RATIO_MAX < = samplingRate ) {
updatedSamplingRate = * desiredRate ;
return NO_ERROR ;
}
}
// But if we have to up-sample from a lower sampling rate, that's OK.
if ( desiredRate ! = mSamplingRates . begin ( ) ) {
uint32_t candidate = * ( - - desiredRate ) ;
if ( candidate * AUDIO_RESAMPLER_UP_RATIO_MAX > = samplingRate ) {
updatedSamplingRate = candidate ;
return NO_ERROR ;
}
}
// leave updatedSamplingRate unmodified
return BAD_VALUE ;
}
status_t AudioProfile : : checkCompatibleChannelMask ( audio_channel_mask_t channelMask ,
audio_channel_mask_t & updatedChannelMask ,
audio_port_type_t portType ,
audio_port_role_t portRole ) const
{
if ( mChannelMasks . empty ( ) ) {
updatedChannelMask = channelMask ;
return NO_ERROR ;
}
const bool isRecordThread = portType = = AUDIO_PORT_TYPE_MIX & & portRole = = AUDIO_PORT_ROLE_SINK ;
const bool isIndex = audio_channel_mask_get_representation ( channelMask )
= = AUDIO_CHANNEL_REPRESENTATION_INDEX ;
const uint32_t channelCount = audio_channel_count_from_in_mask ( channelMask ) ;
int bestMatch = 0 ;
for ( const auto & supported : mChannelMasks ) {
if ( supported = = channelMask ) {
// Exact matches always taken.
updatedChannelMask = channelMask ;
return NO_ERROR ;
}
// AUDIO_CHANNEL_NONE (value: 0) is used for dynamic channel support
if ( isRecordThread & & supported ! = AUDIO_CHANNEL_NONE ) {
// Approximate (best) match:
// The match score measures how well the supported channel mask matches the
// desired mask, where increasing-is-better.
//
// TODO: Some tweaks may be needed.
// Should be a static function of the data processing library.
//
// In priority:
// match score = 1000 if legacy channel conversion equivalent (always prefer this)
// OR
// match score += 100 if the channel mask representations match
// match score += number of channels matched.
// match score += 100 if the channel mask representations DO NOT match
// but the profile has positional channel mask and less than 2 channels.
// This is for audio HAL convention to not list index masks for less than 2 channels
//
// If there are no matched channels, the mask may still be accepted
// but the playback or record will be silent.
const bool isSupportedIndex = ( audio_channel_mask_get_representation ( supported )
= = AUDIO_CHANNEL_REPRESENTATION_INDEX ) ;
const uint32_t supportedChannelCount = audio_channel_count_from_in_mask ( supported ) ;
int match ;
if ( isIndex & & isSupportedIndex ) {
// index equivalence
match = 100 + __builtin_popcount (
audio_channel_mask_get_bits ( channelMask )
& audio_channel_mask_get_bits ( supported ) ) ;
} else if ( isIndex & & ! isSupportedIndex ) {
const uint32_t equivalentBits = ( 1 < < supportedChannelCount ) - 1 ;
match = __builtin_popcount (
audio_channel_mask_get_bits ( channelMask ) & equivalentBits ) ;
if ( supportedChannelCount < = FCC_2 ) {
match + = 100 ;
}
} else if ( ! isIndex & & isSupportedIndex ) {
const uint32_t equivalentBits = ( 1 < < channelCount ) - 1 ;
match = __builtin_popcount (
equivalentBits & audio_channel_mask_get_bits ( supported ) ) ;
} else {
// positional equivalence
match = 100 + __builtin_popcount (
audio_channel_mask_get_bits ( channelMask )
& audio_channel_mask_get_bits ( supported ) ) ;
switch ( supported ) {
case AUDIO_CHANNEL_IN_FRONT_BACK :
case AUDIO_CHANNEL_IN_STEREO :
if ( channelMask = = AUDIO_CHANNEL_IN_MONO ) {
match = 1000 ;
}
break ;
case AUDIO_CHANNEL_IN_MONO :
if ( channelMask = = AUDIO_CHANNEL_IN_FRONT_BACK
| | channelMask = = AUDIO_CHANNEL_IN_STEREO ) {
match = 1000 ;
}
break ;
default :
break ;
}
}
if ( match > bestMatch ) {
bestMatch = match ;
updatedChannelMask = supported ;
}
}
}
return bestMatch > 0 ? NO_ERROR : BAD_VALUE ;
}
void AudioProfile : : dump ( String8 * dst , int spaces ) const
void AudioProfile : : dump ( std : : string * dst , int spaces ) const
{
dst - > append Format ( " %s%s%s \n " , mIsDynamicFormat ? " [dynamic format] " : " " ,
dst - > append ( base : : StringPrintf ( " %s%s%s \n " , mIsDynamicFormat ? " [dynamic format] " : " " ,
mIsDynamicChannels ? " [dynamic channels] " : " " ,
mIsDynamicRate ? " [dynamic rates] " : " " ) ;
mIsDynamicRate ? " [dynamic rates] " : " " ) ) ;
if ( mName . length ( ) ! = 0 ) {
dst - > append Format ( " %*s- name: %s \n " , spaces , " " , mName . string ( ) ) ;
dst - > append ( base : : StringPrintf ( " %*s- name: %s \n " , spaces , " " , mName . c_str ( ) ) ) ;
}
std : : string formatLiteral ;
if ( FormatConverter : : toString ( mFormat , formatLiteral ) ) {
dst - > append Format ( " %*s- format: %s \n " , spaces , " " , formatLiteral . c_str ( ) ) ;
dst - > append ( base : : StringPrintf ( " %*s- format: %s \n " , spaces , " " , formatLiteral . c_str ( ) ) ) ;
}
if ( ! mSamplingRates . empty ( ) ) {
dst - > append Format ( " %*s- sampling rates: " , spaces , " " ) ;
dst - > append ( base : : StringPrintf ( " %*s- sampling rates: " , spaces , " " ) ) ;
for ( auto it = mSamplingRates . begin ( ) ; it ! = mSamplingRates . end ( ) ; ) {
dst - > append Format ( " %d " , * it ) ;
dst - > append ( base : : StringPrintf ( " %d " , * it ) ) ;
dst - > append ( + + it = = mSamplingRates . end ( ) ? " " : " , " ) ;
}
dst - > append ( " \n " ) ;
}
if ( ! mChannelMasks . empty ( ) ) {
dst - > append Format ( " %*s- channel masks: " , spaces , " " ) ;
dst - > append ( base : : StringPrintf ( " %*s- channel masks: " , spaces , " " ) ) ;
for ( auto it = mChannelMasks . begin ( ) ; it ! = mChannelMasks . end ( ) ; ) {
dst - > append Format ( " 0x%04x " , * it ) ;
dst - > append ( base : : StringPrintf ( " 0x%04x " , * it ) ) ;
dst - > append ( + + it = = mChannelMasks . end ( ) ? " " : " , " ) ;
}
dst - > append ( " \n " ) ;
}
}
ssize_t AudioProfileVector : : add ( const sp < AudioProfile > & profile )
ssize_t AudioProfileVectorBase : : add ( const sp < AudioProfile > & profile )
{
ssize_t index = size ( ) ;
push_back ( profile ) ;
// we sort from worst to best, so that AUDIO_FORMAT_DEFAULT is always the first entry.
std : : sort ( begin ( ) , end ( ) ,
[ ] ( const sp < AudioProfile > & a , const sp < AudioProfile > & b )
{
return AudioPort : : compareFormats ( a - > getFormat ( ) , b - > getFormat ( ) ) < 0 ;
} ) ;
return index ;
}
ssize_t AudioProfileVector : : addProfileFromHal ( const sp < AudioProfile > & profileToAdd )
{
// Check valid profile to add:
if ( ! profileToAdd - > hasValidFormat ( ) ) {
return - 1 ;
}
if ( ! profileToAdd - > hasValidChannels ( ) & & ! profileToAdd - > hasValidRates ( ) ) {
FormatVector formats ;
formats . push_back ( profileToAdd - > getFormat ( ) ) ;
setFormats ( FormatVector ( formats ) ) ;
return 0 ;
}
if ( ! profileToAdd - > hasValidChannels ( ) & & profileToAdd - > hasValidRates ( ) ) {
setSampleRatesFor ( profileToAdd - > getSampleRates ( ) , profileToAdd - > getFormat ( ) ) ;
return 0 ;
}
if ( profileToAdd - > hasValidChannels ( ) & & ! profileToAdd - > hasValidRates ( ) ) {
setChannelsFor ( profileToAdd - > getChannels ( ) , profileToAdd - > getFormat ( ) ) ;
return 0 ;
}
// Go through the list of profile to avoid duplicates
for ( size_t profileIndex = 0 ; profileIndex < size ( ) ; profileIndex + + ) {
const sp < AudioProfile > & profile = at ( profileIndex ) ;
if ( profile - > isValid ( ) & & profile = = profileToAdd ) {
// Nothing to do
return profileIndex ;
}
}
profileToAdd - > setDynamicFormat ( true ) ; // set the format as dynamic to allow removal
return add ( profileToAdd ) ;
}
status_t AudioProfileVector : : checkExactProfile ( uint32_t samplingRate ,
audio_channel_mask_t channelMask ,
audio_format_t format ) const
{
if ( empty ( ) ) {
return NO_ERROR ;
}
for ( const auto & profile : * this ) {
if ( profile - > checkExact ( samplingRate , channelMask , format ) = = NO_ERROR ) {
return NO_ERROR ;
}
}
return BAD_VALUE ;
}
status_t AudioProfileVector : : checkCompatibleProfile ( uint32_t & samplingRate ,
audio_channel_mask_t & channelMask ,
audio_format_t & format ,
audio_port_type_t portType ,
audio_port_role_t portRole ) const
{
if ( empty ( ) ) {
return NO_ERROR ;
}
const bool checkInexact = // when port is input and format is linear pcm
portType = = AUDIO_PORT_TYPE_MIX & & portRole = = AUDIO_PORT_ROLE_SINK
& & audio_is_linear_pcm ( format ) ;
// iterate from best format to worst format (reverse order)
for ( ssize_t i = size ( ) - 1 ; i > = 0 ; - - i ) {
const sp < AudioProfile > profile = at ( i ) ;
audio_format_t formatToCompare = profile - > getFormat ( ) ;
if ( formatToCompare = = format | |
( checkInexact
& & formatToCompare ! = AUDIO_FORMAT_DEFAULT
& & audio_is_linear_pcm ( formatToCompare ) ) ) {
// Compatible profile has been found, checks if this profile has compatible
// rate and channels as well
audio_channel_mask_t updatedChannels ;
uint32_t updatedRate ;
if ( profile - > checkCompatibleChannelMask ( channelMask , updatedChannels ,
portType , portRole ) = = NO_ERROR & &
profile - > checkCompatibleSamplingRate ( samplingRate , updatedRate ) = = NO_ERROR ) {
// for inexact checks we take the first linear pcm format due to sorting.
format = formatToCompare ;
channelMask = updatedChannels ;
samplingRate = updatedRate ;
return NO_ERROR ;
}
}
}
return BAD_VALUE ;
}
void AudioProfileVector : : clearProfiles ( )
void AudioProfileVectorBase : : clearProfiles ( )
{
for ( auto it = begin ( ) ; it ! = end ( ) ; ) {
if ( ( * it ) - > isDynamicFormat ( ) & & ( * it ) - > hasValidFormat ( ) ) {
@ -379,77 +137,7 @@ void AudioProfileVector::clearProfiles()
}
}
// Returns an intersection between two possibly unsorted vectors and the contents of 'order'.
// The result is ordered according to 'order'.
template < typename T , typename Order >
std : : vector < typename T : : value_type > intersectFilterAndOrder (
const T & input1 , const T & input2 , const Order & order )
{
std : : set < typename T : : value_type > set1 { input1 . begin ( ) , input1 . end ( ) } ;
std : : set < typename T : : value_type > set2 { input2 . begin ( ) , input2 . end ( ) } ;
std : : set < typename T : : value_type > common ;
std : : set_intersection ( set1 . begin ( ) , set1 . end ( ) , set2 . begin ( ) , set2 . end ( ) ,
std : : inserter ( common , common . begin ( ) ) ) ;
std : : vector < typename T : : value_type > result ;
for ( const auto & e : order ) {
if ( common . find ( e ) ! = common . end ( ) ) result . push_back ( e ) ;
}
return result ;
}
// Intersect two possibly unsorted vectors, return common elements according to 'comp' ordering.
// 'comp' is a comparator function.
template < typename T , typename Compare >
std : : vector < typename T : : value_type > intersectAndOrder (
const T & input1 , const T & input2 , Compare comp )
{
std : : set < typename T : : value_type , Compare > set1 { input1 . begin ( ) , input1 . end ( ) , comp } ;
std : : set < typename T : : value_type , Compare > set2 { input2 . begin ( ) , input2 . end ( ) , comp } ;
std : : vector < typename T : : value_type > result ;
std : : set_intersection ( set1 . begin ( ) , set1 . end ( ) , set2 . begin ( ) , set2 . end ( ) ,
std : : back_inserter ( result ) , comp ) ;
return result ;
}
status_t AudioProfileVector : : findBestMatchingOutputConfig ( const AudioProfileVector & outputProfiles ,
const std : : vector < audio_format_t > & preferredFormats ,
const std : : vector < audio_channel_mask_t > & preferredOutputChannels ,
bool preferHigherSamplingRates ,
audio_config_base * bestOutputConfig ) const
{
auto formats = intersectFilterAndOrder ( getSupportedFormats ( ) ,
outputProfiles . getSupportedFormats ( ) , preferredFormats ) ;
// Pick the best compatible profile.
for ( const auto & f : formats ) {
sp < AudioProfile > inputProfile = getFirstValidProfileFor ( f ) ;
sp < AudioProfile > outputProfile = outputProfiles . getFirstValidProfileFor ( f ) ;
if ( inputProfile = = nullptr | | outputProfile = = nullptr ) {
continue ;
}
auto channels = intersectFilterAndOrder ( asOutMask ( inputProfile - > getChannels ( ) ) ,
outputProfile - > getChannels ( ) , preferredOutputChannels ) ;
if ( channels . empty ( ) ) {
continue ;
}
auto sampleRates = preferHigherSamplingRates ?
intersectAndOrder ( inputProfile - > getSampleRates ( ) , outputProfile - > getSampleRates ( ) ,
std : : greater < typename SampleRateSet : : value_type > ( ) ) :
intersectAndOrder ( inputProfile - > getSampleRates ( ) , outputProfile - > getSampleRates ( ) ,
std : : less < typename SampleRateSet : : value_type > ( ) ) ;
if ( sampleRates . empty ( ) ) {
continue ;
}
ALOGD ( " %s() found channel mask %#x and sample rate %d for format %#x. " ,
__func__ , * channels . begin ( ) , * sampleRates . begin ( ) , f ) ;
bestOutputConfig - > format = f ;
bestOutputConfig - > sample_rate = * sampleRates . begin ( ) ;
bestOutputConfig - > channel_mask = * channels . begin ( ) ;
return NO_ERROR ;
}
return BAD_VALUE ;
}
sp < AudioProfile > AudioProfileVector : : getFirstValidProfile ( ) const
sp < AudioProfile > AudioProfileVectorBase : : getFirstValidProfile ( ) const
{
for ( const auto & profile : * this ) {
if ( profile - > isValid ( ) ) {
@ -459,7 +147,7 @@ sp<AudioProfile> AudioProfileVector::getFirstValidProfile() const
return nullptr ;
}
sp < AudioProfile > AudioProfileVector : : getFirstValidProfileFor ( audio_format_t format ) const
sp < AudioProfile > AudioProfileVector Base : : getFirstValidProfileFor ( audio_format_t format ) const
{
for ( const auto & profile : * this ) {
if ( profile - > isValid ( ) & & profile - > getFormat ( ) = = format ) {
@ -469,7 +157,7 @@ sp<AudioProfile> AudioProfileVector::getFirstValidProfileFor(audio_format_t form
return nullptr ;
}
FormatVector AudioProfileVector : : getSupportedFormats ( ) const
FormatVector AudioProfileVector Base : : getSupportedFormats ( ) const
{
FormatVector supportedFormats ;
for ( const auto & profile : * this ) {
@ -480,7 +168,7 @@ FormatVector AudioProfileVector::getSupportedFormats() const
return supportedFormats ;
}
bool AudioProfileVector : : hasDynamicChannelsFor ( audio_format_t format ) const
bool AudioProfileVector Base : : hasDynamicChannelsFor ( audio_format_t format ) const
{
for ( const auto & profile : * this ) {
if ( profile - > getFormat ( ) = = format & & profile - > isDynamicChannels ( ) ) {
@ -490,97 +178,44 @@ bool AudioProfileVector::hasDynamicChannelsFor(audio_format_t format) const
return false ;
}
bool AudioProfileVector : : hasDynamicProfile ( ) const
bool AudioProfileVector Base: : hasDynamicFormat ( ) const
{
for ( const auto & profile : * this ) {
if ( profile - > isDynamic ( ) ) {
if ( profile - > isDynamic Format ( ) ) {
return true ;
}
}
return false ;
}
bool AudioProfileVector : : hasDynamicRateFor ( audio_format_t format ) const
bool AudioProfileVector Base: : hasDynamicProfile ( ) const
{
for ( const auto & profile : * this ) {
if ( profile - > getFormat( ) = = format & & profile - > isDynamicRate ( ) ) {
if ( profile - > isDynamic( ) ) {
return true ;
}
}
return false ;
}
void AudioProfileVector : : setFormats ( const FormatVector & formats )
{
// Only allow to change the format of dynamic profile
sp < AudioProfile > dynamicFormatProfile = getProfileFor ( gDynamicFormat ) ;
if ( dynamicFormatProfile = = 0 ) {
return ;
}
for ( const auto & format : formats ) {
sp < AudioProfile > profile = new AudioProfile ( format ,
dynamicFormatProfile - > getChannels ( ) ,
dynamicFormatProfile - > getSampleRates ( ) ) ;
profile - > setDynamicFormat ( true ) ;
profile - > setDynamicChannels ( dynamicFormatProfile - > isDynamicChannels ( ) ) ;
profile - > setDynamicRate ( dynamicFormatProfile - > isDynamicRate ( ) ) ;
add ( profile ) ;
}
}
void AudioProfileVector : : dump ( String8 * dst , int spaces ) const
{
dst - > appendFormat ( " %*s- Profiles: \n " , spaces , " " ) ;
for ( size_t i = 0 ; i < size ( ) ; i + + ) {
dst - > appendFormat ( " %*sProfile %zu: " , spaces + 4 , " " , i ) ;
at ( i ) - > dump ( dst , spaces + 8 ) ;
}
}
sp < AudioProfile > AudioProfileVector : : getProfileFor ( audio_format_t format ) const
{
for ( const auto & profile : * this ) {
if ( profile - > getFormat ( ) = = format ) {
return profile ;
}
}
return nullptr ;
}
void AudioProfileVector : : setSampleRatesFor (
const SampleRateSet & sampleRates , audio_format_t format )
bool AudioProfileVectorBase : : hasDynamicRateFor ( audio_format_t format ) const
{
for ( const auto & profile : * this ) {
if ( profile - > getFormat ( ) = = format & & profile - > isDynamicRate ( ) ) {
if ( profile - > hasValidRates ( ) ) {
// Need to create a new profile with same format
sp < AudioProfile > profileToAdd = new AudioProfile ( format , profile - > getChannels ( ) ,
sampleRates ) ;
profileToAdd - > setDynamicFormat ( true ) ; // need to set to allow cleaning
add ( profileToAdd ) ;
} else {
profile - > setSampleRates ( sampleRates ) ;
}
return ;
return true ;
}
}
return false ;
}
void AudioProfileVector : : setChannelsFor ( const ChannelMaskSet & channelMasks , audio_format_t format )
void AudioProfileVectorBase : : dump ( std : : string * dst , int spaces ) const
{
for ( const auto & profile : * this ) {
if ( profile - > getFormat ( ) = = format & & profile - > isDynamicChannels ( ) ) {
if ( profile - > hasValidChannels ( ) ) {
// Need to create a new profile with same format
sp < AudioProfile > profileToAdd = new AudioProfile ( format , channelMasks ,
profile - > getSampleRates ( ) ) ;
profileToAdd - > setDynamicFormat ( true ) ; // need to set to allow cleaning
add ( profileToAdd ) ;
} else {
profile - > setChannels ( channelMasks ) ;
}
return ;
}
dst - > append ( base : : StringPrintf ( " %*s- Profiles: \n " , spaces , " " ) ) ;
for ( size_t i = 0 ; i < size ( ) ; i + + ) {
dst - > append ( base : : StringPrintf ( " %*sProfile %zu: " , spaces + 4 , " " , i ) ) ;
std : : string profileStr ;
at ( i ) - > dump ( & profileStr , spaces + 8 ) ;
dst - > append ( profileStr ) ;
}
}