Audio V6 wrapper: IDevice|IStream|IEffect.close releases HAL resource

Fixed behavior of IStream|IEffect.close to release the underlying
HAL resource synchronously. This is to avoid adding artificial
delays in VTS that become totally unpractical in V6.

Added clarification about expected client behavior for
IStream|IEffect.close w.r.t. audio data transfer.

Added IDevice.close method which releases HAL device resource.

Updated VTS tests to remove delays in V6.

Bug: 114451103
Bug: 141989952
Test: atest VtsHalAudioV6_0TargetTest
Change-Id: I439f0f923c091af2ab234d15ca847cfade341f25
Merged-In: I439f0f923c091af2ab234d15ca847cfade341f25
This commit is contained in:
Mikhail Naganov 2019-11-14 13:57:15 -08:00
parent 942de9e663
commit d041930df9
16 changed files with 120 additions and 45 deletions

View file

@ -280,4 +280,15 @@ interface IDevice {
*/
setConnectedState(DeviceAddress address, bool connected)
generates (Result retval);
/**
* Called by the framework to deinitialize the device and free up
* all currently allocated resources. It is recommended to close
* the device on the client side as soon as it is becomes unused.
*
* @return retval OK in case the success.
* INVALID_STATE if the device was already closed.
*/
@exit
close() generates (Result retval);
};

View file

@ -300,13 +300,17 @@ interface IStream {
/**
* Called by the framework to deinitialize the stream and free up
* all the currently allocated resources. It is recommended to close
* all currently allocated resources. It is recommended to close
* the stream on the client side as soon as it is becomes unused.
*
* The client must ensure that this function is not called while
* audio data is being transferred through the stream's message queues.
*
* @return retval OK in case the success.
* NOT_SUPPORTED if called on IStream instead of input or
* output stream interface.
* INVALID_STATE if the stream was already closed.
*/
@exit
close() generates (Result retval);
};

View file

@ -39,11 +39,10 @@ namespace implementation {
using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
Device::Device(audio_hw_device_t* device) : mDevice(device) {}
Device::Device(audio_hw_device_t* device) : mIsClosed(false), mDevice(device) {}
Device::~Device() {
int status = audio_hw_device_close(mDevice);
ALOGW_IF(status, "Error closing audio hw device %p: %s", mDevice, strerror(-status));
(void)doClose();
mDevice = nullptr;
}
@ -383,6 +382,18 @@ Return<Result> Device::setConnectedState(const DeviceAddress& address, bool conn
}
#endif
Result Device::doClose() {
if (mIsClosed) return Result::INVALID_STATE;
mIsClosed = true;
return analyzeStatus("close", audio_hw_device_close(mDevice));
}
#if MAJOR_VERSION >= 6
Return<Result> Device::close() {
return doClose();
}
#endif
} // namespace implementation
} // namespace CPP_VERSION
} // namespace audio

View file

@ -31,7 +31,11 @@ namespace implementation {
PrimaryDevice::PrimaryDevice(audio_hw_device_t* device) : mDevice(new Device(device)) {}
PrimaryDevice::~PrimaryDevice() {}
PrimaryDevice::~PrimaryDevice() {
// Do not call mDevice->close here. If there are any unclosed streams,
// they only hold IDevice instance, not IPrimaryDevice, thus IPrimaryDevice
// "part" of a device can be destroyed before the streams.
}
// Methods from ::android::hardware::audio::CPP_VERSION::IDevice follow.
Return<Result> PrimaryDevice::initCheck() {
@ -160,6 +164,11 @@ Return<Result> PrimaryDevice::setConnectedState(const DeviceAddress& address, bo
return mDevice->setConnectedState(address, connected);
}
#endif
#if MAJOR_VERSION >= 6
Return<Result> PrimaryDevice::close() {
return mDevice->close();
}
#endif
// Methods from ::android::hardware::audio::CPP_VERSION::IPrimaryDevice follow.
Return<Result> PrimaryDevice::setVoiceVolume(float volume) {

View file

@ -139,8 +139,7 @@ bool ReadThread::threadLoop() {
} // namespace
StreamIn::StreamIn(const sp<Device>& device, audio_stream_in_t* stream)
: mIsClosed(false),
mDevice(device),
: mDevice(device),
mStream(stream),
mStreamCommon(new Stream(&stream->common)),
mStreamMmap(new StreamMmap<audio_stream_in_t>(stream)),
@ -159,7 +158,9 @@ StreamIn::~StreamIn() {
status_t status = EventFlag::deleteEventFlag(&mEfGroup);
ALOGE_IF(status, "read MQ event flag deletion error: %s", strerror(-status));
}
#if MAJOR_VERSION <= 5
mDevice->closeInputStream(mStream);
#endif
mStream = nullptr;
}
@ -303,14 +304,16 @@ Return<void> StreamIn::getMmapPosition(getMmapPosition_cb _hidl_cb) {
}
Return<Result> StreamIn::close() {
if (mIsClosed) return Result::INVALID_STATE;
mIsClosed = true;
if (mReadThread.get()) {
mStopReadThread.store(true, std::memory_order_release);
if (mStopReadThread.load(std::memory_order_relaxed)) { // only this thread writes
return Result::INVALID_STATE;
}
mStopReadThread.store(true, std::memory_order_release);
if (mEfGroup) {
mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));
}
#if MAJOR_VERSION >= 6
mDevice->closeInputStream(mStream);
#endif
return Result::OK;
}

View file

@ -138,8 +138,7 @@ bool WriteThread::threadLoop() {
} // namespace
StreamOut::StreamOut(const sp<Device>& device, audio_stream_out_t* stream)
: mIsClosed(false),
mDevice(device),
: mDevice(device),
mStream(stream),
mStreamCommon(new Stream(&stream->common)),
mStreamMmap(new StreamMmap<audio_stream_out_t>(stream)),
@ -148,7 +147,7 @@ StreamOut::StreamOut(const sp<Device>& device, audio_stream_out_t* stream)
StreamOut::~StreamOut() {
ATRACE_CALL();
close();
(void)close();
if (mWriteThread.get()) {
ATRACE_NAME("mWriteThread->join");
status_t status = mWriteThread->join();
@ -159,10 +158,12 @@ StreamOut::~StreamOut() {
ALOGE_IF(status, "write MQ event flag deletion error: %s", strerror(-status));
}
mCallback.clear();
#if MAJOR_VERSION <= 5
mDevice->closeOutputStream(mStream);
// Closing the output stream in the HAL waits for the callback to finish,
// and joins the callback thread. Thus is it guaranteed that the callback
// thread will not be accessing our object anymore.
#endif
mStream = nullptr;
}
@ -291,14 +292,16 @@ Return<Result> StreamOut::setParameters(const hidl_vec<ParameterValue>& context,
#endif
Return<Result> StreamOut::close() {
if (mIsClosed) return Result::INVALID_STATE;
mIsClosed = true;
if (mWriteThread.get()) {
mStopWriteThread.store(true, std::memory_order_release);
if (mStopWriteThread.load(std::memory_order_relaxed)) { // only this thread writes
return Result::INVALID_STATE;
}
mStopWriteThread.store(true, std::memory_order_release);
if (mEfGroup) {
mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY));
}
#if MAJOR_VERSION >= 6
mDevice->closeOutputStream(mStream);
#endif
return Result::OK;
}

View file

@ -114,6 +114,9 @@ struct Device : public IDevice, public ParametersUtil {
Return<void> getMicrophones(getMicrophones_cb _hidl_cb) override;
Return<Result> setConnectedState(const DeviceAddress& address, bool connected) override;
#endif
#if MAJOR_VERSION >= 6
Return<Result> close() override;
#endif
Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;
@ -124,11 +127,14 @@ struct Device : public IDevice, public ParametersUtil {
void closeOutputStream(audio_stream_out_t* stream);
audio_hw_device_t* device() const { return mDevice; }
private:
private:
bool mIsClosed;
audio_hw_device_t* mDevice;
virtual ~Device();
Result doClose();
// Methods from ParametersUtil.
char* halGetParameters(const char* keys) override;
int halSetParameters(const char* keysAndValues) override;

View file

@ -96,6 +96,9 @@ struct PrimaryDevice : public IPrimaryDevice {
Return<void> getMicrophones(getMicrophones_cb _hidl_cb) override;
Return<Result> setConnectedState(const DeviceAddress& address, bool connected) override;
#endif
#if MAJOR_VERSION >= 6
Return<Result> close() override;
#endif
Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;

View file

@ -120,7 +120,6 @@ struct StreamIn : public IStreamIn {
uint64_t* time);
private:
bool mIsClosed;
const sp<Device> mDevice;
audio_stream_in_t* mStream;
const sp<Stream> mStreamCommon;

View file

@ -126,7 +126,6 @@ struct StreamOut : public IStreamOut {
TimeSpec* timeStamp);
private:
bool mIsClosed;
const sp<Device> mDevice;
audio_stream_out_t* mStream;
const sp<Stream> mStreamCommon;

View file

@ -22,18 +22,16 @@ TEST_P(AudioHidlTest, OpenPrimaryDeviceUsingGetDevice) {
GTEST_SKIP() << "No primary device on this factory"; // returns
}
struct WaitExecutor {
~WaitExecutor() { DeviceManager::waitForInstanceDestruction(); }
} waitExecutor; // Make sure we wait for the device destruction on exiting from the test.
Result result;
sp<IDevice> baseDevice;
ASSERT_OK(getDevicesFactory()->openDevice("primary", returnIn(result, baseDevice)));
ASSERT_OK(result);
ASSERT_TRUE(baseDevice != nullptr);
Return<sp<IPrimaryDevice>> primaryDevice = IPrimaryDevice::castFrom(baseDevice);
ASSERT_TRUE(primaryDevice.isOk());
ASSERT_TRUE(sp<IPrimaryDevice>(primaryDevice) != nullptr);
{ // Scope for device SPs
sp<IDevice> baseDevice =
DeviceManager::getInstance().get(getFactoryName(), DeviceManager::kPrimaryDevice);
ASSERT_TRUE(baseDevice != nullptr);
Return<sp<IPrimaryDevice>> primaryDevice = IPrimaryDevice::castFrom(baseDevice);
EXPECT_TRUE(primaryDevice.isOk());
EXPECT_TRUE(sp<IPrimaryDevice>(primaryDevice) != nullptr);
}
EXPECT_TRUE(
DeviceManager::getInstance().reset(getFactoryName(), DeviceManager::kPrimaryDevice));
}
//////////////////////////////////////////////////////////////////////////////
@ -113,10 +111,12 @@ TEST_P(AudioHidlDeviceTest, GetMicrophonesTest) {
ASSERT_NE(0U, activeMicrophones.size());
}
stream->close();
#if MAJOR_VERSION <= 5
// Workaround for b/139329877. Ensures the stream gets closed on the audio hal side.
stream.clear();
IPCThreadState::self()->flushCommands();
usleep(1000);
#endif
if (efGroup) {
EventFlag::deleteEventFlag(&efGroup);
}

View file

@ -859,6 +859,7 @@ class OpenStreamTest : public AudioHidlTestWithDeviceConfigParameter {
}
static void waitForStreamDestruction() {
#if MAJOR_VERSION <= 5
// FIXME: there is no way to know when the remote IStream is being destroyed
// Binder does not support testing if an object is alive, thus
// wait for 100ms to let the binder destruction propagates and
@ -867,6 +868,7 @@ class OpenStreamTest : public AudioHidlTestWithDeviceConfigParameter {
// the latency between local and remote destruction.
IPCThreadState::self()->flushCommands();
usleep(100 * 1000);
#endif
}
private:

View file

@ -22,25 +22,33 @@
template <class Derived, class Key, class Interface>
class InterfaceManager {
public:
sp<Interface> getExisting(const Key& name) {
auto existing = instances.find(name);
return existing != instances.end() ? existing->second : sp<Interface>();
}
sp<Interface> get(const Key& name) {
auto existing = instances.find(name);
if (existing != instances.end()) return existing->second;
auto [inserted, _] = instances.emplace(name, Derived::createInterfaceInstance(name));
if (inserted->second) {
environment->registerTearDown([name]() { (void)Derived::getInstance().reset(name); });
environment->registerTearDown(
[name]() { (void)Derived::getInstance().reset(name, false); });
}
return inserted->second;
}
// The test must check that reset was successful. Reset failure means that the test code
// is holding a strong reference to the device.
bool reset(const Key& name) __attribute__((warn_unused_result)) {
bool reset(const Key& name, bool waitForDestruction) __attribute__((warn_unused_result)) {
auto iter = instances.find(name);
if (iter == instances.end()) return true;
::android::wp<Interface> weak = iter->second;
instances.erase(iter);
if (weak.promote() != nullptr) return false;
waitForInstanceDestruction();
if (waitForDestruction) {
waitForInstanceDestruction();
}
return true;
}
@ -100,7 +108,15 @@ class DeviceManager : public InterfaceManager<DeviceManager, FactoryAndDevice, I
}
bool reset(const std::string& factoryName, const std::string& name)
__attribute__((warn_unused_result)) {
return InterfaceManager::reset(std::make_tuple(factoryName, name));
#if MAJOR_VERSION <= 5
return InterfaceManager::reset(std::make_tuple(factoryName, name), true);
#elif MAJOR_VERSION >= 6
{
sp<IDevice> device = getExisting(std::make_tuple(factoryName, name));
if (device != nullptr) device->close();
}
return InterfaceManager::reset(std::make_tuple(factoryName, name), false);
#endif
}
bool resetPrimary(const std::string& factoryName) __attribute__((warn_unused_result)) {
return reset(factoryName, kPrimaryDevice);

View file

@ -407,9 +407,12 @@ interface IEffect {
/**
* Called by the framework to deinitialize the effect and free up
* all the currently allocated resources. It is recommended to close
* all currently allocated resources. It is recommended to close
* the effect on the client side as soon as it is becomes unused.
*
* The client must ensure that this function is not called while
* audio data is being transferred through the effect's message queues.
*
* @return retval OK in case the success.
* INVALID_STATE if the effect was already closed.
*/

View file

@ -138,11 +138,11 @@ const char* Effect::sContextCallToCommand = "error";
const char* Effect::sContextCallFunction = sContextCallToCommand;
Effect::Effect(effect_handle_t handle)
: mIsClosed(false), mHandle(handle), mEfGroup(nullptr), mStopProcessThread(false) {}
: mHandle(handle), mEfGroup(nullptr), mStopProcessThread(false) {}
Effect::~Effect() {
ATRACE_CALL();
close();
(void)close();
if (mProcessThread.get()) {
ATRACE_NAME("mProcessThread->join");
status_t status = mProcessThread->join();
@ -154,8 +154,10 @@ Effect::~Effect() {
}
mInBuffer.clear();
mOutBuffer.clear();
#if MAJOR_VERSION <= 5
int status = EffectRelease(mHandle);
ALOGW_IF(status, "Error releasing effect %p: %s", mHandle, strerror(-status));
#endif
EffectMap::getInstance().remove(mHandle);
mHandle = 0;
}
@ -699,15 +701,20 @@ Return<Result> Effect::setCurrentConfigForFeature(uint32_t featureId,
}
Return<Result> Effect::close() {
if (mIsClosed) return Result::INVALID_STATE;
mIsClosed = true;
if (mProcessThread.get()) {
mStopProcessThread.store(true, std::memory_order_release);
if (mStopProcessThread.load(std::memory_order_relaxed)) { // only this thread modifies
return Result::INVALID_STATE;
}
mStopProcessThread.store(true, std::memory_order_release);
if (mEfGroup) {
mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_QUIT));
}
#if MAJOR_VERSION <= 5
return Result::OK;
#elif MAJOR_VERSION >= 6
// No need to join the processing thread, it is part of the API contract that the client
// must finish processing before closing the effect.
return analyzeStatus("EffectRelease", "", sContextCallFunction, EffectRelease(mHandle));
#endif
}
Return<void> Effect::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& /* options */) {

View file

@ -170,7 +170,6 @@ struct Effect : public IEffect {
static const char* sContextCallToCommand;
static const char* sContextCallFunction;
bool mIsClosed;
effect_handle_t mHandle;
sp<AudioBufferWrapper> mInBuffer;
sp<AudioBufferWrapper> mOutBuffer;