Import Cobalt 12.88774
Change-Id: Id685e20e1b1f8fec13607b39f552fad3cc76ebae
diff --git a/src/starboard/shared/alsa/alsa_audio_sink_type.cc b/src/starboard/shared/alsa/alsa_audio_sink_type.cc
index 25688a5..e0cbd79 100644
--- a/src/starboard/shared/alsa/alsa_audio_sink_type.cc
+++ b/src/starboard/shared/alsa/alsa_audio_sink_type.cc
@@ -99,6 +99,11 @@
}
#endif // SB_API_VERSION >= 4
+ void SetVolume(double volume) SB_OVERRIDE {
+ ScopedLock lock(mutex_);
+ volume_ = volume;
+ }
+
bool is_valid() { return playback_handle_ != NULL; }
private:
@@ -124,6 +129,7 @@
void* context_;
double playback_rate_;
+ double volume_;
std::vector<uint8_t> resample_buffer_;
int channels_;
@@ -157,6 +163,7 @@
void* context)
: type_(type),
playback_rate_(1.0),
+ volume_(1.0),
resample_buffer_(channels * kFramesPerRequest *
GetSampleSize(sample_type)),
channels_(channels),
@@ -272,6 +279,7 @@
bool AlsaAudioSink::PlaybackLoop() {
SB_DLOG(INFO) << "alsa::AlsaAudioSink enters playback loop";
+ // TODO: Also handle |volume_| here.
double playback_rate = 1.0;
for (;;) {
int delayed_frame = AlsaGetBufferedFrames(playback_handle_);
diff --git a/src/starboard/shared/dlmalloc/system_get_used_cpu_memory.cc b/src/starboard/shared/dlmalloc/system_get_used_cpu_memory.cc
new file mode 100644
index 0000000..0fe2662
--- /dev/null
+++ b/src/starboard/shared/dlmalloc/system_get_used_cpu_memory.cc
@@ -0,0 +1,39 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/memory.h"
+
+#include "starboard/shared/dlmalloc/page_internal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Returns the number of bytes obtained from the system. The total
+// number of bytes allocated by malloc, realloc etc., is less than this
+// value. Unlike mallinfo, this function returns only a precomputed
+// result, so can be called frequently to monitor memory consumption.
+// Even if locks are otherwise defined, this function does not use them,
+// so results might not be up to date.
+//
+// See http://gee.cs.oswego.edu/pub/misc/malloc.h for more details.
+size_t SB_ALLOCATOR(malloc_footprint)();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+int64_t SbSystemGetUsedCPUMemory() {
+ return static_cast<int64_t>(SB_ALLOCATOR(malloc_footprint)());
+}
diff --git a/src/starboard/shared/linux/dev_input/dev_input.cc b/src/starboard/shared/linux/dev_input/dev_input.cc
index bc3202e..d0bd7fe 100644
--- a/src/starboard/shared/linux/dev_input/dev_input.cc
+++ b/src/starboard/shared/linux/dev_input/dev_input.cc
@@ -24,6 +24,8 @@
#include <vector>
#include <algorithm>
+#include <cmath>
+#include <map>
#include <string>
#include "starboard/configuration.h"
@@ -44,15 +46,44 @@
using ::starboard::shared::starboard::Application;
typedef int FileDescriptor;
-const FileDescriptor kInvalidFd = -1;
-const int kKeyboardDeviceId = 1;
+const FileDescriptor kInvalidFd = -ENODEV;
+
+enum InputDeviceIds {
+ kKeyboardDeviceId = 1,
+ kGamepadDeviceId,
+};
+
+enum TouchPadPositionState {
+ kTouchPadPositionNone = 0,
+ kTouchPadPositionX = 1,
+ kTouchPadPositionY = 2,
+ kTouchPadPositionAll = kTouchPadPositionX | kTouchPadPositionY
+};
+
+struct InputDeviceInfo {
+ InputDeviceInfo() : fd(-1), touchpad_position_state(kTouchPadPositionNone) {}
+
+ // File descriptor open for the device
+ FileDescriptor fd;
+ // Absolute Axis info.
+ std::map<int, struct input_absinfo> axis_info;
+ std::map<int, float> axis_value;
+ int touchpad_position_state;
+};
+
+bool IsTouchpadPositionKnown(InputDeviceInfo* device_info) {
+ return device_info->touchpad_position_state == kTouchPadPositionAll;
+}
// Private implementation of DevInput.
class DevInputImpl : public DevInput {
public:
explicit DevInputImpl(SbWindow window);
+ DevInputImpl(SbWindow window, FileDescriptor wake_up_fd);
~DevInputImpl() SB_OVERRIDE;
+ void InitDevInputImpl(SbWindow window);
+
Event* PollNextSystemEvent() SB_OVERRIDE;
Event* WaitForSystemEventWithTimeout(SbTime time) SB_OVERRIDE;
void WakeSystemEventWait() SB_OVERRIDE;
@@ -61,13 +92,27 @@
// Converts an input_event into a kSbEventInput Application::Event. The caller
// is responsible for deleting the returned event.
Event* InputToApplicationEvent(const struct input_event& event,
+ InputDeviceInfo* device_info,
int modifiers);
+ // Converts an input_event containing a key input into a kSbEventInput
+ // Application::Event. The caller is responsible for deleting the returned
+ // event.
+ Event* KeyInputToApplicationEvent(const struct input_event& event,
+ int modifiers);
+
+ // Converts an input_event containing an axis event into a kSbEventInput
+ // Application::Event. The caller is responsible for deleting the returned
+ // event.
+ Event* AxisInputToApplicationEvent(const struct input_event& event,
+ InputDeviceInfo* device_info,
+ int modifiers);
+
// The window to attribute /dev/input events to.
SbWindow window_;
// A set of read-only file descriptor of keyboard input devices.
- std::vector<FileDescriptor> keyboard_fds_;
+ std::vector<InputDeviceInfo> input_devices_;
// A file descriptor of the write end of a pipe that can be written to from
// any thread to wake up this waiter in a thread-safe manner.
@@ -76,6 +121,8 @@
// A file descriptor of the read end of a pipe that this waiter will wait on
// to allow it to be signalled safely from other threads.
FileDescriptor wakeup_read_fd_;
+
+ FileDescriptor wake_up_fd_;
};
// Helper class to manage a file descriptor set.
@@ -126,16 +173,12 @@
case KEY_PAGEDOWN:
return kSbKeyNext;
case KEY_LEFT:
- case BTN_DPAD_LEFT:
return kSbKeyLeft;
case KEY_RIGHT:
- case BTN_DPAD_RIGHT:
return kSbKeyRight;
case KEY_DOWN:
- case BTN_DPAD_DOWN:
return kSbKeyDown;
case KEY_UP:
- case BTN_DPAD_UP:
return kSbKeyUp;
case KEY_ESC:
return kSbKeyEscape;
@@ -374,6 +417,49 @@
return kSbKeyBrightnessDown;
case KEY_BRIGHTNESSUP:
return kSbKeyBrightnessUp;
+
+ // Gamepad buttons.
+ // https://www.kernel.org/doc/Documentation/input/gamepad.txt
+ case BTN_TL:
+ return kSbKeyGamepadLeftTrigger;
+ case BTN_TR:
+ return kSbKeyGamepadRightTrigger;
+ case BTN_DPAD_DOWN:
+ return kSbKeyGamepadDPadDown;
+ case BTN_DPAD_UP:
+ return kSbKeyGamepadDPadUp;
+ case BTN_DPAD_LEFT:
+ return kSbKeyGamepadDPadLeft;
+ case BTN_DPAD_RIGHT:
+ return kSbKeyGamepadDPadRight;
+ // The mapping for the buttons below can vary from controller to controller.
+ // TODO: Include button mapping for controllers with different layout.
+ case BTN_B:
+ return kSbKeyGamepad1;
+ case BTN_C:
+ return kSbKeyGamepad2;
+ case BTN_A:
+ return kSbKeyGamepad3;
+ case BTN_X:
+ return kSbKeyGamepad4;
+ case BTN_Y:
+ return kSbKeyGamepadLeftBumper;
+ case BTN_Z:
+ return kSbKeyGamepadRightBumper;
+ case BTN_TL2:
+ return kSbKeyGamepad5;
+ case BTN_TR2:
+ return kSbKeyGamepad6;
+ case BTN_SELECT:
+ return kSbKeyGamepadLeftStick;
+ case BTN_START:
+ return kSbKeyGamepadRightStick;
+ case BTN_MODE:
+ return kSbKeyGamepadSystem;
+ case BTN_THUMBL:
+ return kSbKeyGamepad1;
+ case BTN_THUMBR:
+ return kSbKeyGamepad1;
}
SB_DLOG(WARNING) << "Unknown code: 0x" << std::hex << code;
return kSbKeyUnknown;
@@ -408,21 +494,129 @@
return !!(bitset.at(bit / 8) & (1 << (bit % 8)));
}
-// Searches for the keyboard /dev/input devices, opens them and returns the file
-// descriptors that report keyboard events.
-std::vector<FileDescriptor> GetKeyboardFds() {
- const char kDevicePath[] = "/dev/input";
- SbDirectory directory = SbDirectoryOpen(kDevicePath, NULL);
- std::vector<FileDescriptor> fds;
- if (!SbDirectoryIsValid(directory)) {
- SB_DLOG(ERROR) << __FUNCTION__ << ": No /dev/input support, "
- << "unable to open: " << kDevicePath;
- return fds;
+bool IsAxisFlat(float median, const struct input_absinfo& axis_info) {
+ SB_DCHECK((axis_info.flat * 2) <= (axis_info.maximum - axis_info.minimum));
+ return (axis_info.flat != 0) && (axis_info.value > median - axis_info.flat) &&
+ (axis_info.value < median + axis_info.flat);
+}
+
+float GetAxisValue(const struct input_absinfo& axis_info) {
+ float median =
+ static_cast<float>(axis_info.maximum + axis_info.minimum) / 2.0f;
+ if (IsAxisFlat(median, axis_info))
+ return 0;
+ float range = static_cast<float>(axis_info.maximum - axis_info.minimum);
+ float radius = range / 2.0f;
+ // Scale the axis value to [-1, 1].
+ float axis_value = (static_cast<float>(axis_info.value) - median) / radius;
+
+ if (axis_info.flat != 0) {
+ // Calculate the flat value scaled to [0, 1].
+ float flat = static_cast<float>(axis_info.flat) / range;
+
+ int sign = axis_value < 0.0f ? -1 : 1;
+ // Rescale the range:
+ // [-1.0f, -flat] to [-1.0f, 0.0f] and [flat, 1.0f] to [0.0f, 1.0f].
+ axis_value = (axis_value - sign * flat) / (1 - flat);
+ }
+ return axis_value;
+}
+
+void GetInputDeviceAbsoluteAxisInfo(int axis,
+ const std::vector<uint8_t>& bits,
+ InputDeviceInfo* info) {
+ if (IsBitSet(bits, axis)) {
+ struct input_absinfo axis_info;
+ int result = ioctl(info->fd, EVIOCGABS(axis), &axis_info);
+ if (result < 0) {
+ return;
+ }
+ info->axis_info.insert(std::make_pair(axis, axis_info));
+ info->axis_value.insert(std::make_pair(axis, GetAxisValue(axis_info)));
+ }
+}
+
+void GetInputDeviceInfo(InputDeviceInfo* info) {
+ std::vector<uint8_t> axis_bits(BytesNeededForBitSet(KEY_MAX));
+ int result =
+ ioctl(info->fd, EVIOCGBIT(EV_ABS, axis_bits.size()), axis_bits.data());
+ if (result < 0) {
+ return;
+ }
+
+ GetInputDeviceAbsoluteAxisInfo(ABS_X, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_Y, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_Z, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_RZ, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_RX, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_RY, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_HAT0X, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_HAT0Y, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_MT_POSITION_X, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_MT_POSITION_Y, axis_bits, info);
+ GetInputDeviceAbsoluteAxisInfo(ABS_MT_TRACKING_ID, axis_bits, info);
+ // TODO: Handle multi-touch using ABS_MT_SLOT.
+}
+
+FileDescriptor OpenDeviceIfKeyboardOrGamepad(const char* path) {
+ FileDescriptor fd = open(path, O_RDONLY | O_NONBLOCK);
+ if (fd < 0) {
+ // Open can fail if the application doesn't have permission to access
+ // the input device directly.
+ return kInvalidFd;
}
std::vector<uint8_t> ev_bits(BytesNeededForBitSet(EV_CNT));
std::vector<uint8_t> key_bits(BytesNeededForBitSet(KEY_MAX));
+ int result = ioctl(fd, EVIOCGBIT(0, ev_bits.size()), ev_bits.data());
+ if (result < 0) {
+ close(fd);
+ return kInvalidFd;
+ }
+
+ bool has_ev_key = IsBitSet(ev_bits, EV_KEY);
+ if (!has_ev_key) {
+ close(fd);
+ return kInvalidFd;
+ }
+
+ result = ioctl(fd, EVIOCGBIT(EV_KEY, key_bits.size()), key_bits.data());
+ if (result < 0) {
+ close(fd);
+ return kInvalidFd;
+ }
+
+ bool has_key_space = IsBitSet(key_bits, KEY_SPACE);
+ bool has_gamepad_button = IsBitSet(key_bits, BTN_GAMEPAD);
+ if (!has_key_space && !has_gamepad_button) {
+ // If it doesn't have a space key or gamepad button, it may be a mouse.
+ close(fd);
+ return kInvalidFd;
+ }
+
+ result = ioctl(fd, EVIOCGRAB, 1);
+ if (result != 0) {
+ SB_DLOG(ERROR) << __FUNCTION__ << ": "
+ << "Unable to get exclusive access to \"" << path << "\".";
+ close(fd);
+ return kInvalidFd;
+ }
+ return fd;
+}
+
+// Searches for the keyboard and game controller /dev/input devices, opens them
+// and returns the device info with a file descriptor and absolute axis details.
+std::vector<InputDeviceInfo> GetInputDevices() {
+ const char kDevicePath[] = "/dev/input";
+ SbDirectory directory = SbDirectoryOpen(kDevicePath, NULL);
+ std::vector<InputDeviceInfo> input_devices;
+ if (!SbDirectoryIsValid(directory)) {
+ SB_DLOG(ERROR) << __FUNCTION__ << ": No /dev/input support, "
+ << "unable to open: " << kDevicePath;
+ return input_devices;
+ }
+
while (true) {
SbDirectoryEntry entry;
if (!SbDirectoryGetNext(directory, &entry)) {
@@ -438,60 +632,25 @@
continue;
}
- FileDescriptor fd = open(path.c_str(), O_RDONLY | O_NONBLOCK);
- if (fd < 0) {
- SB_DLOG(ERROR) << __FUNCTION__ << ": Unable to open \"" << path << "\".";
+ FileDescriptor fd = OpenDeviceIfKeyboardOrGamepad(path.c_str());
+ if (fd == kInvalidFd) {
continue;
}
+ InputDeviceInfo info;
+ info.fd = fd;
+ GetInputDeviceInfo(&info);
- int result = ioctl(fd, EVIOCGBIT(0, ev_bits.size()), ev_bits.data());
-
- if (result < 0) {
- close(fd);
- continue;
- }
-
- bool has_ev_key = IsBitSet(ev_bits, EV_KEY);
-
- if (!has_ev_key) {
- close(fd);
- continue;
- }
-
- result = ioctl(fd, EVIOCGBIT(EV_KEY, key_bits.size()), key_bits.data());
-
- if (result < 0) {
- close(fd);
- continue;
- }
-
- bool has_key_space = IsBitSet(key_bits, KEY_SPACE);
-
- if (!has_key_space) {
- // If it doesn't have a space key, it may be a mouse
- close(fd);
- continue;
- }
-
- result = ioctl(fd, EVIOCGRAB, 1);
- if (result != 0) {
- SB_DLOG(ERROR) << __FUNCTION__ << ": "
- << "Unable to get exclusive access to \"" << path << "\".";
- close(fd);
- continue;
- }
-
- SB_DCHECK(fd != kInvalidFd);
- fds.push_back(fd);
+ SB_DCHECK(info.fd != kInvalidFd);
+ input_devices.push_back(info);
}
- if (fds.empty()) {
+ if (input_devices.empty()) {
SB_DLOG(ERROR) << __FUNCTION__ << ": No /dev/input support. "
- << "No keyboards available.";
+ << "No keyboards or game controllers available.";
}
SbDirectoryClose(directory);
- return fds;
+ return input_devices;
}
// Returns whether |key_code|'s bit is set in the bitmap |map|, assuming
@@ -513,14 +672,14 @@
return 0;
}
-// Polls the given keyboard file descriptor for an input_event. If there are no
+// Polls the given input file descriptor for an input_event. If there are no
// bytes available, assumes that there is no input event to read. If it gets a
// partial event, it will assume that it will be completed, and spins until it
// receives an entire event.
-bool PollKeyboardEvent(FileDescriptor fd,
- struct input_event* out_event,
- int* out_modifiers) {
- if (fd == kInvalidFd) {
+bool PollInputEvent(InputDeviceInfo* device_info,
+ struct input_event* out_event,
+ int* out_modifiers) {
+ if (device_info->fd == kInvalidFd) {
return false;
}
@@ -531,7 +690,7 @@
size_t remaining = kEventSize;
char* buffer = reinterpret_cast<char*>(out_event);
while (remaining > 0) {
- int bytes_read = read(fd, buffer, remaining);
+ int bytes_read = read(device_info->fd, buffer, remaining);
if (bytes_read <= 0) {
if (errno == EAGAIN || bytes_read == 0) {
if (remaining == kEventSize) {
@@ -544,18 +703,18 @@
}
// Some unexpected type of read error occured.
- SB_DLOG(ERROR) << __FUNCTION__ << ": Error reading keyboard: " << errno
+ SB_DLOG(ERROR) << __FUNCTION__ << ": Error reading input: " << errno
<< " - " << strerror(errno);
return false;
}
- SB_DCHECK(bytes_read <= remaining) << "bytes_read=" << bytes_read
- << ", remaining=" << remaining;
+ SB_DCHECK(bytes_read <= remaining)
+ << "bytes_read=" << bytes_read << ", remaining=" << remaining;
remaining -= bytes_read;
buffer += bytes_read;
}
- if (out_event->type != EV_KEY) {
+ if ((out_event->type != EV_KEY) && (out_event->type != EV_ABS)) {
return false;
}
@@ -563,7 +722,7 @@
int modifiers = 0;
char map[(KEY_MAX / 8) + 1] = {0};
errno = 0;
- int result = ioctl(fd, EVIOCGKEY(sizeof(map)), map);
+ int result = ioctl(device_info->fd, EVIOCGKEY(sizeof(map)), map);
if (result != -1) {
modifiers |=
GetModifier(KEY_LEFTSHIFT, KEY_RIGHTSHIFT, kSbKeyModifiersShift, map);
@@ -593,20 +752,35 @@
// Also in starboard/shared/libevent/socket_waiter_internal.cc
// TODO: Consider consolidating.
-int SetNonBlocking(int fd) {
+int SetNonBlocking(FileDescriptor fd) {
int flags = fcntl(fd, F_GETFL, 0);
- if (flags == -1)
+ if (flags == -1) {
flags = 0;
+ }
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
DevInputImpl::DevInputImpl(SbWindow window)
: window_(window),
- keyboard_fds_(GetKeyboardFds()),
+ input_devices_(GetInputDevices()),
wakeup_write_fd_(kInvalidFd),
- wakeup_read_fd_(kInvalidFd) {
+ wakeup_read_fd_(kInvalidFd),
+ wake_up_fd_(kInvalidFd) {
+ InitDevInputImpl(window);
+}
+
+DevInputImpl::DevInputImpl(SbWindow window, FileDescriptor wake_up_fd)
+ : window_(window),
+ input_devices_(GetInputDevices()),
+ wakeup_write_fd_(kInvalidFd),
+ wakeup_read_fd_(kInvalidFd),
+ wake_up_fd_(wake_up_fd) {
+ InitDevInputImpl(window);
+}
+
+void DevInputImpl::InitDevInputImpl(SbWindow window) {
// Initialize wakeup pipe.
- int fds[2] = {kInvalidFd, kInvalidFd};
+ FileDescriptor fds[2] = {kInvalidFd, kInvalidFd};
int result = pipe(fds);
SB_DCHECK(result == 0) << "result=" << result;
@@ -622,9 +796,8 @@
}
DevInputImpl::~DevInputImpl() {
- for (std::vector<FileDescriptor>::const_iterator it = keyboard_fds_.begin();
- it != keyboard_fds_.end(); ++it) {
- close(*it);
+ for (const auto& device : input_devices_) {
+ close(device.fd);
}
CloseFdSafely(&wakeup_write_fd_);
CloseFdSafely(&wakeup_read_fd_);
@@ -633,13 +806,12 @@
DevInput::Event* DevInputImpl::PollNextSystemEvent() {
struct input_event event;
int modifiers = 0;
- for (std::vector<FileDescriptor>::const_iterator it = keyboard_fds_.begin();
- it != keyboard_fds_.end(); ++it) {
- if (!PollKeyboardEvent(*it, &event, &modifiers)) {
+ for (auto& device : input_devices_) {
+ if (!PollInputEvent(&device, &event, &modifiers)) {
continue;
}
- return InputToApplicationEvent(event, modifiers);
+ return InputToApplicationEvent(event, &device, modifiers);
}
return NULL;
}
@@ -651,9 +823,13 @@
}
FdSet read_set;
- for (std::vector<FileDescriptor>::const_iterator it = keyboard_fds_.begin();
- it != keyboard_fds_.end(); ++it) {
- read_set.Set(*it);
+ if (wake_up_fd_ != kInvalidFd) {
+ read_set.Set(wake_up_fd_);
+ }
+
+ for (std::vector<InputDeviceInfo>::const_iterator it = input_devices_.begin();
+ it != input_devices_.end(); ++it) {
+ read_set.Set(it->fd);
}
read_set.Set(wakeup_read_fd_);
@@ -693,13 +869,256 @@
}
}
-DevInput::Event* DevInputImpl::InputToApplicationEvent(
- const struct input_event& event,
- int modifiers) {
- if (event.type != EV_KEY) {
+namespace {
+
+// Creates a key event for an analog button input.
+DevInput::Event* CreateAnalogButtonKeyEvent(SbWindow window,
+ float axis_value,
+ float previous_axis_value,
+ SbKey key,
+ SbKeyLocation location,
+ int modifiers,
+ const struct input_event& event) {
+ SbInputEventType previous_type =
+ (std::abs(previous_axis_value) > 0.5 ? kSbInputEventTypePress
+ : kSbInputEventTypeUnpress);
+ SbInputEventType type =
+ (std::abs(axis_value) > 0.5 ? kSbInputEventTypePress
+ : kSbInputEventTypeUnpress);
+ if (previous_type == type) {
+ // Key press/unpress state did not change.
return NULL;
}
+ SbInputData* data = new SbInputData();
+ SbMemorySet(data, 0, sizeof(*data));
+ data->window = window;
+ data->type = type;
+ data->device_type = kSbInputDeviceTypeGamepad;
+ data->device_id = kGamepadDeviceId;
+ data->key = key;
+ data->key_location = location;
+ data->key_modifiers = modifiers;
+ return new DevInput::Event(kSbEventTypeInput, data,
+ &Application::DeleteDestructor<SbInputData>);
+}
+
+// Creates a move event with key for a stick input.
+DevInput::Event* CreateMoveEventWithKey(SbWindow window,
+ SbKey key,
+ SbKeyLocation location,
+ int modifiers,
+ const SbInputVector& input_vector) {
+ SbInputData* data = new SbInputData();
+ SbMemorySet(data, 0, sizeof(*data));
+
+ data->window = window;
+ data->type = kSbInputEventTypeMove;
+ data->device_type = kSbInputDeviceTypeGamepad;
+ data->device_id = kGamepadDeviceId;
+
+ data->key = key;
+ data->key_location = location;
+ data->key_modifiers = modifiers;
+ data->position = input_vector;
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+ data->pressure = NAN;
+ data->size = {NAN, NAN};
+ data->tilt = {NAN, NAN};
+#endif
+
+ return new DevInput::Event(kSbEventTypeInput, data,
+ &Application::DeleteDestructor<SbInputData>);
+}
+
+DevInput::Event* CreateTouchPadEvent(SbWindow window,
+ SbInputEventType type,
+ SbKey key,
+ SbKeyLocation location,
+ int modifiers,
+ const SbInputVector& input_vector) {
+ SbInputData* data = new SbInputData();
+ SbMemorySet(data, 0, sizeof(*data));
+
+ data->window = window;
+ data->type = type;
+ data->device_type = kSbInputDeviceTypeTouchPad;
+ data->device_id = kGamepadDeviceId;
+
+ data->key = key;
+ data->key_location = location;
+ data->key_modifiers = modifiers;
+ data->position = input_vector;
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+ data->pressure = NAN;
+ data->size = {NAN, NAN};
+ data->tilt = {NAN, NAN};
+#endif
+
+ return new DevInput::Event(kSbEventTypeInput, data,
+ &Application::DeleteDestructor<SbInputData>);
+}
+
+} // namespace
+
+DevInput::Event* DevInputImpl::AxisInputToApplicationEvent(
+ const struct input_event& event,
+ InputDeviceInfo* device_info,
+ int modifiers) {
+ SB_DCHECK(event.type == EV_ABS);
+ SbKey key = kSbKeyUnknown;
+ float axis_value = 0;
+ float previous_axis_value = 0;
+ auto axis_info_it = device_info->axis_info.find(event.code);
+ if (axis_info_it != device_info->axis_info.end()) {
+ struct input_absinfo& axis_info = axis_info_it->second;
+ axis_info.value = event.value;
+ axis_value = GetAxisValue(axis_info);
+ float& stored_axis_value = device_info->axis_value[event.code];
+ previous_axis_value = stored_axis_value;
+ if (previous_axis_value == axis_value) {
+ // If the value is unchanged, don't do anything.
+ return NULL;
+ }
+ stored_axis_value = axis_value;
+ }
+
+ SbKeyLocation location = kSbKeyLocationUnspecified;
+ SbInputVector input_vector;
+ // The mapping for the axis codes can vary from controller to controller.
+ // TODO: Include axis mapping for controllers with different layout.
+ switch (event.code) {
+ case ABS_X:
+ // Report up and left as positive values.
+ input_vector.x = -axis_value;
+ input_vector.y = -device_info->axis_value[ABS_Y];
+ key = kSbKeyGamepadLeftStickLeft;
+ location = kSbKeyLocationLeft;
+ return CreateMoveEventWithKey(window_, key, location, modifiers,
+ input_vector);
+ case ABS_Y: {
+ // Report up and left as positive values.
+ input_vector.x = -device_info->axis_value[ABS_X];
+ input_vector.y = -axis_value;
+ key = kSbKeyGamepadLeftStickUp;
+ location = kSbKeyLocationLeft;
+ return CreateMoveEventWithKey(window_, key, location, modifiers,
+ input_vector);
+ }
+ case ABS_Z:
+ // Report up and left as positive values.
+ input_vector.x = -axis_value;
+ input_vector.y = -device_info->axis_value[ABS_RZ];
+ key = kSbKeyGamepadRightStickLeft;
+ location = kSbKeyLocationRight;
+ return CreateMoveEventWithKey(window_, key, location, modifiers,
+ input_vector);
+ case ABS_RZ:
+ // Report up and left as positive values.
+ input_vector.x = -device_info->axis_value[ABS_Z];
+ input_vector.y = -axis_value;
+ key = kSbKeyGamepadRightStickUp;
+ location = kSbKeyLocationRight;
+ return CreateMoveEventWithKey(window_, key, location, modifiers,
+ input_vector);
+ case ABS_RX: {
+ key = kSbKeyGamepadLeftTrigger;
+ location = kSbKeyLocationLeft;
+ // For trigger buttons, the range is [0..1].
+ float trigger_value = (axis_value + 1) / 2;
+ float previous_trigger_value = (previous_axis_value + 1) / 2;
+ return CreateAnalogButtonKeyEvent(window_, trigger_value,
+ previous_trigger_value, key, location,
+ modifiers, event);
+ }
+ case ABS_RY: {
+ key = kSbKeyGamepadRightTrigger;
+ location = kSbKeyLocationRight;
+ // For trigger buttons, the range is [0..1].
+ float trigger_value = (axis_value + 1) / 2;
+ float previous_trigger_value = (previous_axis_value + 1) / 2;
+ return CreateAnalogButtonKeyEvent(window_, trigger_value,
+ previous_trigger_value, key, location,
+ modifiers, event);
+ }
+ case ABS_HAT0X: {
+ float axis_value_for_key =
+ std::abs(axis_value) > 0.5f ? axis_value : previous_axis_value;
+ key = (axis_value_for_key < 0) ? kSbKeyGamepadDPadLeft
+ : kSbKeyGamepadDPadRight;
+ return CreateAnalogButtonKeyEvent(window_, axis_value,
+ previous_axis_value, key, location,
+ modifiers, event);
+ }
+ case ABS_HAT0Y: {
+ float axis_value_for_key =
+ std::abs(axis_value) > 0.5f ? axis_value : previous_axis_value;
+ key = (axis_value_for_key < 0) ? kSbKeyGamepadDPadUp
+ : kSbKeyGamepadDPadDown;
+ return CreateAnalogButtonKeyEvent(window_, axis_value,
+ previous_axis_value, key, location,
+ modifiers, event);
+ }
+ case ABS_MT_TRACKING_ID:
+ if (event.value == -1) {
+ bool touchpad_position_is_known = IsTouchpadPositionKnown(device_info);
+ device_info->touchpad_position_state = kTouchPadPositionNone;
+ if (touchpad_position_is_known) {
+ // Touch point is released, report last known position as unpress.
+ input_vector.x = (device_info->axis_value[ABS_MT_POSITION_X] + 1 / 2);
+ input_vector.y = (device_info->axis_value[ABS_MT_POSITION_Y] + 1 / 2);
+ return CreateTouchPadEvent(window_, kSbInputEventTypeUnpress, key,
+ location, modifiers, input_vector);
+ }
+ }
+ return NULL;
+ case ABS_MT_POSITION_X: {
+ // If all positions were known before this event, then this event is a
+ // move.
+ SbInputEventType type = IsTouchpadPositionKnown(device_info)
+ ? kSbInputEventTypeMove
+ : kSbInputEventTypePress;
+ device_info->touchpad_position_state |= kTouchPadPositionX;
+ if (IsTouchpadPositionKnown(device_info)) {
+ // For touchpads, the range is [0..1].
+ input_vector.x = (axis_value + 1) / 2;
+ input_vector.y = (device_info->axis_value[ABS_MT_POSITION_Y] + 1) / 2;
+ return CreateTouchPadEvent(window_, type, key, location, modifiers,
+ input_vector);
+ }
+ // Not all axis positions are known yet.
+ return NULL;
+ }
+ case ABS_MT_POSITION_Y: {
+ // If all positions were known before this event, then this event is a
+ // move.
+ SbInputEventType type = IsTouchpadPositionKnown(device_info)
+ ? kSbInputEventTypeMove
+ : kSbInputEventTypePress;
+ device_info->touchpad_position_state |= kTouchPadPositionY;
+ if (IsTouchpadPositionKnown(device_info)) {
+ // For touchpads, the range is [0..1].
+ input_vector.x = (device_info->axis_value[ABS_MT_POSITION_X] + 1) / 2;
+ input_vector.y = (axis_value + 1) / 2;
+ return CreateTouchPadEvent(window_, type, key, location, modifiers,
+ input_vector);
+ }
+ // Not all axis positions are known yet.
+ return NULL;
+ }
+ default:
+ // Ignored event codes.
+ return NULL;
+ }
+
+ SB_NOTREACHED();
+ return NULL;
+}
+
+DevInput::Event* DevInputImpl::KeyInputToApplicationEvent(
+ const struct input_event& event,
+ int modifiers) {
+ SB_DCHECK(event.type == EV_KEY);
SB_DCHECK(event.value <= 2);
SbInputData* data = new SbInputData();
SbMemorySet(data, 0, sizeof(*data));
@@ -715,6 +1134,21 @@
&Application::DeleteDestructor<SbInputData>);
}
+DevInput::Event* DevInputImpl::InputToApplicationEvent(
+ const struct input_event& event,
+ InputDeviceInfo* device_info,
+ int modifiers) {
+ // EV_ABS events are axis values: Sticks, dpad, and touchpad.
+ // https://www.kernel.org/doc/Documentation/input/event-codes.txt
+ switch (event.type) {
+ case EV_ABS:
+ return AxisInputToApplicationEvent(event, device_info, modifiers);
+ case EV_KEY:
+ return KeyInputToApplicationEvent(event, modifiers);
+ }
+ return NULL;
+}
+
} // namespace
// static
@@ -722,6 +1156,11 @@
return new DevInputImpl(window);
}
+// static
+DevInput* DevInput::Create(SbWindow window, int wake_up_fd) {
+ return new DevInputImpl(window, wake_up_fd);
+}
+
} // namespace dev_input
} // namespace shared
} // namespace starboard
diff --git a/src/starboard/shared/linux/dev_input/dev_input.h b/src/starboard/shared/linux/dev_input/dev_input.h
index aa8577a..11b94c4 100644
--- a/src/starboard/shared/linux/dev_input/dev_input.h
+++ b/src/starboard/shared/linux/dev_input/dev_input.h
@@ -48,6 +48,11 @@
// Creates an instance of DevInput for the given window.
static DevInput* Create(SbWindow window);
+ // Creates an instance of DevInput for the given window.
+ // The wake_up_fd will be used in WaitForSystemEventWithTimeout() to return
+ // early when input is available on it.
+ static DevInput* Create(SbWindow window, int wake_up_fd);
+
protected:
DevInput() {}
};
diff --git a/src/starboard/shared/msvc/uwp/toolchain.py b/src/starboard/shared/msvc/uwp/msvc_toolchain.py
similarity index 100%
rename from src/starboard/shared/msvc/uwp/toolchain.py
rename to src/starboard/shared/msvc/uwp/msvc_toolchain.py
diff --git a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
index cf40ad5..4aaeef1 100644
--- a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
+++ b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
@@ -42,6 +42,9 @@
#if SB_API_VERSION >= 4
virtual void SetPlaybackRate(double playback_rate) = 0;
#endif // SB_API_VERSION >= 4
+
+ virtual void SetVolume(double volume) = 0;
+
virtual bool IsType(Type* type) = 0;
// The following two functions will be called during application startup and
diff --git a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
index 597820a..f6ccd09 100644
--- a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
+++ b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
@@ -44,6 +44,10 @@
}
#endif // SB_API_VERSION >= 4
+ void SetVolume(double volume) SB_OVERRIDE {
+ SB_UNREFERENCED_PARAMETER(volume);
+ }
+
private:
static void* ThreadEntryPoint(void* context);
void AudioThreadFunc();
diff --git a/src/starboard/shared/starboard/file_storage/storage_delete_record.cc b/src/starboard/shared/starboard/file_storage/storage_delete_record.cc
index 891fbbb..c5c7fe3 100644
--- a/src/starboard/shared/starboard/file_storage/storage_delete_record.cc
+++ b/src/starboard/shared/starboard/file_storage/storage_delete_record.cc
@@ -18,14 +18,23 @@
#include "starboard/shared/starboard/file_storage/storage_internal.h"
#include "starboard/user.h"
-bool SbStorageDeleteRecord(SbUser user) {
+bool SbStorageDeleteRecord(SbUser user
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ ,
+ const char* name
+#endif // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ ) {
if (!SbUserIsValid(user)) {
return false;
}
+#if SB_API_VERSION < SB_STORAGE_NAMES_API_VERSION
+ const char* name = NULL;
+#endif // SB_API_VERSION < SB_STORAGE_NAMES_API_VERSION
+
char path[SB_FILE_MAX_PATH];
bool success = starboard::shared::starboard::GetUserStorageFilePath(
- user, path, SB_ARRAY_SIZE_INT(path));
+ user, name, path, SB_ARRAY_SIZE_INT(path));
if (!success) {
return false;
}
diff --git a/src/starboard/shared/starboard/file_storage/storage_internal.h b/src/starboard/shared/starboard/file_storage/storage_internal.h
index 6bb3362..a56cd34 100644
--- a/src/starboard/shared/starboard/file_storage/storage_internal.h
+++ b/src/starboard/shared/starboard/file_storage/storage_internal.h
@@ -18,6 +18,8 @@
#ifndef STARBOARD_SHARED_STARBOARD_FILE_STORAGE_STORAGE_INTERNAL_H_
#define STARBOARD_SHARED_STARBOARD_FILE_STORAGE_STORAGE_INTERNAL_H_
+#include <string>
+
#include "starboard/file.h"
#include "starboard/shared/internal_only.h"
#include "starboard/storage.h"
@@ -27,6 +29,9 @@
struct SbStorageRecordPrivate {
SbUser user;
SbFile file;
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ std::string name;
+#endif // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
};
namespace starboard {
@@ -34,6 +39,7 @@
namespace starboard {
// Gets the path to the storage file for the given user.
static SB_C_INLINE bool GetUserStorageFilePath(SbUser user,
+ const char* name,
char* out_path,
int path_size) {
bool success = SbUserGetProperty(user, kSbUserPropertyHomeDirectory, out_path,
@@ -42,7 +48,12 @@
return false;
}
- SbStringConcat(out_path, "/.starboard.storage", path_size);
+ SbStringConcat(out_path, "/.starboard", path_size);
+ if (name && SbStringGetLength(name) > 0) {
+ SbStringConcat(out_path, ".", path_size);
+ SbStringConcat(out_path, name, path_size);
+ }
+ SbStringConcat(out_path, ".storage", path_size);
return true;
}
} // namespace starboard
diff --git a/src/starboard/shared/starboard/file_storage/storage_open_record.cc b/src/starboard/shared/starboard/file_storage/storage_open_record.cc
index 5be40f4..aaea393 100644
--- a/src/starboard/shared/starboard/file_storage/storage_open_record.cc
+++ b/src/starboard/shared/starboard/file_storage/storage_open_record.cc
@@ -19,14 +19,23 @@
#include "starboard/shared/starboard/file_storage/storage_internal.h"
#include "starboard/user.h"
-SbStorageRecord SbStorageOpenRecord(SbUser user) {
+SbStorageRecord SbStorageOpenRecord(SbUser user
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ ,
+ const char* name
+#endif // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ ) {
if (!SbUserIsValid(user)) {
return kSbStorageInvalidRecord;
}
+#if SB_API_VERSION < SB_STORAGE_NAMES_API_VERSION
+ const char* name = NULL;
+#endif // SB_API_VERSION < SB_STORAGE_NAMES_API_VERSION
+
char path[SB_FILE_MAX_PATH];
bool success = starboard::shared::starboard::GetUserStorageFilePath(
- user, path, SB_ARRAY_SIZE_INT(path));
+ user, name, path, SB_ARRAY_SIZE_INT(path));
if (!success) {
return kSbStorageInvalidRecord;
}
@@ -43,5 +52,10 @@
SB_DCHECK(SbStorageIsValidRecord(result));
result->user = user;
result->file = file;
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ if (name) {
+ result->name = name;
+ }
+#endif
return result;
}
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
index dbaa49e..9e63967 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
@@ -71,6 +71,7 @@
sink_sample_type_(GetSinkAudioSampleType()),
bytes_per_frame_(media::GetBytesPerSample(sink_sample_type_) * channels_),
playback_rate_(1.0),
+ volume_(1.0),
paused_(true),
seeking_(false),
seeking_to_pts_(0),
@@ -178,6 +179,14 @@
}
#endif // SB_API_VERSION >= 4
+void AudioRendererImpl::SetVolume(double volume) {
+ SB_DCHECK(BelongsToCurrentThread());
+ volume_ = volume;
+ if (audio_sink_) {
+ audio_sink_->SetVolume(volume_);
+ }
+}
+
void AudioRendererImpl::Seek(SbMediaTime seek_to_pts) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(seek_to_pts >= 0);
@@ -287,6 +296,7 @@
// support play/pause.
audio_sink_->SetPlaybackRate(playback_rate_ > 0.0 ? 1.0 : 0.0);
#endif // SB_API_VERSION >= 4
+ audio_sink_->SetVolume(volume_);
}
void AudioRendererImpl::UpdateSourceStatus(int* frames_in_buffer,
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
index d9278da..a469037 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
@@ -60,6 +60,7 @@
#if SB_API_VERSION >= 4
void SetPlaybackRate(double playback_rate) SB_OVERRIDE;
#endif // SB_API_VERSION >= 4
+ void SetVolume(double volume) SB_OVERRIDE;
void Seek(SbMediaTime seek_to_pts) SB_OVERRIDE;
bool IsEndOfStreamWritten() const SB_OVERRIDE {
@@ -117,6 +118,7 @@
scoped_ptr<AudioResampler> resampler_;
AudioTimeStretcher time_stretcher_;
double playback_rate_;
+ double volume_;
atomic_bool paused_;
atomic_bool seeking_;
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
index 1c192c1..a6ca64b 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
@@ -38,6 +38,7 @@
#if SB_API_VERSION >= 4
virtual void SetPlaybackRate(double playback_rate) = 0;
#endif // SB_API_VERSION >= 4
+ virtual void SetVolume(double volume) = 0;
virtual void Seek(SbMediaTime seek_to_pts) = 0;
virtual bool IsEndOfStreamWritten() const = 0;
virtual bool IsEndOfStreamPlayed() const = 0;
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
index 76b6e53..c373d90 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -62,7 +62,11 @@
audio_codec_(audio_codec),
drm_system_(drm_system),
audio_header_(audio_header),
- paused_(false)
+ paused_(false),
+#if SB_API_VERSION >= 4
+ playback_rate_(1.0),
+#endif // SB_API_VERSION >= 4
+ volume_(1.0)
#if SB_API_VERSION >= 4
,
output_mode_(output_mode),
@@ -87,12 +91,12 @@
bounds_ = PlayerWorker::Bounds();
}
-bool FilterBasedPlayerWorkerHandler::IsPunchoutMode() const {
-#if SB_API_VERSION >= 4
- return (output_mode_ == kSbPlayerOutputModePunchOut);
-#else
- return true;
-#endif // SB_API_VERSION >= 4
+bool FilterBasedPlayerWorkerHandler::IsPunchoutMode() const {
+#if SB_API_VERSION >= 4
+ return (output_mode_ == kSbPlayerOutputModePunchOut);
+#else
+ return true;
+#endif // SB_API_VERSION >= 4
}
bool FilterBasedPlayerWorkerHandler::Init(
@@ -144,6 +148,11 @@
::starboard::ScopedLock lock(video_renderer_existence_mutex_);
media_components->GetRenderers(&audio_renderer_, &video_renderer_);
+#if SB_API_VERSION >= 4
+ audio_renderer_->SetPlaybackRate(playback_rate_);
+#endif // SB_API_VERSION >= 4
+ audio_renderer_->SetVolume(volume_);
+
job_queue_->Schedule(update_closure_, kUpdateInterval);
return true;
@@ -276,15 +285,26 @@
bool FilterBasedPlayerWorkerHandler::SetPlaybackRate(double playback_rate) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
+ playback_rate_ = playback_rate;
+
if (!audio_renderer_) {
return false;
}
- audio_renderer_->SetPlaybackRate(playback_rate);
+ audio_renderer_->SetPlaybackRate(playback_rate_);
return true;
}
#endif // SB_API_VERSION >= 4
+void FilterBasedPlayerWorkerHandler::SetVolume(double volume) {
+ SB_DCHECK(job_queue_->BelongsToCurrentThread());
+
+ volume_ = volume;
+ if (audio_renderer_) {
+ audio_renderer_->SetVolume(volume_);
+ }
+}
+
bool FilterBasedPlayerWorkerHandler::SetBounds(
const PlayerWorker::Bounds& bounds) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
@@ -328,9 +348,9 @@
player_worker_->UpdateDroppedVideoFrames(
video_renderer_->GetDroppedFrames());
- if (IsPunchoutMode()) {
- shared::starboard::Application::Get()->HandleFrame(
- player_, frame, bounds_.x, bounds_.y, bounds_.width, bounds_.height);
+ if (IsPunchoutMode()) {
+ shared::starboard::Application::Get()->HandleFrame(
+ player_, frame, bounds_.x, bounds_.y, bounds_.width, bounds_.height);
}
(*player_worker_.*update_media_time_cb_)(audio_renderer_->GetCurrentTime());
@@ -355,10 +375,10 @@
}
video_renderer.reset();
- if (IsPunchoutMode()) {
- // Clear the video frame as we terminate.
- shared::starboard::Application::Get()->HandleFrame(
- player_, VideoFrame::CreateEOSFrame(), 0, 0, 0, 0);
+ if (IsPunchoutMode()) {
+ // Clear the video frame as we terminate.
+ shared::starboard::Application::Get()->HandleFrame(
+ player_, VideoFrame::CreateEOSFrame(), 0, 0, 0, 0);
}
}
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
index 8715ab9..20655ca 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
@@ -68,6 +68,7 @@
#if SB_API_VERSION >= 4
bool SetPlaybackRate(double playback_rate) SB_OVERRIDE;
#endif // SB_API_VERSION >= 4
+ void SetVolume(double volume) SB_OVERRIDE;
bool SetBounds(const PlayerWorker::Bounds& bounds) SB_OVERRIDE;
void Stop() SB_OVERRIDE;
@@ -98,6 +99,10 @@
scoped_ptr<VideoRenderer> video_renderer_;
bool paused_;
+#if SB_API_VERSION >= 4
+ double playback_rate_;
+#endif // SB_API_VERSION >= 4
+ double volume_;
PlayerWorker::Bounds bounds_;
Closure update_closure_;
diff --git a/src/starboard/shared/starboard/player/player_internal.cc b/src/starboard/shared/starboard/player/player_internal.cc
index 20268ea..3b46a5d 100644
--- a/src/starboard/shared/starboard/player/player_internal.cc
+++ b/src/starboard/shared/starboard/player/player_internal.cc
@@ -133,8 +133,8 @@
#endif // SB_API_VERSION >= 4
void SbPlayerPrivate::SetVolume(double volume) {
- SB_UNREFERENCED_PARAMETER(volume);
- SB_NOTIMPLEMENTED();
+ volume_ = volume;
+ worker_->SetVolume(volume_);
}
void SbPlayerPrivate::UpdateMediaTime(SbMediaTime media_time, int ticket) {
diff --git a/src/starboard/shared/starboard/player/player_worker.cc b/src/starboard/shared/starboard/player/player_worker.cc
index 38a0a25..24e834c 100644
--- a/src/starboard/shared/starboard/player/player_worker.cc
+++ b/src/starboard/shared/starboard/player/player_worker.cc
@@ -270,6 +270,11 @@
}
#endif // SB_API_VERSION >= 4
+void PlayerWorker::DoSetVolume(double volume) {
+ SB_DCHECK(job_queue_->BelongsToCurrentThread());
+ handler_->SetVolume(volume);
+}
+
void PlayerWorker::DoStop() {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
diff --git a/src/starboard/shared/starboard/player/player_worker.h b/src/starboard/shared/starboard/player/player_worker.h
index a1d9333..84be839 100644
--- a/src/starboard/shared/starboard/player/player_worker.h
+++ b/src/starboard/shared/starboard/player/player_worker.h
@@ -83,6 +83,8 @@
#if SB_API_VERSION >= 4
virtual bool SetPlaybackRate(double playback_rate) = 0;
#endif // SB_API_VERSION >= 4
+ virtual void SetVolume(double volume) = 0;
+
virtual bool SetBounds(const Bounds& bounds) = 0;
// Once this function returns, all processing on the Handler and related
@@ -147,6 +149,10 @@
}
#endif // SB_API_VERSION >= 4
+ void SetVolume(double volume) {
+ job_queue_->Schedule(Bind(&PlayerWorker::DoSetVolume, this, volume));
+ }
+
void UpdateDroppedVideoFrames(int dropped_video_frames) {
host_->UpdateDroppedVideoFrames(dropped_video_frames);
}
@@ -177,6 +183,7 @@
#if SB_API_VERSION >= 4
void DoSetPlaybackRate(double rate);
#endif // SB_API_VERSION >= 4
+ void DoSetVolume(double volume);
void DoStop();
void UpdateDecoderState(SbMediaType type, SbPlayerDecoderState state);
diff --git a/src/starboard/shared/stub/drm_create_system.cc b/src/starboard/shared/stub/drm_create_system.cc
index a9a1c84..64139a9 100644
--- a/src/starboard/shared/stub/drm_create_system.cc
+++ b/src/starboard/shared/stub/drm_create_system.cc
@@ -14,6 +14,24 @@
#include "starboard/drm.h"
+#if SB_API_VERSION >= SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION
+
+SbDrmSystem SbDrmCreateSystem(
+ const char* key_system,
+ void* context,
+ SbDrmSessionUpdateRequestFunc update_request_callback,
+ SbDrmSessionUpdatedFunc session_updated_callback,
+ SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback) {
+ SB_UNREFERENCED_PARAMETER(context);
+ SB_UNREFERENCED_PARAMETER(key_system);
+ SB_UNREFERENCED_PARAMETER(update_request_callback);
+ SB_UNREFERENCED_PARAMETER(session_updated_callback);
+ SB_UNREFERENCED_PARAMETER(key_statuses_changed_callback);
+ return kSbDrmSystemInvalid;
+}
+
+#else // SB_API_VERSION >= SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION
+
SbDrmSystem SbDrmCreateSystem(
const char* key_system,
void* context,
@@ -25,3 +43,5 @@
SB_UNREFERENCED_PARAMETER(session_updated_callback);
return kSbDrmSystemInvalid;
}
+
+#endif // SB_API_VERSION >= SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION
diff --git a/src/starboard/shared/stub/storage_delete_record.cc b/src/starboard/shared/stub/storage_delete_record.cc
index ba13cd1..a669054 100644
--- a/src/starboard/shared/stub/storage_delete_record.cc
+++ b/src/starboard/shared/stub/storage_delete_record.cc
@@ -14,6 +14,11 @@
#include "starboard/storage.h"
-bool SbStorageDeleteRecord(SbUser /*user*/) {
+bool SbStorageDeleteRecord(SbUser /*user*/
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ ,
+ const char* /*name*/
+#endif // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ ) {
return false;
}
diff --git a/src/starboard/shared/stub/storage_open_record.cc b/src/starboard/shared/stub/storage_open_record.cc
index bcd5f3d..b0f503e 100644
--- a/src/starboard/shared/stub/storage_open_record.cc
+++ b/src/starboard/shared/stub/storage_open_record.cc
@@ -14,6 +14,11 @@
#include "starboard/storage.h"
-SbStorageRecord SbStorageOpenRecord(SbUser /*user*/) {
+SbStorageRecord SbStorageOpenRecord(SbUser /*user*/
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ ,
+ const char* /*name*/
+#endif // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+ ) {
return kSbStorageInvalidRecord;
}
diff --git a/src/starboard/shared/test_webapi_extension/my_new_enum.idl b/src/starboard/shared/test_webapi_extension/my_new_enum.idl
new file mode 100644
index 0000000..1a6f73c
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/my_new_enum.idl
@@ -0,0 +1,19 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+enum MyNewEnum {
+ "apples",
+ "oranges",
+ "peaches"
+};
diff --git a/src/starboard/shared/test_webapi_extension/my_new_interface.cc b/src/starboard/shared/test_webapi_extension/my_new_interface.cc
new file mode 100644
index 0000000..18af5b4
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/my_new_interface.cc
@@ -0,0 +1,29 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/test_webapi_extension/my_new_interface.h"
+
+namespace cobalt {
+namespace webapi_extension {
+
+MyNewInterface::MyNewInterface(const scoped_refptr<dom::Window>& window) {
+ UNREFERENCED_PARAMETER(window);
+ // Provide an initial value for the enum.
+ enum_value_ = kMyNewEnumApples;
+}
+
+MyNewInterface::~MyNewInterface() OVERRIDE {}
+
+} // namespace webapi_extension
+} // namespace cobalt
diff --git a/src/starboard/shared/test_webapi_extension/my_new_interface.h b/src/starboard/shared/test_webapi_extension/my_new_interface.h
new file mode 100644
index 0000000..eb30d4e
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/my_new_interface.h
@@ -0,0 +1,55 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_TEST_WEBAPI_EXTENSION_MY_NEW_INTERFACE_H_
+#define STARBOARD_SHARED_TEST_WEBAPI_EXTENSION_MY_NEW_INTERFACE_H_
+
+#include <string>
+
+#include "cobalt/dom/window.h"
+#include "cobalt/script/wrappable.h"
+#include "starboard/shared/test_webapi_extension/my_new_enum.h"
+
+namespace cobalt {
+namespace webapi_extension {
+
+class MyNewInterface : public script::Wrappable {
+ public:
+ explicit MyNewInterface(const scoped_refptr<dom::Window>& window);
+
+ const std::string& foo() const { return foo_; }
+ void set_foo(const std::string& value) { foo_ = value; }
+
+ void SetMyNewEnum(MyNewEnum value) { enum_value_ = value; }
+ MyNewEnum GetMyNewEnum() const { return enum_value_; }
+
+ // All types derived from script::Wrappable must have this annotation.
+ DEFINE_WRAPPABLE_TYPE(MyNewInterface);
+
+ private:
+ // Since script::Wrappable inherits from base::RefCounted<>, we make the
+ // destructor private.
+ ~MyNewInterface() OVERRIDE;
+
+ std::string foo_;
+
+ MyNewEnum enum_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(MyNewInterface);
+};
+
+} // namespace webapi_extension
+} // namespace cobalt
+
+#endif // STARBOARD_SHARED_TEST_WEBAPI_EXTENSION_MY_NEW_INTERFACE_H_
diff --git a/src/starboard/shared/test_webapi_extension/my_new_interface.idl b/src/starboard/shared/test_webapi_extension/my_new_interface.idl
new file mode 100644
index 0000000..3e64764
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/my_new_interface.idl
@@ -0,0 +1,20 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+interface MyNewInterface {
+ attribute DOMString foo;
+
+ void SetMyNewEnum(MyNewEnum value);
+ MyNewEnum GetMyNewEnum();
+};
diff --git a/src/starboard/shared/test_webapi_extension/test_webapi_extension.gypi b/src/starboard/shared/test_webapi_extension/test_webapi_extension.gypi
new file mode 100644
index 0000000..74de31a
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/test_webapi_extension.gypi
@@ -0,0 +1,29 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This file can be included from a platform's gyp_configuration.gypi file to
+# setup the test custom webapi_extension defined in this directory to be
+# injected into that platform.
+{
+ 'variables': {
+ 'cobalt_webapi_extension_source_idl_files': [
+ 'my_new_interface.idl'
+ ],
+ 'cobalt_webapi_extension_generated_header_idl_files': [
+ 'my_new_enum.idl'
+ ],
+ 'cobalt_webapi_extension_gyp_target':
+ '<(DEPTH)/starboard/shared/test_webapi_extension/webapi_extension.gyp:cobalt_test_webapi_extension',
+ },
+}
\ No newline at end of file
diff --git a/src/starboard/shared/test_webapi_extension/webapi_extension.cc b/src/starboard/shared/test_webapi_extension/webapi_extension.cc
new file mode 100644
index 0000000..06bb89c
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/webapi_extension.cc
@@ -0,0 +1,38 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/browser/webapi_extension.h"
+
+#include "base/compiler_specific.h"
+#include "cobalt/script/global_environment.h"
+#include "starboard/shared/test_webapi_extension/my_new_interface.h"
+
+namespace cobalt {
+namespace browser {
+
+base::optional<std::string> GetWebAPIExtensionObjectPropertyName() {
+ return std::string("myInterface");
+}
+
+scoped_refptr<script::Wrappable> CreateWebAPIExtensionObject(
+ const scoped_refptr<dom::Window>& window,
+ script::GlobalEnvironment* global_environment) {
+ UNREFERENCED_PARAMETER(global_environment);
+
+ return scoped_refptr<script::Wrappable>(
+ new webapi_extension::MyNewInterface(window));
+}
+
+} // namespace browser
+} // namespace cobalt
diff --git a/src/starboard/shared/test_webapi_extension/webapi_extension.gyp b/src/starboard/shared/test_webapi_extension/webapi_extension.gyp
new file mode 100644
index 0000000..5c238e0
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/webapi_extension.gyp
@@ -0,0 +1,35 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+{
+ 'targets': [
+ {
+ 'target_name': 'cobalt_test_webapi_extension',
+ 'type': 'static_library',
+
+ # List of all source files and header files needed to support the IDL
+ # definitions.
+ 'sources': [
+ 'my_new_interface.h',
+ 'my_new_interface.cc',
+ 'webapi_extension.cc',
+ ],
+
+ 'dependencies': [
+ '<(DEPTH)/cobalt/dom/dom.gyp:dom',
+ '<(DEPTH)/cobalt/script/script.gyp:script',
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ },
+ ],
+}
diff --git a/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc b/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
index eaf8bd3..6f71b80 100644
--- a/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
+++ b/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
@@ -98,7 +98,7 @@
WebTokenRequestResult^ RequestToken(WebTokenRequest^ request) {
using starboard::shared::uwp::WaitForResult;
- IAsyncOperation<WebTokenRequestResult ^> ^ request_operation = nullptr;
+ IAsyncOperation<WebTokenRequestResult^>^ request_operation = nullptr;
base::WaitableEvent request_operation_set(false, false);
// Ensure WebAuthenticationCoreManager::RequestTokenAsync is called on the
// UI thread, since documentation states that "This method cannot be called
diff --git a/src/starboard/shared/uwp/starboard_platform.gypi b/src/starboard/shared/uwp/starboard_platform.gypi
index ee1b5bd..85f0f72 100644
--- a/src/starboard/shared/uwp/starboard_platform.gypi
+++ b/src/starboard/shared/uwp/starboard_platform.gypi
@@ -22,6 +22,8 @@
'system_clear_platform_error.cc',
'system_get_device_type.cc',
'system_get_property.cc',
+ 'system_get_total_cpu_memory.cc',
+ 'system_get_used_cpu_memory.cc',
'system_raise_platform_error.cc',
'window_create.cc',
'window_destroy.cc',
diff --git a/src/starboard/shared/uwp/system_get_device_type.cc b/src/starboard/shared/uwp/system_get_device_type.cc
index 81179ca..33ce323 100644
--- a/src/starboard/shared/uwp/system_get_device_type.cc
+++ b/src/starboard/shared/uwp/system_get_device_type.cc
@@ -23,7 +23,7 @@
using Windows::System::Profile::AnalyticsVersionInfo;
SbSystemDeviceType SbSystemGetDeviceType() {
- AnalyticsVersionInfo ^ version_info = AnalyticsInfo::VersionInfo;
+ AnalyticsVersionInfo^ version_info = AnalyticsInfo::VersionInfo;
std::string family = starboard::shared::win32::platformStringToString(
version_info->DeviceFamily);
diff --git a/src/starboard/shared/uwp/system_get_total_cpu_memory.cc b/src/starboard/shared/uwp/system_get_total_cpu_memory.cc
new file mode 100644
index 0000000..293fd7d
--- /dev/null
+++ b/src/starboard/shared/uwp/system_get_total_cpu_memory.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+using Windows::System::MemoryManager;
+
+int64_t SbSystemGetTotalCPUMemory() {
+ return static_cast<int64_t>(MemoryManager::AppMemoryUsageLimit);
+}
diff --git a/src/starboard/shared/uwp/system_get_used_cpu_memory.cc b/src/starboard/shared/uwp/system_get_used_cpu_memory.cc
new file mode 100644
index 0000000..adc9c0d
--- /dev/null
+++ b/src/starboard/shared/uwp/system_get_used_cpu_memory.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+using Windows::System::MemoryManager;
+
+int64_t SbSystemGetUsedCPUMemory() {
+ return static_cast<int64_t>(MemoryManager::AppMemoryUsage);
+}
diff --git a/src/starboard/shared/win32/audio_decoder.cc b/src/starboard/shared/win32/audio_decoder.cc
index d6749f2..879202c 100644
--- a/src/starboard/shared/win32/audio_decoder.cc
+++ b/src/starboard/shared/win32/audio_decoder.cc
@@ -56,19 +56,24 @@
};
AudioDecoder::AudioDecoder(SbMediaAudioCodec audio_codec,
- const SbMediaAudioHeader& audio_header)
- : sample_type_(kSbMediaAudioSampleTypeFloat32),
- stream_ended_(false),
- audio_codec_(audio_codec),
- audio_header_(audio_header) {
+ const SbMediaAudioHeader& audio_header,
+ SbDrmSystem drm_system)
+ : audio_codec_(audio_codec),
+ audio_header_(audio_header),
+ drm_system_(drm_system),
+ sample_type_(kSbMediaAudioSampleTypeFloat32),
+ stream_ended_(false) {
SB_DCHECK(audio_codec == kSbMediaAudioCodecAac);
decoder_impl_ = AbstractWin32AudioDecoder::Create(
- audio_codec_, GetStorageType(), GetSampleType(), audio_header_);
+ audio_codec_, GetStorageType(), GetSampleType(), audio_header_,
+ drm_system_);
decoder_thread_.reset(new AudioDecoderThread(decoder_impl_.get(), this));
callback_scheduler_.reset(new CallbackScheduler());
}
AudioDecoder::~AudioDecoder() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
decoder_thread_.reset(nullptr);
decoder_impl_.reset(nullptr);
callback_scheduler_.reset(nullptr);
@@ -76,7 +81,9 @@
void AudioDecoder::Decode(const scoped_refptr<InputBuffer>& input_buffer,
const Closure& consumed_cb) {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
SB_DCHECK(input_buffer);
+
callback_scheduler_->SetCallbackOnce(consumed_cb);
callback_scheduler_->OnCallbackSignaled();
const bool can_take_more_data = decoder_thread_->QueueInput(input_buffer);
@@ -91,22 +98,29 @@
}
void AudioDecoder::WriteEndOfStream() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
::starboard::ScopedLock lock(mutex_);
stream_ended_ = true;
decoder_thread_->QueueEndOfStream();
}
scoped_refptr<AudioDecoder::DecodedAudio> AudioDecoder::Read() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
DecodedAudioPtr data = decoded_data_.PopFront();
SB_DCHECK(data);
return data;
}
void AudioDecoder::Reset() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
decoder_thread_.reset(nullptr);
decoder_impl_.reset(nullptr);
decoder_impl_ = AbstractWin32AudioDecoder::Create(
- audio_codec_, GetStorageType(), GetSampleType(), audio_header_);
+ audio_codec_, GetStorageType(), GetSampleType(), audio_header_,
+ drm_system_);
decoder_thread_.reset(new AudioDecoderThread(decoder_impl_.get(), this));
decoded_data_.Clear();
stream_ended_ = false;
@@ -114,14 +128,20 @@
}
SbMediaAudioSampleType AudioDecoder::GetSampleType() const {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
return sample_type_;
}
int AudioDecoder::GetSamplesPerSecond() const {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
return audio_header_.samples_per_second;
}
void AudioDecoder::Initialize(const Closure& output_cb) {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
SB_DCHECK(output_cb.is_valid());
SB_DCHECK(!output_cb_.is_valid());
output_cb_ = output_cb;
diff --git a/src/starboard/shared/win32/audio_decoder.h b/src/starboard/shared/win32/audio_decoder.h
index f036287..5bd335c 100644
--- a/src/starboard/shared/win32/audio_decoder.h
+++ b/src/starboard/shared/win32/audio_decoder.h
@@ -18,11 +18,13 @@
#include "starboard/common/ref_counted.h"
#include "starboard/common/scoped_ptr.h"
#include "starboard/configuration.h"
+#include "starboard/drm.h"
#include "starboard/media.h"
#include "starboard/shared/internal_only.h"
#include "starboard/shared/starboard/player/decoded_audio_internal.h"
#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
#include "starboard/shared/starboard/player/job_queue.h"
+#include "starboard/shared/starboard/thread_checker.h"
#include "starboard/shared/win32/atomic_queue.h"
#include "starboard/shared/win32/audio_decoder_thread.h"
#include "starboard/shared/win32/media_common.h"
@@ -32,16 +34,14 @@
namespace shared {
namespace win32 {
-using JobQueue = ::starboard::shared::starboard::player::JobQueue;
-using JobOwner = JobQueue::JobOwner;
-
class AudioDecoder
: public ::starboard::shared::starboard::player::filter::AudioDecoder,
- private JobOwner,
+ private ::starboard::shared::starboard::player::JobQueue::JobOwner,
private AudioDecodedCallback {
public:
AudioDecoder(SbMediaAudioCodec audio_codec,
- const SbMediaAudioHeader& audio_header);
+ const SbMediaAudioHeader& audio_header,
+ SbDrmSystem drm_system);
~AudioDecoder() SB_OVERRIDE;
void Decode(const scoped_refptr<InputBuffer>& input_buffer,
@@ -60,9 +60,13 @@
private:
class CallbackScheduler;
- SbMediaAudioHeader audio_header_;
- SbMediaAudioSampleType sample_type_;
+
+ ::starboard::shared::starboard::ThreadChecker thread_checker_;
+
SbMediaAudioCodec audio_codec_;
+ SbMediaAudioHeader audio_header_;
+ SbDrmSystem drm_system_;
+ SbMediaAudioSampleType sample_type_;
bool stream_ended_;
AtomicQueue<DecodedAudioPtr> decoded_data_;
@@ -71,7 +75,7 @@
scoped_ptr<AudioDecoderThread> decoder_thread_;
Closure output_cb_;
- ::starboard::Mutex mutex_;
+ Mutex mutex_;
};
} // namespace win32
diff --git a/src/starboard/shared/win32/audio_sink.cc b/src/starboard/shared/win32/audio_sink.cc
index c5d8de2..5967211 100644
--- a/src/starboard/shared/win32/audio_sink.cc
+++ b/src/starboard/shared/win32/audio_sink.cc
@@ -72,6 +72,10 @@
ScopedLock lock(mutex_);
playback_rate_ = playback_rate;
}
+ void SetVolume(double volume) SB_OVERRIDE {
+ ScopedLock lock(mutex_);
+ volume_ = volume;
+ }
private:
static void* ThreadEntryPoint(void* context);
@@ -99,6 +103,7 @@
::starboard::Mutex mutex_;
bool destroying_;
double playback_rate_;
+ double volume_;
};
XAudioAudioSink::XAudioAudioSink(
@@ -118,7 +123,8 @@
frame_buffers_size_in_frames_(frame_buffers_size_in_frames),
wfx_(wfx),
destroying_(false),
- playback_rate_(1.0) {
+ playback_rate_(1.0),
+ volume_(1.0) {
// TODO: Check MaxFrequencyRatio
CHECK_HRESULT_OK(
type_->x_audio2_->CreateSourceVoice(&source_voice_, &wfx, 0,
@@ -187,6 +193,7 @@
int frames_in_buffer, offset_in_frames;
bool is_playing, is_eos_reached;
bool is_playback_rate_zero;
+ // TODO: Support |volume_| here as well...
{
ScopedLock lock(mutex_);
is_playback_rate_zero = playback_rate_ == 0.0;
diff --git a/src/starboard/shared/win32/decode_target_internal.cc b/src/starboard/shared/win32/decode_target_internal.cc
index 70b8e32..8188621 100644
--- a/src/starboard/shared/win32/decode_target_internal.cc
+++ b/src/starboard/shared/win32/decode_target_internal.cc
@@ -26,6 +26,7 @@
#include "third_party/angle/include/EGL/egl.h"
#include "third_party/angle/include/EGL/eglext.h"
#include "third_party/angle/include/GLES2/gl2.h"
+#include "third_party/angle/include/GLES2/gl2ext.h"
using Microsoft::WRL::ComPtr;
using starboard::shared::win32::VideoFramePtr;
@@ -73,24 +74,24 @@
SbDecodeTargetInfoPlane* planeY = &(info.planes[kSbDecodeTargetPlaneY]);
SbDecodeTargetInfoPlane* planeUV = &(info.planes[kSbDecodeTargetPlaneUV]);
- planeY->width = texture_desc.Width;
- planeY->height = texture_desc.Height;
+ planeY->width = info.width;
+ planeY->height = info.height;
planeY->content_region.left = 0;
- planeY->content_region.top = 0;
- planeY->content_region.right = texture_desc.Width;
- planeY->content_region.bottom = texture_desc.Height;
+ planeY->content_region.top = info.height;
+ planeY->content_region.right = frame->width();
+ planeY->content_region.bottom = info.height - frame->height();
- planeUV->width = texture_desc.Width / 2;
- planeUV->height = texture_desc.Height / 2;
- planeUV->content_region.left = 0;
- planeUV->content_region.top = 0;
- planeUV->content_region.right = texture_desc.Width / 2;
- planeUV->content_region.bottom = texture_desc.Height / 2;
+ planeUV->width = info.width / 2;
+ planeUV->height = info.height / 2;
+ planeUV->content_region.left = planeY->content_region.left / 2;
+ planeUV->content_region.top = planeY->content_region.top / 2;
+ planeUV->content_region.right = planeY->content_region.right / 2;
+ planeUV->content_region.bottom = planeY->content_region.bottom / 2;
EGLint luma_texture_attributes[] = {EGL_WIDTH,
- static_cast<EGLint>(texture_desc.Width),
+ static_cast<EGLint>(info.width),
EGL_HEIGHT,
- static_cast<EGLint>(texture_desc.Height),
+ static_cast<EGLint>(info.height),
EGL_TEXTURE_TARGET,
EGL_TEXTURE_2D,
EGL_TEXTURE_FORMAT,
@@ -119,16 +120,16 @@
dxgi_buffer.GetAddressOf());
SB_DCHECK(SUCCEEDED(hr));
- EGLSurface surface = eglCreatePbufferFromClientBuffer(
- display, EGL_D3D_TEXTURE_ANGLE, d3texture.Get(), config,
- luma_texture_attributes);
+ surface[0] = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
+ d3texture.Get(), config,
+ luma_texture_attributes);
- SB_DCHECK(surface != EGL_NO_SURFACE);
+ SB_DCHECK(surface[0] != EGL_NO_SURFACE);
glBindTexture(GL_TEXTURE_2D, gl_textures[0]);
SB_DCHECK(glGetError() == GL_NO_ERROR);
- ok = eglBindTexImage(display, surface, EGL_BACK_BUFFER);
+ ok = eglBindTexImage(display, surface[0], EGL_BACK_BUFFER);
SB_DCHECK(ok);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -136,6 +137,7 @@
planeY->texture = gl_textures[0];
planeY->gl_texture_target = GL_TEXTURE_2D;
+ planeY->gl_texture_format = GL_RED_EXT;
// This tells ANGLE that the texture it creates should draw
// the chroma channel on R8G8.
@@ -145,24 +147,24 @@
EGLint chroma_texture_attributes[] = {
EGL_WIDTH,
- static_cast<EGLint>(texture_desc.Width) / 2,
+ static_cast<EGLint>(info.width) / 2,
EGL_HEIGHT,
- static_cast<EGLint>(texture_desc.Height) / 2,
+ static_cast<EGLint>(info.height) / 2,
EGL_TEXTURE_TARGET,
EGL_TEXTURE_2D,
EGL_TEXTURE_FORMAT,
EGL_TEXTURE_RGBA,
EGL_NONE};
- surface = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
- d3texture.Get(), config,
- chroma_texture_attributes);
+ surface[1] = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
+ d3texture.Get(), config,
+ chroma_texture_attributes);
- SB_DCHECK(surface != EGL_NO_SURFACE);
+ SB_DCHECK(surface[1] != EGL_NO_SURFACE);
glBindTexture(GL_TEXTURE_2D, gl_textures[1]);
SB_DCHECK(glGetError() == GL_NO_ERROR);
- ok = eglBindTexImage(display, surface, EGL_BACK_BUFFER);
+ ok = eglBindTexImage(display, surface[1], EGL_BACK_BUFFER);
SB_DCHECK(ok);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -170,6 +172,7 @@
planeUV->texture = gl_textures[1];
planeUV->gl_texture_target = GL_TEXTURE_2D;
+ planeUV->gl_texture_format = GL_RG_EXT;
hr = d3texture->SetPrivateData(kCobaltDxgiBuffer, 0, nullptr);
SB_DCHECK(SUCCEEDED(hr));
@@ -178,6 +181,14 @@
SbDecodeTargetPrivate::~SbDecodeTargetPrivate() {
glDeleteTextures(1, &(info.planes[kSbDecodeTargetPlaneY].texture));
glDeleteTextures(1, &(info.planes[kSbDecodeTargetPlaneUV].texture));
+
+ EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+ eglReleaseTexImage(display, surface[0], EGL_BACK_BUFFER);
+ eglDestroySurface(display, surface[0]);
+
+ eglReleaseTexImage(display, surface[1], EGL_BACK_BUFFER);
+ eglDestroySurface(display, surface[1]);
}
void SbDecodeTargetRelease(SbDecodeTarget decode_target) {
diff --git a/src/starboard/shared/win32/decode_target_internal.h b/src/starboard/shared/win32/decode_target_internal.h
index 6204200..81aa66e 100644
--- a/src/starboard/shared/win32/decode_target_internal.h
+++ b/src/starboard/shared/win32/decode_target_internal.h
@@ -23,6 +23,9 @@
// Publicly accessible information about the decode target.
SbDecodeTargetInfo info;
::starboard::shared::win32::VideoFramePtr frame;
+ // EGLSurface is defined as void* in "third_party/angle/include/EGL/egl.h".
+ // Use void* directly here to avoid `egl.h` being included broadly.
+ void* surface[2];
explicit SbDecodeTargetPrivate(starboard::shared::win32::VideoFramePtr frame);
~SbDecodeTargetPrivate();
};
diff --git a/src/starboard/shared/win32/gyp_configuration.py b/src/starboard/shared/win32/gyp_configuration.py
index 0bf0e26..93f92f1 100644
--- a/src/starboard/shared/win32/gyp_configuration.py
+++ b/src/starboard/shared/win32/gyp_configuration.py
@@ -85,5 +85,5 @@
def GetToolchain(self):
sys.path.append(
os.path.join(STARBOARD_ROOT, 'shared', 'msvc', 'uwp'))
- from toolchain import MSVCUWPToolchain # pylint: disable=g-import-not-at-top,g-bad-import-order
+ from msvc_toolchain import MSVCUWPToolchain # pylint: disable=g-import-not-at-top,g-bad-import-order
return MSVCUWPToolchain()
diff --git a/src/starboard/shared/win32/media_is_output_protected.cc b/src/starboard/shared/win32/media_is_output_protected.cc
new file mode 100644
index 0000000..73d2f07
--- /dev/null
+++ b/src/starboard/shared/win32/media_is_output_protected.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+bool SbMediaIsOutputProtected() {
+ // Pretend that HDCP is always on.
+ // TODO: Fix this with proper HDCP query.
+ return true;
+}
diff --git a/src/starboard/shared/win32/player_components_impl.cc b/src/starboard/shared/win32/player_components_impl.cc
index cc86d10..fa2c4af 100644
--- a/src/starboard/shared/win32/player_components_impl.cc
+++ b/src/starboard/shared/win32/player_components_impl.cc
@@ -35,9 +35,13 @@
using VideoRendererImpl = ::starboard::shared::win32::VideoRendererImpl;
AudioDecoderImpl* audio_decoder = new AudioDecoderImpl(
- audio_parameters.audio_codec, audio_parameters.audio_header);
+ audio_parameters.audio_codec, audio_parameters.audio_header,
+ audio_parameters.drm_system);
- VideoDecoderImpl* video_decoder = new VideoDecoderImpl(video_parameters);
+ VideoDecoderImpl* video_decoder = new VideoDecoderImpl(
+ video_parameters.video_codec, video_parameters.output_mode,
+ video_parameters.decode_target_graphics_context_provider,
+ video_parameters.drm_system);
AudioRendererImpl* audio_renderer =
new AudioRendererImpl(scoped_ptr<AudioDecoder>(audio_decoder).Pass(),
diff --git a/src/starboard/shared/win32/video_decoder.cc b/src/starboard/shared/win32/video_decoder.cc
index 40fd85d..0a08c9a 100644
--- a/src/starboard/shared/win32/video_decoder.cc
+++ b/src/starboard/shared/win32/video_decoder.cc
@@ -24,17 +24,24 @@
namespace shared {
namespace win32 {
-VideoDecoder::VideoDecoder(const VideoParameters& params)
- : video_codec_(params.video_codec),
+VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec,
+ SbPlayerOutputMode output_mode,
+ SbDecodeTargetGraphicsContextProvider*
+ decode_target_graphics_context_provider,
+ SbDrmSystem drm_system)
+ : video_codec_(video_codec),
+ drm_system_(drm_system),
host_(NULL),
- output_mode_(params.output_mode),
+ output_mode_(output_mode),
decode_target_graphics_context_provider_(
- params.decode_target_graphics_context_provider) {
- impl_ = AbstractWin32VideoDecoder::Create(video_codec_);
+ decode_target_graphics_context_provider) {
+ impl_ = AbstractWin32VideoDecoder::Create(video_codec_, drm_system_);
video_decoder_thread_.reset(new VideoDecoderThread(impl_.get(), this));
}
VideoDecoder::~VideoDecoder() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
video_decoder_thread_.reset(nullptr);
impl_.reset(nullptr);
}
@@ -71,7 +78,7 @@
SB_DCHECK(host_);
video_decoder_thread_.reset(nullptr);
impl_.reset(nullptr);
- impl_ = AbstractWin32VideoDecoder::Create(video_codec_);
+ impl_ = AbstractWin32VideoDecoder::Create(video_codec_, drm_system_);
video_decoder_thread_.reset(new VideoDecoderThread(impl_.get(), this));
}
@@ -83,7 +90,7 @@
}
void VideoDecoder::OnVideoDecoded(VideoFramePtr data) {
- Status sts = data->IsEndOfStream() ? kBufferFull : kNeedMoreInput;
+ Status sts = (data && data->IsEndOfStream()) ? kBufferFull : kNeedMoreInput;
host_->OnDecoderStatusUpdate(sts, data);
}
diff --git a/src/starboard/shared/win32/video_decoder.h b/src/starboard/shared/win32/video_decoder.h
index 1fd2bfa..5d3734b 100644
--- a/src/starboard/shared/win32/video_decoder.h
+++ b/src/starboard/shared/win32/video_decoder.h
@@ -18,6 +18,7 @@
#include "starboard/common/ref_counted.h"
#include "starboard/common/scoped_ptr.h"
#include "starboard/configuration.h"
+#include "starboard/drm.h"
#include "starboard/shared/starboard/player/filter/player_components.h"
#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
#include "starboard/shared/starboard/player/input_buffer_internal.h"
@@ -38,7 +39,11 @@
private ::starboard::shared::starboard::player::JobQueue::JobOwner,
private VideoDecodedCallback {
public:
- explicit VideoDecoder(const VideoParameters& params);
+ VideoDecoder(SbMediaVideoCodec video_codec,
+ SbPlayerOutputMode output_mode,
+ SbDecodeTargetGraphicsContextProvider*
+ decode_target_graphics_context_provider,
+ SbDrmSystem drm_system);
~VideoDecoder() SB_OVERRIDE;
void SetHost(Host* host) SB_OVERRIDE;
@@ -53,22 +58,23 @@
void OnVideoDecoded(VideoFramePtr data) SB_OVERRIDE;
private:
+ ::starboard::shared::starboard::ThreadChecker thread_checker_;
+
// These variables will be initialized inside ctor or SetHost() and will not
// be changed during the life time of this class.
const SbMediaVideoCodec video_codec_;
+ SbDrmSystem drm_system_;
Host* host_;
// Decode-to-texture related state.
- SbPlayerOutputMode output_mode_;
-
- SbDecodeTargetGraphicsContextProvider*
+ const SbPlayerOutputMode output_mode_;
+ SbDecodeTargetGraphicsContextProvider* const
decode_target_graphics_context_provider_;
scoped_ptr<AbstractWin32VideoDecoder> impl_;
AtomicQueue<VideoFramePtr> decoded_data_;
- ::starboard::Mutex mutex_;
+ Mutex mutex_;
scoped_ptr<VideoDecoderThread> video_decoder_thread_;
- ::starboard::shared::starboard::ThreadChecker thread_checker_;
};
} // namespace win32
diff --git a/src/starboard/shared/win32/video_decoder_thread.cc b/src/starboard/shared/win32/video_decoder_thread.cc
index 64a7b2e..d2f8ba2 100644
--- a/src/starboard/shared/win32/video_decoder_thread.cc
+++ b/src/starboard/shared/win32/video_decoder_thread.cc
@@ -102,6 +102,7 @@
if (number_written > 0) {
processing_elements_.fetch_sub(static_cast<int32_t>(number_written));
work_done = true;
+ callback_->OnVideoDecoded(NULL);
}
while (VideoFramePtr decoded_datum =
diff --git a/src/starboard/shared/win32/win32_audio_decoder.cc b/src/starboard/shared/win32/win32_audio_decoder.cc
index 0b85f30..4142764 100644
--- a/src/starboard/shared/win32/win32_audio_decoder.cc
+++ b/src/starboard/shared/win32/win32_audio_decoder.cc
@@ -103,18 +103,19 @@
};
class AbstractWin32AudioDecoderImpl : public AbstractWin32AudioDecoder,
- public MediaBufferConsumerInterface {
+ public MediaBufferConsumerInterface {
public:
AbstractWin32AudioDecoderImpl(SbMediaAudioCodec codec,
- SbMediaAudioFrameStorageType audio_frame_fmt,
- SbMediaAudioSampleType sample_type,
- const SbMediaAudioHeader& audio_header)
+ SbMediaAudioFrameStorageType audio_frame_fmt,
+ SbMediaAudioSampleType sample_type,
+ const SbMediaAudioHeader& audio_header,
+ SbDrmSystem drm_system)
: codec_(codec),
audio_frame_fmt_(audio_frame_fmt),
sample_type_(sample_type),
audio_header_(audio_header) {
MediaBufferConsumerInterface* media_cb = this;
- impl_.reset(new DecoderImpl("audio", media_cb));
+ impl_.reset(new DecoderImpl("audio", media_cb, drm_system));
EnsureAudioDecoderCreated();
}
@@ -151,6 +152,8 @@
media_buffer->Unlock();
}
+ void OnNewOutputType(const ComPtr<IMFMediaType>& /*type*/) override {}
+
ComPtr<IMFMediaType> Configure(IMFTransform* decoder) {
ComPtr<IMFMediaType> input_type;
HRESULT hr = MFCreateMediaType(&input_type);
@@ -209,7 +212,6 @@
SB_DCHECK(decoder);
impl_->set_decoder(decoder);
- impl_->ActivateDecryptor(media_type);
// TODO: MFWinAudioFormat_PCM?
ComPtr<IMFMediaType> output_type =
@@ -234,17 +236,40 @@
const int size = buff.size();
const int64_t media_timestamp = buff.pts();
- // These parameters are used for decryption. But these are not used right
- // now and so remain empty.
std::vector<uint8_t> key_id;
std::vector<uint8_t> iv;
std::vector<Subsample> subsamples;
+ const SbDrmSampleInfo* drm_info = buff.drm_info();
+
+ if (drm_info != NULL && drm_info->initialization_vector_size != 0) {
+ key_id.assign(drm_info->identifier,
+ drm_info->identifier + drm_info->identifier_size);
+ iv.assign(drm_info->initialization_vector,
+ drm_info->initialization_vector +
+ drm_info->initialization_vector_size);
+ subsamples.reserve(drm_info->subsample_count);
+ for (int32_t i = 0; i < drm_info->subsample_count; ++i) {
+ Subsample subsample = {
+ static_cast<uint32_t>(
+ drm_info->subsample_mapping[i].clear_byte_count),
+ static_cast<uint32_t>(
+ drm_info->subsample_mapping[i].encrypted_byte_count)};
+ subsamples.push_back(subsample);
+ }
+ }
+
const std::int64_t win32_time_stamp = ConvertToWin32Time(media_timestamp);
// Adjust the offset for 7 bytes to remove the ADTS header.
- const uint8_t* audio_start = static_cast<const uint8_t*>(data) + 7;
- const int audio_size = size - 7;
+ const int kADTSHeaderSize = 7;
+ const uint8_t* audio_start =
+ static_cast<const uint8_t*>(data) + kADTSHeaderSize;
+ const int audio_size = size - kADTSHeaderSize;
+ if (!subsamples.empty()) {
+ SB_DCHECK(subsamples[0].clear_bytes == kADTSHeaderSize);
+ subsamples[0].clear_bytes = 0;
+ }
const bool write_ok = impl_->TryWriteInputBuffer(
audio_start, audio_size, win32_time_stamp, key_id, iv, subsamples);
@@ -282,10 +307,11 @@
SbMediaAudioCodec code,
SbMediaAudioFrameStorageType audio_frame_fmt,
SbMediaAudioSampleType sample_type,
- const SbMediaAudioHeader& audio_header) {
- return scoped_ptr<AbstractWin32AudioDecoder>(new
- AbstractWin32AudioDecoderImpl(code, audio_frame_fmt, sample_type,
- audio_header));
+ const SbMediaAudioHeader& audio_header,
+ SbDrmSystem drm_system) {
+ return scoped_ptr<AbstractWin32AudioDecoder>(
+ new AbstractWin32AudioDecoderImpl(code, audio_frame_fmt, sample_type,
+ audio_header, drm_system));
}
} // namespace win32
diff --git a/src/starboard/shared/win32/win32_audio_decoder.h b/src/starboard/shared/win32/win32_audio_decoder.h
index 4cc5f27..3bfe78c 100644
--- a/src/starboard/shared/win32/win32_audio_decoder.h
+++ b/src/starboard/shared/win32/win32_audio_decoder.h
@@ -18,6 +18,7 @@
#include <vector>
#include "starboard/common/scoped_ptr.h"
+#include "starboard/drm.h"
#include "starboard/media.h"
#include "starboard/shared/starboard/player/decoded_audio_internal.h"
#include "starboard/shared/starboard/player/video_frame_internal.h"
@@ -35,7 +36,8 @@
SbMediaAudioCodec codec,
SbMediaAudioFrameStorageType audio_frame_fmt,
SbMediaAudioSampleType sample_type,
- const SbMediaAudioHeader& audio_header);
+ const SbMediaAudioHeader& audio_header,
+ SbDrmSystem drm_system);
virtual ~AbstractWin32AudioDecoder() {}
// INPUT:
diff --git a/src/starboard/shared/win32/win32_decoder_impl.cc b/src/starboard/shared/win32/win32_decoder_impl.cc
index b06fcdc..b41b418 100644
--- a/src/starboard/shared/win32/win32_decoder_impl.cc
+++ b/src/starboard/shared/win32/win32_decoder_impl.cc
@@ -117,11 +117,15 @@
} // namespace
DecoderImpl::DecoderImpl(const std::string& type,
- MediaBufferConsumerInterface* media_buffer_consumer)
+ MediaBufferConsumerInterface* media_buffer_consumer,
+ SbDrmSystem drm_system)
: type_(type),
media_buffer_consumer_(media_buffer_consumer),
discontinuity_(false) {
SB_DCHECK(media_buffer_consumer_);
+ drm_system_ =
+ static_cast<::starboard::xb1::shared::playready::SbDrmSystemPlayready*>(
+ drm_system);
}
DecoderImpl::~DecoderImpl() {
@@ -137,13 +141,29 @@
return decoder;
}
-void DecoderImpl::ActivateDecryptor(ComPtr<IMFMediaType> input_type) {
+void DecoderImpl::ActivateDecryptor() {
+ SB_DCHECK(decoder_);
+
if (!decryptor_) {
return;
}
- HRESULT hr = decryptor_->SetInputType(kStreamId, input_type.Get(),
- 0); // MFT_SET_TYPE_TEST_FLAGS.
+ ComPtr<IMFMediaType> decryptor_input_type;
+
+ HRESULT hr = decoder_->GetInputCurrentType(
+ kStreamId, decryptor_input_type.GetAddressOf());
+ CheckResult(hr);
+
+ GUID original_sub_type;
+ {
+ ComPtr<IMFMediaType> output_type;
+ hr = decoder_->GetOutputCurrentType(kStreamId, output_type.GetAddressOf());
+ CheckResult(hr);
+ output_type->GetGUID(MF_MT_SUBTYPE, &original_sub_type);
+ }
+
+ const DWORD kFlags = 0;
+ hr = decryptor_->SetInputType(kStreamId, decryptor_input_type.Get(), kFlags);
CheckResult(hr);
// Ensure that the decryptor and the decoder agrees on the protection of
@@ -158,7 +178,6 @@
BYTE* crypt_seed = NULL;
DWORD crypt_seed_size = 0;
ComPtr<IMFMediaType> decoder_input_type;
- ComPtr<IMFMediaType> decoder_output_type;
hr = decryptor_.As(&decryption_sample_protection);
CheckResult(hr);
@@ -211,6 +230,21 @@
// Start the decryptor, note that this should be better abstracted.
SendMFTMessage(decryptor_.Get(), MFT_MESSAGE_NOTIFY_BEGIN_STREAMING);
SendMFTMessage(decryptor_.Get(), MFT_MESSAGE_NOTIFY_START_OF_STREAM);
+
+ for (int index = 0;; ++index) {
+ ComPtr<IMFMediaType> output_type;
+ hr = decoder_->GetOutputAvailableType(0, index, &output_type);
+ if (SUCCEEDED(hr)) {
+ GUID sub_type;
+ output_type->GetGUID(MF_MT_SUBTYPE, &sub_type);
+ if (IsEqualGUID(sub_type, original_sub_type)) {
+ hr = decoder_->SetOutputType(0, output_type.Get(), 0);
+ CheckResult(hr);
+ break;
+ }
+ }
+ output_type.Reset();
+ }
}
bool DecoderImpl::TryWriteInputBuffer(
@@ -236,6 +270,19 @@
// Better check the key id size is 16 and iv size is 8 or 16.
bool encrypted = !key_id.empty() && !iv.empty();
if (encrypted) {
+ if (!decryptor_) {
+ scoped_refptr<::starboard::xb1::shared::playready::PlayreadyLicense>
+ license = drm_system_->GetLicense(key_id.data(),
+ static_cast<int>(key_id.size()));
+ if (license && license->usable()) {
+ decryptor_ = license->decryptor();
+ ActivateDecryptor();
+ }
+ }
+ if (!decryptor_) {
+ SB_NOTREACHED();
+ return false;
+ }
size_t iv_size = iv.size();
const char kEightZeros[8] = {0};
if (iv_size == 16 && SbMemoryCompare(iv.data() + 8, kEightZeros, 8) == 0) {
@@ -315,27 +362,33 @@
bool DecoderImpl::DeliverOneOutputOnAllTransforms() {
SB_DCHECK(decoder_);
- bool delivered = false;
if (decryptor_) {
- if (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decryptor_)) {
- HRESULT hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0);
+ if (!cached_decryptor_output_) {
+ cached_decryptor_output_ = DeliverOutputOnTransform(decryptor_);
+ }
+ while (cached_decryptor_output_) {
+ HRESULT hr =
+ decoder_->ProcessInput(kStreamId, cached_decryptor_output_.Get(), 0);
if (hr == MF_E_NOTACCEPTING) {
- // The protocol says that when ProcessInput() returns MF_E_NOTACCEPTING,
- // there must be some output available. Retrieve the output and the next
- // ProcessInput() should succeed.
- ComPtr<IMFSample> sample_inner = DeliverOutputOnTransform(decoder_);
- if (sample_inner) {
- DeliverDecodedSample(sample_inner);
- delivered = true;
- }
- hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0);
+ break;
+ } else {
CheckResult(hr);
- return delivered;
+ cached_decryptor_output_ = DeliverOutputOnTransform(decryptor_);
}
- CheckResult(hr);
- delivered = true;
+ }
+ if (cached_decryptor_output_) {
+ // The protocol says that when ProcessInput() returns MF_E_NOTACCEPTING,
+ // there must be some output available. Retrieve the output and the next
+ // ProcessInput() should succeed.
+ ComPtr<IMFSample> decoder_output = DeliverOutputOnTransform(decoder_);
+ if (decoder_output) {
+ DeliverDecodedSample(decoder_output);
+ }
+ return decoder_output != NULL;
}
}
+
+ bool delivered = false;
if (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decoder_)) {
DeliverDecodedSample(sample);
delivered = true;
@@ -371,18 +424,6 @@
HRESULT hr = transform->GetOutputStreamInfo(kStreamId, &output_stream_info);
CheckResult(hr);
- // Each media sample (IMFSample interface) of output data from the MFT
- // contains complete, unbroken units of data. The definition of a unit
- // of data depends on the media type: For uncompressed video, a video
- // frame; for compressed data, a compressed packet; for uncompressed audio,
- // a single audio frame.
- //
- // For uncompressed audio formats, this flag is always implied. (It is valid
- // to set the flag, but not required.) An uncompressed audio frame should
- // never span more than one media sample.
- SB_DCHECK((output_stream_info.dwFlags & MFT_OUTPUT_STREAM_WHOLE_SAMPLES) !=
- 0);
-
if (StreamAllocatesMemory(output_stream_info.dwFlags)) {
// Try to let the IMFTransform allocate the memory if possible.
return;
@@ -431,9 +472,21 @@
hr = transform->SetOutputType(kStreamId, media_type.Get(), 0);
CheckResult(hr);
- return NULL;
- } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT ||
- output_data_buffer.dwStatus == MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) {
+
+ media_buffer_consumer_->OnNewOutputType(media_type);
+
+ hr = transform->ProcessOutput(kFlags, kNumberOfBuffers, &output_data_buffer,
+ &status);
+
+ SB_DCHECK(!output_data_buffer.pEvents);
+
+ output = output_data_buffer.pSample;
+ ReleaseIfNotNull(&output_data_buffer.pEvents);
+ ReleaseIfNotNull(&output_data_buffer.pSample);
+ }
+
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT ||
+ output_data_buffer.dwStatus == MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) {
return NULL;
}
diff --git a/src/starboard/shared/win32/win32_decoder_impl.h b/src/starboard/shared/win32/win32_decoder_impl.h
index 38374ff..cef0d5d 100644
--- a/src/starboard/shared/win32/win32_decoder_impl.h
+++ b/src/starboard/shared/win32/win32_decoder_impl.h
@@ -25,10 +25,14 @@
#include <vector>
#include "starboard/common/scoped_ptr.h"
+#include "starboard/drm.h"
#include "starboard/media.h"
#include "starboard/shared/win32/media_common.h"
#include "starboard/types.h"
+// TODO: Win32 code shouldn't depend on Xb1 code. Refactor this.
+#include "starboard/xb1/shared/playready/drm_system_playready.h"
+
namespace starboard {
namespace shared {
namespace win32 {
@@ -38,6 +42,8 @@
virtual void Consume(Microsoft::WRL::ComPtr<IMFMediaBuffer> media_buffer,
int64_t win32_timestamp) = 0;
+ virtual void OnNewOutputType(const ComPtr<IMFMediaType>& type) = 0;
+
protected:
~MediaBufferConsumerInterface() {}
};
@@ -50,12 +56,13 @@
class DecoderImpl {
public:
DecoderImpl(const std::string& type,
- MediaBufferConsumerInterface* media_buffer_consumer);
+ MediaBufferConsumerInterface* media_buffer_consumer,
+ SbDrmSystem drm_system);
~DecoderImpl();
static Microsoft::WRL::ComPtr<IMFTransform> CreateDecoder(CLSID clsid);
- void ActivateDecryptor(Microsoft::WRL::ComPtr<IMFMediaType> input_type);
+ void ActivateDecryptor();
bool TryWriteInputBuffer(const void* data,
int size,
std::int64_t win32_timestamp,
@@ -84,9 +91,11 @@
const std::string type_; // For debugging purpose.
// This is the target for the all completed media buffers.
MediaBufferConsumerInterface* media_buffer_consumer_;
+ ::starboard::xb1::shared::playready::SbDrmSystemPlayready* drm_system_;
bool discontinuity_;
Microsoft::WRL::ComPtr<IMFTransform> decryptor_;
+ Microsoft::WRL::ComPtr<IMFSample> cached_decryptor_output_;
Microsoft::WRL::ComPtr<IMFTransform> decoder_;
};
diff --git a/src/starboard/shared/win32/win32_video_decoder.cc b/src/starboard/shared/win32/win32_video_decoder.cc
index 93bc487..667b2e5 100644
--- a/src/starboard/shared/win32/win32_video_decoder.cc
+++ b/src/starboard/shared/win32/win32_video_decoder.cc
@@ -36,31 +36,22 @@
// This magic number is taken directly from sample from MS. Can be further
// tuned in case if there is playback issues.
const int kSampleAllocatorFramesMax = 5;
+// This is the minimum allocated frames in the output ring buffer. Can be
+// further tuned to save memory. Note that use a value that is too small leads
+// to hang when calling ProcessOutput().
+const int kMinimumOutputSampleCount = 10;
// CLSID_CMSVideoDecoderMFT {62CE7E72-4C71-4D20-B15D-452831A87D9D}
const GUID CLSID_VideoDecoder = {0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45,
0x28, 0x31, 0xA8, 0x7D, 0x9D};
-ComPtr<ID3D11Texture2D> GetTexture2D(ComPtr<IMFMediaBuffer> media_buffer) {
- ComPtr<IMFDXGIBuffer> dxgi_buffer;
- HRESULT hr = media_buffer.As(&dxgi_buffer);
- CheckResult(hr);
- SB_DCHECK(dxgi_buffer.Get());
-
- ComPtr<ID3D11Texture2D> dx_texture;
- hr = dxgi_buffer->GetResource(IID_PPV_ARGS(&dx_texture));
- CheckResult(hr);
- return dx_texture;
-}
-
class VideoFrameFactory {
public:
static VideoFramePtr Construct(SbMediaTime timestamp,
- ComPtr<IMFMediaBuffer> media_buffer) {
- ComPtr<ID3D11Texture2D> texture = GetTexture2D(media_buffer);
- D3D11_TEXTURE2D_DESC tex_desc;
- texture->GetDesc(&tex_desc);
- VideoFramePtr out(new VideoFrame(tex_desc.Width, tex_desc.Height,
+ ComPtr<IMFMediaBuffer> media_buffer,
+ uint32_t width,
+ uint32_t height) {
+ VideoFramePtr out(new VideoFrame(width, height,
timestamp, media_buffer.Detach(),
nullptr, DeleteTextureFn));
frames_in_flight_.increment();
@@ -84,10 +75,10 @@
class AbstractWin32VideoDecoderImpl : public AbstractWin32VideoDecoder,
public MediaBufferConsumerInterface {
public:
- explicit AbstractWin32VideoDecoderImpl(SbMediaVideoCodec codec)
+ AbstractWin32VideoDecoderImpl(SbMediaVideoCodec codec, SbDrmSystem drm_system)
: codec_(codec), surface_width_(0), surface_height_(0) {
MediaBufferConsumerInterface* media_cb = this;
- impl_.reset(new DecoderImpl("video", media_cb));
+ impl_.reset(new DecoderImpl("video", media_cb, drm_system));
EnsureVideoDecoderCreated();
}
@@ -95,10 +86,32 @@
int64_t win32_timestamp) {
const SbMediaTime media_timestamp = ConvertToMediaTime(win32_timestamp);
VideoFramePtr frame_output =
- VideoFrameFactory::Construct(media_timestamp, media_buffer);
+ VideoFrameFactory::Construct(media_timestamp, media_buffer,
+ surface_width_, surface_height_);
output_queue_.PushBack(frame_output);
}
+ void OnNewOutputType(const ComPtr<IMFMediaType>& type) override {
+ MFVideoArea aperture;
+ HRESULT hr = type->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
+ reinterpret_cast<UINT8*>(&aperture), sizeof(MFVideoArea), nullptr);
+ if (SUCCEEDED(hr)) {
+ // TODO: consider offset as well
+ surface_width_ = aperture.Area.cx;
+ surface_height_ = aperture.Area.cy;
+ return;
+ }
+
+ uint32_t width;
+ uint32_t height;
+ hr = MFGetAttributeSize(type.Get(), MF_MT_FRAME_SIZE,
+ &width, &height);
+ if (SUCCEEDED(hr)) {
+ surface_width_ = width;
+ surface_height_ = height;
+ }
+ }
+
ComPtr<IMFMediaType> Configure(IMFTransform* decoder) {
ComPtr<IMFMediaType> input_type;
HRESULT hr = MFCreateMediaType(&input_type);
@@ -160,8 +173,6 @@
SB_DCHECK(1 == input_stream_count);
SB_DCHECK(1 == output_stream_count);
- impl_->ActivateDecryptor(media_type);
-
ComPtr<IMFMediaType> output_type =
FindMediaType(MFVideoFormat_YV12, decoder.Get());
@@ -174,6 +185,10 @@
hr = decoder->GetAttributes(attributes.GetAddressOf());
CheckResult(hr);
+ hr = attributes->SetUINT32(MF_SA_MINIMUM_OUTPUT_SAMPLE_COUNT,
+ kMinimumOutputSampleCount);
+ CheckResult(hr);
+
UINT32 value = 0;
hr = attributes->GetUINT32(MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE, &value);
SB_DCHECK(hr == S_OK || hr == MF_E_ATTRIBUTENOTFOUND);
@@ -225,19 +240,35 @@
return false; // Wait for more data.
}
- // This would be used for decrypting content.
- // For now this is empty.
std::vector<uint8_t> key_id;
std::vector<uint8_t> iv;
+ std::vector<Subsample> subsamples;
- std::vector<Subsample> empty_subsample;
+ // TODO: Merge this with similar code in the audio decoder.
+ const SbDrmSampleInfo* drm_info = buff->drm_info();
+
+ if (drm_info != NULL && drm_info->initialization_vector_size != 0) {
+ key_id.assign(drm_info->identifier,
+ drm_info->identifier + drm_info->identifier_size);
+ iv.assign(drm_info->initialization_vector,
+ drm_info->initialization_vector +
+ drm_info->initialization_vector_size);
+ subsamples.reserve(drm_info->subsample_count);
+ for (int32_t i = 0; i < drm_info->subsample_count; ++i) {
+ Subsample subsample = {
+ static_cast<uint32_t>(
+ drm_info->subsample_mapping[i].clear_byte_count),
+ static_cast<uint32_t>(
+ drm_info->subsample_mapping[i].encrypted_byte_count)};
+ subsamples.push_back(subsample);
+ }
+ }
const SbMediaTime media_timestamp = buff->pts();
const int64_t win32_timestamp = ConvertToWin32Time(media_timestamp);
const bool write_ok = impl_->TryWriteInputBuffer(
- buff->data(), buff->size(), win32_timestamp,
- key_id, iv, empty_subsample);
+ buff->data(), buff->size(), win32_timestamp, key_id, iv, subsamples);
return write_ok;
}
@@ -271,12 +302,14 @@
uint32_t surface_height_;
HardwareDecoderContext dx_decoder_ctx_;
};
+
} // anonymous namespace.
scoped_ptr<AbstractWin32VideoDecoder> AbstractWin32VideoDecoder::Create(
- SbMediaVideoCodec codec) {
+ SbMediaVideoCodec codec,
+ SbDrmSystem drm_system) {
return scoped_ptr<AbstractWin32VideoDecoder>(
- new AbstractWin32VideoDecoderImpl(codec));
+ new AbstractWin32VideoDecoderImpl(codec, drm_system));
}
} // namespace win32
diff --git a/src/starboard/shared/win32/win32_video_decoder.h b/src/starboard/shared/win32/win32_video_decoder.h
index 84ff7b7..a32158b 100644
--- a/src/starboard/shared/win32/win32_video_decoder.h
+++ b/src/starboard/shared/win32/win32_video_decoder.h
@@ -20,6 +20,7 @@
#include "starboard/common/ref_counted.h"
#include "starboard/common/scoped_ptr.h"
+#include "starboard/drm.h"
#include "starboard/media.h"
#include "starboard/shared/starboard/player/decoded_audio_internal.h"
#include "starboard/shared/starboard/player/video_frame_internal.h"
@@ -33,7 +34,8 @@
// VideoDecoder for Win32.
class AbstractWin32VideoDecoder {
public:
- static scoped_ptr<AbstractWin32VideoDecoder> Create(SbMediaVideoCodec codec);
+ static scoped_ptr<AbstractWin32VideoDecoder> Create(SbMediaVideoCodec codec,
+ SbDrmSystem drm_system);
virtual ~AbstractWin32VideoDecoder() {}
virtual bool TryWrite(const scoped_refptr<InputBuffer>& buff) = 0;
diff --git a/src/starboard/shared/x11/application_x11.cc b/src/starboard/shared/x11/application_x11.cc
index 19fb1f2..c4a5cb6 100644
--- a/src/starboard/shared/x11/application_x11.cc
+++ b/src/starboard/shared/x11/application_x11.cc
@@ -43,6 +43,9 @@
namespace starboard {
namespace shared {
namespace x11 {
+
+using ::starboard::shared::dev_input::DevInput;
+
namespace {
enum {
@@ -647,28 +650,6 @@
}
#endif
-bool XNextEventTimed(Display* display, XEvent* out_event, SbTime duration) {
- if (XPending(display) == 0) {
- if (duration <= SbTime()) {
- return false;
- }
-
- int fd = ConnectionNumber(display);
- fd_set read_set;
- FD_ZERO(&read_set);
- FD_SET(fd, &read_set);
- struct timeval tv;
- SbTime clamped_duration = std::max(duration, SbTime());
- ToTimevalDuration(clamped_duration, &tv);
- if (select(fd + 1, &read_set, NULL, NULL, &tv) == 0) {
- return false;
- }
- }
-
- XNextEvent(display, out_event);
- return true;
-}
-
void XSendAtom(Window window, Atom atom) {
// XLib is not thread-safe. Since we may be coming from another thread, we
// have to open another connection to the display to inject the wake-up event.
@@ -732,6 +713,10 @@
SbWindow window = new SbWindowPrivate(display_, options);
windows_.push_back(window);
+ if (!dev_input_) {
+ // evdev input will be sent to the first created window only.
+ dev_input_.reset(DevInput::Create(window, ConnectionNumber(display_)));
+ }
return window;
}
@@ -745,6 +730,12 @@
std::find(windows_.begin(), windows_.end(), window);
SB_DCHECK(iterator != windows_.end());
windows_.erase(iterator);
+
+ if (windows_.empty()) {
+ SB_DCHECK(dev_input_);
+ dev_input_.reset();
+ }
+
delete window;
if (windows_.empty()) {
StopX();
@@ -849,23 +840,29 @@
ApplicationX11::WaitForSystemEventWithTimeout(SbTime time) {
SB_DCHECK(display_);
- XEvent x_event;
-
shared::starboard::Application::Event* pending_event = GetPendingEvent();
if (pending_event) {
return pending_event;
}
- if (XNextEventTimed(display_, &x_event, time)) {
+ SB_DCHECK(dev_input_);
+ shared::starboard::Application::Event* evdev_event =
+ dev_input_->WaitForSystemEventWithTimeout(time);
+
+ if (!evdev_event && XPending(display_) != 0) {
+ XEvent x_event;
+ XNextEvent(display_, &x_event);
return XEventToEvent(&x_event);
- } else {
- return NULL;
}
+
+ return evdev_event;
}
void ApplicationX11::WakeSystemEventWait() {
SB_DCHECK(!windows_.empty());
XSendAtom((*windows_.begin())->window, wake_up_atom_);
+ SB_DCHECK(dev_input_);
+ dev_input_->WakeSystemEventWait();
}
bool ApplicationX11::EnsureX() {
@@ -879,7 +876,7 @@
XSetErrorHandler(ErrorHandler);
display_ = XOpenDisplay(NULL);
if (!display_) {
- const char *display_environment = getenv("DISPLAY");
+ const char* display_environment = getenv("DISPLAY");
if (display_environment == NULL) {
SB_LOG(ERROR) << "Unable to open display, DISPLAY not set.";
} else {
diff --git a/src/starboard/shared/x11/application_x11.h b/src/starboard/shared/x11/application_x11.h
index a0235b1..cadfc90 100644
--- a/src/starboard/shared/x11/application_x11.h
+++ b/src/starboard/shared/x11/application_x11.h
@@ -20,9 +20,11 @@
#include <queue>
#include <vector>
+#include "base/memory/scoped_ptr.h"
#include "starboard/configuration.h"
#include "starboard/player.h"
#include "starboard/shared/internal_only.h"
+#include "starboard/shared/linux/dev_input/dev_input.h"
#include "starboard/shared/starboard/application.h"
#include "starboard/shared/starboard/queue_application.h"
#include "starboard/types.h"
@@ -117,6 +119,9 @@
// Indicates whether a key press event that requires a matching release has
// been dispatched.
bool paste_buffer_key_release_pending_;
+
+ // The /dev/input input handler. Only set when there is an open window.
+ scoped_ptr<::starboard::shared::dev_input::DevInput> dev_input_;
};
} // namespace x11