mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
PipeWire: Make native data format detection more specific.
This commit is contained in:
@@ -1869,6 +1869,135 @@ static struct ma_pw_metadata_events ma_gMetadataEventsPipeWire =
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
enum ma_spa_audio_format format;
|
||||||
|
ma_uint32 channels;
|
||||||
|
ma_uint32 sampleRate;
|
||||||
|
const ma_uint32* pChannelPositions;
|
||||||
|
} ma_pipewire_audio_info;
|
||||||
|
|
||||||
|
static void ma_pipewire_audio_info_parse(const struct ma_spa_pod* pParam, ma_log* pLog, ma_pipewire_audio_info* pAudioInfo)
|
||||||
|
{
|
||||||
|
struct ma_spa_pod_object* pObject;
|
||||||
|
struct ma_spa_pod_prop* pNextProp;
|
||||||
|
ma_uint8* ptr = (ma_uint8*)pParam;
|
||||||
|
ma_uint32 cursor = 0;
|
||||||
|
ma_uint32 size;
|
||||||
|
|
||||||
|
MA_PIPEWIRE_ZERO_OBJECT(pAudioInfo);
|
||||||
|
|
||||||
|
if (pParam == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pParam->type != MA_SPA_TYPE_Object || pParam->size < sizeof(struct ma_spa_pod_object_body)) {
|
||||||
|
ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid pod type).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pObject = (struct ma_spa_pod_object*)pParam;
|
||||||
|
|
||||||
|
/* The size of the pod does not include the header which makes parsing kind of annoying for us. We'll add it here. */
|
||||||
|
size = pParam->size + sizeof(struct ma_spa_pod);
|
||||||
|
cursor += sizeof(struct ma_spa_pod);
|
||||||
|
|
||||||
|
if (pObject->body.type != MA_SPA_TYPE_OBJECT_Format) {
|
||||||
|
ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid body type).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor += sizeof(struct ma_spa_pod_object_body);
|
||||||
|
|
||||||
|
/* At this point we have parsed the header part of the object, and what follows now should be a list of properties. */
|
||||||
|
while (cursor < size) {
|
||||||
|
struct ma_spa_pod* pPropValue;
|
||||||
|
|
||||||
|
pNextProp = (struct ma_spa_pod_prop*)(ptr + cursor);
|
||||||
|
if (cursor + sizeof(struct ma_spa_pod_prop) > size) {
|
||||||
|
ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid size for property).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Place the cursor on the prop value, which is a spa_pod. */
|
||||||
|
cursor += 8; /* Size of the key and flags. */
|
||||||
|
pPropValue = (struct ma_spa_pod*)(ptr + cursor);
|
||||||
|
|
||||||
|
switch (pNextProp->key)
|
||||||
|
{
|
||||||
|
case MA_SPA_FORMAT_AUDIO_format:
|
||||||
|
{
|
||||||
|
pAudioInfo->format = (enum ma_spa_audio_format)ma_spa_pod_get_id_value(pPropValue);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case MA_SPA_FORMAT_AUDIO_channels:
|
||||||
|
{
|
||||||
|
pAudioInfo->channels = ma_spa_pod_get_int_value(pPropValue);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case MA_SPA_FORMAT_AUDIO_rate:
|
||||||
|
{
|
||||||
|
pAudioInfo->sampleRate = ma_spa_pod_get_int_value(pPropValue);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case MA_SPA_FORMAT_AUDIO_position:
|
||||||
|
{
|
||||||
|
/* I'm assuming we can only get an array back for this. */
|
||||||
|
if (pPropValue->type != MA_SPA_TYPE_Array) {
|
||||||
|
ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid type for position property).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pAudioInfo->pChannelPositions = (const ma_uint32*)ma_spa_pod_array_get_values((const struct ma_spa_pod_array*)pPropValue);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor += ma_align_64(8 + pPropValue->size); /* Size of the value pod header + the size of the pod itself. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ma_log* pLog;
|
||||||
|
ma_uint32 channels;
|
||||||
|
ma_uint32 sampleRate;
|
||||||
|
ma_bool32 done;
|
||||||
|
} ma_stream_event_param_changed_enumeration_data__pipewire;
|
||||||
|
|
||||||
|
static void ma_stream_event_param_changed_enumeration__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam)
|
||||||
|
{
|
||||||
|
ma_stream_event_param_changed_enumeration_data__pipewire* pData = (ma_stream_event_param_changed_enumeration_data__pipewire*)pUserData;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (id == MA_SPA_PARAM_Format) {
|
||||||
|
ma_pipewire_audio_info audioInfo;
|
||||||
|
ma_pipewire_audio_info_parse(pParam, pData->pLog, &audioInfo);
|
||||||
|
|
||||||
|
pData->channels = audioInfo.channels;
|
||||||
|
pData->sampleRate = audioInfo.sampleRate;
|
||||||
|
|
||||||
|
pData->done = MA_TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Enumeration =
|
||||||
|
{
|
||||||
|
MA_PW_VERSION_STREAM_EVENTS,
|
||||||
|
NULL, /* destroy */
|
||||||
|
NULL, /* state_changed */
|
||||||
|
NULL, /* control_info */
|
||||||
|
NULL, /* io_changed */
|
||||||
|
ma_stream_event_param_changed_enumeration__pipewire,
|
||||||
|
NULL, /* add_buffer */
|
||||||
|
NULL, /* remove_buffer */
|
||||||
|
NULL, /* process */
|
||||||
|
NULL, /* drained */
|
||||||
|
NULL, /* command */
|
||||||
|
NULL, /* trigger_done */
|
||||||
|
};
|
||||||
|
|
||||||
static void ma_registry_event_global_add_enumeration_by_type__pipewire(ma_enumerate_devices_data_pipewire* pEnumData, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props, ma_device_type deviceType)
|
static void ma_registry_event_global_add_enumeration_by_type__pipewire(ma_enumerate_devices_data_pipewire* pEnumData, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props, ma_device_type deviceType)
|
||||||
{
|
{
|
||||||
ma_device_info deviceInfo;
|
ma_device_info deviceInfo;
|
||||||
@@ -1901,13 +2030,85 @@ static void ma_registry_event_global_add_enumeration_by_type__pipewire(ma_enumer
|
|||||||
/* Name. */
|
/* Name. */
|
||||||
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pNiceName, (size_t)-1);
|
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pNiceName, (size_t)-1);
|
||||||
|
|
||||||
/* Data Format. Just support everything for now. */
|
/* Data Format. Create a stream so we can identify the native channels and sample rate. */
|
||||||
/* TODO: See if there's a reasonable way to query the true "native" format. Maybe just initialize a stream and handle the SPA_PARAM_Format parameter in param_changed()? */
|
{
|
||||||
|
struct ma_pw_properties* pProperties;
|
||||||
|
struct ma_pw_stream* pStream;
|
||||||
|
|
||||||
|
pProperties = pEnumData->pContextStatePipeWire->pw_properties_new(
|
||||||
|
MA_PW_KEY_MEDIA_TYPE, "Audio",
|
||||||
|
MA_PW_KEY_MEDIA_CATEGORY, (deviceType == ma_device_type_playback) ? "Playback" : "Capture",
|
||||||
|
MA_PW_KEY_MEDIA_ROLE, "Game",
|
||||||
|
MA_PW_KEY_TARGET_OBJECT, pNodeName,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
pStream = pEnumData->pContextStatePipeWire->pw_stream_new(pEnumData->pCore, "miniaudio-enumeration", pProperties);
|
||||||
|
if (pStream != NULL) {
|
||||||
|
ma_stream_event_param_changed_enumeration_data__pipewire eventsData;
|
||||||
|
struct ma_spa_hook eventListener;
|
||||||
|
enum ma_spa_audio_format formatPA;
|
||||||
|
struct ma_spa_pod_audio_info_raw podAudioInfo;
|
||||||
|
const struct ma_spa_pod* pConnectionParameters[1];
|
||||||
|
enum ma_pw_stream_flags streamFlags;
|
||||||
|
int connectResult;
|
||||||
|
|
||||||
|
/* The way to determine the "native" channel count and sample rate is by handling the SPA_PARAM_Format event in the params_changed callback. */
|
||||||
|
MA_PIPEWIRE_ZERO_OBJECT(&eventsData);
|
||||||
|
eventsData.pLog = pEnumData->pContextStatePipeWire->pLog;
|
||||||
|
|
||||||
|
pEnumData->pContextStatePipeWire->pw_stream_add_listener(pStream, &eventListener, &ma_gStreamEventsPipeWire_Enumeration, &eventsData);
|
||||||
|
|
||||||
|
|
||||||
|
/* Now we can connect the stream. */
|
||||||
|
if (ma_is_little_endian()) {
|
||||||
|
formatPA = MA_SPA_AUDIO_FORMAT_F32_LE;
|
||||||
|
} else {
|
||||||
|
formatPA = MA_SPA_AUDIO_FORMAT_F32_BE;
|
||||||
|
}
|
||||||
|
|
||||||
|
podAudioInfo = ma_spa_pod_audio_info_raw_init(formatPA, 0, 0);
|
||||||
|
pConnectionParameters[0] = (struct ma_spa_pod*)&podAudioInfo;
|
||||||
|
|
||||||
|
streamFlags = (enum ma_pw_stream_flags)(MA_PW_STREAM_FLAG_AUTOCONNECT);
|
||||||
|
connectResult = pEnumData->pContextStatePipeWire->pw_stream_connect(pStream, (deviceType == ma_device_type_playback) ? MA_SPA_DIRECTION_OUTPUT : MA_SPA_DIRECTION_INPUT, MA_PW_ID_ANY, streamFlags, pConnectionParameters, ma_countof(pConnectionParameters));
|
||||||
|
if (connectResult >= 0) {
|
||||||
|
/* Keep looping until we've processed our channel count and sample rate. */
|
||||||
|
while (!eventsData.done) {
|
||||||
|
pEnumData->pContextStatePipeWire->pw_loop_iterate(pEnumData->pLoop, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventsData.channels != 0 && eventsData.sampleRate != 0) {
|
||||||
|
ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, eventsData.channels, eventsData.channels, eventsData.sampleRate, eventsData.sampleRate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ma_log_postf(pEnumData->pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire stream for enumeration. Device ID: \"%s\".", pNodeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We're done with the stream. */
|
||||||
|
pEnumData->pContextStatePipeWire->pw_stream_destroy(pStream);
|
||||||
|
} else {
|
||||||
|
ma_log_postf(pEnumData->pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to create PipeWire stream for enumeration. Device ID: \"%s\".", pNodeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
In the logic above we chose the "native" format that PipeWire would pick if no channel count or sample rate is
|
||||||
|
specified during device initialization. However, PipeWire will actually do it's own data conversion, so from the
|
||||||
|
perspective of miniaudio, it also "natively" supports any format, channel count and sample rate. I'm not sure if
|
||||||
|
it's better to include these entries of if we should just leave it set to the one format.
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
||||||
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
||||||
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
||||||
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s24, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s24, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
||||||
ma_device_info_add_native_data_format(&deviceInfo, ma_format_u8, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
ma_device_info_add_native_data_format(&deviceInfo, ma_format_u8, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Just in case something went wrong in the logic above, if we never extracted a native format we'll add a fallback. */
|
||||||
|
if (deviceInfo.nativeDataFormatCount == 0) {
|
||||||
|
ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
||||||
|
}
|
||||||
|
|
||||||
ma_enumerate_devices_data_pipewire_add(pEnumData, deviceType, &deviceInfo);
|
ma_enumerate_devices_data_pipewire_add(pEnumData, deviceType, &deviceInfo);
|
||||||
|
|
||||||
@@ -2118,10 +2319,7 @@ static void ma_stream_event_param_changed__pipewire(void* pUserData, ma_uint32 i
|
|||||||
const struct ma_spa_pod* pBufferParameters[1];
|
const struct ma_spa_pod* pBufferParameters[1];
|
||||||
ma_uint32 bytesPerFrame;
|
ma_uint32 bytesPerFrame;
|
||||||
ma_uint32 iChannel;
|
ma_uint32 iChannel;
|
||||||
enum ma_spa_audio_format formatPA = MA_SPA_AUDIO_FORMAT_UNKNOWN;
|
ma_pipewire_audio_info audioInfo;
|
||||||
ma_uint32 channels = 0;
|
|
||||||
ma_uint32 sampleRate = 0;
|
|
||||||
const ma_uint32* pChannelPositionsPA = NULL;
|
|
||||||
|
|
||||||
/* It's possible for PipeWire to fire this callback with pParam set to NULL. I noticed it when tearing down a stream. Why does it do this? */
|
/* It's possible for PipeWire to fire this callback with pParam set to NULL. I noticed it when tearing down a stream. Why does it do this? */
|
||||||
if (pParam == NULL) {
|
if (pParam == NULL) {
|
||||||
@@ -2150,94 +2348,25 @@ static void ma_stream_event_param_changed__pipewire(void* pUserData, ma_uint32 i
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
struct ma_spa_pod_object* pObject;
|
ma_pipewire_audio_info_parse(pParam, pContextStatePipeWire->pLog, &audioInfo);
|
||||||
struct ma_spa_pod_prop* pNextProp;
|
|
||||||
ma_uint8* ptr = (ma_uint8*)pParam;
|
|
||||||
ma_uint32 cursor = 0;
|
|
||||||
ma_uint32 size;
|
|
||||||
|
|
||||||
if (pParam->type != MA_SPA_TYPE_Object || pParam->size < sizeof(struct ma_spa_pod_object_body)) {
|
|
||||||
ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid pod type).");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pObject = (struct ma_spa_pod_object*)pParam;
|
|
||||||
|
|
||||||
/* The size of the pod does not include the header which makes parsing kind of annoying for us. We'll add it here. */
|
|
||||||
size = pParam->size + sizeof(struct ma_spa_pod);
|
|
||||||
cursor += sizeof(struct ma_spa_pod);
|
|
||||||
|
|
||||||
if (pObject->body.type != MA_SPA_TYPE_OBJECT_Format) {
|
|
||||||
ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid body type).");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor += sizeof(struct ma_spa_pod_object_body);
|
|
||||||
|
|
||||||
/* At this point we have parsed the header part of the object, and what follows now should be a list of properties. */
|
|
||||||
while (cursor < size) {
|
|
||||||
struct ma_spa_pod* pPropValue;
|
|
||||||
|
|
||||||
pNextProp = (struct ma_spa_pod_prop*)(ptr + cursor);
|
|
||||||
if (cursor + sizeof(struct ma_spa_pod_prop) > size) {
|
|
||||||
ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid size for property).");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Place the cursor on the prop value, which is a spa_pod. */
|
|
||||||
cursor += 8; /* Size of the key and flags. */
|
|
||||||
pPropValue = (struct ma_spa_pod*)(ptr + cursor);
|
|
||||||
|
|
||||||
switch (pNextProp->key)
|
|
||||||
{
|
|
||||||
case MA_SPA_FORMAT_AUDIO_format:
|
|
||||||
{
|
|
||||||
formatPA = (enum ma_spa_audio_format)ma_spa_pod_get_id_value(pPropValue);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case MA_SPA_FORMAT_AUDIO_channels:
|
|
||||||
{
|
|
||||||
channels = ma_spa_pod_get_int_value(pPropValue);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case MA_SPA_FORMAT_AUDIO_rate:
|
|
||||||
{
|
|
||||||
sampleRate = ma_spa_pod_get_int_value(pPropValue);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case MA_SPA_FORMAT_AUDIO_position:
|
|
||||||
{
|
|
||||||
/* I'm assuming we can only get an array back for this. */
|
|
||||||
if (pPropValue->type != MA_SPA_TYPE_Array) {
|
|
||||||
ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid type for position property).");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pChannelPositionsPA = (const ma_uint32*)ma_spa_pod_array_get_values((const struct ma_spa_pod_array*)pPropValue);
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor += ma_align_64(8 + pPropValue->size); /* Size of the value pod header + the size of the pod itself. */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Now that we definitely know the sample rate, we can reliably configure the size of the buffer. */
|
/* Now that we definitely know the sample rate, we can reliably configure the size of the buffer. */
|
||||||
if (pStreamState->bufferSizeInFrames == 0) {
|
if (pStreamState->bufferSizeInFrames == 0) {
|
||||||
pStreamState->bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pStreamState->pDescriptor, sampleRate);
|
pStreamState->bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pStreamState->pDescriptor, audioInfo.sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
pStreamState->format = ma_format_from_pipewire(formatPA);
|
pStreamState->format = ma_format_from_pipewire(audioInfo.format);
|
||||||
pStreamState->channels = channels;
|
pStreamState->channels = audioInfo.channels;
|
||||||
pStreamState->sampleRate = sampleRate;
|
pStreamState->sampleRate = audioInfo.sampleRate;
|
||||||
|
|
||||||
/* We should always get a channel map, but just to be safe we'll check for it, and if we don't get one back we'll use a default. */
|
/* We should always get a channel map, but just to be safe we'll check for it, and if we don't get one back we'll use a default. */
|
||||||
if (pChannelPositionsPA != NULL) {
|
if (audioInfo.pChannelPositions != NULL) {
|
||||||
for (iChannel = 0; iChannel < channels; iChannel += 1) {
|
for (iChannel = 0; iChannel < audioInfo.channels; iChannel += 1) {
|
||||||
pStreamState->channelMap[iChannel] = ma_channel_from_pipewire(pChannelPositionsPA[iChannel]);
|
pStreamState->channelMap[iChannel] = ma_channel_from_pipewire(audioInfo.pChannelPositions[iChannel]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ma_channel_map_init_standard(ma_standard_channel_map_alsa, pStreamState->channelMap, ma_countof(pStreamState->channelMap), channels);
|
ma_channel_map_init_standard(ma_standard_channel_map_alsa, pStreamState->channelMap, ma_countof(pStreamState->channelMap), audioInfo.channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user