- 本文是根據PaintEvent事件處理函數不停在組件中繪制視頻幀數據
做過圖像界面開發的都知道,任何耗時的操作都不能放在主線程進行,一旦主線程阻塞了,那么體現出來的就是界面卡了。 而我們讀取視頻和解碼視頻是一個非常耗時的操作,因此需要另外開辟一個線程來專門做這件事
項目准備工作:
- 安裝和配置Qt;
- ffmpeg配置
具體步驟讀者可以直接百度搜索;
項目具體步驟如下:
- ffmpeg解碼視頻文件得到的yuv數據
- 將yuv數據轉為RGB數據
- 用QImage加載RGB數據
- 傳輸至組件
- PaintEvent函數繪制圖像
項目代碼實現
- 新建一個線程類,繼承自QThread
class VideoPlayer : public QThread
- 在新建的線程類中重載其run函數,把解碼耗時操作全部都在run函數里面執行。
解碼具體流程如下(引用雷博士博客中的圖片,致敬,連接:https://blog.csdn.net/leixiaohua1020/article/details/8652605)
視頻解碼就是要到視頻文件中尋找視頻流,找到后對流逐幀解碼
代碼如下所示
//1 初始化FFMPEG
av_register_all(); //調用了這個才能正常適用編碼器和解碼器
//2 分配AVFormatContext
AVFormatContext *pFormatCtx = avformat_alloc_context();//FFMPEG所有的操作都要通過這個AVFormatContext來進行
char *file_path = "join.avi";
//3. 打開視頻文件
if (avformat_open_input(&pFormatCtx, file_path, NULL, NULL) != 0) {//打開視頻文件
qDebug()<<"can't open the file. ";
return ; }
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {//因為視頻文件比較復雜所以打開后還需要讀取詳細的流信息
qDebug()<<"Could't find stream infomation.";
return ;
}
// 4. 找文件中的視頻流
///循環查找視頻中包含的流信息,直到找到視頻類型的流
///便將其記錄下來保存到videoStream變量中
///這里我們現在只處理視頻流 音頻流先不管他
int videoStream=-1;
qDebug()<<QObject::tr("視頻流個數:")<<pFormatCtx->nb_streams;
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
}
}
///如果videoStream為說明沒有找到視頻流
if (videoStream == -1) {
qDebug()<<"Didn't find a video stream.";
return ;
}
//5 根據視頻流 打開一個解碼器來解碼:
///查找解碼器
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
//5.1 找到視頻流相對應的解碼器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
qDebug()<<"Codec not found. ";
return ;
}
///5.2 打開解碼器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
qDebug()<<"Could not open codec.";
return ;}
//6 讀取視頻
AVFrame *pFrame, *pFrameRGB;
int numBytes; uint8_t *out_buffer;
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
static struct SwsContext *img_convert_ctx;
//將解碼后的YUV數據轉換成RGB32
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
out_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
pCodecCtx->width, pCodecCtx->height);
int y_size = pCodecCtx->width * pCodecCtx->height;
AVPacket *packet = (AVPacket *) malloc(sizeof(AVPacket)); //6.1 分配一個packet AVPacket是存儲壓縮編碼數據相關信息的一個結構體。
av_new_packet(packet, y_size); //分配packet的數據
av_dump_format(pFormatCtx, 0, file_path, 0); //輸出視頻信息
int index = 0;
int ret, got_picture;
while (1)
{
if (av_read_frame(pFormatCtx, packet) < 0)
{
break; //這里認為視頻讀取完了
}
if (packet->stream_index == videoStream) {
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);//7.解碼
if (ret < 0) {
qDebug()<<"decode error.";
return ;
}
if (got_picture) {//8. YUV420數據>RGB
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);
}
}
av_free_packet(packet);
msleep(40); //延時ms使其幀率=1000/40
}
- 發送QImage數據到界面
由於我們不能夠在子線程中操作界面,(操作界面只能在主線程中進行,幾乎所有的圖形界面開發都是這樣設定),因此我們只能給主線程發送信號,信號帶上這個QIMage,讓主線程幫忙把這個圖像顯示出來。所以需要在 YUV420數據>RGB 后加入以下代碼
//把這個RGB數據用QImage加載
QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
QImage image = tmpImg.copy(); //把圖像復制一份傳遞給界面顯示
emit sig_GetOneFrame(image); //發送信號
- 槽函數調用update函數,自動調事件處理函數
void CameraClient::slotGetOneFrame(QImage img){
mImage = img;
update(); //調用update將執行paintEvent函數
}
- 事件處理函數繪制
void CameraClient::paintEvent(QPaintEvent *event){
QPainter painter(this);
painter.setBrush(Qt::black);
painter.drawRect(0, 0, this->width(), this->height()); //先畫成黑色
if (mImage.size().width() <= 0) return;
///將圖像按比例縮放成和窗口一樣大小
QImage img = mImage.scaled(this->size(),Qt::KeepAspectRatio);
int x = this->width() - img.width();
int y = this->height() - img.height();
x /= 2;
y /= 2;
painter.drawImage(QPoint(x,y),img); //畫出圖像
}
這樣就完成了簡單的視頻顯示
完整代碼:
https://download.csdn.net/download/a18796007675/10454097
參考:http://blog.yundiantech.com/?log=blog&id=9
