前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >音视频开发(一)-流媒体数据传输RTSP

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

原创
作者头像
嵌入式音视频
发布2022-09-24 17:10:54
8510
发布2022-09-24 17:10:54
举报
文章被收录于专栏:音视频进阶之旅

一、运行流程

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

接收到的数据:

代码语言: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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、运行流程
  • 接收到的数据:
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,助力维护团队卓越代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档