使用SDL渲染h264格式的视频

#include "xvideo_view.h"
#include "xsdl.h"

#include <iostream>

using namespace std;

extern "C"
{
#include <libavcodec/avcodec.h>
}

#pragma comment(lib, "avutil.lib")

XVideoView* XVideoView::Create(RenderType type)
{
    switch (type)
    {
    case XVideoView::SDL:
        return new XSDL();
        break;
    default:
        break;
    }


    // 如果不支持的话,就直接返回nullptr
    return nullptr;
}

XVideoView::~XVideoView()
{
    if (cache_)
        delete cache_;
    cache_ = nullptr;
}


bool XVideoView::DrawFrame(AVFrame* frame)
{
    if (!frame)
    {
        cout << "input frame can not be null" << endl;
        return false;
    }

    // 累积显示次数
    count_++;

    if (beg_ms_ <= 0)
    {
        beg_ms_ = clock();                                                // 开始计时
    }
    else if ((clock() - beg_ms_) / (CLOCKS_PER_SEC / 1000) >= 1000)        // 1s 计算一次fps
    {
        render_fps_ = count_;
        count_ = 0;
        beg_ms_ = clock();                                                // 重新计时
    }

    int linesize = 0;
    switch (frame->format)
    {
    case AV_PIX_FMT_YUV420P:
        return Draw(frame->data[0], frame->linesize[0],                // Y
                    frame->data[1], frame->linesize[1],                // U
                    frame->data[2], frame->linesize[2]                // V
        );
    case AV_PIX_FMT_NV12:
        if (!cache_)
        {
            // 若空间尚未分配,则申请一块较大的空间(4K的一幅图像的大小)
            cache_ = new unsigned char[4096 * 2160 * 1.5];
        }

        // PS: 注意!这里面涉及到一个内存对齐的问题!
        // 如果是400x300的YUV420P/NV12格式,它的linesize很可能会被FFmpeg自动进行字节对齐, 即当图像尺寸为400x300时,它的linesize可能
        // 不是400,而是416(假设ffmpeg是以16字节进行对齐)
        // 而在使用SDL进行渲染时,linesize若传递416,很可能导致渲染出现问题,为了避免出现这种情况,下面提供了一种逐行复制的策略。
        
        // 下面的内存拷贝主要目的是为了让数据是连续的
        linesize = frame->width;
        if (frame->linesize[0] == frame->width)
        {
            // ---------------- 若linesize与图像的宽度一致,说明未发生字节对齐 --------------- 
            // 拷贝所有Y分量
            memcpy(cache_, frame->data[0], frame->linesize[0] * frame->height); 
            // 拷贝所有的UV分量
            memcpy(cache_ + frame->linesize[0] * frame->height, frame->data[1], frame->linesize[1] * frame->height / 2);
        }
        else
        {
            // ---------------- 若linesize与图像的宽度一致,说明发生了字节对齐 --------------- 
            // 使用逐行拷贝的方式
            // 拷贝所有的Y分量
            for (int i = 0; i < frame->height; i++)
            {
                memcpy(cache_ + i * frame->width, 
                    frame->data[0] + i * frame->linesize[0], 
                    frame->width // 注意只需要拷贝frame->width个数即可,丢弃因为对齐产生的多余数据
                );
            }

            // 拷贝所有UV分量
            for (int i = 0; i < frame->height / 2; i++)
            {
                // 将指针定位到所有Y分量数据的结尾处,从所有Y分量数据的结尾处开始拷贝UV分量
                auto p = cache_ + frame->width * frame->height;
                memcpy(p + i * frame->width,
                    frame->data[1] + i * frame->linesize[1],
                    frame->width // 注意只需要拷贝frame->width个数即可,丢弃因为对齐产生的多余数据
                );
            }
        }

        return Draw(cache_, linesize);
    case AV_PIX_FMT_RGBA:
    case AV_PIX_FMT_BGRA:
    case AV_PIX_FMT_ARGB:
        return Draw(frame->data[0], frame->linesize[0]);
    default:
        break;
    }

    return false;
}

/**
 * @brief 返回当前时间戳
 * @return 当前时间戳
*/
long long NowMs()
{
    // 注意!!! 这里不要直接返回clock(), 这样的话只能在windows中当中使用,因为这个函数在windows当中返回的是毫秒数
    // 但是在linux当中这个值返回的是微秒数,所以需要除以 (CLOCKS_PER_SEC / 1000)
    // 因为在linux当中 CLOCKS_PER_SEC 这个值返回的是1000000, 但是在windows当中这个值返回的是1000.
    // 这样可以最大限度地保证兼容性和跨平台
    return clock() / (CLOCKS_PER_SEC / 1000);
}

AVFrame* XVideoView::Read()
{
    // 如果宽度小于0,高度小于0,且文件未成功打开,则直接返回
    if (width_ <= 0 || height_ <= 0 || !ifs_) return nullptr;

    // 若AVFrame内存空间已申请,但参数发生变化,则需要释放空间
    // 因为用户可能通过界面上的文本输入框重新设定图像大小
    if (frame_)
    {
        if (frame_->width != width_ ||
            frame_->height != height_ ||
            frame_->format != fmt_)
        {
            // 说明用户重新选择了界面参数(宽,高,像素格式),需要释放AVFrame空间
            av_frame_free(&frame_);
        }
    }

    // 若frame为空,则重新申请
    if (!frame_)
    {
        frame_ = av_frame_alloc();
        frame_->width = width_;
        frame_->height = height_;
        frame_->format = fmt_;

        frame_->linesize[0] = width_ * 4;            // 默认像素格式为RGBA
        if (frame_->format == AV_PIX_FMT_YUV420P)
        {
            frame_->linesize[0] = width_;            // Y
            frame_->linesize[1] = width_ / 2;        // U
            frame_->linesize[2] = width_ / 2;        // V
        }

        // 生成AVFrame的buff空间,使用默认对齐方式
        auto re = av_frame_get_buffer(frame_, 0);
        if (re != 0)
        {
            char buf[1024] = { 0 };
            av_strerror(re, buf, sizeof(buf) - 1);
            cout << buf << endl;
            av_frame_free(&frame_);
            return nullptr;
        }
    }

    if (!frame_) return nullptr;

    // 读取一帧数据
    if (frame_->format == AV_PIX_FMT_YUV420P)
    {
        ifs_.read((char*)frame_->data[0], frame_->linesize[0] * height_);            // 读取Y
        ifs_.read((char*)frame_->data[1], frame_->linesize[1] * height_ / 2);        // 读取U
        ifs_.read((char*)frame_->data[2], frame_->linesize[2] * height_ / 2);        // 读取V
    }
    else
    {
        ifs_.read((char *)frame_->data[0], frame_->linesize[0] * height_);
    }

    // 如果读取到文件末尾,则直接返回
    if (ifs_.gcount() == 0)
        return nullptr;

    // 否则返回读取后的AVFrame数据
    return frame_;
}

bool XVideoView::Open(std::string filepath)
{
    if (ifs_.is_open())
    {
        ifs_.close();
    }

    ifs_.open(filepath, ios::binary);

    return ifs_.is_open();
}
bool XSDL::Draw(const unsigned char* data, int linesize)
{
    if (!data)
    {
        cout << "input data is null" << endl;
        return false;
    }

    // 保证线程同步
    unique_lock<mutex> sdl_lock(mtx_);
    
    if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)
    {
        cout << "draw failed, param error" << endl;
        return false;
    }

    if (linesize <= 0)
    {
        switch (fmt_)
        {
        case XVideoView::RGBA:
        case XVideoView::ARGB:
            linesize = width_ * height_ * 4;
            break;
        case XVideoView::YUV420P:
            linesize = width_;
            break;
        default:
            break;
        }
    }

    if (linesize <= 0)
    {
        cout << "linesize is error" << endl;
        return false;
    }

    // 复制内存到显存
    auto re = SDL_UpdateTexture(texture_, NULL, data, linesize);
    if (re)
    {
        cout << "update texture failed" << endl;
        return false;
    }

    // 清理渲染器
    SDL_RenderClear(render_);

    // 如果用户手动设置了缩放,就按照用户设置的大小显示
    // 如果用户没有设置,就传递null, 采用默认的窗口大小
    SDL_Rect *prect = nullptr;
    if (scale_w_ > 0 || scale_h_ > 0)
    {
        SDL_Rect rect;
        rect.x = 0;
        rect.y = 0;
        rect.w = scale_w_;
        rect.h = scale_h_;
        prect = &rect;
    }
    
    
    // 拷贝材质到渲染器
    re = SDL_RenderCopy(render_, texture_, NULL, prect);
    if (re)
    {
        cout << "copy texture failed" << endl;
        return false;
    }

    // 显示
    SDL_RenderPresent(render_);

    return true;
}

参考:

音视频技术应用(17)- 开启DXVA2硬件加速, 并使用SDL显示 - 夜行过客 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值