Files
Sunshine/tests/unit/platform/linux/test_wayland_shm_protocol.cpp

579 lines
24 KiB
C++

/**
* @file tests/unit/platform/linux/test_wayland_shm_protocol.cpp
* @brief Exercise shared-memory capture over a private Wayland connection.
*/
#ifdef SUNSHINE_BUILD_WAYLAND
// test includes
#include "../../../tests_common.h"
// standard includes
#include <array>
#include <cstring>
#include <memory>
#include <string_view>
#include <vector>
// ffmpeg includes
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
}
// system includes
#include <drm_fourcc.h>
#include <poll.h>
#include <sys/socket.h>
#include <wayland-server.h>
// local includes
#include <src/config.h>
#include <src/platform/linux/wayland.h>
namespace {
/**
* @brief Private compositor using libwayland's real SHM pool implementation.
*/
class WaylandShmProtocolTest: public testing::Test { // NOSONAR(cpp:S3656): TEST_F generates subclasses that access the shared fixture state.
protected:
/**
* @brief Observe the active proxy without replacing capture's protocol handling.
*/
struct capture_t: wl::dmabuf_t {
zwlr_screencopy_frame_v1 *request {}; ///< Last frame advertised by the compositor.
};
/**
* @brief Create an in-process compositor without connecting to the desktop.
*/
void SetUp() override {
connection.server = wl_display_create();
ASSERT_NE(connection.server, nullptr);
ASSERT_EQ(wl_display_init_shm(connection.server), 0);
ASSERT_NE(wl_global_create(connection.server, &zwlr_screencopy_manager_v1_interface, 3, this, bind_manager), nullptr);
ASSERT_NE(wl_global_create(connection.server, &zwp_linux_dmabuf_v1_interface, 3, this, bind_dmabuf), nullptr);
ASSERT_NE(wl_global_create(connection.server, &wl_output_interface, 1, this, bind_output), nullptr);
std::array<int, 2> sockets;
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockets.data()), 0);
ASSERT_NE(wl_client_create(connection.server, sockets[0]), nullptr);
connection.client = wl_display_connect_to_fd(sockets[1]);
ASSERT_NE(connection.client, nullptr);
connection.registry = wl_display_get_registry(connection.client);
static const wl_registry_listener listener {global, global_remove};
wl_registry_add_listener(connection.registry, &listener, this);
ASSERT_TRUE(sync());
ASSERT_TRUE(sync());
ASSERT_NE(connection.shm, nullptr);
ASSERT_NE(connection.manager, nullptr);
ASSERT_NE(connection.dmabuf, nullptr);
ASSERT_NE(connection.output, nullptr);
capture = std::make_unique<capture_t>();
capture->listener.buffer = [](void *data, zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t stride) { // NOSONAR(cpp:S5008): Required by the Wayland C listener signature.
auto &capture = *static_cast<capture_t *>(static_cast<wl::dmabuf_t *>(data));
capture.request = frame;
capture.buffer(frame, format, width, height, stride);
};
}
/**
* @brief Release capture objects before disconnecting the private client.
*/
void TearDown() override {
if (capture) {
capture->cancel();
capture.reset();
}
if (connection.client) {
wl_display_disconnect(connection.client);
}
if (connection.server) {
wl_display_destroy_clients(connection.server);
wl_display_destroy(connection.server);
}
}
/**
* @brief Pump both ends until a real Wayland synchronization callback arrives.
* @return True when the callback completes without a protocol error or timeout.
*/
bool sync() {
bool done = false;
auto callback = wl_display_sync(connection.client);
static const wl_callback_listener listener {
[](void *data, wl_callback *callback, std::uint32_t) { // NOSONAR(cpp:S5008): Required by the Wayland C listener signature.
*static_cast<bool *>(data) = true;
wl_callback_destroy(callback);
},
};
wl_callback_add_listener(callback, &listener, &done);
while (!done) {
if (wl_display_flush(connection.client) < 0 || wl_event_loop_dispatch(wl_display_get_event_loop(connection.server), 0) < 0) {
return false;
}
wl_display_flush_clients(connection.server);
pollfd fd {wl_display_get_fd(connection.client), POLLIN, 0};
if (poll(&fd, 1, 1000) <= 0 || wl_display_dispatch(connection.client) < 0) {
return false;
}
}
return wl_display_get_error(connection.client) == 0;
}
/**
* @brief Begin a capture and process its advertised buffer formats.
*/
void start() {
capture->listen(connection.manager, connection.dmabuf, nullptr, connection.shm, connection.output);
ASSERT_TRUE(sync());
ASSERT_TRUE(sync());
}
/**
* @brief Inject successful DMA-BUF creation followed by a terminal copy failure.
* @param fallback Whether the capture has a SHM factory for recovery.
*/
void fail_dmabuf_copy(bool fallback = true) {
hold_buffer_done = true;
advertise_dmabuf = true;
accept_dmabuf = true;
capture->with_damage = true;
capture->listen(connection.manager, connection.dmabuf, nullptr, fallback ? connection.shm : nullptr, connection.output, true);
ASSERT_TRUE(sync());
ASSERT_TRUE(sync());
hold_buffer_done = false;
auto params = zwp_linux_dmabuf_v1_create_params(connection.dmabuf);
static const zwp_linux_buffer_params_v1_listener listener {
wl::dmabuf_t::buffer_params_created,
wl::dmabuf_t::buffer_params_failed,
};
zwp_linux_buffer_params_v1_add_listener(params, &listener, capture->request);
zwp_linux_buffer_params_v1_create(params, width, height, DRM_FORMAT_XRGB8888, 0);
for (int i = 0; i < 5; ++i) {
ASSERT_TRUE(sync());
}
}
/**
* @brief Complete a pending copy through the real shared-memory buffer.
*/
void complete() {
ASSERT_NE(frame_resource, nullptr);
ASSERT_NE(buffer_resource, nullptr);
auto buffer = wl_shm_buffer_get(buffer_resource);
ASSERT_NE(buffer, nullptr);
wl_shm_buffer_begin_access(buffer);
auto pixels = static_cast<std::uint8_t *>(wl_shm_buffer_get_data(buffer));
for (std::uint32_t y = 0; y < height; ++y) {
for (std::uint32_t x = 0; x < width; ++x) {
const std::array<std::uint8_t, 4> pixel {static_cast<std::uint8_t>(10 + y), 20, 30, 255};
std::memcpy(pixels + y * stride + x * 4, pixel.data(), pixel.size());
}
}
wl_shm_buffer_end_access(buffer);
wl_resource_post_event(frame_resource, 1, inverted ? ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT : 0);
wl_resource_post_event(frame_resource, 2, 0u, 1u, 0u);
ASSERT_TRUE(sync());
}
/**
* @brief Bind the globals advertised by the private compositor.
* @param data Test fixture.
* @param registry Client registry.
* @param name Global object identifier.
* @param interface Advertised interface name.
* @param version Advertised interface version.
*/
static void global(void *data, wl_registry *registry, std::uint32_t name, const char *interface, std::uint32_t version) { // NOSONAR(cpp:S5008): Required by the Wayland C registry listener signature.
auto &self = *static_cast<WaylandShmProtocolTest *>(data);
if (std::string_view(interface) == "wl_shm") {
self.connection.shm = static_cast<wl_shm *>(wl_registry_bind(registry, name, &wl_shm_interface, 1));
static const wl_shm_listener listener {[](void *, wl_shm *, std::uint32_t) { // NOSONAR(cpp:S5008): Required by the Wayland C SHM listener signature.
// The private compositor always advertises XRGB8888 to capture.
}};
wl_shm_add_listener(self.connection.shm, &listener, nullptr);
} else if (std::string_view(interface) == "wl_output") {
self.connection.output = static_cast<wl_output *>(wl_registry_bind(registry, name, &wl_output_interface, 1));
} else if (std::string_view(interface) == "zwlr_screencopy_manager_v1") {
self.connection.manager = static_cast<zwlr_screencopy_manager_v1 *>(wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, version));
} else if (std::string_view(interface) == "zwp_linux_dmabuf_v1") {
self.connection.dmabuf = static_cast<zwp_linux_dmabuf_v1 *>(wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version));
}
}
/**
* @brief Ignore global removal; this compositor retains globals until teardown.
*/
static void global_remove(void *, wl_registry *, std::uint32_t) { // NOSONAR(cpp:S5008): Required by the Wayland C registry listener signature.
// The private compositor retains all globals until teardown.
}
/**
* @brief Bind an output used only as the screencopy request's target.
*/
static void bind_output(wl_client *client, void *, std::uint32_t version, std::uint32_t id) { // NOSONAR(cpp:S5008): Required by the Wayland C global-bind signature.
wl_resource_create(client, &wl_output_interface, version, id);
}
/**
* @brief Bind the screencopy manager and install its request dispatcher.
*/
static void bind_manager(wl_client *client, void *data, std::uint32_t version, std::uint32_t id) { // NOSONAR(cpp:S5008): Required by the Wayland C global-bind signature.
auto resource = wl_resource_create(client, &zwlr_screencopy_manager_v1_interface, version, id);
wl_resource_set_dispatcher(resource, dispatch_manager, nullptr, data, nullptr);
}
/**
* @brief Bind the controlled DMA-BUF import factory.
*/
static void bind_dmabuf(wl_client *client, void *data, std::uint32_t version, std::uint32_t id) { // NOSONAR(cpp:S5008): Required by the Wayland C global-bind signature.
auto resource = wl_resource_create(client, &zwp_linux_dmabuf_v1_interface, version, id);
wl_resource_set_dispatcher(resource, dispatch_dmabuf, nullptr, data, nullptr);
}
/**
* @brief Advertise a SHM frame, optionally holding buffer_done for an import rejection.
*/
static int dispatch_manager(const void *, void *target, std::uint32_t opcode, const wl_message *, wl_argument *args) { // NOSONAR(cpp:S5008): Required by wl_dispatcher_func_t.
auto resource = static_cast<wl_resource *>(target);
auto &self = *static_cast<WaylandShmProtocolTest *>(wl_resource_get_user_data(resource));
if (opcode == 2) {
wl_resource_destroy(resource);
return 0;
}
++self.requests;
self.blend_cursor = args[1].i;
self.frame_resource = wl_resource_create(wl_resource_get_client(resource), &zwlr_screencopy_frame_v1_interface, 3, args[0].n);
wl_resource_set_dispatcher(self.frame_resource, dispatch_frame, nullptr, &self, nullptr);
wl_resource_post_event(self.frame_resource, 0, WL_SHM_FORMAT_XRGB8888, self.width, self.height, self.stride);
if (self.advertise_dmabuf) {
wl_resource_post_event(self.frame_resource, 5, DRM_FORMAT_XRGB8888, self.width, self.height);
}
if (!self.hold_buffer_done) {
wl_resource_post_event(self.frame_resource, 6);
}
return 0;
}
/**
* @brief Retain copy buffers until the test completes or cancels a request.
*/
static int dispatch_frame(const void *, void *target, std::uint32_t opcode, const wl_message *, wl_argument *args) { // NOSONAR(cpp:S5008): Required by wl_dispatcher_func_t.
auto resource = static_cast<wl_resource *>(target);
auto &self = *static_cast<WaylandShmProtocolTest *>(wl_resource_get_user_data(resource));
if (opcode == 1) {
self.frame_resource = nullptr;
self.buffer_resource = nullptr;
++self.destroyed_frames;
if (self.failed_frame == resource) {
self.failed_frame = nullptr;
}
wl_resource_destroy(resource);
} else if (resource == self.failed_frame) {
wl_resource_post_error(resource, 0, "Cannot copy a terminal screencopy frame");
} else {
self.damage_copy = opcode == 2;
self.buffer_resource = reinterpret_cast<wl_resource *>(args[0].o); // NOSONAR(cpp:S3630): Wayland's server dispatcher encodes wl_resource pointers in wl_argument::o.
++self.copies;
if (!wl_shm_buffer_get(self.buffer_resource)) {
self.failed_frame = resource;
wl_resource_post_event(resource, 3);
}
}
return 0;
}
/**
* @brief Create import parameter resources without using a GPU.
*/
static int dispatch_dmabuf(const void *, void *target, std::uint32_t opcode, const wl_message *, wl_argument *args) { // NOSONAR(cpp:S5008): Required by wl_dispatcher_func_t.
auto resource = static_cast<wl_resource *>(target);
if (opcode == 0) {
wl_resource_destroy(resource);
} else if (opcode == 1) {
auto params = wl_resource_create(wl_resource_get_client(resource), &zwp_linux_buffer_params_v1_interface, 3, args[0].n);
wl_resource_set_dispatcher(params, dispatch_params, nullptr, wl_resource_get_user_data(resource), nullptr);
}
return 0;
}
/**
* @brief Complete or reject an asynchronous import through the Wayland event queue.
*/
static int dispatch_params(const void *, void *target, std::uint32_t opcode, const wl_message *, wl_argument *) { // NOSONAR(cpp:S5008): Required by wl_dispatcher_func_t.
auto resource = static_cast<wl_resource *>(target);
if (opcode == 0) {
wl_resource_destroy(resource);
} else if (opcode == 2) {
auto &self = *static_cast<WaylandShmProtocolTest *>(wl_resource_get_user_data(resource));
if (self.accept_dmabuf) {
auto buffer = wl_resource_create(wl_resource_get_client(resource), &wl_buffer_interface, 1, 0);
wl_resource_set_dispatcher(buffer, [](const void *, void *target, std::uint32_t, const wl_message *, wl_argument *) { // NOSONAR(cpp:S5008): Required by wl_dispatcher_func_t.
wl_resource_destroy(static_cast<wl_resource *>(target));
return 0;
},
nullptr,
nullptr,
nullptr);
wl_resource_post_event(resource, 0, buffer);
} else {
wl_resource_post_event(resource, 1);
}
}
return 0;
}
struct {
wl_display *server {}; ///< Private compositor display.
wl_display *client {}; ///< Private capture connection.
wl_registry *registry {}; ///< Registry used to bind test globals.
wl_shm *shm {}; ///< Real libwayland SHM factory.
wl_output *output {}; ///< Output targeted by capture requests.
zwlr_screencopy_manager_v1 *manager {}; ///< Private screencopy factory.
zwp_linux_dmabuf_v1 *dmabuf {}; ///< Controlled DMA-BUF import factory.
} connection; ///< Objects belonging to the private Wayland connection.
wl_resource *frame_resource {}; ///< Active server-side screencopy frame.
wl_resource *buffer_resource {}; ///< Buffer retained by the pending copy.
wl_resource *failed_frame {}; ///< Terminal frame that must not receive another copy.
std::unique_ptr<capture_t> capture; ///< Actual Sunshine capture implementation.
std::uint32_t width {2}; ///< Advertised frame width.
std::uint32_t height {2}; ///< Advertised frame height.
std::uint32_t stride {12}; ///< Advertised padded row size.
bool hold_buffer_done {false}; ///< Defer the allocation decision for the rejection test.
bool advertise_dmabuf {false}; ///< Attempt real GBM initialization before falling back.
bool accept_dmabuf {false}; ///< Accept buffer construction but reject the later copy.
bool blend_cursor {false}; ///< Cursor option of the most recently requested capture.
int requests {0}; ///< Number of capture requests issued.
bool inverted {false}; ///< Advertise vertically inverted captured pixels.
bool damage_copy {false}; ///< Whether the last request used copy_with_damage.
int copies {0}; ///< Number of real copy requests received.
int destroyed_frames {0}; ///< Number of screencopy objects released by the client.
};
} // namespace
TEST_F(WaylandShmProtocolTest, CompletedPixelsRemainOwnedAcrossResolutionChanges) {
start();
ASSERT_EQ(copies, 1);
ASSERT_EQ(capture->status, wl::dmabuf_t::WAITING);
complete();
ASSERT_EQ(capture->status, wl::dmabuf_t::READY);
ASSERT_TRUE(capture->current_frame->is_shm);
const auto first = capture->current_frame->shm_data;
ASSERT_EQ(first.size(), stride * height);
EXPECT_EQ(first[0], 10);
EXPECT_EQ(first[stride], 11);
width = 3;
height = 1;
stride = 16;
start();
EXPECT_EQ(capture->current_frame->shm_data, first);
complete();
EXPECT_EQ(capture->current_frame->sd.width, width);
EXPECT_EQ(capture->current_frame->sd.height, height);
EXPECT_EQ(capture->current_frame->shm_data.size(), stride * height);
}
TEST_F(WaylandShmProtocolTest, DamageCopyCanBeCancelledAndReplaced) {
capture->with_damage = true;
start();
EXPECT_TRUE(damage_copy);
EXPECT_TRUE(capture->cancel());
ASSERT_TRUE(sync());
EXPECT_EQ(destroyed_frames, 1);
EXPECT_FALSE(capture->cancel());
capture->with_damage = false;
start();
EXPECT_FALSE(damage_copy);
complete();
EXPECT_EQ(capture->status, wl::dmabuf_t::READY);
}
TEST_F(WaylandShmProtocolTest, AsyncImportRejectionCompletesShmWithoutDisplayRecreation) {
hold_buffer_done = true;
start();
ASSERT_EQ(copies, 0);
auto params = zwp_linux_dmabuf_v1_create_params(connection.dmabuf);
static const zwp_linux_buffer_params_v1_listener listener {
wl::dmabuf_t::buffer_params_created,
wl::dmabuf_t::buffer_params_failed,
};
// Inject the import request, not the result: the real failed event must reach
// Sunshine's callback and keep the original screencopy proxy usable.
zwp_linux_buffer_params_v1_add_listener(params, &listener, capture->request);
zwp_linux_buffer_params_v1_create(params, width, height, WL_SHM_FORMAT_XRGB8888, 0);
ASSERT_TRUE(sync());
ASSERT_TRUE(sync());
EXPECT_EQ(copies, 1);
EXPECT_EQ(destroyed_frames, 0);
ASSERT_EQ(capture->status, wl::dmabuf_t::WAITING);
complete();
EXPECT_EQ(capture->status, wl::dmabuf_t::READY);
EXPECT_TRUE(capture->current_frame->is_shm);
}
TEST_F(WaylandShmProtocolTest, LateDmabufCopyFailureRetriesShmOnFreshRequest) {
fail_dmabuf_copy();
ASSERT_EQ(requests, 2);
EXPECT_EQ(destroyed_frames, 1);
EXPECT_EQ(copies, 2);
EXPECT_TRUE(blend_cursor);
EXPECT_FALSE(damage_copy);
ASSERT_EQ(capture->status, wl::dmabuf_t::WAITING);
complete();
EXPECT_EQ(capture->status, wl::dmabuf_t::READY);
ASSERT_TRUE(capture->current_frame->is_shm);
ASSERT_EQ(capture->current_frame->shm_data.size(), stride * height);
EXPECT_EQ(capture->current_frame->shm_data[0], 10);
}
TEST_F(WaylandShmProtocolTest, FailedShmRetryDoesNotIssueAnotherCapture) {
fail_dmabuf_copy();
ASSERT_EQ(requests, 2);
ASSERT_NE(frame_resource, nullptr);
wl_resource_post_event(frame_resource, 3);
ASSERT_TRUE(sync());
ASSERT_TRUE(sync());
EXPECT_EQ(requests, 2);
EXPECT_EQ(destroyed_frames, 2);
EXPECT_EQ(capture->status, wl::dmabuf_t::REINIT);
EXPECT_FALSE(capture->cancel());
}
TEST_F(WaylandShmProtocolTest, LateDmabufCopyFailureWithoutShmReinitializes) {
fail_dmabuf_copy(false);
EXPECT_EQ(requests, 1);
EXPECT_EQ(copies, 1);
EXPECT_EQ(destroyed_frames, 1);
EXPECT_EQ(capture->status, wl::dmabuf_t::REINIT);
}
TEST_F(WaylandShmProtocolTest, FailureBeforeCopyDoesNotRetry) {
hold_buffer_done = true;
start();
wl_resource_post_event(frame_resource, 3);
ASSERT_TRUE(sync());
ASSERT_TRUE(sync());
EXPECT_EQ(requests, 1);
EXPECT_EQ(copies, 0);
EXPECT_EQ(capture->status, wl::dmabuf_t::REINIT);
}
TEST_F(WaylandShmProtocolTest, ShmCopyFailureDoesNotRetry) {
start();
wl_resource_post_event(frame_resource, 3);
ASSERT_TRUE(sync());
ASSERT_TRUE(sync());
EXPECT_EQ(requests, 1);
EXPECT_EQ(copies, 1);
EXPECT_EQ(capture->status, wl::dmabuf_t::REINIT);
}
TEST_F(WaylandShmProtocolTest, LateDmabufFailureRetryCanBeCancelled) {
fail_dmabuf_copy();
ASSERT_EQ(requests, 2);
EXPECT_TRUE(capture->cancel());
ASSERT_TRUE(sync());
EXPECT_EQ(destroyed_frames, 2);
EXPECT_EQ(capture->status, wl::dmabuf_t::READY);
EXPECT_FALSE(capture->cancel());
}
TEST_F(WaylandShmProtocolTest, InvertedPixelsConvertIntoFrameOwnedBgra) {
inverted = true;
start();
complete();
std::array<std::uint8_t, 16> pixels {};
platf::img_t img;
img.data = pixels.data();
img.width = width;
img.height = height;
img.row_pitch = width * 4;
img.pixel_pitch = 4;
ASSERT_TRUE(wl::convert_shm_frame(*capture->current_frame, img));
EXPECT_EQ(pixels[0], 11);
EXPECT_EQ(pixels[8], 10);
}
TEST_F(WaylandShmProtocolTest, GbmInitializationFailureStillCompletesShm) {
const auto previous = config::video.adapter_name;
auto restore = util::fail_guard([&previous] {
config::video.adapter_name = previous;
});
config::video.adapter_name = "/nonexistent-sunshine-test-render-node";
advertise_dmabuf = true;
start();
ASSERT_EQ(copies, 1);
complete();
EXPECT_EQ(capture->status, wl::dmabuf_t::READY);
EXPECT_TRUE(capture->current_frame->is_shm);
}
TEST_F(WaylandShmProtocolTest, CapturedShmPixelsCanBeEncodedAsH264) {
width = 16;
height = 16;
stride = width * 4;
start();
complete();
std::vector<std::uint8_t> pixels(stride * height + AV_INPUT_BUFFER_PADDING_SIZE);
platf::img_t img;
img.data = pixels.data();
img.width = width;
img.height = height;
img.row_pitch = stride;
img.pixel_pitch = 4;
ASSERT_TRUE(wl::convert_shm_frame(*capture->current_frame, img));
auto codec = avcodec_find_encoder_by_name("libx264");
ASSERT_NE(codec, nullptr);
auto free_context = [](AVCodecContext *context) {
avcodec_free_context(&context);
};
std::unique_ptr<AVCodecContext, decltype(free_context)> context(avcodec_alloc_context3(codec), free_context);
ASSERT_NE(context, nullptr);
context->width = width;
context->height = height;
context->pix_fmt = AV_PIX_FMT_YUV420P;
context->time_base = AVRational {1, 60};
context->thread_count = 1;
ASSERT_EQ(av_opt_set(context->priv_data, "preset", "ultrafast", 0), 0);
ASSERT_EQ(av_opt_set(context->priv_data, "tune", "zerolatency", 0), 0);
ASSERT_EQ(avcodec_open2(context.get(), codec, nullptr), 0);
auto free_frame = [](AVFrame *frame) {
av_frame_free(&frame);
};
std::unique_ptr<AVFrame, decltype(free_frame)> frame(av_frame_alloc(), free_frame);
ASSERT_NE(frame, nullptr);
frame->format = context->pix_fmt;
frame->width = width;
frame->height = height;
frame->pts = 0;
ASSERT_EQ(av_frame_get_buffer(frame.get(), 0), 0);
std::unique_ptr<SwsContext, decltype(&sws_freeContext)> converter(sws_getContext(width, height, AV_PIX_FMT_BGRA, width, height, AV_PIX_FMT_YUV420P, SWS_POINT, nullptr, nullptr, nullptr), &sws_freeContext);
ASSERT_NE(converter, nullptr);
const std::array<const std::uint8_t *, 4> source {pixels.data()};
const std::array<int, 4> pitches {static_cast<int>(stride)};
ASSERT_EQ(sws_scale(converter.get(), source.data(), pitches.data(), 0, height, frame->data, frame->linesize), height);
ASSERT_EQ(avcodec_send_frame(context.get(), frame.get()), 0);
auto free_packet = [](AVPacket *packet) {
av_packet_free(&packet);
};
std::unique_ptr<AVPacket, decltype(free_packet)> packet(av_packet_alloc(), free_packet);
ASSERT_NE(packet, nullptr);
ASSERT_EQ(avcodec_receive_packet(context.get(), packet.get()), 0);
EXPECT_GT(packet->size, 0);
}
TEST_F(WaylandShmProtocolTest, MissingShmReinitializesWithoutIssuingCopy) {
capture->listen(connection.manager, connection.dmabuf, nullptr, nullptr, connection.output);
ASSERT_TRUE(sync());
ASSERT_TRUE(sync());
EXPECT_EQ(copies, 0);
EXPECT_EQ(destroyed_frames, 1);
EXPECT_EQ(capture->status, wl::dmabuf_t::REINIT);
}
#endif