本文檔是自己所整理的一份文檔,部分是原創,還轉貼了網上的一此資料(已經標明了),(難點是多線程的編寫),是有源代碼的,大家可以作為參考,用到的知識是視頻采集,壓縮解壓(xvid),實時傳輸(jrtp),基於qt庫所寫的,由於本人對qt下的多線程還不很了解,只做了單線程的(采集-->壓縮-->解壓-->發送-->接收--顯示),用timer來刷新視頻播放窗口,現在正在研究多線程(代碼還在整理中),以后再換成多線程(用qt4的多線程,因為qt4的線程繼承於QObject的,線程間可以使用signal-slot機制通信),建設先看看“linux下的tv播放器.doc(網上的資料)”
一.把視頻顯示到界面的方法
(1)針對qt4的(視頻格式為rgb32)
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
QImage image(pBuffer,320,240,QImage::Format_RGB32);
QPixmap pixmap;
pixmap=pixmap.fromImage(image);
label->setPixmap(pixmap);
label->setFixedSize(pixmap.width(),pixmap.height());
(2)針對qt3的
1)格式為rgb32的
QImage *img;
unsigned char *bit=image;
setWFlags(getWFlags() | Qt::WRepaintNoErase);
img=new QImage((uchar *)bit,MAX_WIDTH, MAX_HEIGHT, 32,NULL,0,QImage::IgnoreEndian);
bitBlt(this, 0, 0, img);
2)格式為rgb24的
int x, y;
int i = 0;
#if 0
QLabel *label_time;
QTime time = QTime::currentTime();
label_time = new QLabel(time.toString(),this, "label_time" );
label_time->setGeometry( 5, 250, 160, 31 );
label_time->setAlignment( QLabel::AlignCenter );
#endif
v4l_grab_movie(&v4l_dev);
QString a;
QString d;
QImage img;
unsigned char *bit= v4l_dev.buffer;
QRgb *point;
int r, g, b;
QPainter paint;
//該步很重要,設置標志
//讓QWidget在更新窗體時,不擦除原來的窗體
//這樣可以避免閃屏
setWFlags(getWFlags() | Qt::WRepaintNoErase);
if(img.create(MAX_WIDTH, MAX_HEIGHT, 32, 0, QImage::IgnoreEndian))
{
for(y=0; y<MAX_HEIGHT; y++)
{
for(x=0; x<MAX_WIDTH; x++)
{
r=(int)bit[i+2];
g=(int)bit[i+1];
b=(int)bit[i];
point= (QRgb *)(img).scanLine(y)+ x;
*point = qRgb(r,g,b);
i+=3;
}
}
}
paint.begin(this);
QDate date=QDate::currentDate();
d=date.toString();
QTime time = QTime::currentTime();
a=time.toString();
paint.drawImage(5, 5, (img));
paint.drawText(20,20,a,-1);
paint.drawText(20,30,d,-1);
paint.end();
二.qt的多線程問題(qt4與qt3有線程是很大不同的)
1)如果不用多線程,一般是通過QApplication的消息循環來處理的
2)QThread本身是繼承於QObject的,為線程間的signal-slot機制打下了基礎(Qt4),而qt3的線程不是繼承於QObject,不能在線程間使用signal-slot機制(如QObject::connect(Thread, SIGNAL(Log(QString)), this, SLOT(Logslots(QString)))不能應用在qt3中,只能應用在qt4中)
3)QObject本身和線程是沒關系的,提供signal-slot機制相關信息
三.事件和信號的區別(問題:哪什么時候用事件,什么時候用信號呢?是不是不同的線程間用事件,信號不能用在線程間?但現在的qt4可以在線程間發送信號)
仔細來看,事件與信號其實並無多大差別,從我們對其需求上來說,都只要能注冊事件或信號響應函數,在事件或信號產生時能夠被通知到即可。但有一項區別在於,事件處理函數的返回值是有意義的,我們要根據這個返回值來確定是否還要繼續事件的處理,比如在QT中,事件處理函數如果返回true,則這個事件處理已完成,QApplication會接着處理下一個事件,而如果返回false,那么事件分派函數會繼續向上尋找下一個可以處理該事件的注冊方法。信號處理函數的返回值對信號分派器來說是無意義的。
另外還有一個需要我們關注的問題是事件和信號處理時的優先級問題。在QT中,事件因為都是與窗口相關的,所以事件回調時都是從當前窗口開始,一級一級向上派發,直到有一個窗口返回true,截斷了事件的處理為止。對於信號的處理則比較簡單,默認是沒有順序的,如果需要明確的順序,可以在信號注冊時顯示地指明槽的位置。
在QT中,事件使用了一個事件隊列來維護,如果事件的處理中又產生了新的事件,那么新的事件會加入到隊列尾,直到當前事件處理完畢后, QApplication再去隊列頭取下一個事件來處理。而信號的處理方式有些不同,信號處理是立即回調的,也就是一個信號產生后,他上面所注冊的所有槽都會立即被回調。這樣就會產生一個遞歸調用的問題,比如某個信號處理器中又產生了一個信號,會使得信號的處理像一棵樹一樣的展開。
四.基於QT4的一個多線程工程實現(網上見到的一個比較好的例子,關於線程任務分配) (網址:http://easons.blogbus.com/logs/14845035.html)
想法:需要模仿ACE異步調用的方法,在一個線程分配任務給工作線程,並等待工作線程完成后返回結果。
定義一個線程類:
頭文件:
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QEvent>
#define METHOD_EVENT QEvent::User + 1028
class MethodEvent : public QEvent
{
public:
MethodEvent() : QEvent(QEvent::Type(METHOD_EVENT))
{
}
~MethodEvent()
{
}
public:
int i;//存儲返回值!
};
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread();
~MyThread();
bool StartThread();
bool StopThread();
protected:
void run();
void customEvent(QEvent * e);
};
#endif // MYTHREAD_H
實現文件:
#include "mythread.h"
MyThread::MyThread()
{
}
MyThread::~MyThread()
{
}
bool MyThread::StartThread()
{
start();//線程啟動,調用run
return true;
}
bool MyThread::StopThread()
{
exit();
return false;
}
void MyThread::run()
{
exec();//進入本線程的消息循環
}
void MyThread::customEvent(QEvent * e)
{
if(0 == e)
return;
if( METHOD_EVENT != e->type() )
{
return;
}
MethodEvent* methodEvent = static_cast<MethodEvent*>(e);
if(NULL == methodEvent)
return;
for (int i = 0; i < 1000000000; ++i)
{
methodEvent->i = i;
}
}
主函數:
#include <QtCore/QCoreApplication>
#include "mythread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread mythread;
mythread.StartThread();//啟動輔助線程,
MethodEvent event;//新建一個任務
QCoreApplication::sendEvent(&mythread, &event);//發給工作線程,並阻塞等待
int result = event.i;//得到返回值。
return a.exec();
}
說明:如果不用到返回值,也就是只是簡單分配任務的話,可以用postEvent的方法,也就是要new一個event,發給工作線程,工作線程處理完事件后會去刪除你new的event,主線程就不管了。換句話說,postEvent馬上返回,不管event是否被處理完,兩個線程同時在工作。而上面是主線程要等待工作線程完成工作后才可以繼續,阻塞在sendEvent上了
五.圖像循環隊列(攝像頭的采集數據放到圖像循環隊列)
程序通過建立帶共享鎖的4幀圖像循環隊列做為圖像采集線程和圖像發送線程進行數據交換的公共緩沖區(帶共享鎖的循環隊列在這個網址下有介紹:http://www.zaoxue.com/article/tech-55122.htm)
能夠在通信中更好的對數據進行讀寫和存儲,在程序編寫過程中就把數據隊列的方式改為了
循環隊列。通過設定數據存儲地址的頭指針和尾指針,以及對數據存儲長度狀態值的判斷,從而達到
循環隊列的目的。當有新的數據到來時,數據的尾指針自動往后移,長度的也做相應的增加。同時判斷數據的長度有沒有超出BANK的地址范圍,如果超出地址范圍,則尾指針跳轉到頭指針之前,繼續往后存儲數據,形成了
循環隊列;同樣,當從RAM里取走數據時,數據的頭指針自動往后移,長度的也做相應的增減。在隊列的出隊操作中,還要對數據的長度進行判斷,如果達到了最大長度,則丟棄后面所有的數據。直到緩存區能有空間繼續作隊列為止。
六.視頻采集數據緩沖機制的研究
在視頻采集系統中,視頻數據的采集與發送需要很好的性能,所以需要一個高性能的數據流緩沖機制,在傳統的生產者/消費者模型的基礎上,提出一種新的數據流緩沖模型(在一份叫做
視頻
會議系統中數據緩沖機制的研究.pdf
中有介紹)(一個關於多線程同步的文章 :http://www.vckbase.com/document/viewdoc/?id=1080)
七.GNU/Linux中解決多線程互斥同步問題的分析和說明(
http://blog.chinaunix.net/u1/35100/showart.php?id=274716)
當解決多線程互斥同步的問題時,
經常會有如下幾個問題:
1. 在一個給定的問題中, 需要多少個Mutex, 多少個Semaphore? 有什么規律?
2. 在對臨界區加鎖和等待信號量的順序上有什么要求和規律?
3. 什么樣操作適合放在臨界區, 什么樣的不適合?
下面就生產者和消費者問題來分析一些這幾個問題.
下面是一個簡單的實現程序:
生產者向數組sharedArray 中寫入數據, 而消費者從該數組中讀取數據.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#define MAXSIZE 5 /* 共享緩沖區的大小*/
int sharedArray[MAXSIZE]; /*sharedArray 是共享緩沖區*/
int curr=-1; /*curr 是用來指定sharedArray 當前存有數據的最大位置*/
/* 注意,sharedArray 和curr 都屬於共享數據*/
int empty=0;
int full=MAXSIZE;
pthread_mutex_t sharedMutex=PTHREAD_MUTEX_INITIALIZER; /* 鎖定臨界區的mutex*/
sem_t waitNonEmpty, waitNonFull; /* 等待" 非空資源" 和等待" 非滿資源" 的semaphor*/
void * readData(void * whichone)
{
int data, position;
while (1){
sem_wait(&waitNonEmpty); /* 是否有" 非空資源"*/
pthread_mutex_lock(&sharedMutex); /* 進入臨界區*/
data = sharedArray[curr];
position = curr--;
printf ("%s read from the %dth: %d, \n", (char*)whichone, position, data);
sem_post(&waitNonFull); /* 生成一個" 非滿資源"*/
pthread_mutex_unlock(&sharedMutex); /* 離開臨界區*/
sleep(2); /* 跟同步無關的費時操作*/
}
}
void * writeData(void * whichone)
{
int data, position;
while (1) {
data=(int)(10.0*random()/RAND_MAX); /* 生成一個隨機數據, 注意是10.0 而不是10*/
sem_wait(&waitNonFull); /* 是否有" 非滿資源"*/
pthread_mutex_lock(&sharedMutex); /* 進入臨界區*/
position = ++curr;
sharedArray[curr]=data;
printf ("%s wrote to the %dth: %d, \n", (char*)whichone, position, data);
sem_post(&waitNonEmpty); /* 生成一個" 非空資源"*/
pthread_mutex_unlock(&sharedMutex); /* 離開臨界區*/
sleep(1); /* 跟同步無關的費時操作*/
}
}
int main (int argc, char** argv)
{
pthread_t consumer1, consumer2, producer1, producer2; /* 兩個生產者和兩個消費者*/
sem_init(&waitNonEmpty, 0, empty); /* 初始化信號量*/
sem_init(&waitNonFull, 0, full);
/* 注意, 本問題中的兩種semaphore 是有一定關系的, 那就是它們的初始值之和應該等於共享緩沖區大小*/
/* 即empty+full 等於MAXSIZE*/
pthread_create (&consumer1, NULL, &readData, "consumer1");
pthread_create (&consumer2, NULL, &readData, "consumer2");
pthread_create (&producer1, NULL, &writeData, "producer1");
pthread_create (&producer2, NULL, &writeData, "producer2");
pthread_join (consumer1, NULL);
pthread_join (consumer2, NULL);
pthread_join (producer1, NULL);
pthread_join (producer2, NULL);
sem_destroy(&waitNonEmpty);
sem_destroy(&waitNonFull);
}
分析和說明:
1. 在一個給定的問題中, 需要多少個Mutex, 多少個Semaphore? 有什么規律?
在本問題中, 共需要一個Mutex 和兩個Semaphore.
其中,Mutex 是用來鎖定臨界區的, 以解決對共享數據的互斥訪問問題( 無論是對生成者還是對消費者);
我們共需要兩個Semaphore, 這是因為在本問題中共有兩個稀缺資源.
第一種是" 非空" 這種資源, 是在消費者之間進行競爭的.
第二種是" 非滿" 這種資源, 是在生產者之間進行競爭的.
所以, 一般來說, 需要鎖定臨界區, 就需要Mutex; 有幾種稀缺資源就需要幾個Semaphore.
對稀缺資源的分析不能想當然. 稀缺資源不一定是指被共享的資源, 很多時候是指線程會被阻塞的條件( 除了要進臨界區被阻塞外).
本例中, 消費者會在緩沖區為空時被阻塞, 所以" 非空" 是一種稀缺資源;
生產者會在緩沖區為滿時被阻塞, 所以" 非滿" 也是一種稀缺資源.
2. 在對臨界區加鎖和等待信號量的順序上有什么要求和規律?
這里要說兩點:
第一, 不要將等待信號量的語句放在被鎖定的臨界區內, 這樣會造成死鎖. 而且這也是很沒有必要的.
比如, 消費者在緩沖區沒有數據的時候進入臨界區, 這樣就會把臨界區鎖上, 由於沒有數據, 消費者也會被鎖上.
這時, 任何生產者都會由於臨界區被鎖上而被block 住, 這樣就造成了死鎖.
第二, 如果有多個Semaphore 需要等待, 那么每個線程中, 最好對這多個信號量進行等待的順序一致,
不然的話很容易造成死鎖.
3. 什么樣操作適合放在臨界區, 什么樣的不適合?
一般來說, 臨界區中只放對共享數據進行訪問的語句, 這樣會改善程序的性能.
很多時候, 取出共享數據的副本后, 對副本進行費時的各種操作就不需要放在臨界區了.
比如, 本例中的sleep 語句就根本不需要放入臨界區.
1. 在一個給定的問題中, 需要多少個Mutex, 多少個Semaphore? 有什么規律?
2. 在對臨界區加鎖和等待信號量的順序上有什么要求和規律?
3. 什么樣操作適合放在臨界區, 什么樣的不適合?
下面就生產者和消費者問題來分析一些這幾個問題.
下面是一個簡單的實現程序:
生產者向數組sharedArray 中寫入數據, 而消費者從該數組中讀取數據.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#define MAXSIZE 5 /* 共享緩沖區的大小*/
int sharedArray[MAXSIZE]; /*sharedArray 是共享緩沖區*/
int curr=-1; /*curr 是用來指定sharedArray 當前存有數據的最大位置*/
/* 注意,sharedArray 和curr 都屬於共享數據*/
int empty=0;
int full=MAXSIZE;
pthread_mutex_t sharedMutex=PTHREAD_MUTEX_INITIALIZER; /* 鎖定臨界區的mutex*/
sem_t waitNonEmpty, waitNonFull; /* 等待" 非空資源" 和等待" 非滿資源" 的semaphor*/
void * readData(void * whichone)
{
int data, position;
while (1){
sem_wait(&waitNonEmpty); /* 是否有" 非空資源"*/
pthread_mutex_lock(&sharedMutex); /* 進入臨界區*/
data = sharedArray[curr];
position = curr--;
printf ("%s read from the %dth: %d, \n", (char*)whichone, position, data);
sem_post(&waitNonFull); /* 生成一個" 非滿資源"*/
pthread_mutex_unlock(&sharedMutex); /* 離開臨界區*/
sleep(2); /* 跟同步無關的費時操作*/
}
}
void * writeData(void * whichone)
{
int data, position;
while (1) {
data=(int)(10.0*random()/RAND_MAX); /* 生成一個隨機數據, 注意是10.0 而不是10*/
sem_wait(&waitNonFull); /* 是否有" 非滿資源"*/
pthread_mutex_lock(&sharedMutex); /* 進入臨界區*/
position = ++curr;
sharedArray[curr]=data;
printf ("%s wrote to the %dth: %d, \n", (char*)whichone, position, data);
sem_post(&waitNonEmpty); /* 生成一個" 非空資源"*/
pthread_mutex_unlock(&sharedMutex); /* 離開臨界區*/
sleep(1); /* 跟同步無關的費時操作*/
}
}
int main (int argc, char** argv)
{
pthread_t consumer1, consumer2, producer1, producer2; /* 兩個生產者和兩個消費者*/
sem_init(&waitNonEmpty, 0, empty); /* 初始化信號量*/
sem_init(&waitNonFull, 0, full);
/* 注意, 本問題中的兩種semaphore 是有一定關系的, 那就是它們的初始值之和應該等於共享緩沖區大小*/
/* 即empty+full 等於MAXSIZE*/
pthread_create (&consumer1, NULL, &readData, "consumer1");
pthread_create (&consumer2, NULL, &readData, "consumer2");
pthread_create (&producer1, NULL, &writeData, "producer1");
pthread_create (&producer2, NULL, &writeData, "producer2");
pthread_join (consumer1, NULL);
pthread_join (consumer2, NULL);
pthread_join (producer1, NULL);
pthread_join (producer2, NULL);
sem_destroy(&waitNonEmpty);
sem_destroy(&waitNonFull);
}
分析和說明:
1. 在一個給定的問題中, 需要多少個Mutex, 多少個Semaphore? 有什么規律?
在本問題中, 共需要一個Mutex 和兩個Semaphore.
其中,Mutex 是用來鎖定臨界區的, 以解決對共享數據的互斥訪問問題( 無論是對生成者還是對消費者);
我們共需要兩個Semaphore, 這是因為在本問題中共有兩個稀缺資源.
第一種是" 非空" 這種資源, 是在消費者之間進行競爭的.
第二種是" 非滿" 這種資源, 是在生產者之間進行競爭的.
所以, 一般來說, 需要鎖定臨界區, 就需要Mutex; 有幾種稀缺資源就需要幾個Semaphore.
對稀缺資源的分析不能想當然. 稀缺資源不一定是指被共享的資源, 很多時候是指線程會被阻塞的條件( 除了要進臨界區被阻塞外).
本例中, 消費者會在緩沖區為空時被阻塞, 所以" 非空" 是一種稀缺資源;
生產者會在緩沖區為滿時被阻塞, 所以" 非滿" 也是一種稀缺資源.
2. 在對臨界區加鎖和等待信號量的順序上有什么要求和規律?
這里要說兩點:
第一, 不要將等待信號量的語句放在被鎖定的臨界區內, 這樣會造成死鎖. 而且這也是很沒有必要的.
比如, 消費者在緩沖區沒有數據的時候進入臨界區, 這樣就會把臨界區鎖上, 由於沒有數據, 消費者也會被鎖上.
這時, 任何生產者都會由於臨界區被鎖上而被block 住, 這樣就造成了死鎖.
第二, 如果有多個Semaphore 需要等待, 那么每個線程中, 最好對這多個信號量進行等待的順序一致,
不然的話很容易造成死鎖.
3. 什么樣操作適合放在臨界區, 什么樣的不適合?
一般來說, 臨界區中只放對共享數據進行訪問的語句, 這樣會改善程序的性能.
很多時候, 取出共享數據的副本后, 對副本進行費時的各種操作就不需要放在臨界區了.
比如, 本例中的sleep 語句就根本不需要放入臨界區.
八.教你如何測試循環緩沖區(代碼我是參考DivX播放器源代碼-playa-0.3.3src.zip這個開源軟件的)
這個緩沖區主要包括這兩個文件:Queue.h,Queue.cpp(
具體代碼請參考程序)
主要函數:
RingBuff::RingBuff()
{
read_pos = 0;
write_pos = 0;
}
void RingBuff::ring_read(unsigned char *data, int size)
{
MutexBuff.lock();
if(write_pos <= read_pos) {
if(read_pos + size < RING_SIZE) {
memcpy(data, ring + read_pos, size);
read_pos += size;
}
else {
if(read_pos + size < RING_SIZE + write_pos) {
unsigned int before, after;
before = (RING_SIZE - 1) - read_pos;
after = size - before;
memcpy(data, ring + read_pos, before);
memcpy(data + before, ring, after);
read_pos = after;
}
else {
}
}
}
else {
if(read_pos + size <= write_pos) {
memcpy(data, ring + read_pos, size);
read_pos += size;
}
else {
}
}
MutexBuff.unlock();
}
void RingBuff::ring_write(unsigned char *data, int size)
{
MutexBuff.lock();
if(write_pos >= read_pos) {
if(write_pos + size < RING_SIZE) {
memcpy(ring + write_pos, data, size);
write_pos += size;
}
else {
if(write_pos + size < RING_SIZE + read_pos) {
unsigned int before, after;
before = (RING_SIZE - 1) - write_pos;
after = size - before;
memcpy(ring + write_pos, data, before);
memcpy(ring, data + before, after);
write_pos = after;
}
}
}
else {
if(write_pos + size <= read_pos) {
memcpy(ring + write_pos, data, size);
write_pos += size;
}
MutexBuff.unlock();
return;
}
MutexBuff.unlock();
}
int RingBuff::ring_full(int size)
{
if(write_pos == read_pos)
return 0;
if(write_pos > read_pos) {
if(write_pos + size < read_pos + RING_SIZE)
return 0;
return 1;
}
else {
if(write_pos + size < read_pos)
return 0;
return 1;
}
}
void RingBuff::CleanUp()
{
if(ring)
delete []ring;
// ring = 0;
write_pos = read_pos = 0;
}
測試自己所建的兩個視頻采集緩沖區:(
這個是應用在多線程系統中的,
但測試是用於一個線程中全部執行的,
測試是沒有問題的,但如果把“從每二個視頻緩沖區中取出視頻數據--->把從第二個視頻緩沖區中取出的視頻數據顯示出來”這部分放到解壓線程中測試時是可以顯示圖像的,但顯示的圖像的顏色變了,我想應該是緩沖區的QMutex
問題)
//
第一種測試(
在同一個線程中執行)
void CapThread::run(){
for(;;){
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
parent->writeCapBuff(pBuffer,320*240*4);//
將視頻數據放入到每一個緩沖區
parent->readCapBuff(testbuffer,320*240*4);//
從每一個緩沖區中取得視頻數據
parent->writeEnCodebuff(testbuffer,320*240*4);//
把從第一個緩沖區中取得的視頻數據放到第二個視頻緩沖區中
parent->readEnCodebuff(testbuffer2,320*240*4);//
從每二個視頻緩沖區中取出視頻數據
//add--->>
把從第二個視頻緩沖區中取出的視頻數據顯示出來
//QImage image(testbuffer,320,240,QImage::Format_RGB32);
//QImage image(pBuffer,320,240,QImage::Format_RGB32);
QImage image(testbuffer2,320,240,QImage::Format_RGB32);
QPixmap pixmap;
pixmap=pixmap.fromImage(image);
parent->label->setPixmap(pixmap);
parent->label->setFixedSize(pixmap.width(),pixmap.height());
//add
}
}
//
第二種情況(
在兩個線程中分別執行)
void CapThread::run(){
for(;;){
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
parent->writeCapBuff(pBuffer,320*240*4);//
將視頻數據放入到每一個緩沖區
parent->readCapBuff(testbuffer,320*240*4);//
從每一個緩沖區中取得視頻數據
parent->writeEnCodebuff(testbuffer,320*240*4);//
把從第一個緩沖區中取得的視頻數據放到第二個視頻緩沖區中
}
}
void CXvidDec::run()
{for(;;){
parent->readEnCodebuff(getEnCodeBuff,320*240*4);
//Decode(getEnCodeBuff, 320*240*4) ;
//v4l_save_pnm(m_image, 320, 240, 3);
//add_display
//QImage image(m_image,320,240,QImage::Format_RGB32);
QImage image(getEnCodeBuff,320,240,QImage::Format_RGB32);
QPixmap pixmap;
pixmap=pixmap.fromImage(image);
parent->label->setPixmap(pixmap);
parent->label->setFixedSize(pixmap.width(),pixmap.height());
//ddd_display
}
}
我們要測試 ring_read()
與ring_write()
是否符合我們的要求,本人設計了一種簡單的測試方法:
就是用ring_write()
把采集到的視頻數據放到緩沖區中,然后用ring_read()
從緩沖區中讀取數據,然后將數據保存成一張圖片,如果圖片是輸入的圖像就證明了緩沖區的代碼是正確的,
由於我使用的隊列緩沖區完全是由生產者驅動的,就是說隊列的推進的速度等於生產者生產產品的速度,
消費者不一定必必須消費每一個產品,在一定程序上,丟失某些數據是允許的
循環緩沖區由一個固定大小的內存緩沖區構成,進程使用這個內存緩沖區進行日志記錄。顧名思義,該緩沖區采用循環的方式進行實現。當該緩沖區填滿了數據時,無需為新的數據分配更多的內存,而是從緩沖區開始的位置對其進行寫操作,因此將覆蓋以前的內容。
1)
對循環緩沖區進行寫操作
2
)
注意事項---在多線程程序中使用循環緩沖區
這個部分介紹了在多線程應用程序中使用循環緩沖區啟時需要考慮的一些重要方面。
在訪問一個公共的資源時,同步 始終是多線程程序不可缺少的部分。因為每個線程都試圖對全局空間進行寫操作,所以必須確保它們同步地寫入內存,否則消息就會遭到破壞。通常,每個線程在寫入緩沖區之前都持有一個鎖,在完成操作時釋放該鎖。您可以下載一個使用鎖對內存進行寫操作的循環緩沖區示例。
這種方法具有以下的缺點:如果您的應用程序中包含幾個線程,並且每個線程都在進行訪問緩沖區,那么該進程的整體性能將會受到影響,因為這些線程將在獲得和釋放鎖上花費了大部分的時間。
通過使得每個線程將數據寫入到它自己的內存塊,就可以完全避免同步問題。當收到來自用戶的轉儲數據的請求時,每個線程獲得一個鎖,並將其轉儲到中心 位置。因為僅在將數據刷新到磁盤時獲得鎖,所以性能並不會受到很大的影響。
九
.
每二種緩沖方法
,
先建多個
buffer,
然后將這些
buffer
構成
bufferpool(
本人覺得這種方法比較好
)
class CBuffer
{
BYTE * m_pbBuffer ; // buffer pointer for data
DWORD m_dwBufferLength ; // allocated buffer length
DWORD m_dwPayloadLength ; // actual data length; <= allocated
LONG m_lRef ; // this object's ref; 0 when we're available
CBufferPool * m_pBufferPool ; // back pointer
DWORD_PTR m_dwCompletionContext ; // anything
LIST_ENTRY m_ListEntry ; // list's link
OVERLAPPED m_Overlapped ; // OVERLAPPED struct we use
public :
CBuffer (
IN CBufferPool * pBufferPool, // back pointer
IN DWORD dwBufferLength, // how much to allocator
OUT HRESULT * phr // success/failre of init
) ;
~CBuffer (
) ;
// LIST_ENTRY manipulation
void InsertHead (IN LIST_ENTRY * pListHead) { ASSERT (IsListEmpty (& m_ListEntry)) ; InsertHeadList (pListHead, & m_ListEntry) ; }
void Unhook () { RemoveEntryList (& m_ListEntry) ; InitializeListHead (& m_ListEntry) ; }
// returns a pointer to the object's OVERLAPPED struct
OVERLAPPED * GetOverlapped () { return & m_Overlapped ; }
// given a LIST_ENTRY, recovers the hosting CBuffer object
static CBuffer * RecoverCBuffer (IN LIST_ENTRY * pListEntry) { CBuffer * pBuffer = CONTAINING_RECORD (pListEntry, CBuffer, m_ListEntry) ;
return pBuffer ; }
// buffer manipulation
BYTE * GetBuffer () { return m_pbBuffer ; }
void SetBuffer (BYTE *pBuffer)
{
if(this->m_pbBuffer != NULL)
delete [] m_pbBuffer;
m_pbBuffer = pBuffer;
}
DWORD GetBufferLength () { return m_dwBufferLength ; }
DWORD GetPayloadLength () { return m_dwPayloadLength ; }
void SetPayloadLength (IN DWORD dw) { ASSERT (dw <= m_dwBufferLength) ; m_dwPayloadLength = dw ; }
// async IO completion context; allows us to store information that
// allows us to recover when the IO completes
void SetCompletionContext (IN DWORD_PTR dw) { m_dwCompletionContext = dw ; }
DWORD_PTR GetCompletionContext () { return m_dwCompletionContext ; }
// refcounting
ULONG AddRef () { return InterlockedIncrement (& m_lRef) ; }
ULONG
Release (
) ;
} ;
class CBufferPool
{
// struct is used to request a CBuffer object; the buffer pool maintains a
// pool of these structs to queue buffer requests when none are available.
struct BLOCK_REQUEST {
LIST_ENTRY ListEntry ;
HANDLE hEvent ;
CBuffer * pBuffer ;
} ;
LIST_ENTRY m_Buffers ; // CBuffer list
LIST_ENTRY m_RequestPool ; // BLOCK_REQUEST list; pool
LIST_ENTRY m_Request ; // BLOCK_REQUEST list; outstanding
CRITICAL_SECTION m_crt ; // lock to access the various lists
DWORD m_dwBufferAllocatedLength ; // allocated length of each
void Lock_ () { EnterCriticalSection (& m_crt) ; }
void Unlock_ () { LeaveCriticalSection (& m_crt) ; }
// gets a request object; must hold the pool lock
BLOCK_REQUEST *
GetRequestLocked_ (
)
{
LIST_ENTRY * pListEntry ;
BLOCK_REQUEST * pBlockRequest ;
if (IsListEmpty (& m_RequestPool) == FALSE) {
// list of unused is not empty; grab one
pListEntry = RemoveHeadList (& m_RequestPool) ;
pBlockRequest = CONTAINING_RECORD (pListEntry, BLOCK_REQUEST, ListEntry) ;
}
else {
// list is empty; must allocate
pBlockRequest = new BLOCK_REQUEST ;
}
// initialize correctly if we got 1
if (pBlockRequest) {
pBlockRequest -> hEvent = NULL ;
pBlockRequest -> pBuffer = NULL ;
}
return pBlockRequest ;
}
// recycles the given block request
void
RecycleRequestLocked_ (
IN BLOCK_REQUEST * pBlockRequest
)
{
InsertHeadList (& m_RequestPool, & pBlockRequest -> ListEntry) ;
}
public :
CBufferPool (
IN DWORD dwPoolSize, // number of buffers to allocate
IN DWORD dwBufferLength, // allocated length of each buffer
OUT HRESULT * phr // success/failure
) ;
~CBufferPool (
) ;
DWORD GetBufferAllocatedLength () { return m_dwBufferAllocatedLength ; }
void
Recycle (
CBuffer *
) ;
CBuffer *
GetBuffer (
IN HANDLE hEvent, // manual reset
IN DWORD dwTimeout = INFINITE
) ;
} ;
#endif // __buffpool_h
以上這些代碼是我宿舍的成哥所寫的,聽說是從directshow下的例子所帶的,我就參考了這個自己再搞一個qt下的,以下代碼是我自己寫的
buffer.h文件
#include <QMutex>
#define MAXBUFSIZE 320*240*4
class Buffer
{
public:
Buffer();
virtual ~Buffer();
char *getBuf();
int getSize();
void setSize(int s);
//int getTag();
//void setTag(int stop);
void lockBuf();
void unlockBuf();
int capacity();
int getAvailableSpace();
//unsigned char*buf;
unsigned char *buf;
//char buf[MAXBUFSIZE];
int size;
//synchronization
//#ifdef WIN32
// CCriticalSection mutex;
//#else
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//#endif
// int stopTag; //flag to indicate I/O operation stops or fails on this buffer
public:
//unsigned char* m_pbBuffer;
int m_dwBufferLength ;
int m_dwPayloadLength ;
QMutex BufferMutex;
};
buffer.cpp文件
#include "buffer.h"
#include <string.h>
#include <malloc.h>
Buffer::Buffer():m_dwBufferLength(MAXBUFSIZE)
{
buf = (unsigned char*)malloc(m_dwBufferLength) ;
}
Buffer::~Buffer()
{
delete buf ;
}
char* Buffer::getBuf()
{
//return &buf[0];
}
int Buffer::getSize()
{
return size;
}
void Buffer::setSize(int s)
{
size = s;
}
//int Buffer::getTag()
//
// return stopTag;
//}
//void Buffer::setTag(int stop)
//{
// stopTag = stop;
//}
int Buffer::capacity()
{
return sizeof(buf);
}
int Buffer::getAvailableSpace()
{
return sizeof(buf)-size;
}
void Buffer::lockBuf()
{
BufferMutex.lock();
}
void Buffer::unlockBuf()
{
BufferMutex.unlock();
}
QueueBuffer.h文件
#include <QMutex>
#include <QWaitCondition>
#include "buffer.h"
#define BUFFERNUM 5
class BufferQueue
{
public:
BufferQueue();
virtual ~BufferQueue();
Buffer *getReadBuffer();
void getWriteBuffer(unsigned char *e,int len);
int IsEmpty() const {return readPos == writePos ;}//&& tag == 0; } //if Queue is empty
int IsFull() const {return readPos == (writePos+1)%BUFFERNUM;}//&&tag ==1; //if Queue is full
//bool moveReadBuffer(bool);
//bool moveWriteBuffer(bool);
//void invalidate();
QMutex QueueBufferMutex;
QWaitCondition bufferNotEmpty;//用於信號等待
QWaitCondition bufferNotFull;//用於信號等待
int numUsedBytes;
void lockAccessMutex();
void unLockAccessMutex();
Buffer buffers[BUFFERNUM];
int readPos;//int front;
int writePos;//int rear;
int num;
bool validFlag;
/*Define two events to synchronize between input thread and output thread
**hFullEvent is defined for the circumstance when the buffer queue is full
**and input thread is waiting for output thread to retrieve data from buffer queue
**hEmptyEvent is defined for the circumstance when the buffer queue is empty
**and output thread is waiting for input thread puts data in buffer queue
*/
static int numOfBuffers(){return BUFFERNUM;}
};
QueueBuffer.cpp文件
#include "QueueBuffer.h"
#include <QThread>
BufferQueue::BufferQueue()
{
readPos=writePos=NULL;
numUsedBytes = 0;
}
BufferQueue::~BufferQueue()
{
}
void BufferQueue::lockAccessMutex()
{
QueueBufferMutex.lock();
}
void BufferQueue::unLockAccessMutex()
{
QueueBufferMutex.unlock();
}
/*
* This is used by read thread
*/
Buffer* BufferQueue::getReadBuffer()
{
Buffer *readBuffer;
QueueBufferMutex.lock();
if (numUsedBytes == 0)
bufferNotEmpty.wait(&QueueBufferMutex);
QueueBufferMutex.unlock();
QueueBufferMutex.lock();
readBuffer=&buffers[readPos];
readPos = (readPos+1)%numOfBuffers();
--numUsedBytes;
bufferNotFull.wakeAll();
QueueBufferMutex.unlock();
return readBuffer;
}
void BufferQueue::getWriteBuffer(unsigned char *e,int len)
{
Buffer *writeBuffer = NULL;
QueueBufferMutex.lock();
if (numUsedBytes == numOfBuffers())
bufferNotFull.wait(&QueueBufferMutex);
QueueBufferMutex.unlock();
QueueBufferMutex.lock();
buffers[writePos].buf=e;
buffers[writePos].m_dwPayloadLength=len;
writePos=(writePos+1)%numOfBuffers();
++numUsedBytes;
bufferNotEmpty.wakeAll();
QueueBufferMutex.unlock();
}
十
.
在qt下如何訪問一個共享對象呢?
這是一個我不斷思考的問題:有一個共享的視頻采集緩沖區(采集線程負責把視頻數據放入緩沖區,壓縮線程負責從緩沖區中取出視頻數據再壓縮),到底這個全局的視頻采集緩沖區如何可以使采集線程和壓縮線程共享呢,開始時我是把這個視頻采集緩沖區對象放到采集線程中的,這樣采集線程就能訪問到這個視頻采集緩沖區,但是壓縮線程如何共享這個視頻采集緩沖區呢?(由於這個壓縮線程要讀這個視頻緩沖區要用到視頻緩沖對象的一個讀函數,但這個視頻緩沖對象是在采集線程中定義的,壓縮線程是不能訪問采集線程下的子對象(視頻采集緩沖區)這成了一個問題),經過兩天的思考終於找到了一種辦法(由於qt跟vc的編程方法有很大不同,加上網上的資料比較少),我決定把視頻緩沖區對象放到主線程(界面對象)中,然后在采集線程和壓縮線程中加上一個主線程對像(通過這個對象就能方便地使采集線程和壓縮線程訪問共享這個視頻采集緩沖區).代碼如下
MainWindow::MainWindow()//主線程
{
setWindowTitle(tr("No movie loaded"));
setMinimumSize(320,240);
resize(520,420);
label=new QLabel(this);
setCentralWidget(label);
label->setBackgroundRole(QPalette::Dark);
btn_start = new QPushButton("start",this);
btn_start ->setGeometry( QRect(50, 300, 80, 40)) ;
connect(btn_start,SIGNAL(clicked()),this, SLOT(save())) ;
piturebuf = (unsigned char *)malloc(320*240*4);
capbuff=new RingBuff();//新建一個視頻采集緩沖區
m_enc = new CXvidEnc(this) ; //新建一個壓縮對象(注意this)
m_enc->AttachCaller(320, 240) ;
CXvidEnc::XVID_GLOBAL_INIT() ;
m_enc->Open() ;
m_enc->start();//啟動壓縮線程
a = new CapThread(this);//新建一個采集對象(注意this)
a->start();//啟動采集線程
};
//主線程中對視頻采集緩沖區的操作
void MainWindow::readCapBuff(unsigned char *data, int size)
{
//unsigned char*piturebuf = (unsigned char *)malloc(320*240*4) ;
capbuff->ring_read(data,size);
}
void MainWindow::writeCapBuff(unsigned char *data, int size)
{
capbuff->ring_write(data,size);
}
//采集線程
CapThread::CapThread(MainWindow *parent){
this->parent = parent;(與上面的this對應)
v4l_open(DEFAULT_DEVICE, &v4l_dev) ;
v4l_get_capability(&v4l_dev);
v4l_set_picture( &v4l_dev );
v4l_get_picture(&v4l_dev);
v4l_init_mbuf(&v4l_dev);
v4l_get_mbuf(&v4l_dev);
v4l_dev.picture.contrast = 110000;
v4l_set_picture(&v4l_dev);
}
//采集線程執行的任務 (把采集到的數據pBuffer放到視頻采集緩沖區中)
void CapThread::run()
{
for(;;){
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
parent->writeCapBuff(pBuffer,320*240*3);(這樣就可以訪問到共享的視頻采集緩沖區了)
}
}
//壓縮線程
CXvidEnc::CXvidEnc(MainWindow *parent)
{
this->parent = parent;
m_closed = true ;
//m_enc_caller = NULL ;
m_enc_handle = NULL ;
m_key = 0 ;
m_width = 0 ;
m_height = 0 ;
m_bitstream = NULL ;
getCapBuff = (unsigned char *)malloc(320*240*4);
}
//壓縮線程要執行的任務(從視頻緩沖區中取出數據並壓縮)
void CXvidEnc::run()
{
for(;;){
parent->readCapBuff(getCapBuff, 320*240*4);
Encode(getCapBuff);
}
}
十
一
.QT多線程入門文章 (轉)
Qt 中的多線程(一)
QT通過三種形式提供了對線程的支持。它們分別是,一、平台無關的線程類,二、線程安全的事件投遞,三、跨線程的信號-槽連接。這使得開發輕巧的多 線程Qt程序更為容易,並能充分利用多處理器機器的優勢。多線程編程也是一個有用的模式,它用於解決執行較長時間的操作而不至於用戶界面失去響應。在Qt 的早期版本中,在構建庫時有不選擇線程支持的選項,從4.0開始,線程總是有效的。
線程類
Qt 包含下面一些線程相關的類:
QThread 提供了開始一個新線程的方法
QThreadStorage 提供逐線程數據存儲
QMutex 提供相互排斥的鎖,或互斥量
QMutexLocker 是一個便利類,它可以自動對QMutex加鎖與解鎖
QReadWriterLock 提供了一個可以同時讀操作的鎖
QReadLocker與QWriteLocker 是便利類,它自動對QReadWriteLock加鎖與解鎖
QSemaphore 提供了一個整型信號量,是互斥量的泛化
QWaitCondition 提供了一種方法,使得線程可以在被另外線程喚醒之前一直休眠。
QThread 提供了開始一個新線程的方法
QThreadStorage 提供逐線程數據存儲
QMutex 提供相互排斥的鎖,或互斥量
QMutexLocker 是一個便利類,它可以自動對QMutex加鎖與解鎖
QReadWriterLock 提供了一個可以同時讀操作的鎖
QReadLocker與QWriteLocker 是便利類,它自動對QReadWriteLock加鎖與解鎖
QSemaphore 提供了一個整型信號量,是互斥量的泛化
QWaitCondition 提供了一種方法,使得線程可以在被另外線程喚醒之前一直休眠。
創建一個線程
為創建一個線程,子類化QThread並且重寫它的run()函數,例如:
class MyThread : public QThread
{
Q_OBJECT protected:
void run();
};
void MyThread::run()
{
...
}
之后,創建這個線程對象的實例,調用QThread::start()。於是,在run()里出現的代碼將會在另外線程中被執行。
注意:QCoreApplication::exec()必須總是在主線程(執行main()的那個線程)中被調用,不能從一個QThread中調用。在 GUI程序中,主線程也被稱為GUI線程,因為它是唯一一個允許執行GUI相關操作的線程。另外,你必須在創建一個QThread之前創建 QApplication(orQCoreApplication)對象。
線程同步
class MyThread : public QThread
{
Q_OBJECT protected:
void run();
};
void MyThread::run()
{
...
}
之后,創建這個線程對象的實例,調用QThread::start()。於是,在run()里出現的代碼將會在另外線程中被執行。
注意:QCoreApplication::exec()必須總是在主線程(執行main()的那個線程)中被調用,不能從一個QThread中調用。在 GUI程序中,主線程也被稱為GUI線程,因為它是唯一一個允許執行GUI相關操作的線程。另外,你必須在創建一個QThread之前創建 QApplication(orQCoreApplication)對象。
線程同步
QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了線程同步的手段。使用線程的主要想法是希望它們可以盡可能並發執行,而一些關鍵點上線程之間需要停止或等待。例如,假如兩個線程試圖同時訪問同一個 全局變量,結果可能不如所願。
QMutex 提供相互排斥的鎖,或互斥量。在一個時刻至多一個線程擁有mutex,假如一個線程試圖訪問已經被鎖定的mutex,那么它將休眠,直到擁有mutex的線程對此mutex解鎖。Mutexes常用來保護共享數據訪問。
QReadWriterLock 與QMutex相似,除了它對 "read","write"訪問進行區別對待。它使得多個讀者可以共時訪問數據。使用QReadWriteLock而不是QMutex,可以使得多線程程序更具有並發性。
QMutex 提供相互排斥的鎖,或互斥量。在一個時刻至多一個線程擁有mutex,假如一個線程試圖訪問已經被鎖定的mutex,那么它將休眠,直到擁有mutex的線程對此mutex解鎖。Mutexes常用來保護共享數據訪問。
QReadWriterLock 與QMutex相似,除了它對 "read","write"訪問進行區別對待。它使得多個讀者可以共時訪問數據。使用QReadWriteLock而不是QMutex,可以使得多線程程序更具有並發性。
QReadWriteLock lock;
void ReaderThread::run()
{
// ...
lock.lockForRead();
read_file();
lock.unlock();
//...
}
void WriterThread::run()
{
// ...
lock.lockForWrite();
write_file();
lock.unlock();
// ...
}
void ReaderThread::run()
{
// ...
lock.lockForRead();
read_file();
lock.unlock();
//...
}
void WriterThread::run()
{
// ...
lock.lockForWrite();
write_file();
lock.unlock();
// ...
}
QSemaphore 是QMutex的一般化,它可以保護一定數量的相同資源,與此相對,一個mutex只保護一個資源。下面例子中,使用QSemaphore來控制對環狀緩 沖的訪問,此緩沖區被生產者線程和消費者線程共享。生產者不斷向緩沖寫入數據直到緩沖末端,再從頭開始。消費者從緩沖不斷讀取數據。信號量比互斥量有更好 的並發性,假如我們用互斥量來控制對緩沖的訪問,那么生產者,消費者不能同時訪問緩沖。然而,我們知道在同一時刻,不同線程訪問緩沖的不同部分並沒有什么 危害。
const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;
class Producer : public QThread
{
public:
void run();
};
void Producer::run()
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for (int i = 0; i < DataSize; ++i)
{
freeBytes.acquire();
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
usedBytes.release();
}
}
class Consumer : public QThread
{
public: void run();
};
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i)
{
usedBytes.acquire();
fprintf(stderr, "%c", buffer[i % BufferSize]);
freeBytes.release();
}
fprintf(stderr, "\n");
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
QWaitCondition 允許線程在某些情況發生時喚醒另外的線程。一個或多個線程可以阻塞等待一QWaitCondition ,用wakeOne()或wakeAll()設置一個條件。wakeOne()隨機喚醒一個,wakeAll()喚醒所有。
下面的例子中,生產者首先必須檢查緩沖是否已滿(numUsedBytes==BufferSize),如果是,線程停下來等待 bufferNotFull條件。如果不是,在緩沖中生產數據,增加numUsedBytes,激活條件bufferNotEmpty。使用mutex來 保護對numUsedBytes的訪問。另外,QWaitCondition::wait()接收一個mutex作為參數,這個mutex應該被調用線程 初始化為鎖定狀態。在線程進入休眠狀態之前,mutex會被解鎖。而當線程被喚醒時,mutex會處於鎖定狀態,而且,從鎖定狀態到等待狀態的轉換是原子 操作,這阻止了競爭條件的產生。當程序開始運行時,只有生產者可以工作。消費者被阻塞等待bufferNotEmpty條件,一旦生產者在緩沖中放入一個 字節,bufferNotEmpty條件被激發,消費者線程於是被喚醒。
const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex; int numUsedBytes = 0;
class Producer : public QThread
{
public: void run();
};
void Producer::run()
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for (int i = 0; i < DataSize; ++i)
{
mutex.lock();
if (numUsedBytes == BufferSize) bufferNotFull.wait(&mutex);
mutex.unlock();
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
mutex.lock();
++numUsedBytes;
bufferNotEmpty.wakeAll();
mutex.unlock();
}
}
class Consumer : public QThread
{
public: void run();
};
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) { mutex.lock(); if (numUsedBytes == 0) bufferNotEmpty.wait(&mutex);
mutex.unlock();
fprintf(stderr, "%c", buffer[i % BufferSize]);
mutex.lock();
--numUsedBytes;
bufferNotFull.wakeAll();
mutex.unlock();
}
fprintf(stderr, "\n");
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
Qt 中的多線程(二) 可重入與線程安全 在Qt文檔中,術語“可重入”與“線程安全”被用來說明一個函數如何用於多線程程序。假如一個類的任何函數在此類的多個不同的實例上,可以被多個線程同時 調用,那么這個類被稱為是“可重入”的。假如不同的線程作用在同一個實例上仍可以正常工作,那么稱之為“線程安全”的。 大多數c++類天生就是可重入的,因為它們典型地僅僅引用成員數據。任何線程可以在類的一個實例上調用這樣的成員函數,只要沒有別的線程在同一個實例上調 用這個成員函數。舉例來講,下面的Counter 類是可重入的:
class Counter
{
public:
Counter() {n=0;}
void increment() {++n;}
void decrement() {--n;}
int value() const {return n;}
private:
int n;
};
這個類不是線程安全的,因為假如多個線程都試圖修改數據成員 n,結果未定義。這是因為c++中的++和--操作符不是原子操作。實際上,它們會被擴展為三個機器指令: 1,把變量值裝入寄存器 2,增加或減少寄存器中的值 3,把寄存器中的值寫回內存 假如線程A與B同時裝載變量的舊值,在寄存器中增值,回寫。他們寫操作重疊了,導致變量值僅增加了一次。很明顯,訪問應該串行化:A執行123步驟時不應 被打斷。使這個類成為線程安全的最簡單方法是使用QMutex來保護數據成員:
class Counter {
public:
Counter() { n = 0; }
void increment()
{
QMutexLocker locker(&mutex);
++n;
}
void decrement() {
QMutexLocker locker(&mutex); --n;
}
int value() const
{
QMutexLocker locker(&mutex);
return n;
}
private:
mutable QMutex mutex;
int n;
};
QMutexLocker類在構造函數中自動對mutex進行加鎖,在析構函數中進行解鎖。隨便一提的是,mutex使用了mutable關鍵字來修飾, 因為我們在value()函數中對mutex進行加鎖與解鎖操作,而value()是一個const函數。 大多數Qt類是可重入,非線程安全的。有一些類與函數是線程安全的,它們主要是線程相關的類,如QMutex,QCoreApplication:: postEvent()。 線程與QObjects QThread 繼承自QObject,它發射信號以指示線程執行開始與結束,而且也提供了許多slots。更有趣的是,QObjects可以用於多線程,這是因為每個線 程被允許有它自己的事件循環。 QObject 可重入性 QObject是可重入的。它的大多數非GUI子類,像QTimer,QTcpSocket,QUdpSocket,QHttp,QFtp, QProcess也是可重入的,在多個線程中同時使用這些類是可能的。需要注意的是,這些類被設計成在一個單線程中創建與使用,因此,在一個線程中創建一 個對象,而在另外的線程中調用它的函數,這樣的行為不能保證工作良好。有三種約束需要注意: 1,QObject的孩子總是應該在它父親被創建的那個線程中創建。這意味着,你絕不應該傳遞QThread對象作為另一個對象的父親(因為 QThread對象本身會在另一個線程中被創建) 2,事件驅動對象僅僅在單線程中使用。明確地說,這個規則適用於"定時器機制“與”網格模塊“,舉例來講,你不應該在一個線程中開始一個定時器或是連接一 個套接字,當這個線程不是這些對象所在的線程。 3,你必須保證在線程中創建的所有對象在你刪除QThread前被刪除。這很容易做到:你可以run()函數運行的棧上創建對象。 盡管QObject是可重入的,但GUI類,特別是QWidget與它的所有子類都是不可重入的。它們僅用於主線程。正如前面提到過的, QCoreApplication::exec()也必須從那個線程中被調用。實踐上,不會在別的線程中使用GUI類,它們工作在主線程上,把一些耗時的 操作放入獨立的工作線程中,當工作線程運行完成,把結果在主線程所擁有的屏幕上顯示。 逐線程事件循環 每個線程可以有它的事件循環,初始線程開始它的事件循環需使用QCoreApplication::exec(),別的線程開始它的事件循環需要用 QThread::exec().像QCoreApplication一樣,QThreadr提供了exit(int)函數,一個quit() slot。 線程中的事件循環,使得線程可以使用那些需要事件循環的非GUI 類(如,QTimer,QTcpSocket,QProcess)。也可以把任何線程的signals連接到特定線程的slots,也就是說信號-槽機制 是可以跨線程使用的。對於在QApplication之前創建的對象,QObject::thread()返回0,這意味着主線程僅為這些對象處理投遞事 件,不會為沒有所屬線程的對象處理另外的事件。可以用QObject::moveToThread()來改變它和它孩子們的線程親緣關系,假如對象有父 親,它不能移動這種關系。在另一個線程(而不是創建它的那個線程)中delete QObject對象是不安全的。除非你可以保證在同一時刻對象不在處理事件。可以用QObject::deleteLater(),它會投遞一個 DeferredDelete事件,這會被對象線程的事件循環最終選取到。 假如沒有事件循環運行,事件不會分發給對象。舉例來說,假如你在一個線程中創建了一個QTimer對象,但從沒有調用過exec(),那么QTimer就 不會發射它的timeout()信號.對deleteLater()也不會工作。(這同樣適用於主線程)。你可以手工使用線程安全的函數 QCoreApplication::postEvent(),在任何時候,給任何線程中的任何對象投遞一個事件,事件會在那個創建了對象的線程中通過事 件循環派發。事件過濾器在所有線程中也被支持,不過它限定被監視對象與監視對象生存在同一線程中。類似地,QCoreApplication:: sendEvent(不是postEvent()),僅用於在調用此函數的線程中向目標對象投遞事件。 從別的線程中訪問QObject子類 QObject和所有它的子類是非線程安全的。這包括整個的事件投遞系統。需要牢記的是,當你正從別的線程中訪問對象時,事件循環可以向你的 QObject子類投遞事件。假如你調用一個不生存在當前線程中的QObject子類的函數時,你必須用mutex來保護QObject子類的內部數據, 否則會遭遇災難或非預期結果。像其它的對象一樣,QThread對象生存在創建它的那個線程中---不是當QThread::run()被調用時創建的那 個線程。一般來講,在你的QThread子類中提供slots是不安全的,除非你用mutex保護了你的成員變量。 另一方面,你可以安全的從QThread::run()的實現中發射信號,因為信號發射是線程安全的。 跨線程的信號-槽 Qt支持三種類型的信號-槽連接: 1,直接連接,當signal發射時,slot立即調用。此slot在發射signal的那個線程中被執行(不一定是接收對象生存的那個線程) 2,隊列連接,當控制權回到對象屬於的那個線程的事件循環時,slot被調用。此slot在接收對象生存的那個線程中被執行 3,自動連接(缺省),假如信號發射與接收者在同一個線程中,其行為如直接連接,否則,其行為如隊列連接。 連接類型可能通過以向connect()傳遞參數來指定。注意的是,當發送者與接收者生存在不同的線程中,而事件循環正運行於接收者的線程中,使用直接連 接是不安全的。同樣的道理,調用生存在不同的線程中的對象的函數也是不是安全的。QObject::connect()本身是線程安全的。 多線程與隱含共享 Qt為它的許多值類型使用了所謂的隱含共享(implicitsharing)來優化性能。原理比較簡單,共享類包含一個指向共享數據塊的指針,這個數據 塊中包含了真正原數據與一個引用計數。把深拷貝轉化為一個淺拷貝,從而提高了性能。這種機制在幕后發生作用,程序員不需要關心它。如果深入點看,假如對象 需要對數據進行修改,而引用計數大於1,那么它應該先detach()。以使得它修改不會對別的共享者產生影響,既然修改后的數據與原來的那份數據不同 了,因此不可能再共享了,於是它先執行深拷貝,把數據取回來,再在這份數據上進行修改。例如: void QPen::setStyle(Qt::PenStyle style) { detach(); // detach From common data d->style = style; // set the style member
}
void QPen::detach()
{
if (d->ref != 1) {
... // perform a deep copy
}
}
一般認為,隱含共享與多線程不太和諧,因為有引用計數的存在。對引用計數進行保護的方法之一是使用mutex,但它很慢,Qt早期版本沒有提供一個滿意的 解決方案。從4.0開始,隱含共享類可以安全地跨線程拷貝,如同別的值類型一樣。它們是完全可重入的。隱含共享真的是"implicit"。它使用匯編語 言實現了原子性引用計數操作,這比用mutex快多了。
假如你在多個線程中同進訪問相同對象,你也需要用mutex來串行化訪問順序,就如同其他可重入對象那樣。總的來講,隱含共享真的給”隱含“掉了,在多線程程序中,你可以把它們看成是一般的,非共享的,可重入的類型,這種做法是安全的。
}
void QPen::detach()
{
if (d->ref != 1) {
... // perform a deep copy
}
}
一般認為,隱含共享與多線程不太和諧,因為有引用計數的存在。對引用計數進行保護的方法之一是使用mutex,但它很慢,Qt早期版本沒有提供一個滿意的 解決方案。從4.0開始,隱含共享類可以安全地跨線程拷貝,如同別的值類型一樣。它們是完全可重入的。隱含共享真的是"implicit"。它使用匯編語 言實現了原子性引用計數操作,這比用mutex快多了。
假如你在多個線程中同進訪問相同對象,你也需要用mutex來串行化訪問順序,就如同其他可重入對象那樣。總的來講,隱含共享真的給”隱含“掉了,在多線程程序中,你可以把它們看成是一般的,非共享的,可重入的類型,這種做法是安全的。
十二
.
服務端
服務端還使用了兩個socket,一個用於和服務端口綁定后偵聽是否有服務請求,另外一個用於發送圖像數據
十三
.
攝像頭采集到的數據的格式可以為下面格式(include/linux/videodev.h),本程序使用的是VIDEO_PALETTE_RGB32(在v4l_set_picture( v4l_device *vd )中定義)
#define VIDEO_PALETTE_GREY 1 /* Linear greyscale */
#define VIDEO_PALETTE_HI240 2 /* High 240 cube (BT848) */
#define VIDEO_PALETTE_RGB565 3 /* 565 16 bit RGB */
#define VIDEO_PALETTE_RGB24 4 /* 24bit RGB */
#define VIDEO_PALETTE_RGB32 5 /* 32bit RGB */
#define VIDEO_PALETTE_RGB555 6 /* 555 15bit RGB */
#define VIDEO_PALETTE_YUV422 7 /* YUV422 capture */
#define VIDEO_PALETTE_YUYV 8
#define VIDEO_PALETTE_UYVY 9 /* The great thing about standards is ... */
#define VIDEO_PALETTE_YUV420 10
#define VIDEO_PALETTE_YUV411 11 /* YUV411 capture */
#define VIDEO_PALETTE_RAW 12 /* RAW capture (BT848) */
#define VIDEO_PALETTE_YUV422P 13 /* YUV 4:2:2 Planar */
#define VIDEO_PALETTE_YUV411P 14 /* YUV 4:1:1 Planar */
#define VIDEO_PALETTE_YUV420P 15 /* YUV 4:2:0 Planar */
#define VIDEO_PALETTE_YUV410P 16 /* YUV 4:1:0 Planar */
#define VIDEO_PALETTE_PLANAR 13 /* start of planar entries */
#define VIDEO_PALETTE_COMPONENT 7 /* start of component entries */
int v4l_set_picture( v4l_device *vd )
{
//vd->picture.palette=VIDEO_PALETTE_YUV420P;
vd->picture.palette=VIDEO_PALETTE_RGB32;
if( ioctl( vd->fd, VIDIOCSPICT, &( vd->picture ) ) < 0 )
{
return -1;
}
return 0;
}
此文檔還沒有完成,還在整理當中
http://no001.blog.51cto.com/1142339/277004
