记录 SDL硬件渲染(AVFrame)

说明

记录一下SDL方式来渲染图像的操作。这里封装成了类,导入SDL库后,复制代码实例化对象直接可用
SDL是跨平台的多媒体开发库,封装了不同系统中复杂的底层渲染细节,提供了统一的接口,极大提升了开发效率。音视频开发肯定离不开FFmpeg,而FFmpeg解码后的图像数据一般都是包含AVFrame中(主要为yuv420p格式)。所以对于解码后的AVFrame数据,线程内直接调用SDL相关接口即可实现图像显示,简单便捷。另外还有很重要的一点是,SDL可选择硬件渲染,对于多路视频将极大减少CPU的占用

代码

已经封装成了类,具体逻辑不复杂,直接看代码即可。需要注意以下两点:

  1. 使用SDK库需要开始运行时先初始化库,程序运行结束后释放库。SDL分为多个子系统,如音频、视频、事件、定时器等,这里是视频即先 SDL_Init(SDL_INIT_VIDEO); 结束后 SDL_Quit();
  2. Qt 环境开发,显示的窗口句柄通过winId()方法获得,格式为WId。其它环境只要得到正确的窗口句柄是一样的,SDL实际需要的是(void *)的一个句柄,自行修改转换即可。

头文件

//SDL渲染类
class VideoOutput
{
public:
    VideoOutput();
    ~VideoOutput();

    //只显示不负责释放 AVFrame为YUV420P格式
    void startDisplay(AVFrame *videoFrame);
    void stopDisplay();

    //设置渲染窗口句柄
    void setShowHandle(void *id);
    //设置视频宽、高、渲染方式
    bool setVideoParm(int width, int height, bool cpuMode = true);
    //释放
    void release();

private:
    //用到的相关变量
    SDL_Window *sdlWindow;
    SDL_Renderer *sdlRender;
    SDL_Texture *sdlTexture;

    //显示的句柄
    void *showId;
};

实现文件

VideoOutput::VideoOutput()
{
    sdlWindow = nullptr;
    sdlRender = nullptr;
    sdlTexture = nullptr;
    showId = nullptr;

    hasInit = false;
}

VideoOutput::~VideoOutput()
{
    release();
}

void VideoOutput::startDisplay(AVFrame *videoFrame)
{
    if(videoFrame == nullptr || !hasInit)
    {
        qDebug() << "视频帧无效或参数未设置";
        return;
    }

    //句柄尺寸变化 那么SDL渲染的窗口也会跟着变化
    SDL_Surface *sdlSurface = SDL_GetWindowSurface(sdlWindow);
    if (!sdlSurface)
    {
        qDebug() << "SDL_GetWindowSurface fail" << SDL_GetError();
        return;
    }

    //渲染
    int ret = SDL_UpdateYUVTexture(sdlTexture, NULL, videoFrame->data[0], videoFrame->linesize[0], videoFrame->data[1], videoFrame->linesize[1], videoFrame->data[2], videoFrame->linesize[2]);
    if (ret != 0)
        qDebug() << "SDL_UpdateYUVTexture fail" << SDL_GetError();

    ret = SDL_RenderClear(sdlRender);
    if (ret != 0)
        qDebug() << "SDL_RenderClear fail" << SDL_GetError();

    ret = SDL_RenderCopy(sdlRender, sdlTexture, NULL, NULL);
    if (ret != 0)
        qDebug() << "SDL_RenderCopy fail" << SDL_GetError();

    SDL_RenderPresent(sdlRender);
}

void VideoOutput::stopDisplay()
{
    if(!hasInit)
        return;

    SDL_SetRenderDrawColor(sdlRender, 240, 240, 240, 100);
    SDL_RenderClear(sdlRender);
    SDL_RenderPresent(sdlRender);
}

void VideoOutput::setShowHandle(void *id)
{
    this->showId = id;
}

bool VideoOutput::setVideoParm(int width, int height, bool cpuMode)
{
    if(this->showId == nullptr)
    {
        qDebug() << "video showId is nullptr";
        return false;
    }

    //SDL存在界面伸缩有时画面卡住 查阅资料应该是与SDL内部逻辑处理冲突 多次测试采用重置参数方法更为直接有效
    release();

    //默认cpu软件渲染模式 相比gpu硬件渲染兼容性更高
    SDL_RendererFlags flag;
    if(cpuMode)
        flag = SDL_RENDERER_SOFTWARE;
    else
        flag = SDL_RENDERER_ACCELERATED;

    sdlWindow = SDL_CreateWindowFrom(showId);
    if(!sdlWindow)
    {
        qDebug() << "SDL_CreateWindowFrom error" << SDL_GetError();
        return false;
    }

    sdlRender = SDL_CreateRenderer(sdlWindow, -1, flag);
    if (!sdlRender)
    {
        qDebug() << "SDL_CreateRenderer error" << SDL_GetError();
        return false;
    }

    sdlTexture = SDL_CreateTexture(sdlRender, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, width, height);
    if (!sdlTexture)
    {
        qDebug() << "SDL_CreateRenderer error" << SDL_GetError();
        return false;
    }

    SDL_ShowWindow(sdlWindow);
    hasInit = true;
    return true;
}

void VideoOutput::release()
{
    if(sdlTexture)
    {
        SDL_DestroyTexture(sdlTexture);
        sdlTexture = nullptr;
    }

    if(sdlRender)
    {
        SDL_DestroyRenderer(sdlRender);
        sdlRender = nullptr;
    }

    if(sdlWindow)
    {
        SDL_DestroyWindow(sdlWindow);
        sdlWindow = nullptr;
    }

    hasInit = false;
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
敬告:该系列的课程在抓紧录制更新中,敬请大家关注。敬告:本课程项目仅供学习参考,请不要直接商用,概不负责任何法律责任。 该系列的课程涉及:FFmpeg,WebRTC,SRS,Nginx,Darwin,Live555,等。包括:音视频、流媒体、直播、Android、视频监控28181、等。 我将带领大家一起来学习使用FFmpeg开发视频监控项目,并动手操练。具体内容包括: 一、视频监控的架构和流程二、FFmpeg4.3+SDL2+Qt5开发环境的搭建三、FFmpeg的SDK编程回顾总结并操练四、SDL2.0的编程回顾总结并操练五、颜色空间转换RGB和YUV的原理与实战六、Qt5+FFmpeg本地摄像头采集预览实战七、代码封装:摄像头h264/5编码并存储八、Qt5+FFmpeg单路网络摄像头采集预览九、Qt5+FFmpeg单路网络摄像头采集预览录制会看十、onvif与GB/T-28181的简介  音视频与流媒体是一门很复杂的技术,涉及的概念、原理、理论非常多,很多初学者不学 基础理论,而是直接做项目,往往会看到c/c++的代码时一头雾水,不知道代码到底是什么意思,这是为什么呢?   因为没有学习音视频和流媒体的基础理论,就比如学习英语,不学习基本单词,而是天天听英语新闻,总也听不懂。 所以呢,一定要认真学习基础理论,然后再学习播放器、转码器、非编、流媒体直播、视频监控、等等。   梅老师从事音视频与流媒体行业18年;曾在永新视博、中科大洋、百度、美国Harris广播事业部等公司就职,经验丰富;曾亲手主导广电直播全套项目,精通h.264/h.265/aac,曾亲自参与百度app上的网页播放器等实战产品。  目前全身心自主创业,主要聚焦音视频+流媒体行业,精通音视频加密、流媒体在线转码快编等热门产品。  
#include <SDL2/SDL.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h>int main() { AVFormatContext *pFormatCtx = NULL; AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVFrame *pFrame = NULL; AVPacket packet; int videoStream; SDL_Window *screen; SDL_Renderer *renderer; SDL_Texture *texture; int i, numBytes; uint8_t *buffer = NULL; // 首先打开视频文件 if(avformat_open_input(&pFormatCtx, "test.mp4", NULL, NULL) != 0) { printf("Couldn't open the file"); return -1; } // 找到视频流 if(avformat_find_stream_info(pFormatCtx, NULL) < 0) { printf("Couldn't find stream info"); return -1; } // 查找视频流索引 videoStream = -1; for(i=0; i<pFormatCtx->nb_streams; i++) { if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; break; } } if(videoStream == -1) { printf("Couldn't find a video stream"); return -1; } // 初始化SDL if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { printf("Couldn't initialize SDL: %s", SDL_GetError()); return -1; } // 创建窗口 screen = SDL_CreateWindow("FFmpeg Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, pFormatCtx->streams[videoStream]->codec->width, pFormatCtx->streams[videoStream]->codec->height, SDL_WINDOW_OPENGL); if(!screen) { printf("SDL: could not create window - exiting: %s", SDL_GetError()); return -1; } // 创建渲染器 renderer = SDL_CreateRenderer(screen, -1, 0); if(!renderer) { printf("SDL: could not create renderer - exiting: %s", SDL_GetError()); return -1; } // 创建纹理 texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, pFormatCtx->streams[videoStream]->codec->width, pFormatCtx->streams[videoStream]->codec->height); if(!texture) { printf("SDL: could not create texture - exiting: %s", SDL_GetError()); return -1; } // 获取解码器 pCodecCtx = pFormatCtx->streams[videoStream]->codec; pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec == NULL) { printf("Unsupported codec"); return -1; } // 打开解码器 if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { printf("Could not open codec"); return -1; } // 分配视频帧 pFrame = av_frame_alloc(); // 申请缓冲区 numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); // 将缓冲区放入视频帧中 avpicture_fill((AVPicture *)pFrame, buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); // 开始解码 while(av_read_frame(pFormatCtx, &packet) >= 0) { if(packet.stream_index == videoStream) { int frameFinished; // 解码视频帧 avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); // 如果解码完成 if(frameFinished) { SDL_UpdateYUVTexture(texture, NULL, pFrame->data[0], pFrame->linesize[0], pFrame->data[1], pFrame->linesize[1], pFrame->data[2], pFrame->linesize[2]); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); } } // 释放packet av_free_packet(&packet); } // 释放资源 av_free(buffer); av_free(pFrame); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0; }答案:使用FFmpeg采集视频数据,然后通过SDL渲染,可以使用如下代码例程:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zhou_Xintong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值