зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
feeaa94e0d
Коммит
77b1b7585c
|
@ -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_
|
||||
|
|
Загрузка…
Ссылка в новой задаче