Commit 339d99b8 by tonihei Committed by kim-vde

Add preferredVideoRoleFlags to TrackSelectionParameters.

And also tweak existing role flag logic to strictly prefer perfect
matches over partial matches.

Caveat: Video role flags only supported for fixed track selections
(same issue as Issue: google/ExoPlayer#9519).

Issue: google/ExoPlayer#9402
PiperOrigin-RevId: 412292835
parent e846e9f0
...@@ -80,6 +80,7 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -80,6 +80,7 @@ public class TrackSelectionParameters implements Bundleable {
private int viewportHeight; private int viewportHeight;
private boolean viewportOrientationMayChange; private boolean viewportOrientationMayChange;
private ImmutableList<String> preferredVideoMimeTypes; private ImmutableList<String> preferredVideoMimeTypes;
private @C.RoleFlags int preferredVideoRoleFlags;
// Audio // Audio
private ImmutableList<String> preferredAudioLanguages; private ImmutableList<String> preferredAudioLanguages;
private @C.RoleFlags int preferredAudioRoleFlags; private @C.RoleFlags int preferredAudioRoleFlags;
...@@ -111,6 +112,7 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -111,6 +112,7 @@ public class TrackSelectionParameters implements Bundleable {
viewportHeight = Integer.MAX_VALUE; viewportHeight = Integer.MAX_VALUE;
viewportOrientationMayChange = true; viewportOrientationMayChange = true;
preferredVideoMimeTypes = ImmutableList.of(); preferredVideoMimeTypes = ImmutableList.of();
preferredVideoRoleFlags = 0;
// Audio // Audio
preferredAudioLanguages = ImmutableList.of(); preferredAudioLanguages = ImmutableList.of();
preferredAudioRoleFlags = 0; preferredAudioRoleFlags = 0;
...@@ -183,6 +185,10 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -183,6 +185,10 @@ public class TrackSelectionParameters implements Bundleable {
firstNonNull( firstNonNull(
bundle.getStringArray(keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES)), bundle.getStringArray(keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES)),
new String[0])); new String[0]));
preferredVideoRoleFlags =
bundle.getInt(
keyForField(FIELD_PREFERRED_VIDEO_ROLE_FLAGS),
DEFAULT_WITHOUT_CONTEXT.preferredVideoRoleFlags);
// Audio // Audio
String[] preferredAudioLanguages1 = String[] preferredAudioLanguages1 =
firstNonNull( firstNonNull(
...@@ -261,6 +267,7 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -261,6 +267,7 @@ public class TrackSelectionParameters implements Bundleable {
viewportHeight = parameters.viewportHeight; viewportHeight = parameters.viewportHeight;
viewportOrientationMayChange = parameters.viewportOrientationMayChange; viewportOrientationMayChange = parameters.viewportOrientationMayChange;
preferredVideoMimeTypes = parameters.preferredVideoMimeTypes; preferredVideoMimeTypes = parameters.preferredVideoMimeTypes;
preferredVideoRoleFlags = parameters.preferredVideoRoleFlags;
// Audio // Audio
preferredAudioLanguages = parameters.preferredAudioLanguages; preferredAudioLanguages = parameters.preferredAudioLanguages;
preferredAudioRoleFlags = parameters.preferredAudioRoleFlags; preferredAudioRoleFlags = parameters.preferredAudioRoleFlags;
...@@ -441,6 +448,17 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -441,6 +448,17 @@ public class TrackSelectionParameters implements Bundleable {
return this; return this;
} }
/**
* Sets the preferred {@link C.RoleFlags} for video tracks.
*
* @param preferredVideoRoleFlags Preferred video role flags.
* @return This builder.
*/
public Builder setPreferredVideoRoleFlags(@C.RoleFlags int preferredVideoRoleFlags) {
this.preferredVideoRoleFlags = preferredVideoRoleFlags;
return this;
}
// Audio // Audio
/** /**
...@@ -770,6 +788,11 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -770,6 +788,11 @@ public class TrackSelectionParameters implements Bundleable {
* no preference. The default is an empty list. * no preference. The default is an empty list.
*/ */
public final ImmutableList<String> preferredVideoMimeTypes; public final ImmutableList<String> preferredVideoMimeTypes;
/**
* The preferred {@link C.RoleFlags} for video tracks. {@code 0} selects the default track if
* there is one, or the first track if there's no default. The default value is {@code 0}.
*/
public final @C.RoleFlags int preferredVideoRoleFlags;
// Audio // Audio
/** /**
* The preferred languages for audio and forced text tracks as IETF BCP 47 conformant tags in * The preferred languages for audio and forced text tracks as IETF BCP 47 conformant tags in
...@@ -853,6 +876,7 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -853,6 +876,7 @@ public class TrackSelectionParameters implements Bundleable {
this.viewportHeight = builder.viewportHeight; this.viewportHeight = builder.viewportHeight;
this.viewportOrientationMayChange = builder.viewportOrientationMayChange; this.viewportOrientationMayChange = builder.viewportOrientationMayChange;
this.preferredVideoMimeTypes = builder.preferredVideoMimeTypes; this.preferredVideoMimeTypes = builder.preferredVideoMimeTypes;
this.preferredVideoRoleFlags = builder.preferredVideoRoleFlags;
// Audio // Audio
this.preferredAudioLanguages = builder.preferredAudioLanguages; this.preferredAudioLanguages = builder.preferredAudioLanguages;
this.preferredAudioRoleFlags = builder.preferredAudioRoleFlags; this.preferredAudioRoleFlags = builder.preferredAudioRoleFlags;
...@@ -898,6 +922,7 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -898,6 +922,7 @@ public class TrackSelectionParameters implements Bundleable {
&& viewportWidth == other.viewportWidth && viewportWidth == other.viewportWidth
&& viewportHeight == other.viewportHeight && viewportHeight == other.viewportHeight
&& preferredVideoMimeTypes.equals(other.preferredVideoMimeTypes) && preferredVideoMimeTypes.equals(other.preferredVideoMimeTypes)
&& preferredVideoRoleFlags == other.preferredVideoRoleFlags
// Audio // Audio
&& preferredAudioLanguages.equals(other.preferredAudioLanguages) && preferredAudioLanguages.equals(other.preferredAudioLanguages)
&& preferredAudioRoleFlags == other.preferredAudioRoleFlags && preferredAudioRoleFlags == other.preferredAudioRoleFlags
...@@ -930,6 +955,7 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -930,6 +955,7 @@ public class TrackSelectionParameters implements Bundleable {
result = 31 * result + viewportWidth; result = 31 * result + viewportWidth;
result = 31 * result + viewportHeight; result = 31 * result + viewportHeight;
result = 31 * result + preferredVideoMimeTypes.hashCode(); result = 31 * result + preferredVideoMimeTypes.hashCode();
result = 31 * result + preferredVideoRoleFlags;
// Audio // Audio
result = 31 * result + preferredAudioLanguages.hashCode(); result = 31 * result + preferredAudioLanguages.hashCode();
result = 31 * result + preferredAudioRoleFlags; result = 31 * result + preferredAudioRoleFlags;
...@@ -978,6 +1004,7 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -978,6 +1004,7 @@ public class TrackSelectionParameters implements Bundleable {
FIELD_SELECTION_OVERRIDE_KEYS, FIELD_SELECTION_OVERRIDE_KEYS,
FIELD_SELECTION_OVERRIDE_VALUES, FIELD_SELECTION_OVERRIDE_VALUES,
FIELD_DISABLED_TRACK_TYPE, FIELD_DISABLED_TRACK_TYPE,
FIELD_PREFERRED_VIDEO_ROLE_FLAGS
}) })
private @interface FieldNumber {} private @interface FieldNumber {}
...@@ -1006,6 +1033,7 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -1006,6 +1033,7 @@ public class TrackSelectionParameters implements Bundleable {
private static final int FIELD_SELECTION_OVERRIDE_KEYS = 23; private static final int FIELD_SELECTION_OVERRIDE_KEYS = 23;
private static final int FIELD_SELECTION_OVERRIDE_VALUES = 24; private static final int FIELD_SELECTION_OVERRIDE_VALUES = 24;
private static final int FIELD_DISABLED_TRACK_TYPE = 25; private static final int FIELD_DISABLED_TRACK_TYPE = 25;
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 26;
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
...@@ -1027,6 +1055,7 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -1027,6 +1055,7 @@ public class TrackSelectionParameters implements Bundleable {
bundle.putStringArray( bundle.putStringArray(
keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES), keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES),
preferredVideoMimeTypes.toArray(new String[0])); preferredVideoMimeTypes.toArray(new String[0]));
bundle.putInt(keyForField(FIELD_PREFERRED_VIDEO_ROLE_FLAGS), preferredVideoRoleFlags);
// Audio // Audio
bundle.putStringArray( bundle.putStringArray(
keyForField(FIELD_PREFERRED_AUDIO_LANGUAGES), keyForField(FIELD_PREFERRED_AUDIO_LANGUAGES),
......
...@@ -27,6 +27,7 @@ import androidx.annotation.Nullable; ...@@ -27,6 +27,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.Bundleable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.C.FormatSupport; import com.google.android.exoplayer2.C.FormatSupport;
import com.google.android.exoplayer2.C.RoleFlags;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
...@@ -368,6 +369,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -368,6 +369,13 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return this; return this;
} }
@Override
public DefaultTrackSelector.ParametersBuilder setPreferredVideoRoleFlags(
@RoleFlags int preferredVideoRoleFlags) {
super.setPreferredVideoRoleFlags(preferredVideoRoleFlags);
return this;
}
// Audio // Audio
@Override @Override
...@@ -2468,6 +2476,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2468,6 +2476,14 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
} }
private static int getRoleFlagMatchScore(int trackRoleFlags, int preferredRoleFlags) {
if (trackRoleFlags != 0 && trackRoleFlags == preferredRoleFlags) {
// Prefer perfect match over partial matches.
return Integer.MAX_VALUE;
}
return Integer.bitCount(trackRoleFlags & preferredRoleFlags);
}
/** Represents how well a video track matches the selection {@link Parameters}. */ /** Represents how well a video track matches the selection {@link Parameters}. */
protected static final class VideoTrackScore implements Comparable<VideoTrackScore> { protected static final class VideoTrackScore implements Comparable<VideoTrackScore> {
...@@ -2483,6 +2499,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2483,6 +2499,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private final int bitrate; private final int bitrate;
private final int pixelCount; private final int pixelCount;
private final int preferredMimeTypeMatchIndex; private final int preferredMimeTypeMatchIndex;
private final int preferredRoleFlagsScore;
private final boolean hasMainOrNoRoleFlag;
public VideoTrackScore( public VideoTrackScore(
Format format, Format format,
...@@ -2510,6 +2528,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2510,6 +2528,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
isSupported(formatSupport, /* allowExceedsCapabilities= */ false); isSupported(formatSupport, /* allowExceedsCapabilities= */ false);
bitrate = format.bitrate; bitrate = format.bitrate;
pixelCount = format.getPixelCount(); pixelCount = format.getPixelCount();
preferredRoleFlagsScore =
getRoleFlagMatchScore(format.roleFlags, parameters.preferredVideoRoleFlags);
hasMainOrNoRoleFlag = format.roleFlags == 0 || (format.roleFlags & C.ROLE_FLAG_MAIN) != 0;
int bestMimeTypeMatchIndex = Integer.MAX_VALUE; int bestMimeTypeMatchIndex = Integer.MAX_VALUE;
for (int i = 0; i < parameters.preferredVideoMimeTypes.size(); i++) { for (int i = 0; i < parameters.preferredVideoMimeTypes.size(); i++) {
if (format.sampleMimeType != null if (format.sampleMimeType != null
...@@ -2537,6 +2558,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2537,6 +2558,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
: FORMAT_VALUE_ORDERING.reverse(); : FORMAT_VALUE_ORDERING.reverse();
return ComparisonChain.start() return ComparisonChain.start()
.compareFalseFirst(this.isWithinRendererCapabilities, other.isWithinRendererCapabilities) .compareFalseFirst(this.isWithinRendererCapabilities, other.isWithinRendererCapabilities)
.compare(this.preferredRoleFlagsScore, other.preferredRoleFlagsScore)
.compareFalseFirst(this.hasMainOrNoRoleFlag, other.hasMainOrNoRoleFlag)
.compareFalseFirst(this.isWithinMaxConstraints, other.isWithinMaxConstraints) .compareFalseFirst(this.isWithinMaxConstraints, other.isWithinMaxConstraints)
.compareFalseFirst(this.isWithinMinConstraints, other.isWithinMinConstraints) .compareFalseFirst(this.isWithinMinConstraints, other.isWithinMinConstraints)
.compare( .compare(
...@@ -2568,6 +2591,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2568,6 +2591,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private final int preferredLanguageScore; private final int preferredLanguageScore;
private final int preferredLanguageIndex; private final int preferredLanguageIndex;
private final int preferredRoleFlagsScore; private final int preferredRoleFlagsScore;
private final boolean hasMainOrNoRoleFlag;
private final int localeLanguageMatchIndex; private final int localeLanguageMatchIndex;
private final int localeLanguageScore; private final int localeLanguageScore;
private final boolean isDefaultSelectionFlag; private final boolean isDefaultSelectionFlag;
...@@ -2598,7 +2622,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2598,7 +2622,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
preferredLanguageIndex = bestLanguageIndex; preferredLanguageIndex = bestLanguageIndex;
preferredLanguageScore = bestLanguageScore; preferredLanguageScore = bestLanguageScore;
preferredRoleFlagsScore = preferredRoleFlagsScore =
Integer.bitCount(format.roleFlags & parameters.preferredAudioRoleFlags); getRoleFlagMatchScore(format.roleFlags, parameters.preferredAudioRoleFlags);
hasMainOrNoRoleFlag = format.roleFlags == 0 || (format.roleFlags & C.ROLE_FLAG_MAIN) != 0;
isDefaultSelectionFlag = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; isDefaultSelectionFlag = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
channelCount = format.channelCount; channelCount = format.channelCount;
sampleRate = format.sampleRate; sampleRate = format.sampleRate;
...@@ -2656,6 +2681,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2656,6 +2681,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
Ordering.natural().reverse()) Ordering.natural().reverse())
.compare(this.preferredLanguageScore, other.preferredLanguageScore) .compare(this.preferredLanguageScore, other.preferredLanguageScore)
.compare(this.preferredRoleFlagsScore, other.preferredRoleFlagsScore) .compare(this.preferredRoleFlagsScore, other.preferredRoleFlagsScore)
.compareFalseFirst(this.hasMainOrNoRoleFlag, other.hasMainOrNoRoleFlag)
.compareFalseFirst(this.isWithinConstraints, other.isWithinConstraints) .compareFalseFirst(this.isWithinConstraints, other.isWithinConstraints)
.compare( .compare(
this.preferredMimeTypeMatchIndex, this.preferredMimeTypeMatchIndex,
...@@ -2732,7 +2758,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2732,7 +2758,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
preferredLanguageIndex = bestLanguageIndex; preferredLanguageIndex = bestLanguageIndex;
preferredLanguageScore = bestLanguageScore; preferredLanguageScore = bestLanguageScore;
preferredRoleFlagsScore = preferredRoleFlagsScore =
Integer.bitCount(format.roleFlags & parameters.preferredTextRoleFlags); getRoleFlagMatchScore(format.roleFlags, parameters.preferredTextRoleFlags);
hasCaptionRoleFlags = hasCaptionRoleFlags =
(format.roleFlags & (C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND)) != 0; (format.roleFlags & (C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND)) != 0;
boolean selectedAudioLanguageUndetermined = boolean selectedAudioLanguageUndetermined =
......
...@@ -594,7 +594,7 @@ public final class DefaultTrackSelectorTest { ...@@ -594,7 +594,7 @@ public final class DefaultTrackSelectorTest {
} }
/** /**
* Tests that track selector will select audio track with the highest number of matching role * Tests that track selector will select the audio track with the highest number of matching role
* flags given by {@link Parameters}. * flags given by {@link Parameters}.
*/ */
@Test @Test
...@@ -619,6 +619,17 @@ public final class DefaultTrackSelectorTest { ...@@ -619,6 +619,17 @@ public final class DefaultTrackSelectorTest {
periodId, periodId,
TIMELINE); TIMELINE);
assertFixedSelection(result.selections[0], trackGroups, moreRoleFlags); assertFixedSelection(result.selections[0], trackGroups, moreRoleFlags);
// Also verify that exact match between parameters and tracks is preferred.
trackSelector.setParameters(
defaultParameters.buildUpon().setPreferredAudioRoleFlags(C.ROLE_FLAG_CAPTION));
result =
trackSelector.selectTracks(
new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
trackGroups,
periodId,
TIMELINE);
assertFixedSelection(result.selections[0], trackGroups, lessRoleFlags);
} }
/** /**
...@@ -1280,6 +1291,45 @@ public final class DefaultTrackSelectorTest { ...@@ -1280,6 +1291,45 @@ public final class DefaultTrackSelectorTest {
} }
/** /**
* Tests that track selector will select the text track with the highest number of matching role
* flags given by {@link Parameters}.
*/
@Test
public void selectTracks_withPreferredTextRoleFlags_selectPreferredTrack() throws Exception {
Format.Builder formatBuilder = TEXT_FORMAT.buildUpon();
Format noRoleFlags = formatBuilder.build();
Format lessRoleFlags = formatBuilder.setRoleFlags(C.ROLE_FLAG_CAPTION).build();
Format moreRoleFlags =
formatBuilder
.setRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY | C.ROLE_FLAG_DUB)
.build();
TrackGroupArray trackGroups = wrapFormats(noRoleFlags, moreRoleFlags, lessRoleFlags);
trackSelector.setParameters(
defaultParameters
.buildUpon()
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY));
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
trackGroups,
periodId,
TIMELINE);
assertFixedSelection(result.selections[0], trackGroups, moreRoleFlags);
// Also verify that exact match between parameters and tracks is preferred.
trackSelector.setParameters(
defaultParameters.buildUpon().setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION));
result =
trackSelector.selectTracks(
new RendererCapabilities[] {ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
trackGroups,
periodId,
TIMELINE);
assertFixedSelection(result.selections[0], trackGroups, lessRoleFlags);
}
/**
* Tests that track selector will select the lowest bitrate supported audio track when {@link * Tests that track selector will select the lowest bitrate supported audio track when {@link
* Parameters#forceLowestBitrate} is set. * Parameters#forceLowestBitrate} is set.
*/ */
...@@ -1809,6 +1859,39 @@ public final class DefaultTrackSelectorTest { ...@@ -1809,6 +1859,39 @@ public final class DefaultTrackSelectorTest {
assertFixedSelection(result.selections[0], trackGroups, formatAv1); assertFixedSelection(result.selections[0], trackGroups, formatAv1);
} }
/**
* Tests that track selector will select the video track with the highest number of matching role
* flags given by {@link Parameters}.
*/
@Test
public void selectTracks_withPreferredVideoRoleFlags_selectPreferredTrack() throws Exception {
Format.Builder formatBuilder = VIDEO_FORMAT.buildUpon();
Format noRoleFlags = formatBuilder.build();
Format lessRoleFlags = formatBuilder.setRoleFlags(C.ROLE_FLAG_CAPTION).build();
Format moreRoleFlags =
formatBuilder
.setRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY | C.ROLE_FLAG_DUB)
.build();
TrackGroupArray trackGroups = wrapFormats(noRoleFlags, moreRoleFlags, lessRoleFlags);
trackSelector.setParameters(
defaultParameters
.buildUpon()
.setPreferredVideoRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY));
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertFixedSelection(result.selections[0], trackGroups, moreRoleFlags);
// Also verify that exact match between parameters and tracks is preferred.
trackSelector.setParameters(
defaultParameters.buildUpon().setPreferredVideoRoleFlags(C.ROLE_FLAG_CAPTION));
result =
trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertFixedSelection(result.selections[0], trackGroups, lessRoleFlags);
}
@Test @Test
public void selectTracks_withPreferredAudioMimeTypes_selectsTrackWithPreferredMimeType() public void selectTracks_withPreferredAudioMimeTypes_selectsTrackWithPreferredMimeType()
throws Exception { throws Exception {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment