直接操作 SDL_Overlay 上的像素

本文详细介绍了如何在ffplay中使用YUV色彩空间进行点线绘制及RGB与YUV420P之间的相互转换,并提供了相关代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我想在ffplay 视频视频加一些东西比如透明菜单,交互界面等。起初直接把Surface Update,发现每隔大半秒就会闪烁。即使调整double buffer,
Delay 参数也无济于事。通过网上查找,其原因是 Surface 和 Overlay 是两套不同的机制,当Overlay 显示时,就会把相同区域的Surface 覆盖掉,不是真正的“Overlay”(文档里明确的这样说了)。看来Overlay 是绕不过去的了。下面摘录一段原文:
It looks like mplayer has an OSD as well, and it supports almost all RGB and 
YUV color schemes. It looks like they create an alpha buffer based on the 
destination and the image to overlay and use it to make a transparent blit. 

It would be really sweet if this could be integrated into SDL or at least a 
nice little addon to handle YUV_Surface and RGB_Surface. 

那我的一个想法是:
drawline on YUV color space.

经过昨天的摸索和今天一天的编码测试,我主要解决了:
o 在YUV Color Space 上画点和画线。
o 像素格式 RGB 与 YUV420P 之间相互转换。
o 把一个SDL_Surface blit 到 SDL_Overlay 上。

效果图:
直接操作 SDL_Overlay 上的像素 - ldy210@126 - ldy210@126的博客
 
下面给出代码:
/*file:sdl_yuv.c
 direct access yuv color space
author: ludi

quick and dirty, but it  is correct:
void drawHLine_(SDL_Overlay *yuv, int sx, int sy, int len)
{
    int w = yuv->pitches[0];
    memset(yuv->pixels[0]+sy*w + sx, 255, len*1);
    memset(yuv->pixels[1]+(sy*w + 2*sx)/4, 255, (len+1)/2);
    memset(yuv->pixels[2]+(sy*w + 2*sx)/4, 255, (len+1)/2);
}

todo: add scalability for blitSurface2YUV (really need this??).
history: 2011.11.05 initial startup.
*/
#include<string.h>
#include<SDL.h>

extern void  rgb2yuv(int r, int g, int b, int *y, int *u, int *v);
extern void  yuv2rgb(int y, int u, int v, int *r, int *g, int *b);

void fillRect(SDL_Overlay *yuv, SDL_Rect *rect, int y0, int u, int v)
{
    int y;
    int size = rect->w;
    int uv_size = (size-1)/2+1;
    int uv_off = 0;

    for(y = 0; y< rect->h; ++y)
    {
    memset(yuv->pixels[0] + (rect->y + y) * yuv->pitches[0] + rect->x, 
    y0, size);
if(y%2 == 0)
{
    memset(yuv->pixels[1] + (uv_off + rect->y /2) * yuv->pitches[1] + rect->x/2, 
    v, uv_size);
    memset(yuv->pixels[2] + (uv_off + rect->y /2) * yuv->pitches[2] + rect->x/2, 
    u, uv_size);
    ++uv_off;
}
    }
}

int blitSurface2YUV(SDL_Surface *src, SDL_Overlay *dst, SDL_Rect *dstrect)
{
    Uint8 r, g, b;
    int y1,u1,v1;
    int y,x;
    int height = src->h < dstrect->h ? src->h: dstrect->h;
    int width =  src->w < dstrect->w ? src->w: dstrect->w;
    int uv_off = 0;
    Uint32 pixel;

    if(dst->format != SDL_YV12_OVERLAY)return 1;

    for(y = 0; y < height; ++y)
    {
for(x = 0; x < width; ++x)
{
    switch(src->format->BitsPerPixel)
    {
case 8:
    pixel = *((Uint8*)src->pixels + y*src->pitch + x);
    break;
case 16:
    pixel = *((Uint16*)src->pixels + y*src->pitch/2 + x);
    break;
case 32:
    pixel = *((Uint32*)src->pixels + y*src->pitch/4 + x);
    break;
default:
    return -1;
    }
    SDL_GetRGB(pixel, src->format, &r, &g, &b);
    rgb2yuv(r, g, b, &y1, &u1, &v1);

memset(dst->pixels[0] + (dstrect->y + y) * dst->pitches[0] + (dstrect->x + x), 
(Uint8)y1, 1);

    if((x%2 == 0 ) && (y%2 == 0 ))
    {
memset(dst->pixels[1] + (uv_off + dstrect->y /2) * dst->pitches[1] + (dstrect->x/2 + x/2), 
(Uint8)v1, 1);
memset(dst->pixels[2] + (uv_off + dstrect->y /2) * dst->pitches[2] + (dstrect->x/2 + x/2), 
(Uint8)u1, 1);
    }
}
if(y%2 == 0)++uv_off;
    }
    return 0;
}


void drawHLine(SDL_Overlay *yuv, int sx, int sy, int len)
{
    SDL_Rect rect = {sx,sy, len, 1};
    fillRect(yuv, &rect, 255,255,255);
}


void drawDot(SDL_Overlay *yuv, int sx, int sy)
{
    drawHLine(yuv, sx,sy, 1);
}

int main(int argc, char **argv)
{
    int w,h;
    SDL_Surface *screen;
    SDL_Overlay *yuv;


    SDL_Init (SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);
    screen = SDL_SetVideoMode(450,350,0,0);
    yuv = SDL_CreateYUVOverlay(w = 400,h = 300,SDL_YV12_OVERLAY, screen);


    SDL_Rect rect0 = {0,0, w,h};
    fillRect(yuv, &rect0, 50,128,128);//RGB(50,50,50) white color
    
    SDL_Rect rect = {w/2,20,w/5,20};
    fillRect(yuv, &rect, 82,90,240);//RGB(255,0,0);
    rect.y += 30;
    fillRect(yuv, &rect, 144,54,34);//RGB(0,255,0)
    rect.y += 30;
    fillRect(yuv, &rect, 41,240,110);//RGB(0,0,255);
    rect.y +=30;
    fillRect(yuv, &rect, 210,16,146);//RGB(255,255,0);
    rect.y +=30;
    fillRect(yuv, &rect, 107,202,222);//RGB(255,0,255);
    rect.y +=30;
    fillRect(yuv, &rect, 169,166,16); //RGB(0,255,255);
    rect.y +=30;
    fillRect(yuv, &rect, 235,128,128);//RGB(255,255,255);
    rect.y +=30;
    fillRect(yuv, &rect, 16,128,128); //RGB(0,0,0);

    {
SDL_Rect rect2 = {0, 0, w, h/4};//a red banner at top
SDL_FillRect(screen, &rect2, 0xffff0000);
rect2.y = h/4; rect2.w /= 2;//move a little down, show the left half.
blitSurface2YUV(screen, yuv, &rect2);
    }
    
#if 1 //FIXME: drawHLine uses a constant color. 
    drawHLine(yuv, 0,10,w/2);
    
    drawHLine(yuv, 0,20,1);
    drawHLine(yuv, w/2,20,1);
    drawHLine(yuv, w/2,30,4);

    int i;
    for(i=0; i<10; i++)
    {
drawDot(yuv, i*w/10, 40);
    }
#endif
    

    for(;;)
    {//event pump, such that we can move the window with proper saving and restoring bg. 
SDL_Event event;
if(SDL_PollEvent(&event))
{
    if(event.type == SDL_QUIT)break;
    
}
SDL_Rect tmp = {10,10, w, h};
SDL_DisplayYUVOverlay(yuv, &tmp);
SDL_Flip(screen);
SDL_Delay(100);
    }
    
    SDL_FreeSurface(screen);//clean up
    SDL_Quit();
    return 0;
}

/*****************************************************************************************************/
/*file:rgb2yuv.c

NOTE: Y  values are conventionally shifted and scaled to the range [16, 235] rather than using the full range of [0, 255]. 
This confusing practice derives from the MPEG standards and 
explains why 16 is added to Y' and why the Y' coefficients in the basic transform sum to 220 instead of 255. 
U and V values, which may be positive or negative, 
are summed with 128 to make them always positive.[4]

ref http://en.wikipedia.org/wiki/YUV
*/
#include<stdio.h>

void  rgb2yuv(int r, int g, int b, int *y, int *u, int *v)
{
    int y0, u0, v0;

    y0 =  66*r + 129*g +  25*b;
    u0 = -38*r + -74*g + 112*b;
    v0 = 112*r + -94*g + -18*b;

    y0 = (y0+128)>>8;
    u0 = (u0+128)>>8;
    v0 = (v0+128)>>8;

    *y = y0 + 16;
    *u = u0 + 128;
    *v = v0 + 128;
}

void  yuv2rgb(int y, int u, int v, int *r, int *g, int *b)
{
    int r0,g0,b0;
    v = v - 128;
    u = u - 128;
    r0 = y + v + (v>>2) + (v>>3) + (v>>5);
    g0 = y - ((u>>2) + (u>>4) + (u>>5)) - ((v>>1) + (v>>3) + (v>>4) + (v>>5));
    b0 = y + u + (u>>1) + (u>>2) + (u>>6);

    *r = r0 > 255 ? 255: r0;
    *g = g0 > 255 ? 255: g0;
    *b = b0 > 255 ? 255: b0;
}

#if defined(GEN_EXE)

int main(int argc, char **argv)
{
    if(argc < 4)return printf("usage: %s R G B \nor    %s r Y U V\n",argv[0],argv[0]);
    if(argv[1][0] == 'r')
    {
int r,g,b;
yuv2rgb(atoi(argv[2]), atoi(argv[3]), atoi(argv[4]), &r,&g,&b);
printf("%d,%d,%d\n",r,g,b);
    }
    else
    {
int y, u, v;
rgb2yuv(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]), &y, &u, &v);
printf("%d,%d,%d\n", y,u,v);
    }
    return 0;
}
#endif
<think>我们正在使用SDL3的应用程序回调模式(由SDL_MAIN_USE_CALLBACKS定义),这种模式下,我们不需要写main函数,而是实现几个特定的回调函数。你的代码有一些问题,我将逐一指出并修正:1.在SDL_AppInit函数中,SDL_Init的返回值判断有误:SDL_Init返回0表示成功,非0表示失败。所以判断条件应该是if(SDL_Init(...)),而不是if(!SDL_Init(...))。2.同样在SDL_AppInit中,SDL_CreateWindowAndRenderer函数的返回值:返回0表示成功,非0失败。所以判断条件应该是if(SDL_CreateWindowAndRenderer(...)),注意这里是检查返回值是否非0(失败),但原代码用了!运算符,即检查0(成功)则进入错误处理。所以应该去掉!。3.在SDL_AppEvent函数中,我们处理SDL_EVENT_QUIT事件时,返回SDL_APP_SUCCESS,这会导致应用程序退出。但是注意,在SDL_AppEvent中返回SDL_APP_SUCCESS表示“应用已经请求退出,不要再调用SDL_AppIterate”,所以这是正确的。4.在SDL_AppIterate函数中,我们使用浮点数颜色,但是注意:SDL_SetRenderDrawColorFloat的第四个参数是alpha,这里使用了SDL_ALPHA_OPAQUE_FLOAT,即1.0f,这是正确的。5.在颜色计算中,将blue和green搞反了:我们通常期望的颜色顺序是红、绿、蓝,这里将green赋值给了blue,blue赋值给了green。你可以根据需要调整,但这样不是错误,只是颜色顺序不同。6.在SDL_AppQuit函数中,我们需要销毁窗口和渲染器,并退出SDL。原代码没有做这些,会导致内存泄漏。让我们修正这些问题,并给出完整的代码:</think> 您的代码展示了使用 SDL3 的回调模式(通过 `SDL_MAIN_USE_CALLBACKS` 定义)的初始实现框架。以下是我对代码的分析和优化建议: ### 代码分析 ```cpp #define SDL_MAIN_USE_CALLBACKS 1 #include <SDL3/SDL.h> #include <SDL3/SDL_main.h> static SDL_Window* window = NULL; static SDL_Renderer* renderer = NULL; SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { SDL_SetAppMetadata("Example Renderer Clear", "1.0", "com.example.renderer-clear"); // ▼ 问题1: SDL_Init() 返回0表示成功,非0表示失败 if (!SDL_Init(SDL_INIT_VIDEO)) { // 判断逻辑反了 SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); return SDL_APP_FAILURE; } // ▼ 问题2: 返回值检查逻辑错误(成功时返回0) if (!SDL_CreateWindowAndRenderer( // 应检查非0值 "example/renderer/clear", 640, 480, 0, &window, &renderer)) { SDL_Log("Couldn't create window/renderer:%s", SDL_GetError()); return SDL_APP_FAILURE; } return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { if (event->type == SDL_EVENT_QUIT) { return SDL_APP_SUCCESS; // 正确的退出请求 } return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppIterate(void* appstate) { const double now = SDL_GetTicks() / 1000.0; const float red = 0.5f + 0.5f * sin(now); const float green = 0.5f + 0.5f * sin(now + 2 * SDL_PI_D / 3); const float blue = 0.5f + 0.5f * sin(now + 4 * SDL_PI_D / 3); // ▼ 颜色顺序: red, blue, green → 非常规的蓝绿色调 SDL_SetRenderDrawColorFloat(renderer, red, blue, green, SDL_ALPHA_OPAQUE_FLOAT); SDL_RenderClear(renderer); SDL_RenderPresent(renderer); return SDL_APP_CONTINUE; } void SDL_AppQuit(void* appstate, SDL_AppResult result) { // ▼ 问题3: 缺少资源释放 } ``` ### 修正后的完整代码 ```cpp #define SDL_MAIN_USE_CALLBACKS 1 #include <SDL3/SDL.h> #include <SDL3/SDL_main.h> static SDL_Window* window = nullptr; static SDL_Renderer* renderer = nullptr; SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { if (SDL_Init(SDL_INIT_VIDEO) != 0) { // 修复1: 检查非0返回值 SDL_Log("SDL初始化失败: %s", SDL_GetError()); return SDL_APP_FAILURE; } SDL_SetAppMetadata("Renderer Demo", "1.0", "com.example.renderer"); // 创建带加速渲染的窗口 if (SDL_CreateWindowAndRenderer( "Renderer Clear", 640, 480, SDL_WINDOW_HIGH_PIXEL_DENSITY, &window, &renderer) != 0) // 修复2: 检查非0返回值 { SDL_Log("窗口创建失败: %s", SDL_GetError()); return SDL_APP_FAILURE; } return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { if (event->type == SDL_EVENT_QUIT) { return SDL_APP_SUCCESS; // 正常退出 } return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppIterate(void* appstate) { const double now = SDL_GetTicks() / 1000.0; // 常规RGB顺序的平滑色彩过渡 const float red = 0.5f + 0.5f * sin(now); const float green = 0.5f + 0.5f * sin(now + 2 * SDL_PI_F / 3); const float blue = 0.5f + 0.5f * sin(now + 4 * SDL_PI_F / 3); SDL_SetRenderDrawColorFloat(renderer, red, green, blue, 1.0f); SDL_RenderClear(renderer); SDL_RenderPresent(renderer); // 添加帧率控制 SDL_Delay(16); // ≈60 FPS return SDL_APP_CONTINUE; } void SDL_AppQuit(void* appstate, SDL_AppResult result) { // 修复3: 安全释放资源 if (renderer) SDL_DestroyRenderer(renderer); if (window) SDL_DestroyWindow(window); SDL_Quit(); } ``` ### 关键修正说明 1. **初始化检查**:SDL函数通常在失败时返回非零值 2. **资源释放**:`SDL_AppQuit()` 中添加了销毁渲染器和窗口的逻辑 3. **色彩通道**:将颜色顺序调整为常规的RGB(原代码为RBG) 4. **性能优化**: - 添加 `SDL_Delay(16)` 控制刷新率(≈60FPS) - 使用 `SDL_WINDOW_HIGH_PIXEL_DENSITY` 启用高DPI支持 5. **类型安全**:用 `nullptr` 替代 `NULL` 6. **浮点常量**:使用 `SDL_PI_F` 替代 `SDL_PI_D` 匹配 float 精度 ### 编译说明 使用以下命令编译(以Linux为例): ```bash g++ main.cpp -o sdl_app $(pkg-config sdl3 --cflags --libs) ``` 运行效果:窗口背景色会平滑过渡显示彩虹色光谱效果,响应退出事件后自动清理资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值