|
一、前言 在前几篇文章当中,有提到过用源码去搭建ffmpeg的命令环境开发,为啥要这样去搭建环境,为什么不用直接用下面这个命令在ubuntu下安装多快,简单又方便: sudo apt install ffmpeg今天分享ffmepg第五弹:ffmpeg+qt+SDL的真正开发环境,就要用源码安装的方式去在qt里面调用ffmpeg相关的库;还记得之前源码搭建创建的三个文件夹不: bin ffmpeg_sources ffmpeg_build& o8 ^9 k& Q9 T5 Cbin文件夹下是编译得到的二进制文件 txp@txp-virtual-machine:~/bin$ lsffmpeg ffplay ffprobe lame nasm 6 l4 ^; i$ S- B% {$ q ndisasm x264 5 C) A& s* K/ o V C& r ffmpeg_sources文件下是下载的各种库的源码: txp@txp-virtual-machine:~/ffmpeg_sources$ lsfdk-aac lame-3.100.tar.gz SDL2-2.0.14.tar.gz* E/ |9 O* ]: O 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.142 ?0 n9 [* g% q9 ~% i 2 }8 \% U& K/ l5 n0 q ffmpeg_build文件夹主要是ffmpeg的一些库文件,等下下面演示的模板就要调用ffmpeg相关的库: txp@txp-virtual-machine:~/ffmpeg_build/lib$ ls7 s0 S% y) A5 T% r* e3 Mcmake libmp3lame.a libSDL2.la libswscale.a2 z$ @/ v* y9 R5 B4 v9 f libavcodec.a libmp3lame.la libSDL2main.a libvpx.a libavdevice.a libopus.a libSDL2main.la libx264.a3 E1 n& }. a9 y/ T) R5 z libavfilter.a libopus.la libSDL2.so libx265.a libavformat.a libpostproc.a libSDL2_test.a pkgconfig3 U- D0 P l; w9 O 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) ? l) z! D2 M; g' @1 S 因为我已经搭建好了开发环境,从现在来看的话,如果你直接用命令去安装ffmpeg的话,到时候我们在qt的环境中去调用ffmpeg的库,至少到目前为止我暂时不知道去如何配置相关路径来调用ffmpeg的库;所以我们明白了这点,那么就撸起袖子肝就是。 二、 qt环境搭建玩过qt的朋友,对于这块应该比我熟悉多了;不过有可能有一些朋友可能没有接触过qt的话,为此我还是简单演示一下qt的安装步骤:
https://download.qt.io/archive/qt/5.12/5.12.10/: v' G+ V4 g+ N' q3 r# k$ S
这里我直接把qt的源码包下载到samba服务共享文件下,当然你也可以直接在ubuntu下载:
然后我在share目录创建一个qt文件夹,用存放qt安装的地方,然后直接运行这个源码文件:
接着就会出现qt的安装界面:
最终qt就安装完成了,但是如果你运行qt执行失败的话(注意qtcreator的所放在的路线): txp@txp-virtual-machine:~/share/qt/Qt5.12.10/Tools/QtCreator/bin$ sudo ./qtcreatorGot keys from plugin meta data ("xcb") QFactoryLoader: FactoryLoader() 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") L6 i/ f b* D x( ?3 d loaded library "Xcursor" Segmentation fault (core dumped)9 X+ h% c; i1 k4 l% x# g' Q
在这个脚本里面添加一句话:
然后接着再安装相关插件: sudo apt install --reinstall libxcb-xinerama09 [! \1 l' P$ i1 Z1 ^% H6 V然后我们执行一下,就可以成功打开qt了:
还有一种方法直接打开qt,因为按照上面的这种方式打开的话,每次都要跑到这个目录去执行这条语句才行: txp@txp-virtual-machine:~/share/qt/Qt5.12.10/Tools/QtCreator/bin$sudo ./qtcreator/ G8 `5 `8 n0 Z" b) d! @ 5 R5 V. m" ~3 m7 x' y 现在我们只要执行下面这条语句就不用这么麻烦了: sudo chown -R txp:txp ~/.config/* t2 d% t6 h! F- ~ 然后你就可以像在windows环境下去直接打开这个软件就行:
现在我们打开刚才安装好的qt软件,来创建一个工程:
最终一个工程项目就建立好了:8 y/ }: }# C$ P8 ?
我们可以看到两个文件,一个是以.pro结尾的qt工程管理配置文件,一个主源码文件,现在我们就简单使用ffmpeg库来打印ffmpeg的版本号:
CONFIG += console) K6 Q7 v' o: G CONFIG -= app_bundle4 {( ~0 _ T E- B- Y! r CONFIG -= qt0 Q. \0 P$ L+ D SOURCES += \ main.c 现在我们要加入ffmpeg_build目录下的ffmpeg库文件: TEMPLATE = appCONFIG += console3 h$ T0 @" e+ t& B& D CONFIG -= app_bundle* \( H9 S) u0 p5 |/ _# l CONFIG -= qt; L" Z* f0 P, F5 k) J1 S SOURCES += \ main.c INCLUDEPATH += /home/txp/ffmpeg_build/include #LIBS += /home/txp/ffmpeg_build/lib/libSDL2.so: [( I' Q# {1 Q+ c4 O* H 0 b- L a) F C5 O; ~7 q LIBS += /home/txp/ffmpeg_build/lib/libavcodec.a \5 }. R, b- Z! M% O( y) L' y( i /home/txp/ffmpeg_build/lib/libavdevice.a \# y0 X# k+ {# Z" y: B& _: z; Z /home/txp/ffmpeg_build/lib/libavfilter.a \ /home/txp/ffmpeg_build/lib/libavformat.a \ /home/txp/ffmpeg_build/lib/libavutil.a \2 h) [9 G. A0 g9 V& t* K( k7 g* e /home/txp/ffmpeg_build/lib/libswresample.a \ /home/txp/ffmpeg_build/lib/libswscale.a1 c' N. J+ F# E9 D 3 j) V" |+ g2 y, }) G T; I. ]! H5 }& u% W. R
//包含ffmpeg头⽂件 #include "libavutil/avutil.h" int main() {, X6 \7 s3 g" Z8 z& l5 v- j* W printf("Hello FFMPEG, version is %s\n", av_version_info());: P+ I! _5 H, Q" n& G: n( V return 0; } 6 l( _ @, @- J& h! \: b R 最终结果,显示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 ...$ _- C3 [8 G# M+ e9 ^5 K) l8 nHello FFMPEG, version is 4.2.1) V& k. ^4 L: A: Q* Q/ P 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 $ R9 y: z5 A, I) R7 ?( [ 注意:我们的ubuntu运行环境一定要是64位的,不然安装不了这个版本的qt;还有一点,其实我们在.pro文件里面,按键盘上的ctrl键然后把鼠标放到库文件路径上,是可以打开里面的:
关于什么是SDL,这里我就不造轮子了,可以参考雷神的文章介绍: https://blog.csdn.net/leixiaohua1020/article/details/11954039& |5 D6 A d! v3 [8 m9 s; n
下面我们去SDL的官网下源码包就行安装: https://www.libsdl.org/download-2.0.php
txp@txp-virtual-machine:~/ffmpeg_sources$ ls( `: f, n2 `& l, o+ A, U# V+ d fdk-aac lame-3.100.tar.gz SDL2-2.0.14.tar.gz8 P5 S7 x9 y8 m$ k, b ffmpeg libvpx SVT-AV1 ffmpeg-4.2.1 nasm-2.14.02 x264& C0 |5 g! ]+ ]; \3 ` 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 . J! x) F0 ~/ T0 b! v. lGenerating build information using autoconf4 `4 ~( F+ I/ n" g This may take a while ...- a0 s. L$ s- V' P& e Now you are ready to run ./configure, a2 O( m3 h& b: W% |: R) T9 A" I 2 \4 U+ S' k/ h' A& P( A 这里提示了你直接运行 ./configure: ./configure --prefix=/home/txp/ffmpeg_build --bindir=/home/txp/bin, I- L+ Z6 h/ Y5 z* }" b& H( g
make -j43 m, V3 f1 S1 x0 \) S3 s+ `4 n : O% f7 A) s6 Y% b. Z
sudo make install 下面我再创建一个工程,具体过程我就再写了,和第一个工程创建是一样的:这里我的main.c文件里面的源代码,大家先不用管代码具体啥意思: #include <stdio.h>#include <string.h> #include "SDL2/SDL.h"//包含SDL动态库文件 //自定义消息类型$ ]: E6 b+ W1 M #define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件 #define QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件 //定义分辨率 // YUV像素分辨率 #define YUV_WIDTH 320 #define YUV_HEIGHT 2402 L, c) ~( e! y/ J6 h9 P# ~6 P: M //定义YUV格式 \- B7 ~ c; M% N! ]9 @) @6 Y #define YUV_FORMAT SDL_PIXELFORMAT_IYUV3 u1 J; T) I1 D# o* l* U% E. z2 W 6 C {, p1 G# v, `; V0 u int s_thread_exit = 0; // 退出标志 = 1则退出) v' \$ t) t6 h" t+ C int refresh_video_timer(void *data) { while (!s_thread_exit)4 S" z+ o p, t5 [6 d4 J# n { SDL_Event event; event.type = REFRESH_EVENT; SDL_PushEvent(&event);" B4 p& O4 j: `( F' L SDL_Delay(40);2 R& m/ }# r, U4 _5 O3 K/ S7 n }0 L1 W) H g* p# |. a s_thread_exit = 0; & \) {4 t& D2 k; X1 b9 J4 Q. O h //push quit event SDL_Event event; event.type = QUIT_EVENT;3 u; b( [2 |0 i' K SDL_PushEvent(&event); return 0;/ S# H) i/ w G+ b/ _ } #undef main int main(int argc, char* argv[]) {5 I) d0 V& k+ B //初始化 SDL if(SDL_Init(SDL_INIT_VIDEO))) b0 N, U( L! c, r7 i6 P& ?$ o { fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError()); return -1; } 0 E9 e8 K/ P5 G+ l. D1 c // SDL SDL_Event event; // 事件 SDL_Rect rect; // 矩形 SDL_Window *window = NULL; // 窗口 SDL_Renderer *renderer = NULL; // 渲染; m5 V+ l$ d( k- q) e q, e SDL_Texture *texture = NULL; // 纹理' X; J! W4 y% k8 \1 p5 w SDL_Thread *timer_thread = NULL; // 请求刷新线程 uint32_t pixformat = YUV_FORMAT; // YUV420P,即是SDL_PIXELFORMAT_IYUV ' ]% ?) b( O9 E, |; \9 E; z // 分辨率 // 1. YUV的分辨率) A) j# A# u2 X int video_width = YUV_WIDTH; int video_height = YUV_HEIGHT; // 2.显示窗口的分辨率 int win_width = YUV_WIDTH; int win_height = YUV_WIDTH;1 h0 X/ ~4 ]4 z // YUV文件句柄# v' @; `0 L1 k' }4 U& O F FILE *video_fd = NULL; k9 o) p# M' h8 g/ Z! N const char *yuv_path = "yuv420p_320x240.yuv";9 N6 i+ y' ?1 z* |" F ( B1 } S+ f. K" _: Q5 S) C& ? size_t video_buff_len = 0;' m' _ L" }2 z; y* Q * ~) M( ^# B# ^ v# e uint8_t *video_buf = NULL; //读取数据后先把放到buffer里面. a5 p: b# t/ U. H: H4 w4 r; @ // 我们测试的文件是YUV420P格式5 V" B5 ~# G6 H& b uint32_t y_frame_len = video_width * video_height; L( R8 b2 D3 T2 m( z* n! n 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; f& f! C7 c+ i+ j+ d //创建窗口 window = SDL_CreateWindow("Simplest YUV Player",+ y/ o1 @) R( g* k) r: y" @ SDL_WINDOWPOS_UNDEFINED,) n% f" W* M8 [5 @1 s" Q/ b SDL_WINDOWPOS_UNDEFINED,0 b0 s X8 W% W( m video_width, video_height, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);; H5 t7 G) Z0 ^5 Y9 u if(!window): Y$ e' G& M% ^+ F" N/ L8 x {" k1 S8 r2 O: p9 ^- Y fprintf(stderr, "SDL: could not create window, err:%s\n",SDL_GetError()); goto _FAIL;) w3 o) { T/ \( U }$ @) f# r5 e U$ S0 s // 基于窗口创建渲染器 renderer = SDL_CreateRenderer(window, -1, 0); // 基于渲染器创建纹理 texture = SDL_CreateTexture(renderer,0 t2 ~7 u& X- ?: Z pixformat, SDL_TEXTUREACCESS_STREAMING, video_width,5 h& P+ u* G- W# }" B- E video_height); 6 U. g8 t: b5 |7 Q, c5 _8 U // 分配空间 video_buf = (uint8_t*)malloc(yuv_frame_len);6 L' V" x+ ^- Q% Q* H if(!video_buf) { fprintf(stderr, "Failed to alloce yuv frame space!\n");& G7 D; Y a1 }+ d: k" ] goto _FAIL; }1 P4 q1 `- c, }) e/ f- N3 X" u // 打开YUV文件. o, b3 |' J. Y! S/ W video_fd = fopen(yuv_path, "rb"); if( !video_fd ) { fprintf(stderr, "Failed to open yuv file\n");7 e0 {: t2 @* a( `8 x goto _FAIL;4 ~+ _- m! d O; x9 d( B }6 v; c# p: _5 F% B; F2 h // 创建请求刷新线程 s) _1 W2 v; | timer_thread = SDL_CreateThread(refresh_video_timer,- p6 i- y* U' k2 y/ q: R7 ~ NULL,) g5 h: s2 K4 K. O$ y: u, i, S NULL);% z( U+ M: t' c, k: v while (1) {9 \0 J0 M; V9 Y t: S4 c // 收取SDL系统里面的事件 SDL_WaitEvent(&event); ; G, I4 K. i2 j/ Y if(event.type == REFRESH_EVENT) // 画面刷新事件 { video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd); if(video_buff_len <= 0)- V$ T. x' p* s9 U: H { fprintf(stderr, "Failed to read data from yuv file!\n"); goto _FAIL; } // 设置纹理的数据 video_width = 320, plane" f: t( N1 X7 ?" G SDL_UpdateTexture(texture, NULL, video_buf, video_width); // 显示区域,可以通过修改w和h进行缩放 rect.x = 0;6 I. ~1 H! x- k- @9 C rect.y = 0; float w_ratio = win_width * 1.0 /video_width;0 V( O% ~. _& c n! U float h_ratio = win_height * 1.0 /video_height; // 320x240 怎么保持原视频的宽高比例 rect.w = video_width * w_ratio; rect.h = video_height * h_ratio;4 |" L8 {" W- d // rect.w = video_width * 0.5;. E" ^# ~+ r3 N4 l. |) ^9 | // rect.h = video_height * 0.5;0 U& P) ], s- q% ~" G1 d8 X+ \' H // 清除当前显示 SDL_RenderClear(renderer);! P g9 u% _4 J" ~5 O3 L. s1 M$ k // 将纹理的数据拷贝给渲染器 SDL_RenderCopy(renderer, texture, NULL, &rect); J& o7 C" r! P) ]9 P // 显示 SDL_RenderPresent(renderer);$ t# w$ d, U. m( N N9 w } else if(event.type == SDL_WINDOWEVENT) {( K/ L) Y9 @* f+ c1 `6 [ //If Resize SDL_GetWindowSize(window, &win_width, &win_height); printf("SDL_WINDOWEVENT win_width:%d, win_height:%d\n",win_width, win_height );) E/ N: T' t; K. C+ \ z$ J } r! Q' R% ?' t; s+ I! { else if(event.type == SDL_QUIT) //退出事件 { s_thread_exit = 1;$ W& g$ d2 D+ q }9 J( v6 l+ |8 d$ \" ~7 G# j else if(event.type == QUIT_EVENT)0 {& E2 g' F g) o- [1 d { break;! |1 ]. N: ^" O$ ^7 G# D- [+ i }! m9 Z2 m) u7 w8 M# U. H* w }1 c4 a8 f; l. _6 J) d5 L4 F& b8 x ; W( B- b3 F) W2 Z0 \ _FAIL: s_thread_exit = 1; // 保证线程能够退出1 D- V2 N- b& v# m2 Y: P // 释放资源 if(timer_thread)' h6 v, ^" r# Q( P1 f SDL_WaitThread(timer_thread, NULL); // 等待线程退出 if(video_buf) free(video_buf);; u0 X* N5 u( O- {5 O- G if(video_fd) fclose(video_fd); if(texture)0 k% [. g* H5 @+ ^ SDL_DestroyTexture(texture); if(renderer) SDL_DestroyRenderer(renderer);. f2 u7 y- D0 n+ n+ e2 x6 {! k if(window) r8 c8 D/ o* O' Z SDL_DestroyWindow(window); SDL_Quit(); + B5 X) n+ E& j- v return 0;: x/ q* d3 Z4 ^1 Q " A* C, |3 R, F9 A& ? }; a( E# z( P$ a .pro文件配置成: TEMPLATE = appCONFIG += console CONFIG -= app_bundle4 A! U$ N8 y e: K( k CONFIG -= qt1 X+ ~, `# X. X; f8 [ 3 a# z' t" F: j% _- X; ^1 d# t, y" R. T SOURCES += \3 d/ \0 a J& _' {) f main.c INCLUDEPATH += /home/txp/ffmpeg_build/include8 r% g4 |3 t% o( ^' h' E LIBS += /home/txp/ffmpeg_build/lib/libSDL2.so 最终运行结果就可以看到在播放一个yuv格式的视频文件了:
注:这里的播放yuv格式的视频文件,我是事先已经准备好的:
同时要注意我们要把播放的视频文件放到工程目录下,不然播放是不会成功的:
现在ffmpeg真正的开发环境已经搭建完了,其实上面搭建环境蛮折腾人的,特别是源码安装ffmpeg。好了今天的文章就分享到这里了,如果你在看完文章后,有不懂的地方可以后台私聊我。 - `, Q* E5 s, q |
微信公众号
手机版