音视频开发(一)-流媒体数据传输RTSP

2022-09-24 17:10:54 浏览数 (2)

一、运行流程

视频数据基本是通过网络传输获取的。针对音视频数据量大的特点,有一套专门的网络传输协议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

0 人点赞