
一、前言 在前几篇文章当中,有提到过用源码去搭建ffmpeg的命令环境开发,为啥要这样去搭建环境,为什么不用直接用下面这个命令在ubuntu下安装多快,简单又方便: sudo apt install ffmpeg- W4 \$ k- o1 \6 {今天分享ffmepg第五弹:ffmpeg+qt+SDL的真正开发环境,就要用源码安装的方式去在qt里面调用ffmpeg相关的库;还记得之前源码搭建创建的三个文件夹不: bin ffmpeg_sources ffmpeg_buildbin文件夹下是编译得到的二进制文件 txp@txp-virtual-machine:~/bin$ ls w- x! Q% O& G; i0 Z; ]$ Mffmpeg ffplay ffprobe lame nasm ndisasm x2644 K( G9 l5 j4 R ffmpeg_sources文件下是下载的各种库的源码: txp@txp-virtual-machine:~/ffmpeg_sources$ lsfdk-aac lame-3.100.tar.gz SDL2-2.0.14.tar.gz* c# b* n$ J; A2 D6 H: _7 L 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& M5 ]' N, s8 h9 V( ` ffmpeg-snapshot.tar.bz2 opus- ?) e' G; q8 Z4 R) {9 ~ lame-3.100 SDL2-2.0.149 o- h0 K5 o+ ?' ^) E! a- R 0 V. d3 I% u1 i- Y, @. u3 Y ffmpeg_build文件夹主要是ffmpeg的一些库文件,等下下面演示的模板就要调用ffmpeg相关的库: txp@txp-virtual-machine:~/ffmpeg_build/lib$ lscmake libmp3lame.a libSDL2.la libswscale.a libavcodec.a libmp3lame.la libSDL2main.a libvpx.a8 z, J# v; ]- Q: q libavdevice.a libopus.a libSDL2main.la libx264.a libavfilter.a libopus.la libSDL2.so libx265.a1 @% ~$ s% {0 t libavformat.a libpostproc.a libSDL2_test.a pkgconfig. G# m8 y1 M6 ?& p libavutil.a libSDL2-2.0.so.0 libSDL2_test.la, }" L2 K! a' @! o- @, \9 ^ libfdk-aac.a libSDL2-2.0.so.0.14.0 libSvtAv1Enc.a! ?6 a9 _6 V3 ~1 k: ^" S$ Y libfdk-aac.la libSDL2.a libswresample.a 因为我已经搭建好了开发环境,从现在来看的话,如果你直接用命令去安装ffmpeg的话,到时候我们在qt的环境中去调用ffmpeg的库,至少到目前为止我暂时不知道去如何配置相关路径来调用ffmpeg的库;所以我们明白了这点,那么就撸起袖子肝就是。 二、 qt环境搭建玩过qt的朋友,对于这块应该比我熟悉多了;不过有可能有一些朋友可能没有接触过qt的话,为此我还是简单演示一下qt的安装步骤:
https://download.qt.io/archive/qt/5.12/5.12.10/$ \, b7 k5 d# u+ b* R ![]()
这里我直接把qt的源码包下载到samba服务共享文件下,当然你也可以直接在ubuntu下载: ![]() 然后我在share目录创建一个qt文件夹,用存放qt安装的地方,然后直接运行这个源码文件: ![]() 接着就会出现qt的安装界面: ![]() ![]() ![]() 最终qt就安装完成了,但是如果你运行qt执行失败的话(注意qtcreator的所放在的路线): txp@txp-virtual-machine:~/share/qt/Qt5.12.10/Tools/QtCreator/bin$ sudo ./qtcreator8 q; ~: z! h/ a5 ]& T5 c& M# I L( e8 ] j5 U Got keys from plugin meta data ("xcb") QFactoryLoader: ![]() loaded library "/home/txp/share/qt/Qt5.12.10/Tools/QtCreator/lib/Qt/plugins/platforms/libqxcb.so" loaded library "Xcursor" Segmentation fault (core dumped)8 W- e, q/ H) j# P( B7 @ ' q, `+ d. B3 B% R4 u
在这个脚本里面添加一句话: ![]() 然后接着再安装相关插件: sudo apt install --reinstall libxcb-xinerama09 Q6 v9 r, Y8 a8 O/ }然后我们执行一下,就可以成功打开qt了: ![]() 还有一种方法直接打开qt,因为按照上面的这种方式打开的话,每次都要跑到这个目录去执行这条语句才行: txp@txp-virtual-machine:~/share/qt/Qt5.12.10/Tools/QtCreator/bin$sudo ./qtcreator 8 K2 a& r# ~7 K% P. { 现在我们只要执行下面这条语句就不用这么麻烦了: sudo chown -R txp:txp ~/.config/; e: Y/ m2 Q0 v1 s# k% v, \ w: f( m; z1 b 然后你就可以像在windows环境下去直接打开这个软件就行: ![]() 现在我们打开刚才安装好的qt软件,来创建一个工程: ![]() ![]() ![]() 最终一个工程项目就建立好了: ![]() 我们可以看到两个文件,一个是以.pro结尾的qt工程管理配置文件,一个主源码文件,现在我们就简单使用ffmpeg库来打印ffmpeg的版本号:
CONFIG += console) i5 _6 @) M: v) K+ N CONFIG -= app_bundle G1 y( f3 C+ O0 F3 h7 ~+ Q% i) X0 T CONFIG -= qt $ I" Z2 u* e' \% @5 Q' ~9 g SOURCES += \' X# Q; f' D p E main.c 现在我们要加入ffmpeg_build目录下的ffmpeg库文件: TEMPLATE = appCONFIG += console CONFIG -= app_bundle1 M' ]7 N" Z! l9 k P CONFIG -= qt5 E+ Q% K7 u0 P & e# o5 Q2 _" _* \ SOURCES += \$ M/ V. |0 I& |3 ~: ]5 ` main.c2 \4 }: F7 t& \9 T INCLUDEPATH += /home/txp/ffmpeg_build/include- V8 R/ c- m0 v( |& W #LIBS += /home/txp/ffmpeg_build/lib/libSDL2.so. W& a* j1 a' h" ^ I LIBS += /home/txp/ffmpeg_build/lib/libavcodec.a \ /home/txp/ffmpeg_build/lib/libavdevice.a \ /home/txp/ffmpeg_build/lib/libavfilter.a \9 O: W- t: t, }9 D- y7 P0 X9 }1 @ /home/txp/ffmpeg_build/lib/libavformat.a \4 U. |8 q* S8 r1 J6 z6 p /home/txp/ffmpeg_build/lib/libavutil.a \ /home/txp/ffmpeg_build/lib/libswresample.a \ /home/txp/ffmpeg_build/lib/libswscale.a) @+ ~/ M1 s1 i/ C ; W- j; X! D/ U! c
//包含ffmpeg头⽂件 #include "libavutil/avutil.h" int main()+ U& o6 V$ V$ r1 ` { printf("Hello FFMPEG, version is %s\n", av_version_info()); return 0; }8 Q& N, W; ]9 G* a3 W: K 最终结果,显示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 " f, G& ^# S7 f+ l 注意:我们的ubuntu运行环境一定要是64位的,不然安装不了这个版本的qt;还有一点,其实我们在.pro文件里面,按键盘上的ctrl键然后把鼠标放到库文件路径上,是可以打开里面的: ![]() 关于什么是SDL,这里我就不造轮子了,可以参考雷神的文章介绍: https://blog.csdn.net/leixiaohua1020/article/details/119540390 R1 m+ g8 h+ T4 a: k: n s M5 E![]() 下面我们去SDL的官网下源码包就行安装: https://www.libsdl.org/download-2.0.php![]()
txp@txp-virtual-machine:~/ffmpeg_sources$ ls fdk-aac lame-3.100.tar.gz SDL2-2.0.14.tar.gz4 q1 H/ j2 M% T- w; d# j1 ? ffmpeg libvpx SVT-AV1- z5 z; t" l9 j* N2 [ ~5 } ffmpeg-4.2.1 nasm-2.14.02 x2647 P: u4 A7 z- M! V |: ^2 @ ffmpeg-4.2.1.tar.bz2 nasm-2.14.02.tar.bz2 x265_git# S. m, Y% y2 l( B( R( A) C ffmpeg-snapshot.tar.bz2 opus lame-3.100 SDL2-2.0.14 然后执行: txp@txp-virtual-machine:~/ffmpeg_sources/SDL2-2.0.14$ ./autogen.sh 1 z# S p5 a1 _9 D- Z1 c9 HGenerating build information using autoconf( w5 ^; E; s, |" z This may take a while ...% ^- l) @3 t2 V, Z e7 r) }; q6 c Now you are ready to run ./configure 这里提示了你直接运行 ./configure: ./configure --prefix=/home/txp/ffmpeg_build --bindir=/home/txp/bin c# E" \$ P9 y8 w5 ~, ` b
make -j4; Y o" b$ S) V1 b+ k$ j9 Y y. n
sudo make install! y) m/ E/ L. A5 } 0 {3 h9 l( w5 I8 ~- P 下面我再创建一个工程,具体过程我就再写了,和第一个工程创建是一样的:这里我的main.c文件里面的源代码,大家先不用管代码具体啥意思: #include <stdio.h>3 z) E; ]7 X4 {; }8 c) M#include <string.h> #include "SDL2/SDL.h"//包含SDL动态库文件" Q2 R+ b% B: O( T* U- C4 d: g //自定义消息类型; p9 E4 o% g6 A #define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件 #define QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件. z3 m4 i6 w9 @% |' k6 Z$ e 9 e6 w z: T) o/ a //定义分辨率 F0 ~5 O+ H, ?' M5 {8 |! E) A // YUV像素分辨率8 S9 f% ~ r: m+ `3 u9 @ #define YUV_WIDTH 3204 ]1 y9 B& a8 t) R* g #define YUV_HEIGHT 240/ v" H- [8 n6 R) G5 M6 u //定义YUV格式 #define YUV_FORMAT SDL_PIXELFORMAT_IYUV* |- B$ F7 ^6 X$ h: I, M: J int s_thread_exit = 0; // 退出标志 = 1则退出 int refresh_video_timer(void *data); v& d4 y0 W- s/ _4 z, b { while (!s_thread_exit)7 E4 B8 o! a2 l% j8 k {, o/ ]. {$ W5 s9 z SDL_Event event; event.type = REFRESH_EVENT;, m' q; y& O2 }# N3 G% O$ } SDL_PushEvent(&event);" v0 E+ w; [+ C& b! a, t SDL_Delay(40); } " g6 U) `: \9 D w8 K, S s_thread_exit = 0;! S4 L9 ]% G* [( l1 t* y0 R //push quit event SDL_Event event; event.type = QUIT_EVENT; SDL_PushEvent(&event); return 0; }6 d* t! v+ N; a* F/ P9 }; o$ @ #undef main6 |+ h5 W2 ~2 w, p5 _. v int main(int argc, char* argv[]): ]: P# E- y! Z& [# k; @ {6 i9 ~0 {, A% b. f5 u //初始化 SDL' H5 ^" H9 ~$ ]4 ?- [+ x7 k0 c if(SDL_Init(SDL_INIT_VIDEO)) { fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError()); return -1; } ; W" q3 M4 h3 b8 R2 c( Z // SDL# P* g+ Z9 F) L. p$ K0 ~+ e SDL_Event event; // 事件 SDL_Rect rect; // 矩形. v9 A. _, n! [, l4 V SDL_Window *window = NULL; // 窗口9 \9 M! H+ s; y+ U/ q+ H) U% h; U SDL_Renderer *renderer = NULL; // 渲染 SDL_Texture *texture = NULL; // 纹理- j0 l! o) ?) z SDL_Thread *timer_thread = NULL; // 请求刷新线程 uint32_t pixformat = YUV_FORMAT; // YUV420P,即是SDL_PIXELFORMAT_IYUV% L1 B4 C8 e4 D% i" X // 分辨率+ n& m) m- W$ L. w4 K // 1. YUV的分辨率 int video_width = YUV_WIDTH; int video_height = YUV_HEIGHT; // 2.显示窗口的分辨率8 z# N! `3 g$ [ ], K int win_width = YUV_WIDTH;+ w" g+ y( w" Z: |1 f% P int win_height = YUV_WIDTH; : z9 M# t* V0 a- V) Q // YUV文件句柄! `2 N d, V. S4 h0 s5 p* T1 |9 j FILE *video_fd = NULL; const char *yuv_path = "yuv420p_320x240.yuv"; x5 j1 a! d; d: r# t3 N; w1 u $ }1 ^4 b; T1 D% h8 q/ p+ h) ~ size_t video_buff_len = 0; . @; l' m+ v2 g/ x n uint8_t *video_buf = NULL; //读取数据后先把放到buffer里面 ' B0 P8 h! {( a* v* m // 我们测试的文件是YUV420P格式0 m' i/ a& Z6 u5 q uint32_t y_frame_len = video_width * video_height;* s' |6 l* Q; ~+ a' x% O uint32_t u_frame_len = video_width * video_height / 4; uint32_t v_frame_len = video_width * video_height / 4;) G0 m1 E# X7 p4 I! l uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;4 a) i' x1 g& h* E* ^: u & g1 X+ m) x$ d% X, ]) Q) o //创建窗口 window = SDL_CreateWindow("Simplest YUV Player",% ~, ~( ]8 }! a: Z v, x7 g' L$ ~ SDL_WINDOWPOS_UNDEFINED," _7 J: Y' C8 s& [) N0 s% f: S' _ SDL_WINDOWPOS_UNDEFINED, video_width, video_height, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);( s6 L& w& W/ h+ R if(!window)) n$ i' t# W6 O9 A3 B" _ { fprintf(stderr, "SDL: could not create window, err:%s\n",SDL_GetError()); goto _FAIL; }' u9 A; ]* H3 ^4 [! i8 Q/ H // 基于窗口创建渲染器 renderer = SDL_CreateRenderer(window, -1, 0);% {0 M3 D! X1 l" @1 Q/ K) ^ // 基于渲染器创建纹理 texture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING,/ b: W5 g/ R. z; t4 J video_width,7 D* [( s2 }9 @4 p4 C video_height);2 [" \! l; r; M; @; y" |2 ]& M/ a // 分配空间5 V. {+ k% Q& i1 x video_buf = (uint8_t*)malloc(yuv_frame_len);! V9 J! c- w, C! E5 r* n+ n if(!video_buf)* n% \& P. o+ ]& u { fprintf(stderr, "Failed to alloce yuv frame space!\n"); goto _FAIL; }5 O' u) s; A, d3 ^7 B( e9 U* q & D+ V' }+ Y" M' j // 打开YUV文件" d* K# z, ?9 q video_fd = fopen(yuv_path, "rb"); if( !video_fd ) { fprintf(stderr, "Failed to open yuv file\n"); goto _FAIL; } // 创建请求刷新线程& }* z) f! F; v1 L timer_thread = SDL_CreateThread(refresh_video_timer,8 p; l" E! f/ h, h* f# }; L NULL, NULL); while (1)4 u+ D9 i; J* G6 o( h { [6 C# {5 _" b" b7 ^ // 收取SDL系统里面的事件" |, ~2 _- [8 v3 C! Q SDL_WaitEvent(&event);. S# Q1 t2 H# s Z0 G if(event.type == REFRESH_EVENT) // 画面刷新事件% t3 [& \/ f. H" U0 L) e {- _3 }& L2 C: M+ v video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd); if(video_buff_len <= 0)8 m- _, u* Z9 ?0 ]- F% A6 |9 B { fprintf(stderr, "Failed to read data from yuv file!\n");. ~2 h1 a, N! I# }0 _: r# ?3 E goto _FAIL;/ w: ]3 R9 W( Q& \4 ~- L I } // 设置纹理的数据 video_width = 320, plane9 o4 g f# R, {% G6 r& U% x SDL_UpdateTexture(texture, NULL, video_buf, video_width);; Y6 Y. N2 o' V O! o/ F1 d( a9 R; n7 p // 显示区域,可以通过修改w和h进行缩放. s! U: C0 C/ A; ` rect.x = 0; rect.y = 0; P1 `- j/ H# m( L/ Q0 S8 R/ r: i/ N float w_ratio = win_width * 1.0 /video_width; float h_ratio = win_height * 1.0 /video_height;1 u! n5 K; Q) k; k6 F6 e; N3 g // 320x240 怎么保持原视频的宽高比例 rect.w = video_width * w_ratio; rect.h = video_height * h_ratio; // rect.w = video_width * 0.5;3 y0 O9 I& \, K' {, u, z2 d // rect.h = video_height * 0.5;9 R! A3 a3 M; }& M, C 8 D7 n' M; v4 f7 a // 清除当前显示# o. K+ N- F+ J- k5 I SDL_RenderClear(renderer);% k. c5 R% d; A. |4 S; N // 将纹理的数据拷贝给渲染器 SDL_RenderCopy(renderer, texture, NULL, &rect); // 显示 SDL_RenderPresent(renderer);8 G2 R2 {0 H4 m } else if(event.type == SDL_WINDOWEVENT)5 T! T8 c b# g9 f& o { //If Resize SDL_GetWindowSize(window, &win_width, &win_height);+ c6 d# C5 M2 L- A6 x5 ], @ printf("SDL_WINDOWEVENT win_width:%d, win_height:%d\n",win_width, win_height );+ q, f& E6 g4 ?& F1 J }! l+ U7 W* I1 ~' z& f else if(event.type == SDL_QUIT) //退出事件( t3 A1 D3 | F! D& d { s_thread_exit = 1;: y/ S1 d' T$ {0 ` }' R7 o# ?9 t" J% g2 F- { else if(event.type == QUIT_EVENT) {% d: f e8 v8 o i$ }% P3 k" @ break;$ U( Z+ b4 ^& K' M( K1 q8 m# @ } } ' o M& W0 i/ c, F4 p _FAIL: s_thread_exit = 1; // 保证线程能够退出& o4 T% {5 }* L0 F9 C" Y. r* A // 释放资源 if(timer_thread) SDL_WaitThread(timer_thread, NULL); // 等待线程退出 if(video_buf)/ {6 y. D; w# y g i9 a free(video_buf); if(video_fd)4 Q+ P {% R( n fclose(video_fd); if(texture) SDL_DestroyTexture(texture); M; O+ G: T9 ]3 {9 g if(renderer)8 S9 t7 V8 O# V3 U SDL_DestroyRenderer(renderer); if(window)0 s H2 V7 {; ^, c6 Z SDL_DestroyWindow(window); SDL_Quit();, k" ^. w$ I: l( c/ i , ]- W! ~( _5 X" V; V" [ return 0; }' K* S5 C7 I2 D! P ! v$ I. c" L7 |3 b9 q+ O .pro文件配置成: TEMPLATE = app7 @. f. r- l" `) z3 ?8 W4 _CONFIG += console CONFIG -= app_bundle CONFIG -= qt/ ?' \, [0 A$ O+ } Y1 M. a 4 t* d/ ~! O4 q. G# M SOURCES += \$ V; m. ?1 ~ B7 j main.c0 o1 ^9 J, @2 {3 p4 [- f INCLUDEPATH += /home/txp/ffmpeg_build/include LIBS += /home/txp/ffmpeg_build/lib/libSDL2.so 6 x8 ^3 b! C3 ^% U9 v: I! ]& B 最终运行结果就可以看到在播放一个yuv格式的视频文件了: ![]() 注:这里的播放yuv格式的视频文件,我是事先已经准备好的: ![]() 同时要注意我们要把播放的视频文件放到工程目录下,不然播放是不会成功的: ![]() 现在ffmpeg真正的开发环境已经搭建完了,其实上面搭建环境蛮折腾人的,特别是源码安装ffmpeg。好了今天的文章就分享到这里了,如果你在看完文章后,有不懂的地方可以后台私聊我。 |