Skip to content
248 changes: 125 additions & 123 deletions ASFWDriver/Async/Rx/ARPacketParser.cpp

Large diffs are not rendered by default.

23 changes: 6 additions & 17 deletions ASFWDriver/Audio/DriverKit/ASFWAudioDriverGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,21 +158,10 @@ kern_return_t BuildAudioGraph(ASFWAudioDriver& driver,
}
parsedConfig.channelCount = std::max(parsedConfig.inputChannelCount, parsedConfig.outputChannelCount);

// Regenerate channel names for the updated channel counts
for (uint32_t index = 0; index < parsedConfig.inputChannelCount && index < ASFW::Isoch::Audio::kMaxNamedChannels; ++index) {
snprintf(parsedConfig.inputChannelNames[index],
sizeof(parsedConfig.inputChannelNames[index]),
"%s %u",
parsedConfig.inputPlugName,
index + 1);
}
for (uint32_t index = 0; index < parsedConfig.outputChannelCount && index < ASFW::Isoch::Audio::kMaxNamedChannels; ++index) {
snprintf(parsedConfig.outputChannelNames[index],
sizeof(parsedConfig.outputChannelNames[index]),
"%s %u",
parsedConfig.outputPlugName,
index + 1);
}
// Regenerate channel names for the updated channel counts. Prefers the
// device's per-channel labels (published by the core side) and falls
// back to synthesized "<plug> N" for any slot without a real label.
ASFW::Isoch::Audio::BuildChannelNamesFromPlugs(parsedConfig);
}

ASFW::Isoch::Audio::BuildFallbackBoolControls(parsedConfig);
Expand Down Expand Up @@ -544,7 +533,7 @@ kern_return_t BuildAudioGraph(ASFWAudioDriver& driver,
}
ASFW_LOG(Audio, "ASFWAudioDriver: IO operation handler installed");

for (uint32_t ch = 1; ch <= ivars.device.outputChannelCount && ch <= 8; ch++) {
for (uint32_t ch = 1; ch <= ivars.device.outputChannelCount && ch <= ASFW::Isoch::Audio::kMaxNamedChannels; ch++) {
auto outChName = OSSharedPtr(OSString::withCString(ivars.device.outputChannelNames[ch - 1]), OSNoRetain);
if (outChName) {
const kern_return_t status =
Expand All @@ -561,7 +550,7 @@ kern_return_t BuildAudioGraph(ASFWAudioDriver& driver,
}
}
}
for (uint32_t ch = 1; ch <= ivars.device.inputChannelCount && ch <= 8; ch++) {
for (uint32_t ch = 1; ch <= ivars.device.inputChannelCount && ch <= ASFW::Isoch::Audio::kMaxNamedChannels; ch++) {
auto inChName = OSSharedPtr(OSString::withCString(ivars.device.inputChannelNames[ch - 1]), OSNoRetain);
if (inChName) {
const kern_return_t status =
Expand Down
4 changes: 2 additions & 2 deletions ASFWDriver/Audio/DriverKit/ASFWAudioDriverPrivate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ struct AudioDriverDeviceState {

char inputPlugName[64]{};
char outputPlugName[64]{};
char inputChannelNames[8][64]{};
char outputChannelNames[8][64]{};
char inputChannelNames[ASFW::Isoch::Audio::kMaxNamedChannels][64]{};
char outputChannelNames[ASFW::Isoch::Audio::kMaxNamedChannels][64]{};
};

class DextTxExecutionTimeline final {
Expand Down
77 changes: 58 additions & 19 deletions ASFWDriver/Audio/DriverKit/Config/AudioDriverConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,6 @@ void AppendBoolControl(ParsedAudioDriverConfig& inOutConfig,
inOutConfig.boolControls[inOutConfig.boolControlCount++] = descriptor;
}

void BuildChannelNamesFromPlugs(ParsedAudioDriverConfig& inOutConfig) {
const uint32_t maxInputChannels = std::min(inOutConfig.inputChannelCount, kMaxNamedChannels);
const uint32_t maxOutputChannels = std::min(inOutConfig.outputChannelCount, kMaxNamedChannels);
for (uint32_t index = 0; index < maxInputChannels; ++index) {
snprintf(inOutConfig.inputChannelNames[index],
sizeof(inOutConfig.inputChannelNames[index]),
"%s %u",
inOutConfig.inputPlugName,
index + 1);
}
for (uint32_t index = 0; index < maxOutputChannels; ++index) {
snprintf(inOutConfig.outputChannelNames[index],
sizeof(inOutConfig.outputChannelNames[index]),
"%s %u",
inOutConfig.outputPlugName,
index + 1);
}
}

void ParseIdentityProperties(OSDictionary* properties, ParsedAudioDriverConfig& inOutConfig) {
if (auto* guid = OSDynamicCast(OSNumber, properties->getObject(Keys::kGuid))) {
inOutConfig.guid = guid->unsigned64BitValue();
Expand Down Expand Up @@ -127,6 +108,29 @@ void ParsePlugNames(OSDictionary* properties, ParsedAudioDriverConfig& inOutConf
}
}

// Read an optional OSArray of OSString device labels into a fixed [N][64] array.
// Indices align with the channel index; missing/empty entries stay empty so
// BuildChannelNamesFromPlugs synthesizes a name for that slot.
void ParseChannelNameArray(OSDictionary* properties,
const char* key,
char (*dst)[64]) {
auto* array = OSDynamicCast(OSArray, properties->getObject(key));
if (array == nullptr) {
return;
}
const uint32_t count = std::min(array->getCount(), kMaxNamedChannels);
for (uint32_t i = 0; i < count; ++i) {
if (auto* name = OSDynamicCast(OSString, array->getObject(i))) {
strlcpy(dst[i], name->getCStringNoCopy(), 64);
}
}
}

void ParseChannelNames(OSDictionary* properties, ParsedAudioDriverConfig& inOutConfig) {
ParseChannelNameArray(properties, Keys::kInputChannelNames, inOutConfig.deviceInputChannelNames);
ParseChannelNameArray(properties, Keys::kOutputChannelNames, inOutConfig.deviceOutputChannelNames);
}

void ParseBoolControlOverrides(OSDictionary* properties, ParsedAudioDriverConfig& inOutConfig) {
auto* overrideArray = OSDynamicCast(OSArray, properties->getObject(Keys::kBoolControlOverrides));
if (overrideArray == nullptr) {
Expand Down Expand Up @@ -159,6 +163,40 @@ void ParseBoolControlOverrides(OSDictionary* properties, ParsedAudioDriverConfig

} // namespace

// Fill each element name, preferring a per-channel device label when present
// and falling back to the synthesized "<plug> N". Centralizes the rule so both
// the initial parse and the post-profile regeneration in BuildAudioGraph agree.
void BuildChannelNamesFromPlugs(ParsedAudioDriverConfig& inOutConfig) {
const uint32_t maxInputChannels = std::min(inOutConfig.inputChannelCount, kMaxNamedChannels);
const uint32_t maxOutputChannels = std::min(inOutConfig.outputChannelCount, kMaxNamedChannels);
for (uint32_t index = 0; index < maxInputChannels; ++index) {
if (inOutConfig.deviceInputChannelNames[index][0] != '\0') {
strlcpy(inOutConfig.inputChannelNames[index],
inOutConfig.deviceInputChannelNames[index],
sizeof(inOutConfig.inputChannelNames[index]));
continue;
}
snprintf(inOutConfig.inputChannelNames[index],
sizeof(inOutConfig.inputChannelNames[index]),
"%s %u",
inOutConfig.inputPlugName,
index + 1);
}
for (uint32_t index = 0; index < maxOutputChannels; ++index) {
if (inOutConfig.deviceOutputChannelNames[index][0] != '\0') {
strlcpy(inOutConfig.outputChannelNames[index],
inOutConfig.deviceOutputChannelNames[index],
sizeof(inOutConfig.outputChannelNames[index]));
continue;
}
snprintf(inOutConfig.outputChannelNames[index],
sizeof(inOutConfig.outputChannelNames[index]),
"%s %u",
inOutConfig.outputPlugName,
index + 1);
}
}

void ParseAudioDriverConfigFromProperties(OSDictionary* properties,
ParsedAudioDriverConfig& inOutConfig) {
if (!properties) {
Expand All @@ -170,6 +208,7 @@ void ParseAudioDriverConfigFromProperties(OSDictionary* properties,
ParseDevicePresentationProperties(properties, inOutConfig);
ParseSampleRates(properties, inOutConfig);
ParsePlugNames(properties, inOutConfig);
ParseChannelNames(properties, inOutConfig);
ParseBoolControlOverrides(properties, inOutConfig);
BuildChannelNamesFromPlugs(inOutConfig);
}
Expand Down
17 changes: 16 additions & 1 deletion ASFWDriver/Audio/DriverKit/Config/AudioDriverConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ namespace ASFW::Isoch::Audio {
constexpr double kDefaultSampleRate = 48000.0;
constexpr uint32_t kDefaultChannelCount = 2;
constexpr uint32_t kMaxSampleRates = 8;
constexpr uint32_t kMaxNamedChannels = 8;
// Covers high-channel-count interfaces (e.g. Midas Venice F32 = 32x32 duplex)
// so per-channel device labels can be carried for every element, not just the
// first 8. Each name is at most 64 bytes (see ParsedAudioDriverConfig).
constexpr uint32_t kMaxNamedChannels = 32;
constexpr uint32_t kMaxBoolControls = 16;

constexpr uint32_t kClassIdPhantomPower = static_cast<uint32_t>('phan');
Expand Down Expand Up @@ -61,13 +64,25 @@ struct ParsedAudioDriverConfig {
char outputPlugName[64]{};
char inputChannelNames[kMaxNamedChannels][64]{};
char outputChannelNames[kMaxNamedChannels][64]{};

// Per-channel device labels published by the core side (empty string =
// none for that slot). BuildChannelNamesFromPlugs prefers these over the
// synthesized "<plug> N" names so CoreAudio shows the device's real labels.
char deviceInputChannelNames[kMaxNamedChannels][64]{};
char deviceOutputChannelNames[kMaxNamedChannels][64]{};
};

void InitializeAudioDriverConfigDefaults(ParsedAudioDriverConfig& outConfig);

void ParseAudioDriverConfigFromProperties(OSDictionary* properties,
ParsedAudioDriverConfig& inOutConfig);

// Fill inputChannelNames/outputChannelNames for the current channel counts,
// preferring per-channel device labels (deviceInput/OutputChannelNames) and
// falling back to synthesized "<plug> N". Idempotent; safe to re-run after the
// channel counts change.
void BuildChannelNamesFromPlugs(ParsedAudioDriverConfig& inOutConfig);

void BuildFallbackBoolControls(ParsedAudioDriverConfig& inOutConfig);

void ApplyBringupSingleFormatPolicy(ParsedAudioDriverConfig& inOutConfig);
Expand Down
31 changes: 31 additions & 0 deletions ASFWDriver/Audio/Model/ASFWAudioDevice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ struct ASFWAudioDevice {
uint32_t currentSampleRate{48000};
std::string inputPlugName{"Input"};
std::string outputPlugName{"Output"};
// Per-channel device labels in channel order (empty = synthesize names).
std::vector<std::string> inputChannelNames{};
std::vector<std::string> outputChannelNames{};
StreamMode streamMode{StreamMode::kNonBlocking};
bool hasPhantomOverride{false};
uint32_t phantomSupportedMask{0};
Expand Down Expand Up @@ -73,8 +76,36 @@ struct ASFWAudioDevice {
properties->setObject(PropertyKeys::kVendorId, vendorIdNum.get());
properties->setObject(PropertyKeys::kModelId, modelIdNum.get());

// Per-channel device labels (optional). The audio side reads these in
// channel order and prefers them over synthesized names.
PublishChannelNames(properties, PropertyKeys::kInputChannelNames, inputChannelNames);
PublishChannelNames(properties, PropertyKeys::kOutputChannelNames, outputChannelNames);

return true;
}

private:
static void PublishChannelNames(OSDictionary* properties,
const char* key,
const std::vector<std::string>& names) {
if (names.empty()) {
return;
}
auto array = OSSharedPtr(OSArray::withCapacity(
static_cast<uint32_t>(names.size())), OSNoRetain);
if (!array) {
return;
}
for (const auto& name : names) {
auto str = OSSharedPtr(OSString::withCString(name.c_str()), OSNoRetain);
// Keep the index aligned with the channel index even for empty
// labels; the audio side falls back to a synthesized name per slot.
if (str) {
array->setObject(str.get());
}
}
properties->setObject(key, array.get());
}
};

} // namespace ASFW::Audio::Model
5 changes: 5 additions & 0 deletions ASFWDriver/Audio/Model/AudioPropertyKeys.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ inline constexpr const char* kInputChannelCount = "ASFWInputChannelCount";
inline constexpr const char* kOutputChannelCount = "ASFWOutputChannelCount";
inline constexpr const char* kInputPlugName = "ASFWInputPlugName";
inline constexpr const char* kOutputPlugName = "ASFWOutputPlugName";
// Optional per-channel device labels (OSArray of OSString, in channel order).
// When present they override the synthesized "<plug> N" names; missing/empty
// entries fall back to the synthesized name.
inline constexpr const char* kInputChannelNames = "ASFWInputChannelNames";
inline constexpr const char* kOutputChannelNames = "ASFWOutputChannelNames";
inline constexpr const char* kCurrentSampleRate = "ASFWCurrentSampleRate";
inline constexpr const char* kStreamMode = "ASFWStreamMode";
inline constexpr const char* kHasPhantomOverride = "ASFWHasPhantomOverride";
Expand Down
48 changes: 44 additions & 4 deletions ASFWDriver/Audio/Protocols/Backends/DiceAudioBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "../../../Audio/Core/AudioRuntimeRegistry.hpp"
#include "../../../Logging/Logging.hpp"
#include "../DICE/Core/DICENotificationMailbox.hpp"
#include "../DICE/Core/IDICEDuplexProtocol.hpp"
#include "../IDeviceProtocol.hpp"
#include "../DeviceProtocolFactory.hpp"
#include "../../DriverKit/Config/DICE/DiceProfileRegistry.hpp"

Expand Down Expand Up @@ -431,11 +433,49 @@ void DiceAudioBackend::EnsureNubForGuid(uint64_t guid) noexcept {
dev.inputPlugName = "Input";
dev.outputPlugName = "Output";

if (auto endpoint = runtime_.EnsureEndpointRuntime(guid)) {
endpoint->UpdateConfig(dev);
}
// Enrich with the device's real per-channel labels (if the protocol has
// loaded them), update the endpoint runtime, then publish the nub. Host
// input == device TX, host output == device RX (see AudioTypes.hpp), which
// is exactly how GetChannelLabels reports them.
auto finish = [this, guid](Model::ASFWAudioDevice dev,
const std::shared_ptr<IDeviceProtocol>& protocol) {
if (stopping_.load(std::memory_order_acquire)) {
return;
}
if (protocol) {
std::vector<std::string> inNames;
std::vector<std::string> outNames;
if (protocol->GetChannelLabels(inNames, outNames)) {
if (!inNames.empty()) {
dev.inputChannelNames = std::move(inNames);
}
if (!outNames.empty()) {
dev.outputChannelNames = std::move(outNames);
}
ASFW_LOG(Audio,
"DiceAudioBackend::EnsureNubForGuid: applied device channel labels in=%zu out=%zu (GUID=0x%016llx)",
dev.inputChannelNames.size(), dev.outputChannelNames.size(), guid);
}
}
if (auto endpoint = runtime_.EnsureEndpointRuntime(guid)) {
endpoint->UpdateConfig(dev);
}
(void)publisher_.EnsureNub(guid, dev, "DICE");
};

(void)publisher_.EnsureNub(guid, dev, "DICE");
// Channel labels live in the TCAT stream-format name sections, cached only
// once runtime caps load (during the first stream discovery). Load them
// once before the first publish so CoreAudio shows the real names from the
// start. The load early-returns if caps are already cached; publish happens
// regardless of outcome (names fall back to synthesized "<plug> N").
if (auto* dice = protocol ? protocol->AsDiceDuplexProtocol() : nullptr) {
dice->EnsureRuntimeStreamGeometry(
[finish, dev, protocol](IOReturn /*status*/) mutable {
finish(std::move(dev), protocol);
});
return;
}
finish(std::move(dev), protocol);
}

IOReturn DiceAudioBackend::StartStreaming(uint64_t guid) noexcept {
Expand Down
Loading
Loading