一、运行流程
视频数据基本是通过网络传输获取的。针对音视频数据量大的特点,有一套专门的网络传输协议RTP/RTSP,它的运行流程是这样的:
RTSP
RTSP(Real Time Streaming Protocol)是一款网络控制协议,用来控制流媒体服务器的,并提供了一些命令,如 play, record, pause。play表示服务开始向请求端发送流媒体数据,pause表示停止。先贴上一篇文章,非常详细的讲解了rtsp的操作,没接触过的童鞋可以了解一些。
以下是客户端同流媒体服务器交互的完整示例,采用WireShark抓包(192.168.0.107->客户端,192.168.0.103->服务端,图片在网页上显示过小,需要保存到本地看):
图书中第二部分为RTSP交互的过程,命令的发送顺序为:OPTIONS、DESCRIBE、SETUP(两次)、PLAY。当PLAY命令发送后,就进入了第三部分RTP协议传输的流媒体数据包。
资料领取直通车:音视频开发&流媒体服务器资料文档 视频教程
音视频学习直通车:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
简单的rtsp交互过程:(C表示rtsp客户端,S表示rtsp服务端)
1.C->S:OPTION request //询问S有哪些方法可用 1.S->C:OPTION response //S回应信息中包括提供的所有可用方法 2.C->S:DESCRIBE request //要求得到S提供的媒体初始化描述信息 2.S->C:DESCRIBE response //S回应媒体初始化描述信息,主要是sdp 3.C->S:SETUP request //设置会话的属性,以及传输模式,提醒S建立会话 3.S->C:SETUP response //S建立会话,返回会话标识符,以及会话相关信息 4.C->S:PLAY request //C请求播放 4.S->C:PLAY response //S回应该请求的信息 5.S->C:发送流媒体数据 6.C->S:TEARDOWN request //C请求关闭会话 6.S->C:TEARDOWN response //S回应该请求
根据rtsp协议传输的步骤,使用tcp协议封装rtsp的发送的参数,即可实现视频传输的控制。tcp与udp的通信采用的是第三方库CocoaAsyncSocket
代码分析:
代码语言:javascript复制#import "RTSPConnection.h"
#import "RTPReceiver.h"
#import "CocoaAsyncSocket.h"
#define HOST @"192.168.0.102" //camera
//#define HOST @"192.168.0.111"
#define PORT 554
#define WRITE_TIMEOUT 3.0
#define READ_TIMEOUT 60.0
const static NSString *VERSION = @" RTSP/1.0rn";
const static NSString *RTSP_OK = @"RTSP/1.0 200 OK";
@interface RTSPConnection() <GCDAsyncSocketDelegate> {
dispatch_queue_t socketQueue;
GCDAsyncSocket *clientSocket;
NSString *_host;
uint16_t _port;
int _rtpPort;
NSString *_rtspAddress;
// 服务端返回数据
NSString *_sessionId;
NSString *_cliendPort; //RTP、RTCP端口
RTPReceiver *_rtpReceiver;
}
@end
@implementation RTSPConnection
- (instancetype)init {
if (self = [super init]) {
socketQueue = dispatch_queue_create("tcpSocketQueue", NULL);
clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:socketQueue];
_host = HOST;
_port = PORT;
// _rtspAddress = [NSString stringWithFormat:@"rtsp://%@:%d/", _host, _port];
_rtspAddress = [NSString stringWithFormat:@"rtsp://%@:%d/live.264", _host, _port];
_rtpPort = arc4random() % 10000 6000;
_cliendPort = [NSString stringWithFormat:@"%d-%d", _rtpPort , _rtpPort 1];
_rtpReceiver = [[RTPReceiver alloc] initWithPort:_rtpPort];
[_rtpReceiver startReceive];
[self connectSocket];
}
return self;
}
- (void)connectSocket {
NSError *error;
int connectRet = [clientSocket connectToHost:_host onPort:_port error:&error];
if (!connectRet) {
NSLog(@"Error Connection: %@", error.localizedDescription);
}
}
#pragma mard socketDelegate
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(@"didConnectToHost, host: %@, port: %d", host, port);
[self doOption];
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"didReadData: %ld, %@", tag, dataString);
switch (tag) {
case 0:
[self doDecribe];
break;
case 1:
[self doSetup];
break;
case 2:
{
NSError *error; //@"Session:\s(\w )[\s\S] ?client_port=(\w )
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"Session:\s(\w )" options:NSRegularExpressionAllowCommentsAndWhitespace error:&error];
NSArray *result = [regex matchesInString:dataString options:NSMatchingReportCompletion range:NSMakeRange(0, dataString.length)];
if ([result count] == 0) {
NSLog(@"ERROR!!! Cann't find session id and client port");
return;
}
_sessionId = [dataString substringWithRange:[result[0] rangeAtIndex:1]];
// [self doSetupSession];
[self doPlay];
}
break;
case 3:
// [self doPlay];
break;
default:
break;
}
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"didWriteDataWithTag: %ld", tag);
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
NSLog(@"socketDidDisconnect: %@", err.localizedDescription);
}
- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length {
NSLog(@"shouldTimeoutReadWithTag, elapsed: %f, bytesDone: %ld", elapsed, length);
return 0.0;
}
#pragma mark send RTSP data
- (void)doOption {
NSMutableString *dataString = [NSMutableString string];
// [dataString appendString:@"OPTIONS "];
[dataString appendString:[NSString stringWithFormat:@"OPTIONS %@ RTSP/1.0rn", _rtspAddress]];
[dataString appendString:@"CSeq: 1rn"];
[dataString appendString:@"rn"];
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
[clientSocket writeData:data withTimeout:WRITE_TIMEOUT tag:0];
[clientSocket readDataWithTimeout:READ_TIMEOUT tag:0];
}
- (void)doDecribe {
NSMutableString *dataString = [NSMutableString string];
[dataString appendString:[NSString stringWithFormat:@"DESCRIBE %@ RTSP/1.0rn", _rtspAddress]];
[dataString appendString:@"Accept: application/sdprn"];
[dataString appendString:@"CSeq: 2rn"];
[dataString appendString:@"rn"];
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
[clientSocket writeData:data withTimeout:WRITE_TIMEOUT tag:1];
[clientSocket readDataWithTimeout:READ_TIMEOUT tag:1];
}
- (void)doSetup {
NSMutableString *dataString = [NSMutableString string];
[dataString appendString:[NSString stringWithFormat:@"SETUP %@/track1 RTSP/1.0rn", _rtspAddress]];
[dataString appendString:[NSString stringWithFormat:@"Transport: RTP/AVP/UDP;unicast;client_port=%@rn", _cliendPort]];
[dataString appendString:@"x-Dynamic-Rate: 0rn"];
[dataString appendString:@"CSeq: 3rn"];
[dataString appendString:@"rn"];
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
[clientSocket writeData:data withTimeout:WRITE_TIMEOUT tag:2];
[clientSocket readDataWithTimeout:READ_TIMEOUT tag:2];
}
- (void)doSetupSession {
NSMutableString *dataString = [NSMutableString string];
[dataString appendString:[NSString stringWithFormat:@"SETUP %@/track2 RTSP/1.0rn", _rtspAddress]];
[dataString appendString:[NSString stringWithFormat:@"Transport: RTP/AVP/UDP;unicast;client_port=%@rn", _cliendPort]];
[dataString appendString:@"x-Dynamic-Rate: 0rn"];
[dataString appendString:@"CSeq: 4rn"];
// [dataString appendString:[NSString stringWithFormat:@"Session: %@", _sessionId]];
[dataString appendString:@"rn"];
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
[clientSocket writeData:data withTimeout:WRITE_TIMEOUT tag:3];
[clientSocket readDataWithTimeout:READ_TIMEOUT tag:3];
}
- (void)doPlay {
NSMutableString *dataString = [NSMutableString string];
[dataString appendString:[NSString stringWithFormat:@"PLAY %@ RTSP/1.0rn", _rtspAddress]];
[dataString appendString:@"Range: npt=0.000-rn"];
[dataString appendString:@"CSeq: 4rn"];
[dataString appendString:@"rn"];
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
[clientSocket writeData:data withTimeout:WRITE_TIMEOUT tag:4];
[clientSocket readDataWithTimeout:READ_TIMEOUT tag:4];
}
接收到的数据:
代码语言:javascript复制2016-08-10 13:41:02.696 RTSPClient[2457:961862] didConnectToHost, host: 192.168.0.102, port: 554
2016-08-10 13:41:02.697 RTSPClient[2457:961862] didWriteDataWithTag: 0
2016-08-10 13:41:02.708 RTSPClient[2457:961874] didReadData: 0, RTSP/1.0 200 OK
CSeq: 1
Date: Wed, Aug 10 2016 05:41:03 GMT
Public: OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE, TEARDOWN, GET_PARAMETER, SET_PARAMETER
2016-08-10 13:41:02.708 RTSPClient[2457:961874] didWriteDataWithTag: 1
2016-08-10 13:41:02.728 RTSPClient[2457:961862] didReadData: 1, RTSP/1.0 200 OK
CSeq: 2
Content-Length: 508
Content-Base: rtsp://192.168.0.102:554/live.264/
Content-Type: application/sdp
Date: Wed, Aug 10 2016 05:41:03 GMT
x-Accept-Dynamic-Rate: 1
v=0
o=- 1773926623 1 IN IP4
s=Session Streamed by LIBZRTSP
i=live.264
t=0 0
a=tool:LIBZRTSPD v1/0.3
a=type:broadcast
a=control:*
a=range:npt=0-
a=x-qt-text-nam:Session Streamed by LIBZRTSP
a=x-qt-text-inf:live.264
m=video 0 RTP/AVP 96
c=IN IP4
b=AS:0
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=4D001E;sprop-parameter-sets=Z00AHpWoLQ9oQAAA gAAHUwB,aO48gA==
a=control:track1
m=audio 0 RTP/AVP 0
c=IN IP4 0.0.0.0
b=AS:64
a=rtpmap:0 PCMU/8000
a=control:track2
2016-08-10 13:41:02.729 RTSPClient[2457:961862] didWriteDataWithTag: 2
2016-08-10 13:41:02.738 RTSPClient[2457:961862] didReadData: 2, RTSP/1.0 200 OK
CSeq: 3
Date: Wed, Aug 10 2016 05:41:03 GMT
Session: 63A3E5E6;timeout=60
Transport: RTP/AVP/UDP;unicast;destination=192.168.0.110;source=192.168.0.102;client_port=12849-12850;server_port=30040-30041;mode=PLAY;
x-Dynamic-Rate: 1
2016-08-10 13:41:02.743 RTSPClient[2457:961862] didWriteDataWithTag: 4
2016-08-10 13:41:02.748 RTSPClient[2457:961857] didReadData: 4, RTSP/1.0 200 OK
CSeq: 4
Date: Wed, Aug 10 2016 05:41:03 GMT
Range: npt=0.000-
Session: 63A3E5E6
RTP-Info: url=rtsp://192.168.0.102:554/live.264/track1;seq=18553;rtptime=3987283666,url=rtsp://192.168.0.102:554/live.264/track2;seq=0;rtptime=0