Merge "Add 'defaults' for 'av-audio-types-aidl'" into main
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 4dc57d2..cba26da 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -2287,6 +2287,8 @@
* <p>When low light boost is enabled by setting the AE mode to
* 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light
* boost when the light level threshold is exceeded.</p>
+ * <p>This field is present in the CaptureResult when the AE mode is set to
+ * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'. Otherwise, the field is not present.</p>
* <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can
* indicate when it is not being applied by returning 'INACTIVE'.</p>
* <p>This key will be absent from the CaptureResult if AE mode is not set to
@@ -8292,7 +8294,10 @@
* FPS.</p>
* <p>If the session configuration is not supported, the AE mode reported in the
* CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p>
- * <p>The application can observe the CapturerResult field
+ * <p>When this AE mode is enabled, the CaptureResult field
+ * ACAMERA_CONTROL_LOW_LIGHT_BOOST_STATE will be present and not null. Otherwise, the
+ * ACAMERA_CONTROL_LOW_LIGHT_BOOST_STATE field will not be present in the CaptureResult.</p>
+ * <p>The application can observe the CaptureResult field
* ACAMERA_CONTROL_LOW_LIGHT_BOOST_STATE to determine when low light boost is 'ACTIVE' or
* 'INACTIVE'.</p>
* <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index d388596..1a637ac 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -47,8 +47,13 @@
],
// Postsubmit tests for TV devices
"tv-postsubmit": [
- {
- "name": "DecoderRenderTest"
- }
+ {
+ "name": "CtsMediaDecoderTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.decoder.cts.DecoderRenderTest"
+ }
+ ]
+ }
]
}
diff --git a/media/libaaudio/src/flowgraph/SampleRateConverter.cpp b/media/libaaudio/src/flowgraph/SampleRateConverter.cpp
index a15fcb8..890057d 100644
--- a/media/libaaudio/src/flowgraph/SampleRateConverter.cpp
+++ b/media/libaaudio/src/flowgraph/SampleRateConverter.cpp
@@ -28,7 +28,8 @@
void SampleRateConverter::reset() {
FlowGraphNode::reset();
- mInputCursor = kInitialCallCount;
+ mInputCallCount = kInitialCallCount;
+ mInputCursor = 0;
}
// Return true if there is a sample available.
diff --git a/media/libaaudio/src/flowgraph/SampleRateConverter.h b/media/libaaudio/src/flowgraph/SampleRateConverter.h
index f883e6c..a4318f0 100644
--- a/media/libaaudio/src/flowgraph/SampleRateConverter.h
+++ b/media/libaaudio/src/flowgraph/SampleRateConverter.h
@@ -54,7 +54,7 @@
int32_t mNumValidInputFrames = 0; // number of valid frames currently in the input port buffer
// We need our own callCount for upstream calls because calls occur at a different rate.
// This means we cannot have cyclic graphs or merges that contain an SRC.
- int64_t mInputCallCount = 0;
+ int64_t mInputCallCount = kInitialCallCount;
};
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 5d96d8e..6772201 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -1680,14 +1680,14 @@
mSelectedDeviceId = deviceId;
if (mStatus == NO_ERROR) {
if (isOffloadedOrDirect_l()) {
- if (mState == STATE_STOPPED || mState == STATE_FLUSHED) {
- ALOGD("%s(%d): creating a new AudioTrack", __func__, mPortId);
- result = restoreTrack_l("setOutputDevice", true /* forceRestore */);
- } else {
+ if (isPlaying_l()) {
ALOGW("%s(%d). Offloaded or Direct track is not STOPPED or FLUSHED. "
"State: %s.",
__func__, mPortId, stateToString(mState));
result = INVALID_OPERATION;
+ } else {
+ ALOGD("%s(%d): creating a new AudioTrack", __func__, mPortId);
+ result = restoreTrack_l("setOutputDevice", true /* forceRestore */);
}
} else {
// allow track invalidation when track is not playing to propagate
diff --git a/media/libstagefright/timedtext/test/fuzzer/Android.bp b/media/libstagefright/timedtext/test/fuzzer/Android.bp
index 6590ebb..8724d51 100644
--- a/media/libstagefright/timedtext/test/fuzzer/Android.bp
+++ b/media/libstagefright/timedtext/test/fuzzer/Android.bp
@@ -48,8 +48,16 @@
],
fuzz_config: {
cc: [
- "android-media-fuzzing-reports@google.com",
+ "android-media-playback@google.com",
],
- componentid: 155276,
+ componentid: 42195,
+ hotlists: [
+ "4593311",
+ ],
+ description: "This fuzzer targets the APIs of libstagefright_timedtext",
+ vector: "local_no_privileges_required",
+ service_privilege: "constrained",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
},
}
diff --git a/media/module/extractors/mp4/MPEG4Extractor.cpp b/media/module/extractors/mp4/MPEG4Extractor.cpp
index b3707c8..cb2994e 100644
--- a/media/module/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/module/extractors/mp4/MPEG4Extractor.cpp
@@ -1615,39 +1615,6 @@
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) {
@@ -3907,17 +3874,18 @@
}
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]);
- // we can get duration value from U64_AT(&buffer[28])
+ duration = 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]);
- // we can get duration value from U32_AT(&buffer[20])
+ duration = U32_AT(&buffer[20]);
} else {
return ERROR_UNSUPPORTED;
}
@@ -3926,6 +3894,15 @@
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/audiopolicy/common/managerdefinitions/include/AudioProfileVectorHelper.h b/services/audiopolicy/common/managerdefinitions/include/AudioProfileVectorHelper.h
index f84bda7..5fb0ad4 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioProfileVectorHelper.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioProfileVectorHelper.h
@@ -38,11 +38,40 @@
void appendAudioProfiles(AudioProfileVector &audioProfileVector,
const AudioProfileVector &audioProfileVectorToAppend);
+/**
+ * Check if the profile vector contains a profile that matches the given sampling rate, channel
+ * mask and format. Note that this method uses `audio_formats_match` from policy.h, which will
+ * consider PCM formats match if their bytes per sample are greater than 2.
+ *
+ * @param audioProfileVector
+ * @param samplingRate
+ * @param channelMask
+ * @param format
+ * @return NO_ERROR if the given profile vector is empty or it contains a profile that matches the
+ * given sampling rate, channel mask and format. Otherwise, returns BAD_VALUE.
+ */
status_t checkExactProfile(const AudioProfileVector &audioProfileVector,
const uint32_t samplingRate,
audio_channel_mask_t channelMask,
audio_format_t format);
+/**
+ * Check if the profile vector contains a profile that has exactly the same sampling rate, channel
+ * mask and format as the given values.
+ *
+ * @param audioProfileVector
+ * @param samplingRate
+ * @param channelMask
+ * @param format
+ * @return NO_ERROR if the given profile vector is empty or it contains a profile that that has
+ * exactly the same sampling rate, channel mask and format as the given values. Otherwise,
+ * returns BAD_VALUE.
+ */
+status_t checkIdenticalProfile(const AudioProfileVector &audioProfileVector,
+ const uint32_t samplingRate,
+ audio_channel_mask_t channelMask,
+ audio_format_t format);
+
status_t checkCompatibleProfile(const AudioProfileVector &audioProfileVector,
uint32_t &samplingRate,
audio_channel_mask_t &channelMask,
diff --git a/services/audiopolicy/common/managerdefinitions/include/PolicyAudioPort.h b/services/audiopolicy/common/managerdefinitions/include/PolicyAudioPort.h
index acf787b..6b21e9f 100644
--- a/services/audiopolicy/common/managerdefinitions/include/PolicyAudioPort.h
+++ b/services/audiopolicy/common/managerdefinitions/include/PolicyAudioPort.h
@@ -56,9 +56,14 @@
// Audio port IDs are in a different namespace than AudioFlinger unique IDs
static audio_port_handle_t getNextUniqueId();
- // searches for an exact match
+ // searches for an exact match, note that this method use `audio_formats_match` from policy.h,
+ // which will consider PCM formats match if their bytes per sample are greater than 2.
virtual status_t checkExactAudioProfile(const struct audio_port_config *config) const;
+ // searches for an identical match, unlike `checkExactAudioProfile` above, this will also
+ // require the formats to be exactly the same.
+ virtual status_t checkIdenticalAudioProfile(const struct audio_port_config *config) const;
+
// searches for a compatible match, currently implemented for input
// parameters are input|output, returned value is the best match.
status_t checkCompatibleAudioProfile(uint32_t &samplingRate,
@@ -100,6 +105,12 @@
const ChannelMaskSet &channelMasks) const;
void pickSamplingRate(uint32_t &rate, const SampleRateSet &samplingRates) const;
+ status_t checkAudioProfile(const struct audio_port_config *config,
+ std::function<status_t(const AudioProfileVector&,
+ const uint32_t samplingRate,
+ audio_channel_mask_t,
+ audio_format_t)> checkProfile) const;
+
sp<HwModule> mModule; // audio HW module exposing this I/O stream
AudioRouteVector mRoutes; // Routes involving this port
};
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioProfileVectorHelper.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioProfileVectorHelper.cpp
index 82f51ad..164f70a 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioProfileVectorHelper.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioProfileVectorHelper.cpp
@@ -190,6 +190,18 @@
return BAD_VALUE;
}
+status_t checkIdentical(const sp<AudioProfile> &audioProfile,
+ uint32_t samplingRate,
+ audio_channel_mask_t channelMask,
+ audio_format_t format) {
+ if(audioProfile->getFormat() == format &&
+ audioProfile->supportsChannels(channelMask) &&
+ audioProfile->supportsRate(samplingRate)) {
+ return NO_ERROR;
+ }
+ return BAD_VALUE;
+}
+
status_t checkCompatibleSamplingRate(const sp<AudioProfile> &audioProfile,
uint32_t samplingRate,
uint32_t &updatedSamplingRate)
@@ -320,23 +332,43 @@
return bestMatch > 0 ? NO_ERROR : BAD_VALUE;
}
-status_t checkExactProfile(const AudioProfileVector& audioProfileVector,
- const uint32_t samplingRate,
- audio_channel_mask_t channelMask,
- audio_format_t format)
-{
+namespace {
+
+status_t checkProfile(const AudioProfileVector& audioProfileVector,
+ const uint32_t samplingRate,
+ audio_channel_mask_t channelMask,
+ audio_format_t format,
+ std::function<status_t(const sp<AudioProfile> &, uint32_t,
+ audio_channel_mask_t, audio_format_t)> check) {
if (audioProfileVector.empty()) {
return NO_ERROR;
}
for (const auto& profile : audioProfileVector) {
- if (checkExact(profile, samplingRate, channelMask, format) == NO_ERROR) {
+ if (check(profile, samplingRate, channelMask, format) == NO_ERROR) {
return NO_ERROR;
}
}
return BAD_VALUE;
}
+} // namespace
+
+status_t checkExactProfile(const AudioProfileVector& audioProfileVector,
+ const uint32_t samplingRate,
+ audio_channel_mask_t channelMask,
+ audio_format_t format)
+{
+ return checkProfile(audioProfileVector, samplingRate, channelMask, format, checkExact);
+}
+
+status_t checkIdenticalProfile(const AudioProfileVector &audioProfileVector,
+ const uint32_t samplingRate,
+ audio_channel_mask_t channelMask,
+ audio_format_t format) {
+ return checkProfile(audioProfileVector, samplingRate, channelMask, format, checkIdentical);
+}
+
status_t checkCompatibleProfile(const AudioProfileVector &audioProfileVector,
uint32_t &samplingRate,
audio_channel_mask_t &channelMask,
diff --git a/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp b/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp
index d9fbd89..991b103 100644
--- a/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp
@@ -73,7 +73,7 @@
if (isRecordThread)
{
if ((flags & AUDIO_INPUT_FLAG_MMAP_NOIRQ) != 0) {
- if (checkExactAudioProfile(&config) != NO_ERROR) {
+ if (checkIdenticalAudioProfile(&config) != NO_ERROR) {
return result;
}
result = EXACT_MATCH;
@@ -86,7 +86,13 @@
return result;
}
} else {
- if (checkExactAudioProfile(&config) == NO_ERROR) {
+ if ((flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) != 0 ||
+ (flags & AUDIO_OUTPUT_FLAG_BIT_PERFECT) != 0) {
+ if (checkIdenticalAudioProfile(&config) != NO_ERROR) {
+ return result;
+ }
+ result = EXACT_MATCH;
+ } else if (checkExactAudioProfile(&config) == NO_ERROR) {
result = EXACT_MATCH;
} else {
return result;
diff --git a/services/audiopolicy/common/managerdefinitions/src/PolicyAudioPort.cpp b/services/audiopolicy/common/managerdefinitions/src/PolicyAudioPort.cpp
index ce8178f..ae99191 100644
--- a/services/audiopolicy/common/managerdefinitions/src/PolicyAudioPort.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/PolicyAudioPort.cpp
@@ -63,21 +63,11 @@
status_t PolicyAudioPort::checkExactAudioProfile(const struct audio_port_config *config) const
{
- status_t status = NO_ERROR;
- auto config_mask = config->config_mask;
- if (config_mask & AUDIO_PORT_CONFIG_GAIN) {
- config_mask &= ~AUDIO_PORT_CONFIG_GAIN;
- status = asAudioPort()->checkGain(&config->gain, config->gain.index);
- if (status != NO_ERROR) {
- return status;
- }
- }
- if (config_mask != 0) {
- // TODO should we check sample_rate / channel_mask / format separately?
- status = checkExactProfile(asAudioPort()->getAudioProfiles(), config->sample_rate,
- config->channel_mask, config->format);
- }
- return status;
+ return checkAudioProfile(config, checkExactProfile);
+}
+
+status_t PolicyAudioPort::checkIdenticalAudioProfile(const struct audio_port_config *config) const {
+ return checkAudioProfile(config, checkIdenticalProfile);
}
void PolicyAudioPort::pickSamplingRate(uint32_t &pickedRate,
@@ -266,4 +256,28 @@
asAudioPort()->getName().c_str(), samplingRate, channelMask, format);
}
+status_t PolicyAudioPort::checkAudioProfile(
+ const struct audio_port_config *config,
+ std::function<status_t(const AudioProfileVector &,
+ const uint32_t,
+ audio_channel_mask_t,
+ audio_format_t)> checkProfile) const {
+ status_t status = NO_ERROR;
+ auto config_mask = config->config_mask;
+ if (config_mask & AUDIO_PORT_CONFIG_GAIN) {
+ config_mask &= ~AUDIO_PORT_CONFIG_GAIN;
+ status = asAudioPort()->checkGain(&config->gain, config->gain.index);
+ if (status != NO_ERROR) {
+ return status;
+ }
+ }
+ if (config_mask != 0) {
+ // TODO should we check sample_rate / channel_mask / format separately?
+ status = checkProfile(asAudioPort()->getAudioProfiles(), config->sample_rate,
+ config->channel_mask, config->format);
+ }
+ return status;
+
+}
+
} // namespace android
diff --git a/services/camera/libcameraservice/aidl/AidlUtils.cpp b/services/camera/libcameraservice/aidl/AidlUtils.cpp
index 48d78e1..d23566c 100644
--- a/services/camera/libcameraservice/aidl/AidlUtils.cpp
+++ b/services/camera/libcameraservice/aidl/AidlUtils.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "AidlUtils"
+//#define LOG_NDEBUG 0
#include <aidl/AidlUtils.h>
#include <aidl/ExtensionMetadataTags.h>
@@ -314,6 +315,7 @@
if (vndkVersion == __ANDROID_API_FUTURE__) {
// VNDK version derived from ro.board.api_level is a version code-name that
// corresponds to the current SDK version.
+ ALOGV("%s: VNDK version is API FUTURE, not filtering any keys", __FUNCTION__);
return OK;
}
const auto &apiLevelToKeys =
@@ -322,9 +324,14 @@
// versions above the given one, need to have their keys filtered from the
// metadata in order to avoid metadata invalidation.
auto it = apiLevelToKeys.upper_bound(vndkVersion);
+ ALOGV("%s: VNDK version for filtering is %d", __FUNCTION__ , vndkVersion);
while (it != apiLevelToKeys.end()) {
for (const auto &key : it->second) {
status_t res = metadata.erase(key);
+ // Should be okay to not use get_local_camera_metadata_tag_name
+ // since we're not filtering vendor tags
+ ALOGV("%s: Metadata key being filtered is %s", __FUNCTION__ ,
+ get_camera_metadata_tag_name(key));
if (res != OK) {
ALOGE("%s metadata key %d could not be erased", __FUNCTION__, key);
return res;
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.cc b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
index ca62cce..f5cf092 100644
--- a/services/camera/virtualcamera/VirtualCameraRenderThread.cc
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include "hardware/gralloc.h"
#define LOG_TAG "VirtualCameraRenderThread"
#include "VirtualCameraRenderThread.h"
@@ -45,6 +44,7 @@
#include "android-base/thread_annotations.h"
#include "android/binder_auto_utils.h"
#include "android/hardware_buffer.h"
+#include "hardware/gralloc.h"
#include "system/camera_metadata.h"
#include "ui/GraphicBuffer.h"
#include "ui/Rect.h"
@@ -422,9 +422,47 @@
void VirtualCameraRenderThread::processCaptureRequest(
const ProcessCaptureRequestTask& request) {
- const std::chrono::nanoseconds timestamp =
+ 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(
+ mLastAcquisitionTimestampNanoseconds.exchange(timestamp.count(),
+ std::memory_order_relaxed));
+
+ if (request.getRequestSettings().fpsRange) {
+ const int maxFps =
+ std::max(1, request.getRequestSettings().fpsRange->maxFps);
+ const std::chrono::nanoseconds minFrameDuration(
+ static_cast<uint64_t>(1e9 / maxFps));
+ const std::chrono::nanoseconds frameDuration =
+ timestamp - lastAcquisitionTimestamp;
+ if (frameDuration < minFrameDuration) {
+ // We're too fast for the configured maxFps, let's wait a bit.
+ const std::chrono::nanoseconds sleepTime =
+ minFrameDuration - frameDuration;
+ ALOGV("Current frame duration would be %" PRIu64
+ " ns corresponding to, "
+ "sleeping for %" PRIu64
+ " ns before updating texture to match maxFps %d",
+ static_cast<uint64_t>(frameDuration.count()),
+ static_cast<uint64_t>(sleepTime.count()), maxFps);
+
+ std::this_thread::sleep_for(sleepTime);
+ timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch());
+ mLastAcquisitionTimestampNanoseconds.store(timestamp.count(),
+ std::memory_order_relaxed);
+ }
+ }
+
+ // Acquire new (most recent) image from the Surface.
+ mEglSurfaceTexture->updateTexture();
CaptureResult captureResult;
captureResult.fmqResultSize = 0;
@@ -439,14 +477,6 @@
const std::vector<CaptureRequestBuffer>& buffers = request.getBuffers();
captureResult.outputBuffers.resize(buffers.size());
- if (mTestMode) {
- // In test mode let's just render something to the Surface ourselves.
- renderTestPatternYCbCr420(mEglSurfaceTexture->getSurface(),
- request.getFrameNumber());
- }
-
- mEglSurfaceTexture->updateTexture();
-
for (int i = 0; i < buffers.size(); ++i) {
const CaptureRequestBuffer& reqBuffer = buffers[i];
StreamBuffer& resBuffer = captureResult.outputBuffers[i];
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.h b/services/camera/virtualcamera/VirtualCameraRenderThread.h
index e222d5b..dfb6f7b 100644
--- a/services/camera/virtualcamera/VirtualCameraRenderThread.h
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERARENDERTHREAD_H
#define ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERARENDERTHREAD_H
+#include <atomic>
#include <cstdint>
#include <deque>
#include <future>
@@ -195,6 +196,9 @@
std::condition_variable mCondVar;
volatile bool mPendingExit GUARDED_BY(mLock);
+ // Acquisition timestamp of last frame.
+ std::atomic<uint64_t> mLastAcquisitionTimestampNanoseconds;
+
// EGL helpers - constructed and accessed only from rendering thread.
std::unique_ptr<EglDisplayContext> mEglDisplayContext;
std::unique_ptr<EglTextureProgram> mEglTextureYuvProgram;