Having problems in screen capturing 3440x1440 resolution, other resolutions wokrs well.

191 views
Skip to first unread message

Davide Perini

unread,
Mar 5, 2023, 9:22:48 AM3/5/23
to gstream...@googlegroups.com
Hi all,
I'm screen capturing using GStreamer bindings for Java without problems
since a lot of time,
it works super awesome at all screen resolutions.

I'm using a pipeline like this:
d3d11screencapturesrc monitor-handle={0} ! d3d11convert ! d3d11download
! video/x-raw(memory:SystemMemory),width=3440,height=1440
this pipeline works as expected.

I recently switched to this "new pipeline" that should be more optimized
for the purpose:
d3d11screencapturesrc monitor-handle={0} ! d3d11convert !
video/x-raw(memory:D3D11Memory),width=3440,height=1440

this "new pipeline" works well at every screen resolutions except
3440x1440,
I even tried another 21:9 resolution like 3840x1080 and it works well at
2560x1080 but not on 3440x1440.

This is so weird. My code should be ok since if I switch to the "older
pipeline" all works well.

The author of the bindings is always right so the problem should be
found in my understanding of the bindings :D

Is there someone who can shed some light please?

Thanks
Davide


Davide Perini

unread,
Mar 5, 2023, 11:03:27 AM3/5/23
to gstream...@googlegroups.com
I created a JPEG file from the captured image, I wrote the IntBuffer to a jpg file to see what it is capturing.

At a different resolution than 3440x1440 the resulted image is a perfect screenshot of my screen.

I attach the result at 3440x1440, it's a completely scrambled image. O_o
boolean write = false;
int skip = 0;

private class AppSinkListener implements AppSink.NEW_SAMPLE {

    public void rgbFrame(int width, int height, IntBuffer rgbBuffer) {


        // If the EDT is still copying data from the buffer, just drop this frame
        if (!bufferLock.tryLock()) {
            return;
        }

        skip++;

        BufferedImage img =
                new BufferedImage(3440, 1440, 1);
        int[] rgbArray = new int[rgbBuffer.capacity()];
        rgbBuffer.rewind();
        rgbBuffer.get(rgbArray);
        img.setRGB(0, 0, img.getWidth(), img.getHeight(), rgbArray, 0, img.getWidth());

        try {
	    // skip some frames
            if (!write && skip == 90) {
                write = true;
                ImageIO.write(img, "jpg", new File("screenshot.jpg"));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }






Il 05/03/2023 15:22, Davide Perini ha scritto:
Hi all,
I'm screen capturing using GStreamer bindings for Java without problems since a lot of time,
it works super awesome at all screen resolutions.

I'm using a pipeline like this:
d3d11screencapturesrc monitor-handle={0} ! d3d11convert ! d3d11download ! video/x-raw(memory:SystemMemory),width=3440,height=1440
this pipeline works as expected.

I recently switched to this "new pipeline" that should be more optimized for the purpose:
d3d11screencapturesrc monitor-handle={0} ! d3d11convert ! video/x-raw(memory:D3D11Memory),width=3440,height=1440

this "new pipeline" works well at every screen resolutions except 3440x1440,
I even tried another 21:9 resolution like 2560x1080 and it works well at 2560x1080 but not on 3440x1440.
screenshot.jpg

Christophe LAFOLET

unread,
Mar 5, 2023, 1:24:58 PM3/5/23
to gstreamer-java
The problem seems to be related to padding 

--
You received this message because you are subscribed to the Google Groups "gstreamer-java" group.
To unsubscribe from this group and stop receiving emails from it, send an email to gstreamer-jav...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/gstreamer-java/8fe58dc4-ad16-094d-08fd-94bfe75e3c9a%40dpsoftware.org.

Davide Perini

unread,
Mar 5, 2023, 3:10:11 PM3/5/23
to gstream...@googlegroups.com
Mmm... Searching the internet for GStreamer padding, I somewhat understood what it is but have no idea on how to solve the problem.
Is it something that can be done by adding params to my pipeline?

Thanks
Davide 

Scooter Willis

unread,
Mar 5, 2023, 3:21:41 PM3/5/23
to gstream...@googlegroups.com
If you are doing screen capture make a black desktop(no icons) and a white square box in the middle. Would help you detect a pattern shift indicating a resolution problem in the file. 

Davide Perini

unread,
Dec 3, 2023, 10:30:10 AM12/3/23
to gstreamer-java
ok, following your suggestion I can see that the image is sliced when using some resolutions like 3440x1440 or 480x720 on a pipeline like this:
./gst-launch-1.0 d3d11screencapturesrc ! d3d11convert ! "video/x-raw(memory:D3D11Memory),width=3440,height=1440,sync=false" ! autovideosink

a less optimized pipeline like this works well.
./gst-launch-1.0 d3d11screencapturesrc ! d3d11convert ! d3d11download ! "video/x-raw(memory:SystemMemory),width=3440,height=1440,sync=false" ! autovideosink

The weird thing is that using the D3D11Memory pipeline on AMD cards creates this problem on other resolutions too.
For example I can correctly capture at 480x720 with an NVIDIA card but not on an AMD card.

I think that this issue should be related to GStreamer Java because running the same pipeline using the gst-launch.1.0 command has no problems, 
no matter the resolution, no matter the GPU vendor.

I attach two images, a test image with a black background and a white rectangle, 
and the image that is captured when the problem occurs.
At 480x720 that image is sliced on an AMD card, same code, same settings works well on Nvidia.

Other resolutions like 3440x1440 creates the problem on both AMD and Nvidia.

Is there someone that can help me understanding what can be the cause of this problem?

I asked on the upstream channel and someone answered me:
"Is this resolution in need of a bit of alignment? The slice length must be
divisible by a number. some are 32 and you have to pad the buffer or it warps (or worse)"

The thing that confuses me is why 480x720 works well on Nvidia and not on AMD and why it works well on gst-launch-1.0 and not when using GStreamer Java...

gstreamer_screenshot.bmpgstreamer_screenshot_KO.bmp


Christophe Lafolet

unread,
Dec 3, 2023, 6:44:44 PM12/3/23
to gstream...@googlegroups.com
with the image screenshot_KO (480x270), we can see that lines are translated around 30 RGB pixels, giving a size of 30x3=90 bytes
the real size of buffer would be 480x360

d3d11screencapturesrc is in BGRA
are you sure the alpha channel have been correctly removed ?

your old pipeline was OK with d3d11download ?
did you read  the source code of d3d11download ?

Christophe

Christophe Lafolet

unread,
Dec 3, 2023, 6:54:47 PM12/3/23
to gstream...@googlegroups.com
sorry, the real slice of buffer would be 270x3+90=900

Davide Perini

unread,
Dec 4, 2023, 12:40:44 PM12/4/23
to gstream...@googlegroups.com
Yes, I wrote 480x720 but I was referring to a 480x270 screen capture, sorry, my fault.
I'm using format=BGRx so this should be good.

how can I be sure that the alpha channel is removed and why should I remove it?

this pipeline works ok (the old one):

./gst-launch-1.0 d3d11screencapturesrc ! d3d11convert ! d3d11download ! "video/x-raw(memory:SystemMemory),width=3440,height=1440,sync=false" ! autovideosink

this one not:
./gst-launch-1.0 d3d11screencapturesrc ! d3d11convert ! "video/x-raw(memory:D3D11Memory),width=3440,height=1440,sync=false" ! autovideosink

no I haven't read the d3d11download source code :)

can you explain me what is this slice please?
270x3+90=900
how do you calculated it?
how can I use this number?

thank you very much.

Davide Perini

unread,
Dec 9, 2023, 9:29:27 AM12/9/23
to gstream...@googlegroups.com
I add a question to the previous...
Is there a way to detect if the captured image needs "alignment" in order to switch to the less efficient pipeline that does not resize the image on the GPU?

Thanks
Davide

Neil C Smith

unread,
Dec 10, 2023, 7:43:27 AM12/10/23
to gstream...@googlegroups.com
On Sun, 3 Dec 2023 at 15:30, Davide Perini <sblan...@gmail.com> wrote:
> I think that this issue should be related to GStreamer Java because running the same pipeline using the gst-launch.1.0 command has no problems,
> no matter the resolution, no matter the GPU vendor.

Are you using autovideosink or appsink with the GStreamer Java
pipeline? Using autovideosink should not differ when called from
Java. Please share exactly how you're configuring things?

On the other hand, appsink doesn't behave like autovideosink, so a
difference would be expected.

What exactly are you doing with the capture? What data do you want
and how are you processing / displaying it?

Best wishes,

Neil

--
Neil C Smith
Codelerity Ltd.
www.codelerity.com

Codelerity Ltd. is a company registered in England and Wales
Registered company number : 12063669
Registered office address : 3rd Floor Suite, 207 Regent Street,
London, W1B 3HH, England

Davide Perini

unread,
Dec 16, 2023, 8:08:03 AM12/16/23
to gstreamer-java
Hi Neil,
thank you very much for the time spent answering me, I appreciate it.
Sorry for the late replay, I don't know why but I lost this message and I haven't replied to it in time.

No,
I'm not using autovideosink nor appsink with the GStreamer Java.

Basically I capture the screen in an rgbBuffer thanks to your wonderful binding, 
then I use that rgbBuffer (IntBuffer) to send the color information of the screen to a led strip to act like an ambilight.
This is the final result just to give you the idea:

This is the interesting code that launches the screen capture pipeline:
/**
* Launch Advanced screen grabber (DDUPL for Windows, ximagesrc for Linux)
*
* @param imageProcessor image processor utility
*/
public void launchAdvancedGrabber(ImageProcessor imageProcessor) {
  imageProcessor.initGStreamerLibraryPaths();
  Gst.init(Constants.SCREEN_GRABBER, "");
  AtomicInteger pipelineRetry = new AtomicInteger();
  pipelineRetry.getAndIncrement();
    log.info("Starting a new pipeline");
    GrabberSingleton.getInstance().pipe = new Pipeline();
    if (NativeExecutor.isWindows()) {
      DisplayManager displayManager = new DisplayManager();
      String monitorNativePeer = String.valueOf(displayManager.getDisplayInfo(MainSingleton.getInstance().config.getMonitorNumber()).getNativePeer());
      bin = Gst.parseBinFromDescription("d3d11screencapturesrc monitor-handle={0} ! d3d11convert\"".replace("{0}", monitorNativePeer), true);
    }
    vc = new GStreamerGrabber();
    GrabberSingleton.getInstance().pipe.addMany(bin, vc.getElement());
    Pipeline.linkMany(bin, vc.getElement());
    JFrame f = new JFrame(Constants.SCREEN_GRABBER);
    f.add(vc);
    vc.setPreferredSize(new Dimension(MainSingleton.getInstance().config.getScreenResX(), MainSingleton.getInstance().config.getScreenResY()));
    f.pack();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    GrabberSingleton.getInstance().pipe.play();
    f.setVisible(false);
}
  
this is the GStreamerGrabber:
/**
* Creates a new instance of GstVideoComponent
*/
public GStreamerGrabber(AppSink appsink) {
  this.videosink = appsink;
  videosink.set(Constants.EMIT_SIGNALS, true);
  AppSinkListener listener = new AppSinkListener();
  videosink.connect(listener);
  String gstreamerPipeline;
  gstreamerPipeline = "video/x-raw(memory:D3D11Memory),width=3440,height=1440,sync=false,";
  gstreamerPipeline = setFramerate(gstreamerPipeline);
  StringBuilder caps = new StringBuilder(gstreamerPipeline);
  videosink.setCaps(new Caps(caps.toString()));
  setLayout(null);
  setOpaque(true);
  setBackground(Color.BLACK);
}

NewSample:
@Override
public FlowReturn newSample(AppSink elem) {
  Sample sample = elem.pullSample();
  Structure capsStruct = sample.getCaps().getStructure(0);
  int w = capsStruct.getInteger("width");
  int h = capsStruct.getInteger("height");
  Buffer buffer = sample.getBuffer();
  ByteBuffer bb = buffer.map(false);
  if (bb != null) {
    rgbFrame(w, h, bb.asIntBuffer());
    buffer.unmap();
  }
  sample.dispose();
  return FlowReturn.OK;
}

This is the callback where I process the captured image
private class AppSinkListener implements AppSink.NEW_SAMPLE {

  public void rgbFrame(int width, int height, IntBuffer rgbBuffer) {

    // If the EDT is still copying data from the buffer, just drop this frame
    if (!bufferLock.tryLock()) {
      return;
    }
 
    HERE IS WHERE I PROCESS the IntBuffer (rgbBuffer) that contains the problematic buffer
    
    bufferLock.unlock();
  }
}

All the code is open source in the Luciferin project here:

By further investigating the problem it seems that when the problem happen, the received IntBuffer is bigger than what I expect.
At 3440x1440 resolution for example, 
I would expect an IntBuffer with a capacity of 
3440 x 1440 = 4.953.600 
but I receive a capacity of 4976640

Other resolutions like 1920x1080 seems to works well on NVIDIA GPUs but not on AMD GPUs.

This is pretty weird...
I have tried to screen capture a full screen white image, I attach the result.
As you can see from that image, 16 black pixels are added every 3440 pixels. 
Considering the fact that we are capturing a 3440 x 1440 display resolution,
if you do simple math:
16 * 1440 you get 23040
23040 is exactly the difference between the expected capacity and the received capacity
4.976.640 -  4.953.600 = 23040

I opened an issue on the GStreamer upstream branch but they pointed out that it may be an issue on the bindings/app.

Thank you Neil for this super awesome binding.

Davide
gstreamer_screenshot.jpg

Christophe Lafolet

unread,
Dec 16, 2023, 9:02:23 AM12/16/23
to gstream...@googlegroups.com
hello Davide,

if you try to get pixels from a gst buffer, you need to know the stride / offset for each plane of this buffer (for RGB, only one plane)

These informations can be retrieve from the video meta when this meta is attached the the buffer

example
3440+16=3456 -> stride
3456*1440=4976640

Christophe
--
You received this message because you are subscribed to the Google Groups "gstreamer-java" group.
To unsubscribe from this group and stop receiving emails from it, send an email to gstreamer-jav...@googlegroups.com.

Davide Perini

unread,
Dec 17, 2023, 9:40:37 AM12/17/23
to gstreamer-java
I discovered why I lost the Neil message before... GMAIL is putting the GStreamer Java mails in my SPAM folder, I don't know why Google should put in the SPAM folder a message from it's own Google Groups but...

Returning in topic.

Ok, now that I'm able to calculate the stride,
how can I use this infos to fix the screen capture problem using GStreamer Java?

I tried to do it "manually" with a dirty workaround like this that basically shifts the pixels by 16 for every row (every 3440 pixel):

IntBuffer rgbBuffer = IntBuffer.allocate(buffer.capacity());
for (int i=0; i<buffer.capacity(); i++) {
  rgbBuffer.put(buffer.get(i));
  if (i%3439 == 0) {
    i+= 16;
  }
}

The situation improved by a lot but there is still some "cut" in the final image.

I attach the completely scrambled image without my "workaround" (KO_gstreamer_screenshot.jpg)
and the same captured image with my workaround. (OK_gstreamer_screenshot.jpg)

Thanks
Davide

Image 
KO_gstreamer_screenshot.jpg
OK_gstreamer_screenshot.jpg

Christophe Lafolet

unread,
Dec 17, 2023, 10:01:47 AM12/17/23
to gstream...@googlegroups.com

for (h = 0; h < height; ++h) {
        for (w = 0; w < width; ++w) {
          guint8 *pixel = pixels + h * stride + w * pixel_stride;
      }
 }

I suppose pixel_stride=3 for RGB (4 for RGBA)

Christophe

Davide Perini

unread,
Dec 17, 2023, 12:48:52 PM12/17/23
to gstream...@googlegroups.com
Hi Christophe,
that made the trick, thank you very much, I really appreciate it.

It made the trick by using "manual math",
I'm not really good in using the Java binding.

What I'm not able to do is to retrieve the stride.

What I have from the lovely Java Binding is a callback like this:

private class AppSinkListener implements AppSink.NEW_SAMPLE {

    public void rgbFrame(int width, int height, IntBuffer rgbBuffer) {
with my "IntBuffer rgbBuffer" that contains all my RGBs pixels.

How can I get the stride inside that callback?
The only "useful" number that I'm able to retrieve in that callback is the capacity from the rgbBuffer.

From the capacity I can do some math to get the stride but I'm not sure if it's correct.

Suppose that we are using a 3440x1440 resolution, this is the calcultions I have done to retrieve the stride.
int capacity = rgbBuffer.capacity(); // at 3440x1440 resolution this number is 4.976.640 
final int exectedCapacityWithoutStride = 3440*1440; // this constant equals to 4.953.600
int tmp = capacity - exectedCapacityWithoutStride; // this equals to 23.040
int tmp2 = tmp / 1440; // that equals to 16
int stride = 3440 + tmp2; // that equals to your stride 3.456
is there a better or smarter way to get the stride using the Java Binding?

Thanks
Davide

Neil C Smith

unread,
Dec 18, 2023, 6:25:11 AM12/18/23
to gstream...@googlegroups.com
On Sat, 16 Dec 2023 at 13:08, Davide Perini <sblan...@gmail.com> wrote:
> No,
> I'm not using autovideosink nor appsink with the GStreamer Java.

Your code shows that GStreamerGrabber is using an AppSink.

Do you see any performance improvement from using D3D11 memory vs
system memory in your application (ie. without autovideosink)? I
suspect that you're trying to solve problems here that won't give you
any improvement.

If anything you might want to look at removing the memory location
from the caps completely. Move the caps to set width, height and
format between d3d11convert and d3d11download. Keep the appsink using
system memory.

Davide Perini

unread,
Dec 18, 2023, 9:33:39 AM12/18/23
to gstream...@googlegroups.com
Hi Neil,
thanks, again, for the answer.

I tried some benchmarks that shows how the screen capture influences a
3D rendering
in terms of absolute performance and resource usage and no, there is
nearly no difference between
using D3D11 Memory vs System Memory.

I haven't understood why, isn't D3D11 Memory supposed to scale the image
in video memory over system memory?
Isn't supposed to be faster?

I think that I lack some basic GStreamer knowledge here, it's never late
to learn something new
but this is obviously something for another mailing list... :)

Thanks for your input Neil, I'll start from there to move forward.

Davide

Christophe Lafolet

unread,
Dec 18, 2023, 1:50:48 PM12/18/23
to gstream...@googlegroups.com
To retrieve the stride with the video meta, you can use the following classes and buffer.getMeta(VideoMeta.API)
Don't forget to register VideoMeta

public interface GstVideoMetaAPI extends com.sun.jna.Library {
    
    GstVideoMetaAPI GSTVIDEO_API = GstNative.load("gstvideo", GstVideoMetaAPI.class);
    
    @Structure.FieldOrder({"meta", "buffer", "flags", "format", "id", "width", "height", "n_planes", "offset", "stride", "mapFn", "unmapFn", "alignment" })
    class GstVideoMetaStruct extends Structure {
        public volatile GstMetaAPI.GstMetaStruct.ByValue meta;
        public volatile Pointer buffer; // Pointer to GstBuffer
        public volatile int flags; // GstVideoFrameFlags
        public volatile int format;
        public int id;
        public int width;
        public int height;
        public int n_planes;
        public NativeLong[] offset = new NativeLong[4];
        public int[] stride = new int[4];
        public volatile Pointer mapFn; // map method
        public volatile Pointer unmapFn; // unmap method
        public volatile int alignment;
        
        public GstVideoMetaStruct() {
        }

        public GstVideoMetaStruct(Pointer p) {
            super(p);
            read();
        }
    }

}

public class VideoMeta extends Meta {
    
    private final GstVideoMetaStruct metaStruct;

    /**
     * Meta.API for VideoMeta.
     */
    public static final API<VideoMeta> API =
            new API<>(VideoMeta.class, "GstVideoMetaAPI");
    
    /**
     * Underlying GType name.
     */
    public static final String GTYPE_NAME = "GstVideoMeta";

    VideoMeta(Initializer init) {
        super(init);
        this.metaStruct = new GstVideoMetaStruct(init.ptr.getPointer());
    }

    public int getPlaneCount() {
        return this.metaStruct.n_planes;
    }
    
    /**
     * The plane line stride is the number of bytes between two consecutive lines in the buffer.
     * This number will vary depending on the frame's {@code VideoFormat} and decoder output.
     * @param planeIndex which plane to get the stride for, valid range is zero
     *            to {@link #getPlaneCount() getPlaneCount()} non-inclusive
     * @return the line stride for the specified plane
     */    
    public int getStrideForPlane(int planeIndex) {
        return this.metaStruct.stride[planeIndex];
    }

    /**
     * @param planeIndex which plane to get the offset for, valid range is zero
     *            to {@link #getPlaneCount() getPlaneCount()} non-inclusive
     * @return the line stride for the specified plane
     */
    public int getOffsetForPlane(int planeIndex) {
        return this.metaStruct.offset[planeIndex].intValue();
    }

}

Davide Perini

unread,
Dec 19, 2023, 4:10:38 AM12/19/23
to gstream...@googlegroups.com
Hi Christophe,
buffer.getMeta(VideoMeta.API).getStrideForPlane(0)
returns 0.

Does the problem depends on the fact that I haven't registered the VideoMeta?
What do you mean to register VideoMeta?
How can I register VideoMeta?

Thanks
Davide

christoph...@laposte.net

unread,
Dec 19, 2023, 7:27:40 AM12/19/23
to Davide Perini, gstream...@googlegroups.com


-------- Message original --------
De : Davide Perini <perini...@dpsoftware.org>
Date : mar. 19 déc. 2023 à 10:10
À : gstream...@googlegroups.com
Objet : Re: [gstreamer-java] Having problems in screen capturing 3440x1440 resolution, other resolutions wokrs well.

Davide Perini

unread,
Dec 19, 2023, 12:24:43 PM12/19/23
to gstream...@googlegroups.com
Thanks
Stream.of(registration(VideoMeta.class, VideoMeta.GTYPE_NAME, VideoMeta::new));
buffer.getMeta(VideoMeta.API).getStrideForPlane(0)
buffer.getMeta(VideoMeta.API).getStrideForPlane(1)        
stride is still 0 for every plane.

Davide Perini

unread,
Dec 20, 2023, 4:37:42 PM12/20/23
to gstream...@googlegroups.com
Following the Neil's suggestions fixed the problem completely,
no need to set the correct stride by following that suggestion...

Thank you all.
Davide
You received this message because you are subscribed to a topic in the Google Groups "gstreamer-java" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/gstreamer-java/q0juwEUJiWc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to gstreamer-jav...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/gstreamer-java/2ba134fd-2609-43a6-a1df-e2126283a5df%40dpsoftware.org.

Reply all
Reply to author
Forward
0 new messages