上篇文章,介紹了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視頻流的解碼如下:
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);
}
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); //畫出圖像
}
av_free(pOutBuffer);
av_free(pFrameRGB);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
}
解碼出一幀圖像后,發送信號給圖像顯示線程顯示
2.3 視頻顯示
這里是圖像顯示的處理:
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 按鍵操作處理
客戶端界面中,有啟動、暫停播放和視頻畫面翻轉按鈕,對應的處理邏輯如下:
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文件
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庫的安裝位置。
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平臺上,來播放網絡視頻,并增加啟動、暫停、畫面翻轉等操作按鈕。
審核編輯:湯梓紅
-
嵌入式
+關注
關注
5141文章
19537瀏覽量
315032 -
播放器
+關注
關注
5文章
411瀏覽量
37926 -
RTSP
+關注
關注
0文章
14瀏覽量
12410 -
Qt
+關注
關注
1文章
313瀏覽量
38806
發布評論請先 登錄
基于MiniGUI的嵌入式媒體播放器的設計與實現
嵌入式媒體播放器怎么實現?
如何實現基于QT4.7.4音樂播放器的設計
嵌入式媒體播放器
基于Qt的嵌入式媒體播放器系統的設計
嵌入式RMVB播放器音視頻同步及其實現
基于DirectFB的嵌入式播放器設計

基于嵌入式Linux流媒體播放器系統軟硬件解決方案

基于Windows CENet 42嵌入式操作系統多媒體播放器的
基于DirectFB的嵌入式播放器的設計與實現

[嵌入式Linux項目實戰開發]基于QT4.7.4的音樂播放器實現與設計【2018年給力項目】
![[<b class='flag-5'>嵌入式</b>Linux項目實戰開發]基于<b class='flag-5'>QT</b>4.7.4的音樂<b class='flag-5'>播放器</b>實現與設計【2018年給力項目】](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評論