mirror of
https://github.com/go-av/audio_video_streaming.git
synced 2025-09-26 19:41:10 +08:00
Update and rename 037-视音频数据处理入门:RGB、YUV像素数据处理.md to 037-RTSP协议学习.md
This commit is contained in:
534
article/037-RTSP协议学习.md
Normal file
534
article/037-RTSP协议学习.md
Normal file
@@ -0,0 +1,534 @@
|
||||
# RTSP协议学习
|
||||
|
||||
## RTSP简介
|
||||
|
||||
RTSP(Real Time Streaming Protocol)是由Real Network和Netscape共同提出的如何有效地在IP网络上传输流媒体数据的应用层协议。RTSP对流媒体提供了诸如暂停,快进等控制,而它本身并不传输数据,RTSP的作用相当于流媒体服务器的远程控制。服务器端可以自行选择使用TCP或UDP来传送串流内容,它的语法和运作跟HTTP 1.1类似,但并不特别强调时间同步,所以比较能容忍网络延迟。
|
||||
|
||||
## RTSP和HTTP RTP(RTCP)的关系
|
||||
|
||||
### RTSP和HTTP
|
||||
|
||||
* 联系:两者都用纯文本来发送消息,且rtsp协议的语法也和HTTP类似。Rtsp一开始这样设计,也是为了能够兼容使用以前写的HTTP协议分析代码 。
|
||||
* 区别:rtsp是有状态的,不同的是RTSP的命令需要知道现在正处于一个什么状态,也就是说rtsp的命令总是按照顺序来发送,某个命令总在另外一个命令之前要发送。Rtsp不管处于什么状态都不会断掉连接。而http则不保存状态,协议在发送一个命令以后,连接就会断开,且命令之间是没有依赖性。rtsp协议使用554端口,http使用80端口。
|
||||
|
||||
### RTSP和RTP(RTCP)
|
||||
|
||||
* RTP:Realtime Transport Potocol 实时传输协议
|
||||
RTP提供时间标志,序列号以及其他能够保证在实时数据传输时处理时间的方法。
|
||||
* RTCP:Realtime Transport Control Potocol 实时传输控制协议
|
||||
RTCP是RTP的控制部分,用来保证服务质量和成员管理。RTP和RTCP是一起使用的。
|
||||
* RTSP:RealTime Streaming Potocol 实时流协议
|
||||
RTSP具体数据传输交给RTP,提供对流的远程控制
|
||||
|
||||
RTP是基于 UDP协议的, UDP不用建立连接,效率更高;但允许丢包, 这就要求在重新组装媒体的时候多做些工作
|
||||
RTP只是包裹内容信息,而RTCP是交换控制信息的,Qos是通过RTCP实现的
|
||||
应用程序对应的是play, seek, pause, stop等命令,RTSP则是处理这些命令,在UDP传输时并使用RTP(RTCP)来完成。如果是TCP连接则不会使用RTP(RTCP)。
|
||||
|
||||

|
||||
|
||||
RTSP的client连接server通过SDP(会话描述协议)传递信息,详细请见:RTSP消息
|
||||
|
||||
RTSP消息
|
||||
RTSP的消息有两大类,一是请求消息(request),一是回应消息(response),两种消息的格式不同。
|
||||
请求消息格式:
|
||||
|
||||
>方法 URI RTSP版本 CR LF<br/>
|
||||
>消息头 CR LF CR LF<br/>
|
||||
>消息体 CR LF
|
||||
|
||||
方法包括:OPTIONS、SETUP、PLAY、TEARDOWN DESCRIBE
|
||||
URI是接收方(服务端)的地址,例如:rtsp://192.168.22.136:5000/v0
|
||||
每行后面的CR LF表示回车换行,需要接收端有相应的解析,消息头需要有两个CR LF。
|
||||
|
||||
>DESCRIBE rtsp://192.168.1.211 RTSP/1.0<br/>
|
||||
>CSeq: 1<br/>
|
||||
>Accept: application/sdp<br/>
|
||||
>User-Agent: magnus-fc
|
||||
|
||||
回应消息格式:
|
||||
|
||||
>RTSP版本 状态码 解释 CR LF<br/>
|
||||
>消息头 CR LF CR LF<br/>
|
||||
>消息体 CR LF
|
||||
|
||||
其中RTSP版本一般都是RTSP/1.0,状态码是一个数值,200表示成功,解释是与状态码对应的文本解释,详细请见:SDP协议介绍。
|
||||
|
||||
>RTSP/1.0 200 OK<br/>
|
||||
>CSeq: 1<br/>
|
||||
>Server: GrandStream Rtsp Server V100R001<br/>
|
||||
>Content-Type: application/sdp<br/>
|
||||
>Content-length: 256<br/>
|
||||
>Content-Base: rtsp://192.168.1.211/0<br/>
|
||||
><br/>
|
||||
>v=0<br/>
|
||||
>o=StreamingServer 3331435948 1116907222000 IN IP4 192.168.1.211<br/>
|
||||
>s=h264.mp4<br/>
|
||||
>c=IN IP4 0.0.0.0<br/>
|
||||
>t=0 0<br/>
|
||||
>a=control:*<br/>
|
||||
>m=video 0 RTP/AVP 96<br/>
|
||||
>a=control:trackID=0<br/>
|
||||
>a=rtpmap:96 H264/90000<br/>
|
||||
>m=audio 0 RTP/AVP 97<br/>
|
||||
>a=control:trackID=1<br/>
|
||||
>a=rtpmap:97 G726-16/8000
|
||||
|
||||
|
||||
## 简单的rtsp交互过程:
|
||||
|
||||
>C表示rtsp客户端, S表示rtsp服务端<br/>
|
||||
><br/>
|
||||
>step1:<br/>
|
||||
>C->S:OPTION request //询问S有哪些方法可用<br/>
|
||||
>S->C:OPTION response //S回应信息中包括提供的所有可用方法<br/>
|
||||
><br/>
|
||||
>step2:<br/>
|
||||
>C->S:DESCRIBE request //要求得到S提供的媒体初始化描述信息<br/>
|
||||
>S->C:DESCRIBE response //S回应媒体初始化描述信息,主要是sdp<br/>
|
||||
><br/>
|
||||
>step3:<br/>
|
||||
>C->S:SETUP request //设置会话的属性,以及传输模式,提醒S建立会话<br/>
|
||||
>S->C:SETUP response //S建立会话,返回会话标识符,以及会话相关信息<br/>
|
||||
><br/>
|
||||
>step4:<br/>
|
||||
>C->S:PLAY request //C请求播放<br/>
|
||||
>S->C:PLAY response //S回应该请求的信息<br/>
|
||||
><br/>
|
||||
>S->C:发送流媒体数据<br/>
|
||||
><br/>
|
||||
>step5:<br/>
|
||||
>C->S:TEARDOWN request //C请求关闭会话<br/>
|
||||
>S->C:TEARDOWN response //S回应该请求
|
||||
|
||||
## RTSP中常用方法
|
||||
|
||||
### OPTION
|
||||
|
||||
得到服务器提供的可用方法
|
||||
|
||||
>OPTIONS rtsp://192.168.20.136:5000/xxx666 RTSP/1.0<br/>
|
||||
>CSeq: 1 //每个消息都有序号来标记,第一个包通常是option请求消息<br/>
|
||||
>User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
|
||||
|
||||
服务器的回应信息包括提供的一些方法,例如:
|
||||
|
||||
>RTSP/1.0 200 OK <br/>
|
||||
>Server: UServer 0.9.7_rc1<br/>
|
||||
>Cseq: 1 //每个回应消息的cseq数值和请求消息的cseq相对应<br/>
|
||||
>Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, SCALE,GET_PARAMETER //服务器提供的可用的方法
|
||||
|
||||
### DESCRIBE
|
||||
|
||||
C向S发起DESCRIBE请求,为了得到会话描述信息(SDP):
|
||||
|
||||
>DESCRIBE rtsp://192.168.20.136:5000/xxx666 RTSP/1.0<br/>
|
||||
>CSeq: 2<br/>
|
||||
>token: <br/>
|
||||
>Accept: application/sdp<br/>
|
||||
>User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
|
||||
|
||||
服务器回应一些对此会话的描述信息(sdp):
|
||||
|
||||
>RTSP/1.0 200 OK <br/>
|
||||
>Server: UServer 0.9.7_rc1 <br/>
|
||||
>Cseq: 2 <br/>
|
||||
>x-prev-url: rtsp://192.168.20.136:5000 <br/>
|
||||
>x-next-url: rtsp://192.168.20.136:5000 <br/>
|
||||
>x-Accept-Retransmit: our-retransmit <br/>
|
||||
>x-Accept-Dynamic-Rate: 1 <br/>
|
||||
>Cache-Control: must-revalidate <br/>
|
||||
>Last-Modified: Fri, 10 Nov 2006 12:34:38 GMT <br/>
|
||||
>Date: Fri, 10 Nov 2006 12:34:38 GMT <br/>
|
||||
>Expires: Fri, 10 Nov 2006 12:34:38 GMT <br/>
|
||||
>Content-Base: rtsp://192.168.20.136:5000/xxx666/ <br/>
|
||||
>Content-Length: 344 <br/>
|
||||
>Content-Type: application/sdp <br/>
|
||||
><br/>
|
||||
>v=0 //以下都是sdp信息 <br/>
|
||||
>o=OnewaveUServerNG 1451516402 1025358037 IN IP4 192.168.20.136 <br/>
|
||||
>s=/xxx666 <br/>
|
||||
>u=http:/// <br/>
|
||||
>e=admin@ <br/>
|
||||
>c=IN IP4 0.0.0.0 <br/>
|
||||
>t=0 0 <br/>
|
||||
>a=isma-compliance:1,1.0,1 <br/>
|
||||
><br/>
|
||||
>a=range:npt=0- <br/>
|
||||
>m=video 0 RTP/AVP 96 //m表示媒体描述,下面是对会话中视频通道的媒体描述<br/>
|
||||
>a=rtpmap:96 MP4V-ES/90000 <br/>
|
||||
>a=fmtp:96 profile-level-id=245;config=000001B0F5000001B509000001000000012000C888B0E0E0FA62D089028307 a=control:trackID=0 //trackID=0表示视频流用的是通道0
|
||||
|
||||
### SETUP
|
||||
|
||||
客户端提醒服务器建立会话,并确定传输模式:
|
||||
|
||||
>SETUP rtsp://192.168.20.136:5000/xxx666/trackID=0 RTSP/1.0 <br/>
|
||||
>CSeq: 3 <br/>
|
||||
>Transport: RTP/AVP/TCP;unicast;interleaved=0-1 <br/>
|
||||
>User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)<br/>
|
||||
>//uri中 带有trackID=0,表示对该通道进行设置。Transport参数设置了传输模式,包的结构。接下来的数据包头部第二个字节位置就是 interleaved,它的值是每个通道都不同的,trackID=0的interleaved值有两个0或1,0表示rtp包,1表示rtcp包,接收端根据interleaved的值来区别是哪种数据包。
|
||||
|
||||
服务器回应信息:
|
||||
|
||||
>RTSP/1.0 200 OK <br/>
|
||||
>Server: UServer 0.9.7_rc1 <br/>
|
||||
>Cseq: 3 <br/>
|
||||
>Session: 6310936469860791894 //服务器回应的会话标识符<br/>
|
||||
>Cache-Control: no-cache <br/>
|
||||
>Transport: RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=6B8B4567
|
||||
|
||||
### PLAY
|
||||
|
||||
客户端发送播放请求:
|
||||
|
||||
>PLAY rtsp://192.168.20.136:5000/xxx666 RTSP/1.0 <br/>
|
||||
>CSeq: 4 <br/>
|
||||
>Session: 6310936469860791894 <br/>
|
||||
>Range: npt=0.000- //设置播放时间的范围<br/>
|
||||
>User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
|
||||
|
||||
|
||||
服务器回应信息:
|
||||
|
||||
>RTSP/1.0 200 OK <br/>
|
||||
>Server: UServer 0.9.7_rc1 <br/>
|
||||
>Cseq: 4 <br/>
|
||||
>Session: 6310936469860791894 <br/>
|
||||
>Range: npt=0.000000- <br/>
|
||||
>RTP-Info: url=trackID=0;seq=17040;rtptime=1467265309 <br/>
|
||||
>//seq和rtptime都是rtp包中的信息
|
||||
|
||||
### TEARDOWN
|
||||
|
||||
客户端发起关闭请求:
|
||||
|
||||
>TEARDOWN rtsp://192.168.20.136:5000/xxx666 RTSP/1.0 <br/>
|
||||
>CSeq: 5 <br/>
|
||||
>Session: 6310936469860791894 <br/>
|
||||
>User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
|
||||
|
||||
服务器回应:
|
||||
|
||||
>RTSP/1.0 200 OK <br/>
|
||||
>Server: UServer 0.9.7_rc1 <br/>
|
||||
>Cseq: 5 <br/>
|
||||
>Session: 6310936469860791894
|
||||
|
||||
## SDP协议
|
||||
|
||||
sdp的格式:
|
||||
|
||||
>v=<version><br/>
|
||||
>o=<username> <session id> <version> <network type> <address type> <address><br/>
|
||||
>s=<session name><br/>
|
||||
>i=<session description><br/>
|
||||
>u=<URI><br/>
|
||||
>e=<email address><br/>
|
||||
>p=<phone number><br/>
|
||||
>c=<network type> <address type> <connection address><br/>
|
||||
>b=<modifier>:<bandwidth-value><br/>
|
||||
>t=<start time> <stop time><br/>
|
||||
>r=<repeat interval> <active duration> <list of offsets from start-time><br/>
|
||||
>z=<adjustment time> <offset> <adjustment time> <offset> ....<br/>
|
||||
>k=<method><br/>
|
||||
>k=<method>:<encryption key><br/>
|
||||
>a=<attribute><br/>
|
||||
>a=<attribute>:<value><br/>
|
||||
>m=<media> <port> <transport> <fmt list>
|
||||
|
||||
>v = (协议版本)<br/>
|
||||
>o = (所有者/创建者和会话标识符)<br/>
|
||||
>s = (会话名称)<br/>
|
||||
>i = * (会话信息)<br/>
|
||||
>u = * (URI 描述)<br/>
|
||||
>e = * (Email 地址)<br/>
|
||||
>p = * (电话号码)<br/>
|
||||
>c = * (连接信息)<br/>
|
||||
>b = * (带宽信息)<br/>
|
||||
>z = * (时间区域调整)<br/>
|
||||
>k = * (加密密钥)<br/>
|
||||
>a = * (0 个或多个会话属性行)
|
||||
|
||||
* 时间描述:
|
||||
t = (会话活动时间)
|
||||
r = * (0或多次重复次数)
|
||||
|
||||
* 媒体描述:
|
||||
m = (媒体名称和传输地址)
|
||||
i = * (媒体标题)
|
||||
c = * (连接信息 — 如果包含在会话层则该字段可选)
|
||||
b = * (带宽信息)
|
||||
k = * (加密密钥)
|
||||
a = * (0 个或多个媒体属性行)
|
||||
|
||||
SDP一会话描述协议一描述SAP、SIP和RTSR会话的协议,是一种文件描述协议,是由服务器生成的描述媒体文件编码信息以及所在服务器的链接等的信息。在多媒体会话 中sDP传送有关媒体流的信息,使会话描述的参人方加人会话。SDP主要用于Intemet网中,但也可以在其它网络环境下使用。SDP十分通用,可描述其它网络环境中的会话,但主要用 于Intemet中。在Intemet环境下,SDP有两个主要目的:一是表明会话存在,二是传送足够信息给接收方,以便能加人、参加该会话。SDP所传达的信息包括:会话名称和目的,会话 活动时间,组成会话媒体种类,接收这些媒体的控制信息(如地址、端口、格式、带宽和会议管理人员资料等)。
|
||||
|
||||
总结:在RTSP交互过程中,只要在客户端发出Describe请求的时候,服务端回应的时候会有SDP消息发出,用SDP来描述会话情况和内容,方便客户端能够加入该会话。
|
||||
|
||||
|
||||
## RTSP基于libcurl代码实现
|
||||
|
||||
```C++
|
||||
/* <DESC>
|
||||
* A basic RTSP transfer
|
||||
* </DESC>
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#if defined (WIN32)
|
||||
#include <conio.h> /* _getch() */
|
||||
#else
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define VERSION_STR "V1.0"
|
||||
|
||||
/* error handling macros */
|
||||
#define my_curl_easy_setopt(A, B, C) \
|
||||
res = curl_easy_setopt((A), (B), (C)); \
|
||||
if(!res) \
|
||||
fprintf(stderr, "curl_easy_setopt(%s, %s, %s) failed: %d\n", \
|
||||
#A, #B, #C, res);
|
||||
|
||||
#define my_curl_easy_perform(A) \
|
||||
res = curl_easy_perform(A); \
|
||||
if(!res) \
|
||||
fprintf(stderr, "curl_easy_perform(%s) failed: %d\n", #A, res);
|
||||
|
||||
static int _getch(void)
|
||||
{
|
||||
struct termios oldt, newt;
|
||||
int ch;
|
||||
tcgetattr(STDIN_FILENO, &oldt);
|
||||
newt = oldt;
|
||||
newt.c_lflag &= ~( ICANON | ECHO);
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
||||
ch = getchar();
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
||||
return ch;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* send RTSP OPTIONS request */
|
||||
static void rtsp_options(CURL *curl, const char *uri)
|
||||
{
|
||||
CURLcode res = CURLE_OK;
|
||||
printf("\nRTSP: OPTIONS %s\n", uri);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, uri);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_OPTIONS);
|
||||
my_curl_easy_perform(curl);
|
||||
}
|
||||
|
||||
/* send RTSP DESCRIBE request and write sdp response to a file */
|
||||
static void rtsp_describe(CURL *curl, const char *uri,
|
||||
const char *sdp_filename)
|
||||
{
|
||||
CURLcode res = CURLE_OK;
|
||||
FILE *sdp_fp = fopen(sdp_filename, "wb");
|
||||
printf("\nRTSP: DESCRIBE %s\n", uri);
|
||||
if(sdp_fp == NULL) {
|
||||
fprintf(stderr, "Could not open '%s' for writing\n", sdp_filename);
|
||||
sdp_fp = stdout;
|
||||
}
|
||||
else {
|
||||
printf("Writing SDP to '%s'\n", sdp_filename);
|
||||
}
|
||||
my_curl_easy_setopt(curl, CURLOPT_WRITEDATA, sdp_fp);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_DESCRIBE);
|
||||
my_curl_easy_perform(curl);
|
||||
my_curl_easy_setopt(curl, CURLOPT_WRITEDATA, stdout);
|
||||
if(sdp_fp != stdout) {
|
||||
fclose(sdp_fp);
|
||||
}
|
||||
}
|
||||
|
||||
/* send RTSP SETUP request */
|
||||
static void rtsp_setup(CURL *curl, const char *uri, const char *transport)
|
||||
{
|
||||
CURLcode res = CURLE_OK;
|
||||
printf("\nRTSP: SETUP %s\n", uri);
|
||||
printf(" TRANSPORT %s\n", transport);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, uri);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RTSP_TRANSPORT, transport);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_SETUP);
|
||||
my_curl_easy_perform(curl);
|
||||
}
|
||||
|
||||
/* send RTSP PLAY request */
|
||||
static void rtsp_play(CURL *curl, const char *uri, const char *range)
|
||||
{
|
||||
CURLcode res = CURLE_OK;
|
||||
printf("\nRTSP: PLAY %s\n", uri);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, uri);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RANGE, range);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_PLAY);
|
||||
my_curl_easy_perform(curl);
|
||||
}
|
||||
|
||||
/* send RTSP TEARDOWN request */
|
||||
static void rtsp_teardown(CURL *curl, const char *uri)
|
||||
{
|
||||
CURLcode res = CURLE_OK;
|
||||
printf("\nRTSP: TEARDOWN %s\n", uri);
|
||||
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_TEARDOWN);
|
||||
my_curl_easy_perform(curl);
|
||||
}
|
||||
|
||||
/* convert url into an sdp filename */
|
||||
static void get_sdp_filename(const char *url, char *sdp_filename,
|
||||
size_t namelen)
|
||||
{
|
||||
const char *s = strrchr(url, '/');
|
||||
strcpy(sdp_filename, "video.sdp");
|
||||
if(s != NULL) {
|
||||
s++;
|
||||
if(s[0] != '\0') {
|
||||
snprintf(sdp_filename, namelen, "%s.sdp", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* scan sdp file for media control attribute */
|
||||
static void get_media_control_attribute(const char *sdp_filename,
|
||||
char *control)
|
||||
{
|
||||
int max_len = 256;
|
||||
char *s = malloc(max_len);
|
||||
FILE *sdp_fp = fopen(sdp_filename, "rb");
|
||||
control[0] = '\0';
|
||||
if(sdp_fp != NULL) {
|
||||
while(fgets(s, max_len - 2, sdp_fp) != NULL) {
|
||||
sscanf(s, " a = control: %s", control);
|
||||
}
|
||||
fclose(sdp_fp);
|
||||
}
|
||||
free(s);
|
||||
}
|
||||
|
||||
/* main app */
|
||||
int main(int argc, char * const argv[])
|
||||
{
|
||||
#if 1
|
||||
const char *transport = "RTP/AVP;unicast;client_port=1234-1235"; /* UDP */
|
||||
#else
|
||||
/* TCP */
|
||||
const char *transport = "RTP/AVP/TCP;unicast;client_port=1234-1235";
|
||||
#endif
|
||||
const char *range = "0.000-";
|
||||
int rc = EXIT_SUCCESS;
|
||||
char *base_name = NULL;
|
||||
|
||||
printf("\nRTSP request %s\n", VERSION_STR);
|
||||
printf(" Project web site: http://code.google.com/p/rtsprequest/\n");
|
||||
printf(" Requires curl V7.20 or greater\n\n");
|
||||
|
||||
/* check command line */
|
||||
if((argc != 2) && (argc != 3)) {
|
||||
base_name = strrchr(argv[0], '/');
|
||||
if(base_name == NULL) {
|
||||
base_name = strrchr(argv[0], '\\');
|
||||
}
|
||||
if(base_name == NULL) {
|
||||
base_name = argv[0];
|
||||
}
|
||||
else {
|
||||
base_name++;
|
||||
}
|
||||
printf("Usage: %s url [transport]\n", base_name);
|
||||
printf(" url of video server\n");
|
||||
printf(" transport (optional) specifier for media stream"
|
||||
" protocol\n");
|
||||
printf(" default transport: %s\n", transport);
|
||||
printf("Example: %s rtsp://192.168.0.2/media/video1\n\n", base_name);
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
else {
|
||||
const char *url = argv[1];
|
||||
char *uri = malloc(strlen(url) + 32);
|
||||
char *sdp_filename = malloc(strlen(url) + 32);
|
||||
char *control = malloc(strlen(url) + 32);
|
||||
CURLcode res;
|
||||
get_sdp_filename(url, sdp_filename, strlen(url) + 32);
|
||||
if(argc == 3) {
|
||||
transport = argv[2];
|
||||
}
|
||||
|
||||
/* initialize curl */
|
||||
res = curl_global_init(CURL_GLOBAL_ALL);
|
||||
if(res == CURLE_OK) {
|
||||
curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
|
||||
CURL *curl;
|
||||
fprintf(stderr, " curl V%s loaded\n", data->version);
|
||||
|
||||
/* initialize this curl session */
|
||||
curl = curl_easy_init();
|
||||
if(curl != NULL) {
|
||||
my_curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);
|
||||
my_curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
|
||||
my_curl_easy_setopt(curl, CURLOPT_HEADERDATA, stdout);
|
||||
my_curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
|
||||
/* request server options */
|
||||
snprintf(uri, strlen(url) + 32, "%s", url);
|
||||
rtsp_options(curl, uri);
|
||||
|
||||
/* request session description and write response to sdp file */
|
||||
rtsp_describe(curl, uri, sdp_filename);
|
||||
|
||||
/* get media control attribute from sdp file */
|
||||
get_media_control_attribute(sdp_filename, control);
|
||||
|
||||
/* setup media stream */
|
||||
snprintf(uri, strlen(url) + 32, "%s/%s", url, control);
|
||||
rtsp_setup(curl, uri, transport);
|
||||
|
||||
/* start playing media stream */
|
||||
snprintf(uri, strlen(url) + 32, "%s/", url);
|
||||
rtsp_play(curl, uri, range);
|
||||
printf("Playing video, press any key to stop ...");
|
||||
_getch();
|
||||
printf("\n");
|
||||
|
||||
/* teardown session */
|
||||
rtsp_teardown(curl, uri);
|
||||
|
||||
/* cleanup */
|
||||
curl_easy_cleanup(curl);
|
||||
curl = NULL;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "curl_easy_init() failed\n");
|
||||
}
|
||||
curl_global_cleanup();
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "curl_global_init(%s) failed: %d\n",
|
||||
"CURL_GLOBAL_ALL", res);
|
||||
}
|
||||
free(control);
|
||||
free(sdp_filename);
|
||||
free(uri);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
原文作者: lory17
|
@@ -1,890 +0,0 @@
|
||||
# 视音频数据处理入门:RGB、YUV像素数据处理
|
||||
|
||||
本文记录RGB/YUV视频像素数据的处理方法。视频像素数据在视频播放器的解码流程中的位置如下图所示。
|
||||
|
||||

|
||||
|
||||
本文分别介绍如下几个RGB/YUV视频像素数据处理函数:
|
||||
分离YUV420P像素数据中的Y、U、V分量
|
||||
分离YUV444P像素数据中的Y、U、V分量
|
||||
将YUV420P像素数据去掉颜色(变成灰度图)
|
||||
将YUV420P像素数据的亮度减半
|
||||
将YUV420P像素数据的周围加上边框
|
||||
生成YUV420P格式的灰阶测试图
|
||||
计算两个YUV420P像素数据的PSNR
|
||||
分离RGB24像素数据中的R、G、B分量
|
||||
将RGB24格式像素数据封装为BMP图像
|
||||
将RGB24格式像素数据转换为YUV420P格式像素数据
|
||||
生成RGB24格式的彩条测试图
|
||||
本文中的RGB/YUV文件需要使用RGB/YUV播放器才能查看。YUV播放器种类比较多,例如YUV Player Deluxe,或者开源播放器(参考文章《修改了一个YUV/RGB播放器》)等。
|
||||
|
||||
## 1. 函数列表
|
||||
|
||||
### 1.1 分离YUV420P像素数据中的Y、U、V分量
|
||||
|
||||
本程序中的函数可以将YUV420P数据中的Y、U、V三个分量分离开来并保存成三个文件。函数的代码如下所示。
|
||||
|
||||
```C++
|
||||
/**
|
||||
* Split Y, U, V planes in YUV420P file.
|
||||
* @param url Location of Input YUV file.
|
||||
* @param w Width of Input YUV file.
|
||||
* @param h Height of Input YUV file.
|
||||
* @param num Number of frames to process.
|
||||
*
|
||||
*/
|
||||
int simplest_yuv420_split(char *url, int w, int h,int num){
|
||||
FILE *fp=fopen(url,"rb+");
|
||||
FILE *fp1=fopen("output_420_y.y","wb+");
|
||||
FILE *fp2=fopen("output_420_u.y","wb+");
|
||||
FILE *fp3=fopen("output_420_v.y","wb+");
|
||||
|
||||
unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
|
||||
|
||||
for(int i=0;i<num;i++){
|
||||
|
||||
fread(pic,1,w*h*3/2,fp);
|
||||
//Y
|
||||
fwrite(pic,1,w*h,fp1);
|
||||
//U
|
||||
fwrite(pic+w*h,1,w*h/4,fp2);
|
||||
//V
|
||||
fwrite(pic+w*h*5/4,1,w*h/4,fp3);
|
||||
}
|
||||
|
||||
free(pic);
|
||||
fclose(fp);
|
||||
fclose(fp1);
|
||||
fclose(fp2);
|
||||
fclose(fp3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
|
||||
```C++
|
||||
simplest_yuv420_split("lena_256x256_yuv420p.yuv",256,256,1);
|
||||
```
|
||||
|
||||
从代码可以看出,如果视频帧的宽和高分别为w和h,那么一帧YUV420P像素数据一共占用w*h*3/2 Byte的数据。其中前w*h Byte存储Y,接着的w*h*1/4 Byte存储U,最后w*h*1/4 Byte存储V。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件分离成为三个文件:
|
||||
|
||||
>* output_420_y.y:纯Y数据,分辨率为256x256。
|
||||
>* output_420_u.y:纯U数据,分辨率为128x128。
|
||||
>* output_420_v.y:纯V数据,分辨率为128x128。
|
||||
|
||||
注:本文中像素的采样位数一律为8bit。由于1Byte=8bit,所以一个像素的一个分量的采样值占用1Byte。
|
||||
|
||||
程序输入的原图如下所示。
|
||||
|
||||

|
||||
|
||||
lena_256x256_yuv420p.yuv
|
||||
|
||||
程序输出的三个文件的截图如下图所示。在这里需要注意输出的U、V分量在YUV播放器中也是当做Y分量进行播放的。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 1.2 分离YUV444P像素数据中的Y、U、V分量
|
||||
|
||||
本程序中的函数可以将YUV444P数据中的Y、U、V三个分量分离开来并保存成三个文件。函数的代码如下所示。
|
||||
|
||||
```C++
|
||||
/**
|
||||
* Split Y, U, V planes in YUV444P file.
|
||||
* @param url Location of YUV file.
|
||||
* @param w Width of Input YUV file.
|
||||
* @param h Height of Input YUV file.
|
||||
* @param num Number of frames to process.
|
||||
*
|
||||
*/
|
||||
int simplest_yuv444_split(char *url, int w, int h,int num){
|
||||
FILE *fp=fopen(url,"rb+");
|
||||
FILE *fp1=fopen("output_444_y.y","wb+");
|
||||
FILE *fp2=fopen("output_444_u.y","wb+");
|
||||
FILE *fp3=fopen("output_444_v.y","wb+");
|
||||
unsigned char *pic=(unsigned char *)malloc(w*h*3);
|
||||
|
||||
for(int i=0;i<num;i++){
|
||||
fread(pic,1,w*h*3,fp);
|
||||
//Y
|
||||
fwrite(pic,1,w*h,fp1);
|
||||
//U
|
||||
fwrite(pic+w*h,1,w*h,fp2);
|
||||
//V
|
||||
fwrite(pic+w*h*2,1,w*h,fp3);
|
||||
}
|
||||
|
||||
free(pic);
|
||||
fclose(fp);
|
||||
fclose(fp1);
|
||||
fclose(fp2);
|
||||
fclose(fp3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
|
||||
```C++
|
||||
simplest_yuv444_split("lena_256x256_yuv444p.yuv",256,256,1);
|
||||
```
|
||||
|
||||
从代码可以看出,如果视频帧的宽和高分别为w和h,那么一帧YUV444P像素数据一共占用w*h*3 Byte的数据。其中前w*h Byte存储Y,接着的w*h Byte存储U,最后w*h Byte存储V。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv444p.yuv的YUV444P格式的像素数据文件分离成为三个文件:
|
||||
|
||||
>* output_444_y.y:纯Y数据,分辨率为256x256。
|
||||
>* output_444_u.y:纯U数据,分辨率为256x256。
|
||||
>* output_444_v.y:纯V数据,分辨率为256x256。
|
||||
|
||||
输入的原图如下所示。
|
||||

|
||||
输出的三个文件的截图如下图所示。
|
||||

|
||||
output_444_y.y
|
||||

|
||||
output_444_u.y
|
||||

|
||||
output_444_v.y
|
||||
|
||||
### 1.3 将YUV420P像素数据去掉颜色(变成灰度图)
|
||||
|
||||
本程序中的函数可以将YUV420P格式像素数据的彩色去掉,变成纯粹的灰度图。函数的代码如下。
|
||||
```C++
|
||||
/**
|
||||
* Convert YUV420P file to gray picture
|
||||
* @param url Location of Input YUV file.
|
||||
* @param w Width of Input YUV file.
|
||||
* @param h Height of Input YUV file.
|
||||
* @param num Number of frames to process.
|
||||
*/
|
||||
int simplest_yuv420_gray(char *url, int w, int h,int num){
|
||||
FILE *fp=fopen(url,"rb+");
|
||||
FILE *fp1=fopen("output_gray.yuv","wb+");
|
||||
unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
|
||||
|
||||
for(int i=0;i<num;i++){
|
||||
fread(pic,1,w*h*3/2,fp);
|
||||
//Gray
|
||||
memset(pic+w*h,128,w*h/2);
|
||||
fwrite(pic,1,w*h*3/2,fp1);
|
||||
}
|
||||
|
||||
free(pic);
|
||||
fclose(fp);
|
||||
fclose(fp1);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
|
||||
```C++
|
||||
simplest_yuv420_gray("lena_256x256_yuv420p.yuv",256,256,1);
|
||||
```
|
||||
|
||||
从代码可以看出,如果想把YUV格式像素数据变成灰度图像,只需要将U、V分量设置成128即可。这是因为U、V是图像中的经过偏置处理的色度分量。色度分量在偏置处理前的取值范围是-128至127,这时候的无色对应的是“0”值。经过偏置后色度分量取值变成了0至255,因而此时的无色对应的就是128了。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件处理成名称为output_gray.yuv的YUV420P格式的像素数据文件。输入的原图如下所示。
|
||||
|
||||

|
||||
|
||||
处理后的图像如下所示。
|
||||
|
||||

|
||||
|
||||
### 1.4 将YUV420P像素数据的亮度减半
|
||||
|
||||
本程序中的函数可以通过将YUV数据中的亮度分量Y的数值减半的方法,降低图像的亮度。函数代码如下所示。
|
||||
|
||||
```C++
|
||||
/**
|
||||
* Halve Y value of YUV420P file
|
||||
* @param url Location of Input YUV file.
|
||||
* @param w Width of Input YUV file.
|
||||
* @param h Height of Input YUV file.
|
||||
* @param num Number of frames to process.
|
||||
*/
|
||||
int simplest_yuv420_halfy(char *url, int w, int h,int num){
|
||||
FILE *fp=fopen(url,"rb+");
|
||||
FILE *fp1=fopen("output_half.yuv","wb+");
|
||||
|
||||
unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
|
||||
|
||||
for(int i=0;i<num;i++){
|
||||
fread(pic,1,w*h*3/2,fp);
|
||||
//Half
|
||||
for(int j=0;j<w*h;j++){
|
||||
unsigned char temp=pic[j]/2;
|
||||
//printf("%d,\n",temp);
|
||||
pic[j]=temp;
|
||||
}
|
||||
fwrite(pic,1,w*h*3/2,fp1);
|
||||
}
|
||||
|
||||
free(pic);
|
||||
fclose(fp);
|
||||
fclose(fp1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
```C++
|
||||
simplest_yuv420_halfy("lena_256x256_yuv420p.yuv",256,256,1);
|
||||
```
|
||||
|
||||
从代码可以看出,如果打算将图像的亮度减半,只要将图像的每个像素的Y值取出来分别进行除以2的工作就可以了。图像的每个Y值占用1 Byte,取值范围是0至255,对应C语言中的unsigned char数据类型。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件处理成名称为output_half.yuv的YUV420P格式的像素数据文件。输入的原图如下所示。
|
||||
|
||||

|
||||
|
||||
处理后的图像如下所示。
|
||||
|
||||

|
||||
|
||||
### 1.5 将YUV420P像素数据的周围加上边框
|
||||
|
||||
本程序中的函数可以通过修改YUV数据中特定位置的亮度分量Y的数值,给图像添加一个“边框”的效果。函数代码如下所示。
|
||||
|
||||
```C++
|
||||
/**
|
||||
* Add border for YUV420P file
|
||||
* @param url Location of Input YUV file.
|
||||
* @param w Width of Input YUV file.
|
||||
* @param h Height of Input YUV file.
|
||||
* @param border Width of Border.
|
||||
* @param num Number of frames to process.
|
||||
*/
|
||||
int simplest_yuv420_border(char *url, int w, int h,int border,int num){
|
||||
FILE *fp=fopen(url,"rb+");
|
||||
FILE *fp1=fopen("output_border.yuv","wb+");
|
||||
|
||||
unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
|
||||
|
||||
for(int i=0;i<num;i++){
|
||||
fread(pic,1,w*h*3/2,fp);
|
||||
//Y
|
||||
for(int j=0;j<h;j++){
|
||||
for(int k=0;k<w;k++){
|
||||
if(k<border||k>(w-border)||j<border||j>(h-border)){
|
||||
pic[j*w+k]=255;
|
||||
//pic[j*w+k]=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
fwrite(pic,1,w*h*3/2,fp1);
|
||||
}
|
||||
|
||||
free(pic);
|
||||
fclose(fp);
|
||||
fclose(fp1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
|
||||
```C++
|
||||
simplest_yuv420_border("lena_256x256_yuv420p.yuv",256,256,20,1);
|
||||
```
|
||||
|
||||
从代码可以看出,图像的边框的宽度为border,本程序将距离图像边缘border范围内的像素的亮度分量Y的取值设置成了亮度最大值255。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件处理成名称为output_border.yuv的YUV420P格式的像素数据文件。输入的原图如下所示。
|
||||
|
||||

|
||||
|
||||
处理后的图像如下所示。
|
||||
|
||||

|
||||
|
||||
### 1.6 生成YUV420P格式的灰阶测试图
|
||||
|
||||
本程序中的函数可以生成一张YUV420P格式的灰阶测试图。函数代码如下所示。
|
||||
|
||||
```C++
|
||||
/**
|
||||
* Generate YUV420P gray scale bar.
|
||||
* @param width Width of Output YUV file.
|
||||
* @param height Height of Output YUV file.
|
||||
* @param ymin Max value of Y
|
||||
* @param ymax Min value of Y
|
||||
* @param barnum Number of bars
|
||||
* @param url_out Location of Output YUV file.
|
||||
*/
|
||||
int simplest_yuv420_graybar(int width, int height,int ymin,int ymax,int barnum,char *url_out){
|
||||
int barwidth;
|
||||
float lum_inc;
|
||||
unsigned char lum_temp;
|
||||
int uv_width,uv_height;
|
||||
FILE *fp=NULL;
|
||||
unsigned char *data_y=NULL;
|
||||
unsigned char *data_u=NULL;
|
||||
unsigned char *data_v=NULL;
|
||||
int t=0,i=0,j=0;
|
||||
|
||||
barwidth=width/barnum;
|
||||
lum_inc=((float)(ymax-ymin))/((float)(barnum-1));
|
||||
uv_width=width/2;
|
||||
uv_height=height/2;
|
||||
|
||||
data_y=(unsigned char *)malloc(width*height);
|
||||
data_u=(unsigned char *)malloc(uv_width*uv_height);
|
||||
data_v=(unsigned char *)malloc(uv_width*uv_height);
|
||||
|
||||
if((fp=fopen(url_out,"wb+"))==NULL){
|
||||
printf("Error: Cannot create file!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Output Info
|
||||
printf("Y, U, V value from picture's left to right:\n");
|
||||
for(t=0;t<(width/barwidth);t++){
|
||||
lum_temp=ymin+(char)(t*lum_inc);
|
||||
printf("%3d, 128, 128\n",lum_temp);
|
||||
}
|
||||
//Gen Data
|
||||
for(j=0;j<height;j++){
|
||||
for(i=0;i<width;i++){
|
||||
t=i/barwidth;
|
||||
lum_temp=ymin+(char)(t*lum_inc);
|
||||
data_y[j*width+i]=lum_temp;
|
||||
}
|
||||
}
|
||||
for(j=0;j<uv_height;j++){
|
||||
for(i=0;i<uv_width;i++){
|
||||
data_u[j*uv_width+i]=128;
|
||||
}
|
||||
}
|
||||
for(j=0;j<uv_height;j++){
|
||||
for(i=0;i<uv_width;i++){
|
||||
data_v[j*uv_width+i]=128;
|
||||
}
|
||||
}
|
||||
fwrite(data_y,width*height,1,fp);
|
||||
fwrite(data_u,uv_width*uv_height,1,fp);
|
||||
fwrite(data_v,uv_width*uv_height,1,fp);
|
||||
fclose(fp);
|
||||
free(data_y);
|
||||
free(data_u);
|
||||
free(data_v);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
|
||||
```C++
|
||||
simplest_yuv420_graybar(640, 360,0,255,10,"graybar_640x360.yuv");
|
||||
```
|
||||
|
||||
从源代码可以看出,本程序一方面通过灰阶测试图的亮度最小值ymin,亮度最大值ymax,灰阶数量barnum确定每一个灰度条中像素的亮度分量Y的取值。另一方面还要根据图像的宽度width和图像的高度height以及灰阶数量barnum确定每一个灰度条的宽度。有了这两方面信息之后,就可以生成相应的图片了。上述调用函数的代码运行后,会生成一个取值范围从0-255,一共包含10个灰度条的YUV420P格式的测试图。测试图的内容如下所示。
|
||||
|
||||

|
||||
|
||||
从程序也可以得到从左到右10个灰度条的Y、U、V取值,如下所示。
|
||||
|
||||

|
||||
|
||||
### 1.7 计算两个YUV420P像素数据的PSNR
|
||||
|
||||
PSNR是最基本的视频质量评价方法。本程序中的函数可以对比两张YUV图片中亮度分量Y的PSNR。函数的代码如下所示。
|
||||
|
||||
```C++
|
||||
/**
|
||||
* Calculate PSNR between 2 YUV420P file
|
||||
* @param url1 Location of first Input YUV file.
|
||||
* @param url2 Location of another Input YUV file.
|
||||
* @param w Width of Input YUV file.
|
||||
* @param h Height of Input YUV file.
|
||||
* @param num Number of frames to process.
|
||||
*/
|
||||
int simplest_yuv420_psnr(char *url1,char *url2,int w,int h,int num){
|
||||
FILE *fp1=fopen(url1,"rb+");
|
||||
FILE *fp2=fopen(url2,"rb+");
|
||||
unsigned char *pic1=(unsigned char *)malloc(w*h);
|
||||
unsigned char *pic2=(unsigned char *)malloc(w*h);
|
||||
|
||||
for(int i=0;i<num;i++){
|
||||
fread(pic1,1,w*h,fp1);
|
||||
fread(pic2,1,w*h,fp2);
|
||||
|
||||
double mse_sum=0,mse=0,psnr=0;
|
||||
for(int j=0;j<w*h;j++){
|
||||
mse_sum+=pow((double)(pic1[j]-pic2[j]),2);
|
||||
}
|
||||
mse=mse_sum/(w*h);
|
||||
psnr=10*log10(255.0*255.0/mse);
|
||||
printf("%5.3f\n",psnr);
|
||||
|
||||
fseek(fp1,w*h/2,SEEK_CUR);
|
||||
fseek(fp2,w*h/2,SEEK_CUR);
|
||||
|
||||
}
|
||||
|
||||
free(pic1);
|
||||
free(pic2);
|
||||
fclose(fp1);
|
||||
fclose(fp2);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
|
||||
```C++
|
||||
simplest_yuv420_psnr("lena_256x256_yuv420p.yuv","lena_distort_256x256_yuv420p.yuv",256,256,1);
|
||||
```
|
||||
|
||||
对于8bit量化的像素数据来说,PSNR的计算公式如下所示。
|
||||
|
||||

|
||||
|
||||
上述公式中mse的计算公式如下所示。
|
||||
|
||||

|
||||
|
||||
其中M,N分别为图像的宽高,xij和yij分别为两张图像的每一个像素值。PSNR通常用于质量评价,就是计算受损图像与原始图像之间的差别,以此来评价受损图像的质量。本程序输入的两张图像的对比图如下图所示。其中左边的图像为原始图像,右边的图像为受损图像。
|
||||
|
||||

|
||||
|
||||
经过程序计算后得到的PSNR取值为26.693。PSNR取值通常情况下都在20-50的范围内,取值越高,代表两张图像越接近,反映出受损图像质量越好。
|
||||
|
||||
### 1.8 分离RGB24像素数据中的R、G、B分量
|
||||
|
||||
本程序中的函数可以将RGB24数据中的R、G、B三个分量分离开来并保存成三个文件。函数的代码如下所示。
|
||||
|
||||
```C++
|
||||
/**
|
||||
* Split R, G, B planes in RGB24 file.
|
||||
* @param url Location of Input RGB file.
|
||||
* @param w Width of Input RGB file.
|
||||
* @param h Height of Input RGB file.
|
||||
* @param num Number of frames to process.
|
||||
*
|
||||
*/
|
||||
int simplest_rgb24_split(char *url, int w, int h,int num){
|
||||
FILE *fp=fopen(url,"rb+");
|
||||
FILE *fp1=fopen("output_r.y","wb+");
|
||||
FILE *fp2=fopen("output_g.y","wb+");
|
||||
FILE *fp3=fopen("output_b.y","wb+");
|
||||
|
||||
unsigned char *pic=(unsigned char *)malloc(w*h*3);
|
||||
|
||||
for(int i=0;i<num;i++){
|
||||
|
||||
fread(pic,1,w*h*3,fp);
|
||||
|
||||
for(int j=0;j<w*h*3;j=j+3){
|
||||
//R
|
||||
fwrite(pic+j,1,1,fp1);
|
||||
//G
|
||||
fwrite(pic+j+1,1,1,fp2);
|
||||
//B
|
||||
fwrite(pic+j+2,1,1,fp3);
|
||||
}
|
||||
}
|
||||
|
||||
free(pic);
|
||||
fclose(fp);
|
||||
fclose(fp1);
|
||||
fclose(fp2);
|
||||
fclose(fp3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
|
||||
```C++
|
||||
simplest_rgb24_split("cie1931_500x500.rgb", 500, 500,1);
|
||||
```
|
||||
|
||||
从代码可以看出,与YUV420P三个分量分开存储不同,RGB24格式的每个像素的三个分量是连续存储的。一帧宽高分别为w、h的RGB24图像一共占用w*h*3 Byte的存储空间。RGB24格式规定首先存储第一个像素的R、G、B,然后存储第二个像素的R、G、B…以此类推。类似于YUV420P的存储方式称为Planar方式,而类似于RGB24的存储方式称为Packed方式。上述调用函数的代码运行后,将会把一张分辨率为500x500的名称为cie1931_500x500.rgb的RGB24格式的像素数据文件分离成为三个文件:
|
||||
|
||||
>* output_r.y:R数据,分辨率为256x256。
|
||||
>* output_g.y:G数据,分辨率为256x256。
|
||||
>* output_b.y:B数据,分辨率为256x256。
|
||||
|
||||
输入的原图是一张标准的CIE 1931色度图。该色度图右下为红色,上方为绿色,左下为蓝色,如下所示。
|
||||
|
||||

|
||||
|
||||
R数据图像如下所示。
|
||||
|
||||

|
||||
|
||||
G数据图像如下所示。
|
||||
|
||||

|
||||
|
||||
B数据图像如下所示。
|
||||
|
||||

|
||||
|
||||
### 1.9 将RGB24格式像素数据封装为BMP图像
|
||||
|
||||
BMP图像内部实际上存储的就是RGB数据。本程序实现了对RGB像素数据的封装处理。通过本程序中的函数,可以将RGB数据封装成为一张BMP图像。
|
||||
|
||||
```C++
|
||||
/**
|
||||
* Convert RGB24 file to BMP file
|
||||
* @param rgb24path Location of input RGB file.
|
||||
* @param width Width of input RGB file.
|
||||
* @param height Height of input RGB file.
|
||||
* @param url_out Location of Output BMP file.
|
||||
*/
|
||||
int simplest_rgb24_to_bmp(const char *rgb24path,int width,int height,const char *bmppath){
|
||||
typedef struct
|
||||
{
|
||||
long imageSize;
|
||||
long blank;
|
||||
long startPosition;
|
||||
}BmpHead;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
long Length;
|
||||
long width;
|
||||
long height;
|
||||
unsigned short colorPlane;
|
||||
unsigned short bitColor;
|
||||
long zipFormat;
|
||||
long realSize;
|
||||
long xPels;
|
||||
long yPels;
|
||||
long colorUse;
|
||||
long colorImportant;
|
||||
}InfoHead;
|
||||
|
||||
int i=0,j=0;
|
||||
BmpHead m_BMPHeader={0};
|
||||
InfoHead m_BMPInfoHeader={0};
|
||||
char bfType[2]={'B','M'};
|
||||
int header_size=sizeof(bfType)+sizeof(BmpHead)+sizeof(InfoHead);
|
||||
unsigned char *rgb24_buffer=NULL;
|
||||
FILE *fp_rgb24=NULL,*fp_bmp=NULL;
|
||||
|
||||
if((fp_rgb24=fopen(rgb24path,"rb"))==NULL){
|
||||
printf("Error: Cannot open input RGB24 file.\n");
|
||||
return -1;
|
||||
}
|
||||
if((fp_bmp=fopen(bmppath,"wb"))==NULL){
|
||||
printf("Error: Cannot open output BMP file.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rgb24_buffer=(unsigned char *)malloc(width*height*3);
|
||||
fread(rgb24_buffer,1,width*height*3,fp_rgb24);
|
||||
|
||||
m_BMPHeader.imageSize=3*width*height+header_size;
|
||||
m_BMPHeader.startPosition=header_size;
|
||||
|
||||
m_BMPInfoHeader.Length=sizeof(InfoHead);
|
||||
m_BMPInfoHeader.width=width;
|
||||
//BMP storage pixel data in opposite direction of Y-axis (from bottom to top).
|
||||
m_BMPInfoHeader.height=-height;
|
||||
m_BMPInfoHeader.colorPlane=1;
|
||||
m_BMPInfoHeader.bitColor=24;
|
||||
m_BMPInfoHeader.realSize=3*width*height;
|
||||
|
||||
fwrite(bfType,1,sizeof(bfType),fp_bmp);
|
||||
fwrite(&m_BMPHeader,1,sizeof(m_BMPHeader),fp_bmp);
|
||||
fwrite(&m_BMPInfoHeader,1,sizeof(m_BMPInfoHeader),fp_bmp);
|
||||
|
||||
//BMP save R1|G1|B1,R2|G2|B2 as B1|G1|R1,B2|G2|R2
|
||||
//It saves pixel data in Little Endian
|
||||
//So we change 'R' and 'B'
|
||||
for(j =0;j<height;j++){
|
||||
for(i=0;i<width;i++){
|
||||
char temp=rgb24_buffer[(j*width+i)*3+2];
|
||||
rgb24_buffer[(j*width+i)*3+2]=rgb24_buffer[(j*width+i)*3+0];
|
||||
rgb24_buffer[(j*width+i)*3+0]=temp;
|
||||
}
|
||||
}
|
||||
fwrite(rgb24_buffer,3*width*height,1,fp_bmp);
|
||||
fclose(fp_rgb24);
|
||||
fclose(fp_bmp);
|
||||
free(rgb24_buffer);
|
||||
printf("Finish generate %s!\n",bmppath);
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
|
||||
```C++
|
||||
simplest_rgb24_to_bmp("lena_256x256_rgb24.rgb",256,256,"output_lena.bmp");
|
||||
```
|
||||
|
||||
通过代码可以看出,改程序完成了主要完成了两个工作:
|
||||
|
||||
* 1) 将RGB数据前面加上文件头。
|
||||
* 2) 将RGB数据中每个像素的“B”和“R”的位置互换。
|
||||
|
||||
BMP文件是由BITMAPFILEHEADER、BITMAPINFOHEADER、RGB像素数据共3个部分构成,它的结构如下图所示。
|
||||
|
||||
>* BITMAPFILEHEADER
|
||||
>* BITMAPINFOHEADER
|
||||
>* RGB像素数据
|
||||
|
||||
其中前两部分的结构如下所示。在写入BMP文件头的时候给其中的每个字段赋上合适的值就可以了。
|
||||
|
||||
```C++
|
||||
typedef struct tagBITMAPFILEHEADER
|
||||
{
|
||||
unsigned short int bfType; //位图文件的类型,必须为BM
|
||||
unsigned long bfSize; //文件大小,以字节为单位
|
||||
unsigned short int bfReserverd1; //位图文件保留字,必须为0
|
||||
unsigned short int bfReserverd2; //位图文件保留字,必须为0
|
||||
unsigned long bfbfOffBits; //位图文件头到数据的偏移量,以字节为单位
|
||||
}BITMAPFILEHEADER;
|
||||
typedef struct tagBITMAPINFOHEADER
|
||||
{
|
||||
long biSize; //该结构大小,字节为单位
|
||||
long biWidth; //图形宽度以象素为单位
|
||||
long biHeight; //图形高度以象素为单位
|
||||
short int biPlanes; //目标设备的级别,必须为1
|
||||
short int biBitcount; //颜色深度,每个象素所需要的位数
|
||||
short int biCompression; //位图的压缩类型
|
||||
long biSizeImage; //位图的大小,以字节为单位
|
||||
long biXPelsPermeter; //位图水平分辨率,每米像素数
|
||||
long biYPelsPermeter; //位图垂直分辨率,每米像素数
|
||||
long biClrUsed; //位图实际使用的颜色表中的颜色数
|
||||
long biClrImportant; //位图显示过程中重要的颜色数
|
||||
}BITMAPINFOHEADER;
|
||||
```
|
||||
|
||||
BMP采用的是小端(Little Endian)存储方式。这种存储方式中“RGB24”格式的像素的分量存储的先后顺序为B、G、R。由于RGB24格式存储的顺序是R、G、B,所以需要将“R”和“B”顺序作一个调换再进行存储。
|
||||
下图为输入的RGB24格式的图像lena_256x256_rgb24.rgb。
|
||||
|
||||

|
||||
|
||||
下图分封装为BMP格式后的图像output_lena.bmp。封装后的图像使用普通的看图软件就可以查看。
|
||||
|
||||

|
||||
|
||||
### 1.10 将RGB24格式像素数据转换为YUV420P格式像素数据
|
||||
|
||||
本程序中的函数可以将RGB24格式的像素数据转换为YUV420P格式的像素数据。函数的代码如下所示。
|
||||
|
||||
```C++
|
||||
unsigned char clip_value(unsigned char x,unsigned char min_val,unsigned char max_val){
|
||||
if(x>max_val){
|
||||
return max_val;
|
||||
}else if(x<min_val){
|
||||
return min_val;
|
||||
}else{
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
//RGB to YUV420
|
||||
bool RGB24_TO_YUV420(unsigned char *RgbBuf,int w,int h,unsigned char *yuvBuf)
|
||||
{
|
||||
unsigned char*ptrY, *ptrU, *ptrV, *ptrRGB;
|
||||
memset(yuvBuf,0,w*h*3/2);
|
||||
ptrY = yuvBuf;
|
||||
ptrU = yuvBuf + w*h;
|
||||
ptrV = ptrU + (w*h*1/4);
|
||||
unsigned char y, u, v, r, g, b;
|
||||
for (int j = 0; j<h;j++){
|
||||
ptrRGB = RgbBuf + w*j*3 ;
|
||||
for (int i = 0;i<w;i++){
|
||||
|
||||
r = *(ptrRGB++);
|
||||
g = *(ptrRGB++);
|
||||
b = *(ptrRGB++);
|
||||
y = (unsigned char)( ( 66 * r + 129 * g + 25 * b + 128) >> 8) + 16 ;
|
||||
u = (unsigned char)( ( -38 * r - 74 * g + 112 * b + 128) >> 8) + 128 ;
|
||||
v = (unsigned char)( ( 112 * r - 94 * g - 18 * b + 128) >> 8) + 128 ;
|
||||
*(ptrY++) = clip_value(y,0,255);
|
||||
if (j%2==0&&i%2 ==0){
|
||||
*(ptrU++) =clip_value(u,0,255);
|
||||
}
|
||||
else{
|
||||
if (i%2==0){
|
||||
*(ptrV++) =clip_value(v,0,255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RGB24 file to YUV420P file
|
||||
* @param url_in Location of Input RGB file.
|
||||
* @param w Width of Input RGB file.
|
||||
* @param h Height of Input RGB file.
|
||||
* @param num Number of frames to process.
|
||||
* @param url_out Location of Output YUV file.
|
||||
*/
|
||||
int simplest_rgb24_to_yuv420(char *url_in, int w, int h,int num,char *url_out){
|
||||
FILE *fp=fopen(url_in,"rb+");
|
||||
FILE *fp1=fopen(url_out,"wb+");
|
||||
|
||||
unsigned char *pic_rgb24=(unsigned char *)malloc(w*h*3);
|
||||
unsigned char *pic_yuv420=(unsigned char *)malloc(w*h*3/2);
|
||||
|
||||
for(int i=0;i<num;i++){
|
||||
fread(pic_rgb24,1,w*h*3,fp);
|
||||
RGB24_TO_YUV420(pic_rgb24,w,h,pic_yuv420);
|
||||
fwrite(pic_yuv420,1,w*h*3/2,fp1);
|
||||
}
|
||||
|
||||
free(pic_rgb24);
|
||||
free(pic_yuv420);
|
||||
fclose(fp);
|
||||
fclose(fp1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
|
||||
```C++
|
||||
simplest_rgb24_to_yuv420("lena_256x256_rgb24.rgb",256,256,1,"output_lena.yuv");
|
||||
```
|
||||
|
||||
从源代码可以看出,本程序实现了RGB到YUV的转换公式:
|
||||
```C++
|
||||
Y= 0.299*R+0.587*G+0.114*B
|
||||
U=-0.147*R-0.289*G+0.463*B
|
||||
V= 0.615*R-0.515*G-0.100*B
|
||||
```
|
||||
|
||||
在转换的过程中有以下几点需要注意:
|
||||
|
||||
* 1) RGB24存储方式是Packed,YUV420P存储方式是Packed。
|
||||
* 2) U,V在水平和垂直方向的取样数是Y的一半
|
||||
*
|
||||
转换前的RGB24格式像素数据lena_256x256_rgb24.rgb的内容如下所示。
|
||||
|
||||

|
||||
|
||||
转换后的YUV420P格式的像素数据output_lena.yuv的内容如下所示。
|
||||
|
||||

|
||||
|
||||
### 1.11 生成RGB24格式的彩条测试图
|
||||
|
||||
本程序中的函数可以生成一张RGB24格式的彩条测试图。函数代码如下所示。
|
||||
```C++
|
||||
/**
|
||||
* Generate RGB24 colorbar.
|
||||
* @param width Width of Output RGB file.
|
||||
* @param height Height of Output RGB file.
|
||||
* @param url_out Location of Output RGB file.
|
||||
*/
|
||||
int simplest_rgb24_colorbar(int width, int height,char *url_out){
|
||||
|
||||
unsigned char *data=NULL;
|
||||
int barwidth;
|
||||
char filename[100]={0};
|
||||
FILE *fp=NULL;
|
||||
int i=0,j=0;
|
||||
|
||||
data=(unsigned char *)malloc(width*height*3);
|
||||
barwidth=width/8;
|
||||
|
||||
if((fp=fopen(url_out,"wb+"))==NULL){
|
||||
printf("Error: Cannot create file!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(j=0;j<height;j++){
|
||||
for(i=0;i<width;i++){
|
||||
int barnum=i/barwidth;
|
||||
switch(barnum){
|
||||
case 0:{
|
||||
data[(j*width+i)*3+0]=255;
|
||||
data[(j*width+i)*3+1]=255;
|
||||
data[(j*width+i)*3+2]=255;
|
||||
break;
|
||||
}
|
||||
case 1:{
|
||||
data[(j*width+i)*3+0]=255;
|
||||
data[(j*width+i)*3+1]=255;
|
||||
data[(j*width+i)*3+2]=0;
|
||||
break;
|
||||
}
|
||||
case 2:{
|
||||
data[(j*width+i)*3+0]=0;
|
||||
data[(j*width+i)*3+1]=255;
|
||||
data[(j*width+i)*3+2]=255;
|
||||
break;
|
||||
}
|
||||
case 3:{
|
||||
data[(j*width+i)*3+0]=0;
|
||||
data[(j*width+i)*3+1]=255;
|
||||
data[(j*width+i)*3+2]=0;
|
||||
break;
|
||||
}
|
||||
case 4:{
|
||||
data[(j*width+i)*3+0]=255;
|
||||
data[(j*width+i)*3+1]=0;
|
||||
data[(j*width+i)*3+2]=255;
|
||||
break;
|
||||
}
|
||||
case 5:{
|
||||
data[(j*width+i)*3+0]=255;
|
||||
data[(j*width+i)*3+1]=0;
|
||||
data[(j*width+i)*3+2]=0;
|
||||
break;
|
||||
}
|
||||
case 6:{
|
||||
data[(j*width+i)*3+0]=0;
|
||||
data[(j*width+i)*3+1]=0;
|
||||
data[(j*width+i)*3+2]=255;
|
||||
|
||||
break;
|
||||
}
|
||||
case 7:{
|
||||
data[(j*width+i)*3+0]=0;
|
||||
data[(j*width+i)*3+1]=0;
|
||||
data[(j*width+i)*3+2]=0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
fwrite(data,width*height*3,1,fp);
|
||||
fclose(fp);
|
||||
free(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
调用上面函数的方法如下所示。
|
||||
```C++
|
||||
simplest_rgb24_colorbar(640, 360,"colorbar_640x360.rgb");
|
||||
```
|
||||
|
||||
从源代码可以看出,本程序循环输出“白黄青绿品红蓝黑”8种颜色的彩条。这8种颜色的彩条的R、G、B取值如下所示。
|
||||

|
||||
|
||||
生成的图像截图如下所示。
|
||||

|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
原文作者: 雷霄骅
|
Reference in New Issue
Block a user