PatchPanel: store "downstream" patches for a device
When the system inserts an intermediate processing module between
audio flinger and hardware HAL, and they are connected via a
software patch, the threads of this patch need to receive
metadata passed to the thread serving the "upstream" device of
the processing module.
PatchPanel stores an association between "upstream" streams
and the software patch. This allows audio flinger to access
the patch threads.
For more info, see the comments in PatchPanel.h
Bug: 63901775
Test: manual
Change-Id: I60e578c37a1bc7385daf32d379ea143120584a31
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 53a4ce9..d186887 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1817,6 +1817,10 @@
mHardwareStatus = AUDIO_HW_IDLE;
}
+ if (strcmp(name, AUDIO_HAL_SERVICE_NAME_MSD) == 0) {
+ // An MSD module is inserted before hardware modules in order to mix encoded streams.
+ flags = static_cast<AudioHwDevice::Flags>(flags | AudioHwDevice::AHWD_IS_INSERT);
+ }
audio_module_handle_t handle = (audio_module_handle_t) nextUniqueId(AUDIO_UNIQUE_ID_USE_MODULE);
mAudioHwDevs.add(handle, new AudioHwDevice(handle, name, dev, flags));
@@ -2096,6 +2100,7 @@
*output, thread.get());
}
mPlaybackThreads.add(*output, thread);
+ mPatchPanel.notifyStreamOpened(outHwDev, *output);
return thread;
}
}
@@ -2231,6 +2236,7 @@
const sp<AudioIoDescriptor> ioDesc = new AudioIoDescriptor();
ioDesc->mIoHandle = output;
ioConfigChanged(AUDIO_OUTPUT_CLOSED, ioDesc);
+ mPatchPanel.notifyStreamClosed(output);
}
// The thread entity (active unit of execution) is no longer running here,
// but the ThreadBase container still exists.
diff --git a/services/audioflinger/AudioHwDevice.h b/services/audioflinger/AudioHwDevice.h
index eb826c6..d4299b0 100644
--- a/services/audioflinger/AudioHwDevice.h
+++ b/services/audioflinger/AudioHwDevice.h
@@ -35,6 +35,9 @@
enum Flags {
AHWD_CAN_SET_MASTER_VOLUME = 0x1,
AHWD_CAN_SET_MASTER_MUTE = 0x2,
+ // Means that this isn't a terminal module, and software patches
+ // are used to transport audio data further.
+ AHWD_IS_INSERT = 0x4,
};
AudioHwDevice(audio_module_handle_t handle,
@@ -55,6 +58,10 @@
return (0 != (mFlags & AHWD_CAN_SET_MASTER_MUTE));
}
+ bool isInsert() const {
+ return (0 != (mFlags & AHWD_IS_INSERT));
+ }
+
audio_module_handle_t handle() const { return mHandle; }
const char *moduleName() const { return mModuleName; }
sp<DeviceHalInterface> hwDevice() const { return mHwDevice; }
diff --git a/services/audioflinger/PatchPanel.cpp b/services/audioflinger/PatchPanel.cpp
index 0caa0af..7f46747 100644
--- a/services/audioflinger/PatchPanel.cpp
+++ b/services/audioflinger/PatchPanel.cpp
@@ -83,6 +83,16 @@
return mPatchPanel.listAudioPatches(num_patches, patches);
}
+status_t AudioFlinger::PatchPanel::SoftwarePatch::getLatencyMs_l(double *latencyMs) const
+{
+ const auto& iter = mPatchPanel.mPatches.find(mPatchHandle);
+ if (iter != mPatchPanel.mPatches.end()) {
+ return iter->second.getLatencyMs(latencyMs);
+ } else {
+ return BAD_VALUE;
+ }
+}
+
/* List connected audio ports and their attributes */
status_t AudioFlinger::PatchPanel::listAudioPorts(unsigned int *num_ports __unused,
struct audio_port *ports __unused)
@@ -159,21 +169,21 @@
}
}
mPatches.erase(iter);
+ removeSoftwarePatchFromInsertedModules(*handle);
}
}
Patch newPatch{*patch};
+ audio_module_handle_t insertedModule = AUDIO_MODULE_HANDLE_NONE;
switch (patch->sources[0].type) {
case AUDIO_PORT_TYPE_DEVICE: {
audio_module_handle_t srcModule = patch->sources[0].ext.device.hw_module;
- ssize_t index = mAudioFlinger.mAudioHwDevs.indexOfKey(srcModule);
- if (index < 0) {
- ALOGW("%s() bad src hw module %d", __func__, srcModule);
+ AudioHwDevice *audioHwDevice = findAudioHwDeviceByModule(srcModule);
+ if (!audioHwDevice) {
status = BAD_VALUE;
goto exit;
}
- AudioHwDevice *audioHwDevice = mAudioFlinger.mAudioHwDevs.valueAt(index);
for (unsigned int i = 0; i < patch->num_sinks; i++) {
// support only one sink if connection to a mix or across HW modules
if ((patch->sinks[i].type == AUDIO_PORT_TYPE_MIX ||
@@ -285,6 +295,9 @@
if (status != NO_ERROR) {
goto exit;
}
+ if (audioHwDevice->isInsert()) {
+ insertedModule = audioHwDevice->handle();
+ }
} else {
if (patch->sinks[0].type == AUDIO_PORT_TYPE_MIX) {
sp<ThreadBase> thread = mAudioFlinger.checkRecordThread_l(
@@ -364,6 +377,9 @@
*handle = (audio_patch_handle_t) mAudioFlinger.nextUniqueId(AUDIO_UNIQUE_ID_USE_PATCH);
newPatch.mHalHandle = halHandle;
mPatches.insert(std::make_pair(*handle, std::move(newPatch)));
+ if (insertedModule != AUDIO_MODULE_HANDLE_NONE) {
+ addSoftwarePatchToInsertedModules(insertedModule, *handle);
+ }
ALOGV("%s() added new patch handle %d halHandle %d", __func__, *handle, halHandle);
} else {
newPatch.clearConnections(this);
@@ -511,21 +527,17 @@
return OK;
}
-String8 AudioFlinger::PatchPanel::Patch::dump(audio_patch_handle_t myHandle)
+String8 AudioFlinger::PatchPanel::Patch::dump(audio_patch_handle_t myHandle) const
{
- String8 result;
-
// TODO: Consider table dump form for patches, just like tracks.
- result.appendFormat("Patch %d: thread %p => thread %p",
- myHandle, mRecord.thread().get(), mPlayback.thread().get());
+ String8 result = String8::format("Patch %d: thread %p => thread %p",
+ myHandle, mRecord.const_thread().get(), mPlayback.const_thread().get());
// add latency if it exists
double latencyMs;
if (getLatencyMs(&latencyMs) == OK) {
result.appendFormat(" latency: %.2lf", latencyMs);
}
-
- result.append("\n");
return result;
}
@@ -596,6 +608,7 @@
}
mPatches.erase(iter);
+ removeSoftwarePatchFromInsertedModules(handle);
return status;
}
@@ -607,35 +620,114 @@
return NO_ERROR;
}
-sp<DeviceHalInterface> AudioFlinger::PatchPanel::findHwDeviceByModule(audio_module_handle_t module)
+status_t AudioFlinger::PatchPanel::getDownstreamSoftwarePatches(
+ audio_io_handle_t stream,
+ std::vector<AudioFlinger::PatchPanel::SoftwarePatch> *patches) const
+{
+ for (const auto& module : mInsertedModules) {
+ if (module.second.streams.count(stream)) {
+ for (const auto& patchHandle : module.second.sw_patches) {
+ const auto& patch_iter = mPatches.find(patchHandle);
+ if (patch_iter != mPatches.end()) {
+ const Patch &patch = patch_iter->second;
+ patches->emplace_back(*this, patchHandle,
+ patch.mPlayback.const_thread()->id(),
+ patch.mRecord.const_thread()->id());
+ } else {
+ ALOGE("Stale patch handle in the cache: %d", patchHandle);
+ }
+ }
+ return OK;
+ }
+ }
+ // The stream is not associated with any of inserted modules.
+ return BAD_VALUE;
+}
+
+void AudioFlinger::PatchPanel::notifyStreamOpened(
+ AudioHwDevice *audioHwDevice, audio_io_handle_t stream)
+{
+ if (audioHwDevice->isInsert()) {
+ mInsertedModules[audioHwDevice->handle()].streams.insert(stream);
+ }
+}
+
+void AudioFlinger::PatchPanel::notifyStreamClosed(audio_io_handle_t stream)
+{
+ for (auto& module : mInsertedModules) {
+ module.second.streams.erase(stream);
+ }
+}
+
+AudioHwDevice* AudioFlinger::PatchPanel::findAudioHwDeviceByModule(audio_module_handle_t module)
{
if (module == AUDIO_MODULE_HANDLE_NONE) return nullptr;
ssize_t index = mAudioFlinger.mAudioHwDevs.indexOfKey(module);
if (index < 0) {
+ ALOGW("%s() bad hw module %d", __func__, module);
return nullptr;
}
- return mAudioFlinger.mAudioHwDevs.valueAt(index)->hwDevice();
+ return mAudioFlinger.mAudioHwDevs.valueAt(index);
}
-void AudioFlinger::PatchPanel::dump(int fd)
+sp<DeviceHalInterface> AudioFlinger::PatchPanel::findHwDeviceByModule(audio_module_handle_t module)
{
+ AudioHwDevice *audioHwDevice = findAudioHwDeviceByModule(module);
+ return audioHwDevice ? audioHwDevice->hwDevice() : nullptr;
+}
+
+void AudioFlinger::PatchPanel::addSoftwarePatchToInsertedModules(
+ audio_module_handle_t module, audio_patch_handle_t handle)
+{
+ mInsertedModules[module].sw_patches.insert(handle);
+}
+
+void AudioFlinger::PatchPanel::removeSoftwarePatchFromInsertedModules(
+ audio_patch_handle_t handle)
+{
+ for (auto& module : mInsertedModules) {
+ module.second.sw_patches.erase(handle);
+ }
+}
+
+void AudioFlinger::PatchPanel::dump(int fd) const
+{
+ String8 patchPanelDump;
+ const char *indent = " ";
+
// Only dump software patches.
bool headerPrinted = false;
- for (auto& iter : mPatches) {
+ for (const auto& iter : mPatches) {
if (iter.second.isSoftware()) {
if (!headerPrinted) {
- String8 header("\nSoftware patches:\n");
- write(fd, header.string(), header.size());
+ patchPanelDump += "\nSoftware patches:\n";
headerPrinted = true;
}
- String8 patchDump(" ");
- patchDump.append(iter.second.dump(iter.first));
- write(fd, patchDump.string(), patchDump.size());
+ patchPanelDump.appendFormat("%s%s\n", indent, iter.second.dump(iter.first).string());
}
}
- if (headerPrinted) {
- String8 trailing("\n");
- write(fd, trailing.string(), trailing.size());
+
+ headerPrinted = false;
+ for (const auto& module : mInsertedModules) {
+ if (!module.second.streams.empty() || !module.second.sw_patches.empty()) {
+ if (!headerPrinted) {
+ patchPanelDump += "\nTracked inserted modules:\n";
+ headerPrinted = true;
+ }
+ String8 moduleDump = String8::format("Module %d: I/O handles: ", module.first);
+ for (const auto& stream : module.second.streams) {
+ moduleDump.appendFormat("%d ", stream);
+ }
+ moduleDump.append("; SW Patches: ");
+ for (const auto& patch : module.second.sw_patches) {
+ moduleDump.appendFormat("%d ", patch);
+ }
+ patchPanelDump.appendFormat("%s%s\n", indent, moduleDump.string());
+ }
+ }
+
+ if (!patchPanelDump.isEmpty()) {
+ write(fd, patchPanelDump.string(), patchPanelDump.size());
}
}
diff --git a/services/audioflinger/PatchPanel.h b/services/audioflinger/PatchPanel.h
index 5d6bf00..269a398 100644
--- a/services/audioflinger/PatchPanel.h
+++ b/services/audioflinger/PatchPanel.h
@@ -19,9 +19,31 @@
#error This header file should only be included from AudioFlinger.h
#endif
+
// PatchPanel is concealed within AudioFlinger, their lifetimes are the same.
class PatchPanel {
public:
+ class SoftwarePatch {
+ public:
+ SoftwarePatch(const PatchPanel &patchPanel, audio_patch_handle_t patchHandle,
+ audio_io_handle_t playbackThreadHandle, audio_io_handle_t recordThreadHandle)
+ : mPatchPanel(patchPanel), mPatchHandle(patchHandle),
+ mPlaybackThreadHandle(playbackThreadHandle),
+ mRecordThreadHandle(recordThreadHandle) {}
+ SoftwarePatch(const SoftwarePatch&) = default;
+ SoftwarePatch& operator=(const SoftwarePatch&) = default;
+
+ // Must be called under AudioFlinger::mLock
+ status_t getLatencyMs_l(double *latencyMs) const;
+ audio_io_handle_t getPlaybackThreadHandle() const { return mPlaybackThreadHandle; };
+ audio_io_handle_t getRecordThreadHandle() const { return mRecordThreadHandle; };
+ private:
+ const PatchPanel &mPatchPanel;
+ const audio_patch_handle_t mPatchHandle;
+ const audio_io_handle_t mPlaybackThreadHandle;
+ const audio_io_handle_t mRecordThreadHandle;
+ };
+
explicit PatchPanel(AudioFlinger* audioFlinger) : mAudioFlinger(*audioFlinger) {}
/* List connected audio ports and their attributes */
@@ -42,7 +64,16 @@
status_t listAudioPatches(unsigned int *num_patches,
struct audio_patch *patches);
- void dump(int fd);
+ // Retrieves all currently estrablished software patches for a stream
+ // opened on an intermediate module.
+ status_t getDownstreamSoftwarePatches(audio_io_handle_t stream,
+ std::vector<SoftwarePatch> *patches) const;
+
+ // Notifies patch panel about all opened and closed streams.
+ void notifyStreamOpened(AudioHwDevice *audioHwDevice, audio_io_handle_t stream);
+ void notifyStreamClosed(audio_io_handle_t stream);
+
+ void dump(int fd) const;
private:
template<typename ThreadType, typename TrackType>
@@ -65,6 +96,7 @@
audio_patch_handle_t handle() const { return mHandle; }
sp<ThreadType> thread() { return mThread; }
sp<TrackType> track() { return mTrack; }
+ sp<const ThreadType> const_thread() const { return mThread; }
sp<const TrackType> const_track() const { return mTrack; }
void closeConnections(PatchPanel *panel) {
@@ -122,7 +154,7 @@
// returns the latency of the patch (from record to playback).
status_t getLatencyMs(double *latencyMs) const;
- String8 dump(audio_patch_handle_t myHandle);
+ String8 dump(audio_patch_handle_t myHandle) const;
// Note that audio_patch::id is only unique within a HAL module
struct audio_patch mAudioPatch;
@@ -138,8 +170,38 @@
Endpoint<RecordThread, RecordThread::PatchRecord> mRecord;
};
+ AudioHwDevice* findAudioHwDeviceByModule(audio_module_handle_t module);
sp<DeviceHalInterface> findHwDeviceByModule(audio_module_handle_t module);
+ void addSoftwarePatchToInsertedModules(
+ audio_module_handle_t module, audio_patch_handle_t handle);
+ void removeSoftwarePatchFromInsertedModules(audio_patch_handle_t handle);
AudioFlinger &mAudioFlinger;
std::map<audio_patch_handle_t, Patch> mPatches;
+
+ // This map allows going from a thread to "downstream" software patches
+ // when a processing module inserted in between. Example:
+ //
+ // from map value.streams map key
+ // [Mixer thread] --> [Virtual output device] --> [Processing module] ---\
+ // [Harware module] <-- [Physical output device] <-- [S/W Patch] <--/
+ // from map value.sw_patches
+ //
+ // This allows the mixer thread to look up the threads of the software patch
+ // for propagating timing info, parameters, etc.
+ //
+ // The current assumptions are:
+ // 1) The processing module acts as a mixer with several outputs which
+ // represent differently downmixed and / or encoded versions of the same
+ // mixed stream. There is no 1:1 correspondence between the input streams
+ // and the software patches, but rather a N:N correspondence between
+ // a group of streams and a group of patches.
+ // 2) There are only a couple of inserted processing modules in the system,
+ // so when looking for a stream or patch handle we can iterate over
+ // all modules.
+ struct ModuleConnections {
+ std::set<audio_io_handle_t> streams;
+ std::set<audio_patch_handle_t> sw_patches;
+ };
+ std::map<audio_module_handle_t, ModuleConnections> mInsertedModules;
};