使用SDL2.0进行YUV显示


本文描述如何从ffmpeg decode出来的YUV frame到SDL2.0显示,重点在如何将ffmpeg decode的yuv format转换到SDL2.0可以显示的format,以及SDL如何显示。


为什么要用YUV显示

在使用软解方式实现一个视频播放器时,显示部分如果需要使用RGB format,由于ffmepg decode出来的format为YUV420,则需要做一次从YUV420到RGB32的转换,这个转换过程中涉及到大量的浮点运算,performance会比较低,导致视频播放帧率受到影响,如果可以直接使用YUV做显示,理论上performance会比使用RGB更好一些。

convert YUV420 to YUV420P(YV12)

1. SDL2.0支援的YUV format

从SDL2.0的官方文档可以了解到, SDL2.0所支援的YUV format如下:

YUV Formatcomment
SDL_PIXELFORMAT_YV12planar mode: Y + V + U (3 planes)
SDL_PIXELFORMAT_IYUVplanar mode: Y + U + V (3 planes)
SDL_PIXELFORMAT_YUY2packed mode: Y0+U0+Y1+V0 (1 plane)
SDL_PIXELFORMAT_UYVYpacked mode: U0+Y0+V0+Y1 (1 plane)
SDL_PIXELFORMAT_YVYUpacked mode: Y0+V0+Y1+U0 (1 plane)

2. ffmepg output YUV format

ffmepg decode output format为YUV420.

从上述可以看出,ffmpeg decode的output没有办法直接送给SDL2.0进行显示,需要将YUV420转换为SDL2.0支援的YUV format,YUV420和YV12(YUV420P)同为YUV420格式,只是排列方式不同,所以我们选取YV12作为我们的转换目标。

3. sample code

AVFrame* pFrameYUV;

pFrameYUV = av_frame_alloc();
if( pFrameYUV == NULL )
    return -1;

int numBytes = avpicture_get_size( PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height );
uint8_t* buffer = ( uint8_t* )av_malloc( numBytes * sizeof( uint8_t ) );

avpicture_fill( ( AVPicture* )pFrameYUV, buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height );

struct SwsContext* sws_ctx = NULL;
sws_ctx = sws_getContext( pCodecCtx->width, pCodecCtx->height, pCdoecCtx->pix_fmt, pCodecCtx->width, pCdoecCtx->height, PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL );

// use av_read_frame & avcodec_decode_video2 to get a complete frame 
sws_scale( sws_ctx, ( uint8_t const* const* )pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize );

save YUV420P to files

YUV420P的格式是先放整张图所有的Y data,然后是所有的U和V(U V顺序需要确认);
这样在将YUV420P存到file的时候,需要从AVFrame中奖data[0],data[1],data[2]的所有数据依次写入。

void SaveYuvFrame( AVFrame* pFrame, int width, int height, int iFrame ) {
    FILE* pFile;
    char szFilename[32];
    int y;

    // Open file
    sprintf( szFilename, "frame%d.yuv", iFrame );
    pFile = fopen( szFilename, "wb" );
    if( pFile == NULL ) {
        return;
    }

    // write yuv data
    fwrite( pFrame->data[0], 1, ( int )(pFrame->linesize[0] * height), pFile );
    fwrite( pFrame->data[1], 1, ( int )pFrame->linesize[1]*height, pFile );
    fwrite( pFrame->data[2], 1, pFrame->linesize[2]*height, pFile );

    // Close file
    fclose( pFile );
}

上面code中,没有将linesize存下来(linesize中有3个数据,y,v,v在每一行中的byte数目),以1920X1080的data为例,linesize={1920, 960, 960}

load YUV420P from files and input to SDL2.0

FILE* pFile = fopen( "frame0.yuv", "rb" );
if( pFile == NULL )
    return -1;

char* yuvdata[3];
int linesize[3] = {1920, 960, 960};
yuvdata[0] = ( char* )malloc( ( linesize[0] +  linesize[1] + linesize[2]) * pCodecCtx->height );
yuvdata[1] = yuvdata[0] + linesize[0] * pCodecCtx->height;
yuvdata[2] = yuvdata[1] + linesize[1] * pCodecCtx->height;

int size = fread( yuvdata[0], 1, linesize[0]*pCodecCtx->height, pFile );
size = fread( yuvdata[1], 1, linesize[1]*pCodecCtx->height, pFile );
size = fread( yuvdata[2], 1, linesize[2]*pCodecCtx->height, pFile );
fclose( pFile );

上面code中,由于yuv file中没有存linesize,所以预设一个linesize,实际使用时,在存yuv data时,可以将linesize当做file header存下来

SDL2.0 image display flow

// SDL init
SDL_Window *window;
SDL_Renderer *renderer;
SDL_RendererInfo info;
SDL_Surface *image;
SDL_Rect    rect;
Uint8 *imageYUV;
SDL_Texture *texture;
SDL_Event event;
Uint32 then, now, frames;
SDL_bool done = SDL_FALSE;

if (SDL_Init(SDL_INIT_VIDEO) < 0) {
    fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
    return 2;
}

/* Create the window and renderer */
window = SDL_CreateWindow("YUV speed test",
                          SDL_WINDOWPOS_UNDEFINED,
                          SDL_WINDOWPOS_UNDEFINED,
                          pCodecCtx->width, pCodecCtx->height,
                          SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE);
if (!window) {
    fprintf(stderr, "Couldn't set create window: %s\n", SDL_GetError());
    quit(5);
}

renderer = SDL_CreateRenderer(window, -1, 0);
if (!renderer) {
    fprintf(stderr, "Couldn't set create renderer: %s\n", SDL_GetError());
    quit(6);
}
SDL_GetRendererInfo(renderer, &info);
printf("Using %s rendering\n", info.name);

texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
if (!texture) {
    fprintf(stderr, "Couldn't set create texture: %s\n", SDL_GetError());
    quit(7);
}

rect.x = 0;
rect.y = 0;
rect.w = pCodecCtx->width;
rect.h = pCodecCtx->height;
// end

// get YUV420P frame data

SDL_UpdateTexture( texture, &rect, yuvdata[0], linesize[0] );
SDL_RenderClear( renderer );
SDL_RenderCopy( renderer, texture, &rect, &rect );
SDL_RenderPresent( renderer );
SDL_Delay( 1000 );
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SDL2.0 支持渲染 YUV4:2:0 格式的视频,需要使用 SDL_Texture 和 SDL_RenderCopy 函数来实现。 以下是一个简单的示例代码: ```c #include <SDL2/SDL.h> #include <stdio.h> const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; int main(int argc, char* argv[]) { SDL_Window* window = NULL; SDL_Renderer* renderer = NULL; SDL_Texture* texture = NULL; if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); return 1; } window = SDL_CreateWindow("YUV4:2:0 Video", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); if (window == NULL) { printf("Window could not be created! SDL_Error: %s\n", SDL_GetError()); return 1; } renderer = SDL_CreateRenderer(window, -1, 0); if (renderer == NULL) { printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError()); return 1; } texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT); if (texture == NULL) { printf("Texture could not be created! SDL_Error: %s\n", SDL_GetError()); return 1; } // YUV data Uint8* yPlane = NULL; Uint8* uPlane = NULL; Uint8* vPlane = NULL; int yPitch = 0; int uPitch = 0; int vPitch = 0; int w = 0; int h = 0; // read YUV file FILE* fp = fopen("video.yuv", "rb"); if (fp == NULL) { printf("Could not open YUV file!\n"); return 1; } // allocate memory for YUV data int bufferSize = SCREEN_WIDTH * SCREEN_HEIGHT * 3 / 2; Uint8* buffer = (Uint8*)malloc(bufferSize); // main loop SDL_Event event; bool quit = false; while (!quit) { while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { quit = true; } } // read YUV data if (fread(buffer, 1, bufferSize, fp) != bufferSize) { // end of file rewind(fp); } // set YUV data yPlane = buffer; uPlane = yPlane + SCREEN_WIDTH * SCREEN_HEIGHT; vPlane = uPlane + SCREEN_WIDTH * SCREEN_HEIGHT / 4; yPitch = SCREEN_WIDTH; uPitch = SCREEN_WIDTH / 2; vPitch = SCREEN_WIDTH / 2; w = SCREEN_WIDTH; h = SCREEN_HEIGHT; // update texture SDL_UpdateYUVTexture(texture, NULL, yPlane, yPitch, uPlane, uPitch, vPlane, vPitch); // clear screen SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); SDL_RenderClear(renderer); // render texture SDL_RenderCopy(renderer, texture, NULL, NULL); // update screen SDL_RenderPresent(renderer); } // free memory free(buffer); // cleanup SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; } ``` 在代码中,我们首先创建一个 SDL 窗口和渲染器。然后,使用 SDL_CreateTexture 函数创建一个 YV12 格式的纹理。接下来,读取 YUV 文件,并将数据设置到纹理中。在主循环中,使用 SDL_RenderCopy 函数将纹理渲染到屏幕上。最后,记得在程序结束前释放所有内存和资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值