Merge "Support AAudioStream_flushFromFrame." into main
diff --git a/media/libaaudio/examples/utils/AAudioSimplePlayer.h b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
index a585078..a413c4b 100644
--- a/media/libaaudio/examples/utils/AAudioSimplePlayer.h
+++ b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
@@ -120,7 +120,8 @@
AAudioStream_dataCallback dataCallback = nullptr,
AAudioStream_errorCallback errorCallback = nullptr,
void *userContext = nullptr,
- AAudioStream_presentationEndCallback presentationEndCallback = nullptr) {
+ AAudioStream_presentationEndCallback presentationEndCallback = nullptr,
+ AAudioStream_partialDataCallback partialDataCallback = nullptr) {
aaudio_result_t result = AAUDIO_OK;
// Use an AAudioStreamBuilder to contain requested parameters.
@@ -142,6 +143,9 @@
AAudioStreamBuilder_setPresentationEndCallback(
builder, presentationEndCallback, userContext);
}
+ if (partialDataCallback != nullptr) {
+ AAudioStreamBuilder_setPartialDataCallback(builder, partialDataCallback, userContext);
+ }
//AAudioStreamBuilder_setFramesPerDataCallback(builder, CALLBACK_SIZE_FRAMES);
//AAudioStreamBuilder_setBufferCapacityInFrames(builder, 48 * 8);
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index 051600b..3e5e9ff 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -2645,6 +2645,70 @@
AAUDIO_API aaudio_result_t AAudioStream_setOffloadEndOfStream(AAudioStream* _Nonnull stream)
__INTRODUCED_IN(36);
+/**
+ * The values are defined to be used for the accuracy requirement when calling
+ * {@link AAudioStream_flushFromFrame}.
+ */
+typedef enum AAudio_FlushFromAccuracy : int32_t {
+ /**
+ * There is not requirement for frame accuracy when flushing, it is up to the framework
+ * to select a right position to flush from.
+ */
+ AAUDIO_FLUSH_FROM_ACCURACY_UNDEFINED = 0,
+
+ /**
+ * The stream must be flushed from the requested position. If it is not possible to flush
+ * from the requested position, the stream must not be flushed.
+ */
+ AAUDIO_FLUSH_FROM_FRAME_ACCURATE = 1
+} AAudio_FlushFromAccuracy;
+
+/**
+ * Flush all data from given position. If this operation returns successfully, the following
+ * data will be written from the returned position.
+ *
+ * This method will only work when the performance mode is
+ * {@link AAUDIO_PERFORMANCE_MODE_POWER_SAVING_OFFLOADED}.
+ *
+ * The requested position must not be negative or greater than the written frames. The current
+ * written position can be known by querying {@link AAudioStream_getFramesWritten}.
+ *
+ * When clients request to flush from a certain position, the audio system will return the actual
+ * flushed position based on the requested position, playback latency, etc. The written position
+ * will be updated as the actual flush position. All data behind actual flush position will be
+ * flushed. The client can provide data from actual flush position at next write operation or data
+ * callback request. When the stream is flushed, the stream end will be reset. The client must not
+ * write any data before this function returns. Otherwise, the data will be corrupted. When the
+ * method returns successfully and the stream is active, the client must write data immediately
+ * if little audio data remains. Otherwise, the stream will underrun.
+ *
+ * If apps prefer data callback, it is suggested to use {@link AAudioStream_partialDataCallback}.
+ * In that case, after the stream is flushed successfully by calling this method, the app can just
+ * fill partial data from the data callback instead of as much (partial) data as possible. That
+ * can help avoid underrun after successfully calling this method.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream().
+ * @param accuracy the accuracy requirement when flushing. The value must be one of the valid
+ * AAudio_FlushFromAccuracy value.
+ * @param[in|out] position the start point in frames to flush the stream. If flushing from frame
+ * is supported for the stream, the position will be updated as the actual
+ * flush from position when successfully flush or the suggested position
+ * to flush from if it cannot flush from the requested position. If there
+ * is not enough data to safely flush, position will remain the same.
+ * @return AAUDIO_OK if the stream is successfully flushed.
+ * AAUDIO_ERROR_UNIMPLEMENTED if it is not supported by the device.
+ * AAUDIO_ERROR_ILLEGAL_ARGUMENT if the stream is not an output offload stream or the
+ * accuracy is not one of valid AAudio_FlushFromAccuracy values.
+ * AAUDIO_ERROR_OUT_OF_RANGE if the provided position is negative or is greater than the
+ * frames written or the stream cannot flush from the requested position and
+ * AAUDIO_FLUSH_FROM_FRAME_ACCURATE is requested.
+ * AAUDIO_ERROR_DISCONNECTED if aaudio service is dead or the stream is disconnected.
+ */
+AAUDIO_API aaudio_result_t AAudioStream_flushFromFrame(
+ AAudioStream* _Nonnull stream,
+ AAudio_FlushFromAccuracy accuracy,
+ int64_t* _Nonnull inOutPosition) __INTRODUCED_IN(37);
+
/************************************************************************************
* Helper functions for AAudio MMAP.
* AAudio MMAP data path uses a memory region that is shared between the hardware and
diff --git a/media/libaaudio/src/binding/AAudioBinderAdapter.cpp b/media/libaaudio/src/binding/AAudioBinderAdapter.cpp
index ee7480b..bcf3353 100644
--- a/media/libaaudio/src/binding/AAudioBinderAdapter.cpp
+++ b/media/libaaudio/src/binding/AAudioBinderAdapter.cpp
@@ -166,4 +166,17 @@
return result;
}
+aaudio_result_t AAudioBinderAdapter::updateTimestamp(
+ const aaudio::AAudioHandleInfo &streamHandleInfo) {
+ if (streamHandleInfo.getServiceLifetimeId() != mServiceLifetimeId) {
+ return AAUDIO_ERROR_DISCONNECTED;
+ }
+ aaudio_result_t result;
+ Status status = mDelegate->updateTimestamp(streamHandleInfo.getHandle(), &result);
+ if (!status.isOk()) {
+ result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
+ }
+ return result;
+}
+
} // namespace aaudio
diff --git a/media/libaaudio/src/binding/AAudioBinderAdapter.h b/media/libaaudio/src/binding/AAudioBinderAdapter.h
index 301150f..fe68245 100644
--- a/media/libaaudio/src/binding/AAudioBinderAdapter.h
+++ b/media/libaaudio/src/binding/AAudioBinderAdapter.h
@@ -60,6 +60,8 @@
aaudio_result_t exitStandby(const AAudioHandleInfo& streamHandleInfo,
AudioEndpointParcelable &parcelable) override;
+ aaudio_result_t updateTimestamp(const AAudioHandleInfo& streamHandleInfo) override;
+
private:
IAAudioService* const mDelegate;
// A unique id to recognize the service that the adapter connected to.
diff --git a/media/libaaudio/src/binding/AAudioBinderClient.cpp b/media/libaaudio/src/binding/AAudioBinderClient.cpp
index 439d5af..3c2319b 100644
--- a/media/libaaudio/src/binding/AAudioBinderClient.cpp
+++ b/media/libaaudio/src/binding/AAudioBinderClient.cpp
@@ -206,3 +206,10 @@
return service->exitStandby(streamHandleInfo, endpointOut);
}
+
+aaudio_result_t AAudioBinderClient::updateTimestamp(const AAudioHandleInfo& streamHandleInfo) {
+ std::shared_ptr<AAudioServiceInterface> service = getAAudioService();
+ if (service.get() == nullptr) return AAUDIO_ERROR_NO_SERVICE;
+
+ return service->updateTimestamp(streamHandleInfo);
+}
diff --git a/media/libaaudio/src/binding/AAudioBinderClient.h b/media/libaaudio/src/binding/AAudioBinderClient.h
index 66d3295..865eb4a 100644
--- a/media/libaaudio/src/binding/AAudioBinderClient.h
+++ b/media/libaaudio/src/binding/AAudioBinderClient.h
@@ -116,6 +116,8 @@
aaudio_result_t exitStandby(const AAudioHandleInfo& streamHandleInfo,
AudioEndpointParcelable &endpointOut) override;
+ aaudio_result_t updateTimestamp(const AAudioHandleInfo& streamHandleInfo) override;
+
void onStreamChange(aaudio_handle_t /*handle*/, int32_t /*opcode*/, int32_t /*value*/) {
// TODO This is just a stub so we can have a client Binder to pass to the service.
// TODO Implemented in a later CL.
diff --git a/media/libaaudio/src/binding/AAudioServiceInterface.h b/media/libaaudio/src/binding/AAudioServiceInterface.h
index 79f498b..2630b59 100644
--- a/media/libaaudio/src/binding/AAudioServiceInterface.h
+++ b/media/libaaudio/src/binding/AAudioServiceInterface.h
@@ -108,6 +108,15 @@
*/
virtual aaudio_result_t exitStandby(const AAudioHandleInfo& streamHandleInfo,
AudioEndpointParcelable &parcelable) = 0;
+
+ /**
+ * AAudio service will send timestamp periodically. Client call this method to trigger
+ * a timestamp update immediately.
+ *
+ * @param streamHandleInfo the stream handle
+ * @return
+ */
+ virtual aaudio_result_t updateTimestamp(const AAudioHandleInfo& streamHandleInfo) = 0;
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/binding/aidl/aaudio/IAAudioService.aidl b/media/libaaudio/src/binding/aidl/aaudio/IAAudioService.aidl
index 485c2e2..768ebe7 100644
--- a/media/libaaudio/src/binding/aidl/aaudio/IAAudioService.aidl
+++ b/media/libaaudio/src/binding/aidl/aaudio/IAAudioService.aidl
@@ -80,4 +80,6 @@
int clientThreadId);
int exitStandby(int streamHandle, out Endpoint endpoint);
+
+ int updateTimestamp(int streamHandle);
}
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index b3e6ef6..a8f7f5a 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -515,11 +515,7 @@
// Start data callback thread.
if (result == AAUDIO_OK && isDataCallbackSet()) {
// Launch the callback loop thread.
- int64_t periodNanos = mCallbackFrames
- * AAUDIO_NANOS_PER_SECOND
- / getSampleRate();
- mCallbackEnabled.store(true);
- result = createThread_l(periodNanos, aaudio_callback_thread_proc, this);
+ result = startCallback_l();
}
if (result != AAUDIO_OK) {
setState(originalState);
@@ -563,6 +559,12 @@
}
}
+aaudio_result_t AudioStreamInternal::startCallback_l() {
+ int64_t periodNanos = mCallbackFrames * AAUDIO_NANOS_PER_SECOND / getSampleRate();
+ mCallbackEnabled.store(true);
+ return createThread_l(periodNanos, aaudio_callback_thread_proc, this);
+}
+
aaudio_result_t AudioStreamInternal::requestStop_l() {
aaudio_result_t result = stopCallback_l();
if (result != AAUDIO_OK) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 06131c5..0b7bb07 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -155,6 +155,8 @@
*/
bool isClockModelInControl() const;
+ aaudio_result_t startCallback_l() REQUIRES(mStreamLock);
+
IsochronousClockModel mClockModel; // timing model for chasing the HAL
std::unique_ptr<AudioEndpoint> mAudioEndpoint; // source for reads or sink for writes
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index 2346e00..b01bac3 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -503,6 +503,62 @@
mCallbackCV.notify_one();
}
+aaudio_result_t AudioStreamInternalPlay::flushFromFrame_l(
+ AAudio_FlushFromAccuracy accuracy, int64_t* position) {
+ if (getServiceHandle() == AAUDIO_HANDLE_INVALID) {
+ ALOGD("%s() mServiceStreamHandle invalid", __func__);
+ return AAUDIO_ERROR_DISCONNECTED;
+ }
+ if (isDisconnected()) {
+ ALOGD("%s() but DISCONNECTED", __func__);
+ return AAUDIO_ERROR_DISCONNECTED;
+ }
+
+ aaudio_result_t result = AAUDIO_OK;
+ {
+ std::lock_guard _endpointLock(mEndpointMutex);
+ int64_t framesWritten = getFramesWritten();
+ if (framesWritten < *position) {
+ ALOGE("%s(), the requested position is not yet written", __func__);
+ result = AAUDIO_ERROR_OUT_OF_RANGE;
+ }
+
+ // The position is updated from the server, it may not be very accurate if the stream has
+ // been active for a while. In that case, updates the latest timestamp and then get the
+ // actual rewind position again.
+ if (aaudio_result_t res = mServiceInterface.updateTimestamp(mServiceStreamHandleInfo);
+ res != AAUDIO_OK) {
+ ALOGE("%s() failed to update timestamp, error=%d", __func__, res);
+ return res;
+ }
+ processCommands();
+ const int64_t safePosition = getFramesRead() + mOffloadSafeMarginInFrames;
+ if (safePosition > framesWritten) {
+ ALOGE("%s() do not have enough data, safePosition=%jd, frameWritten=%jd",
+ __func__, safePosition, framesWritten);
+ return AAUDIO_ERROR_OUT_OF_RANGE;
+ }
+ int64_t actualPosition = std::max(safePosition, *position);
+ if (accuracy == AAUDIO_FLUSH_FROM_FRAME_ACCURATE && actualPosition != *position) {
+ result = AAUDIO_ERROR_OUT_OF_RANGE;
+ }
+ if (result != AAUDIO_OK) {
+ *position = actualPosition;
+ return result;
+ }
+
+ // Rewind successfully, update the written position as the rewound position.
+ mLastFramesWritten = actualPosition;
+ mAudioEndpoint->setDataWriteCounter(actualPosition - mFramesOffsetFromService);
+ }
+ {
+ std::lock_guard _streamEndLock(mStreamEndMutex);
+ mOffloadEosPending = false;
+ }
+ wakeupCallbackThread();
+ return result;
+}
+
// Render audio in the application callback and then write the data to the stream.
void *AudioStreamInternalPlay::callbackLoop() {
ALOGD("%s() entering >>>>>>>>>>>>>>>", __func__);
@@ -528,8 +584,11 @@
}
}
}
- // Call application using the AAudio callback interface.
- callbackResult = maybeCallDataCallback(mCallbackBuffer.get(), mCallbackFrames);
+ {
+ std::lock_guard _endpointLock(mEndpointMutex);
+ // Call application using the AAudio callback interface.
+ callbackResult = maybeCallDataCallback(mCallbackBuffer.get(), mCallbackFrames);
+ }
if (callbackResult < 0) {
if (!shouldStopStream()) {
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.h b/media/libaaudio/src/client/AudioStreamInternalPlay.h
index f5c39a4..2b6a60b 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.h
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.h
@@ -104,6 +104,8 @@
aaudio_result_t requestStop_l() REQUIRES(mStreamLock) final;
void wakeupCallbackThread() final;
+ aaudio_result_t flushFromFrame_l(AAudio_FlushFromAccuracy accuracy, int64_t* position)
+ REQUIRES(mStreamLock) final;
private:
/*
* Asynchronous write with data conversion.
@@ -133,6 +135,8 @@
std::mutex mCallbackMutex;
std::condition_variable mCallbackCV;
bool mSuspendCallback GUARDED_BY(mCallbackMutex){false};
+
+ std::mutex mEndpointMutex;
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/core/AAudioAudio.cpp b/media/libaaudio/src/core/AAudioAudio.cpp
index a720643..d0567cf 100644
--- a/media/libaaudio/src/core/AAudioAudio.cpp
+++ b/media/libaaudio/src/core/AAudioAudio.cpp
@@ -26,6 +26,7 @@
#include <aaudio/AAudio.h>
#include <aaudio/AAudioTesting.h>
#include <com_android_media_aaudio.h>
+#include <com_android_media_audioserver.h>
#include <system/aaudio/AAudio.h>
#include <system/audio.h>
#include "AudioClock.h"
@@ -776,3 +777,12 @@
AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
return audioStream->setOffloadEndOfStream();
}
+
+AAUDIO_API aaudio_result_t AAudioStream_flushFromFrame(
+ AAudioStream* stream, AAudio_FlushFromAccuracy accuracy, int64_t* inOutPosition) {
+ if (!com::android::media::audioserver::mmap_pcm_offload_support()) {
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+ AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
+ return audioStream->flushFromFrame(accuracy, inOutPosition);
+}
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index d998e7e..123b705 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -28,6 +28,7 @@
#include <aaudio/AAudio.h>
#include <android-base/strings.h>
+#include <com_android_media_audioserver.h>
#include "AudioStreamBuilder.h"
#include "AudioStream.h"
@@ -669,6 +670,28 @@
return android::base::Join(mTags, AUDIO_ATTRIBUTES_TAGS_SEPARATOR);
}
+aaudio_result_t AudioStream::flushFromFrame(AAudio_FlushFromAccuracy accuracy, int64_t* position) {
+ ALOGD("%s(%d, %jd)", __func__, accuracy, *position);
+ if (!com_android_media_audioserver_mmap_pcm_offload_support()) {
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+ if (getDirection() != AAUDIO_DIRECTION_OUTPUT ||
+ getPerformanceMode() != AAUDIO_PERFORMANCE_MODE_POWER_SAVING_OFFLOADED ||
+ (accuracy != AAUDIO_FLUSH_FROM_ACCURACY_UNDEFINED &&
+ accuracy != AAUDIO_FLUSH_FROM_FRAME_ACCURATE)) {
+ return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
+ }
+ if (*position < 0) {
+ return AAUDIO_ERROR_OUT_OF_RANGE;
+ }
+ const int64_t requestedPosition = *position;
+ std::lock_guard lock(mStreamLock);
+ const aaudio_result_t result = flushFromFrame_l(accuracy, position);
+ ALOGD("%s(%d, %jd), actual position = %jd, result = %d",
+ __func__, accuracy, requestedPosition, *position, result);
+ return result;
+}
+
int AudioStream::dataCallbackInternal(void *audioData, int32_t numFrames) {
const aaudio_data_callback_result_t result = std::invoke(mDataCallbackProc,
(AAudioStream*) this,
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index db42422..67de5a9 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -96,6 +96,12 @@
virtual aaudio_result_t requestStop_l() REQUIRES(mStreamLock) = 0;
+ virtual aaudio_result_t flushFromFrame_l(AAudio_FlushFromAccuracy accuracy [[maybe_unused]],
+ int64_t* position [[maybe_unused]])
+ REQUIRES(mStreamLock) {
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+
public:
virtual aaudio_result_t getTimestamp(clockid_t clockId,
int64_t *framePosition,
@@ -521,6 +527,9 @@
aaudio_result_t safeReleaseCloseInternal() EXCLUDES(mStreamLock);
+ aaudio_result_t flushFromFrame(AAudio_FlushFromAccuracy accuracy, int64_t* position)
+ EXCLUDES(mStreamLock);
+
protected:
// PlayerBase allows the system to control the stream volume.
diff --git a/media/libaaudio/src/libaaudio.map.txt b/media/libaaudio/src/libaaudio.map.txt
index 4fed354..416f26f 100644
--- a/media/libaaudio/src/libaaudio.map.txt
+++ b/media/libaaudio/src/libaaudio.map.txt
@@ -79,6 +79,7 @@
AAudioStream_getOffloadDelay; #introduced=36
AAudioStream_getOffloadPadding; #introduced=36
AAudioStream_setOffloadEndOfStream; #introduced=36
+ AAudioStream_flushFromFrame; #introduced=37
AAudioStreamBuilder_addTag; # systemapi
AAudioStreamBuilder_clearTags; # systemapi
diff --git a/media/libaaudio/tests/Android.bp b/media/libaaudio/tests/Android.bp
index 1ff75be..1e099dc 100644
--- a/media/libaaudio/tests/Android.bp
+++ b/media/libaaudio/tests/Android.bp
@@ -292,3 +292,11 @@
header_libs: ["libaaudio_example_utils"],
shared_libs: ["libaaudio"],
}
+
+cc_binary {
+ name: "test_flush_from_frame",
+ defaults: ["libaaudio_tests_defaults"],
+ srcs: ["test_flush_from_frame.cpp"],
+ header_libs: ["libaaudio_example_utils"],
+ shared_libs: ["libaaudio"],
+}
diff --git a/media/libaaudio/tests/test_flush_from_frame.cpp b/media/libaaudio/tests/test_flush_from_frame.cpp
new file mode 100644
index 0000000..70727cd
--- /dev/null
+++ b/media/libaaudio/tests/test_flush_from_frame.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <stdio.h>
+#include <stdlib.h>
+#include <thread>
+#include <vector>
+
+#include <aaudio/AAudio.h>
+
+#include "AAudioArgsParser.h"
+#include "AAudioSimplePlayer.h"
+#include "SineGenerator.h"
+
+/**
+ * This tests AAudioStream_flushFromPosition. This test will do playback in offload mode
+ * and try to flush from frame every 2 seconds. It will switch the sine generator between
+ * 440Hz and 880Hz when flush from frame.
+ */
+
+constexpr static int DEFAULT_TIME_TO_RUN_IN_SECOND = 10;
+constexpr static int PLAYBACK_LENGTH_SECONDS = 2;
+constexpr static int MORE_DATA_IN_SECONDS = 3;
+
+int32_t MyPartialDataCallback(
+ AAudioStream* stream, void* userData, void* audioData, int32_t numFrames);
+
+void MyErrorCallback(AAudioStream* /*stream*/, void* /*userData*/, aaudio_result_t error);
+
+class MyPlayer : public AAudioSimplePlayer {
+public:
+ MyPlayer(AAudioArgsParser& argParser, bool useDataCallback)
+ : mArgParser(argParser), mUseDataCallback(useDataCallback) {}
+
+ aaudio_result_t open() {
+ aaudio_result_t result = AAudioSimplePlayer::open(
+ mArgParser,
+ nullptr /*dataCallback*/,
+ &MyErrorCallback,
+ this,
+ nullptr /*presentationEndCallback*/,
+ mUseDataCallback ? &MyPartialDataCallback : nullptr);
+ if (result != AAUDIO_OK) {
+ return result;
+ }
+ mChannelCount = getChannelCount();
+ mSampleRate = getSampleRate();
+ mSines.resize(2);
+ for (int i = 0; i < mChannelCount; ++i) {
+ SineGenerator sine;
+ sine.setup(440.0, mSampleRate);
+ mSines[0].push_back(sine);
+ SineGenerator sine1;
+ sine1.setup(880.0, mSampleRate);
+ mSines[1].push_back(sine1);
+ }
+ return result;
+ }
+
+ int32_t renderAudio(AAudioStream* stream, void* audioData, int32_t numFrames) {
+ int index = 0;
+ {
+ std::lock_guard _l(mFlushMutex);
+ if (mPendingFlush) {
+ return 0;
+ }
+ index = mSineGeneratorIndex;
+ }
+ // Just handle PCM_16 and PCM_FLOAT for testing
+ if (!fillData(stream, audioData, numFrames, index)) {
+ printf("Failed to render data, stop the stream\n");
+ return -1;
+ }
+ return numFrames;
+ }
+
+ void writeData() {
+ for (int i = 0; i < MORE_DATA_IN_SECONDS; ++i) {
+ writeOneSecondData();
+ }
+ }
+
+ aaudio_result_t flushFromFrame(AAudio_FlushFromAccuracy accuracy, int64_t* position) {
+ if (position == nullptr) {
+ return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
+ }
+ aaudio_result_t result = AAUDIO_OK;
+ {
+ std::lock_guard _l(mFlushMutex);
+ mPendingFlush = true;
+ mSineGeneratorIndex ^= 1;
+ }
+ {
+ std::lock_guard _l(mStreamMutex);
+ auto framesWritten = AAudioStream_getFramesWritten(getStream());
+ int64_t requestedPosition = *position;
+ result = AAudioStream_flushFromFrame(getStream(), accuracy, position);
+ printf("%s(%d, %jd) result=%d actual position=%jd, frames written before "
+ "flushFromFrame=%jd frames written after flushFromFrame=%jd\n",
+ __func__, accuracy, requestedPosition, result, *position, framesWritten,
+ AAudioStream_getFramesWritten(getStream()));
+ }
+ {
+ std::lock_guard _l(mFlushMutex);
+ mPendingFlush = false;
+ }
+ return result;
+ }
+
+ aaudio_result_t close() override {
+ std::lock_guard _l(mStreamMutex);
+ return AAudioSimplePlayer::close();
+ }
+
+private:
+ void writeOneSecondData() {
+ int index = 0;
+ {
+ std::lock_guard _l(mFlushMutex);
+ if (mPendingFlush) {
+ return;
+ }
+ index = mSineGeneratorIndex;
+ }
+ // Lock to prevent the stream is released
+ std::lock_guard _l(mStreamMutex);
+ AAudioStream* stream = getStream();
+ if (stream == nullptr) {
+ return;
+ }
+ int bytesPerFrame = mChannelCount;
+ std::shared_ptr<uint8_t[]> data;
+ switch (AAudioStream_getFormat(stream)) {
+ case AAUDIO_FORMAT_PCM_I16: {
+ bytesPerFrame *= 2;
+ } break;
+ case AAUDIO_FORMAT_PCM_FLOAT: {
+ bytesPerFrame *= 4;
+ } break;
+ default:
+ printf("Unsupported format %d\n", AAudioStream_getFormat(stream));
+ return;
+ }
+ data = std::make_shared<uint8_t[]>(bytesPerFrame * mSampleRate);
+ fillData(stream, static_cast<void*>(data.get()), mSampleRate, index);
+ int bytesWritten = 0;
+ int framesLeft = mSampleRate;
+ while (framesLeft > 0) {
+ auto framesWritten = AAudioStream_write(
+ stream, static_cast<void *>(&data[bytesWritten]),
+ framesLeft, 0 /*timeoutNanoseconds*/);
+ if (framesWritten < 0) {
+ printf("Failed to write data %d\n", framesWritten);
+ return;
+ }
+ printf("Write data succeed, frames=%d\n", framesWritten);
+ framesLeft -= framesWritten;
+ bytesWritten += framesWritten * bytesPerFrame;
+ }
+ }
+
+ bool fillData(AAudioStream* stream, void* data, int numFrames, int sineGeneratorIndex) {
+ switch (AAudioStream_getFormat(stream)) {
+ case AAUDIO_FORMAT_PCM_I16: {
+ int16_t *audioBuffer = static_cast<int16_t *>(data);
+ for (int i = 0; i < mChannelCount; ++i) {
+ mSines[sineGeneratorIndex][i].render(&audioBuffer[i], mChannelCount, numFrames);
+ }
+ } break;
+ case AAUDIO_FORMAT_PCM_FLOAT: {
+ float *audioBuffer = static_cast<float *>(data);
+ for (int i = 0; i < mChannelCount; ++i) {
+ mSines[sineGeneratorIndex][i].render(&audioBuffer[i], mChannelCount, numFrames);
+ }
+ } break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ const AAudioArgsParser mArgParser;
+ const bool mUseDataCallback;
+
+ int mSampleRate;
+ int mChannelCount;
+ std::vector<std::vector<SineGenerator>> mSines;
+ int mSineGeneratorIndex = 0;
+
+ std::mutex mStreamMutex;
+
+ std::mutex mFlushMutex;
+ bool mPendingFlush = false;
+};
+
+int32_t MyPartialDataCallback(
+ AAudioStream* stream, void* userData, void* audioData, int32_t numFrames) {
+ MyPlayer* player = static_cast<MyPlayer*>(userData);
+ return player->renderAudio(stream, audioData, numFrames);
+}
+
+void MyErrorCallback(AAudioStream* /*stream*/, void* /*userData*/, aaudio_result_t error) {
+ printf("Error callback, error=%d\n", error);
+}
+
+static void usage() {
+ printf("This test will playback in offload mode and try to flush from frame "
+ "every 2 seconds\n");
+ AAudioArgsParser::usage();
+ printf(" -T{seconds} time to run the test\n");
+ printf(" -B use blocking write instead of data callback\n");
+}
+
+int main(int argc, char **argv) {
+ AAudioArgsParser argParser;
+ int timeToRun = DEFAULT_TIME_TO_RUN_IN_SECOND;
+ bool useDataCallback = true;
+ for (int i = 1; i < argc; ++i) {
+ const char *arg = argv[i];
+ if (argParser.parseArg(arg)) {
+ if (arg[0] == '-') {
+ char option = arg[1];
+ switch (option) {
+ case 'T':
+ timeToRun = atoi(&arg[2]);
+ break;
+ case 'B':
+ useDataCallback = false;
+ break;
+ default:
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+ // Force to use offload mode
+ argParser.setPerformanceMode(AAUDIO_PERFORMANCE_MODE_POWER_SAVING_OFFLOADED);
+ argParser.setNumberOfBursts(0);
+
+ MyPlayer player(argParser, useDataCallback);
+ if (auto result = player.open(); result != AAUDIO_OK) {
+ printf("Failed to open stream, error=%d\n", result);
+ exit(EXIT_FAILURE);
+ }
+
+ if (auto result = player.start(); result != AAUDIO_OK) {
+ printf("Failed to start stream, error=%d", result);
+ exit(EXIT_FAILURE);
+ }
+
+ auto timeStart = std::chrono::system_clock::now();
+ int64_t position = 0;
+
+ while (std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now() - timeStart).count() < timeToRun) {
+ if (useDataCallback) {
+ std::this_thread::sleep_for(std::chrono::seconds(PLAYBACK_LENGTH_SECONDS));
+ player.flushFromFrame(AAUDIO_FLUSH_FROM_ACCURACY_UNDEFINED, &position);
+ } else {
+ auto timeToWakeUp = std::chrono::system_clock::now() +
+ std::chrono::seconds(PLAYBACK_LENGTH_SECONDS);
+ player.writeData();
+ std::this_thread::sleep_until(timeToWakeUp);
+ player.flushFromFrame(AAUDIO_FLUSH_FROM_ACCURACY_UNDEFINED, &position);
+ }
+ }
+
+ player.close();
+ return EXIT_SUCCESS;
+}
diff --git a/services/oboeservice/AAudioService.cpp b/services/oboeservice/AAudioService.cpp
index 76a507d..2e59ac5 100644
--- a/services/oboeservice/AAudioService.cpp
+++ b/services/oboeservice/AAudioService.cpp
@@ -314,6 +314,17 @@
AIDL_RETURN(result);
}
+Status AAudioService::updateTimestamp(int32_t streamHandle, int32_t *_aidl_return) {
+ static_assert(std::is_same_v<aaudio_result_t, std::decay_t<typeof(*_aidl_return)>>);
+
+ const sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
+ if (serviceStream.get() == nullptr) {
+ ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
+ AIDL_RETURN(AAUDIO_ERROR_INVALID_HANDLE);
+ }
+ AIDL_RETURN(serviceStream->updateTimestamp());
+}
+
bool AAudioService::isCallerInService() {
const pid_t clientPid = VALUE_OR_FATAL(
aidl2legacy_int32_t_pid_t(mAudioClient.attributionSource.pid));
diff --git a/services/oboeservice/AAudioService.h b/services/oboeservice/AAudioService.h
index ada3d53..e18060f 100644
--- a/services/oboeservice/AAudioService.h
+++ b/services/oboeservice/AAudioService.h
@@ -86,6 +86,8 @@
binder::Status exitStandby(int32_t streamHandle, ::aaudio::Endpoint* endpoint,
int32_t* _aidl_return) override;
+ binder::Status updateTimestamp(int32_t streamHandle, int32_t* _aidl_return) override;
+
aaudio_result_t startClient(aaudio::aaudio_handle_t streamHandle,
const android::AudioClient& client,
const audio_attributes_t *attr,
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index 8ec7de1..ffaaa04 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -403,6 +403,10 @@
return AAUDIO_OK;
}
+aaudio_result_t AAudioServiceStreamBase::updateTimestamp() {
+ return sendCommand(UPDATE_TIMESTAMP, nullptr /*param*/, true /*waitForReply*/, TIMEOUT_NANOS);
+}
+
// implement Runnable, periodically send timestamps to client and process commands from queue.
// Enter standby mode if idle for a while.
__attribute__((no_sanitize("integer")))
@@ -547,6 +551,9 @@
command->result = param == nullptr ? AAUDIO_ERROR_ILLEGAL_ARGUMENT
: stopClient_l(param->mClientHandle);
} break;
+ case UPDATE_TIMESTAMP: {
+ command->result = sendCurrentTimestamp_l();
+ } break;
default:
ALOGE("Invalid command op code: %d", command->operationCode);
break;
diff --git a/services/oboeservice/AAudioServiceStreamBase.h b/services/oboeservice/AAudioServiceStreamBase.h
index 20737bc..4580522 100644
--- a/services/oboeservice/AAudioServiceStreamBase.h
+++ b/services/oboeservice/AAudioServiceStreamBase.h
@@ -122,6 +122,8 @@
*/
aaudio_result_t exitStandby(AudioEndpointParcelable *parcelable) EXCLUDES(mLock);
+ aaudio_result_t updateTimestamp() EXCLUDES(mLock);
+
virtual aaudio_result_t startClient(const android::AudioClient& client,
const audio_attributes_t *attr __unused,
audio_port_handle_t *clientHandle __unused) {
@@ -405,6 +407,7 @@
EXIT_STANDBY,
START_CLIENT,
STOP_CLIENT,
+ UPDATE_TIMESTAMP,
};
AAudioThread mCommandThread;
std::atomic_bool mThreadEnabled{false};
diff --git a/services/oboeservice/fuzzer/oboeservice_fuzzer.cpp b/services/oboeservice/fuzzer/oboeservice_fuzzer.cpp
index e80f51d..142361d 100644
--- a/services/oboeservice/fuzzer/oboeservice_fuzzer.cpp
+++ b/services/oboeservice/fuzzer/oboeservice_fuzzer.cpp
@@ -189,6 +189,8 @@
return AAUDIO_ERROR_UNAVAILABLE;
}
+ aaudio_result_t updateTimestamp(const AAudioHandleInfo& streamHandleInfo) override;
+
void onStreamChange(aaudio_handle_t handle, int32_t opcode, int32_t value) {}
int getDeathCount() { return mDeathCount; }
@@ -338,6 +340,14 @@
return service->unregisterAudioThread(streamHandleInfo, clientThreadId);
}
+aaudio_result_t FuzzAAudioClient::updateTimestamp(const AAudioHandleInfo& streamHandleInfo) {
+ AAudioServiceInterface *service = getAAudioService();
+ if (!service) {
+ return AAUDIO_ERROR_NO_SERVICE;
+ }
+ return service->updateTimestamp(streamHandleInfo);
+}
+
class OboeserviceFuzzer {
public:
OboeserviceFuzzer();
@@ -411,7 +421,7 @@
}
while (fdp.remaining_bytes()) {
AudioEndpointParcelable audioEndpointParcelable;
- int action = fdp.ConsumeIntegralInRange<int32_t>(0, 4);
+ int action = fdp.ConsumeIntegralInRange<int32_t>(0, 5);
switch (action) {
case 0:
mClient->getStreamDescription(streamHandleInfo, audioEndpointParcelable);
@@ -428,6 +438,9 @@
case 4:
mClient->flushStream(streamHandleInfo);
break;
+ case 5:
+ mClient->updateTimestamp(streamHandleInfo);
+ break;
}
}
mClient->closeStream(streamHandleInfo);