此文档用于Ubuntu系统中搭建FFmpeg的开发环境。Qt作为开发软件,SDL2是一套开放源代码的跨平台多媒体开发库。
1.安装Qt
(1)下载版本:qt-opensource-linux-x64-5.9.8.run
(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
编译后生成物都会这上面这个路径下:
(5)编译和安装:
make -j128
sudo make install
3.安装FFmpeg
3.1 获取源码地址
- Download FFmpeg
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
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)新建项目
后面的选项一直默认就可以。
(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)运行结果
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解码一个视频流程如下图所示:
(2)SDL2.0显示YUV的流程图:
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 【亲测有效】