Bug 1545079 - Enhance MockCubeb class to simulate a cubeb stream. r=padenot

On MochCubeb add a fake audio thread and the corresponding methods for stream_{init,start,stop,destroy}.

Differential Revision: https://phabricator.services.mozilla.com/D30888

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Alex Chronopoulos 2019-05-17 16:38:04 +00:00
Родитель feeaa94e0d
Коммит 77b1b7585c
1 изменённых файлов: 181 добавлений и 22 удалений

Просмотреть файл

@ -3,6 +3,15 @@
#include "AudioDeviceInfo.h"
#include <thread>
#include <atomic>
#include <chrono>
using namespace std::chrono_literals;
const long NUM_OF_FRAMES = 512;
const uint32_t NUM_OF_CHANNELS = 2;
struct cubeb_ops {
int (*init)(cubeb** context, char const* context_name);
char const* (*get_backend_id)(cubeb* context);
@ -54,24 +63,45 @@ static int cubeb_mock_register_device_collection_changed(
cubeb* context, cubeb_device_type devtype,
cubeb_device_collection_changed_callback callback, void* user_ptr);
static int cubeb_mock_stream_init(
cubeb* context, cubeb_stream** stream, char const* stream_name,
cubeb_devid input_device, cubeb_stream_params* input_stream_params,
cubeb_devid output_device, cubeb_stream_params* output_stream_params,
unsigned int latency, cubeb_data_callback data_callback,
cubeb_state_callback state_callback, void* user_ptr);
static int cubeb_mock_stream_start(cubeb_stream* stream);
static int cubeb_mock_stream_stop(cubeb_stream* stream);
static void cubeb_mock_stream_destroy(cubeb_stream* stream);
static char const* cubeb_mock_get_backend_id(cubeb* context);
static int cubeb_mock_stream_set_volume(cubeb_stream* stream, float volume);
static int cubeb_mock_get_min_latency(cubeb* context,
cubeb_stream_params params,
uint32_t* latency_ms);
// Mock cubeb impl, only supports device enumeration for now.
cubeb_ops const mock_ops = {
/*.init =*/NULL,
/*.get_backend_id =*/NULL,
/*.get_backend_id =*/cubeb_mock_get_backend_id,
/*.get_max_channel_count =*/NULL,
/*.get_min_latency =*/NULL,
/*.get_min_latency =*/cubeb_mock_get_min_latency,
/*.get_preferred_sample_rate =*/NULL,
/*.enumerate_devices =*/cubeb_mock_enumerate_devices,
/*.device_collection_destroy =*/cubeb_mock_device_collection_destroy,
/*.destroy =*/cubeb_mock_destroy,
/*.stream_init =*/NULL,
/*.stream_destroy =*/NULL,
/*.stream_start =*/NULL,
/*.stream_stop =*/NULL,
/*.stream_init =*/cubeb_mock_stream_init,
/*.stream_destroy =*/cubeb_mock_stream_destroy,
/*.stream_start =*/cubeb_mock_stream_start,
/*.stream_stop =*/cubeb_mock_stream_stop,
/*.stream_reset_default_device =*/NULL,
/*.stream_get_position =*/NULL,
/*.stream_get_latency =*/NULL,
/*.stream_set_volume =*/NULL,
/*.stream_set_volume =*/cubeb_mock_stream_set_volume,
/*.stream_set_panning =*/NULL,
/*.stream_get_current_device =*/NULL,
/*.stream_device_destroy =*/NULL,
@ -85,13 +115,11 @@ cubeb_ops const mock_ops = {
// should do, depending on what is being tested.
class MockCubeb {
public:
MockCubeb()
: ops(&mock_ops),
mInputDeviceCollectionChangeCallback(nullptr),
mOutputDeviceCollectionChangeCallback(nullptr),
mInputDeviceCollectionChangeUserPtr(nullptr),
mOutputDeviceCollectionChangeUserPtr(nullptr),
mSupportsDeviceCollectionChangedCallback(true) {}
MockCubeb() : ops(&mock_ops) {}
~MockCubeb() {
assert(!mFakeAudioThread);
assert(!mMockStream);
}
// Cubeb backend implementation
// This allows passing this class as a cubeb* instance.
cubeb* AsCubebContext() { return reinterpret_cast<cubeb*>(this); }
@ -241,33 +269,109 @@ class MockCubeb {
mSupportsDeviceCollectionChangedCallback = aSupports;
}
// Represents the fake cubeb_stream. The context instance is needed to
// provide access on cubeb_ops struct.
struct MockCubebStream {
cubeb* context = nullptr;
};
// Simulates the audio thread. The thread is created at StreamStart and
// destroyed at StreamStop. At next StreamStart a new thread is created.
static void ThreadFunction_s(MockCubeb* that) { that->ThreadFunction(); }
void ThreadFunction() {
while (!mStreamStop) {
cubeb_stream* stream = reinterpret_cast<cubeb_stream*>(mMockStream.get());
long outframes = mDataCallback(stream, mUserPtr, nullptr, mOutputBuffer,
NUM_OF_FRAMES);
if (outframes < NUM_OF_FRAMES) {
mStateCallback(stream, mUserPtr, CUBEB_STATE_DRAINED);
break;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(NUM_OF_FRAMES * 1000 / mSampleRate));
}
}
int StreamInit(cubeb* aContext, cubeb_stream** aStream,
cubeb_stream_params* aInputStreamParams,
cubeb_stream_params* aOutputStreamParams,
cubeb_data_callback aDataCallback,
cubeb_state_callback aStateCallback, void* aUserPtr) {
assert(!mFakeAudioThread);
mMockStream.reset(new MockCubebStream);
mMockStream->context = aContext;
*aStream = reinterpret_cast<cubeb_stream*>(mMockStream.get());
mDataCallback = aDataCallback;
mStateCallback = aStateCallback;
mUserPtr = aUserPtr;
mSampleRate = aInputStreamParams ? aInputStreamParams->rate
: aOutputStreamParams->rate;
return CUBEB_OK;
}
int StreamStart(cubeb_stream* aStream) {
assert(!mFakeAudioThread);
mStreamStop = false;
mFakeAudioThread.reset(new std::thread(ThreadFunction_s, this));
assert(mFakeAudioThread);
mStateCallback(aStream, mUserPtr, CUBEB_STATE_STARTED);
return CUBEB_OK;
}
int StreamStop(cubeb_stream* aStream) {
assert(mFakeAudioThread);
mStreamStop = true;
mFakeAudioThread->join();
mFakeAudioThread.reset();
mStateCallback(aStream, mUserPtr, CUBEB_STATE_STOPPED);
return CUBEB_OK;
}
void StreamDestroy(cubeb_stream* aStream) { mMockStream.reset(); }
private:
// This needs to have the exact same memory layout as a real cubeb backend.
// It's very important for this `ops` member to be the very first member of
// the class, and to not have any virtual members (to avoid having a vtable).
const cubeb_ops* ops;
// The callback to call when the device list has been changed.
cubeb_device_collection_changed_callback mInputDeviceCollectionChangeCallback;
cubeb_device_collection_changed_callback
mOutputDeviceCollectionChangeCallback;
mInputDeviceCollectionChangeCallback = nullptr;
cubeb_device_collection_changed_callback
mOutputDeviceCollectionChangeCallback = nullptr;
cubeb_data_callback mDataCallback = nullptr;
cubeb_state_callback mStateCallback = nullptr;
// The pointer to pass in the callback.
void* mInputDeviceCollectionChangeUserPtr;
void* mOutputDeviceCollectionChangeUserPtr;
void* mInputDeviceCollectionChangeUserPtr = nullptr;
void* mOutputDeviceCollectionChangeUserPtr = nullptr;
void* mUserPtr = nullptr;
// Whether or not this backend supports device collection change notification
// via a system callback. If not, Gecko is expected to re-query the list every
// time.
bool mSupportsDeviceCollectionChangedCallback;
bool mSupportsDeviceCollectionChangedCallback = true;
// Our input and output devices.
nsTArray<cubeb_device_info> mInputDevices;
nsTArray<cubeb_device_info> mOutputDevices;
// Thread that simulates the audio thread.
std::unique_ptr<std::thread> mFakeAudioThread;
// Signal to the audio thread that stream is stopped.
std::atomic_bool mStreamStop{true};
// The fake stream instance.
std::unique_ptr<MockCubebStream> mMockStream;
// The stream sample rate
uint32_t mSampleRate = 0;
// The audio buffer used on data callback.
float mOutputBuffer[NUM_OF_CHANNELS * NUM_OF_FRAMES];
};
void cubeb_mock_destroy(cubeb* context) {
delete reinterpret_cast<MockCubeb*>(context);
}
static int cubeb_mock_enumerate_devices(cubeb* context, cubeb_device_type type,
cubeb_device_collection* out) {
int cubeb_mock_enumerate_devices(cubeb* context, cubeb_device_type type,
cubeb_device_collection* out) {
MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
return mock->EnumerateDevices(type, out);
}
@ -286,6 +390,61 @@ int cubeb_mock_register_device_collection_changed(
user_ptr);
}
int cubeb_mock_stream_init(
cubeb* context, cubeb_stream** stream, char const* stream_name,
cubeb_devid input_device, cubeb_stream_params* input_stream_params,
cubeb_devid output_device, cubeb_stream_params* output_stream_params,
unsigned int latency, cubeb_data_callback data_callback,
cubeb_state_callback state_callback, void* user_ptr) {
MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
return mock->StreamInit(context, stream, input_stream_params,
output_stream_params, data_callback, state_callback,
user_ptr);
}
int cubeb_mock_stream_start(cubeb_stream* stream) {
MockCubeb::MockCubebStream* mockStream =
reinterpret_cast<MockCubeb::MockCubebStream*>(stream);
MockCubeb* mock = reinterpret_cast<MockCubeb*>(mockStream->context);
return mock->StreamStart(stream);
}
int cubeb_mock_stream_stop(cubeb_stream* stream) {
MockCubeb::MockCubebStream* mockStream =
reinterpret_cast<MockCubeb::MockCubebStream*>(stream);
MockCubeb* mock = reinterpret_cast<MockCubeb*>(mockStream->context);
return mock->StreamStop(stream);
}
void cubeb_mock_stream_destroy(cubeb_stream* stream) {
MockCubeb::MockCubebStream* mockStream =
reinterpret_cast<MockCubeb::MockCubebStream*>(stream);
MockCubeb* mock = reinterpret_cast<MockCubeb*>(mockStream->context);
return mock->StreamDestroy(stream);
}
static char const* cubeb_mock_get_backend_id(cubeb* context) {
#if defined(XP_LINUX)
return "pulse";
#elif defined(XP_MACOSX)
return "audiounit";
#elif defined(XP_WIN)
return "wasapi";
#elif defined(ANDROID)
return "opensl";
#endif
}
static int cubeb_mock_stream_set_volume(cubeb_stream* stream, float volume) {
return CUBEB_OK;
}
int cubeb_mock_get_min_latency(cubeb* context, cubeb_stream_params params,
uint32_t* latency_ms) {
*latency_ms = NUM_OF_FRAMES;
return CUBEB_OK;
}
void PrintDevice(cubeb_device_info aInfo) {
printf(
"id: %zu\n"
@ -412,4 +571,4 @@ void AddDevices(MockCubeb* mock, uint32_t device_count,
}
}
#endif // MOCKCUBEB_H_
#endif // MOCKCUBEB_H_