音视频开发——流媒体数据传输RTSP(二)

iOS音视频开发相关文章:

音视频开发——概述(一)

音视频开发——流媒体数据传输RTSP(二)

音视频开发——流媒体数据传输RTP(三)

音视频开发——ffmpeg解码(四)


tutk的demo下载:

本站下载:TUTK demo iOS

github(有用的话记得给star):https://github.com/bingly/AvSamplePlayer_TUTK



视频数据基本是通过网络传输获取的。针对音视频数据量大的特点,有一套专门的网络传输协议RTP/RTSP,它的运行流程是这样的:


RTSP

RTSP(Real Time Streaming Protocol)是一款网络控制协议,用来控制流媒体服务器的,并提供了一些命令,如 play, record, pause。play表示服务开始向请求端发送流媒体数据,pause表示停止。先贴上一篇文章,非常详细的讲解了rtsp的操作,没接触过的童鞋可以了解一些。RTSP交互命令简介及过程参数描述

以下是客户端同流媒体服务器交互的完整示例,采用WireShark抓包(192.168.0.107->客户端,192.168.0.103->服务端,图片在网页上显示过小,需要保存到本地看):


图书中第二部分为RTSP交互的过程,命令的发送顺序为:OPTIONS、DESCRIBE、SETUP(两次)、PLAY。当PLAY命令发送后,就进入了第三部分RTP协议传输的流媒体数据包。

简单的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 
代码分析:
#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.0\r\n";
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.0\r\n", _rtspAddress]];
    [dataString appendString:@"CSeq: 1\r\n"];
    [dataString appendString:@"\r\n"];
    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.0\r\n", _rtspAddress]];
    [dataString appendString:@"Accept: application/sdp\r\n"];
    [dataString appendString:@"CSeq: 2\r\n"];
    [dataString appendString:@"\r\n"];
    
    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.0\r\n", _rtspAddress]];
    [dataString appendString:[NSString stringWithFormat:@"Transport: RTP/AVP/UDP;unicast;client_port=%@\r\n", _cliendPort]];
    [dataString appendString:@"x-Dynamic-Rate: 0\r\n"];
    [dataString appendString:@"CSeq: 3\r\n"];
    [dataString appendString:@"\r\n"];
    
    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.0\r\n", _rtspAddress]];
    [dataString appendString:[NSString stringWithFormat:@"Transport: RTP/AVP/UDP;unicast;client_port=%@\r\n", _cliendPort]];
    [dataString appendString:@"x-Dynamic-Rate: 0\r\n"];
    [dataString appendString:@"CSeq: 4\r\n"];
//    [dataString appendString:[NSString stringWithFormat:@"Session: %@", _sessionId]];
    [dataString appendString:@"\r\n"];
    
    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.0\r\n", _rtspAddress]];
    [dataString appendString:@"Range: npt=0.000-\r\n"];
    [dataString appendString:@"CSeq: 4\r\n"];
    [dataString appendString:@"\r\n"];
    
    NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
    [clientSocket writeData:data withTimeout:WRITE_TIMEOUT tag:4];
    [clientSocket readDataWithTimeout:READ_TIMEOUT tag:4];
}

接收到的数据:
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


本教程的demo:http://download.csdn.net/detail/a411358606/9599939(教程是针对特定的服务端,使用时先抓包,根据实际情况修改)
另外,欢迎大家加入iOS音视频开发的QQ群: 331753091

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值