Merge changes from topic "revert-27482397-VHRVJUTYHK" into main
* changes:
Revert^2 "Audio policy: anonymize Bluetooth MAC addresses"
Revert "Audio policy: anonymize Bluetooth MAC addresses take 2"
diff --git a/media/audio/aconfig/aaudio.aconfig b/media/audio/aconfig/aaudio.aconfig
index c160109..f9fb4c7 100644
--- a/media/audio/aconfig/aaudio.aconfig
+++ b/media/audio/aconfig/aaudio.aconfig
@@ -11,3 +11,10 @@
description: "Enable the AAudio sample rate converter."
bug: "219533889"
}
+
+flag {
+ name: "start_stop_client_from_command_thread"
+ namespace: "media_audio"
+ description: "Start or stop client from command thread."
+ bug: "341627085"
+}
diff --git a/media/libaudioclient/Android.bp b/media/libaudioclient/Android.bp
index 04a8a45..e667964 100644
--- a/media/libaudioclient/Android.bp
+++ b/media/libaudioclient/Android.bp
@@ -315,6 +315,7 @@
"aidl/android/media/DeviceConnectedState.aidl",
"aidl/android/media/EffectDescriptor.aidl",
"aidl/android/media/SurroundSoundConfig.aidl",
+ "aidl/android/media/TrackInternalMuteInfo.aidl",
"aidl/android/media/TrackSecondaryOutputInfo.aidl",
],
defaults: [
diff --git a/media/libaudioclient/IAudioFlinger.cpp b/media/libaudioclient/IAudioFlinger.cpp
index aa5c840..cf3b43a 100644
--- a/media/libaudioclient/IAudioFlinger.cpp
+++ b/media/libaudioclient/IAudioFlinger.cpp
@@ -918,6 +918,11 @@
return OK;
}
+status_t AudioFlingerClientAdapter::setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMuted) {
+ return statusTFromBinderStatus(mDelegate->setTracksInternalMute(tracksInternalMuted));
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
// AudioFlingerServerAdapter
AudioFlingerServerAdapter::AudioFlingerServerAdapter(
@@ -1477,4 +1482,9 @@
return Status::ok();
}
+Status AudioFlingerServerAdapter::setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) {
+ return Status::fromStatusT(mDelegate->setTracksInternalMute(tracksInternalMute));
+}
+
} // namespace android
diff --git a/media/libaudioclient/aidl/android/media/IAudioFlingerService.aidl b/media/libaudioclient/aidl/android/media/IAudioFlingerService.aidl
index 31d3af5..e8fcf77 100644
--- a/media/libaudioclient/aidl/android/media/IAudioFlingerService.aidl
+++ b/media/libaudioclient/aidl/android/media/IAudioFlingerService.aidl
@@ -41,6 +41,7 @@
import android.media.ISoundDoseCallback;
import android.media.MicrophoneInfoFw;
import android.media.RenderPosition;
+import android.media.TrackInternalMuteInfo;
import android.media.TrackSecondaryOutputInfo;
import android.media.audio.common.AudioChannelLayout;
import android.media.audio.common.AudioFormatDescription;
@@ -293,6 +294,11 @@
*/
AudioPortFw getAudioMixPort(in AudioPortFw devicePort, in AudioPortFw mixPort);
+ /**
+ * Set internal mute for a list of tracks.
+ */
+ void setTracksInternalMute(in TrackInternalMuteInfo[] tracksInternalMute);
+
// When adding a new method, please review and update
// IAudioFlinger.h AudioFlingerServerAdapter::Delegate::TransactionCode
// AudioFlinger.cpp AudioFlinger::onTransactWrapper()
diff --git a/media/libaudioclient/aidl/android/media/TrackInternalMuteInfo.aidl b/media/libaudioclient/aidl/android/media/TrackInternalMuteInfo.aidl
new file mode 100644
index 0000000..05b1fa4
--- /dev/null
+++ b/media/libaudioclient/aidl/android/media/TrackInternalMuteInfo.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+parcelable TrackInternalMuteInfo {
+ /* Interpreted as audio_port_handle_t. */
+ int portId;
+ boolean muted;
+}
diff --git a/media/libaudioclient/include/media/IAudioFlinger.h b/media/libaudioclient/include/media/IAudioFlinger.h
index 5a1e037..860a0bc 100644
--- a/media/libaudioclient/include/media/IAudioFlinger.h
+++ b/media/libaudioclient/include/media/IAudioFlinger.h
@@ -60,6 +60,7 @@
#include "android/media/OpenInputResponse.h"
#include "android/media/OpenOutputRequest.h"
#include "android/media/OpenOutputResponse.h"
+#include "android/media/TrackInternalMuteInfo.h"
#include "android/media/TrackSecondaryOutputInfo.h"
namespace android {
@@ -388,6 +389,9 @@
virtual status_t getAudioMixPort(const struct audio_port_v7 *devicePort,
struct audio_port_v7 *mixPort) const = 0;
+
+ virtual status_t setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) = 0;
};
/**
@@ -504,6 +508,8 @@
status_t getAudioPolicyConfig(media::AudioPolicyConfig* output) override;
status_t getAudioMixPort(const struct audio_port_v7 *devicePort,
struct audio_port_v7 *mixPort) const override;
+ status_t setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) override;
private:
const sp<media::IAudioFlingerService> mDelegate;
@@ -606,6 +612,7 @@
GET_AUDIO_POLICY_CONFIG =
media::BnAudioFlingerService::TRANSACTION_getAudioPolicyConfig,
GET_AUDIO_MIX_PORT = media::BnAudioFlingerService::TRANSACTION_getAudioMixPort,
+ SET_TRACKS_INTERNAL_MUTE = media::BnAudioFlingerService::TRANSACTION_setTracksInternalMute,
};
protected:
@@ -742,6 +749,8 @@
Status getAudioMixPort(const media::AudioPortFw& devicePort,
const media::AudioPortFw& mixPort,
media::AudioPortFw* _aidl_return) override;
+ Status setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) override;
private:
const sp<AudioFlingerServerAdapter::Delegate> mDelegate;
};
diff --git a/media/libaudiohal/impl/StreamHalAidl.h b/media/libaudiohal/impl/StreamHalAidl.h
index fff7a92..4523cc5 100644
--- a/media/libaudiohal/impl/StreamHalAidl.h
+++ b/media/libaudiohal/impl/StreamHalAidl.h
@@ -95,7 +95,8 @@
size_t getBufferSizeBytes() const { return mFrameSizeBytes * mBufferSizeFrames; }
size_t getBufferSizeFrames() const { return mBufferSizeFrames; }
size_t getBufferDurationMs(int32_t sampleRate) const {
- return sampleRate != 0 ? mBufferSizeFrames * MILLIS_PER_SECOND / sampleRate : 0;
+ auto bufferSize = mIsMmapped ? getMmapBurstSize() : mBufferSizeFrames;
+ return sampleRate != 0 ? bufferSize * MILLIS_PER_SECOND / sampleRate : 0;
}
CommandMQ* getCommandMQ() const { return mCommandMQ.get(); }
DataMQ* getDataMQ() const { return mDataMQ.get(); }
@@ -104,7 +105,7 @@
bool isAsynchronous() const { return mIsAsynchronous; }
bool isMmapped() const { return mIsMmapped; }
const MmapBufferDescriptor& getMmapBufferDescriptor() const { return mMmapBufferDescriptor; }
-
+ size_t getMmapBurstSize() const { return mMmapBufferDescriptor.burstSizeFrames;}
private:
static std::unique_ptr<DataMQ> maybeCreateDataMQ(
const ::aidl::android::hardware::audio::core::StreamDescriptor& descriptor) {
diff --git a/media/libstagefright/data/media_codecs_sw.xml b/media/libstagefright/data/media_codecs_sw.xml
index d50bc1e..6fb9232 100644
--- a/media/libstagefright/data/media_codecs_sw.xml
+++ b/media/libstagefright/data/media_codecs_sw.xml
@@ -203,8 +203,7 @@
<Feature name="dynamic-color-aspects" />
<Attribute name="software-codec" />
</MediaCodec>
- <MediaCodec name="c2.android.av1.decoder" type="video/av01" variant="slow-cpu,!slow-cpu">
- <!-- TODO: implement a mechanism to prevent AV1 Decoder usage on pre-U devices -->
+ <MediaCodec name="c2.android.av1-dav1d.decoder" type="video/av01" variant="slow-cpu,!slow-cpu">
<Limit name="alignment" value="1x1" />
<Limit name="block-size" value="16x16" />
<Variant name="!slow-cpu">
@@ -224,7 +223,8 @@
<Feature name="low-latency" />
<Attribute name="software-codec" />
</MediaCodec>
- <MediaCodec name="c2.android.av1-dav1d.decoder" type="video/av01" variant="slow-cpu,!slow-cpu" rank="1024">
+ <MediaCodec name="c2.android.av1.decoder" type="video/av01" variant="slow-cpu,!slow-cpu" rank="1024">
+ <!-- TODO: implement a mechanism to prevent AV1 Decoder usage on pre-U devices -->
<Limit name="alignment" value="1x1" />
<Limit name="block-size" value="16x16" />
<Variant name="!slow-cpu">
diff --git a/media/module/extractors/mp4/MPEG4Extractor.cpp b/media/module/extractors/mp4/MPEG4Extractor.cpp
index cb2994e..b3707c8 100644
--- a/media/module/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/module/extractors/mp4/MPEG4Extractor.cpp
@@ -1615,6 +1615,39 @@
mLastTrack->timescale = ntohl(timescale);
+ // 14496-12 says all ones means indeterminate, but some files seem to use
+ // 0 instead. We treat both the same.
+ int64_t duration = 0;
+ if (version == 1) {
+ if (mDataSource->readAt(
+ timescale_offset + 4, &duration, sizeof(duration))
+ < (ssize_t)sizeof(duration)) {
+ return ERROR_IO;
+ }
+ if (duration != -1) {
+ duration = ntoh64(duration);
+ }
+ } else {
+ uint32_t duration32;
+ if (mDataSource->readAt(
+ timescale_offset + 4, &duration32, sizeof(duration32))
+ < (ssize_t)sizeof(duration32)) {
+ return ERROR_IO;
+ }
+ if (duration32 != 0xffffffff) {
+ duration = ntohl(duration32);
+ }
+ }
+ if (duration != 0 && mLastTrack->timescale != 0) {
+ long double durationUs = ((long double)duration * 1000000) / mLastTrack->timescale;
+ if (durationUs < 0 || durationUs > INT64_MAX) {
+ ALOGE("cannot represent %lld * 1000000 / %lld in 64 bits",
+ (long long) duration, (long long) mLastTrack->timescale);
+ return ERROR_MALFORMED;
+ }
+ AMediaFormat_setInt64(mLastTrack->meta, AMEDIAFORMAT_KEY_DURATION, durationUs);
+ }
+
uint8_t lang[2];
off64_t lang_offset;
if (version == 1) {
@@ -3874,18 +3907,17 @@
}
int32_t id;
- int64_t duration;
if (version == 1) {
// we can get ctime value from U64_AT(&buffer[4])
// we can get mtime value from U64_AT(&buffer[12])
id = U32_AT(&buffer[20]);
- duration = U64_AT(&buffer[28]);
+ // we can get duration value from U64_AT(&buffer[28])
} else if (version == 0) {
// we can get ctime value from U32_AT(&buffer[4])
// we can get mtime value from U32_AT(&buffer[8])
id = U32_AT(&buffer[12]);
- duration = U32_AT(&buffer[20]);
+ // we can get duration value from U32_AT(&buffer[20])
} else {
return ERROR_UNSUPPORTED;
}
@@ -3894,15 +3926,6 @@
return ERROR_MALFORMED;
AMediaFormat_setInt32(mLastTrack->meta, AMEDIAFORMAT_KEY_TRACK_ID, id);
- if (duration != 0 && mHeaderTimescale != 0) {
- long double durationUs = ((long double)duration * 1000000) / mHeaderTimescale;
- if (durationUs < 0 || durationUs > INT64_MAX) {
- ALOGE("cannot represent %lld * 1000000 / %lld in 64 bits",
- (long long) duration, (long long) mHeaderTimescale);
- return ERROR_MALFORMED;
- }
- AMediaFormat_setInt64(mLastTrack->meta, AMEDIAFORMAT_KEY_DURATION, durationUs);
- }
size_t matrixOffset = dynSize + 16;
int32_t a00 = U32_AT(&buffer[matrixOffset]);
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index e8da710..af67a56 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -4962,6 +4962,23 @@
return mPatchPanel->getAudioMixPort_l(devicePort, mixPort);
}
+status_t AudioFlinger::setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) {
+ audio_utils::lock_guard _l(mutex());
+ ALOGV("%s", __func__);
+
+ std::map<audio_port_handle_t, bool> tracksInternalMuteMap;
+ for (const auto& trackInternalMute : tracksInternalMute) {
+ audio_port_handle_t portId = VALUE_OR_RETURN_STATUS(
+ aidl2legacy_int32_t_audio_port_handle_t(trackInternalMute.portId));
+ tracksInternalMuteMap.emplace(portId, trackInternalMute.muted);
+ }
+ for (size_t i = 0; i < mPlaybackThreads.size() && !tracksInternalMuteMap.empty(); i++) {
+ mPlaybackThreads.valueAt(i)->setTracksInternalMute(&tracksInternalMuteMap);
+ }
+ return NO_ERROR;
+}
+
// ----------------------------------------------------------------------------
status_t AudioFlinger::onTransactWrapper(TransactionCode code,
@@ -4996,6 +5013,7 @@
case TransactionCode::INVALIDATE_TRACKS:
case TransactionCode::GET_AUDIO_POLICY_CONFIG:
case TransactionCode::GET_AUDIO_MIX_PORT:
+ case TransactionCode::SET_TRACKS_INTERNAL_MUTE:
ALOGW("%s: transaction %d received from PID %d",
__func__, static_cast<int>(code), IPCThreadState::self()->getCallingPid());
// return status only for non void methods
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 6acd194..4711dc5 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -259,6 +259,10 @@
status_t getAudioMixPort(const struct audio_port_v7* devicePort,
struct audio_port_v7* mixPort) const final EXCLUDES_AudioFlinger_Mutex;
+ status_t setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) final
+ EXCLUDES_AudioFlinger_Mutex;
+
status_t onTransactWrapper(TransactionCode code, const Parcel& data, uint32_t flags,
const std::function<status_t()>& delegate) final EXCLUDES_AudioFlinger_Mutex;
diff --git a/services/audioflinger/IAfThread.h b/services/audioflinger/IAfThread.h
index 4b6ab89..204cc63 100644
--- a/services/audioflinger/IAfThread.h
+++ b/services/audioflinger/IAfThread.h
@@ -536,6 +536,9 @@
virtual const std::atomic<int64_t>& framesWritten() const = 0;
virtual bool usesHwAvSync() const = 0;
+
+ virtual void setTracksInternalMute(std::map<audio_port_handle_t, bool>* tracksInternalMute)
+ EXCLUDES_ThreadBase_Mutex = 0;
};
class IAfDirectOutputThread : public virtual IAfPlaybackThread {
diff --git a/services/audioflinger/IAfTrack.h b/services/audioflinger/IAfTrack.h
index a0d20ac..168bcca 100644
--- a/services/audioflinger/IAfTrack.h
+++ b/services/audioflinger/IAfTrack.h
@@ -427,6 +427,10 @@
virtual FillingStatus& fillingStatus() = 0;
virtual int8_t& retryCount() = 0;
virtual FastTrackUnderruns& fastTrackUnderruns() = 0;
+
+ // Internal mute, this is currently only used for bit-perfect playback
+ virtual bool getInternalMute() const = 0;
+ virtual void setInternalMute(bool muted) = 0;
};
// playback track, used by DuplicatingThread
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index 89ee5c0..1e1b6d2 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -220,6 +220,8 @@
*/
void processMuteEvent_l(const sp<IAudioManager>& audioManager, mute_state_t muteState) final;
+ bool getInternalMute() const final { return mInternalMute; }
+ void setInternalMute(bool muted) final { mInternalMute = muted; }
protected:
DISALLOW_COPY_AND_ASSIGN(Track);
@@ -401,6 +403,8 @@
// access these two variables only when holding player thread lock.
std::unique_ptr<os::PersistableBundle> mMuteEventExtras;
mute_state_t mMuteState;
+
+ bool mInternalMute = false;
}; // end of Track
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index d45fca1..d59697d 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -33,6 +33,7 @@
#include <afutils/Vibrator.h>
#include <audio_utils/MelProcessor.h>
#include <audio_utils/Metadata.h>
+#include <com_android_media_audioserver.h>
#ifdef DEBUG_CPU_USAGE
#include <audio_utils/Statistics.h>
#include <cpustats/ThreadCpuUsage.h>
@@ -5774,6 +5775,11 @@
vlf *= volume;
vrf *= volume;
+ if (track->getInternalMute()) {
+ vlf = 0.f;
+ vrf = 0.f;
+ }
+
track->setFinalVolume(vlf, vrf);
++fastTracks;
} else {
@@ -5973,6 +5979,11 @@
vaf = v * sendLevel * (1. / MAX_GAIN_INT);
}
+ if (track->getInternalMute()) {
+ vrf = 0.f;
+ vlf = 0.f;
+ }
+
track->setFinalVolume(vlf, vrf);
// Delegate volume control to effect in track effect chain if needed
@@ -11356,14 +11367,15 @@
// If there is only one active track and it is bit-perfect, enable tee buffer.
float volumeLeft = 1.0f;
float volumeRight = 1.0f;
- if (mActiveTracks.size() == 1 && mActiveTracks[0]->isBitPerfect()) {
- const int trackId = mActiveTracks[0]->id();
+ if (sp<IAfTrack> bitPerfectTrack = getTrackToStreamBitPerfectly_l();
+ bitPerfectTrack != nullptr) {
+ const int trackId = bitPerfectTrack->id();
mAudioMixer->setParameter(
trackId, AudioMixer::TRACK, AudioMixer::TEE_BUFFER, (void *)mSinkBuffer);
mAudioMixer->setParameter(
trackId, AudioMixer::TRACK, AudioMixer::TEE_BUFFER_FRAME_COUNT,
(void *)(uintptr_t)mNormalFrameCount);
- mActiveTracks[0]->getFinalVolume(&volumeLeft, &volumeRight);
+ bitPerfectTrack->getFinalVolume(&volumeLeft, &volumeRight);
mIsBitPerfect = true;
} else {
mIsBitPerfect = false;
@@ -11388,4 +11400,39 @@
mHasDataCopiedToSinkBuffer = mIsBitPerfect;
}
+void BitPerfectThread::setTracksInternalMute(
+ std::map<audio_port_handle_t, bool>* tracksInternalMute) {
+ for (auto& track : mTracks) {
+ if (auto it = tracksInternalMute->find(track->portId()); it != tracksInternalMute->end()) {
+ track->setInternalMute(it->second);
+ tracksInternalMute->erase(it);
+ }
+ }
+}
+
+sp<IAfTrack> BitPerfectThread::getTrackToStreamBitPerfectly_l() {
+ if (com::android::media::audioserver::
+ fix_concurrent_playback_behavior_with_bit_perfect_client()) {
+ sp<IAfTrack> bitPerfectTrack = nullptr;
+ bool allOtherTracksMuted = true;
+ // Return the bit perfect track if all other tracks are muted
+ for (const auto& track : mActiveTracks) {
+ if (track->isBitPerfect()) {
+ bitPerfectTrack = track;
+ } else if (track->getFinalVolume() != 0.f) {
+ allOtherTracksMuted = false;
+ if (bitPerfectTrack != nullptr) {
+ break;
+ }
+ }
+ }
+ return allOtherTracksMuted ? bitPerfectTrack : nullptr;
+ } else {
+ if (mActiveTracks.size() == 1 && mActiveTracks[0]->isBitPerfect()) {
+ return mActiveTracks[0];
+ }
+ }
+ return nullptr;
+}
+
} // namespace android
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 86e1894..7e9bef1 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -1200,6 +1200,11 @@
}
return mHalStarted;
}
+
+ void setTracksInternalMute(std::map<audio_port_handle_t, bool>* /* tracksInternalMute */)
+ override EXCLUDES_ThreadBase_Mutex {
+ // Do nothing. It is only used for bit perfect thread
+ }
protected:
// updated by readOutputParameters_l()
size_t mNormalFrameCount; // normal mixer and effects
@@ -2449,12 +2454,17 @@
BitPerfectThread(const sp<IAfThreadCallback>& afThreadCallback, AudioStreamOut *output,
audio_io_handle_t id, bool systemReady);
+ void setTracksInternalMute(std::map<audio_port_handle_t, bool>* tracksInternalMuted)
+ final EXCLUDES_ThreadBase_Mutex;
+
protected:
mixer_state prepareTracks_l(Vector<sp<IAfTrack>>* tracksToRemove) final
REQUIRES(mutex(), ThreadBase_ThreadLoop);
void threadLoop_mix() final REQUIRES(ThreadBase_ThreadLoop);
private:
+ sp<IAfTrack> getTrackToStreamBitPerfectly_l() REQUIRES(mutex());
+
// These variables are only accessed on the threadLoop; hence need no mutex.
bool mIsBitPerfect GUARDED_BY(ThreadBase_ThreadLoop) = false;
float mVolumeLeft GUARDED_BY(ThreadBase_ThreadLoop) = 0.f;
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index c40f795..f8cfc12 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -913,7 +913,7 @@
" Format Chn mask SRate "
"ST Usg CT "
" G db L dB R dB VS dB "
- " Server FrmCnt FrmRdy F Underruns Flushed BitPerfect"
+ " Server FrmCnt FrmRdy F Underruns Flushed BitPerfect InternalMute"
"%s\n",
isServerLatencySupported() ? " Latency" : "");
}
@@ -999,7 +999,7 @@
"%08X %08X %6u "
"%2u %3x %2x "
"%5.2g %5.2g %5.2g %5.2g%c "
- "%08X %6zu%c %6zu %c %9u%c %7u %10s",
+ "%08X %6zu%c %6zu %c %9u%c %7u %10s %12s",
active ? "yes" : "no",
(mClient == 0) ? getpid() : mClient->pid(),
mSessionId,
@@ -1029,7 +1029,8 @@
mAudioTrackServerProxy->getUnderrunFrames(),
nowInUnderrun,
(unsigned)mAudioTrackServerProxy->framesFlushed() % 10000000,
- isBitPerfect() ? "true" : "false"
+ isBitPerfect() ? "true" : "false",
+ getInternalMute() ? "true" : "false"
);
if (isServerLatencySupported()) {
diff --git a/services/audiopolicy/AudioPolicyInterface.h b/services/audiopolicy/AudioPolicyInterface.h
index 8f17ffc..de6eed8 100644
--- a/services/audiopolicy/AudioPolicyInterface.h
+++ b/services/audiopolicy/AudioPolicyInterface.h
@@ -18,6 +18,7 @@
#define ANDROID_AUDIOPOLICY_INTERFACE_H
#include <android/media/DeviceConnectedState.h>
+#include <android/media/TrackInternalMuteInfo.h>
#include <media/AudioCommonTypes.h>
#include <media/AudioContainers.h>
#include <media/AudioDeviceTypeAddr.h>
@@ -585,6 +586,9 @@
// Get the attributes of the mix port when connecting to the given device port.
virtual status_t getAudioMixPort(const struct audio_port_v7 *devicePort,
struct audio_port_v7 *mixPort) = 0;
+
+ virtual status_t setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) = 0;
};
// These are the signatures of createAudioPolicyManager/destroyAudioPolicyManager
diff --git a/services/audiopolicy/common/include/policy.h b/services/audiopolicy/common/include/policy.h
index 4643bd1..04d5362 100644
--- a/services/audiopolicy/common/include/policy.h
+++ b/services/audiopolicy/common/include/policy.h
@@ -17,6 +17,7 @@
#pragma once
#include <system/audio.h>
+#include <set>
#include <vector>
#include <media/AudioContainers.h>
@@ -27,6 +28,10 @@
static const audio_attributes_t defaultAttr = AUDIO_ATTRIBUTES_INITIALIZER;
+static const std::set<audio_usage_t > gHighPriorityUseCases = {
+ AUDIO_USAGE_ALARM, AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE
+};
+
} // namespace android
static const audio_format_t gDynamicFormat = AUDIO_FORMAT_DEFAULT;
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 00958aa..914f3fe 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -29,6 +29,7 @@
#include "ClientDescriptor.h"
#include "DeviceDescriptor.h"
#include "PolicyAudioPort.h"
+#include "PreferredMixerAttributesInfo.h"
#include <vector>
namespace android {
@@ -477,6 +478,16 @@
PortHandleVector getClientsForStream(audio_stream_type_t streamType) const;
+ bool isBitPerfect() const {
+ return (getFlags().output & AUDIO_OUTPUT_FLAG_BIT_PERFECT) != AUDIO_OUTPUT_FLAG_NONE;
+ }
+
+ /**
+ * Return true if there is any client with the same usage active on the given device.
+ * When the given device is null, return true if there is any client active.
+ */
+ bool isUsageActiveOnDevice(audio_usage_t usage, sp<DeviceDescriptor> device) const;
+
virtual std::string info() const override;
const sp<IOProfile> mProfile; // I/O profile this output derives from
@@ -489,7 +500,7 @@
audio_session_t mDirectClientSession; // session id of the direct output client
bool mPendingReopenToQueryProfiles = false;
audio_channel_mask_t mMixerChannelMask = AUDIO_CHANNEL_NONE;
- bool mUsePreferredMixerAttributes = false;
+ sp<PreferredMixerAttributesInfo> mPreferredAttrInfo = nullptr;
};
// Audio output driven by an input device directly.
@@ -616,6 +627,8 @@
*/
bool isAnyDeviceTypeActive(const DeviceTypeSet& deviceTypes) const;
+ bool isUsageActiveOnDevice(audio_usage_t usage, sp<DeviceDescriptor> device) const;
+
void dump(String8 *dst) const;
};
diff --git a/services/audiopolicy/common/managerdefinitions/include/ClientDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/ClientDescriptor.h
index fe90a1e..a596c43 100644
--- a/services/audiopolicy/common/managerdefinitions/include/ClientDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/ClientDescriptor.h
@@ -165,6 +165,18 @@
mIsInvalid = true;
}
+ bool getInternalMute() const { return mInternalMute; }
+
+ /**
+ * Set the internal mute for a client. Return true if the existing value is different from
+ * the given value.
+ */
+ bool setInternalMute(bool muted) {
+ const bool result = (mInternalMute != muted);
+ mInternalMute = muted;
+ return result;
+ }
+
private:
const audio_stream_type_t mStream;
const product_strategy_t mStrategy;
@@ -178,6 +190,7 @@
*/
uint32_t mActivityCount = 0;
bool mIsInvalid = false;
+ bool mInternalMute = false;
};
class RecordClientDescriptor: public ClientDescriptor
diff --git a/services/audiopolicy/common/managerdefinitions/include/PreferredMixerAttributesInfo.h b/services/audiopolicy/common/managerdefinitions/include/PreferredMixerAttributesInfo.h
index 9472481..a493e3c 100644
--- a/services/audiopolicy/common/managerdefinitions/include/PreferredMixerAttributesInfo.h
+++ b/services/audiopolicy/common/managerdefinitions/include/PreferredMixerAttributesInfo.h
@@ -44,6 +44,17 @@
void increaseActiveClient() { mActiveClientsCount++; }
void decreaseActiveClient() { mActiveClientsCount--; }
+ void resetActiveClient() { mActiveClientsCount = 0; }
+
+ bool isBitPerfect() const {
+ return (getFlags() & AUDIO_OUTPUT_FLAG_BIT_PERFECT) != AUDIO_OUTPUT_FLAG_NONE;
+ }
+
+ bool configMatches(const audio_config_t& config) const {
+ return config.format == mMixerAttributes.config.format &&
+ config.channel_mask == mMixerAttributes.config.channel_mask &&
+ config.sample_rate == mMixerAttributes.config.sample_rate;
+ }
void dump(String8 *dst);
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index acd0ff5..68b2e7b 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -792,6 +792,16 @@
mDevices = devices;
}
+bool SwAudioOutputDescriptor::isUsageActiveOnDevice(audio_usage_t usage,
+ sp<android::DeviceDescriptor> device) const {
+ if (device != nullptr && !mDevices.contains(device)) {
+ return false;
+ }
+ return std::any_of(mActiveClients.begin(), mActiveClients.end(),
+ [usage](sp<TrackClientDescriptor> client) {
+ return client->attributes().usage == usage; });
+}
+
// HwAudioOutputDescriptor implementation
HwAudioOutputDescriptor::HwAudioOutputDescriptor(const sp<SourceClientDescriptor>& source,
AudioPolicyClientInterface *clientInterface)
@@ -1017,6 +1027,17 @@
return clientsForStream;
}
+bool SwAudioOutputCollection::isUsageActiveOnDevice(audio_usage_t usage,
+ sp<android::DeviceDescriptor> device) const {
+ for (size_t i = 0; i < this->size(); i++) {
+ const sp<SwAudioOutputDescriptor> outputDesc = this->valueAt(i);
+ if (outputDesc->isUsageActiveOnDevice(usage, device)) {
+ return true;
+ }
+ }
+ return false;
+}
+
std::string SwAudioOutputDescriptor::info() const {
std::string result;
result.append("[" );
diff --git a/services/audiopolicy/common/managerdefinitions/src/ClientDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/ClientDescriptor.cpp
index 2aee501..667c189 100644
--- a/services/audiopolicy/common/managerdefinitions/src/ClientDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/ClientDescriptor.cpp
@@ -57,8 +57,8 @@
void TrackClientDescriptor::dump(String8 *dst, int spaces) const
{
ClientDescriptor::dump(dst, spaces);
- dst->appendFormat("%*sStream: %d; Flags: %08x; Refcount: %d\n", spaces, "",
- mStream, mFlags, mActivityCount);
+ dst->appendFormat("%*sStream: %d; Flags: %08x; Refcount: %d; InternalMute: %s\n",
+ spaces, "", mStream, mFlags, mActivityCount, mInternalMute ? "Yes" : "No");
dst->appendFormat("%*sDAP Primary Mix: %p\n", spaces, "", mPrimaryMix.promote().get());
if (!mSecondaryOutputs.empty()) {
dst->appendFormat("%*sDAP Secondary Outputs: ", spaces - 2, "");
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 7f4be79..87b6c3d 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -338,7 +338,7 @@
&& (!device_distinguishes_on_address(device->type())
// always force when disconnecting (a non-duplicated device)
|| (state == AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE));
- if (desc->mUsePreferredMixerAttributes && newDevices != desc->devices()) {
+ if (desc->mPreferredAttrInfo != nullptr && newDevices != desc->devices()) {
// If the device is using preferred mixer attributes, the output need to reopen
// with default configuration when the new selected devices are different from
// current routing devices
@@ -563,15 +563,31 @@
}
}
auto musicStrategy = streamToStrategy(AUDIO_STREAM_MUSIC);
+ uint32_t muteWaitMs = 0;
for (size_t i = 0; i < mOutputs.size(); i++) {
sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);
- // mute media strategies and delay device switch by the largest
- // This avoid sending the music tail into the earpiece or headset.
+ // mute media strategies to avoid sending the music tail into
+ // the earpiece or headset.
+ if (desc->isStrategyActive(musicStrategy)) {
+ uint32_t tempRecommendedMuteDuration = desc->getRecommendedMuteDurationMs();
+ uint32_t tempMuteDurationMs = tempRecommendedMuteDuration > 0 ?
+ tempRecommendedMuteDuration : desc->latency() * 4;
+ if (muteWaitMs < tempMuteDurationMs) {
+ muteWaitMs = tempMuteDurationMs;
+ }
+ }
setStrategyMute(musicStrategy, true, desc);
setStrategyMute(musicStrategy, false, desc, MUTE_TIME_MS,
mEngine->getOutputDevicesForAttributes(attributes_initializer(AUDIO_USAGE_MEDIA),
nullptr, true /*fromCache*/).types());
}
+ // Wait for the muted audio to propagate down the audio path see checkDeviceMuteStrategies().
+ // We assume that MUTE_TIME_MS is way larger than muteWaitMs so that unmuting still
+ // happens after the actual device switch.
+ if (muteWaitMs > 0) {
+ ALOGW_IF(MUTE_TIME_MS < muteWaitMs * 2, "%s excessive mute wait %d", __func__, muteWaitMs);
+ usleep(muteWaitMs * 1000);
+ }
// Toggle the device state: UNAVAILABLE -> AVAILABLE
// This will force reading again the device configuration
status_t status = setDeviceConnectionState(device,
@@ -917,15 +933,15 @@
for (size_t i = 0; i < mOutputs.size(); i++) {
sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);
DeviceVector newDevices = getNewOutputDevices(desc, true /*fromCache*/);
+ if (state != AUDIO_MODE_NORMAL && oldState == AUDIO_MODE_NORMAL
+ && desc->mPreferredAttrInfo != nullptr) {
+ // If the output is using preferred mixer attributes and the audio mode is not normal,
+ // the output need to reopen with default configuration.
+ outputsToReopen.emplace(mOutputs.keyAt(i), newDevices);
+ continue;
+ }
if (state != AUDIO_MODE_IN_CALL || (desc != mPrimaryOutput && !isTelephonyRxOrTx(desc))) {
bool forceRouting = !newDevices.isEmpty();
- if (desc->mUsePreferredMixerAttributes && newDevices != desc->devices()) {
- // If the device is using preferred mixer attributes, the output need to reopen
- // with default configuration when the new selected devices are different from
- // current routing devices.
- outputsToReopen.emplace(mOutputs.keyAt(i), newDevices);
- continue;
- }
setOutputDevices(__func__, desc, newDevices, forceRouting, 0 /*delayMs*/, nullptr,
true /*requiresMuteCheck*/, !forceRouting /*requiresVolumeCheck*/);
}
@@ -1337,23 +1353,40 @@
// Only use preferred mixer if the uid matches or the preferred mixer is bit-perfect
// and it is currently active.
if (info != nullptr && info->getUid() != uid &&
- ((info->getFlags() & AUDIO_OUTPUT_FLAG_BIT_PERFECT) == AUDIO_OUTPUT_FLAG_NONE ||
- info->getActiveClientCount() == 0)) {
+ (!info->isBitPerfect() || info->getActiveClientCount() == 0)) {
info = nullptr;
}
+ if (com::android::media::audioserver::
+ fix_concurrent_playback_behavior_with_bit_perfect_client()) {
+ if (info != nullptr && info->getUid() == uid &&
+ info->configMatches(*config) &&
+ (mEngine->getPhoneState() != AUDIO_MODE_NORMAL ||
+ std::any_of(gHighPriorityUseCases.begin(), gHighPriorityUseCases.end(),
+ [this, &outputDevices](audio_usage_t usage) {
+ return mOutputs.isUsageActiveOnDevice(
+ usage, outputDevices[0]); }))) {
+ // Bit-perfect request is not allowed when the phone mode is not normal or
+ // there is any higher priority user case active.
+ return INVALID_OPERATION;
+ }
+ }
}
*output = getOutputForDevices(outputDevices, session, resultAttr, config,
flags, isSpatialized, info, resultAttr->flags & AUDIO_FLAG_MUTE_HAPTIC);
// The client will be active if the client is currently preferred mixer owner and the
// requested configuration matches the preferred mixer configuration.
*isBitPerfect = (info != nullptr
- && (info->getFlags() & AUDIO_OUTPUT_FLAG_BIT_PERFECT) != AUDIO_OUTPUT_FLAG_NONE
+ && info->isBitPerfect()
&& info->getUid() == uid
&& *output != AUDIO_IO_HANDLE_NONE
// When bit-perfect output is selected for the preferred mixer attributes owner,
// only need to consider the config matches.
&& mOutputs.valueFor(*output)->isConfigurationMatched(
clientConfig, AUDIO_OUTPUT_FLAG_NONE));
+
+ if (*isBitPerfect) {
+ *flags = (audio_output_flags_t)(*flags | AUDIO_OUTPUT_FLAG_BIT_PERFECT);
+ }
}
if (*output == AUDIO_IO_HANDLE_NONE) {
AudioProfileVector profiles;
@@ -1708,6 +1741,24 @@
// at this stage we should ignore the DIRECT flag as no direct output could be
// found earlier
*flags = (audio_output_flags_t) (*flags & ~AUDIO_OUTPUT_FLAG_DIRECT);
+ if (com::android::media::audioserver::
+ fix_concurrent_playback_behavior_with_bit_perfect_client()) {
+ // If the preferred mixer attributes is null, do not select the bit-perfect output
+ // unless the bit-perfect output is the only output.
+ // The bit-perfect output can exist while the passed in preferred mixer attributes
+ // info is null when it is a high priority client. The high priority clients are
+ // ringtone or alarm, which is not a bit-perfect use case.
+ size_t i = 0;
+ while (i < outputs.size() && outputs.size() > 1) {
+ auto desc = mOutputs.valueFor(outputs[i]);
+ // The output descriptor must not be null here.
+ if (desc->isBitPerfect()) {
+ outputs.removeItemsAt(i);
+ } else {
+ i += 1;
+ }
+ }
+ }
output = selectOutput(
outputs, *flags, config->format, channelMask, config->sample_rate, session);
}
@@ -2191,6 +2242,20 @@
ALOGV("startOutput() output %d, stream %d, session %d",
outputDesc->mIoHandle, client->stream(), client->session());
+ if (com::android::media::audioserver::fix_concurrent_playback_behavior_with_bit_perfect_client()
+ && gHighPriorityUseCases.count(client->attributes().usage) != 0
+ && outputDesc->isBitPerfect()) {
+ // Usually, APM selects bit-perfect output for high priority use cases only when
+ // bit-perfect output is the only output that can be routed to the selected device.
+ // However, here is no need to play high priority use cases such as ringtone and alarm
+ // on the bit-perfect path. Reopen the output and return DEAD_OBJECT so that the client
+ // can attach to new output.
+ ALOGD("%s: reopen bit-perfect output as high priority use case(%d) is starting",
+ __func__, client->stream());
+ reopenOutput(outputDesc, nullptr /*config*/, AUDIO_OUTPUT_FLAG_NONE, __func__);
+ return DEAD_OBJECT;
+ }
+
status_t status = outputDesc->start();
if (status != NO_ERROR) {
return status;
@@ -2209,7 +2274,6 @@
ALOGE("%s unable to open output with default config", __func__);
return status;
}
- desc->mUsePreferredMixerAttributes = true;
}
return status;
}
@@ -2233,14 +2297,13 @@
if (desc == nullptr) {
return BAD_VALUE;
}
- desc->mUsePreferredMixerAttributes = true;
+ desc->mPreferredAttrInfo = info;
// Intentionally return error to let the client side resending request for
// creating and starting.
return DEAD_OBJECT;
}
info->increaseActiveClient();
- if (info->getActiveClientCount() == 1 &&
- (info->getFlags() & AUDIO_OUTPUT_FLAG_BIT_PERFECT) != AUDIO_OUTPUT_FLAG_NONE) {
+ if (info->getActiveClientCount() == 1 && info->isBitPerfect()) {
// If it is first bit-perfect client, reroute all clients that will be routed to
// the bit-perfect sink so that it is guaranteed only bit-perfect stream is active.
PortHandleVector clientsToInvalidate;
@@ -2270,6 +2333,15 @@
usleep(delayMs * 1000);
}
+ if (status == NO_ERROR &&
+ outputDesc->mPreferredAttrInfo != nullptr &&
+ outputDesc->isBitPerfect() &&
+ com::android::media::audioserver::
+ fix_concurrent_playback_behavior_with_bit_perfect_client()) {
+ // A new client is started on bit-perfect output, update all clients internal mute.
+ updateClientsInternalMute(outputDesc);
+ }
+
return status;
}
@@ -2374,6 +2446,11 @@
followsSameRouting(clientAttr, attributes_initializer(AUDIO_USAGE_NOTIFICATION)) ||
(beaconMuteLatency > 0));
uint32_t waitMs = beaconMuteLatency;
+ const bool needToCloseBitPerfectOutput =
+ (com::android::media::audioserver::
+ fix_concurrent_playback_behavior_with_bit_perfect_client() &&
+ gHighPriorityUseCases.count(clientAttr.usage) != 0);
+ std::vector<sp<SwAudioOutputDescriptor>> outputsToReopen;
for (size_t i = 0; i < mOutputs.size(); i++) {
sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);
if (desc != outputDesc) {
@@ -2410,15 +2487,22 @@
// Note restoring AudioTracks onto this output needs to invoke
// a volume ramp if there is no mute.
requiresMuteCheck |= sharedDevice && isActive;
+
+ if (needToCloseBitPerfectOutput && desc->isBitPerfect()) {
+ outputsToReopen.push_back(desc);
+ }
}
}
- if (outputDesc->mUsePreferredMixerAttributes && devices != outputDesc->devices()) {
+ if (outputDesc->mPreferredAttrInfo != nullptr && devices != outputDesc->devices()) {
// If the output is open with preferred mixer attributes, but the routed device is
// changed when calling this function, returning DEAD_OBJECT to indicate routing
// changed.
return DEAD_OBJECT;
}
+ for (auto& outputToReopen : outputsToReopen) {
+ reopenOutput(outputToReopen, nullptr /*config*/, AUDIO_OUTPUT_FLAG_NONE, __func__);
+ }
const uint32_t muteWaitMs =
setOutputDevices(__func__, outputDesc, devices, force, 0, nullptr,
requiresMuteCheck);
@@ -2497,7 +2581,7 @@
getAudioDeviceOutLeAudioUnicastSet()).isEmpty()))) {
DeviceVector newDevices = getNewOutputDevices(desc, false /*fromCache*/);
bool force = desc->devices() != newDevices;
- if (desc->mUsePreferredMixerAttributes && force) {
+ if (desc->mPreferredAttrInfo != nullptr && force) {
// If the device is using preferred mixer attributes, the output need to reopen
// with default configuration when the new selected devices are different from
// current routing devices.
@@ -2545,12 +2629,21 @@
if (outputDesc->devices().size() == 1) {
sp<PreferredMixerAttributesInfo> info = getPreferredMixerAttributesInfo(
outputDesc->devices()[0]->getId(), client->strategy());
+ bool outputReopened = false;
if (info != nullptr && info->getUid() == client->uid()) {
info->decreaseActiveClient();
if (info->getActiveClientCount() == 0) {
reopenOutput(outputDesc, nullptr /*config*/, AUDIO_OUTPUT_FLAG_NONE, __func__);
+ outputReopened = true;
}
}
+ if (com::android::media::audioserver::
+ fix_concurrent_playback_behavior_with_bit_perfect_client() &&
+ !outputReopened && outputDesc->isBitPerfect()) {
+ // Only need to update the clients' internal mute when the output is bit-perfect and it
+ // is not reopened.
+ updateClientsInternalMute(outputDesc);
+ }
}
return status;
}
@@ -2622,7 +2715,7 @@
DeviceVector newDevices2 = getNewOutputDevices(desc, false /*fromCache*/);
bool force = desc->devices() != newDevices2;
- if (desc->mUsePreferredMixerAttributes && force) {
+ if (desc->mPreferredAttrInfo != nullptr && force) {
// If the device is using preferred mixer attributes, the output need to
// reopen with default configuration when the new selected devices are
// different from current routing devices.
@@ -4114,7 +4207,7 @@
// preventing the force re-routing in case of default dev that distinguishes on address.
// Let's give back to engine full device choice decision however.
bool forceRouting = !newDevices.isEmpty();
- if (outputDesc->mUsePreferredMixerAttributes && newDevices != outputDesc->devices()) {
+ if (outputDesc->mPreferredAttrInfo != nullptr && newDevices != outputDesc->devices()) {
// If the device is using preferred mixer attributes, the output need to reopen
// with default configuration when the new selected devices are different from
// current routing devices.
@@ -4715,7 +4808,7 @@
const auto output = mOutputs.valueAt(i);
if (output->mProfile == profile && output->devices().onlyContainsDevice(deviceDescriptor)) {
if (output->isConfigurationMatched(mixerAttributes->config, flags)) {
- output->mUsePreferredMixerAttributes = true;
+ output->mPreferredAttrInfo = mixerAttrInfo;
} else {
for (const auto &client: output->getActiveClients()) {
if (client->uid() == uid && client->strategy() == strategy) {
@@ -4737,7 +4830,7 @@
ALOGE("%s, failed to reopen output with preferred mixer attributes", __func__);
continue;
}
- desc->mUsePreferredMixerAttributes = true;
+ desc->mPreferredAttrInfo = mixerAttrInfo;
}
return NO_ERROR;
@@ -4753,8 +4846,7 @@
}
if (activeBitPerfectPreferred) {
for (auto [strategy, info] : it->second) {
- if ((info->getFlags() & AUDIO_OUTPUT_FLAG_BIT_PERFECT) != AUDIO_OUTPUT_FLAG_NONE
- && info->getActiveClientCount() != 0) {
+ if (info->isBitPerfect() && info->getActiveClientCount() != 0) {
return info;
}
}
@@ -5554,7 +5646,7 @@
invalidateStreams(mEngine->getStreamTypesForProductStrategy(ps));
} else {
DeviceVector newDevices = getNewOutputDevices(outputDesc, false /*fromCache*/);
- if (outputDesc->mUsePreferredMixerAttributes && outputDesc->devices() != newDevices) {
+ if (outputDesc->mPreferredAttrInfo != nullptr && outputDesc->devices() != newDevices) {
// If the device is using preferred mixer attributes, the output need to reopen
// with default configuration when the new selected devices are different from
// current routing devices.
@@ -6925,8 +7017,7 @@
closingOutput->stop();
}
closingOutput->close();
- if ((closingOutput->getFlags().output & AUDIO_OUTPUT_FLAG_BIT_PERFECT)
- == AUDIO_OUTPUT_FLAG_BIT_PERFECT) {
+ if (closingOutput->isBitPerfect()) {
for (const auto device : closingOutput->devices()) {
device->setPreferredConfig(nullptr);
}
@@ -6958,6 +7049,10 @@
setMsdOutputPatches();
}
}
+
+ if (closingOutput->mPreferredAttrInfo != nullptr) {
+ closingOutput->mPreferredAttrInfo->resetActiveClient();
+ }
}
void AudioPolicyManager::closeInput(audio_io_handle_t input)
@@ -8815,4 +8910,60 @@
mpClientInterface->invalidateTracks(clients);
}
+void AudioPolicyManager::updateClientsInternalMute(
+ const sp<android::SwAudioOutputDescriptor> &desc) {
+ if (!desc->isBitPerfect() ||
+ !com::android::media::audioserver::
+ fix_concurrent_playback_behavior_with_bit_perfect_client()) {
+ // This is only used for bit perfect output now.
+ return;
+ }
+ sp<TrackClientDescriptor> bitPerfectClient = nullptr;
+ bool bitPerfectClientInternalMute = false;
+ std::vector<media::TrackInternalMuteInfo> clientsInternalMute;
+ for (const sp<TrackClientDescriptor>& client : desc->getActiveClients()) {
+ if ((client->flags() & AUDIO_OUTPUT_FLAG_BIT_PERFECT) != AUDIO_OUTPUT_FLAG_NONE) {
+ bitPerfectClient = client;
+ continue;
+ }
+ bool muted = false;
+ if (client->stream() == AUDIO_STREAM_SYSTEM) {
+ // System sound is muted.
+ muted = true;
+ } else {
+ bitPerfectClientInternalMute = true;
+ }
+ if (client->setInternalMute(muted)) {
+ auto result = legacy2aidl_audio_port_handle_t_int32_t(client->portId());
+ if (!result.ok()) {
+ ALOGE("%s, failed to convert port id(%d) to aidl", __func__, client->portId());
+ continue;
+ }
+ media::TrackInternalMuteInfo info;
+ info.portId = result.value();
+ info.muted = client->getInternalMute();
+ clientsInternalMute.push_back(std::move(info));
+ }
+ }
+ if (bitPerfectClient != nullptr &&
+ bitPerfectClient->setInternalMute(bitPerfectClientInternalMute)) {
+ auto result = legacy2aidl_audio_port_handle_t_int32_t(bitPerfectClient->portId());
+ if (result.ok()) {
+ media::TrackInternalMuteInfo info;
+ info.portId = result.value();
+ info.muted = bitPerfectClient->getInternalMute();
+ clientsInternalMute.push_back(std::move(info));
+ } else {
+ ALOGE("%s, failed to convert port id(%d) of bit perfect client to aidl",
+ __func__, bitPerfectClient->portId());
+ }
+ }
+ if (!clientsInternalMute.empty()) {
+ if (status_t status = mpClientInterface->setTracksInternalMute(clientsInternalMute);
+ status != NO_ERROR) {
+ ALOGE("%s, failed to update tracks internal mute, err=%d", __func__, status);
+ }
+ }
+}
+
} // namespace android
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.h b/services/audiopolicy/managerdefault/AudioPolicyManager.h
index 2d015d3..83597d8 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.h
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.h
@@ -1386,6 +1386,8 @@
bool checkHapticCompatibilityOnSpatializerOutput(const audio_config_t* config,
audio_session_t sessionId) const;
+
+ void updateClientsInternalMute(const sp<SwAudioOutputDescriptor>& desc);
};
};
diff --git a/services/audiopolicy/service/AudioPolicyClientImpl.cpp b/services/audiopolicy/service/AudioPolicyClientImpl.cpp
index 6de71a3..f70dc52 100644
--- a/services/audiopolicy/service/AudioPolicyClientImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyClientImpl.cpp
@@ -352,4 +352,13 @@
return af->getAudioMixPort(devicePort, port);
}
+status_t AudioPolicyService::AudioPolicyClient::setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) {
+ sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
+ if (af == nullptr) {
+ return PERMISSION_DENIED;
+ }
+ return af->setTracksInternalMute(tracksInternalMute);
+}
+
} // namespace android
diff --git a/services/audiopolicy/service/AudioPolicyService.h b/services/audiopolicy/service/AudioPolicyService.h
index 5297e47..eb9d081 100644
--- a/services/audiopolicy/service/AudioPolicyService.h
+++ b/services/audiopolicy/service/AudioPolicyService.h
@@ -872,6 +872,9 @@
status_t getAudioMixPort(const struct audio_port_v7 *devicePort,
struct audio_port_v7 *port) override;
+ status_t setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) override;
+
private:
AudioPolicyService *mAudioPolicyService;
};
diff --git a/services/audiopolicy/tests/Android.bp b/services/audiopolicy/tests/Android.bp
index fc349ee..dc61115 100644
--- a/services/audiopolicy/tests/Android.bp
+++ b/services/audiopolicy/tests/Android.bp
@@ -40,6 +40,7 @@
static_libs: [
"android.media.audiopolicy-aconfig-cc",
"audioclient-types-aidl-cpp",
+ "com.android.media.audioserver-aconfig-cc",
"libaudiopolicycomponents",
"libflagtest",
"libgmock",
diff --git a/services/audiopolicy/tests/AudioPolicyManagerTestClient.h b/services/audiopolicy/tests/AudioPolicyManagerTestClient.h
index 7ef0266..30894c1 100644
--- a/services/audiopolicy/tests/AudioPolicyManagerTestClient.h
+++ b/services/audiopolicy/tests/AudioPolicyManagerTestClient.h
@@ -221,6 +221,15 @@
return NO_ERROR;
}
+ status_t setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& tracksInternalMute) override {
+ for (const auto& trackInternalMute : tracksInternalMute) {
+ mTracksInternalMute[(audio_port_handle_t)trackInternalMute.portId] =
+ trackInternalMute.muted;
+ }
+ return NO_ERROR;
+ }
+
void addSupportedFormat(audio_format_t format) {
mSupportedFormats.insert(format);
}
@@ -229,6 +238,11 @@
mSupportedChannelMasks.insert(channelMask);
}
+ bool getTrackInternalMute(audio_port_handle_t portId) {
+ auto it = mTracksInternalMute.find(portId);
+ return it == mTracksInternalMute.end() ? false : it->second;
+ }
+
private:
audio_module_handle_t mNextModuleHandle = AUDIO_MODULE_HANDLE_NONE + 1;
audio_io_handle_t mNextIoHandle = AUDIO_IO_HANDLE_NONE + 1;
@@ -241,6 +255,7 @@
std::vector<struct audio_port_v7> mDisconnectedDevicePorts;
std::set<audio_format_t> mSupportedFormats;
std::set<audio_channel_mask_t> mSupportedChannelMasks;
+ std::map<audio_port_handle_t, bool> mTracksInternalMute;
};
} // namespace android
diff --git a/services/audiopolicy/tests/AudioPolicyTestClient.h b/services/audiopolicy/tests/AudioPolicyTestClient.h
index e55e935..c15adcb 100644
--- a/services/audiopolicy/tests/AudioPolicyTestClient.h
+++ b/services/audiopolicy/tests/AudioPolicyTestClient.h
@@ -110,6 +110,11 @@
struct audio_port_v7 *mixPort __unused) override {
return INVALID_OPERATION;
}
+
+ status_t setTracksInternalMute(
+ const std::vector<media::TrackInternalMuteInfo>& /*tracksInternalMute*/) override {
+ return INVALID_OPERATION;
+ }
};
} // namespace android
diff --git a/services/audiopolicy/tests/audiopolicymanager_tests.cpp b/services/audiopolicy/tests/audiopolicymanager_tests.cpp
index 5dc039c..4f9e98e 100644
--- a/services/audiopolicy/tests/audiopolicymanager_tests.cpp
+++ b/services/audiopolicy/tests/audiopolicymanager_tests.cpp
@@ -29,6 +29,7 @@
#include <android-base/properties.h>
#include <android/content/AttributionSourceState.h>
#include <android_media_audiopolicy.h>
+#include <com_android_media_audioserver.h>
#include <flag_macros.h>
#include <hardware/audio_effect.h>
#include <media/AudioPolicy.h>
@@ -1155,129 +1156,6 @@
"", "", AUDIO_FORMAT_LDAC));
}
-TEST_F(AudioPolicyManagerTestWithConfigurationFile, BitPerfectPlayback) {
- const audio_format_t bitPerfectFormat = AUDIO_FORMAT_PCM_16_BIT;
- const audio_channel_mask_t bitPerfectChannelMask = AUDIO_CHANNEL_OUT_QUAD;
- const uint32_t bitPerfectSampleRate = 48000;
- mClient->addSupportedFormat(bitPerfectFormat);
- mClient->addSupportedChannelMask(bitPerfectChannelMask);
- ASSERT_EQ(NO_ERROR, mManager->setDeviceConnectionState(AUDIO_DEVICE_OUT_USB_DEVICE,
- AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
- "", "", AUDIO_FORMAT_DEFAULT));
- auto devices = mManager->getAvailableOutputDevices();
- audio_port_handle_t usbPortId = AUDIO_PORT_HANDLE_NONE;
- for (auto device : devices) {
- if (device->type() == AUDIO_DEVICE_OUT_USB_DEVICE) {
- usbPortId = device->getId();
- break;
- }
- }
- EXPECT_NE(AUDIO_PORT_HANDLE_NONE, usbPortId);
-
- const uid_t uid = 1234;
- const uid_t anotherUid = 5678;
- const audio_attributes_t mediaAttr = {
- .content_type = AUDIO_CONTENT_TYPE_MUSIC,
- .usage = AUDIO_USAGE_MEDIA,
- };
-
- std::vector<audio_mixer_attributes_t> mixerAttributes;
- EXPECT_EQ(NO_ERROR, mManager->getSupportedMixerAttributes(usbPortId, mixerAttributes));
- EXPECT_GT(mixerAttributes.size(), 0);
- size_t bitPerfectIndex = 0;
- for (; bitPerfectIndex < mixerAttributes.size(); ++bitPerfectIndex) {
- if (mixerAttributes[bitPerfectIndex].mixer_behavior == AUDIO_MIXER_BEHAVIOR_BIT_PERFECT) {
- break;
- }
- }
- EXPECT_LT(bitPerfectIndex, mixerAttributes.size());
- EXPECT_EQ(bitPerfectFormat, mixerAttributes[bitPerfectIndex].config.format);
- EXPECT_EQ(bitPerfectChannelMask, mixerAttributes[bitPerfectIndex].config.channel_mask);
- EXPECT_EQ(bitPerfectSampleRate, mixerAttributes[bitPerfectIndex].config.sample_rate);
- EXPECT_EQ(NO_ERROR,
- mManager->setPreferredMixerAttributes(
- &mediaAttr, usbPortId, uid, &mixerAttributes[bitPerfectIndex]));
-
- audio_io_handle_t bitPerfectOutput = AUDIO_IO_HANDLE_NONE;
- audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
- audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
- audio_port_handle_t bitPerfectPortId = AUDIO_PORT_HANDLE_NONE;
- audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
- bool isBitPerfect;
-
- // When there is no active bit-perfect playback, the output selection will follow default
- // routing strategy.
- getOutputForAttr(&selectedDeviceId, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO,
- 48000, AUDIO_OUTPUT_FLAG_NONE, &output, &portId, mediaAttr, AUDIO_SESSION_NONE,
- uid, &isBitPerfect);
- EXPECT_FALSE(isBitPerfect);
- EXPECT_NE(AUDIO_IO_HANDLE_NONE, output);
- const auto outputDesc = mManager->getOutputs().valueFor(output);
- EXPECT_NE(nullptr, outputDesc);
- EXPECT_NE(AUDIO_OUTPUT_FLAG_BIT_PERFECT, outputDesc->mFlags & AUDIO_OUTPUT_FLAG_BIT_PERFECT);
-
- // Start bit-perfect playback
- getOutputForAttr(&selectedDeviceId, bitPerfectFormat, bitPerfectChannelMask,
- bitPerfectSampleRate, AUDIO_OUTPUT_FLAG_NONE, &bitPerfectOutput, &bitPerfectPortId,
- mediaAttr, AUDIO_SESSION_NONE, uid, &isBitPerfect);
- status_t status = mManager->startOutput(bitPerfectPortId);
- if (status == DEAD_OBJECT) {
- getOutputForAttr(&selectedDeviceId, bitPerfectFormat, bitPerfectChannelMask,
- bitPerfectSampleRate, AUDIO_OUTPUT_FLAG_NONE, &bitPerfectOutput, &bitPerfectPortId,
- mediaAttr, AUDIO_SESSION_NONE, uid, &isBitPerfect);
- status = mManager->startOutput(bitPerfectPortId);
- }
- EXPECT_EQ(NO_ERROR, status);
- EXPECT_TRUE(isBitPerfect);
- EXPECT_NE(AUDIO_IO_HANDLE_NONE, bitPerfectOutput);
- const auto bitPerfectOutputDesc = mManager->getOutputs().valueFor(bitPerfectOutput);
- EXPECT_NE(nullptr, bitPerfectOutputDesc);
- EXPECT_EQ(AUDIO_OUTPUT_FLAG_BIT_PERFECT,
- bitPerfectOutputDesc->mFlags & AUDIO_OUTPUT_FLAG_BIT_PERFECT);
-
- // If the playback is from preferred mixer attributes owner but the request doesn't match
- // preferred mixer attributes, it will not be bit-perfect.
- getOutputForAttr(&selectedDeviceId, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO,
- 48000, AUDIO_OUTPUT_FLAG_NONE, &output, &portId, mediaAttr, AUDIO_SESSION_NONE,
- uid, &isBitPerfect);
- EXPECT_FALSE(isBitPerfect);
- EXPECT_EQ(bitPerfectOutput, output);
-
- // When bit-perfect playback is active, all other playback will be routed to bit-perfect output.
- getOutputForAttr(&selectedDeviceId, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO,
- 48000, AUDIO_OUTPUT_FLAG_NONE, &output, &portId, mediaAttr, AUDIO_SESSION_NONE,
- anotherUid, &isBitPerfect);
- EXPECT_FALSE(isBitPerfect);
- EXPECT_EQ(bitPerfectOutput, output);
-
- const audio_attributes_t dtmfAttr = {
- .content_type = AUDIO_CONTENT_TYPE_UNKNOWN,
- .usage = AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING,
- };
- audio_io_handle_t dtmfOutput = AUDIO_IO_HANDLE_NONE;
- selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
- portId = AUDIO_PORT_HANDLE_NONE;
- getOutputForAttr(&selectedDeviceId, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO,
- 48000, AUDIO_OUTPUT_FLAG_NONE, &dtmfOutput, &portId, dtmfAttr,
- AUDIO_SESSION_NONE, anotherUid, &isBitPerfect);
- EXPECT_FALSE(isBitPerfect);
- EXPECT_EQ(bitPerfectOutput, dtmfOutput);
-
- // When configuration matches preferred mixer attributes, which is bit-perfect, but the client
- // is not the owner of preferred mixer attributes, the playback will not be bit-perfect.
- getOutputForAttr(&selectedDeviceId, bitPerfectFormat, bitPerfectChannelMask,
- bitPerfectSampleRate, AUDIO_OUTPUT_FLAG_NONE, &output, &portId, mediaAttr,
- AUDIO_SESSION_NONE, anotherUid, &isBitPerfect);
- EXPECT_FALSE(isBitPerfect);
- EXPECT_EQ(bitPerfectOutput, output);
-
- EXPECT_EQ(NO_ERROR,
- mManager->clearPreferredMixerAttributes(&mediaAttr, usbPortId, uid));
- ASSERT_EQ(NO_ERROR, mManager->setDeviceConnectionState(AUDIO_DEVICE_OUT_USB_DEVICE,
- AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
- "", "", AUDIO_FORMAT_LDAC));
-}
-
TEST_F(AudioPolicyManagerTestWithConfigurationFile, PreferExactConfigForInput) {
const audio_channel_mask_t deviceChannelMask = AUDIO_CHANNEL_IN_3POINT1;
mClient->addSupportedFormat(AUDIO_FORMAT_PCM_16_BIT);
@@ -3612,3 +3490,314 @@
// unregister effect should succeed since effect shall have been restore on the client session
ASSERT_EQ(NO_ERROR, mManager->unregisterEffect(effectId));
}
+
+class AudioPolicyManagerTestBitPerfectBase : public AudioPolicyManagerTestWithConfigurationFile {
+protected:
+ void SetUp() override;
+ void TearDown() override;
+
+ void startBitPerfectOutput();
+ void reset();
+ void getBitPerfectOutput(status_t expected);
+
+ const audio_format_t mBitPerfectFormat = AUDIO_FORMAT_PCM_16_BIT;
+ const audio_channel_mask_t mBitPerfectChannelMask = AUDIO_CHANNEL_OUT_STEREO;
+ const uint32_t mBitPerfectSampleRate = 48000;
+ const uid_t mUid = 1234;
+ audio_port_handle_t mUsbPortId = AUDIO_PORT_HANDLE_NONE;
+
+ audio_io_handle_t mBitPerfectOutput = AUDIO_IO_HANDLE_NONE;
+ audio_port_handle_t mSelectedDeviceId = AUDIO_PORT_HANDLE_NONE;
+ audio_port_handle_t mBitPerfectPortId = AUDIO_PORT_HANDLE_NONE;
+
+ static constexpr audio_attributes_t sMediaAttr = {
+ .content_type = AUDIO_CONTENT_TYPE_MUSIC,
+ .usage = AUDIO_USAGE_MEDIA,
+ };
+};
+
+void AudioPolicyManagerTestBitPerfectBase::SetUp() {
+ ASSERT_NO_FATAL_FAILURE(AudioPolicyManagerTestWithConfigurationFile::SetUp());
+
+ mClient->addSupportedFormat(mBitPerfectFormat);
+ mClient->addSupportedChannelMask(mBitPerfectChannelMask);
+ ASSERT_EQ(NO_ERROR, mManager->setDeviceConnectionState(AUDIO_DEVICE_OUT_USB_DEVICE,
+ AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
+ "", "", AUDIO_FORMAT_DEFAULT));
+ auto devices = mManager->getAvailableOutputDevices();
+ mUsbPortId = AUDIO_PORT_HANDLE_NONE;
+ for (auto device : devices) {
+ if (device->type() == AUDIO_DEVICE_OUT_USB_DEVICE) {
+ mUsbPortId = device->getId();
+ break;
+ }
+ }
+ EXPECT_NE(AUDIO_PORT_HANDLE_NONE, mUsbPortId);
+
+ std::vector<audio_mixer_attributes_t> mixerAttributes;
+ EXPECT_EQ(NO_ERROR, mManager->getSupportedMixerAttributes(mUsbPortId, mixerAttributes));
+ EXPECT_GT(mixerAttributes.size(), 0);
+ size_t bitPerfectIndex = 0;
+ for (; bitPerfectIndex < mixerAttributes.size(); ++bitPerfectIndex) {
+ if (mixerAttributes[bitPerfectIndex].mixer_behavior == AUDIO_MIXER_BEHAVIOR_BIT_PERFECT) {
+ break;
+ }
+ }
+ EXPECT_LT(bitPerfectIndex, mixerAttributes.size());
+ EXPECT_EQ(mBitPerfectFormat, mixerAttributes[bitPerfectIndex].config.format);
+ EXPECT_EQ(mBitPerfectChannelMask, mixerAttributes[bitPerfectIndex].config.channel_mask);
+ EXPECT_EQ(mBitPerfectSampleRate, mixerAttributes[bitPerfectIndex].config.sample_rate);
+ EXPECT_EQ(NO_ERROR,
+ mManager->setPreferredMixerAttributes(
+ &sMediaAttr, mUsbPortId, mUid, &mixerAttributes[bitPerfectIndex]));
+}
+
+void AudioPolicyManagerTestBitPerfectBase::TearDown() {
+ EXPECT_EQ(NO_ERROR,
+ mManager->clearPreferredMixerAttributes(&sMediaAttr, mUsbPortId, mUid));
+ ASSERT_EQ(NO_ERROR, mManager->setDeviceConnectionState(AUDIO_DEVICE_OUT_USB_DEVICE,
+ AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+ "", "", AUDIO_FORMAT_LDAC));
+
+ ASSERT_NO_FATAL_FAILURE(AudioPolicyManagerTestWithConfigurationFile::TearDown());
+}
+
+void AudioPolicyManagerTestBitPerfectBase::startBitPerfectOutput() {
+ reset();
+ bool isBitPerfect;
+
+ getOutputForAttr(&mSelectedDeviceId, mBitPerfectFormat, mBitPerfectChannelMask,
+ mBitPerfectSampleRate, AUDIO_OUTPUT_FLAG_NONE, &mBitPerfectOutput,
+ &mBitPerfectPortId, sMediaAttr, AUDIO_SESSION_NONE, mUid, &isBitPerfect);
+ status_t status = mManager->startOutput(mBitPerfectPortId);
+ if (status == DEAD_OBJECT) {
+ getOutputForAttr(&mSelectedDeviceId, mBitPerfectFormat, mBitPerfectChannelMask,
+ mBitPerfectSampleRate, AUDIO_OUTPUT_FLAG_NONE, &mBitPerfectOutput,
+ &mBitPerfectPortId, sMediaAttr, AUDIO_SESSION_NONE, mUid, &isBitPerfect);
+ status = mManager->startOutput(mBitPerfectPortId);
+ }
+ EXPECT_EQ(NO_ERROR, status);
+ EXPECT_TRUE(isBitPerfect);
+ EXPECT_NE(AUDIO_IO_HANDLE_NONE, mBitPerfectOutput);
+ const auto bitPerfectOutputDesc = mManager->getOutputs().valueFor(mBitPerfectOutput);
+ EXPECT_NE(nullptr, bitPerfectOutputDesc);
+ EXPECT_EQ(AUDIO_OUTPUT_FLAG_BIT_PERFECT,
+ bitPerfectOutputDesc->mFlags & AUDIO_OUTPUT_FLAG_BIT_PERFECT);
+};
+
+void AudioPolicyManagerTestBitPerfectBase::reset() {
+ mBitPerfectOutput = AUDIO_IO_HANDLE_NONE;
+ mSelectedDeviceId = AUDIO_PORT_HANDLE_NONE;
+ mBitPerfectPortId = AUDIO_PORT_HANDLE_NONE;
+}
+
+void AudioPolicyManagerTestBitPerfectBase::getBitPerfectOutput(status_t expected) {
+ reset();
+ audio_stream_type_t stream = AUDIO_STREAM_DEFAULT;
+ AttributionSourceState attributionSource = createAttributionSourceState(mUid);
+ audio_config_t config = AUDIO_CONFIG_INITIALIZER;
+ config.sample_rate = mBitPerfectSampleRate;
+ config.channel_mask = mBitPerfectChannelMask;
+ config.format = mBitPerfectFormat;
+ audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_BIT_PERFECT;
+ AudioPolicyInterface::output_type_t outputType;
+ bool isSpatialized;
+ bool isBitPerfect;
+ EXPECT_EQ(expected,
+ mManager->getOutputForAttr(&sMediaAttr, &mBitPerfectOutput, AUDIO_SESSION_NONE,
+ &stream, attributionSource, &config, &flags,
+ &mSelectedDeviceId, &mBitPerfectPortId, {}, &outputType,
+ &isSpatialized, &isBitPerfect));
+}
+
+class AudioPolicyManagerTestBitPerfect : public AudioPolicyManagerTestBitPerfectBase {
+};
+
+TEST_F(AudioPolicyManagerTestBitPerfect, UseBitPerfectOutput) {
+ const uid_t anotherUid = 5678;
+ audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
+ audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
+ audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
+ bool isBitPerfect;
+
+ // When there is no active bit-perfect playback, the output selection will follow default
+ // routing strategy.
+ getOutputForAttr(&selectedDeviceId, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_QUAD,
+ 48000, AUDIO_OUTPUT_FLAG_NONE, &output, &portId, sMediaAttr,
+ AUDIO_SESSION_NONE, mUid, &isBitPerfect);
+ EXPECT_FALSE(isBitPerfect);
+ EXPECT_NE(AUDIO_IO_HANDLE_NONE, output);
+ const auto outputDesc = mManager->getOutputs().valueFor(output);
+ EXPECT_NE(nullptr, outputDesc);
+ EXPECT_NE(AUDIO_OUTPUT_FLAG_BIT_PERFECT, outputDesc->mFlags & AUDIO_OUTPUT_FLAG_BIT_PERFECT);
+
+ // Start bit-perfect playback
+ ASSERT_NO_FATAL_FAILURE(startBitPerfectOutput());
+
+ // If the playback is from preferred mixer attributes owner but the request doesn't match
+ // preferred mixer attributes, it will not be bit-perfect.
+ getOutputForAttr(&selectedDeviceId, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_QUAD,
+ 48000, AUDIO_OUTPUT_FLAG_NONE, &output, &portId, sMediaAttr,
+ AUDIO_SESSION_NONE, mUid, &isBitPerfect);
+ EXPECT_FALSE(isBitPerfect);
+ EXPECT_EQ(mBitPerfectOutput, output);
+
+ // When bit-perfect playback is active, all other playback will be routed to bit-perfect output.
+ getOutputForAttr(&selectedDeviceId, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO,
+ 48000, AUDIO_OUTPUT_FLAG_NONE, &output, &portId, sMediaAttr,
+ AUDIO_SESSION_NONE, anotherUid, &isBitPerfect);
+ EXPECT_FALSE(isBitPerfect);
+ EXPECT_EQ(mBitPerfectOutput, output);
+
+ // When bit-pefect playback is active, dtmf will also be routed to bit-perfect output.
+ const audio_attributes_t dtmfAttr = {
+ .content_type = AUDIO_CONTENT_TYPE_UNKNOWN,
+ .usage = AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING,
+ };
+ audio_io_handle_t dtmfOutput = AUDIO_IO_HANDLE_NONE;
+ selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
+ portId = AUDIO_PORT_HANDLE_NONE;
+ getOutputForAttr(&selectedDeviceId, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO,
+ 48000, AUDIO_OUTPUT_FLAG_NONE, &dtmfOutput, &portId, dtmfAttr,
+ AUDIO_SESSION_NONE, anotherUid, &isBitPerfect);
+ EXPECT_FALSE(isBitPerfect);
+ EXPECT_EQ(mBitPerfectOutput, dtmfOutput);
+
+ // When configuration matches preferred mixer attributes, which is bit-perfect, but the client
+ // is not the owner of preferred mixer attributes, the playback will not be bit-perfect.
+ getOutputForAttr(&selectedDeviceId, mBitPerfectFormat, mBitPerfectChannelMask,
+ mBitPerfectSampleRate, AUDIO_OUTPUT_FLAG_NONE, &output, &portId, sMediaAttr,
+ AUDIO_SESSION_NONE, anotherUid, &isBitPerfect);
+ EXPECT_FALSE(isBitPerfect);
+ EXPECT_EQ(mBitPerfectOutput, output);
+}
+
+TEST_F_WITH_FLAGS(
+ AudioPolicyManagerTestBitPerfect,
+ InternalMuteWhenBitPerfectCLientIsActive,
+ REQUIRES_FLAGS_ENABLED(
+ ACONFIG_FLAG(com::android::media::audioserver,
+ fix_concurrent_playback_behavior_with_bit_perfect_client))
+) {
+ ASSERT_NO_FATAL_FAILURE(startBitPerfectOutput());
+
+ // When bit-perfect playback is active, the system sound will be routed to bit-perfect output.
+ // The system sound will be muted internally in this case. The bit-perfect client will be
+ // played normally.
+ const uint32_t anotherSampleRate = 44100;
+ audio_port_handle_t systemSoundPortId = AUDIO_PORT_HANDLE_NONE;
+ audio_io_handle_t systemSoundOutput = AUDIO_IO_HANDLE_NONE;
+ const audio_attributes_t systemSoundAttr = {
+ .content_type = AUDIO_CONTENT_TYPE_SONIFICATION,
+ .usage = AUDIO_USAGE_ASSISTANCE_SONIFICATION,
+ };
+ audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
+ bool isBitPerfect;
+ getOutputForAttr(&selectedDeviceId, mBitPerfectFormat, mBitPerfectChannelMask,
+ anotherSampleRate, AUDIO_OUTPUT_FLAG_NONE, &systemSoundOutput,
+ &systemSoundPortId, systemSoundAttr, AUDIO_SESSION_NONE, mUid, &isBitPerfect);
+ EXPECT_FALSE(isBitPerfect);
+ EXPECT_EQ(mBitPerfectOutput, systemSoundOutput);
+ EXPECT_EQ(NO_ERROR, mManager->startOutput(systemSoundPortId));
+ EXPECT_TRUE(mClient->getTrackInternalMute(systemSoundPortId));
+ EXPECT_FALSE(mClient->getTrackInternalMute(mBitPerfectPortId));
+ EXPECT_EQ(NO_ERROR, mManager->stopOutput(systemSoundPortId));
+ EXPECT_FALSE(mClient->getTrackInternalMute(mBitPerfectPortId));
+
+ // When bit-perfect playback is active, the notification will be routed to bit-perfect output.
+ // The notification sound will be played normally while the bit-perfect client will be muted
+ // internally.
+ audio_port_handle_t notificationPortId = AUDIO_PORT_HANDLE_NONE;
+ audio_io_handle_t notificationOutput = AUDIO_IO_HANDLE_NONE;
+ const audio_attributes_t notificationAttr = {
+ .content_type = AUDIO_CONTENT_TYPE_SONIFICATION,
+ .usage = AUDIO_USAGE_NOTIFICATION,
+ };
+ getOutputForAttr(&selectedDeviceId, mBitPerfectFormat, mBitPerfectChannelMask,
+ anotherSampleRate, AUDIO_OUTPUT_FLAG_NONE, ¬ificationOutput,
+ ¬ificationPortId, notificationAttr, AUDIO_SESSION_NONE, mUid,
+ &isBitPerfect);
+ EXPECT_FALSE(isBitPerfect);
+ EXPECT_EQ(mBitPerfectOutput, notificationOutput);
+ EXPECT_EQ(NO_ERROR, mManager->startOutput(notificationPortId));
+ EXPECT_FALSE(mClient->getTrackInternalMute(notificationPortId));
+ EXPECT_TRUE(mClient->getTrackInternalMute(mBitPerfectPortId));
+ EXPECT_EQ(NO_ERROR, mManager->stopOutput(notificationPortId));
+ EXPECT_FALSE(mClient->getTrackInternalMute(mBitPerfectPortId));
+
+ EXPECT_EQ(NO_ERROR, mManager->stopOutput(mBitPerfectPortId));
+}
+
+class AudioPolicyManagerTestBitPerfectPhoneMode : public AudioPolicyManagerTestBitPerfectBase,
+ public testing::WithParamInterface<audio_mode_t> {
+};
+
+TEST_P(AudioPolicyManagerTestBitPerfectPhoneMode, RejectBitPerfectWhenPhoneModeIsNotNormal) {
+ if (!com::android::media::audioserver::
+ fix_concurrent_playback_behavior_with_bit_perfect_client()) {
+ GTEST_SKIP()
+ << "Flag fix_concurrent_playback_behavior_with_bit_perfect_client is not enabled";
+ }
+
+ ASSERT_NO_FATAL_FAILURE(startBitPerfectOutput());
+
+ audio_mode_t mode = GetParam();
+ mManager->setPhoneState(mode);
+ // When the phone mode is not normal, the bit-perfect output will be reopned
+ EXPECT_EQ(nullptr, mManager->getOutputs().valueFor(mBitPerfectOutput));
+
+ // When the phone mode is not normal, the bit-perfect output will be closed.
+ ASSERT_NO_FATAL_FAILURE(getBitPerfectOutput(INVALID_OPERATION));
+
+ mManager->setPhoneState(AUDIO_MODE_NORMAL);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ PhoneMode,
+ AudioPolicyManagerTestBitPerfectPhoneMode,
+ testing::Values(AUDIO_MODE_IN_CALL,
+ AUDIO_MODE_RINGTONE,
+ AUDIO_MODE_IN_COMMUNICATION,
+ AUDIO_MODE_CALL_SCREEN)
+);
+
+class AudioPolicyManagerTestBitPerfectHigherPriorityUseCaseActive :
+ public AudioPolicyManagerTestBitPerfectBase,
+ public testing::WithParamInterface<audio_usage_t> {
+};
+
+TEST_P(AudioPolicyManagerTestBitPerfectHigherPriorityUseCaseActive,
+ RejectBitPerfectWhenHigherPriorityUseCaseIsActive) {
+ if (!com::android::media::audioserver::
+ fix_concurrent_playback_behavior_with_bit_perfect_client()) {
+ GTEST_SKIP()
+ << "Flag fix_concurrent_playback_behavior_with_bit_perfect_client is not enabled";
+ }
+
+ ASSERT_NO_FATAL_FAILURE(startBitPerfectOutput());
+
+ audio_attributes_t attr = {
+ .usage = GetParam(),
+ .content_type = AUDIO_CONTENT_TYPE_UNKNOWN
+ };
+ audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
+ audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
+ audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
+ ASSERT_NO_FATAL_FAILURE(
+ getOutputForAttr(&selectedDeviceId, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO,
+ 48000, AUDIO_OUTPUT_FLAG_NONE, &output, &portId, attr));
+ EXPECT_NE(mBitPerfectOutput, output);
+ EXPECT_EQ(NO_ERROR, mManager->startOutput(portId));
+ // When a high priority use case is active, the bit-perfect output will be closed.
+ EXPECT_EQ(nullptr, mManager->getOutputs().valueFor(mBitPerfectOutput));
+
+ // When any higher priority use case is active, the bit-perfect request will be rejected.
+ ASSERT_NO_FATAL_FAILURE(getBitPerfectOutput(INVALID_OPERATION));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ HigherPriorityUseCases,
+ AudioPolicyManagerTestBitPerfectHigherPriorityUseCaseActive,
+ testing::Values(AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE,
+ AUDIO_USAGE_ALARM)
+);
diff --git a/services/camera/libcameraservice/api2/DepthCompositeStream.cpp b/services/camera/libcameraservice/api2/DepthCompositeStream.cpp
index 1bd0b85..8b41d00 100644
--- a/services/camera/libcameraservice/api2/DepthCompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/DepthCompositeStream.cpp
@@ -517,6 +517,15 @@
return false;
}
+bool DepthCompositeStream::isDepthCompositeStreamInfo(const OutputStreamInfo& streamInfo) {
+ if ((streamInfo.dataSpace == static_cast<android_dataspace_t>(HAL_DATASPACE_DYNAMIC_DEPTH)) &&
+ (streamInfo.format == HAL_PIXEL_FORMAT_BLOB)) {
+ return true;
+ }
+
+ return false;
+}
+
static bool setContains(std::unordered_set<int32_t> containerSet, int32_t value) {
return containerSet.find(value) != containerSet.end();
}
diff --git a/services/camera/libcameraservice/api2/DepthCompositeStream.h b/services/camera/libcameraservice/api2/DepthCompositeStream.h
index f797f9c..3f8f6a2 100644
--- a/services/camera/libcameraservice/api2/DepthCompositeStream.h
+++ b/services/camera/libcameraservice/api2/DepthCompositeStream.h
@@ -46,6 +46,7 @@
~DepthCompositeStream() override;
static bool isDepthCompositeStream(const sp<Surface> &surface);
+ static bool isDepthCompositeStreamInfo(const OutputStreamInfo& streamInfo);
// CompositeStream overrides
status_t createInternalStreams(const std::vector<sp<Surface>>& consumers,
diff --git a/services/camera/libcameraservice/api2/HeicCompositeStream.cpp b/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
index 68e9ad4..225d7f5 100644
--- a/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
@@ -94,6 +94,11 @@
mMainImageSurface.clear();
}
+bool HeicCompositeStream::isHeicCompositeStreamInfo(const OutputStreamInfo& streamInfo) {
+ return ((streamInfo.dataSpace == static_cast<android_dataspace_t>(HAL_DATASPACE_HEIF)) &&
+ (streamInfo.format == HAL_PIXEL_FORMAT_BLOB));
+}
+
bool HeicCompositeStream::isHeicCompositeStream(const sp<Surface> &surface) {
ANativeWindow *anw = surface.get();
status_t err;
diff --git a/services/camera/libcameraservice/api2/HeicCompositeStream.h b/services/camera/libcameraservice/api2/HeicCompositeStream.h
index b539cdd..9cfd78b 100644
--- a/services/camera/libcameraservice/api2/HeicCompositeStream.h
+++ b/services/camera/libcameraservice/api2/HeicCompositeStream.h
@@ -42,6 +42,7 @@
~HeicCompositeStream() override;
static bool isHeicCompositeStream(const sp<Surface> &surface);
+ static bool isHeicCompositeStreamInfo(const OutputStreamInfo& streamInfo);
status_t createInternalStreams(const std::vector<sp<Surface>>& consumers,
bool hasDeferredConsumer, uint32_t width, uint32_t height, int format,
diff --git a/services/camera/libcameraservice/api2/JpegRCompositeStream.cpp b/services/camera/libcameraservice/api2/JpegRCompositeStream.cpp
index a1b9383..dadefcc 100644
--- a/services/camera/libcameraservice/api2/JpegRCompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/JpegRCompositeStream.cpp
@@ -520,6 +520,15 @@
return false;
}
+bool JpegRCompositeStream::isJpegRCompositeStreamInfo(const OutputStreamInfo& streamInfo) {
+ if ((streamInfo.format == HAL_PIXEL_FORMAT_BLOB) &&
+ (streamInfo.dataSpace == static_cast<int>(kJpegRDataSpace))) {
+ return true;
+ }
+
+ return false;
+}
+
void JpegRCompositeStream::deriveDynamicRangeAndDataspace(int64_t dynamicProfile,
int64_t* /*out*/dynamicRange, int64_t* /*out*/dataSpace) {
if ((dynamicRange == nullptr) || (dataSpace == nullptr)) {
@@ -832,8 +841,8 @@
(*compositeOutput)[0].colorSpace =
ANDROID_REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED;
- if (CameraProviderManager::isConcurrentDynamicRangeCaptureSupported(staticInfo,
- streamInfo.dynamicRangeProfile,
+ if (CameraProviderManager::isConcurrentDynamicRangeCaptureSupported(
+ staticInfo, dynamicRange,
ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD)) {
compositeOutput->push_back({});
(*compositeOutput)[1].width = streamInfo.width;
diff --git a/services/camera/libcameraservice/api2/JpegRCompositeStream.h b/services/camera/libcameraservice/api2/JpegRCompositeStream.h
index 016d57c..6669739 100644
--- a/services/camera/libcameraservice/api2/JpegRCompositeStream.h
+++ b/services/camera/libcameraservice/api2/JpegRCompositeStream.h
@@ -43,6 +43,7 @@
~JpegRCompositeStream() override;
static bool isJpegRCompositeStream(const sp<Surface> &surface);
+ static bool isJpegRCompositeStreamInfo(const OutputStreamInfo& streamInfo);
// CompositeStream overrides
status_t createInternalStreams(const std::vector<sp<Surface>>& consumers,
diff --git a/services/camera/libcameraservice/utils/SessionConfigurationUtils.cpp b/services/camera/libcameraservice/utils/SessionConfigurationUtils.cpp
index a7a2b5e..40ca276 100644
--- a/services/camera/libcameraservice/utils/SessionConfigurationUtils.cpp
+++ b/services/camera/libcameraservice/utils/SessionConfigurationUtils.cpp
@@ -21,6 +21,7 @@
#include "../api2/HeicCompositeStream.h"
#include "aidl/android/hardware/graphics/common/Dataspace.h"
#include "api2/JpegRCompositeStream.h"
+#include "binder/Status.h"
#include "common/CameraDeviceBase.h"
#include "common/HalConversionsTemplated.h"
#include "../CameraService.h"
@@ -679,6 +680,67 @@
stream->useCase = static_cast<StreamUseCases>(streamInfo.streamUseCase);
}
+binder::Status mapStream(const OutputStreamInfo& streamInfo, bool isCompositeJpegRDisabled,
+ const CameraMetadata& deviceInfo, camera_stream_rotation_t rotation,
+ size_t* streamIdx/*out*/, const std::string &physicalId, int32_t groupId,
+ const std::string& logicalCameraId,
+ aidl::android::hardware::camera::device::StreamConfiguration &streamConfiguration /*out*/,
+ bool *earlyExit /*out*/) {
+ bool isDepthCompositeStream =
+ camera3::DepthCompositeStream::isDepthCompositeStreamInfo(streamInfo);
+ bool isHeicCompositeStream =
+ camera3::HeicCompositeStream::isHeicCompositeStreamInfo(streamInfo);
+ bool isJpegRCompositeStream =
+ camera3::JpegRCompositeStream::isJpegRCompositeStreamInfo(streamInfo) &&
+ !isCompositeJpegRDisabled;
+ if (isDepthCompositeStream || isHeicCompositeStream || isJpegRCompositeStream) {
+ // We need to take in to account that composite streams can have
+ // additional internal camera streams.
+ std::vector<OutputStreamInfo> compositeStreams;
+ status_t ret;
+ if (isDepthCompositeStream) {
+ // TODO: Take care of composite streams.
+ ret = camera3::DepthCompositeStream::getCompositeStreamInfo(streamInfo,
+ deviceInfo, &compositeStreams);
+ } else if (isHeicCompositeStream) {
+ ret = camera3::HeicCompositeStream::getCompositeStreamInfo(streamInfo,
+ deviceInfo, &compositeStreams);
+ } else {
+ ret = camera3::JpegRCompositeStream::getCompositeStreamInfo(streamInfo,
+ deviceInfo, &compositeStreams);
+ }
+
+ if (ret != OK) {
+ std::string msg = fmt::sprintf(
+ "Camera %s: Failed adding composite streams: %s (%d)",
+ logicalCameraId.c_str(), strerror(-ret), ret);
+ ALOGE("%s: %s", __FUNCTION__, msg.c_str());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT, msg.c_str());
+ }
+
+ if (compositeStreams.size() == 0) {
+ // No internal streams means composite stream not
+ // supported.
+ *earlyExit = true;
+ return binder::Status::ok();
+ } else if (compositeStreams.size() > 1) {
+ size_t streamCount = streamConfiguration.streams.size() + compositeStreams.size() - 1;
+ streamConfiguration.streams.resize(streamCount);
+ }
+
+ for (const auto& compositeStream : compositeStreams) {
+ mapStreamInfo(compositeStream, rotation,
+ physicalId, groupId,
+ &streamConfiguration.streams[(*streamIdx)++]);
+ }
+ } else {
+ mapStreamInfo(streamInfo, rotation,
+ physicalId, groupId, &streamConfiguration.streams[(*streamIdx)++]);
+ }
+
+ return binder::Status::ok();
+}
+
binder::Status
convertToHALStreamCombination(
const SessionConfiguration& sessionConfiguration,
@@ -831,8 +893,13 @@
"Deferred surface sensor pixel modes not valid");
}
streamInfo.streamUseCase = streamUseCase;
- mapStreamInfo(streamInfo, camera3::CAMERA_STREAM_ROTATION_0, physicalCameraId, groupId,
- &streamConfiguration.streams[streamIdx++]);
+ auto status = mapStream(streamInfo, isCompositeJpegRDisabled, deviceInfo,
+ camera3::CAMERA_STREAM_ROTATION_0, &streamIdx, physicalCameraId, groupId,
+ logicalCameraId, streamConfiguration, earlyExit);
+ if (*earlyExit || !status.isOk()) {
+ return status;
+ }
+
isStreamInfoValid = true;
if (numBufferProducers == 0) {
@@ -851,57 +918,11 @@
return res;
if (!isStreamInfoValid) {
- bool isDepthCompositeStream =
- camera3::DepthCompositeStream::isDepthCompositeStream(surface);
- bool isHeicCompositeStream =
- camera3::HeicCompositeStream::isHeicCompositeStream(surface);
- bool isJpegRCompositeStream =
- camera3::JpegRCompositeStream::isJpegRCompositeStream(surface) &&
- !isCompositeJpegRDisabled;
- if (isDepthCompositeStream || isHeicCompositeStream || isJpegRCompositeStream) {
- // We need to take in to account that composite streams can have
- // additional internal camera streams.
- std::vector<OutputStreamInfo> compositeStreams;
- if (isDepthCompositeStream) {
- // TODO: Take care of composite streams.
- ret = camera3::DepthCompositeStream::getCompositeStreamInfo(streamInfo,
- deviceInfo, &compositeStreams);
- } else if (isHeicCompositeStream) {
- ret = camera3::HeicCompositeStream::getCompositeStreamInfo(streamInfo,
- deviceInfo, &compositeStreams);
- } else {
- ret = camera3::JpegRCompositeStream::getCompositeStreamInfo(streamInfo,
- deviceInfo, &compositeStreams);
- }
-
- if (ret != OK) {
- std::string msg = fmt::sprintf(
- "Camera %s: Failed adding composite streams: %s (%d)",
- logicalCameraId.c_str(), strerror(-ret), ret);
- ALOGE("%s: %s", __FUNCTION__, msg.c_str());
- return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT, msg.c_str());
- }
-
- if (compositeStreams.size() == 0) {
- // No internal streams means composite stream not
- // supported.
- *earlyExit = true;
- return binder::Status::ok();
- } else if (compositeStreams.size() > 1) {
- streamCount += compositeStreams.size() - 1;
- streamConfiguration.streams.resize(streamCount);
- }
-
- for (const auto& compositeStream : compositeStreams) {
- mapStreamInfo(compositeStream,
- static_cast<camera_stream_rotation_t> (it.getRotation()),
- physicalCameraId, groupId,
- &streamConfiguration.streams[streamIdx++]);
- }
- } else {
- mapStreamInfo(streamInfo,
- static_cast<camera_stream_rotation_t> (it.getRotation()),
- physicalCameraId, groupId, &streamConfiguration.streams[streamIdx++]);
+ auto status = mapStream(streamInfo, isCompositeJpegRDisabled, deviceInfo,
+ static_cast<camera_stream_rotation_t> (it.getRotation()), &streamIdx,
+ physicalCameraId, groupId, logicalCameraId, streamConfiguration, earlyExit);
+ if (*earlyExit || !status.isOk()) {
+ return status;
}
isStreamInfoValid = true;
}
diff --git a/services/camera/virtualcamera/Android.bp b/services/camera/virtualcamera/Android.bp
index 90530f6..fc186fb 100644
--- a/services/camera/virtualcamera/Android.bp
+++ b/services/camera/virtualcamera/Android.bp
@@ -50,7 +50,6 @@
"util/JpegUtil.cc",
"util/MetadataUtil.cc",
"util/Util.cc",
- "util/TestPatternHelper.cc",
"util/EglDisplayContext.cc",
"util/EglFramebuffer.cc",
"util/EglProgram.cc",
@@ -72,6 +71,7 @@
"VirtualCameraStream.cc",
"VirtualCameraService.cc",
"VirtualCameraSessionContext.cc",
+ "VirtualCameraTestInstance.cc",
"VirtualCameraRenderThread.cc",
],
defaults: [
diff --git a/services/camera/virtualcamera/VirtualCameraDevice.cc b/services/camera/virtualcamera/VirtualCameraDevice.cc
index fe9e0ed..ba4ea6b 100644
--- a/services/camera/virtualcamera/VirtualCameraDevice.cc
+++ b/services/camera/virtualcamera/VirtualCameraDevice.cc
@@ -73,13 +73,14 @@
constexpr int32_t kMaxJpegSize = 3 * 1024 * 1024 /*3MiB*/;
-constexpr int32_t kMinFps = 15;
-
constexpr std::chrono::nanoseconds kMaxFrameDuration =
- std::chrono::duration_cast<std::chrono::nanoseconds>(1e9ns / kMinFps);
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
+ 1e9ns / VirtualCameraDevice::kMinFps);
constexpr uint8_t kPipelineMaxDepth = 2;
+constexpr int k30Fps = 30;
+
constexpr MetadataBuilder::ControlRegion kDefaultEmptyControlRegion{};
const std::array<Resolution, 5> kStandardJpegThumbnailSizes{
@@ -130,16 +131,20 @@
std::set<FpsRange> availableRanges;
for (const SupportedStreamConfiguration& config : configs) {
- availableRanges.insert({.minFps = kMinFps, .maxFps = config.maxFps});
+ availableRanges.insert(
+ {.minFps = VirtualCameraDevice::kMinFps, .maxFps = config.maxFps});
availableRanges.insert({.minFps = config.maxFps, .maxFps = config.maxFps});
}
if (std::any_of(configs.begin(), configs.end(),
[](const SupportedStreamConfiguration& config) {
- return config.maxFps >= 30;
+ return config.maxFps >= k30Fps;
})) {
- availableRanges.insert({.minFps = kMinFps, .maxFps = 30});
- availableRanges.insert({.minFps = 30, .maxFps = 30});
+ // Extend the set of available ranges with (minFps <= 15, 30) & (30, 30) as
+ // required by CDD.
+ availableRanges.insert(
+ {.minFps = VirtualCameraDevice::kMinFps, .maxFps = k30Fps});
+ availableRanges.insert({.minFps = k30Fps, .maxFps = k30Fps});
}
return std::vector<FpsRange>(availableRanges.begin(), availableRanges.end());
diff --git a/services/camera/virtualcamera/VirtualCameraDevice.h b/services/camera/virtualcamera/VirtualCameraDevice.h
index cba0674..8a7875f 100644
--- a/services/camera/virtualcamera/VirtualCameraDevice.h
+++ b/services/camera/virtualcamera/VirtualCameraDevice.h
@@ -126,6 +126,10 @@
// Default JPEG orientation.
static constexpr uint8_t kDefaultJpegOrientation = 0;
+ // TODO(b/342674104) CDD requires <= 15.
+ // Change this to lower value after confirming it doesn't cause any issue (timeouts).
+ static constexpr int kMinFps = 15;
+
// Default Make and Model for Exif
static constexpr char kDefaultMakeAndModel[] = "Android Virtual Camera";
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.cc b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
index f5cf092..08b2698 100644
--- a/services/camera/virtualcamera/VirtualCameraRenderThread.cc
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
@@ -51,7 +51,6 @@
#include "util/EglFramebuffer.h"
#include "util/JpegUtil.h"
#include "util/MetadataUtil.h"
-#include "util/TestPatternHelper.h"
#include "util/Util.h"
#include "utils/Errors.h"
@@ -278,6 +277,16 @@
return app1Data;
}
+std::chrono::nanoseconds getMaxFrameDuration(
+ const RequestSettings& requestSettings) {
+ if (requestSettings.fpsRange.has_value()) {
+ return std::chrono::nanoseconds(static_cast<uint64_t>(
+ 1e9 / std::max(1, requestSettings.fpsRange->minFps)));
+ }
+ return std::chrono::nanoseconds(
+ static_cast<uint64_t>(1e9 / VirtualCameraDevice::kMinFps));
+}
+
} // namespace
CaptureRequestBuffer::CaptureRequestBuffer(int streamId, int bufferId,
@@ -300,11 +309,10 @@
VirtualCameraRenderThread::VirtualCameraRenderThread(
VirtualCameraSessionContext& sessionContext,
const Resolution inputSurfaceSize, const Resolution reportedSensorSize,
- std::shared_ptr<ICameraDeviceCallback> cameraDeviceCallback, bool testMode)
+ std::shared_ptr<ICameraDeviceCallback> cameraDeviceCallback)
: mCameraDeviceCallback(cameraDeviceCallback),
mInputSurfaceSize(inputSurfaceSize),
mReportedSensorSize(reportedSensorSize),
- mTestMode(testMode),
mSessionContext(sessionContext) {
}
@@ -401,10 +409,6 @@
mEglSurfaceTexture = std::make_unique<EglSurfaceTexture>(
mInputSurfaceSize.width, mInputSurfaceSize.height);
- sp<Surface> inputSurface = mEglSurfaceTexture->getSurface();
- if (mTestMode) {
- inputSurface->connect(NATIVE_WINDOW_API_CPU, false, nullptr);
- }
mInputSurfacePromise.set_value(mEglSurfaceTexture->getSurface());
while (std::unique_ptr<ProcessCaptureRequestTask> task = dequeueTask()) {
@@ -422,16 +426,10 @@
void VirtualCameraRenderThread::processCaptureRequest(
const ProcessCaptureRequestTask& request) {
- if (mTestMode) {
- // In test mode let's just render something to the Surface ourselves.
- renderTestPatternYCbCr420(mEglSurfaceTexture->getSurface(),
- request.getFrameNumber());
- }
-
std::chrono::nanoseconds timestamp =
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch());
- std::chrono::nanoseconds lastAcquisitionTimestamp(
+ const std::chrono::nanoseconds lastAcquisitionTimestamp(
mLastAcquisitionTimestampNanoseconds.exchange(timestamp.count(),
std::memory_order_relaxed));
@@ -461,6 +459,29 @@
}
}
+ // Calculate the maximal amount of time we can afford to wait for next frame.
+ const std::chrono::nanoseconds maxFrameDuration =
+ getMaxFrameDuration(request.getRequestSettings());
+ const std::chrono::nanoseconds elapsedDuration =
+ timestamp - lastAcquisitionTimestamp;
+ if (elapsedDuration < maxFrameDuration) {
+ // We can afford to wait for next frame.
+ // Note that if there's already new frame in the input Surface, the call
+ // below returns immediatelly.
+ bool gotNewFrame = mEglSurfaceTexture->waitForNextFrame(maxFrameDuration -
+ elapsedDuration);
+ timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch());
+ if (!gotNewFrame) {
+ ALOGV(
+ "%s: No new frame received on input surface after waiting for "
+ "%" PRIu64 "ns, repeating last frame.",
+ __func__,
+ static_cast<uint64_t>((timestamp - lastAcquisitionTimestamp).count()));
+ }
+ mLastAcquisitionTimestampNanoseconds.store(timestamp.count(),
+ std::memory_order_relaxed);
+ }
// Acquire new (most recent) image from the Surface.
mEglSurfaceTexture->updateTexture();
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.h b/services/camera/virtualcamera/VirtualCameraRenderThread.h
index dfb6f7b..ce8965f 100644
--- a/services/camera/virtualcamera/VirtualCameraRenderThread.h
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.h
@@ -111,8 +111,7 @@
Resolution reportedSensorSize,
std::shared_ptr<
::aidl::android::hardware::camera::device::ICameraDeviceCallback>
- cameraDeviceCallback,
- bool testMode = false);
+ cameraDeviceCallback);
~VirtualCameraRenderThread();
@@ -184,7 +183,6 @@
const Resolution mInputSurfaceSize;
const Resolution mReportedSensorSize;
- const int mTestMode;
VirtualCameraSessionContext& mSessionContext;
diff --git a/services/camera/virtualcamera/VirtualCameraService.cc b/services/camera/virtualcamera/VirtualCameraService.cc
index b5b07f0..724ec62 100644
--- a/services/camera/virtualcamera/VirtualCameraService.cc
+++ b/services/camera/virtualcamera/VirtualCameraService.cc
@@ -30,10 +30,12 @@
#include "VirtualCameraDevice.h"
#include "VirtualCameraProvider.h"
+#include "VirtualCameraTestInstance.h"
#include "aidl/android/companion/virtualcamera/Format.h"
#include "aidl/android/companion/virtualcamera/LensFacing.h"
#include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.h"
#include "android/binder_auto_utils.h"
+#include "android/binder_interface_utils.h"
#include "android/binder_libbinder.h"
#include "android/binder_status.h"
#include "binder/Status.h"
@@ -64,6 +66,7 @@
constexpr int kVgaWidth = 640;
constexpr int kVgaHeight = 480;
constexpr int kMaxFps = 60;
+constexpr int kTestCameraDefaultInputFps = 30;
constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
constexpr char kHelp[] = "help";
@@ -75,6 +78,7 @@
Options:
--camera_id=(ID) - override numerical ID for test camera instance
--lens_facing=(front|back|external) - specifies lens facing for test camera instance
+ --input_fps=(fps) - specify input fps for test camera, valid values are from 1 to 1000
* disable_test_camera
)";
constexpr char kCreateVirtualDevicePermission[] =
@@ -94,6 +98,13 @@
Status::EX_ILLEGAL_ARGUMENT);
}
+ if (configuration.virtualCameraCallback == nullptr) {
+ ALOGE("%s: Input configuration is missing virtual camera callback",
+ __func__);
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ Status::EX_ILLEGAL_ARGUMENT);
+ }
+
for (const SupportedStreamConfiguration& config :
configuration.supportedStreamConfigs) {
if (!isFormatSupportedForInput(config.width, config.height,
@@ -411,6 +422,18 @@
}
}
+ std::optional<int> inputFps;
+ it = options.find("input_fps");
+ if (it != options.end()) {
+ inputFps = parseInt(it->second);
+ if (!inputFps.has_value() || inputFps.value() < 1 ||
+ inputFps.value() > 1000) {
+ dprintf(err, "Invalid input fps: %s\n, must be integer in <1,1000> range.",
+ it->second.c_str());
+ return STATUS_BAD_VALUE;
+ }
+ }
+
sp<BBinder> token = sp<BBinder>::make();
mTestCameraToken.set(AIBinder_fromPlatformBinder(token));
@@ -418,9 +441,12 @@
VirtualCameraConfiguration configuration;
configuration.supportedStreamConfigs.push_back({.width = kVgaWidth,
.height = kVgaHeight,
- Format::YUV_420_888,
+ Format::RGBA_8888,
.maxFps = kMaxFps});
configuration.lensFacing = lensFacing.value_or(LensFacing::EXTERNAL);
+ configuration.virtualCameraCallback =
+ ndk::SharedRefBase::make<VirtualCameraTestInstance>(
+ inputFps.value_or(kTestCameraDefaultInputFps));
registerCamera(mTestCameraToken, configuration, cameraId.value_or(sNextId++),
kDefaultDeviceId, &ret);
if (ret) {
diff --git a/services/camera/virtualcamera/VirtualCameraSession.cc b/services/camera/virtualcamera/VirtualCameraSession.cc
index 28fa495..7f0adc3 100644
--- a/services/camera/virtualcamera/VirtualCameraSession.cc
+++ b/services/camera/virtualcamera/VirtualCameraSession.cc
@@ -66,7 +66,6 @@
#include "util/EglProgram.h"
#include "util/JpegUtil.h"
#include "util/MetadataUtil.h"
-#include "util/TestPatternHelper.h"
#include "util/Util.h"
namespace android {
@@ -360,12 +359,9 @@
return cameraStatus(Status::ILLEGAL_ARGUMENT);
}
if (mRenderThread == nullptr) {
- // If there's no client callback, start camera in test mode.
- const bool testMode = mVirtualCameraClientCallback == nullptr;
mRenderThread = std::make_unique<VirtualCameraRenderThread>(
mSessionContext, resolutionFromInputConfig(*inputConfig),
- virtualCamera->getMaxInputResolution(), mCameraDeviceCallback,
- testMode);
+ virtualCamera->getMaxInputResolution(), mCameraDeviceCallback);
mRenderThread->start();
inputSurface = mRenderThread->getInputSurface();
}
diff --git a/services/camera/virtualcamera/VirtualCameraTestInstance.cc b/services/camera/virtualcamera/VirtualCameraTestInstance.cc
new file mode 100644
index 0000000..bae871d
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraTestInstance.cc
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VirtualCameraTestInstance"
+
+#include "VirtualCameraTestInstance.h"
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <ratio>
+#include <thread>
+
+#include "GLES/gl.h"
+#include "android/binder_auto_utils.h"
+#include "android/native_window.h"
+#include "log/log.h"
+#include "util/EglDisplayContext.h"
+#include "util/EglProgram.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::view::Surface;
+using ::ndk::ScopedAStatus;
+
+namespace {
+
+std::shared_ptr<ANativeWindow> nativeWindowFromSurface(const Surface& surface) {
+ ANativeWindow* nativeWindow = surface.get();
+ if (nativeWindow != nullptr) {
+ ANativeWindow_acquire(nativeWindow);
+ }
+ return std::shared_ptr<ANativeWindow>(nativeWindow, ANativeWindow_release);
+}
+
+std::chrono::nanoseconds getCurrentTimestamp() {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch());
+}
+
+} // namespace
+
+TestPatternRenderer::TestPatternRenderer(
+ std::shared_ptr<ANativeWindow> nativeWindow, int fps)
+ : mFps(fps), mNativeWindow(nativeWindow) {
+}
+
+void TestPatternRenderer::start() {
+ std::lock_guard<std::mutex> lock(mLock);
+ if (mRunning.exchange(true, std::memory_order_relaxed)) {
+ ALOGW("Render thread already started.");
+ return;
+ }
+ mThread =
+ std::thread(&TestPatternRenderer::renderThreadLoop, this, mNativeWindow);
+}
+
+void TestPatternRenderer::stop() {
+ std::lock_guard<std::mutex> lock(mLock);
+ if (!mRunning.exchange(false, std::memory_order_relaxed)) {
+ ALOGW("Render thread already stopped.");
+ return;
+ }
+ mThread.detach();
+ mRunning = false;
+}
+
+void TestPatternRenderer::renderThreadLoop(
+ std::shared_ptr<ANativeWindow> nativeWindow) {
+ // Prevent destruction of this instance until the thread terminates.
+ std::shared_ptr<TestPatternRenderer> thiz = shared_from_this();
+
+ ALOGV("Starting test client render loop");
+
+ EglDisplayContext eglDisplayContext(nativeWindow);
+ EglTestPatternProgram testPatternProgram;
+
+ const std::chrono::nanoseconds frameDuration(
+ static_cast<uint64_t>(1e9 / mFps));
+
+ std::chrono::nanoseconds lastFrameTs(0);
+ int frameNumber = 0;
+ while (mRunning) {
+ // Wait for appropriate amount of time to meet configured FPS.
+ std::chrono::nanoseconds ts = getCurrentTimestamp();
+ std::chrono::nanoseconds currentDuration = ts - lastFrameTs;
+ if (currentDuration < frameDuration) {
+ std::this_thread::sleep_for(frameDuration - currentDuration);
+ }
+
+ // Render the test pattern and update timestamp.
+ testPatternProgram.draw(frameNumber++);
+ eglDisplayContext.swapBuffers();
+ lastFrameTs = getCurrentTimestamp();
+ }
+
+ ALOGV("Terminating test client render loop");
+}
+
+VirtualCameraTestInstance::VirtualCameraTestInstance(const int fps)
+ : mFps(fps) {
+}
+
+ScopedAStatus VirtualCameraTestInstance::onStreamConfigured(
+ const int32_t streamId, const Surface& surface, const int32_t width,
+ const int32_t height, const Format pixelFormat) {
+ ALOGV("%s: streamId %d, %dx%d pixFmt=%s", __func__, streamId, width, height,
+ toString(pixelFormat).c_str());
+
+ std::lock_guard<std::mutex> lock(mLock);
+ mRenderer = std::make_shared<TestPatternRenderer>(
+ nativeWindowFromSurface(surface), mFps);
+ mRenderer->start();
+
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus VirtualCameraTestInstance::onProcessCaptureRequest(
+ const int32_t /*in_streamId*/, const int32_t /*in_frameId*/) {
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus VirtualCameraTestInstance::onStreamClosed(const int32_t streamId) {
+ ALOGV("%s: streamId %d", __func__, streamId);
+
+ std::lock_guard<std::mutex> lock(mLock);
+ if (mRenderer != nullptr) {
+ mRenderer->stop();
+ mRenderer.reset();
+ }
+ return ScopedAStatus::ok();
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/VirtualCameraTestInstance.h b/services/camera/virtualcamera/VirtualCameraTestInstance.h
new file mode 100644
index 0000000..43e33d5
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraTestInstance.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERATESTINSTANCE_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERATESTINSTANCE_H
+
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <thread>
+
+#include "aidl/android/companion/virtualcamera/BnVirtualCameraCallback.h"
+#include "android/native_window.h"
+#include "utils/Mutex.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Wraps render loop run in a dedicated thread, rendering test pattern to
+// provided Surface (a.k.a. native window) at configured FPS.
+class TestPatternRenderer
+ : public std::enable_shared_from_this<TestPatternRenderer> {
+ public:
+ TestPatternRenderer(std::shared_ptr<ANativeWindow> nativeWindow, int fps);
+
+ // Start rendering.
+ void start() EXCLUDES(mLock);
+
+ // Stop rendering.
+ // Call returns immediatelly, render thread might take some time (1 frame)
+ // to finish rendering and terminate the thread.
+ void stop() EXCLUDES(mLock);
+
+ private:
+ // Render thread entry point.
+ void renderThreadLoop(std::shared_ptr<ANativeWindow> nativeWindow);
+
+ const int mFps;
+
+ std::shared_ptr<ANativeWindow> mNativeWindow;
+
+ std::mutex mLock;
+ std::atomic_bool mRunning;
+ std::thread mThread GUARDED_BY(mLock);
+};
+
+// VirtualCamera callback implementation for test camera.
+//
+// For every configure call, starts rendering of test pattern on provided surface.
+class VirtualCameraTestInstance
+ : public aidl::android::companion::virtualcamera::BnVirtualCameraCallback {
+ public:
+ explicit VirtualCameraTestInstance(int fps = 30);
+
+ ::ndk::ScopedAStatus onStreamConfigured(
+ int32_t streamId, const ::aidl::android::view::Surface& surface,
+ int32_t width, int32_t height,
+ ::aidl::android::companion::virtualcamera::Format pixelFormat) override
+ EXCLUDES(mLock);
+
+ ::ndk::ScopedAStatus onProcessCaptureRequest(int32_t in_streamId,
+ int32_t in_frameId) override;
+
+ ::ndk::ScopedAStatus onStreamClosed(int32_t streamId) override EXCLUDES(mLock);
+
+ private:
+ const int mFps;
+
+ std::mutex mLock;
+ std::shared_ptr<TestPatternRenderer> mRenderer GUARDED_BY(mLock);
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERATESTINSTANCE_H
diff --git a/services/camera/virtualcamera/main.cc b/services/camera/virtualcamera/main.cc
index 43b0219..3db9d9c 100644
--- a/services/camera/virtualcamera/main.cc
+++ b/services/camera/virtualcamera/main.cc
@@ -38,7 +38,7 @@
} // namespace
int main() {
- ALOGI("CameraProvider: virtual webcam service is starting.");
+ ALOGI("virtual_camera service is starting.");
ABinderProcess_setThreadPoolMaxThreadCount(HWBINDER_THREAD_COUNT);
diff --git a/services/camera/virtualcamera/tests/EglUtilTest.cc b/services/camera/virtualcamera/tests/EglUtilTest.cc
index 589e312..813be75 100644
--- a/services/camera/virtualcamera/tests/EglUtilTest.cc
+++ b/services/camera/virtualcamera/tests/EglUtilTest.cc
@@ -55,6 +55,11 @@
};
TEST_F(EglTest, EglTestPatternProgramSuccessfulInit) {
+ if (!isGlExtensionSupported(kGlExtYuvTarget)) {
+ GTEST_SKIP() << "Skipping test because of missing required GL extension "
+ << kGlExtYuvTarget;
+ }
+
EglTestPatternProgram eglTestPatternProgram;
// Verify the shaders compiled and linked successfully.
diff --git a/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
index 5927b05..50977d8 100644
--- a/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
+++ b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
@@ -15,6 +15,7 @@
*/
#include <algorithm>
+#include <cstdint>
#include <cstdio>
#include <iterator>
#include <memory>
@@ -75,6 +76,17 @@
const VirtualCameraConfiguration kEmptyVirtualCameraConfiguration;
+class MockVirtualCameraCallback : public BnVirtualCameraCallback {
+ public:
+ MOCK_METHOD(ndk::ScopedAStatus, onStreamConfigured,
+ (int32_t, const ::aidl::android::view::Surface&, int, int,
+ ::aidl::android::companion::virtualcamera::Format pixelFormat),
+ (override));
+ MOCK_METHOD(ndk::ScopedAStatus, onProcessCaptureRequest, (int32_t, int32_t),
+ (override));
+ MOCK_METHOD(ndk::ScopedAStatus, onStreamClosed, (int32_t), (override));
+};
+
VirtualCameraConfiguration createConfiguration(const int width, const int height,
const Format format,
const int maxFps) {
@@ -85,6 +97,8 @@
.maxFps = maxFps});
configuration.sensorOrientation = kSensorOrientation;
configuration.lensFacing = kLensFacing;
+ configuration.virtualCameraCallback =
+ ndk::SharedRefBase::make<MockVirtualCameraCallback>();
return configuration;
}
@@ -247,6 +261,24 @@
EXPECT_THAT(getCameraIds(), IsEmpty());
}
+TEST_F(VirtualCameraServiceTest,
+ ConfigurationWithoutVirtualCameraCallbackFails) {
+ sp<BBinder> token = sp<BBinder>::make();
+ ndk::SpAIBinder ndkToken(AIBinder_fromPlatformBinder(token));
+ bool aidlRet;
+
+ VirtualCameraConfiguration config =
+ createConfiguration(kVgaWidth, kVgaHeight, Format::RGBA_8888, kMaxFps);
+ config.virtualCameraCallback = nullptr;
+
+ ASSERT_FALSE(mCameraService
+ ->registerCamera(ndkToken, config, kDefaultDeviceId, &aidlRet)
+ .isOk());
+
+ EXPECT_FALSE(aidlRet);
+ EXPECT_THAT(getCameraIds(), IsEmpty());
+}
+
TEST_F(VirtualCameraServiceTest, ConfigurationWithUnsupportedPixelFormatFails) {
bool aidlRet;
@@ -456,6 +488,23 @@
Eq(STATUS_BAD_VALUE));
}
+TEST_F(VirtualCameraServiceTest, TestCameraShellCmdWithInputFps) {
+ EXPECT_THAT(execute_shell_command("enable_test_camera --input_fps=15"),
+ Eq(NO_ERROR));
+
+ std::vector<std::string> cameraIds = getCameraIds();
+ ASSERT_THAT(cameraIds, SizeIs(1));
+}
+
+TEST_F(VirtualCameraServiceTest, TestCameraShellCmdWithInvalidInputFps) {
+ EXPECT_THAT(execute_shell_command("enable_test_camera --input_fps=1001"),
+ Eq(STATUS_BAD_VALUE));
+ EXPECT_THAT(execute_shell_command("enable_test_camera --input_fps=0"),
+ Eq(STATUS_BAD_VALUE));
+ EXPECT_THAT(execute_shell_command("enable_test_camera --input_fps=foo"),
+ Eq(STATUS_BAD_VALUE));
+}
+
} // namespace
} // namespace virtualcamera
} // namespace companion
diff --git a/services/camera/virtualcamera/util/EglDisplayContext.cc b/services/camera/virtualcamera/util/EglDisplayContext.cc
index 6d343a2..166ac75 100644
--- a/services/camera/virtualcamera/util/EglDisplayContext.cc
+++ b/services/camera/virtualcamera/util/EglDisplayContext.cc
@@ -30,8 +30,9 @@
namespace companion {
namespace virtualcamera {
-EglDisplayContext::EglDisplayContext()
+EglDisplayContext::EglDisplayContext(std::shared_ptr<ANativeWindow> nativeWindow)
: mEglDisplay(EGL_NO_DISPLAY),
+ mEglSurface(EGL_NO_SURFACE),
mEglContext(EGL_NO_CONTEXT),
mEglConfig(nullptr) {
EGLBoolean result;
@@ -52,8 +53,10 @@
EGLint numConfigs = 0;
EGLint configAttribs[] = {
- EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE,
- EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8,
+ EGL_SURFACE_TYPE,
+ nativeWindow == nullptr ? EGL_PBUFFER_BIT : EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE,
+ 8, EGL_BLUE_SIZE, 8,
// no alpha
EGL_NONE};
@@ -72,6 +75,14 @@
return;
}
+ if (nativeWindow != nullptr) {
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig,
+ nativeWindow.get(), NULL);
+ if (mEglSurface == EGL_NO_SURFACE) {
+ ALOGE("eglCreateWindowSurface error: %#x", eglGetError());
+ }
+ }
+
if (!makeCurrent()) {
ALOGE(
"Failed to set newly initialized EGLContext and EGLDisplay connection "
@@ -82,6 +93,9 @@
}
EglDisplayContext::~EglDisplayContext() {
+ if (mEglSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ }
if (mEglDisplay != EGL_NO_DISPLAY) {
eglTerminate(mEglDisplay);
}
@@ -99,8 +113,14 @@
return mEglContext != EGL_NO_CONTEXT && mEglDisplay != EGL_NO_DISPLAY;
}
+void EglDisplayContext::swapBuffers() const {
+ if (mEglSurface != EGL_NO_SURFACE) {
+ eglSwapBuffers(mEglDisplay, mEglSurface);
+ }
+}
+
bool EglDisplayContext::makeCurrent() {
- if (!eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglContext)) {
+ if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
ALOGE("eglMakeCurrent failed: %#x", eglGetError());
return false;
}
diff --git a/services/camera/virtualcamera/util/EglDisplayContext.h b/services/camera/virtualcamera/util/EglDisplayContext.h
index 402ca3c..6dc3080 100644
--- a/services/camera/virtualcamera/util/EglDisplayContext.h
+++ b/services/camera/virtualcamera/util/EglDisplayContext.h
@@ -17,7 +17,10 @@
#ifndef ANDROID_COMPANION_VIRTUALCAMERA_EGLDISPLAYCONTEXT_H
#define ANDROID_COMPANION_VIRTUALCAMERA_EGLDISPLAYCONTEXT_H
+#include <memory>
+
#include "EGL/egl.h"
+#include "system/window.h"
namespace android {
namespace companion {
@@ -30,7 +33,7 @@
// out of scope.
class EglDisplayContext {
public:
- EglDisplayContext();
+ EglDisplayContext(std::shared_ptr<ANativeWindow> nativeWindow = nullptr);
~EglDisplayContext();
// Sets EGLDisplay & EGLContext for current thread.
@@ -44,8 +47,13 @@
// EGLDisplay & EGLContext.
bool isInitialized() const;
+ void swapBuffers() const;
+
private:
+ std::shared_ptr<ANativeWindow> mNativeWindow;
+
EGLDisplay mEglDisplay;
+ EGLSurface mEglSurface;
EGLContext mEglContext;
EGLConfig mEglConfig;
};
diff --git a/services/camera/virtualcamera/util/EglProgram.cc b/services/camera/virtualcamera/util/EglProgram.cc
index 7554a67..7498fbc 100644
--- a/services/camera/virtualcamera/util/EglProgram.cc
+++ b/services/camera/virtualcamera/util/EglProgram.cc
@@ -35,19 +35,28 @@
constexpr char kGlExtYuvTarget[] = "GL_EXT_YUV_target";
-constexpr char kIdentityVertexShader[] = R"(
- attribute vec4 vPosition;
+constexpr char kJuliaFractalVertexShader[] = R"(#version 300 es
+ in vec4 aPosition;
+ in vec2 aTextureCoord;
+ out vec2 vFractalCoord;
+ out vec2 vUVCoord;
void main() {
- gl_Position = vPosition;
+ gl_Position = aPosition;
+ vUVCoord = aTextureCoord;
+ vFractalCoord = vec2(aTextureCoord.x - 0.5, aTextureCoord.y - 0.5) * 4.0;
})";
-constexpr char kJuliaFractalFragmentShader[] = R"(
+constexpr char kJuliaFractalFragmentShader[] = R"(#version 300 es
+ #extension GL_EXT_YUV_target : require
precision mediump float;
- uniform vec2 uResolution;
- uniform vec2 uC;
- uniform vec2 uUV;
+
const float kIter = 64.0;
+ in vec2 vFractalCoord;
+ in vec2 vUVCoord;
+ out vec4 fragColor;
+ uniform vec2 uC;
+
vec2 imSq(vec2 n){
return vec2(pow(n.x,2.0)-pow(n.y,2.0), 2.0*n.x*n.y);
}
@@ -62,9 +71,8 @@
}
void main() {
- vec2 uv = vec2(gl_FragCoord.x / uResolution.x - 0.5, gl_FragCoord.y / uResolution.y - 0.5);
- float juliaVal = julia(uv * 4.0, uC);
- gl_FragColor = vec4( juliaVal,uUV.x,uUV.y,0.0);
+ float juliaVal = julia(vFractalCoord, uC);
+ fragColor = vec4(yuv_2_rgb(vec3(juliaVal, vUVCoord.x, vUVCoord.y), itu_601_full_range), 0.0);
})";
constexpr char kExternalTextureVertexShader[] = R"(#version 300 es
@@ -89,7 +97,7 @@
})";
constexpr char kExternalRgbaTextureFragmentShader[] = R"(#version 300 es
- #extension GL_OES_EGL_image_external : require
+ #extension GL_OES_EGL_image_external_essl3 : require
#extension GL_EXT_YUV_target : require
precision mediump float;
in vec2 vTextureCoord;
@@ -200,17 +208,28 @@
}
EglTestPatternProgram::EglTestPatternProgram() {
- if (initialize(kIdentityVertexShader, kJuliaFractalFragmentShader)) {
+ if (initialize(kJuliaFractalVertexShader, kJuliaFractalFragmentShader)) {
ALOGV("Successfully initialized EGL shaders for test pattern program.");
} else {
ALOGE("Test pattern EGL shader program initialization failed.");
}
+
+ mCHandle = glGetUniformLocation(mProgram, "uC");
+ mPositionHandle = glGetAttribLocation(mProgram, "aPosition");
+ mTextureCoordHandle = glGetAttribLocation(mProgram, "aTextureCoord");
+
+ // Pass vertex array to draw.
+ glEnableVertexAttribArray(mPositionHandle);
+ // Prepare the triangle coordinate data.
+ glVertexAttribPointer(mPositionHandle, kCoordsPerVertex, GL_FLOAT, false,
+ kSquareCoords.size(), kSquareCoords.data());
+
+ glEnableVertexAttribArray(mTextureCoordHandle);
+ glVertexAttribPointer(mTextureCoordHandle, 2, GL_FLOAT, false,
+ kTextureCoords.size(), kTextureCoords.data());
}
-bool EglTestPatternProgram::draw(int width, int height, int frameNumber) {
- glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height));
- checkEglError("glViewport");
-
+bool EglTestPatternProgram::draw(int frameNumber) {
// Load compiled shader.
glUseProgram(mProgram);
checkEglError("glUseProgram");
@@ -219,28 +238,8 @@
float time = float(frameNumber) / 120.0f;
const std::complex<float> c(std::sin(time) * 0.78f, std::cos(time) * 0.78f);
- // Pass uniform values to the shader.
- int resolutionHandle = glGetUniformLocation(mProgram, "uResolution");
- checkEglError("glGetUniformLocation -> uResolution");
- glUniform2f(resolutionHandle, static_cast<float>(width),
- static_cast<float>(height));
- checkEglError("glUniform2f -> uResolution");
-
// Pass "C" constant value determining the Julia set to the shader.
- int cHandle = glGetUniformLocation(mProgram, "uC");
- glUniform2f(cHandle, c.imag(), c.real());
-
- // Pass chroma value to the shader.
- int uvHandle = glGetUniformLocation(mProgram, "uUV");
- glUniform2f(uvHandle, (c.imag() + 1.f) / 2.f, (c.real() + 1.f) / 2.f);
-
- // Pass vertex array to draw.
- int positionHandle = glGetAttribLocation(mProgram, "vPosition");
- glEnableVertexAttribArray(positionHandle);
-
- // Prepare the triangle coordinate data.
- glVertexAttribPointer(positionHandle, kCoordsPerVertex, GL_FLOAT, false,
- kSquareCoords.size(), kSquareCoords.data());
+ glUniform2f(mCHandle, c.imag(), c.real());
// Draw triangle strip forming a square filling the viewport.
glDrawElements(GL_TRIANGLES, kDrawOrder.size(), GL_UNSIGNED_BYTE,
diff --git a/services/camera/virtualcamera/util/EglProgram.h b/services/camera/virtualcamera/util/EglProgram.h
index c695cbb..d09f11e 100644
--- a/services/camera/virtualcamera/util/EglProgram.h
+++ b/services/camera/virtualcamera/util/EglProgram.h
@@ -46,7 +46,12 @@
public:
EglTestPatternProgram();
- bool draw(int width, int height, int frameNumber);
+ bool draw(int frameNumber);
+
+ private:
+ int mPositionHandle = -1;
+ int mTextureCoordHandle = -1;
+ int mCHandle = -1;
};
// Shader program to draw texture.
diff --git a/services/camera/virtualcamera/util/EglSurfaceTexture.cc b/services/camera/virtualcamera/util/EglSurfaceTexture.cc
index 9f26e19..7de5020 100644
--- a/services/camera/virtualcamera/util/EglSurfaceTexture.cc
+++ b/services/camera/virtualcamera/util/EglSurfaceTexture.cc
@@ -15,6 +15,7 @@
*/
// #define LOG_NDEBUG 0
+#include "utils/Timers.h"
#define LOG_TAG "EglSurfaceTexture"
#include <cstdint>
@@ -63,6 +64,11 @@
return mGlConsumer->getCurrentBuffer();
}
+bool EglSurfaceTexture::waitForNextFrame(const std::chrono::nanoseconds timeout) {
+ return mSurface->waitForNextFrame(mGlConsumer->getFrameNumber(),
+ static_cast<nsecs_t>(timeout.count()));
+}
+
GLuint EglSurfaceTexture::updateTexture() {
mGlConsumer->updateTexImage();
return mTextureId;
diff --git a/services/camera/virtualcamera/util/EglSurfaceTexture.h b/services/camera/virtualcamera/util/EglSurfaceTexture.h
index faad7c4..b9c5126 100644
--- a/services/camera/virtualcamera/util/EglSurfaceTexture.h
+++ b/services/camera/virtualcamera/util/EglSurfaceTexture.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_COMPANION_VIRTUALCAMERA_EGLSURFACETEXTURE_H
#define ANDROID_COMPANION_VIRTUALCAMERA_EGLSURFACETEXTURE_H
+#include <chrono>
#include <cstdint>
#include "GLES/gl.h"
@@ -51,6 +52,12 @@
// Get height of surface / texture.
uint32_t getHeight() const;
+ // Wait for next frame to be available in the surface
+ // until timeout.
+ //
+ // Returns false on timeout, true if new frame was received before timeout.
+ bool waitForNextFrame(std::chrono::nanoseconds timeout);
+
// Update the texture with the most recent submitted buffer.
// Most be called on thread with EGL context.
//
diff --git a/services/camera/virtualcamera/util/TestPatternHelper.cc b/services/camera/virtualcamera/util/TestPatternHelper.cc
deleted file mode 100644
index 274996a..0000000
--- a/services/camera/virtualcamera/util/TestPatternHelper.cc
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// #define LOG_NDEBUG 0
-
-#define LOG_TAG "TestPatternHelper"
-
-#include "TestPatternHelper.h"
-
-#include <complex>
-#include <cstdint>
-
-#include "log/log.h"
-#include "nativebase/nativebase.h"
-#include "system/graphics.h"
-#include "ui/GraphicBuffer.h"
-#include "utils/Errors.h"
-
-namespace android {
-namespace companion {
-namespace virtualcamera {
-
-namespace {
-
-using namespace std::chrono_literals;
-
-static constexpr std::chrono::milliseconds kAcquireFenceTimeout = 500ms;
-
-uint8_t julia(const std::complex<float> n, const std::complex<float> c) {
- std::complex<float> z = n;
- for (int i = 0; i < 64; i++) {
- z = z * z + c;
- if (std::abs(z) > 2.0) return i * 4;
- }
- return 0xff;
-}
-
-uint8_t pixelToFractal(const int x, const int y, const int width,
- const int height, const std::complex<float> c) {
- std::complex<float> n(float(x) / float(width) - 0.5,
- float(y) / float(height) - 0.5);
- return julia(n * 5.f, c);
-}
-
-void renderTestPatternYcbCr420(const android_ycbcr& ycbr, const int width,
- const int height, const int frameNumber) {
- float time = float(frameNumber) / 120.0f;
- const std::complex<float> c(std::sin(time), std::cos(time));
-
- uint8_t* y = reinterpret_cast<uint8_t*>(ycbr.y);
- uint8_t* cb = reinterpret_cast<uint8_t*>(ycbr.cb);
- uint8_t* cr = reinterpret_cast<uint8_t*>(ycbr.cr);
-
- for (int row = 0; row < height; row++) {
- for (int col = 0; col < width; col++) {
- y[row * ycbr.ystride + col] =
- pixelToFractal(col, row, width, height, c * 0.78f);
- }
- }
-
- int cWidth = width / 2;
- int cHeight = height / 2;
- for (int row = 0; row < cHeight; row++) {
- for (int col = 0; col < cWidth; col++) {
- cb[row * ycbr.cstride + col * ycbr.chroma_step] =
- static_cast<uint8_t>((float(col) / float(cWidth)) * 255.f);
- cr[row * ycbr.cstride + col * ycbr.chroma_step] =
- static_cast<uint8_t>((float(row) / float(cHeight)) * 255.f);
- }
- }
-}
-
-} // namespace
-
-void renderTestPatternYCbCr420(sp<Surface> surface, int frameNumber) {
- if (surface == nullptr) {
- ALOGE("%s: null surface, skipping render", __func__);
- return;
- }
-
- ANativeWindowBuffer* buffer;
- int fenceFd;
- int ret = ANativeWindow_dequeueBuffer(surface.get(), &buffer, &fenceFd);
- if (ret != NO_ERROR) {
- ALOGE(
- "%s: Error while deuqueing buffer from surface, "
- "ANativeWindow_dequeueBuffer returned %d",
- __func__, ret);
- return;
- }
-
- if (buffer == nullptr) {
- ALOGE("%s: ANativeWindowBuffer is null after dequeing", __func__);
- return;
- }
-
- sp<Fence> fence = sp<Fence>::make(fenceFd);
- if (fence->isValid()) {
- ret = fence->wait(kAcquireFenceTimeout.count());
- if (ret != NO_ERROR) {
- ALOGE("%s: Timeout while waiting for the fence to clear", __func__);
- ANativeWindow_queueBuffer(surface.get(), buffer, fence->dup());
- return;
- }
- }
-
- sp<GraphicBuffer> gBuffer = GraphicBuffer::from(buffer);
- android_ycbcr ycbr;
-
- ret = gBuffer->lockAsyncYCbCr(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &ycbr,
- fence->dup());
- if (ret != NO_ERROR) {
- ALOGE("%s: Failed to lock buffer retrieved from surface, ret %d", __func__,
- ret);
- return;
- }
-
- renderTestPatternYcbCr420(ycbr, gBuffer->getWidth(), gBuffer->getHeight(),
- frameNumber);
-
- ret = gBuffer->unlock();
- if (ret != NO_ERROR) {
- ALOGE("%s: Failed to unlock buffer, ret %d", __func__, ret);
- return;
- }
-
- ret = ANativeWindow_queueBuffer(surface.get(), buffer, /*fenceFd=*/-1);
- if (ret != NO_ERROR) {
- ALOGE(
- "%s: Error while queing buffer to surface, ANativeWindow_queueBuffer "
- "returned %d",
- __func__, ret);
- return;
- }
-}
-
-} // namespace virtualcamera
-} // namespace companion
-} // namespace android
diff --git a/services/camera/virtualcamera/util/TestPatternHelper.h b/services/camera/virtualcamera/util/TestPatternHelper.h
deleted file mode 100644
index f842b29..0000000
--- a/services/camera/virtualcamera/util/TestPatternHelper.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_COMPANION_VIRTUALCAMERA_TESTPATTERNHELPER_H
-#define ANDROID_COMPANION_VIRTUALCAMERA_TESTPATTERNHELPER_H
-
-#include "gui/Surface.h"
-
-namespace android {
-namespace companion {
-namespace virtualcamera {
-
-// Helper function for rendering test pattern into Surface.
-void renderTestPatternYCbCr420(sp<Surface> surface, int frameNumber);
-
-} // namespace virtualcamera
-} // namespace companion
-} // namespace android
-
-#endif // ANDROID_COMPANION_VIRTUALCAMERA_TESTPATTERNHELPER_H
diff --git a/services/mediametrics/include/mediametricsservice/AudioTypes.h b/services/mediametrics/include/mediametricsservice/AudioTypes.h
index b5fe28b..59654bf 100644
--- a/services/mediametrics/include/mediametricsservice/AudioTypes.h
+++ b/services/mediametrics/include/mediametricsservice/AudioTypes.h
@@ -18,6 +18,7 @@
#include <string>
#include <unordered_map>
+#include <vector>
namespace android::mediametrics::types {