上篇文章,介绍了FFmpeg的交叉编译,以及在嵌入式Linux平台,运行ffmpeg指令来播放视频。

本篇,将通过Qt程序,设计一个RTSP视频播放器,来播放网络视频,并增加启动、暂停等操作按钮。

1 FFMPEG 库介绍

1.1 ffmpeg的7个库

ffmpeg有7个library,分别是:

  • avutil
  • swscale
  • swresample
  • avcodec
  • avformat
  • avdevice
  • avfilter

avutil 工具库

avutil是一个实用的工具库用于辅助可移植的多媒体编程。它包含安全的可移植的字符串函数,随机数生成器,数据结构,附加的数学函数,密码学和多媒体相关功能(例如像素和样本格式的枚举)。它不是 libavcodec 和 libavformat 都需要的代码库。

swscale 视频像素数据格式转换

swscale库执行高度优化的图像缩放以及色彩空间和像素格式转换操作,这个库执行以下转换:

  • Recailing:是改变视频大小的过程。 有几个重新缩放选项和算法可用。 这通常是一个有损过程。

  • Pixel format conversion:是将图像的图像格式和色彩空间转换的过程,例如从平面YUV420P 到RGB24 打包。 它还处理打包方式转换,即从Packed布局转换为Planar布局。

    注意:如果源和目标颜色空间不同,这通常是一个有损过程。

swresample 音频采样数据格式转换

swresample库执行高度优化的音频重采样,重矩阵化和样本格式转换操作,这个库执行以下转换:

  • Resampling:是改变音频码率的过程,例如从一个高采样率44100Hz转化为8000Hz。音频从高采样率转换为低采样率是一个有损的过程。有几种重采样选项和算法可用。
  • Format conversion:是一个转换样本类型的过程,例如从有符号16-bit(int16_t)样本转换为无符号8-bit(uint8_t)或浮点样本。它也处理打包方式转换,如从Packed布局转换为Planar布局。
  • Rematrixing:是改变通道布局的过程,例如从立体声到单声道。当输入通道不能映射到输出流时,这个过程是有损的,因为它涉及不同的增益因子和混合。
    通过专用选项启用各种其他音频转换(例如拉伸和填充)。

avcodec 编解码

avcodec库提供了一个通用的编码/解码框架,并且包含用于音频、视频、字幕流的多个编解器和解码器共享架构提供从比特流 I/O 到 DSP 优化的各种服务,使其适用于实现鲁棒和快速的编解码器以及实验。

avformat 封装格式处理

libavformat库为音频、视频和字幕流的复用和解复用(muxing and demuxing)提供了一个通用框架。它包含多个用于媒体容器格式的多个复用器和解复用器,它还支持多种输入和输出协议来访问媒体资源。

avdevice 设备的输入输出

avdevice 库提供了一个通用框架,用于从许多常见的多媒体输入/输出设备进行抓取和渲染,并支持多种输入和输出设备,包括 Video4Linux2、VfW、DShow 和 ALSA。

avfilter 滤镜特效处理

avfilter 库提供了一个通用的音频/视频过滤框架,其中包含多个过滤器、源和接收器。

1.2 win平台FFmpeg库下载

Win平台的Qt Creator需要用到Visual Stdio的功能,我电脑的Visual Stdio的2015版(对应的是msvc14),因此,我下载的FFmpeg是4.4版的,再高的版本就没有msvc14的了。

https://github.com/ShiftMediaProject/FFmpeg/releases/tag/4.4.r101753

2 Qt程序设计

2.1 RTSP解码与视频播放流程

先来看下FFmpeg对RTSP解码的处理流程:

2.2 视频解码

对照上面的流程图,使用FFmpeg对RTSP视频流的解码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
void VideoPlayer::run()
{
AVFormatContext *pFormatCtx; //音视频封装格式上下文结构体
AVCodecContext *pCodecCtx; //音视频编码器上下文结构体
AVCodec *pCodec; //音视频编码器结构体
AVFrame *pFrame; //存储一帧解码后像素数据
AVFrame *pFrameRGB;
AVPacket *pPacket; //存储一帧压缩编码数据
uint8_t *pOutBuffer;
static struct SwsContext *pImgConvertCtx;

avformat_network_init(); //初始化FFmpeg网络模块
av_register_all(); //初始化FFMPEG 调用了这个才能正常适用编码器和解码器

//Allocate an AVFormatContext.
pFormatCtx = avformat_alloc_context();

//AVDictionary
AVDictionary *avdic=nullptr;
char option_key[]="rtsp_transport";
char option_value[]="udp";
av_dict_set(&avdic,option_key,option_value,0);
char option_key2[]="max_delay";
char option_value2[]="100";
av_dict_set(&avdic,option_key2,option_value2,0);

if (avformat_open_input(&pFormatCtx, m_strFileName.toLocal8Bit().data(), nullptr, &avdic) != 0)
{
printf("can't open the file. \n");
return;
}

if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
{
printf("Could't find stream infomation.\n");
return;
}

//查找视频中包含的流信息,音频流先不处理
int videoStreamIdx = -1;
qDebug("apFormatCtx->nb_streams:%d", pFormatCtx->nb_streams);
for (int i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStreamIdx = i; //视频流
}
}
if (videoStreamIdx == -1)
{
printf("Didn't find a video stream.\n"); //没有找到视频流
return;
}

//查找解码器
qDebug("avcodec_find_decoder...");
pCodecCtx = pFormatCtx->streams[videoStreamIdx]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == nullptr)
{
printf("Codec not found.\n");
return;
}
pCodecCtx->bit_rate =0; //初始化为0
pCodecCtx->time_base.num=1; //下面两行:一秒钟25帧
pCodecCtx->time_base.den=10;
pCodecCtx->frame_number=1; //每包一个视频帧

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

//将解码后的YUV数据转换成RGB32
pImgConvertCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);

int numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);

pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
pOutBuffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
avpicture_fill((AVPicture *) pFrameRGB, pOutBuffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height);

pPacket = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
int y_size = pCodecCtx->width * pCodecCtx->height;
av_new_packet(pPacket, y_size); //分配packet的数据

while (1)
{
if (av_read_frame(pFormatCtx, pPacket) < 0)
{
break; //这里认为视频读取完了
}

if (pPacket->stream_index == videoStreamIdx)
{
int got_picture;
int ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,pPacket);
if (ret < 0)
{
printf("decode error.\n");
return;
}

if (got_picture)
{
sws_scale(pImgConvertCtx, (uint8_t const * const *) pFrame->data, pFrame->linesize,
0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

//把这个RGB数据 用QImage加载
QImage tmpImg((uchar *)pOutBuffer, pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB32);
QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
emit sig_GetOneFrame(image); //发送信号
}
}
av_free_packet(pPacket);
//msleep(0.02);
}

av_free(pOutBuffer);
av_free(pFrameRGB);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
}

解码出一帧图像后,发送信号给图像显示线程显示

2.3 视频显示

这里是图像显示的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void MainWindow::slotGetOneFrame(QImage img)
{
ui->labelCenter->clear();
if(m_kPlayState == RPS_PAUSE)
{
return;
}

m_Image = img;
update(); //调用update将执行paintEvent函数
}

void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
int showWidth = this->width() - 100;
int showHeight = this->height() - 50;

painter.setBrush(Qt::white);
painter.drawRect(0, 0, this->width(), this->height()); //先画成白色

if (m_Image.size().width() <= 0)
{
return;
}

//将图像按比例缩放
QImage img = m_Image.scaled(QSize(showWidth, showHeight),Qt::KeepAspectRatio);
img = img.mirrored(m_bHFlip, m_bVFlip);

int x = this->width() - img.width();
int y = this->height() - img.height();
x /= 2;
y /= 2;

painter.drawImage(QPoint(x-40,y+20),img); //画出图像
}

2.4 按键操作处理

客户端界面中,有启动、暂停播放和视频画面翻转按钮,对应的处理逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void MainWindow::on_pushButton_toggled(bool checked)
{
if (checked) //第一次按下为启动,后续则为继续
{
if(m_kPlayState == RPS_IDLE)
{
ui->lineEditUrl->setEnabled(false);
m_strUrl = ui->lineEditUrl->text();
m_pPlayer->startPlay(m_strUrl);

ui->labelCenter->setText("rtsp网络连接中...");
}
m_kPlayState = RPS_RUNNING;
ui->pushButton->setText("暂停");
}
else
{
m_kPlayState = RPS_PAUSE;
ui->pushButton->setText("播放");
}
}

void MainWindow::on_checkBoxVFlip_clicked(bool checked)
{
m_bVFlip = checked;
}

void MainWindow::on_checkBoxHFlip_clicked(bool checked)
{
m_bHFlip = checked;
}

2.5 pro文件

因为要用到FFmpeg库,因此需要注意以下对FFmpeg库的引用,需要修改Qt工程的pro文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = rtspPlayer
TEMPLATE = app

SOURCES += main.cpp \
videoplayer.cpp \
mainwindow.cpp

HEADERS += \
videoplayer.h \
mainwindow.h

FORMS += \
mainwindow.ui

INCLUDEPATH+=$$PWD/ffmpeg/include

LIBS += $$PWD/ffmpeg/lib/x64/avcodec.lib \
$$PWD/ffmpeg/lib/x64/avdevice.lib \
$$PWD/ffmpeg/lib/x64/avfilter.lib \
$$PWD/ffmpeg/lib/x64/avformat.lib \
$$PWD/ffmpeg/lib/x64/avutil.lib \
$$PWD/ffmpeg/lib/x64/postproc.lib \
$$PWD/ffmpeg/lib/x64/swresample.lib \
$$PWD/ffmpeg/lib/x64/swscale.lib

3 运行测试

3.1 Win平台测试

在Win10平台上测试效果如下:

3.2 嵌入式Linux平台测试

在嵌入式Linux平台运行,也需要先进行FFmpeg运行环境的搭建,上篇文章已介绍如何交叉编译FFmpeg源码以及在嵌入式Linux平台搭建FFmpeg运行环境。

3.2.1 需要安装4.4版本的库

由于不同版本FFmpeg的API函数有些差别,上篇使用的是较新版本的FFmpeg源码,与4.4版本的可能不太一样,因此,需要参考上篇文章,重新在嵌入式Linux环境中安装4.4版本的FFmpeg。

4.4版本的源码可从如下链接下载:https://ffmpeg.org/download.html

3.2.2 修改pro文件

然后就是将Qt程序拷贝到Ubuntu中进行交叉编译,在编译之前,还要修改pro文件,使程序能够链接到linux版本的FFmpeg库,具体的修改如下,主要路径要修改为自己的ffmpeg库的安装位置。

1
2
3
4
5
6
7
8
9
10
11
12
INCLUDEPATH+=$$PWD/../ffmpeg442_install/include \
$$PWD/../x264_install/include

LIBS += $$PWD/../ffmpeg442_install/lib/libavcodec.so \
$$PWD/../ffmpeg442_install/lib/libavdevice.so \
$$PWD/../ffmpeg442_install/lib/libavfilter.so \
$$PWD/../ffmpeg442_install/lib/libavformat.so \
$$PWD/../ffmpeg442_install/lib/libavutil.so \
$$PWD/../ffmpeg442_install/lib/libpostproc.so \
$$PWD/../ffmpeg442_install/lib/libswresample.so \
$$PWD/../ffmpeg442_install/lib/libswscale.so \
$$PWD/../x264_install/lib/libx264.so

3.3 演示视频

https://www.bilibili.com/video/BV1PB4y1n7ax

4 总结

本篇介绍了通过Qt程序,设计一个RTSP视频播放器,运行在嵌入式Linux平台上,来播放网络视频,并增加启动、暂停、画面翻转等操作按钮。