ffmpeg第五弹:Qt+SDL+ffmpeg视频播放演示

一、前言

在前几篇文章当中,有提到过用源码去搭建ffmpeg的命令环境开发,为啥要这样去搭建环境,为什么不用直接用下面这个命令在ubuntu下安装多快,简单又方便:

sudo apt install ffmpeg

今天分享ffmepg第五弹:ffmpeg+qt+SDL的真正开发环境,就要用源码安装的方式去在qt里面调用ffmpeg相关的库;还记得之前源码搭建创建的三个文件夹不:

bin   ffmpeg_sources    ffmpeg_build

bin文件夹下是编译得到的二进制文件

txp@txp-virtual-machine:~/bin$ ls
ffmpeg  ffplay  ffprobe  lame  nasm  
ndisasm  x264

ffmpeg_sources文件下是下载的各种库的源码:

txp@txp-virtual-machine:~/ffmpeg_sources$ ls
fdk-aac                  lame-3.100.tar.gz     SDL2-2.0.14.tar.gz
ffmpeg                   libvpx                SVT-AV1
ffmpeg-4.2.1             nasm-2.14.02          x264
ffmpeg-4.2.1.tar.bz2     nasm-2.14.02.tar.bz2  x265_git
ffmpeg-snapshot.tar.bz2  opus
lame-3.100               SDL2-2.0.14

ffmpeg_build文件夹主要是ffmpeg的一些库文件,等下下面演示的模板就要调用ffmpeg相关的库:

txp@txp-virtual-machine:~/ffmpeg_build/lib$ ls
cmake          libmp3lame.a           libSDL2.la       libswscale.a
libavcodec.a   libmp3lame.la          libSDL2main.a    libvpx.a
libavdevice.a  libopus.a              libSDL2main.la   libx264.a
libavfilter.a  libopus.la             libSDL2.so       libx265.a
libavformat.a  libpostproc.a          libSDL2_test.a   pkgconfig
libavutil.a    libSDL2-2.0.so.0       libSDL2_test.la
libfdk-aac.a   libSDL2-2.0.so.0.14.0  libSvtAv1Enc.a
libfdk-aac.la  libSDL2.a              libswresample.a

因为我已经搭建好了开发环境,从现在来看的话,如果你直接用命令去安装ffmpeg的话,到时候我们在qt的环境中去调用ffmpeg的库,至少到目前为止我暂时不知道去如何配置相关路径来调用ffmpeg的库;所以我们明白了这点,那么就撸起袖子肝就是。

二、 qt环境搭建

玩过qt的朋友,对于这块应该比我熟悉多了;不过有可能有一些朋友可能没有接触过qt的话,为此我还是简单演示一下qt的安装步骤:

  • 首先我们要去qt的官网下载linux版本的源码安装包:
版本下载地址:
https://download.qt.io/archive/qt/5.12/5.12.10/
  • 开始安装qt:

这里我直接把qt的源码包下载到samba服务共享文件下,当然你也可以直接在ubuntu下载:

然后我在share目录创建一个qt文件夹,用存放qt安装的地方,然后直接运行这个源码文件:

接着就会出现qt的安装界面:

最终qt就安装完成了,但是如果你运行qt执行失败的话(注意qtcreator的所放在的路线):

txp@txp-virtual-machine:~/share/qt/Qt5.12.10/Tools/QtCreator/bin$ sudo ./qtcreator

Got keys from plugin meta data ("xcb")
QFactoryLoader::QFactoryLoader() checking directory path "/home/txp/share/qt/Qt5.12.10/Tools/QtCreator/bin/platforms" ...
loaded library "/home/txp/share/qt/Qt5.12.10/Tools/QtCreator/lib/Qt/plugins/platforms/libqxcb.so"
loaded library "Xcursor"
Segmentation fault (core dumped)

  • 解决qt运行失败的方法:

在这个脚本里面添加一句话:

然后接着再安装相关插件:

sudo apt install --reinstall libxcb-xinerama0

然后我们执行一下,就可以成功打开qt了:

还有一种方法直接打开qt,因为按照上面的这种方式打开的话,每次都要跑到这个目录去执行这条语句才行:

txp@txp-virtual-machine:~/share/qt/Qt5.12.10/Tools/QtCreator/bin$ 
sudo ./qtcreator

现在我们只要执行下面这条语句就不用这么麻烦了:

sudo chown -R txp:txp ~/.config/

然后你就可以像在windows环境下去直接打开这个软件就行:

三、牛刀小试,调用ffmpeg库

3.1 示例模板,显示打印ffmpeg的版本:

现在我们打开刚才安装好的qt软件,来创建一个工程:

最终一个工程项目就建立好了:

我们可以看到两个文件,一个是以.pro结尾的qt工程管理配置文件,一个主源码文件,现在我们就简单使用ffmpeg库来打印ffmpeg的版本号:

  • 配置pro文件: pro文件里面默认是:
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt

SOURCES += \
        main.c

现在我们要加入ffmpeg_build目录下的ffmpeg库文件:

TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt

SOURCES += \
        main.c
INCLUDEPATH += /home/txp/ffmpeg_build/include
#LIBS += /home/txp/ffmpeg_build/lib/libSDL2.so

LIBS += /home/txp/ffmpeg_build/lib/libavcodec.a \
        /home/txp/ffmpeg_build/lib/libavdevice.a \
        /home/txp/ffmpeg_build/lib/libavfilter.a \
        /home/txp/ffmpeg_build/lib/libavformat.a \
        /home/txp/ffmpeg_build/lib/libavutil.a \
        /home/txp/ffmpeg_build/lib/libswresample.a \
        /home/txp/ffmpeg_build/lib/libswscale.a


  • main.c源码文件就可以修改成这样:
#include 
//包含ffmpeg头⽂件
#include "libavutil/avutil.h"
int main()
{
    printf("Hello FFMPEG, version is %s\n", av_version_info());
    return 0;
}

最终结果,显示ffmpeg的版本为 4.2.1:

20:01:19: Starting /home/txp/share/qt/workspace/build-linux_1-ffmpeg-Desktop_Qt_5_12_10_GCC_64bit-Debug/linux_1-ffmpeg ...
Hello FFMPEG, version is 4.2.1
20:01:19: /home/txp/share/qt/workspace/build-linux_1-ffmpeg-Desktop_Qt_5_12_10_GCC_64bit-Debug/linux_1-ffmpeg exited with code 0

注意:我们的ubuntu运行环境一定要是64位的,不然安装不了这个版本的qt;还有一点,其实我们在.pro文件里面,按键盘上的ctrl键然后把鼠标放到库文件路径上,是可以打开里面的:

3.2、搭建SDL,然后播放yuv格式的视频文件:

关于什么是SDL,这里我就不造轮子了,可以参考雷神的文章介绍:

https://blog.csdn.net/leixiaohua1020/article/details/11954039

下面我们去SDL的官网下源码包就行安装:

https://www.libsdl.org/download-2.0.php
  • 开始安装,先把这个源码包放到ffmpeg_sources目录下去,然后进行解压:
txp@txp-virtual-machine:~/ffmpeg_sources$ tar zxf SDL2-2.0.14.tar.gz 
txp@txp-virtual-machine:~/ffmpeg_sources$ ls
fdk-aac                  lame-3.100.tar.gz     SDL2-2.0.14.tar.gz
ffmpeg                   libvpx                SVT-AV1
ffmpeg-4.2.1             nasm-2.14.02          x264
ffmpeg-4.2.1.tar.bz2     nasm-2.14.02.tar.bz2  x265_git
ffmpeg-snapshot.tar.bz2  opus
lame-3.100               SDL2-2.0.14

然后执行:

txp@txp-virtual-machine:~/ffmpeg_sources/SDL2-2.0.14$ ./autogen.sh 
Generating build information using autoconf
This may take a while ...
Now you are ready to run ./configure

这里提示了你直接运行 ./configure:

 ./configure --prefix=/home/txp/ffmpeg_build --bindir=/home/txp/bin 

  • 然后进行编译:make -j4
txp@txp-virtual-machine:~/ffmpeg_sources/SDL2-2.0.14$ 
make -j4

  • 最后再执行sudo make install就行,SDL就安装成功了:
txp@txp-virtual-machine:~/ffmpeg_sources/SDL2-2.0.14$ 
sudo make install

下面我再创建一个工程,具体过程我就再写了,和第一个工程创建是一样的: 这里我的main.c文件里面的源代码,大家先不用管代码具体啥意思:

#include 
#include 
#include "SDL2/SDL.h"//包含SDL动态库文件

//自定义消息类型
#define REFRESH_EVENT   (SDL_USEREVENT + 1)     // 请求画面刷新事件
#define QUIT_EVENT      (SDL_USEREVENT + 2)     // 退出事件

//定义分辨率
// YUV像素分辨率
#define YUV_WIDTH   320
#define YUV_HEIGHT  240
//定义YUV格式
#define YUV_FORMAT  SDL_PIXELFORMAT_IYUV

int s_thread_exit = 0;  // 退出标志 = 1则退出

int refresh_video_timer(void *data)
{
    while (!s_thread_exit)
    {
        SDL_Event event;
        event.type = REFRESH_EVENT;
        SDL_PushEvent(&event);
        SDL_Delay(40);
    }

    s_thread_exit = 0;

    //push quit event
    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);

    return 0;
}
#undef main
int main(int argc, char* argv[])
{
    //初始化 SDL
    if(SDL_Init(SDL_INIT_VIDEO))
    {
        fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    // SDL
    SDL_Event event;                            // 事件
    SDL_Rect rect;                              // 矩形
    SDL_Window *window = NULL;                  // 窗口
    SDL_Renderer *renderer = NULL;              // 渲染
    SDL_Texture *texture = NULL;                // 纹理
    SDL_Thread *timer_thread = NULL;            // 请求刷新线程
    uint32_t pixformat = YUV_FORMAT;            // YUV420P,即是SDL_PIXELFORMAT_IYUV

    // 分辨率
    // 1. YUV的分辨率
    int video_width = YUV_WIDTH;
    int video_height = YUV_HEIGHT;
    // 2.显示窗口的分辨率
    int win_width = YUV_WIDTH;
    int win_height = YUV_WIDTH;

    // YUV文件句柄
    FILE *video_fd = NULL;
    const char *yuv_path = "yuv420p_320x240.yuv";

    size_t video_buff_len = 0;

    uint8_t *video_buf = NULL; //读取数据后先把放到buffer里面

    // 我们测试的文件是YUV420P格式
    uint32_t y_frame_len = video_width * video_height;
    uint32_t u_frame_len = video_width * video_height / 4;
    uint32_t v_frame_len = video_width * video_height / 4;
    uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;

    //创建窗口
    window = SDL_CreateWindow("Simplest YUV Player",
                           SDL_WINDOWPOS_UNDEFINED,
                           SDL_WINDOWPOS_UNDEFINED,
                           video_width, video_height,
                           SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
    if(!window)
    {
        fprintf(stderr, "SDL: could not create window, err:%s\n",SDL_GetError());
        goto _FAIL;
    }
    // 基于窗口创建渲染器
    renderer = SDL_CreateRenderer(window, -1, 0);
    // 基于渲染器创建纹理
    texture = SDL_CreateTexture(renderer,
                                pixformat,
                                SDL_TEXTUREACCESS_STREAMING,
                                video_width,
                                video_height);

    // 分配空间
    video_buf = (uint8_t*)malloc(yuv_frame_len);
    if(!video_buf)
    {
        fprintf(stderr, "Failed to alloce yuv frame space!\n");
        goto _FAIL;
    }

    // 打开YUV文件
    video_fd = fopen(yuv_path, "rb");
    if( !video_fd )
    {
        fprintf(stderr, "Failed to open yuv file\n");
        goto _FAIL;
    }
    // 创建请求刷新线程
    timer_thread = SDL_CreateThread(refresh_video_timer,
                                    NULL,
                                    NULL);

    while (1)
    {
        // 收取SDL系统里面的事件
        SDL_WaitEvent(&event);

        if(event.type == REFRESH_EVENT) // 画面刷新事件
        {
            video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd);
            if(video_buff_len <= 0)
            {
                fprintf(stderr, "Failed to read data from yuv file!\n");
                goto _FAIL;
            }
            // 设置纹理的数据 video_width = 320, plane
            SDL_UpdateTexture(texture, NULL, video_buf, video_width);

            // 显示区域,可以通过修改w和h进行缩放
            rect.x = 0;
            rect.y = 0;
            float w_ratio = win_width * 1.0 /video_width;
            float h_ratio = win_height * 1.0 /video_height;
            // 320x240 怎么保持原视频的宽高比例
            rect.w = video_width * w_ratio;
            rect.h = video_height * h_ratio;
//            rect.w = video_width * 0.5;
//            rect.h = video_height * 0.5;

            // 清除当前显示
            SDL_RenderClear(renderer);
            // 将纹理的数据拷贝给渲染器
            SDL_RenderCopy(renderer, texture, NULL, &rect);
            // 显示
            SDL_RenderPresent(renderer);
        }
        else if(event.type == SDL_WINDOWEVENT)
        {
            //If Resize
            SDL_GetWindowSize(window, &win_width, &win_height);
            printf("SDL_WINDOWEVENT win_width:%d, win_height:%d\n",win_width,
                   win_height );
        }
        else if(event.type == SDL_QUIT) //退出事件
        {
            s_thread_exit = 1;
        }
        else if(event.type == QUIT_EVENT)
        {
            break;
        }
    }

_FAIL:
    s_thread_exit = 1;      // 保证线程能够退出
    // 释放资源
    if(timer_thread)
        SDL_WaitThread(timer_thread, NULL); // 等待线程退出
    if(video_buf)
        free(video_buf);
    if(video_fd)
        fclose(video_fd);
    if(texture)
        SDL_DestroyTexture(texture);
    if(renderer)
        SDL_DestroyRenderer(renderer);
    if(window)
        SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;

}

.pro文件配置成:

TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt

SOURCES += \
        main.c
INCLUDEPATH += /home/txp/ffmpeg_build/include
LIBS += /home/txp/ffmpeg_build/lib/libSDL2.so

最终运行结果就可以看到在播放一个yuv格式的视频文件了:

注:这里的播放yuv格式的视频文件,我是事先已经准备好的:

同时要注意我们要把播放的视频文件放到工程目录下,不然播放是不会成功的:

四、总结:

现在ffmpeg真正的开发环境已经搭建完了,其实上面搭建环境蛮折腾人的,特别是源码安装ffmpeg。好了今天的文章就分享到这里了,如果你在看完文章后,有不懂的地方可以后台私聊我:

我是txp,一个半路出家的程序猿打工仔,我们下期见!

站在巨人的肩膀上:

https://blog.csdn.net/u012768805/article/details/98756925

https://ke.qq.com/webcourse/index.html#cid=468797&term_id=100561187&taid=4217090949457725&vid=5285890812708218640

https://www.libsdl.org/download-2.0.php

你可能感兴趣的:(音视频开发,ffmpeg)