mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-11-03 18:57:09 +09:00
159 lines
4.9 KiB
C++
159 lines
4.9 KiB
C++
/**
|
|
* @file tests/unit/platform/linux/test_audio.cpp
|
|
* @brief Tests for the Linux audio capture channel map.
|
|
*/
|
|
|
|
#ifdef __linux__
|
|
|
|
// test includes
|
|
#include "../../../tests_common.h"
|
|
|
|
// standard includes
|
|
#include <chrono>
|
|
#include <vector>
|
|
|
|
// lib includes
|
|
#include <pulse/pulseaudio.h>
|
|
|
|
// local includes
|
|
#include "src/platform/common.h"
|
|
#include "src/thread_safe.h"
|
|
|
|
namespace platf {
|
|
bool fill_canonical_channel_map(int channels, pa_channel_map &pa_map);
|
|
|
|
namespace pa {
|
|
/**
|
|
* @brief Forward a PulseAudio integer result to the caller's alarm.
|
|
* @param value Result supplied by PulseAudio.
|
|
* @param userdata Alarm receiving the result.
|
|
*/
|
|
void cb_i(pa_context *, std::uint32_t value, void *userdata);
|
|
|
|
/**
|
|
* @brief Forward PulseAudio operation completion to the caller's alarm.
|
|
* @param status Nonzero on success, zero on failure.
|
|
* @param userdata Alarm receiving the translated result.
|
|
*/
|
|
void success_cb(pa_context *, int status, void *userdata);
|
|
} // namespace pa
|
|
} // namespace platf
|
|
|
|
namespace {
|
|
/**
|
|
* @brief Collect the speaker positions a channel map describes.
|
|
*
|
|
* @param pa_map Channel map to read.
|
|
* @return The positions of its channels, in channel order.
|
|
*/
|
|
std::vector<pa_channel_position_t> positions_of(const pa_channel_map &pa_map) {
|
|
return {pa_map.map, pa_map.map + pa_map.channels};
|
|
}
|
|
} // namespace
|
|
|
|
/**
|
|
* @brief Capture must use the canonical layout, whatever Opus mapping the client asked for.
|
|
*
|
|
* The Opus mapping is applied by the multistream encoder and undone by the client when it
|
|
* decodes, so applying it to the captured PCM as well leaves the client rendering permuted
|
|
* channels. The webOS client requests 0,1,4,5,2,3, which would put the centre channel on the
|
|
* rear left speaker.
|
|
*/
|
|
TEST(LinuxAudioChannelMap, Surround51UsesCanonicalOrder) {
|
|
pa_channel_map pa_map;
|
|
ASSERT_TRUE(platf::fill_canonical_channel_map(6, pa_map));
|
|
|
|
const std::vector<pa_channel_position_t> expected {
|
|
PA_CHANNEL_POSITION_FRONT_LEFT,
|
|
PA_CHANNEL_POSITION_FRONT_RIGHT,
|
|
PA_CHANNEL_POSITION_FRONT_CENTER,
|
|
PA_CHANNEL_POSITION_LFE,
|
|
PA_CHANNEL_POSITION_REAR_LEFT,
|
|
PA_CHANNEL_POSITION_REAR_RIGHT
|
|
};
|
|
EXPECT_EQ(positions_of(pa_map), expected);
|
|
}
|
|
|
|
/**
|
|
* @brief Stereo capture maps onto the two front channels.
|
|
*/
|
|
TEST(LinuxAudioChannelMap, StereoUsesCanonicalOrder) {
|
|
pa_channel_map pa_map;
|
|
ASSERT_TRUE(platf::fill_canonical_channel_map(2, pa_map));
|
|
|
|
const std::vector<pa_channel_position_t> expected {
|
|
PA_CHANNEL_POSITION_FRONT_LEFT,
|
|
PA_CHANNEL_POSITION_FRONT_RIGHT
|
|
};
|
|
EXPECT_EQ(positions_of(pa_map), expected);
|
|
}
|
|
|
|
/**
|
|
* @brief 7.1 capture adds the two side channels to the 5.1 layout.
|
|
*/
|
|
TEST(LinuxAudioChannelMap, Surround71UsesCanonicalOrder) {
|
|
pa_channel_map pa_map;
|
|
ASSERT_TRUE(platf::fill_canonical_channel_map(8, pa_map));
|
|
|
|
const std::vector<pa_channel_position_t> expected {
|
|
PA_CHANNEL_POSITION_FRONT_LEFT,
|
|
PA_CHANNEL_POSITION_FRONT_RIGHT,
|
|
PA_CHANNEL_POSITION_FRONT_CENTER,
|
|
PA_CHANNEL_POSITION_LFE,
|
|
PA_CHANNEL_POSITION_REAR_LEFT,
|
|
PA_CHANNEL_POSITION_REAR_RIGHT,
|
|
PA_CHANNEL_POSITION_SIDE_LEFT,
|
|
PA_CHANNEL_POSITION_SIDE_RIGHT
|
|
};
|
|
EXPECT_EQ(positions_of(pa_map), expected);
|
|
}
|
|
|
|
/**
|
|
* @brief The map the backend hands PulseAudio must be a valid one.
|
|
*/
|
|
TEST(LinuxAudioChannelMap, ProducesValidPulseAudioMaps) {
|
|
for (const int channels : {2, 6, 8}) {
|
|
pa_channel_map pa_map;
|
|
ASSERT_TRUE(platf::fill_canonical_channel_map(channels, pa_map)) << "channels: " << channels;
|
|
EXPECT_TRUE(pa_channel_map_valid(&pa_map)) << "channels: " << channels;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Channel counts with no known speaker layout are rejected rather than read out of bounds.
|
|
*/
|
|
TEST(LinuxAudioChannelMap, RejectsUnsupportedChannelCounts) {
|
|
pa_channel_map pa_map;
|
|
EXPECT_FALSE(platf::fill_canonical_channel_map(0, pa_map));
|
|
EXPECT_FALSE(platf::fill_canonical_channel_map(-1, pa_map));
|
|
EXPECT_FALSE(platf::fill_canonical_channel_map(platf::speaker::MAX_SPEAKERS + 1, pa_map));
|
|
}
|
|
|
|
/**
|
|
* @brief PulseAudio integer callbacks ring the supplied alarm without requiring a context.
|
|
*/
|
|
TEST(LinuxAudioCallbackTest, ForwardsIntegerResultWithoutContext) {
|
|
safe::alarm_raw_t<int> alarm;
|
|
platf::pa::cb_i(nullptr, 42, &alarm);
|
|
|
|
ASSERT_TRUE(alarm.wait_for(std::chrono::milliseconds {0}));
|
|
ASSERT_TRUE(alarm.status().has_value());
|
|
EXPECT_EQ(*alarm.status(), 42);
|
|
}
|
|
|
|
/**
|
|
* @brief PulseAudio success callbacks preserve the zero-success and one-failure alarm convention.
|
|
*/
|
|
TEST(LinuxAudioCallbackTest, TranslatesSuccessAndFailureWithoutContext) {
|
|
for (const int status : {0, 1, -1}) {
|
|
safe::alarm_raw_t<int> alarm;
|
|
platf::pa::success_cb(nullptr, status, &alarm);
|
|
|
|
ASSERT_TRUE(alarm.wait_for(std::chrono::milliseconds {0}));
|
|
ASSERT_TRUE(alarm.status().has_value());
|
|
EXPECT_EQ(*alarm.status(), status == 0 ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
#endif // __linux__
|