QT下使用ffmpeg+SDL实现音视频播放器,支持录像截图功能,提供源码分享与下载

news2024/11/24 3:03:25

前言:

    SDL是音视频播放和渲染的一个开源库,主要利用它进行视频渲染和音频播放。
SDL库下载路径:https://github.com/libsdl-org/SDL/releases/tag/release-2.26.3,我使用的是2.26.3版本,大家可以自行选择该版本或其他版本的库。

在这里插入图片描述

一、SDL库介绍:

    SDL2.lib、SDL2main.lib和SDL2test.lib是SDL库的不同部分和功能。

    SDL2.lib:这是SDL库的主要部分,包含了所有常用的SDL功能和函数。它提供了与窗口、渲染、音频、事件处理等相关的功能。

    SDL2main.lib:这是用于Windows平台上的SDL2的可执行文件的入口点的库文件。它包含了与Windows系统相关的代码,用于初始化SDL2和设置应用程序的入口点。

    SDL2test.lib:这是SDL测试库,包含了一些用于测试和验证SDL功能的测试代码和工具。

    通常情况下,您只需要链接SDL2.lib和SDL2main.lib就可以使用SDL库的大部分功能。SDL2test.lib主要是用于SDL开发的测试和验证,一般情况下不需要链接到您的应用程序中。

    请注意,如果你在Qt项目中使用了Qt的消息循环(例如使用QApplication::exec()),则应该使用SDL2main.lib而不是SDL2.lib。这是因为SDL2main.lib包含了一个定义了WinMain函数的模块,可以与Qt的消息循环兼容。如果你不使用Qt的消息循环,可以使用SDL2.lib。

音频方面:
SDL提供两种使音频设备取得音频数据方法:
a. push,SDL以特定的频率调用回调函数,在回调函数中取得音频数据(本文采用);
b. pull,用户程序以特定的频率调用SDL_QueueAudio(),向音频设备提供数据。

二、SDL常用函数介绍:

2.1. SDL初始化,初始化音频和视频模块以及时间模块

if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
TestNotNull(NULL, SDL_GetError());
}

2.2. 创建一个显示窗口给SDL winid是qt窗口的id,这样SDL窗口就会嵌入到Qt窗口里面

window = SDL_CreateWindowFrom((void *)winId());

SDL_Window* SDL_CreateWindow(const char* A,int B,int C,int D,int E,Uint32 F)
函数说明:创建窗口,成功返回指向SDL_Window的指针,失败返回NULL

2.3.为指定窗口创建渲染器上下文

SDL_Renderer* SDL_CreateRenderer(SDL_Window* window, int index, Uint32 flags)

2.4.将制定区域(srcrect)的纹理数据,拷贝到渲染目标尺寸为(dstrect)的渲染器上下文(renderer)中,为下一步的渲染做准备

int SDL_RenderCopy(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect)

参数的含义如下。
renderer:渲染目标。
texture:输入纹理。
srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入。
dstrect:选择渲染目标的一块矩形区域作为输出。设置为NULL的时候整个渲染目标作为输出。

2.5.将渲染器上下文中的数据,渲染到关联窗体上去

void SDL_RenderPresent(SDL_Renderer* renderer)

2.6.此函数可为渲染器上下文创建纹理

SDL_Texture* SDL_CreateTexture(SDL_Renderer* renderer,
Uint32 format,
int access,
int w,
int h)

2.7.使用新的像素数据更新给定的纹理矩形。也就是说,可以固定刷新纹理的某一分部区域

图像数据写入显存中
int SDL_UpdateTexture(SDL_Texture* texture,
const SDL_Rect* rect,
const void* pixels,
int pitch)

2.8.清理屏幕

int SDLCALL SDL_RenderClear(SDL_Renderer * renderer);

2.9.线程、锁、条件变量

SDL线程创建:SDL_CreateThread
SDL线程等待:SDL_WaitThead
SDL互斥锁:SDL_CreateMutex/SDL_DestroyMutex
SDL锁定互斥:SDL_LockMutex/SDL_UnlockMutex
SDL条件变量(信号量):SDL_CreateCond/SDL_DestoryCond
SDL条件变量(信号量)等待/通知:SDL_CondWait/SDL_CondSingal

10.读取事件

SDL_PollEvent
从事件队列中,读取事件的常用函数
如果时间队列中有待处理事件,返回1;如果没有可处理事件,则返回0

11.发送事件

int SDL_PushEvent(SDL_Event * event);

12.等待事件

SDL_WaitEvent

13.音频相关

SDL_PauseAudio(0) //恢复音频播放
SDL_PauseAudio(1) //暂停音频播放
SDL_CloseAudio() //关闭音频

三、ffmpeg+SDL实现音视频播放器:

3.1 支持实时流、支持视频文件的音视频播放

在这里插入图片描述
在这里插入图片描述

3.2 支持录像、支持截图功能

录像:
在这里插入图片描述

截图:
在这里插入图片描述

3.3 核心代码

cvideoplayer.h

#ifndef CVIDEOPLAYER_H
#define CVIDEOPLAYER_H

#include <QObject>
#include <QWidget>

#include <QtWidgets>
#include <SDL.h>
#include "commondef.h"
#include <QQueue>
#include "mp4recorder.h"

#define MAX_AUDIO_OUT_SIZE 8*1152

typedef struct audio_data
{
    char data[MAX_AUDIO_OUT_SIZE];
    int size;
}audio_data_t;


class cVideoPlayer : public QWidget
{
    Q_OBJECT
public:
    cVideoPlayer(QWidget *parent = nullptr, int nWidth = 704, int nHeight = 576);
    ~cVideoPlayer();

public:
    bool loadVideo(const QString &sUrl);
    void playVideo();
    void OpenAudio(bool bOpen);
    void Snapshot();
    void startRecord(bool bStart);

protected:
    void paintEvent(QPaintEvent *event) override;

public:
    QQueue<audio_data_t*> m_adq;

private:
    bool m_bRun = false;
    int m_nPlayWay = MEDIA_PLAY_STREAM;
    SDL_Window *m_pWindow = nullptr;
    SDL_Renderer *m_pRenderer = nullptr;
    SDL_Texture *m_pTexture = nullptr;
    AVFormatContext *m_pFormatCtx = nullptr;
    AVCodecContext* m_pVideoCodecCxt = nullptr;          //视频解码器上下文
    AVCodecContext* m_pAudioCodecCxt = nullptr;          //音频解码器上下文
    SwrContext *m_pAudioSwrContext = nullptr;            //音频重采样上下文
    AVFrame *m_pFrame = nullptr;
    AVFrame *m_pYuvFrame = nullptr;
    AVFrame *m_pPcmFrame = nullptr;
    struct SwsContext *m_pSwsCtx = nullptr;
    int m_nBufferSize = 0;
    uint8_t *m_pDstBuffer = nullptr;
    int m_nVideoIndex = -1;
    int m_nAudioIndex = -1;
    int64_t m_nStartTime;
    AVPacket m_packet;
    enum AVCodecID m_CodecId;
    enum AVCodecID m_AudioCodecId;
    int m_nSDLWidth;
    int m_nSDLHeight;

    //音频
    SDL_AudioSpec m_audioSpec;
    bool m_bSupportAudio = false;
    bool m_bPauseAudio = false;
    int m_nAudioSampleRate = 8000;                       //音频采样率
    int m_nAudioPlaySampleRate = 44100;                  //音频播放采样率
    int m_nAudioPlayChannelNum = 1;                      //音频播放通道数

    //mp4录像
    mp4Recorder m_mp4Recorder;
    bool m_bRecord = false;

    //截图
    bool m_bSnapshot = false;
    SDL_Surface *m_pSurface = nullptr;
};

#endif // CVIDEOPLAYER_H

cvideoplayer.cpp

#include "cvideoplayer.h"

QMutex g_lock;

// 音频处理回调函数。读队列获取音频包,解码,播放
void sdl_audio_callback(void *userdata, uint8_t *stream, int len)
{
    cVideoPlayer *pPlayer = (cVideoPlayer*)userdata;

    SDL_memset(stream, 0, len);

    if(pPlayer && pPlayer->m_adq.size() > 0)
    {
        QMutexLocker guard(&g_lock);
        audio_data_t* pAudioData = pPlayer->m_adq.dequeue();
        if(pAudioData->size == 0)
            return;

        len = (len>pAudioData->size?pAudioData->size:len);
        SDL_MixAudio(stream, (uint8_t *)pAudioData->data, len, SDL_MIX_MAXVOLUME);
        delete pAudioData;
    }
}

cVideoPlayer::cVideoPlayer(QWidget *parent, int nWidth, int nHeight) : QWidget(parent),
    m_nSDLWidth(nWidth), m_nSDLHeight(nHeight)
{
    this->resize(nWidth, nHeight);
}

cVideoPlayer::~cVideoPlayer()
{
    m_bRun = false;

    MY_DEBUG << "~cVideoPlayer 000";

    if(nullptr != m_pSurface)
    {
        SDL_FreeSurface(m_pSurface);
        m_pSurface = nullptr;
    }

    if(nullptr != m_pTexture)
    {
        SDL_DestroyTexture(m_pTexture);
        m_pTexture = nullptr;
    }

    MY_DEBUG << "~cVideoPlayer 111";

    if(nullptr != m_pRenderer)
    {
        SDL_DestroyRenderer(m_pRenderer);
        m_pRenderer = nullptr;
    }

    MY_DEBUG << "~cVideoPlayer 222";

    if(nullptr != m_pWindow)
    {
        SDL_DestroyWindow(m_pWindow);
        m_pWindow = nullptr;
    }

    MY_DEBUG << "~cVideoPlayer 333";

    SDL_CloseAudio();
    SDL_Quit();

    MY_DEBUG << "~cVideoPlayer 444";

    if(nullptr != m_pFrame)
    {
        av_frame_free(&m_pFrame);
        m_pFrame = nullptr;
    }

    if(nullptr != m_pYuvFrame)
    {
        av_frame_free(&m_pYuvFrame);
        m_pYuvFrame = nullptr;
    }

    MY_DEBUG << "~cVideoPlayer 555";

    if(nullptr != m_pVideoCodecCxt)
    {
        avcodec_close(m_pVideoCodecCxt);
        //m_pVideoCodecCxt = nullptr; //设置为nullptr会导致崩溃
    }

    MY_DEBUG << "~cVideoPlayer 666";

    if(m_pFormatCtx)
    {
        avformat_close_input(&m_pFormatCtx);
        avformat_free_context(m_pFormatCtx);
        m_pFormatCtx = nullptr;
    }

    MY_DEBUG << "~cVideoPlayer 777";

    if(nullptr != m_pSwsCtx)
    {
        sws_freeContext(m_pSwsCtx);
        m_pSwsCtx = nullptr;
    }

    MY_DEBUG << "~cVideoPlayer 888";

    if(nullptr != m_pDstBuffer)
    {
        av_free(m_pDstBuffer);
    }

    MY_DEBUG << "~cVideoPlayer end";
}

bool cVideoPlayer::loadVideo(const QString &sUrl)
{
    avformat_network_init();

    if(sUrl.contains("rtsp://") || sUrl.contains("http://")) //实时流
        m_nPlayWay = MEDIA_PLAY_STREAM;
    else
        m_nPlayWay = MEDIA_PLAY_FILE;

    m_pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&m_pFormatCtx, sUrl.toStdString().c_str(), nullptr, nullptr) != 0)
    {
        MY_DEBUG << "Failed to open video file";
        return false;
    }

    if (avformat_find_stream_info(m_pFormatCtx, nullptr) < 0)
    {
        MY_DEBUG << "Failed to find stream information";
        return false;
    }

    m_nVideoIndex = av_find_best_stream(m_pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    m_nAudioIndex = av_find_best_stream(m_pFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);

    if (m_nVideoIndex == -1)
    {
        MY_DEBUG << "Failed to find video stream";
        return false;
    }

    //查找并打开视频解码器
    m_pVideoCodecCxt = avcodec_alloc_context3(nullptr);
    if (avcodec_parameters_to_context(m_pVideoCodecCxt, m_pFormatCtx->streams[m_nVideoIndex]->codecpar) != 0)
    {
        MY_DEBUG << "Failed to copy codec parameters to codec context";
        return false;
    }

    const AVCodec *codec = avcodec_find_decoder(m_pVideoCodecCxt->codec_id);
    if (codec == nullptr)
    {
        MY_DEBUG << "Failed to find video decoder";
        return false;
    }

    if (avcodec_open2(m_pVideoCodecCxt, codec, nullptr) < 0)
    {
        MY_DEBUG << "Failed to open video decoder";
        return false;
    }

    m_CodecId = codec->id;

    //查找并打开音频解码器
    if(m_nAudioIndex > 0)
    {
        //查找音频解码器
        const AVCodec *pAVCodec = avcodec_find_decoder(m_pFormatCtx->streams[m_nAudioIndex]->codecpar->codec_id);
        if(!pAVCodec)
        {
            MY_DEBUG << "audio decoder not found";
            return false;
        }

        m_AudioCodecId = pAVCodec->id;

        //音频解码器参数配置
        if (!m_pAudioCodecCxt)
            m_pAudioCodecCxt = avcodec_alloc_context3(nullptr);
        if(nullptr == m_pAudioCodecCxt)
        {
            MY_DEBUG << "avcodec_alloc_context3 error m_pAudioCodecCxt=nullptr";
            return false;
        }
        avcodec_parameters_to_context(m_pAudioCodecCxt, m_pFormatCtx->streams[m_nAudioIndex]->codecpar);

        //打开音频解码器
        int nRet = avcodec_open2(m_pAudioCodecCxt, pAVCodec, nullptr);
        if(nRet < 0)
        {
            avcodec_close(m_pAudioCodecCxt);
            MY_DEBUG << "avcodec_open2 error m_pAudioCodecCxt";
            return false;
        }

        //音频重采样初始化
        if (nullptr == m_pAudioSwrContext)
        {
            if(m_pAudioCodecCxt->channel_layout <= 0 || m_pAudioCodecCxt->channel_layout > 3)
                m_pAudioCodecCxt->channel_layout = 1;

            MY_DEBUG << "m_audioCodecContext->channel_layout:" << m_pAudioCodecCxt->channel_layout;
            MY_DEBUG << "m_audioCodecContext->channels:" << m_pAudioCodecCxt->channels;

            m_pAudioSwrContext = swr_alloc_set_opts(0,
                                                   m_pAudioCodecCxt->channel_layout,
                                                   AV_SAMPLE_FMT_S16,
                                                   m_pAudioCodecCxt->sample_rate,
                                                   av_get_default_channel_layout(m_pAudioCodecCxt->channels),
                                                   m_pAudioCodecCxt->sample_fmt,
                                                   m_pAudioCodecCxt->sample_rate,
                                                   0,
                                                   0);
            auto nRet = swr_init(m_pAudioSwrContext);
            if(nRet < 0)
            {
                MY_DEBUG << "swr_init error";
                return false;
            }
        }

        m_nAudioSampleRate = m_pAudioCodecCxt->sample_rate;
        m_audioSpec.freq = m_nAudioSampleRate;                  // 采样率
        m_audioSpec.format = AUDIO_S16SYS;                      // S表带符号,16是采样深度,SYS表采用系统字节序
        m_audioSpec.channels = m_pAudioCodecCxt->channels;      // 声道数
        m_audioSpec.silence = 0;                                // 静音值
        m_audioSpec.samples = m_pAudioCodecCxt->frame_size;     // SDL声音缓冲区尺寸,单位是单声道采样点尺寸x通道数
        m_audioSpec.callback = sdl_audio_callback;              // 回调函数,若为NULL,则应使用SDL_QueueAudio()机制
        m_audioSpec.userdata = this;                            // 提供给回调函数的参数

        //打开音频设备并创建音频处理线程
        if(SDL_OpenAudio(&m_audioSpec, NULL) < 0)
        {
            MY_DEBUG << "SDL_OpenAudio failed.";
            return false;
        }
        if(nullptr == m_pPcmFrame)
            m_pPcmFrame = av_frame_alloc();

        m_bSupportAudio = true;
    }

    av_dump_format(m_pFormatCtx, 0, NULL, 0);

    m_pFrame = av_frame_alloc();
    m_pSwsCtx = sws_getContext(m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, m_pVideoCodecCxt->pix_fmt,
                            m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, AV_PIX_FMT_YUV420P,
                            SWS_BICUBIC, nullptr, nullptr, nullptr);

    m_nBufferSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, 1);

    m_pDstBuffer = (unsigned char*)av_malloc(m_nBufferSize);
    if (!m_pDstBuffer)
    {
        MY_DEBUG << "av_malloc error";
        return false;
    }

    m_pYuvFrame = av_frame_alloc();
    int ret = av_image_fill_arrays(m_pYuvFrame->data, m_pYuvFrame->linesize, m_pDstBuffer, AV_PIX_FMT_YUV420P, m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, 1);
    if(ret <= 0)
    {
        return false;
    }

    //SDL
    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
    {
        MY_DEBUG << "SDL_Init failed.";
        return false;
    }

    m_pWindow = SDL_CreateWindowFrom((void *)winId());
    if(!m_pWindow)
    {
        MY_DEBUG << "SDL_CreateWindowFrom failed.";
        return false;
    }

    SDL_ShowWindow(m_pWindow);

    m_pRenderer = SDL_CreateRenderer(m_pWindow, -1, 0);
    if(!m_pRenderer)
    {
        MY_DEBUG << "SDL_CreateRenderer failed.";
        return false;
    }

    m_pTexture = SDL_CreateTexture(m_pRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, m_pVideoCodecCxt->width, m_pVideoCodecCxt->height);
    if(!m_pTexture)
    {
        MY_DEBUG << "SDL_CreateTexture failed.";
        return false;
    }
    m_pSurface = SDL_CreateRGBSurface(0, m_nSDLWidth, m_nSDLHeight, 24, 0x000000FF, 0x0000FF00, 0x00FF0000, 0);
    
    m_bRun = true;
    m_nStartTime = av_gettime();

    return true;
}

void cVideoPlayer::playVideo()
{
    SDL_Event event;
    //启用音频子线程回调处理
    if(m_bSupportAudio)
        SDL_PauseAudio(0);

    while(m_bRun)
    {
        if(av_read_frame(m_pFormatCtx, &m_packet) >= 0)
        {
            if(m_bRecord)
            {
                //MY_DEBUG << "record...";
                AVPacket* pPkt = av_packet_clone(&m_packet);
                m_mp4Recorder.saveOneFrame(*pPkt, m_CodecId, m_AudioCodecId);
                av_packet_free(&pPkt);
            }

            if (m_packet.stream_index == m_nVideoIndex)
            {
                if(m_nPlayWay == MEDIA_PLAY_FILE)//本地文件播放延时处理
                {
                    AVRational time_base = m_pFormatCtx->streams[m_nVideoIndex]->time_base;
                    AVRational time_base_q = {1, AV_TIME_BASE}; // AV_TIME_BASE_Q;
                    int64_t pts_time = av_rescale_q(m_packet.dts, time_base, time_base_q);
                    //MY_DEBUG << "pts_time:" << pts_time;
                    int64_t now_time = av_gettime() - m_nStartTime;
                    if (pts_time > now_time)
                        av_usleep((pts_time - now_time));
                }

                avcodec_send_packet(m_pVideoCodecCxt, &m_packet);
                while (avcodec_receive_frame(m_pVideoCodecCxt, m_pFrame) == 0)
                {
                    sws_scale(m_pSwsCtx, m_pFrame->data, m_pFrame->linesize, 0, m_pVideoCodecCxt->height,
                              m_pYuvFrame->data, m_pYuvFrame->linesize);

                    SDL_UpdateTexture(m_pTexture, nullptr, m_pYuvFrame->data[0], m_pYuvFrame->linesize[0]);
                    SDL_RenderClear(m_pRenderer);
                    SDL_RenderCopy(m_pRenderer, m_pTexture, nullptr, nullptr);
                    SDL_RenderPresent(m_pRenderer);

                    if(m_bSnapshot)
                    {
                        SDL_RenderReadPixels(m_pRenderer, NULL, SDL_PIXELFORMAT_RGB24, m_pSurface->pixels, m_pSurface->pitch);

                        QString sImgePath = QString("%1screenshot.bmp").arg(SNAPSHOT_DEFAULT_PATH);

                        SDL_SaveBMP(m_pSurface, sImgePath.toUtf8().data());
                        m_bSnapshot = false;
                    }

                    SDL_PollEvent(&event);
                    if (event.type == SDL_QUIT)
                    {
                        MY_DEBUG << "event.type == SDL_QUIT";
                        m_bRun = false;
                        break;
                    }

                }
            }
            else if(m_packet.stream_index == m_nAudioIndex)
            {
                int nRet = avcodec_send_packet(m_pAudioCodecCxt, &m_packet);
                if(nRet != 0)
                {
                    av_packet_unref(&m_packet);
                    continue;
                }

                nRet = 0;
                while(!nRet)
                {
                    nRet = avcodec_receive_frame(m_pAudioCodecCxt, m_pPcmFrame);
                    if(nRet == 0)
                    {
                        audio_data_t* pAudioData = new audio_data_t;

                        uint8_t  *pData[1];
                        pData[0] = (uint8_t *)pAudioData->data;

                        //获取目标样本数
                        auto nDstNbSamples = av_rescale_rnd(m_pPcmFrame->nb_samples,
                                                                m_nAudioPlaySampleRate,
                                                                m_nAudioSampleRate,
                                                                AV_ROUND_ZERO);

                        //重采样
                        int nLen = swr_convert(m_pAudioSwrContext, pData, nDstNbSamples,
                                              (const uint8_t **)m_pPcmFrame->data,
                                              m_pPcmFrame->nb_samples);

                        if(nLen <= 0)
                        {
                            MY_DEBUG << "swr_convert error";
                            delete pAudioData;
                            continue;
                        }

                        //获取样本保存的缓存大小
                        int nOutsize = av_samples_get_buffer_size(nullptr, m_pAudioCodecCxt->channels,
                                                                 m_pPcmFrame->nb_samples,
                                                                 AV_SAMPLE_FMT_S16,
                                                                 0);
                        pAudioData->size = nOutsize;
                        QMutexLocker guard(&g_lock);
                        if(!m_bPauseAudio)
                            m_adq.enqueue(pAudioData);
                    }
                }
            }
            av_packet_unref(&m_packet);
        }
    }
}

void cVideoPlayer::OpenAudio(bool bOpen)
{
    if(bOpen)
    {
        m_bPauseAudio = false;
        SDL_PauseAudio(0);
    }
    else
    {
        m_bPauseAudio = true;
        SDL_PauseAudio(1);
    }
}

void cVideoPlayer::Snapshot()
{
    m_bSnapshot = true;
}

void cVideoPlayer::startRecord(bool bStart)
{
    if(bStart)
    {
        QString sPath = RECORD_DEFAULT_PATH;
        QDate date = QDate::currentDate();
        QTime time = QTime::currentTime();
        QString sRecordPath = QString("%1%2-%3-%4-%5%6%7.mp4").arg(sPath).arg(date.year()). \
                arg(date.month()).arg(date.day()).arg(time.hour()).arg(time.minute()). \
                arg(time.second());

        MY_DEBUG << "sRecordPath:" << sRecordPath;

        if(nullptr != m_pFormatCtx && m_bRun)
        {
            m_bRecord = m_mp4Recorder.Init(m_pFormatCtx, m_CodecId, m_AudioCodecId, sRecordPath);
        }

    }
    else
    {
        if(m_bRecord)
        {
            MY_DEBUG << "stopRecord...";
            m_mp4Recorder.DeInit();
            m_bRecord = false;
        }
    }
}

void cVideoPlayer::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    painter.fillRect(rect(), Qt::black);
}

3.4 开发过程问题处理

1)qtmain.lib 无法解析外部符号_main _winmain
这个错误通常是由于Qt和SDL使用了不同的入口函数引起的。Qt使用的是main函数作为入口,而SDL使用的是WinMain函数作为入口。当你在Qt项目中调用SDL函数时,链接器会找不到main或WinMain函数,从而导致链接错误。
解决方法:在main函数上增加#undef main,如下

#undef main
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

2)视频没渲染问题
创建好窗口以后需要调用 SDL_ShowWindow(window);显示SDL窗口

3.5 播放器工程下载

https://download.csdn.net/download/linyibin_123/88262235

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/942125.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构大全】你想要的都有,数组、链表、堆栈、二叉树、红黑树、B树、图......

目录 1.概述 2.线性结构 3.时间复杂度 4.查找算法 5.树 6.图 1.概述 博主之前写过一个完整的关于数据结构的系列文章&#xff0c;一共十三篇&#xff0c;内容包含&#xff0c;数组、链表、堆栈、队列、时间复杂度、顺序查找、二分查找、二叉树、二叉搜索树、平衡二叉树、…

【Android-Flutter】我的Flutter开发之旅

目录: 0、文档&#xff1a;1、在Windows上搭建Flutter开发环境&#xff08;1&#xff09;[使用中国镜像(❌详细看官方文档)](https://docs.flutter.dev/community/china)&#xff08;2&#xff09;[下载最新版Flutter SDK&#xff08;已包含Dart&#xff09;](https://docs.flu…

从项目中突显技能:在面试中讲述你的编程故事

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

华为数通方向HCIP-DataCom H12-821题库(单选题:141-160)

第141题 Router-LSA 能够描述不同的链路类型&#xff0c;不属于Router LSA 链路类型的是以下哪一项? A、Link Type 可以用来描述到末梢网络的连接&#xff0c;即 SubNet B、Link Type 可以用来描述到中转网络的连接&#xff0c;即 TranNet C、Link Type 可以用来描述到另一…

16.CSS菜单悬停特效

效果 源码 <!DOCTYPE html> <html> <head> <title>Creative Menu Item Hover Effects</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body><section><…

Vue项目中app.js过大,导致web初始化加载过慢问题

1、删除多余不需要的库&#xff1a; npm uninstall xxx 如例如moment库文件是很大的可以直接放到index.html文件直接CDN引入 2、修改/config/index.js配置文件&#xff1a;将productionGzip设置为false ​ 3、设置vue-router懒加载 懒加载配置&#xff1a; ​ 非懒加载配置&…

winpe使用AOMEI Backupper备份磁盘和操作系统

winpe系统说明 我们可以将winpe制作成再u盘启动&#xff1b; 我们可以在winpe上安装备份磁盘和操作的软件AOMEI Backupper&#xff1b; 磁盘备份和系统备份软件 AOMEI Backupper 直接双击exe即可 选择磁盘备份 点击添加磁盘 选择磁盘&#xff0c;点击添加 点击开始备份&…

django中使用websocket

python本身只支持http协议 使用websocket需要下载第三方库 pip install -U channels 需要在seting.py里配置&#xff0c;将我们的channels加入INSTALLED_APP里。 INSTALLED_APPS ( django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, …

扩散模型实战(七):Diffusers蝴蝶图像生成实战

推荐阅读列表&#xff1a; 扩散模型实战&#xff08;一&#xff09;&#xff1a;基本原理介绍 扩散模型实战&#xff08;二&#xff09;&#xff1a;扩散模型的发展 扩散模型实战&#xff08;三&#xff09;&#xff1a;扩散模型的应用 扩散模型实战&#xff08;四&#xf…

ASUS华硕天选4笔记本电脑FA507XV原厂Windows11系统22H2

天选四FA507X原装系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件 华硕电脑管家、奥创控制中心等预装程序&#xff0c;恢复出厂状态W11 链接&#xff1a;https://pan.baidu.com/s/1SPoFW7wR5KawGu-yMckNzg?pwdayxd 提取码&#xff1a;ayxd

听会议整理的几个问题整理

听会议整理的几个问题整理 AR与NAR正样本和负样本数据蒸馏 AR与NAR 正样本和负样本 对于ar 和nar ar虽然从一个人来看是串行的&#xff0c;但是可以对用户进行并行 nar对于一个人的需求是并行的。但是对于多用户是无法并行的 所以ar并非一定效率低 数据蒸馏

CUDA小白 - NPP(2) -图像处理-算数和逻辑操作

cuda小白 原文链接 NPP GPU架构近些年也有不少的变化&#xff0c;具体的可以参考别的博主的介绍&#xff0c;都比较详细。还有一些cuda中的专有名词的含义&#xff0c;可以参考《详解CUDA的Context、Stream、Warp、SM、SP、Kernel、Block、Grid》 常见的NppStatus&#xff0c…

手机无人直播软件有哪些,又有哪些优势?

如今&#xff0c;随着智能手机的普及和移动互联网的发展&#xff0c;手机无人直播成为了一个炙手可热的领域。手机无人直播软件为用户提供了便捷、灵活的直播方式&#xff0c;让更多商家人能够实现自己的直播带货的梦想。接下来&#xff0c;我们将探讨手机无人直播软件有哪些&a…

解密算法与数据结构面试:程序员如何应对挑战

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

el-upload调用内部方法删除文件

从Element UI 的官方文档中&#xff0c; Upload 上传组组件提供了on-remove和before-remove的文件删除的钩子属性&#xff08;回调方法名&#xff09;&#xff0c;但如何调用组件删除方法&#xff08;让该方法删除本地上传文件列表以及触发这两个钩子&#xff09;并无相关说明。…

解锁安全高效办公——私有化部署的WorkPlus即时通讯软件

在当今信息时代&#xff0c;高效的沟通与协作对于企业的成功至关重要。然而&#xff0c;随着信息技术的发展&#xff0c;保护敏感信息和数据安全也变得越来越重要。为了满足企业对于安全沟通和高效办公的需求&#xff0c;我们隆重推出私有化部署的WorkPlus即时通讯软件&#xf…

Marin说PCB之如何使用CAM350做Gerber compare ?

最近小编在追一部东北武侠喜剧&#xff08;鹊刀门传奇&#xff09;&#xff0c;大部分人员都是乡村爱情的人员演的&#xff0c;这部剧真的是超级搞笑&#xff0c;小编我以人格担保要是不搞笑的话&#xff0c;你来找我。 正当小编我周日在家里追剧的时候&#xff0c;手机上弹出了…

Git仓库简介

1、工作区、暂存区、仓库 工作区&#xff1a;电脑里能看到的目录。 暂存区&#xff1a;工作区有一个隐藏目录.git&#xff0c;是Git的版本库&#xff0c;Git的版本库里存了很多东西&#xff0c;其中最重要的就是称为stage&#xff08;或者叫index&#xff09;的暂存区&#xf…

【技术】SpringBoot Word 模板替换

SpringBoot Word 模板替换 什么是 Word 模板替换如何实现 Word 模板替换 什么是 Word 模板替换 模板一般是具有固定格式的内容&#xff0c;其中一部分需要替换。Word 模板通俗的讲是以 Word 的形式制作模板&#xff0c;固定格式和内容&#xff0c;然后将其中的一部分数据替换掉…

第 1 章 绪论 (三元组)

1. 示例代码&#xff1a; 1&#xff09;status.h /* DataStructure 预定义常量和类型头文件 */#ifndef STATUS_H #define STATUS_H/* 函数结果状态码 */ #define TRUE 1 /* 返回值为真 */ #define FALSE 0 /* 返回值为假 */ #define RET_OK 0 /* 返回值…