2009年11月30日星期一

如何将BGR转成YUV420

今天心情非常之好,花了一个下午时间搞定了困扰了我一个月的事情。写了一个程序可以把openCV的BGR图像格式转换成YUV4:2:0,然后通过FFmpeg的API把YUV4:2:0的图像编码压缩,最后利用live555把压缩后的buffer打包成RTP包,最后用Unicast将它们发送出去。简单的说就是 BGR—>YUV4:2:0—>encode to buffer—>RTP—>Unicast这样的过程。

把预想的程序完成真是一大快事,随便记录一部分比较特别的东西,因为openCV没有BGR转YUV420的函数,所以需要自己写一个,我写的这个函数读入一个三通道的图像数据,然后先使用cvCvtColor函数,将BGR转成YCrCb,这个YCrCb是4:4:4也就是全抽样,然后对Cb和Cr分量进行抽样,最后我们可以得到三个数组,分别存Y, U, V三个分量。这个转换不算复杂,就是使用了以下的概念。

YUV4:2:0

 yuv420 4:2:0并不意味着只有Y,Cb而没有Cr分量。它指得是对每行扫描线来说,只有一种色度分量以2:1的抽样率存储。相邻的扫描行存储不同的色度分量,也就是说,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0...以此类推。对每个色度分量来说,水平方向和竖直方向的抽样率都是2:1,所以可以说色度的抽样率是4:1。对非压缩的8比特量化的视频来说,每个由2x2个2行2列相邻的像素组成的宏像素需要占用6字节内存。
八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]
存放的码流为:Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8
射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7][Y5 U0 V5] [Y6 U0 V5] [Y7U2 V7] [Y8 U2 V7](来自百度百科)我们可以对每八个像素:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]进行一次抽样,抽样: Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7 给Y分量,U0 U2 给U分量,V5 V7给V分量。

现在提供另一个简单的转换函数:

void convertBGR2YUV420(IplImage *in, unsigned char* out_y, unsigned char* out_u, unsigned char* out_v)  
{  
    // first, convert the input image into YCbCr 
    IplImage *tmp = cvCreateImage(cvSize(in->width, in->height), 8, 3); 
    cvCvtColor(in, tmp, CV_RGB2YCrCb); 
    /*  
    * widthStep = channel number * width  
    * if width%4 == 0  
    * for example, width = 352, width%4 == 0, widthStep = 3 * 352 = 1056  
    */ 
    int idx_in = 0;  
    int idx_out = 0;  
    int idx_out_y = 0;  
    int idx_out_u = 0;  
    int idx_out_v = 0; 
    
    for(int j = 0; j < in->height; j+=1) { 
        idx_in = j * in->widthStep; 
    
        for(int i = 0; i < in->widthStep; i+=12) { 
        // We use the chroma sample here, and put it into the out buffer 
        // take the luminance sample 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 0]; // Y 
        idx_out_y++; 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 3]; // Y 
        idx_out_y++; 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 6]; // Y 
        idx_out_y++; 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 9]; // Y 
        idx_out_y++; 

        if((j % 2) == 0) { 
              // take the blue-difference and red-difference chroma components sample  
              out_u[idx_out_u++] = tmp->imageData[idx_in + i + 1]; // Cr U  
              out_u[idx_out_u++] = tmp->imageData[idx_in + i + 7]; // Cr U  
              out_v[idx_out_v++] = tmp->imageData[idx_in + i + 2]; // Cb V  
              out_v[idx_out_v++] = tmp->imageData[idx_in + i + 8]; // Cb V 
        }  

        }    
    }  
    cvReleaseImage(&tmp); 
}

杜塞多夫,科隆,波恩两日游

上周五,六,作导游陪国内来的同学逛了逛杜塞多夫,科隆,波恩(今年至少去了5次杜塞,每次去都是阴天下雨,这次也不例外)。还是采用“我的经典旅游路径”:去了杜塞老城,国王大道,莱茵河畔,波恩市政厅,皇宫,贝多芬故居,老城,科隆大教堂,巧克力博物馆。随手照了几张照片。

100_3397 杜塞老城的骑警。

100_3399 在杜塞老城看到的彩虹。

100_3402 波恩的圣诞市场。

100_3415 100_3427科隆的巧克力博物馆展示的巧克力生产线一角。

2009年11月24日星期二

挖概念:RTP timestamp, payload, RTP, RTSP, MPEG ES, TS, PS

阅读别人写的程序绝对是一个很好的提高编程水平的途径,同时也是一个快速获取新知的方式。但是如何从源程序的一点“新的知rtp_timestamp_braindiagram 识点”拓展到一个大的“知识面”,这是一个很有意思的问题,今天我想结合自己的小小的经验来谈谈这个问题。
我采用土法炼钢:“挖概念”法,就是从一个小的概念不停地向外延伸,直到对整个知识架构有一定程度的了解。
举一个我自己的例子,前不久我在看一个视频接收程序的源代码,看到有一行关于timestamp的计算,什么是timestamp这就是一个“新的知识点”。然后我就通过这个点挖出一些我需要的知识,几小时我慢慢地对整个知识架构有一定程度的了解,图示说明了我“挖概念”的过程,以下选载一些我找到的知识点,它们都是围绕着timestamp这个问题展开的。

[1]:


时间戳(Timestamp)-->在RTP中反映RTP数据信息包中第一个字节的采样时刻(时间)。接收端可以利用这个时间戳来去除由网络引起的信息包的抖动,并且在接收端为播放提供同步功能。
时间戳字段是RTP首部中说明数据包时间的同步信息,是数据能以正确的时间顺序恢复的关键。时间戳的值给出了分组中数据的第一个字节的采样时间(Sampling Instant),要求发送方时间戳的时钟是连续、单调增长的,即使在没有数据输入或发送数据时也是如此。在静默时,发送方不必发送数据,保持时间戳的增长,在接收端,由于接收到的数据分组的序号没有丢失,就知道没有发生数据丢失,而且只要比较前后分组的时间戳的差异,就可以确定输出的时间间隔。RTP规定一次会话的初始时间戳必须随机选择,但协议没有规定时间戳的单位,也没有规定该值的精确解释,而是由负载类型来确定时钟的颗粒,这样各种应用类型可以根据需要选择合适的输出计时精度。(来自:百度百科+Wiki)


[2]:


RTP实时传送协议Real-time Transport Protocol由两个紧密链接部分组成: RTP ― 传送具有实时属性的数据;RTP 控制协议(RTCP)。
RTCP的一个关键作用就是能让接收方同步多个RTP流,例如:当音频与视频一起传输的时候,由于编码的不同,RTP使用两个流分别进行传输,这样两个流的时间戳以不同的速率运行,接收方必须同步两个流,以保证声音与影像的一致。为能进行流同步,RTCP要求发送方给每个传送一个唯一的标识数据源的规范名(Canonical Name),尽管由一个数据源发出的不同的流具有不同的同步源标识(SSRC),但具有相同的规范名,这样接收方就知道哪些流是有关联的。而发送方报告报文所包含的信息可被接收方用于协调两个流中的时间戳值。发送方报告中含有一个以网络时间协议NTP(Network Time Protocol)格式表示的绝对时间值,接着RTCP报告中给出一个RTP时间戳值,产生该值的时钟就是产生RTP分组中的TimeStamp字段的那个时钟。由于发送方发出的所有流和发送方报告都使用同一个绝对时钟,接收方就可以比较来自同一数据源的两个流的绝对时间,从而确定如何将一个流中的时间戳值映射为另一个流中的时间戳值。RTSP实时流放协议(Real-Time Streaming Protocol)提供控制多种应用数据传送的功能,提供一种选择传送通道的方法,例如UDP, TCP, IP多目标广播通道,以及提供一种基于RTP协议的递送方法。

RTSP将工作在RTP的上层,用来控制和传送实时的内容。RTSP能够与资源保留协议一起使用,用来设置和管理保留带宽的流式会话或者广播。

(来自:百度百科+Wiki)

[3]:


读了RTP的说明(RFC 2250)后发现RTP with payload type 32 (MPEG1/2 video ES): The RTP timestamp 表示了video frame的PRESENTATION time。
RTP with payload type 33 (MPEG2 TS): The RTP timestamp 表示RTP packet的TRANSMISSION time。(来自:RFC 2250)


[4]:


MPEG一2标准的正式名称为“ISO/IEC13818信息技术——活动图象和相关声音信息的一般编码方法”,是其中的一个编码标准,主要是用于传输声音、图象数据压缩标准,它是MPEG-1的进一步发展,码流在1.5Mb/S到50Mb/s之间。

MPEG-2标准是将视、音频及其他数据基本流(ES)组合成一个或多个适宜于存储或传输的数据流的规范。MPEG-2分为压缩层和系统层:压缩层中,数字视音频数据分别经过编码器,生成连续不分段的基本码流(ES),对于视音频来说,ES就是由一系列编码后的视音频帧的存取单元(AU)组成;在系统层将视音频ES分别通过各自的打包器,加上相应的包头,打包分组为包长度可变的基本流(PES)。系统层主要用来描述视、音频数据复用和同步方式,节目复用器和传输复用器分别将视音频PES加入系统层信息组成相应的节目流(PS)和传输流(TS),多条TS流还可以再次复用成一条多节目TS(MPTS)。

1.数字化的视频、音频和辅助数据,经过压缩后形成各自的基本流(ES)。
2.视频和音频的ES流分别按一定的格式打包,构成具有某种格式的打包的基本信息流(PES:Packetized Elementary Stream),分别称为视频PES和音频PES。这一步骤在打包器内实现,PES的长度可在一定范围内变化。
3.将视频、音频的PES流以及辅助数据按不同的格式再打包,然后进行复用,即分别生成了TS流和PS流.
根据传输媒体的质量不同,MPEG-2中定义了两种复合信息流:传送流(TS:Transport Stream)和节目流(PS:Program Stream)原始的视音频数据流经编码器编码输出压缩后的基本码流 ES, 它含有解码器所必需的、用于恢复原始视音频的信息。 基本码流 ES分解打包成 PES数据包, 每个 PES包在复用的过程中被分成固定长度的传输流包( TS Packet)。TS包的长度是固定的,为188字节。
(来自:张佳,电影频道节目中心传送部,有线电视技术,MPEG-2码流的层次分析,2008,9)

-----------
“挖概念”法,有时候挺好用的,可以在短时间内对一个比较大的知识领域得到一定的理解。

2009年11月23日星期一

浅议SDP(会话描述协议)

因为最近常常使用到SDP(会话描述协议Session Description Protocol)写了一些SDP的文本,在linux里使用例如"ffplay test.sdp"来播放媒体流,今天想简单谈谈SDP,做了一个小小的总结和分析,希望对大家有帮助。

SDP是描述的是流媒体的初始化参数,IETF对其的描述可以在RFC 2327找到,SDP是一个纯文本文档,后缀为.sdp,它的基本内容包括:

# 会话信息:
* 会话名和目的;
* 会话时间;
* 会话使用的带宽;
* 会话的用户信息;
# 媒体信息:
* 媒体类型,例如:视频或音频;
* 传输协议,例如:RTP/UDP/IP;
* 媒体格式,例如:H.263视频或者MPEG视频;
* 多播地址和媒体传输端口(IP多播会话);
* IP单播会话的联系地址和媒体传输端口

举一个例子来进行分析(live555的testMPEG1or2VideoStreamer里附加的SDP文本):

----------------------------------------------------------------------------------------------

v=0

o=- 49451 3 IN IP4 127.0.0.1

s=Test MPEG Video session

i=Parameters for the session streamed by "testMPEG1or2VideoStreamer"

t=0 0

a=tool:testMPEG1or2VideoStreamer

a=type:broadcast

m=video 1234 RTP/AVP 32

c=IN IP4 239.255.42.42/127

----------------------------------------------------------------------------------------------

可以发现SDP会话描述由许多文本行组成,它的格式为“类型=值”。其中v,o,s等等代表了是类型。

第1行v代表了协议版本,例子中为0。
第2行o代表所有者/创建者和会话标识符。

第3行s代表会话名称,例子中为Test MPEG Video session,用户可以自己填写。
第4行t代表会话活动时间。

第5行和第6行a代表会话属性行,可写0个或多个。

第7行m代表代表媒体信息;video代表是视频流;1234代表UDP端口号是1234;RTP/AVP指媒体传输协议使用RTP/AVP;32代表媒体格式使用MPV并且使用90KHz的时钟。关于RTP/AVP可以在RFC 3551 RTP A/V Profile July 2003找到。以下是部分截取:

PT encoding media type clock rate
name (Hz)24 unassigned V
25 CelB V 90,000
26 JPEG V 90,000
27 unassigned V
28 nv V 90,000
29 unassigned V
30 unassigned V
31 H261 V 90,000
32 MPV V 90,000 (这就是例子中的RTP/AVP类型)
33 MP2T AV 90,000
34 H263 V 90,000
35-71 unassigned ?
72-76 reserved N/A N/A
77-95 unassigned ?
96-127 dynamic ?
dyn H263-1998 V 90,000

第8行c代表连接信息。

当然了还可以加上更多的信息描述,例如b=AS:104857,b代表了带宽信息。

2009年11月22日星期日

除了编GUI,Qt还可以做很多事

昨天和学长一起讨论一个旧的项目,他看到我的project里大量使用了Qt的库,说Qt不是做GUI的吗,在编一些非GUI的东西你用它们干嘛(看来这是很多人的既定观点,真的需要改变一下),我说Qt不仅只是可以做GUI还可以完成很多东西,并且很强大,绝对不会比你常用的库弱。整个项目使用Qt的库还可以在一定程度上统一编程风格,并提高代码质量和减少维护开销,Qt库也提供很好的线上资源,很方便查阅。我这里举三个很简单的例子,说明Qt库的一些其他特性。

信号和槽

正如CObject是大多数MFC类的根类或基类,QObject是Qt的基类。先在我们的类继承QObject,并在类声明中使用的Q_OBJECT宏,我们就可以使用QObject信号和槽的强大的机制。
QObject信号和槽即简单又好用,现在举一个简单的例子,在例子中使用QTime和QObject信号和槽,每100微妙对象间自动进行一次接口的数据交换。

/* myclass.h */
#include <QObject> 
#include <QTime> 

class MyClass : public QObject { 
    Q_OBJECT 
public: 
    MyClass(); 
    int data_in; 
    int data_out; 

private slots: 
    void dataExchangePipe(); 

private: 
    AnotherClass anotherclass; 
    QTimer *poller; 
};
 
 
/* myclass.cpp */
MyClass::MyClass() { 
    object = new AnotherClass; 
    poller = new QTimer(this); 
    connect(poller, SIGNAL(timeout()), this, SLOT(dataExchangePipe())); 
    poller->start(100); // the time to poller 
}
 
void MyClass::dataExchangePipe() { 
    data_in = object->data_out; 
    object->data_in = data_out; 
}

容器,迭代器

还在使用STL容器吗,也许Qt也是一个很好的选择,Qt提供了QVector<T>, QLinkedList<T>,QList<T>QMap<K,T> and QHash<K,T>容器。Qt提供对容器的两种风格的迭代:Java风格的迭代器和STL风格迭代器

Java风格(非常简单,Qt官网上的一个例子)

QList<QString> list; 
list << "A" << "B" << "C" << "D"; 
QListIterator<QString> i(list); 
while (i.hasNext()) 
    qDebug() << i.next();


STL风格(比较powerful,每一个sequential container class C<T>都有两个 STL-style 迭代器类型: C<T>::iterator 和 C<T>::const_iterator,以下是Qt官网上的例子)

QList<QString> list;  
list << "A" << "B" << "C" << "D";  
QList<QString>::iterator i = list.end(); 
while (i != list.begin()) { 
    --i;
    *i = (*i).toLower(); 
}

多线程

Qt的QThread很类似C++Boost线程库。使用起来也非常容易,例如:

建thread:

#include <QThread> 
class MyThread : public QThread { 
  public: void run();  
}; 

void MyThread::run() { 
... 
... 
}

使用thread,很简单:

先声明对象,MyThread *mythread = new MyThread;

启动: mythread->start();

终止: mythread->terminate();

对线程的同步,Qt也提供了QMutex;我们可以声明QMutex mutex;然后使用mutex.lock();和mutex.unlock();进行线程同步。

更多有用的特性请查阅:http://doc.trolltech.com/4.5/index.html

2009年11月16日星期一

源代码行数计算器

刚刚完成了一个项目的重构。使用源代码计算器一查,代码少了30%,系统的结构也更清晰了。总结经验,因为,抛弃了很多原型代码。改变以前自上而下的设计方法,使用了自下而上的设计方法,并把一些绝对要做的模块做了,然后再把它们搭起来。 还加入了一些Facade降低了一些模块间的耦合。重写了一些utility classes(工具类),然后就可以让大量的类使用某个给定的类,达到了《代码大全》所谓的high fan-in(高扇入)。《代码大全》中提到了应该让每个类都应该少或适中使用其他的类(不要超过7个)这就是所谓的低扇出,low fan-out,在我重构的时候也努力的遵守这个原则,这大大减低了类的复杂度。

呵,做这个项目走了不少弯路,看来还是要多读书,并结合一些编程经验带着问题读,多思考。这样会提高编程的质量,并提高效率。

line counter

想计算所编软件源代码共有几行,网上搜到了n种计算器,随便找了一种,使用了line counter,还不错,该软件下载地址:http://sourceforge.net/projects/lcounter/

2009年11月5日星期四

电影:圣诞颂歌-Eine Weihnachtsgeschichte

3D 版的“人物动作捕捉”技术的动画片圣诞颂歌(A Christmas Carol,德语:Eine Weihnachtsgeschichte)在德国上映的第一天就跑到电影院尝鲜,第一次看“人物动作捕捉”动画片的3D版,个人感受相当不错,某些场景还挺震撼的。

disney-s-eine-weihnachtsgeschichte-2008-imagemovers 值得一提的是金·凯瑞(Jim Carrey)一人分饰多角,在影片中饰演守财奴艾柏纳泽·斯克鲁奇(Ebenezer Scrooge)从儿童到老年的四个时期的角色,还有圣诞三精灵。

如果想回忆一下的查尔斯·狄更斯的原著内容:在http://publicliterature.org/可以找到A Christmas Carol的有声书和原著的pdf。http://publicliterature.org/books/christmas_carol/xaa.php

enjoy!