C++ FFmpeg+OpenGL+IP摄像头:YUVJ420P

6 篇文章 0 订阅

1、前言

 参考博客:雷霄骅:OpenGL播放YUV420P(通过Texture,使用Shader)
                  闲聊代码:使用OpenGL播放yuv420p数据
                  闲聊代码:ffmpeg解码+OpenGL播放视频,yuv420p格式
源码下载:雷霄骅:simplest media play,可直接运行


2.代码实现步骤

2.1、首先我得自己用雷神的源码实现OpenGL播放YUV420P功能

2.1.1、新建C++空项目:OpenGL-texture

将雷神的“simplest_video_play_opengl_texture”项目中的代码直接拷贝过来,包括《Shader.fsh》《Shader.vsh》文件、《test_yuv420p_320x180.yuv》格式视频。测试使用,发现
问题一:textFileRead("Shader.vsh");处,出现 “const char *“ 类型的实参与 “char *” 类型的形参不兼容
解决办法参考:
在VS2019中依次点击项目->属性->C/C+±>语言->符合模式,将原来的“是”改为“否”即可。
问题二:error X3000: unrecognized identifier 'attribute'
解决办法:
依次选中文件,右键-->属性-->常规-->项类型,将原来的“HLSL 编译器”改为“不参与生成”即可。

2.1.2、修改源码中视频格式路径 

如此,一个glut自绘窗口便能正常运行播放了。

2.1.3、我的源码,只是丰富了备注

GitHub - van9420/OpenGL-texture: OpenGL播放YUV420P视频-参照雷神代码

2.2、应用到我的项目需求上:MFC--Picture控件中展示

2.2.1、新建MFC应用,基于对话框:MFC-OpenGL-texture

吸取前面出现的问题一、二的教训,这次我没将《Shader.fsh》《Shader.vsh》文件添加到项目中,只是复制到了文件夹内,问题一、二均没有出现。源于之前对这俩文件不熟悉,以为是需要在项目中添加这个格式的文件。
相比上面多了如下主要步骤:
        1、设定像素格式,为OpenGL与HDC连接做准备
        2、创建OpenGL渲染上下文,使得OpenGL可在上面绘制
        3、使用定时器调用绘图函数绘制 或者 在多线程中循环绘制

2.2.2、主要代码

1、设定像素格式

/// <summary>
/// 设置像素格式,为OpenGL与HDC连接做准备
/// </summary>
/// <param name="hDC">设备上下文(dc)的句柄</param>
/// <returns></returns>
BOOL XXX::SetWindowPixelFormat(HDC hDC)
{
    PIXELFORMATDESCRIPTOR pixelDesc;                // 像素格式
    pixelDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);// 结构大小
    pixelDesc.nVersion = 1;                         // 结构版本
    pixelDesc.dwFlags = PFD_DRAW_TO_WINDOW |        // (PFD_DRAW_TO_WINDOW)  绘制到窗口,
        PFD_SUPPORT_OPENGL |                        // (PFD_SUPPORT_OPENGL)  绘制到窗口支持opengl
        PFD_DOUBLEBUFFER |                          // (PFD_DOUBLEBUFFER)    告知OpenGL如何处理像素,采用双缓冲
        PFD_TYPE_RGBA;                              // (PFD_TYPE_RGBA)       颜色模式,像素类型 RGBA
    pixelDesc.iPixelType = PFD_TYPE_RGBA;           // 颜色模式,像素类型 RGBA
    pixelDesc.cColorBits = 32;                      // 颜色位数
    pixelDesc.cRedBits = 0;
    pixelDesc.cRedShift = 0;
    pixelDesc.cGreenBits = 0;
    pixelDesc.cGreenShift = 0;
    pixelDesc.cBlueBits = 0;
    pixelDesc.cBlueShift = 0;
    pixelDesc.cAlphaBits = 0;                       // RGBA颜色缓存中Alpha的位数
    pixelDesc.cAlphaShift = 0;                      // 现不支持置为0
    pixelDesc.cAccumBits = 0;                       // 累计缓存的位数
    pixelDesc.cAccumRedBits = 0;
    pixelDesc.cAccumGreenBits = 0;
    pixelDesc.cAccumBlueBits = 0;
    pixelDesc.cAccumAlphaBits = 0;
    pixelDesc.cDepthBits = 0;                       // 深度缓冲区位数
    pixelDesc.cStencilBits = 1;                     // 模板缓冲位数
    pixelDesc.cAuxBuffers = 0;                      // 辅助缓存为主
    pixelDesc.iLayerType = PFD_MAIN_PLANE;          // 层面类型:主层
    pixelDesc.bReserved = 0;
    pixelDesc.dwLayerMask = 0;
    pixelDesc.dwVisibleMask = 0;
    pixelDesc.dwDamageMask = 0;


    int PixelFormat;                                    // 选择匹配像素格式,并返回索引
    PixelFormat = ChoosePixelFormat(hDC, &pixelDesc);   // 匹配像素格式的索引
    if (PixelFormat == 0) // Choose default
    {
        PixelFormat = 1;
        if (DescribePixelFormat(hDC, PixelFormat,
            sizeof(PIXELFORMATDESCRIPTOR), &pixelDesc) == 0)
        {
            return FALSE;
        }
    }
    if (SetPixelFormat(hDC, PixelFormat, &pixelDesc) == FALSE)//设置像素格式
    {
        return FALSE;
    }
    return TRUE;
}

2、创建OpenGL渲染上下文

/// <summary>
/// 创建OpenGL渲染上下文,使得OpenGL可在上面绘制
/// </summary>
/// <param name="hDC"></param>
/// <returns></returns>
BOOL XXX::CreateViewGLContext(HDC hDC)
{
    // 创建一个新的OpenGL渲染描述表
    HGLRC hrenderRC = wglCreateContext(hDC);
    if (hrenderRC == NULL)
        return FALSE;
    // 设定OpenGL当前线程的渲染环境
    if (wglMakeCurrent(hDC, hrenderRC) == FALSE)
        return FALSE;
    return TRUE;
}

3、使用定时器调用绘图函数绘制 或者 在多线程中循环绘制
        注意:如果 创建DC与RC关联的函数不在同一个线程,则不属于同一个线程,结果显示是黑色,因为根本没有关联。

2.2.3、我的源码

GitHub - van9420/MFC-OpenGL-texture: MFC+OpenGL播放YUV420P视频到指定Picture控件中

2.3、FFmpeg软解码

FFmpeg的基本配置这里暂且先不再重复

2.3.1、主要代码

void XXX::display() 
{   
    //IP摄像机-视频流地址
    char* url = "rtsp://192.168.1.88:554/11";
    int ret;
    /**************************获取码流参数信息****************************************/
    AVFormatContext* fmt_ctx = NULL;    //包含码流参数较多的结构体
    AVDictionary* options = NULL;       //健值对存储工具,类似于c++中的map
    //参数设置解析
    av_dict_set(&options, "buffer_size", "1044000", 0); //buffer_size:减少卡顿或者花屏现象,相当于增加或扩大了缓冲区,给予编码和发送足够的时间
    av_dict_set(&options, "stimeout", "20000000", 0);   //stimeout:设置超时断开,在进行连接时是阻塞状态,若没有设置超时断开则会一直去阻塞获取数据,单位是微秒。
    av_dict_set(&options, "rtsp_transport", "tcp", 0);  //rtsp_transport:修改优先连接发送方式,可以用udp、tcp、rtp
    av_dict_set(&options, "tune", "zerolatency", 0);    //zerolatency:转码延迟,以牺牲视频质量减少时延    
    /***
    * 打开多媒体数据并且获取一些参数信息
    * ps:函数调用成功之后处理过的AVFormatContext结构体。
    * file:打开的视音频流的URL。
    * fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。
    * dictionay:附加的一些选项,一般情况下可以设置为NULL。
    **/
    ret = avformat_open_input(&fmt_ctx, url, NULL, &options);
    if (ret < 0)
    {
        fprintf(stderr, "Could not open input\n");
        goto end;
    }
    /***
    * 读取一部分视音频数据并且获得一些相关的信息(AVStream)
    * ic:输入的AVFormatContext。
    * options:额外的选项,目前没有深入研究过。
    **/
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret < 0)
    {
        fprintf(stderr, "Could not find stream information\n");
        goto end;
    }
    /***
    * 获取音视频对应的流索引(stream_index)
    **/
    ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (ret < 0)
    {
        fprintf(stderr, "Cannot find a video stream in the input file\n");
        goto end;
    }

    int video_stream = ret;             // 视频对应流索引
    av_dump_format(fmt_ctx, 0, url, 0); // 打印关于输入或输出格式的详细信息,最后一个参数0:打印输入流

    /*********************************获取解码器并解码***********************************/
    AVCodec* pCodec = NULL;             // 存储编解码器的结构体
    AVCodecContext* pCodecCtx = NULL;   // 解码器上下文
    AVPacket packet;                    // 解码前的音频或者视频数据
    AVFrame* frame = av_frame_alloc();  // 用来存储解码后的(或原始)音频或视频数据
                                        // 必须由av_frame_alloc()分配内存,同时必须由av_frame_free()释放
    avcodec_register_all();             // 注册解码器

    if (pCodec == NULL)
        pCodec = avcodec_find_decoder(fmt_ctx->streams[video_stream]->codecpar->codec_id);// 通过ID号查找解码器
    pCodecCtx = avcodec_alloc_context3(pCodec);                                           // 配置解码器,申请AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。
    avcodec_parameters_to_context(pCodecCtx, fmt_ctx->streams[video_stream]->codecpar);   // 该函数用于将流里面的参数,也就是AVStream里面的参数直接复制到AVCodecContext的上下文当中。
    ret = avcodec_open2(pCodecCtx, pCodec, NULL);                                         // 初始化一个视音频编解码器的AVCodecContext
    if (ret < 0)
    {
        fprintf(stderr, "Cannot open decode\n");
        goto end;
    }

    while (m_threadLoop)
    {
        uint8_t y[WIDTH * HEIGHT];
        uint8_t u[WIDTH * HEIGHT / 4];
        uint8_t v[WIDTH * HEIGHT / 4];


        if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)   //读取码流中的音频若干帧或者视频一帧
            break;
        if (video_stream == packet.stream_index)
        {
            ret = avcodec_send_packet(pCodecCtx, &packet); //发送视频一帧到解码器中
            if (ret < 0)
            {
                av_packet_unref(&packet);           // 将缓存空间的引用计数-1,并将Packet中的其他字段设为初始值。如果引用计数为0,自动的释放缓存空间。
                avcodec_flush_buffers(pCodecCtx);   // 清空内部缓存的帧数据
                continue;
            }

            while (ret >= 0)
            {
                ret = avcodec_receive_frame(pCodecCtx, frame);    // 从解码器中获取解码的输出数据。ret=0:成功,返回一帧数据
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) // AVERROR(EAGAIN):当前输出无效,用户必须发送新的输入,AVERROR_EOF:解码器已经完全刷新,当前没有多余的帧可以输出
                    break;
                else if (ret < 0)                                 // 对应其他的解码错误
                    break;

                /*****************使用OpenGL绘制yuvj420p******************/

                //把解码后的AVFrame数据复制到为OpenGL准备的3个缓冲区里
                int i;
                for (i = 0; i < frame->height; i++)
                    memcpy(y + i * frame->width, frame->data[0] + i * frame->linesize[0], frame->width);
                for (i = 0; i < frame->height / 2; i++)
                    memcpy(u + i * frame->width / 2, frame->data[1] + i * frame->linesize[1], frame->width / 2);
                for (i = 0; i < frame->height / 2; i++)
                    memcpy(v + i * frame->width / 2, frame->data[2] + i * frame->linesize[2], frame->width / 2);

                //----------------------OpenGL绘制主要代码-------------------------------
            }
        }
        av_packet_unref(&packet);
    }
    av_packet_unref(&packet);

    avcodec_flush_buffers(pCodecCtx);
    avcodec_close(pCodecCtx);  //close,如果为rk3399的硬件编解码,则需要等待MPP_Buff释放完成后再关闭?是否需要这样不知道

end:
    av_frame_free(&frame);
    avformat_close_input(&fmt_ctx);
    avcodec_free_context(&pCodecCtx);
}

2.3.2、我的源码

GitHub - van9420/MFC-FFmpeg-OpenGL

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++结合FFmpeg可以实现对RTSP流的拉取和处理。下面是一个使用C++FFmpeg的示例代码,用于拉取RTSP流并将其保存为本地文件: ```cpp #include <iostream> #include <fstream> #include <string> #include <thread> #include <chrono> extern "C" { #include <libavformat/avformat.h> } int main() { // 初始化FFmpeg库 av_register_all(); // 创建AVFormatContext对象 AVFormatContext* formatContext = avformat_alloc_context(); // 设置RTSP地址 std::string rtspUrl = "rtsp://example.com/stream"; // 打开RTSP流 if (avformat_open_input(&formatContext, rtspUrl.c_str(), nullptr, nullptr) != 0) { std::cerr << "无法打开RTSP流" << std::endl; return -1; } // 查找流信息 if (avformat_find_stream_info(formatContext, nullptr) < 0) { std::cerr << "无法获取流信息" << std::endl; return -1; } // 找到视频流索引 int videoStreamIndex = -1; for (unsigned int i = 0; i < formatContext->nb_streams; i++) { if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } // 检查是否找到视频流 if (videoStreamIndex == -1) { std::cerr << "未找到视频流" << std::endl; return -1; } // 获取视频流的解码器参数 AVCodecParameters* codecParameters = formatContext->streams[videoStreamIndex]->codecpar; // 查找视频流的解码器 AVCodec* codec = avcodec_find_decoder(codecParameters->codec_id); if (codec == nullptr) { std::cerr << "未找到解码器" << std::endl; return -1; } // 创建解码器上下文 AVCodecContext* codecContext = avcodec_alloc_context3(codec); if (codecContext == nullptr) { std::cerr << "无法创建解码器上下文" << std::endl; return -1; } // 将解码器参数复制到解码器上下文 if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) { std::cerr << "无法复制解码器参数到解码器上下文" << std::endl; return -1; } // 打开解码器 if (avcodec_open2(codecContext, codec, nullptr) < 0) { std::cerr << "无法打开解码器" << std::endl; return -1; } // 创建AVPacket对象 AVPacket* packet = av_packet_alloc(); // 创建AVFrame对象 AVFrame* frame = av_frame_alloc(); // 创建输出文件 std::ofstream outputFile("output.yuv", std::ios::binary); // 读取并保存视频帧 while (av_read_frame(formatContext, packet) >= 0) { // 检查是否为视频流 if (packet->stream_index == videoStreamIndex) { // 解码视频帧 avcodec_send_packet(codecContext, packet); while (avcodec_receive_frame(codecContext, frame) == 0) { // 将YUV数据写入文件 for (int y = 0; y < frame->height; y++) { outputFile.write(reinterpret_cast<char*>(frame->data[0] + y * frame->linesize[0]), frame->width); } for (int y = 0; y < frame->height / 2; y++) { outputFile.write(reinterpret_cast<char*>(frame->data[1] + y * frame->linesize[1]), frame->width / 2); outputFile.write(reinterpret_cast<char*>(frame->data[2] + y * frame->linesize[2]), frame->width / 2); } } } // 释放AVPacket对象 av_packet_unref(packet); // 暂停一段时间,模拟实时处理 std::this_thread::sleep_for(std::chrono::milliseconds(10)); } // 释放AVFrame对象 av_frame_free(&frame); // 释放AVPacket对象 av_packet_free(&packet); // 关闭解码器 avcodec_close(codecContext); // 释放解码器上下文 avcodec_free_context(&codecContext); // 关闭输入流 avformat_close_input(&formatContext); // 释放AVFormatContext对象 avformat_free_context(formatContext); // 关闭输出文件 outputFile.close(); return 0; } ``` 这段代码使用FFmpeg库打开RTSP流,查找视频流并解码视频帧,然后将YUV数据保存到本地文件中。你可以根据自己的需求进行修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值