Ubuntu+Qt+SDL2+FFmpeg

此文檔用于Ubuntu系統(tǒng)中搭建FFmpeg的開發(fā)環(huán)境。Qt作為開發(fā)軟件,SDL2是一套開放源代碼的跨平臺多媒體開發(fā)庫。

1.安裝Qt

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

image.png

(2)安裝

給文件添加可執(zhí)行權限:

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

開始安裝:安裝過程中選擇全部安裝即可。(需要根據(jù)提示注冊帳號)

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

添加環(huán)境變量:

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

使環(huán)境變量生效:

source ~/.bashrc

啟動:

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

2.安裝SDL2

(1)SDL的官網(wǎng)下源碼包:release-2.26.3,或者下載地址。

(2)開始安裝:

??先把這個源碼包放到/home/zhou/code/ffmpeg/SDL2目錄下去,然后進行解壓。

(3)然后執(zhí)行:

./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 獲取源碼地址

git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
image.png

3.2 克隆項目到本地

(1)創(chuàng)建本地文件夾
    cd code
    mkdir ffmpeg

(2)初始化倉庫
    git init

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

3.3 安裝編譯依賴

??ffmpeg編譯過程中,依賴很多。官網(wǎng)給我們提供了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 添加環(huán)境變量

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

4.測試項目

4.1 Qt中引用ffmpeg庫

(1)新建項目

image.png

后面的選項一直默認就可以。

(2)代碼

main.cpp

#include <iostream>
#include <stdio.h>

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)參數(shù)說明:

  • -i: 表示要輸入的流媒體文件

  • -t: 表示截取流媒體文件內(nèi)容長度

  • -pix_fmt:指定要流媒體要轉(zhuǎn)換的格式

  • -s:指定分辨率大小

將yuv420p_320x240.yuv文件放入到下面路徑:(和執(zhí)行文件放一起)

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 <stdio.h>
#include <string.h>
#include "SDL2/SDL.h"http://包含SDL動態(tài)庫文件

//自定義消息類型
#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; //讀取數(shù)據(jù)后先把放到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;

    //創(chuàng)建窗口
    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;
    }
    // 基于窗口創(chuàng)建渲染器
    renderer = SDL_CreateRenderer(window, -1, 0);
    // 基于渲染器創(chuàng)建紋理
    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;
    }
    // 創(chuàng)建請求刷新線程
    timer_thread = SDL_CreateThread(refresh_video_timer,
                                    NULL,
                                    NULL);

    while (1)
    {
        // 收取SDL系統(tǒng)里面的事件
        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;
            }
            // 設置紋理的數(shù)據(jù) video_width = 320, plane
            SDL_UpdateTexture(texture, NULL, video_buf, video_width);

            // 顯示區(qū)域,可以通過修改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);
            // 將紋理的數(shù)據(jù)拷貝給渲染器
            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 <stdio.h>
#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()接口已經(jīng)廢棄
 * 該接口功能分成avcodec_send_packet()和avcodec_receive_frame()兩步實現(xiàn)
 * 以下代碼根據(jù)這兩個接口,重新實現(xiàn)avcodec_decode_video2(),以便后面代碼調(diào)用
 */
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); // 發(fā)送編碼數(shù)據(jù)包
        // 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); //接收解碼后數(shù)據(jù)
    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.執(zhí)行網(wǎng)絡庫的全局初始化(可選)
    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)創(chuàng)建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)創(chuàng)建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數(shù)據(jù)包(未解碼)
    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 <stdio.h>
#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發(fā)送一個刷新事件,即顯示一幀
    while (!thread_exit) {
        if (!thread_pause) {
            SDL_Event event;
            event.type = SFM_REFRESH_EVENT;
            SDL_PushEvent(&event); // 發(fā)送刷新事件
        }
        SDL_Delay(40);
    }
    thread_exit = 0;
    thread_pause = 0;
    //Break
    SDL_Event event;
    event.type = SFM_BREAK_EVENT;
    SDL_PushEvent(&event); // 發(fā)送Break事件
    return 0;
}

/** 最新版本avcodec_decode_video2()接口已經(jīng)廢棄
 * 該接口功能分成avcodec_send_packet()和avcodec_receive_frame()兩步實現(xiàn)
 * 以下代碼根據(jù)這兩個接口,重新實現(xiàn)avcodec_decode_video2(),以便后面代碼調(diào)用
 */
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); // 發(fā)送編碼數(shù)據(jù)包
        // 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); //接收解碼后數(shù)據(jù)
    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.執(zhí)行網(wǎng)絡庫的全局初始化(可選)
    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)創(chuàng)建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)創(chuàng)建renderer
    sdlRenderer = SDL_CreateRenderer(window, -1, 0);
    //IYUV: Y + U + V  (3 planes)
    //YV12: Y + V + U  (3 planes)
    //(4)創(chuàng)建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)創(chuàng)建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;
                // 獲取到視頻幀流就跳出循環(huán),準備對視頻流的下一步處理
                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圖標沒有響應,執(zhí)行下面代碼:

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函數(shù)簡單分析

(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.參考資料

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容