Qt多線程安全更新QLabel的實現(xiàn)
背景——在 Qt 應(yīng)用程序中渲染視頻時,一個常見的需求是將每一幀更新到 QLabel 控件中顯示。但是,當(dāng)渲染線程不在主線程時,直接更新UI可能會導(dǎo)致程序崩潰。
解析視頻數(shù)據(jù)
處理原始字節(jié)數(shù)組
對于RGBA格式的原始字節(jié)數(shù)組,可以直接創(chuàng)建QImage:
QImage image(frame, width, height, width * 4, QImage::Format_RGBA8888);
image = image.scaled(label->width(), label->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
處理YUV數(shù)據(jù)
對于YUV格式的數(shù)據(jù),需要先獲取Y、U、V分量的buffer和pitch(步長):
uint8_t *i420_buffer[3];
int pitch[3];
i420_buffer[0] = frame->GetBufferY();
i420_buffer[1] = frame->GetBufferU();
i420_buffer[2] =frame->GetBufferV();
pitch[0] = frame->GetPitch0();
pitch[1] = frame->GetPitch1();
pitch[2] = frame->GetPitch2();
i420_buffer 數(shù)組和 pitch 數(shù)組分別包含了YUV幀的數(shù)據(jù)和每行的字節(jié)數(shù)(也稱為步長)。i420_buffer[0],i420_buffer[1] 和i420_buffer[2]分別包含了Y,U和V的數(shù)據(jù)。pitch[0],pitch[1]和pitch[2]分別是Y,U和V的步長。然后我們就可以使用這些數(shù)據(jù)和步長來訪問YUV幀的每個像素:
- 訪問第y行第x列的Y值:
uint8_t Y = i420_buffer[0][y * pitch[0] + x];
- 訪問第y行第x列的U和V值:
uint8_t U = i420_buffer[1][(y/2)* pitch[1]+(x / 2)];
uint8_t V = i420_buffer[2][(y/2)* pitch[2]+(x / 2)];
(U和V的數(shù)據(jù)是以2x2的塊存儲的,所以需要除以2來獲取正確的索引。)
YUV到RGB的轉(zhuǎn)換可以通過以下公式進行:
R = Y + 1.402 * (V - 128) G = Y - 0.344 * (U - 128) - 0.714 * (V - 128) B = Y + 1.772 * (U - 128)
for(int y = 0; y < height; ++y){
for(int x = 0; x < width; ++x){
uint8_t Y = i420 buffer[0][y * pitch[0] + x];
uint8_t U = i420 buffer[1][(y / 2) * pitch[1] + (x / 2)];
uint8_t V = i420 buffer[2][(y / 2) * pitch[2] + (x / 2)];
int R = Y + 1.402 * (V - 128);
int G = Y - 0,344 * (U - 128) - 0.714 * (V - 128);
int B = Y + 1.772 * (U -128);
image.setPixelColor(x,y,QColor(qBound(0, R, 255), qBound(0, G, 255), qBound(0, B, 255)));
}
}
但是?。。?/p>
使用 setPixelColor 效率太低啦,一不小心還會收到 Qt Creator 的小提醒 ????:
The cached device pixel ratio value was stale on window expose. Please file a QTBUG which explains how to reproduce.
我們有更好用的工具:libyuv
libyuv::I420ToARGB(src_y, src_stride_y, src_u, src_stride_u, src_v, stc_stride_v, argb_buffer, width * 4, width, height);
安全地渲染到QLabel
如果渲染視頻的線程是主線程,我們可以在得到 QImage 之后直接設(shè)置到 QLabel :
label->setPixmap(QPixmap::fromImage(image));
但是如果我們是在回調(diào)函數(shù)中解析幀數(shù)據(jù)并更新(也就是在非主線程中更新UI組件),那就可能會導(dǎo)致崩潰或其他不穩(wěn)定行為啦。Qt 的 GUI 組件不是線程安全的,這意味著我們只能在主線程中對它們進行操作。
可以使用信號和槽機制將圖像傳遞回主線程:在非主線程中處理圖像并通過信號發(fā)送圖像數(shù)據(jù),在主線程中接收信號并更新QLabel。
// 在非主線程中
void processImage() {
// 將數(shù)據(jù)轉(zhuǎn)換為QImage
emit imageProcessed(image);
}
// 在主線程中
void renderImage(const QImage& image) {
// ……
label->setPixmap(QPixmap::fromImage(image));
}
還有一種更巧妙的寫法(參考 Qt Forum 討論 ):
QTimer::singleShot(0, this, [this, pixmap](){
ui->label->setPixmap(pixmap);
ui->label->setScaledContents(true);
ui->label->show();
});
這種方法會在主事件循環(huán)的下一個時間點執(zhí)行更新操作,從而確保在主線程中安全地更新UI。
總結(jié)
通過以上方法,我們可以在多線程環(huán)境下安全高效地將視頻幀渲染到QLabel控件中,避免程序崩潰等問題。
到此這篇關(guān)于Qt多線程安全更新QLabel的實現(xiàn)的文章就介紹到這了,更多相關(guān)Qt 安全更新QLabel內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Qt實現(xiàn)的自定義樹結(jié)構(gòu)容器
在Qt框架中,盡管其提供了許多強大的容器類,但缺少一個通用的、靈活的樹結(jié)構(gòu)容器,所以本文將設(shè)計并實現(xiàn)一個可復(fù)用的自定義樹結(jié)構(gòu)容器,需要的可以參考下2024-12-12
C語言 OutputDebugString與格式化輸出函數(shù)OutputDebugPrintf案例詳解
這篇文章主要介紹了C語言 OutputDebugString與格式化輸出函數(shù)OutputDebugPrintf案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08
關(guān)于C++STL string類的介紹及模擬實現(xiàn)
這篇文章主要介紹了關(guān)于C++STL string類的介紹及模擬實現(xiàn)的相關(guān)資料,需要的朋友可以參考下面具體的文章內(nèi)容2021-09-09

