如何从Python将视频内存映射到C?

6

我一直在用Python玩一些简单的图形组件,但为了提高性能,我想用C语言做一些工作。

我知道如何来回传递数组等数据,那很容易。但我想,如果我能创建一个带画布的窗口,传递一个指向视频内存的指针(当然不是物理视频内存),然后让Python负责将该内存放到屏幕上,而其余部分由C完成,这可能会更有益。可能是异步的,但我不知道这是否重要。

这种做法可行吗?或者我完全错了方向?

我的努力:

# graphics.py

import ctypes
import pygame

screen = pygame.display.set_mode((300,200))

f = ctypes.CDLL('/path/to/engine.so')

f.loop(screen._pixels_address)

并且

// engine.c

#include <stdint.h>

void loop(void *mem) {
    while(1) {
        uint8_t *p = (uint8_t*) mem;
      
        // Was hoping this would make some pixels change
        for(int i=0; i<20000; i++)
            *p=127;
    }
}

这并没有起作用,最终导致了崩溃。我并不感到意外,但这是我目前所得到的。

使用Python并非必须,但我想用C语言。我也知道,在大多数情况下,我的方法并不是最好的选择,但我喜欢使用C语言和老派的编程方式。


你能上传一些代码展示你想要提高速度的地方吗?根据我的经验,使用openCV和numpy的Python会非常快。 - Deepak Garud
@DeepakGarud 我还没有任何代码。只是我在C语言中相当熟练地操作指针,并且我喜欢用老派的方式做事。 - klutt
我试图理解你想做什么。你只是需要用Python将图像放在屏幕上吗?如果你正在循环处理图像数据,那你想要处理什么? - Deepak Garud
使用一些C语言的GUI库怎么样? - the busybee
相反,通用库的目的是抽象出底层平台。例如SDL、fltk、tkinter等。我不是在谈论Win32或X Window系统。 - the busybee
显示剩余4条评论
2个回答

3

大多数GUI工具包和游戏库都会提供一种或另一种方式来访问像素缓冲区。

一个简单入门的方法是使用pygame。可以将pygame的Surfaceget_buffer().raw数组传递给C程序。

import pygame

background_colour = (255,255,255) # For the background color of your window
(width, height) = (300, 200) # Dimension of the window

screen = pygame.display.set_mode((width, height))

f = CDLL('engine.so')
f.loop(screen.get_buffer().raw)

问题在于它给我缓冲区的副本,而不是指向缓冲区的指针。 - klutt
然而,pygame.Surface._pixels_address 可能会起作用。 - klutt
好眼力!是的,那应该可以。 - mnistic
我尝试了,但不幸的是它没有起作用。请查看我的更新问题。 - klutt
@klutt 理论上似乎有一个 get_buffer().write 方法,你可以复制缓冲区,修改它,然后将副本写回。但是,我无法让它起作用。 - asynts
1
@asynts 这有点像是对缓冲区的直接访问的相反方式 ;) - klutt

2
基本上,您想与Python应用程序共享缓冲区。有一个缓冲区协议专门用于此目的。实际上,您分享一个Py_buffer对象,该对象具有buflen字段。(我没有完全阅读整个内容,但您可以查看更多信息。)
事实证明,Surface.get_buffer()已经返回这样的缓冲区对象。换句话说,您可以直接将其传递给外部函数:
import pygame

background_colour = (255,255,255) # For the background color of your window
(width, height) = (300, 200) # Dimension of the window

screen = pygame.display.set_mode((width, height))

import engine # engine.so

b_running = True
while b_running:
    # Quit when 'X' button is pressed.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            b_running = False

    # Add some amount of delay.
    pygame.time.wait(50)

    # Give the buffer object to C.
    engine.paint_gray(screen.get_buffer())
    pygame.display.flip()

这里的关键是:
engine.paint_gray(screen.get_buffer())

这不完全是你原来在做的,但我认为你应该保留Python应用程序中的循环以处理事件。因此,这个函数只是在缓冲区上绘制。这是C语言中的实现方式:
// Related: https://docs.python.org/3/extending/extending.html

#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include <stdint.h>
#include <assert.h>

static PyObject* engine_paint_gray(PyObject *self, PyObject *args)
{
    Py_buffer buffer;

    if (!PyArg_ParseTuple(args, "s*", &buffer))
        assert(0);

    uint8_t *raw_buffer = buffer.buf;
    for (int index=0; index<buffer.len; ++index)
        raw_buffer[index] = 127;

    return Py_None;
}

static PyMethodDef EngineMethods[] = {
    {"paint_gray", engine_paint_gray, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL} /* sentinel */
};

static struct PyModuleDef enginemodule = {
    PyModuleDef_HEAD_INIT,
    "engine",
    NULL,
    -1,
    EngineMethods
};

PyMODINIT_FUNC
PyInit_engine(void)
{
    return PyModule_Create(&enginemodule);
}

请注意,我还调用了pygame.display.flip()。这是必要的,因为pygame保留两个缓冲区,一个前缓冲区绘制在屏幕上,另一个后缓冲区可以被修改。

差不多就行吧 - klutt
顺便说一句:如果你想的话,也可以用C语言来循环。当我测试时,我注意到关闭按钮没有工作,因为事件必须在Python中处理,这就是我更改它的原因。 - asynts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接