Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a1d4803 authored by Patrick Williams's avatar Patrick Williams
Browse files

Add support for multiple displays

This change sets the output size based on the max height and width
across all physical displays. When display size changes, the display
projection is updated. This ensures the entire display is visible when
using screenrecord on foldable devices.

Bug: 261217582
Test: manual test with multiple displays and screen rotations
Change-Id: I8a770308dcaf024a9670c48eba92694b12fcb57a
parent 2248fa1b
Loading
Loading
Loading
Loading
+108 −36
Original line number Diff line number Diff line
@@ -122,7 +122,7 @@ static uint32_t gVideoHeight = 0;
static uint32_t gBitRate = 20000000;     // 20Mbps
static uint32_t gTimeLimitSec = kMaxTimeLimitSec;
static uint32_t gBframes = 0;
static PhysicalDisplayId gPhysicalDisplayId;
static std::optional<PhysicalDisplayId> gPhysicalDisplayId;
// Set by signal handler to stop recording.
static volatile bool gStopRequested = false;

@@ -335,6 +335,24 @@ static status_t setDisplayProjection(
    return NO_ERROR;
}

/*
 * Gets the physical id of the display to record. If the user specified a physical
 * display id, then that id will be set. Otherwise, the default display will be set.
 */
static status_t getPhysicalDisplayId(PhysicalDisplayId& outDisplayId) {
    if (gPhysicalDisplayId) {
        outDisplayId = *gPhysicalDisplayId;
        return NO_ERROR;
    }

    const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
    if (ids.empty()) {
        return INVALID_OPERATION;
    }
    outDisplayId = ids.front();
    return NO_ERROR;
}

/*
 * Configures the virtual display.  When this completes, virtual display
 * frames will start arriving from the buffer producer.
@@ -350,7 +368,12 @@ static status_t prepareVirtualDisplay(
    setDisplayProjection(t, dpy, displayState);
    ui::LayerStack layerStack = ui::LayerStack::fromValue(std::rand());
    t.setDisplayLayerStack(dpy, layerStack);
    *mirrorRoot = SurfaceComposerClient::getDefault()->mirrorDisplay(gPhysicalDisplayId);
    PhysicalDisplayId displayId;
    status_t err = getPhysicalDisplayId(displayId);
    if (err != NO_ERROR) {
        return err;
    }
    *mirrorRoot = SurfaceComposerClient::getDefault()->mirrorDisplay(displayId);
    if (*mirrorRoot == nullptr) {
        ALOGE("Failed to create a mirror for screenrecord");
        return UNKNOWN_ERROR;
@@ -485,6 +508,43 @@ static status_t writeWinscopeMetadata(const Vector<std::int64_t>& timestampsMono
    return AMediaMuxer_writeSampleData(muxer, metaTrackIdx, buffer->data(), &bufferInfo);
}

/*
 * Update the display projection if size or orientation have changed.
 */
void updateDisplayProjection(const sp<IBinder>& virtualDpy, ui::DisplayState& displayState) {
    ATRACE_NAME("updateDisplayProjection");

    PhysicalDisplayId displayId;
    if (getPhysicalDisplayId(displayId) != NO_ERROR) {
        fprintf(stderr, "ERROR: Failed to get display id\n");
        return;
    }

    sp<IBinder> displayToken = SurfaceComposerClient::getPhysicalDisplayToken(displayId);
    if (!displayToken) {
        fprintf(stderr, "ERROR: failed to get display token\n");
        return;
    }

    ui::DisplayState currentDisplayState;
    if (SurfaceComposerClient::getDisplayState(displayToken, &currentDisplayState) != NO_ERROR) {
        ALOGW("ERROR: failed to get display state\n");
        return;
    }

    if (currentDisplayState.orientation != displayState.orientation ||
        currentDisplayState.layerStackSpaceRect != displayState.layerStackSpaceRect) {
        displayState = currentDisplayState;
        ALOGD("display state changed, now has orientation %s, size (%d, %d)",
              toCString(displayState.orientation), displayState.layerStackSpaceRect.getWidth(),
              displayState.layerStackSpaceRect.getHeight());

        SurfaceComposerClient::Transaction t;
        setDisplayProjection(t, virtualDpy, currentDisplayState);
        t.apply();
    }
}

/*
 * Runs the MediaCodec encoder, sending the output to the MediaMuxer.  The
 * input frames are coming from the virtual display as fast as SurfaceFlinger
@@ -494,9 +554,8 @@ static status_t writeWinscopeMetadata(const Vector<std::int64_t>& timestampsMono
 *
 * The muxer must *not* have been started before calling.
 */
static status_t runEncoder(const sp<MediaCodec>& encoder,
        AMediaMuxer *muxer, FILE* rawFp, const sp<IBinder>& display,
        const sp<IBinder>& virtualDpy, ui::Rotation orientation) {
static status_t runEncoder(const sp<MediaCodec>& encoder, AMediaMuxer* muxer, FILE* rawFp,
                           const sp<IBinder>& virtualDpy, ui::DisplayState displayState) {
    static int kTimeout = 250000;   // be responsive on signal
    status_t err;
    ssize_t trackIdx = -1;
@@ -555,24 +614,7 @@ static status_t runEncoder(const sp<MediaCodec>& encoder,
                ALOGV("Got data in buffer %zu, size=%zu, pts=%" PRId64,
                        bufIndex, size, ptsUsec);

                { // scope
                    ATRACE_NAME("orientation");
                    // Check orientation, update if it has changed.
                    //
                    // Polling for changes is inefficient and wrong, but the
                    // useful stuff is hard to get at without a Dalvik VM.
                    ui::DisplayState displayState;
                    err = SurfaceComposerClient::getDisplayState(display, &displayState);
                    if (err != NO_ERROR) {
                        ALOGW("getDisplayState() failed: %d", err);
                    } else if (orientation != displayState.orientation) {
                        ALOGD("orientation changed, now %s", toCString(displayState.orientation));
                        SurfaceComposerClient::Transaction t;
                        setDisplayProjection(t, virtualDpy, displayState);
                        t.apply();
                        orientation = displayState.orientation;
                    }
                }
                updateDisplayProjection(virtualDpy, displayState);

                // If the virtual display isn't providing us with timestamps,
                // use the current time.  This isn't great -- we could get
@@ -763,6 +805,38 @@ struct RecordingData {
    }
};

/*
 * Computes the maximum width and height across all physical displays.
 */
static ui::Size getMaxDisplaySize() {
    const std::vector<PhysicalDisplayId> physicalDisplayIds =
            SurfaceComposerClient::getPhysicalDisplayIds();
    if (physicalDisplayIds.empty()) {
        fprintf(stderr, "ERROR: Failed to get physical display ids\n");
        return {};
    }

    ui::Size result;
    for (auto& displayId : physicalDisplayIds) {
        sp<IBinder> displayToken = SurfaceComposerClient::getPhysicalDisplayToken(displayId);
        if (!displayToken) {
            fprintf(stderr, "ERROR: failed to get display token\n");
            continue;
        }

        ui::DisplayState displayState;
        status_t err = SurfaceComposerClient::getDisplayState(displayToken, &displayState);
        if (err != NO_ERROR) {
            fprintf(stderr, "ERROR: failed to get display state\n");
            continue;
        }

        result.height = std::max(result.height, displayState.layerStackSpaceRect.getHeight());
        result.width = std::max(result.width, displayState.layerStackSpaceRect.getWidth());
    }
    return result;
}

/*
 * Main "do work" start point.
 *
@@ -781,9 +855,15 @@ static status_t recordScreen(const char* fileName) {
    sp<ProcessState> self = ProcessState::self();
    self->startThreadPool();

    PhysicalDisplayId displayId;
    err = getPhysicalDisplayId(displayId);
    if (err != NO_ERROR) {
        fprintf(stderr, "ERROR: Failed to get display id\n");
        return err;
    }

    // Get main display parameters.
    sp<IBinder> display = SurfaceComposerClient::getPhysicalDisplayToken(
            gPhysicalDisplayId);
    sp<IBinder> display = SurfaceComposerClient::getPhysicalDisplayToken(displayId);
    if (display == nullptr) {
        fprintf(stderr, "ERROR: no display\n");
        return NAME_NOT_FOUND;
@@ -808,7 +888,8 @@ static status_t recordScreen(const char* fileName) {
        return INVALID_OPERATION;
    }

    const ui::Size& layerStackSpaceRect = displayState.layerStackSpaceRect;
    const ui::Size layerStackSpaceRect =
        gPhysicalDisplayId ? displayState.layerStackSpaceRect : getMaxDisplaySize();
    if (gVerbose) {
        printf("Display is %dx%d @%.2ffps (orientation=%s), layerStack=%u\n",
                layerStackSpaceRect.getWidth(), layerStackSpaceRect.getHeight(),
@@ -973,8 +1054,7 @@ static status_t recordScreen(const char* fileName) {
        }
    } else {
        // Main encoder loop.
        err = runEncoder(recordingData.encoder, muxer, rawFp, display, recordingData.dpy,
                         displayState.orientation);
        err = runEncoder(recordingData.encoder, muxer, rawFp, recordingData.dpy, displayState);
        if (err != NO_ERROR) {
            fprintf(stderr, "Encoder failed (err=%d)\n", err);
            // fall through to cleanup
@@ -1175,14 +1255,6 @@ int main(int argc, char* const argv[]) {
        { NULL,                 0,                  NULL, 0 }
    };

    const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
    if (ids.empty()) {
        fprintf(stderr, "Failed to get ID for any displays\n");
        return 1;
    }

    gPhysicalDisplayId = ids.front();

    while (true) {
        int optionIndex = 0;
        int ic = getopt_long(argc, argv, "", longOptions, &optionIndex);