Ubuntu+Qt+SDL2+FFmpeg

此文档用于Ubuntu系统中搭建FFmpeg的开发环境。Qt作为开发软件,SDL2是一套开放源代码的跨平台多媒体开发库。

1.安装Qt

(1)下载版本:qt-opensource-linux-x64-5.9.8.run

image.png

(2)安装

给文件添加可执行权限:

sudo chmod -R 777 qt-opensource-linux-x64-5.9.8.run

开始安装:安装过程中选择全部安装即可。(需要根据提示注册帐号)

sudo ./qt-opensource-linux-x64-5.9.8.run

安装插件:

sudo apt-get install mesa-common-dev
sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev
sudo apt-get install libxcb-xinerama0
sudo apt-get install xcb

添加环境变量:

sudo vim ~/.bashrc

在文件末尾插入以下代码,注意安装路径

#add QT ENV
export PATH=/opt/Qt5.9.8/5.9.8/gcc_64/bin:$PATH
export LD_LIBRARY_PATH=/opt/Qt5.9.8/5.9.8/gcc_64/lib:$LD_LIBRARY_PATH
export QT_PLUGIN_PATH=/opt/Qt5.9.8/5.9.8/gcc_64/plugins:$QT_PLUGIN_PATH
export QML2_IMPORT_PATH=/opt/Qt5.9.8/5.9.8/gcc_64/qml:$QML2_IMPORT_PATH

使环境变量生效:

source ~/.bashrc

启动:

cd /opt/Qt5.9.8/Tools/QtCreator/bin
./qtcreator.sh

2.安装SDL2

(1)SDL的官网下源码包:release-2.26.3,或者下载地址。

(2)开始安装:

  先把这个源码包放到/home/zhou/code/ffmpeg/SDL2目录下去,然后进行解压。

(3)然后执行:

./autogen.sh

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

./configure --prefix=/home/zhou/code/ffmpeg/SDL2 --bindir=/home/zhou/code/ffmpeg/SDL2/bin

编译后生成物都会这上面这个路径下:

image.png

(5)编译和安装:

make -j128
sudo make install

3.安装FFmpeg

3.1 获取源码地址

  • Download FFmpeg
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
image.png

3.2 克隆项目到本地

(1)创建本地文件夹
    cd code
    mkdir ffmpeg

(2)初始化仓库
    git init

(3)克隆项目
    git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg

3.3 安装编译依赖

  ffmpeg编译过程中,依赖很多。官网给我们提供了apt命令,我们可以在编译ffmpeg之前,先安装这些依赖。如果占用空间太多,可以在编译完成之后再remove掉。里面既有能够直接通过apt安装的,也有必须通过源码安装的。

sudo apt-get update && sudo apt-get -y install \
  autoconf \
  automake \
  build-essential \
  cmake \
  git-core \
  libass-dev \
  libfreetype6-dev \
  libgnutls28-dev \
  libmp3lame-dev \
  libsdl2-dev \
  libtool \
  libva-dev \
  libvdpau-dev \
  libvorbis-dev \
  libxcb1-dev \
  libxcb-shm0-dev \
  libxcb-xfixes0-dev \
  meson \
  ninja-build \
  pkg-config \
  texinfo \
  wget \
  yasm \
  zlib1g-dev \
  libunistring-dev

安装NASM:参考https://pateo.feishu.cn/docx/O4Knd0IcSoLaNcxdljWc4x44ngd 4.2安装更高版本
sudo apt-get install nasm

支持h264编码
sudo apt-get install libx264-dev

支持h265
sudo apt-get install libx265-dev libnuma-dev

支持VP8/VP9编码
sudo apt-get install libvpx-dev

支持aac编码
sudo apt-get install libfdk-aac-dev

支持opus编码
sudo apt-get install libopus-dev

支持dash demuxer
sudo apt-get install libxml2 
sudo apt-get install libxml2-dev

sudo apt-get install libvpx-dev

3.4 配置编译选项

进入源码目录,配置编译选项

sudo ./configure \
  --extra-libs="-lpthread -lm" \
  --ld="g++" \
  --enable-gpl \
  --enable-gnutls \
  --enable-libass \
  --enable-libfdk-aac \
  --enable-libfreetype \
  --enable-libmp3lame \
  --enable-libopus \
  --enable-libvorbis \
  --enable-libvpx \
  --enable-libx264 \
  --enable-libx265 \
  --enable-libxml2 \
  --enable-nonfree \
  --disable-vaapi \
  --enable-shared

3.5 编译和安装

make -j128
sudo make install

3.6 添加环境变量

sudo gedit ~/.bashrc
将export LD_LIBRARY_PATH=/usr/local/lib/添加到最后一行即可。
source ~/.bashrc

4.测试项目

4.1 Qt中引用ffmpeg库

(1)新建项目

image.png

后面的选项一直默认就可以。

(2)代码

main.cpp

#include 
#include 

extern "C"{// 使用C++需要这个,C语言则不用
#include "libavutil/avutil.h"
}

using namespace std;

int main()
{
    printf("Hello FFMPEG, version is %s\n", av_version_info());
    return 0;
}

Helloworld.pro

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

SOURCES += \
        main.c

# add for ffmpeg by darcy
INCLUDEPATH += /usr/local/include

LIBS += /usr/local/lib/libavcodec.a \
        /usr/local/lib/libavdevice.a \
        /usr/local/lib/libavfilter.a \
        /usr/local/lib/libavformat.a \
        /usr/local/lib/libavutil.a \
        /usr/local/lib/libswresample.a \
        /usr/local/lib/libswscale.a

(3)运行结果

image.png

4.2 Qt中引用SDL2库

(1)前置条件,使用ffmpeg命令生成对应的yuv文件:

ffmpeg -i test.mp4 -t 6 -pix_fmt yuv420p -s 320x240 yuv420p_320x240.yuv

(1)效果如下:

  分辨率小了,所以播放变模糊了。

(2)参数说明:

  • -i: 表示要输入的流媒体文件

  • -t: 表示截取流媒体文件内容长度

  • -pix_fmt:指定要流媒体要转换的格式

  • -s:指定分辨率大小

将yuv420p_320x240.yuv文件放入到下面路径:(和执行文件放一起)

xxx/HelloWorld/build-HelloWorld-Desktop_Qt_5_9_8_GCC_64bit-Debug

(2)Helloworld.pro中添加如下代码:

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

SOURCES += \
        main.c

# add for SDL2 by darcy
INCLUDEPATH += /home/zhou/code/ffmpeg/SDL2/include
LIBS += /home/zhou/code/ffmpeg/SDL2/lib/libSDL2.so

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;

}

4.3 Qt+SDL2+FFMPEG

(1)FFmpeg解码一个视频流程如下图所示:

image.png

(2)SDL2.0显示YUV的流程图:

image.png

4.3.1 simplest_ffmpeg_player(标准版)代码

  此实例需要一个src01_480x272_22.h265的码流文件,同样可以通过ffmpeg生成。
(1)HelloWorld.pro

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

SOURCES += \
        main.c

# add by darcy
INCLUDEPATH += /usr/include \
               /usr/local/include \
               /home/zhou/code/ffmpeg/SDL2/include

LIBS += /usr/local/lib/libavformat.a \
        /usr/local/lib/libavdevice.a \
        /usr/local/lib/libavfilter.a \
        /usr/local/lib/libavcodec.a \
        /usr/local/lib/libavutil.a \
        /usr/local/lib/libswresample.a \
        /usr/local/lib/libswscale.a \
        /usr/local/lib/libpostproc.a

LIBS += /home/zhou/code/ffmpeg/SDL2/lib/libSDL2.so \
        -lxcb -lX11 -lXext -lvdpau -lasound -lsndio \
        -lfreetype -lass -lfdk-aac -lmp3lame -lopus \
        -lvorbis -lvorbisenc -lvpx -lx265 -lxml2 -lgnutls \
        /usr/local/lib/libz.a \
        /usr/lib/x86_64-linux-gnu/libpthread.so \
        /usr/local/lib/libx264.so \
        /usr/lib/x86_64-linux-gnu/libxcb.so \
        /usr/lib/x86_64-linux-gnu/libxcb-shm.a \
        /usr/lib/x86_64-linux-gnu/libxcb-xfixes.a \
        /usr/lib/x86_64-linux-gnu/libxcb-shape.a \
        /usr/lib/x86_64-linux-gnu/libXv.a

(2)main.c

#include 
#include "libavcodec/avcodec.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL2/SDL.h"

/** 最新版本avcodec_decode_video2()接口已经废弃
 * 该接口功能分成avcodec_send_packet()和avcodec_receive_frame()两步实现
 * 以下代码根据这两个接口,重新实现avcodec_decode_video2(),以便后面代码调用
 */
static int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt)
{
    int ret;

    *got_frame = 0;

    if (pkt) {
        ret = avcodec_send_packet(avctx, pkt); // 发送编码数据包
        // In particular, we don't expect AVERROR(EAGAIN), because we read all
        // decoded frames with avcodec_receive_frame() until done.
        if (ret < 0 && ret != AVERROR_EOF)
            return ret;
    }

    ret = avcodec_receive_frame(avctx, frame); //接收解码后数据
    if (ret < 0 && ret != AVERROR(EAGAIN))
        return ret;
    if (ret >= 0)
        *got_frame = 1;

    return 0;
}

int main() {
    AVFormatContext *pFormatCtx;
    AVCodecContext  *pCodecCtx;
    AVCodec         *pCodec;
    AVPacket        *packet;
    AVFrame         *pFrame,*pFrameYUV;

    // 视频文件放在项目路径里面
    char filepath[] = "src01_480x272_22.h265";
//    char filepath[] = "test.mp4";
    unsigned char *out_buffer;
    struct SwsContext *img_convert_ctx;
    int i;
    int videoindex = -1;
    int ret;
    int got_picture;

    //SDL---------------------------
    int screen_w = 0;
    int screen_h = 0;
    SDL_Window *screen;
    SDL_Renderer* sdlRenderer;
    SDL_Texture* sdlTexture;
    SDL_Rect sdlRect;

    // 1.注册输入/输出设备
    avdevice_register_all();
    // 2.执行网络库的全局初始化(可选)
    avformat_network_init();
    pFormatCtx = avformat_alloc_context();

    // 3.打开流媒体
    if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
        printf("Couldn't open input stream.\n");
        return -1;
    }

    // 4.获取更多的码流信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        printf("Couldn't find stream information.\n");
        return -1;
    }

    // 5.获取视频流的id
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            videoindex = i;
            break;
        }
    }

    if (videoindex == -1) {
        printf("Didn't find a video stream.\n");
        return -1;
    }

    pCodecCtx = avcodec_alloc_context3(NULL);
    if (!pCodecCtx)
        return AVERROR(ENOMEM);

    // 6.将音频流信息拷贝到新的AVCodecContext结构体中
    ret = avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);
    if (ret < 0)
        return -1;

    // 7.通codecId来查找解码器
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
        printf("Codec not found.\n");
        return -1;
    }

    // 8.打开编解码器
    if (avcodec_open2(pCodecCtx, pCodec,NULL) < 0) {
        printf("Could not open codec.\n");
        return -1;
    }

    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();
    out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,
        AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

    packet = (AVPacket *)av_malloc(sizeof(AVPacket));

    //Output Info-----------------------------
    printf("--------------- File Information ----------------\n");
    av_dump_format(pFormatCtx,0,filepath,0);
    printf("-------------------------------------------------\n");
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
                                     pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,
                                     SWS_BICUBIC, NULL, NULL, NULL);

    //(1)初始化SDL
    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
        printf( "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    screen_w = pCodecCtx->width;
    screen_h = pCodecCtx->height;
    //SDL 2.0 Support for multiple windows
    //(2)创建window
    screen = SDL_CreateWindow("Simplest ffmpeg player's Window",
                              SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                              screen_w, screen_h, SDL_WINDOW_OPENGL);

    if (!screen) {
        printf("SDL: could not create window - exiting:%s\n",SDL_GetError());
        return -1;
    }

    //(3)创建renderer
    sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
    //IYUV: Y + U + V  (3 planes)
    //YV12: Y + V + U  (3 planes)
    sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
                                   pCodecCtx->width, pCodecCtx->height);

    sdlRect.x=0;
    sdlRect.y=0;
    sdlRect.w=screen_w;
    sdlRect.h=screen_h;

    //SDL End----------------------
    // 9.读取音频流、视频流、字幕流,得到AVPacket数据包(未解码)
    while (av_read_frame(pFormatCtx, packet) >= 0) {
        if (packet->stream_index == videoindex) {
            // 10.解码过程:输入AVPacket,输出AVFrame
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
            if(ret < 0){
                printf("Decode Error.\n");
                return -1;
            }
            if (got_picture) {
                sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
                    pFrameYUV->data, pFrameYUV->linesize);

                //SDL---------------------------
#if 0
                SDL_UpdateTexture( sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0] );
#else
                //(4)更新YUV
                SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
                pFrameYUV->data[0], pFrameYUV->linesize[0],
                pFrameYUV->data[1], pFrameYUV->linesize[1],
                pFrameYUV->data[2], pFrameYUV->linesize[2]);
#endif

                SDL_RenderClear( sdlRenderer );
                // (5)复制到renderer
                SDL_RenderCopy( sdlRenderer, sdlTexture,  NULL, &sdlRect);
                // (6)
                SDL_RenderPresent( sdlRenderer );
                //SDL End-----------------------
                //Delay 40ms
                SDL_Delay(40);
            }
        }
    }
    //flush decoder
    //FIX: Flush Frames remained in Codec
    // darcy: 暂时没有理解此处的作用,删除后同样播放
    while (1) {
        ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
        if (ret < 0)
            break;
        if (!got_picture)
            break;
        sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
            pFrameYUV->data, pFrameYUV->linesize);

        //SDL---------------------------
        SDL_UpdateTexture( sdlTexture, &sdlRect, pFrameYUV->data[0], pFrameYUV->linesize[0] );
        SDL_RenderClear( sdlRenderer );
        SDL_RenderCopy( sdlRenderer, sdlTexture,  NULL, &sdlRect);
        SDL_RenderPresent( sdlRenderer );
        //SDL End-----------------------
        //Delay 40ms
        SDL_Delay(40);
    }

    sws_freeContext(img_convert_ctx);
    SDL_Quit();
    av_frame_free(&pFrameYUV);
    av_frame_free(&pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    av_packet_free(&packet);

    return 0;
}

4.3.2 simplest_ffmpeg_player_su(SU版)代码

  标准版的基础之上引入了SDL的Event。效果如下:

  • SDL弹出的窗口可以移动了

  • 画面显示是严格的40ms一帧

(1).pro

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

SOURCES += \
        main.c

# add by darcy
INCLUDEPATH += /usr/include \
               /usr/local/include \
               /home/zhou/code/ffmpeg/SDL2/include

LIBS += /usr/local/lib/libavformat.a \
        /usr/local/lib/libavdevice.a \
        /usr/local/lib/libavfilter.a \
        /usr/local/lib/libavcodec.a \
        /usr/local/lib/libavutil.a \
        /usr/local/lib/libswresample.a \
        /usr/local/lib/libswscale.a \
        /usr/local/lib/libpostproc.a

LIBS += /home/zhou/code/ffmpeg/SDL2/lib/libSDL2.so \
        -lxcb -lX11 -lXext -lvdpau -lasound -lsndio \
        -lfreetype -lass -lfdk-aac -lmp3lame -lopus \
        -lvorbis -lvorbisenc -lvpx -lx265 -lxml2 -lgnutls \
        /usr/local/lib/libz.a \
        /usr/lib/x86_64-linux-gnu/libpthread.so \
        /usr/local/lib/libx264.so \
        /usr/lib/x86_64-linux-gnu/libxcb.so \
        /usr/lib/x86_64-linux-gnu/libxcb-shm.a \
        /usr/lib/x86_64-linux-gnu/libxcb-xfixes.a \
        /usr/lib/x86_64-linux-gnu/libxcb-shape.a \
        /usr/lib/x86_64-linux-gnu/libXv.a

(2)main.c

#include 
#include "libavcodec/avcodec.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL2/SDL.h"

//Refresh Event
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)

int thread_exit = 0; // 退出
int thread_pause = 0; // 暂停

int sfp_refresh_thread(void *opaque){
    thread_exit  =0;
    thread_pause = 0;

    // 每40s发送一个刷新事件,即显示一帧
    while (!thread_exit) {
        if (!thread_pause) {
            SDL_Event event;
            event.type = SFM_REFRESH_EVENT;
            SDL_PushEvent(&event); // 发送刷新事件
        }
        SDL_Delay(40);
    }
    thread_exit = 0;
    thread_pause = 0;
    //Break
    SDL_Event event;
    event.type = SFM_BREAK_EVENT;
    SDL_PushEvent(&event); // 发送Break事件
    return 0;
}

/** 最新版本avcodec_decode_video2()接口已经废弃
 * 该接口功能分成avcodec_send_packet()和avcodec_receive_frame()两步实现
 * 以下代码根据这两个接口,重新实现avcodec_decode_video2(),以便后面代码调用
 */
static int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt)
{
    int ret;

    *got_frame = 0;

    if (pkt) {
        ret = avcodec_send_packet(avctx, pkt); // 发送编码数据包
        // In particular, we don't expect AVERROR(EAGAIN), because we read all
        // decoded frames with avcodec_receive_frame() until done.
        if (ret < 0 && ret != AVERROR_EOF)
            return ret;
    }

    ret = avcodec_receive_frame(avctx, frame); //接收解码后数据
    if (ret < 0 && ret != AVERROR(EAGAIN))
        return ret;
    if (ret >= 0)
        *got_frame = 1;

    return 0;
}

int main()
{
    AVFormatContext *pFormatCtx;
    AVCodecContext  *pCodecCtx;
    AVCodec         *pCodec;
    AVPacket        *packet;
    AVFrame         *pFrame;
    AVFrame         *pFrameYUV;

    // 视频文件放在项目路径里面
    char filepath[]="src01_480x272_22.h265";
//    char filepath[] = "test.mp4";
    unsigned char *out_buffer;
    int i;
    int videoindex = -1;
    int ret;
    int got_picture;

    //------------SDL----------------
    int screen_w, screen_h; // 窗口宽高
    SDL_Window *window;
    SDL_Renderer* sdlRenderer;
    SDL_Texture* sdlTexture;
    SDL_Rect sdlRect;
    SDL_Thread *video_tid;
    SDL_Event event;

    struct SwsContext *img_convert_ctx;

    // 1.注册输入/输出设备
    avdevice_register_all();
    // 2.执行网络库的全局初始化(可选)
    avformat_network_init();
    pFormatCtx = avformat_alloc_context();

    // 3.打开流媒体
    if (avformat_open_input(&pFormatCtx, filepath,NULL,NULL) !=0 ) {
        printf("Couldn't open input stream.\n");
        return -1;
    }

    // 4.获取更多的码流信息
    if (avformat_find_stream_info(pFormatCtx,NULL) < 0) {
        printf("Couldn't find stream information.\n");
        return -1;
    }

    // 5.获取视频流的id
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            videoindex = i;
            break;
        }
    }

    if (videoindex == -1) {
        printf("Didn't find a video stream.\n");
        return -1;
    }

    pCodecCtx = avcodec_alloc_context3(NULL);
    if (!pCodecCtx) return AVERROR(ENOMEM);

    // 6.将音频流信息拷贝到新的AVCodecContext结构体中
    ret = avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);
    if (ret < 0) return -1;

    // 7.通codecId来查找解码器
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
        printf("Codec not found.\n");
        return -1;
    }

    // 8.打开编解码器
    if (avcodec_open2(pCodecCtx, pCodec,NULL) < 0) {
        printf("Could not open codec.\n");
        return -1;
    }

    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();

    out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
                         AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1);

    //Output Info-----------------------------
    printf("---------------- File Information ---------------\n");
    av_dump_format(pFormatCtx,0,filepath,0);
    printf("-------------------------------------------------\n");

    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
                                     pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,
                                     SWS_BICUBIC, NULL, NULL, NULL);

    //(1)初始化SDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
        printf( "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    //SDL 2.0 Support for multiple windows
    screen_w = pCodecCtx->width;
    screen_h = pCodecCtx->height;
    //(2)创建window
    window = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED,
                              SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h, SDL_WINDOW_OPENGL);

    if (!window) {
        printf("SDL: could not create window - exiting:%s\n",SDL_GetError());
        return -1;
    }
    //(3)创建renderer
    sdlRenderer = SDL_CreateRenderer(window, -1, 0);
    //IYUV: Y + U + V  (3 planes)
    //YV12: Y + V + U  (3 planes)
    //(4)创建texture
    sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
                                   pCodecCtx->width, pCodecCtx->height);

    sdlRect.x = 0;
    sdlRect.y = 0;
    sdlRect.w = screen_w;
    sdlRect.h = screen_h;

    packet = (AVPacket *)av_malloc(sizeof(AVPacket));

    // (5)创建thread
    video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
    //------------SDL End------------

    //Event Loop
    for (;;) {
        //Wait
        SDL_WaitEvent(&event);
        if (event.type == SFM_REFRESH_EVENT) {// 接受到刷新事件
            while(1) {
                if(av_read_frame(pFormatCtx, packet) < 0)
                    thread_exit = 1;
                // 获取到视频帧流就跳出循环,准备对视频流的下一步处理
                if (packet->stream_index == videoindex)
                    break;
            }
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
            if (ret < 0){
                printf("Decode Error.\n");
                return -1;
            }

            if(got_picture){
                sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0,
                          pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
                //SDL---------------------------
                SDL_UpdateTexture( sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0] );
                SDL_RenderClear( sdlRenderer );
                //SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
                SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, NULL);
                SDL_RenderPresent( sdlRenderer );
                //SDL End-----------------------
            }
        } else if (event.type == SDL_KEYDOWN) {
            //Pause
            if (event.key.keysym.sym == SDLK_SPACE)
                thread_pause = !thread_pause;
        } else if (event.type == SDL_QUIT) {
            thread_exit = 1;
        } else if (event.type == SFM_BREAK_EVENT) {
            break;
        }
    }

    sws_freeContext(img_convert_ctx);
    SDL_Quit();
    //--------------
    av_frame_free(&pFrameYUV);
    av_frame_free(&pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    av_packet_free(&packet);
    return 0;
}

5.问题总结

(1)Ubuntu下点击QtCreator后,没有响应的解决

点击Qt图标没有响应,执行下面代码:

find / -name QtProject
rm -fr /home/zhou/.local/share/QtProject

(2)Qt无法输入中文的解决办法

(3)qt报错undefined reference to symbol 'pthread_create@@GLIBC_2.2.5'

https://blog.csdn.net/weixin_39490421/article/details/86511629

报错原因:未链接到pthread库
在.pro文件中加入以下一行代码
LIBS+=/usr/lib/x86_64-linux-gnu/libpthread.so

(4)undefined reference to 'uncompress'

原因:缺少zlib库。

安装:【ubuntu】zlib 库下载编译安装

# add for zlib by darcy
LIBS+=/usr/local/lib/libz.a

(5)undefined reference to 'x264_encoder_delayed_frames'

undefined reference to 'x264_encoder_reconfig'

原因:缺少libx264.so

# add for libx264 by darcy
LIBS+=/usr/local/lib/libx264.so

(6)undefined reference to 'vaMaxNumProfiles'

undefined reference to 'vaQueryConfigProfiles'

https://blog.csdn.net/spy_007_/article/details/114368608

添加下面的配置后重新编译ffmpeg:
sudo ./configure --disable-vaapi

(7)undefined reference to 'XDisplayString'

qt 在 linux 下引用 x11 库编译错误的解决办法

LIBS+=-lX11

(8)undefined reference to 'vdp_device_create_x11'

https://www.likecs.com/show-204242964.html

LIBS+=-lvdpau

(9)undefined reference to 'av_free_packet'

av_free_packet替换为:av_packet_free

(10)undefined reference to 'avcodec_decode_video2'

【FFmpeg API】avcodec_decode_video2函数简单分析

(11)undefined reference to 'xcb_setup_pixmap_formats_length'

ubuntu下编译QT6报缺少 libxcb 的处理方法

# add for libxcb by darcy
LIBS+=/usr/lib/x86_64-linux-gnu/libxcb.so

(12)undefined reference to symbol 'XShmDetach'

# add for x11 by darcy
LIBS+=-lX11 -lXext

(13)undefined reference to 'snd_pcm_readi'

LIBS+=-lasound

(14)undefined reference to 'sio_read'

LIBS+=-lsndio

(15)undefined reference to 'xcb_shm_attach'

   LIBS+=/usr/lib/x86_64-linux-gnu/libxcb-shm.a

(15)undefined reference to 'xcb_xfixes_get_cursor_image'

LIBS+=/usr/lib/x86_64-linux-gnu/libxcb-xfixes.a

(17)undefined reference to 'xcb_shape_rectangles'

LIBS+=/usr/lib/x86_64-linux-gnu/libxcb-shape.a

(18)undefined reference to 'XvShmPutImage'

LIBS+=/usr/lib/x86_64-linux-gnu/libXv.a

(19)undefined reference to ass_set_frame_size

LIBS+=-lfreetype

(20)其他

undefined reference to FT_Init_FreeType
undefined reference to ass_set_frame_size
undefined reference to opus_multistream_decoder_ctl
undefined reference to vorbis_analysis_buffer
undefined reference to vorbis_encode_setup_vbr
undefined reference to vpx_codec_decode
undefined reference to x265_api_get
undefined reference to xmlFree
undefined reference to gnutls_strerror
undefined reference to lame_encode_buffer
undefined reference to aacDecoder_DecodeFrame

总结:缺少依赖库的问题,在.pro文件中按如下配置即可。

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

SOURCES += \
        main.c

# add by darcy
INCLUDEPATH += /usr/include \
               /usr/local/include \
               /home/zhou/code/ffmpeg/SDL2/include

LIBS += /usr/local/lib/libavformat.a \
        /usr/local/lib/libavdevice.a \
        /usr/local/lib/libavfilter.a \
        /usr/local/lib/libavcodec.a \
        /usr/local/lib/libavutil.a \
        /usr/local/lib/libswresample.a \
        /usr/local/lib/libswscale.a \
        /usr/local/lib/libpostproc.a

LIBS += /home/zhou/code/ffmpeg/SDL2/lib/libSDL2.so \
        -lxcb -lX11 -lXext -lvdpau -lasound -lsndio \
        -lfreetype -lass -lfdk-aac -lmp3lame -lopus \
        -lvorbis -lvorbisenc -lvpx -lx265 -lxml2 -lgnutls \
        /usr/local/lib/libz.a \
        /usr/lib/x86_64-linux-gnu/libpthread.so \
        /usr/local/lib/libx264.so \
        /usr/lib/x86_64-linux-gnu/libxcb.so \
        /usr/lib/x86_64-linux-gnu/libxcb-shm.a \
        /usr/lib/x86_64-linux-gnu/libxcb-xfixes.a \
        /usr/lib/x86_64-linux-gnu/libxcb-shape.a \
        /usr/lib/x86_64-linux-gnu/libXv.a

6.参考资料

  • 最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)
  • Linux+Qt+ffmpeg开发环境搭建(ubuntu 16.04)
  • ubuntu 安装 QT 【亲测有效】

你可能感兴趣的:(Ubuntu+Qt+SDL2+FFmpeg)