视频音频编程知识点视频直播

RTMP 握手

2016-08-02  本文已影响953人  youngyunxing

很多初学者就是看了恶心的握手就再也没有研究的兴趣了,不过,弄懂了就感觉没什么了.
socket建立连接以后,就需要认证了,也就是握手.只有握手完成以后才能进入下一步的操作,比如发送控制命令,交换消息,发送音视频包等.客户端需要发送C0,C1,C2给服务器端,服务器端需要发送S0,S1,S2给客户端,至于C0,C1,C2,S0,S1,S2是什么,下面会介绍,暂时认为是数据吧.
重点来了:

是不是感觉很复杂的样子,来一张图清晰明了:

客户端       服务器端

 C0  ------->  
     
     <------- S0
 
 C1  ------->  
     
     <------- S1
     
 C2  ------->  
     
     <------- S2
     

然而实际上,客户端通常都是C0和C1一起发送,或者C0发送完马上发送C1.而服务器端一般都做了兼容处理,可能按照顺序发送,也可能在收到C1后就把S0,S1,S2一起发送给客户端.

先来个分类:主要是解析url的各个部分:

//解析推流地址
@interface NSString (URL)

@property(readonly) NSString *scheme;
@property(readonly) NSString *host;
@property(readonly) NSString *app;
@property(readonly) NSString *playPath;
@property(readonly) UInt32    port;

@end

//------------华丽的分割线 .m文件---------------------

#import "NSString+URL.h"

@implementation NSString (URL)
- (NSString *)scheme{
    return [self componentsSeparatedByString:@"://"].firstObject;
}
- (NSString *)host{
    NSURL *url = [NSURL URLWithString:self];
    return url.host;
}
- (NSString *)app{
    NSString *sep = [NSString stringWithFormat:@"%@/",self.host];
    NSString *res = [self componentsSeparatedByString:sep].lastObject;
    return [res componentsSeparatedByString:@"/"].firstObject;
}
- (NSString *)playPath{
    NSString *sep = [NSString stringWithFormat:@"%@/",self.host];
    NSString *res = [self componentsSeparatedByString:sep].lastObject;
    NSString *reu = [res componentsSeparatedByString:@"/"].lastObject;
    return [reu componentsSeparatedByString:@":"].firstObject;
}
- (UInt32)port{
    NSString *sep = [NSString stringWithFormat:@"%@/",self.host];
    NSString *res = [self componentsSeparatedByString:sep].lastObject;
    NSString *reu = [res componentsSeparatedByString:@"/"].lastObject;
    NSArray  *ret = [reu componentsSeparatedByString:@":"];
    if (ret.count < 2) {
    return 0;
    }
    return [ret.lastObject intValue];
}
@end
//核心代码

//SGRtmpSession.h
#import <Foundation/Foundation.h>

typedef NS_ENUM(NSUInteger, SGRtmpSessionStatus) {
    SGRtmpSessionStatusNone              = 0,
    SGRtmpSessionStatusConnected         = 1,

    SGRtmpSessionStatusHandshake0        = 2,
    SGRtmpSessionStatusHandshake1        = 3,
    SGRtmpSessionStatusHandshake2        = 4,
    SGRtmpSessionStatusHandshakeComplete = 5,

    SGRtmpSessionStatusFCPublish         = 6,
    SGRtmpSessionStatusReady             = 7,
    SGRtmpSessionStatusSessionStarted    = 8,

    SGRtmpSessionStatusError             = 9,
    SGRtmpSessionStatusNotConnected      = 10
};

@class SGRtmpSession;
@protocol SGRtmpSessionDeleagte <NSObject>

- (void)rtmpSession:(SGRtmpSession *)rtmpSession didChangeStatus:(SGRtmpSessionStatus)rtmpStatus;

@end

@interface SGRtmpSession : NSObject

@property (nonatomic,weak) id<SGRtmpSessionDeleagte> delegate;
@property (nonatomic,copy) NSString *url;

- (void)connect;

- (void)disConnect;
@end

//------------华丽的分割线 .m文件---------------------

#import "SGRtmpSession.h"
#import "SGStreamSession.h"
#import "NSString+URL.h"

//c1,c2,s1,s2的大小
static const size_t kRTMPSignatureSize = 1536;

@interface SGRtmpSession()<SGStreamSessionDelegate>


@property (nonatomic,strong) SGStreamSession *session;
/**
 *  状态很重要,贯穿整个项目
 */
@property (nonatomic,assign) SGRtmpSessionStatus rtmpStatus;

@property (nonatomic,strong) NSMutableData *handshake;

@end


@implementation SGRtmpSession

- (void)dealloc{
    NSLog(@"%s",__func__);
    self.url = nil;
    self.delegate = nil;
    self.session = nil;
    _rtmpStatus = SGRtmpSessionStatusNone;
}


- (SGStreamSession *)session{
    if (_session == nil) {
        _session = [[SGStreamSession alloc] init];
        _session.delegate = self;
    }
    return _session;
}

- (void)setUrl:(NSString *)url{
    _url = url;
    NSLog(@"scheme:%@",url.scheme);
    NSLog(@"host:%@",url.host);
    NSLog(@"app:%@",url.app);
    NSLog(@"playPath:%@",url.playPath);
    NSLog(@"port:%zd",url.port);
}

- (void)setRtmpStatus:(SGRtmpSessionStatus)rtmpStatus{
    _rtmpStatus = rtmpStatus;
    NSLog(@"rtmpStatus-----%zd",rtmpStatus);
    if ([self.delegate respondsToSelector:@selector(rtmpSession:didChangeStatus:)]) {
        [self.delegate rtmpSession:self didChangeStatus:_rtmpStatus];
    }
}

- (instancetype)init{
   
    if (self = [super init]) {
        _rtmpStatus = SGRtmpSessionStatusNone;
    }
    
    return self;
    
}

- (void)connect{
    [self.session connectToServer:self.url.host port:self.url.port];
}
- (void)disConnect{
    [self.session disConnect];
}

#pragma mark -------delegate---------
- (void)streamSession:(SGStreamSession *)session didChangeStatus:(SGStreamStatus)streamStatus{
    
    if (streamStatus & NSStreamEventHasBytesAvailable) {//收到数据
        [self didReceivedata];
        return;//return
    }
    
    if (streamStatus & NSStreamEventHasSpaceAvailable){ //可以写数据
        if (_rtmpStatus == SGRtmpSessionStatusConnected) {
           [self handshake0];
        }
        return;//return
    }
    
    if ((streamStatus & NSStreamEventOpenCompleted) &&
        _rtmpStatus < SGRtmpSessionStatusConnected) {
        self.rtmpStatus = SGRtmpSessionStatusConnected;
    }
    
    if (streamStatus & NSStreamEventErrorOccurred) {
        self.rtmpStatus = SGRtmpSessionStatusError;
    }
    
    if (streamStatus & NSStreamEventEndEncountered) {
        self.rtmpStatus = SGRtmpSessionStatusNotConnected;
    }
}

- (void)handshake0{
    
    self.rtmpStatus = SGRtmpSessionStatusHandshake0;
    
    //c0
    char c0Byte = 0x03;
    NSData *c0 = [NSData dataWithBytes:&c0Byte length:1];
    [self writeData:c0];
    
    //c1
    uint8_t *c1Bytes = (uint8_t *)malloc(kRTMPSignatureSize);
    memset(c1Bytes, 0, 4 + 4);
    NSData *c1 = [NSData dataWithBytes:c1Bytes length:kRTMPSignatureSize];
    free(c1Bytes);
    [self writeData:c1];
}

- (void)handshake1{
    self.rtmpStatus = SGRtmpSessionStatusHandshake2;
    NSData *s1 = [self.handshake subdataWithRange:NSMakeRange(0, kRTMPSignatureSize)];
    //c2
    uint8_t *s1Bytes = (uint8_t *)s1.bytes;
    memset(s1Bytes + 4, 0, 4);
    NSData *c2 = [NSData dataWithBytes:s1Bytes length:s1.length];
    [self writeData:c2];
}



//接收到数据
- (void)didReceivedata{
    NSData *data = [self.session readData];
    
    if (self.rtmpStatus >= SGRtmpSessionStatusConnected &&
        self.rtmpStatus < SGRtmpSessionStatusHandshakeComplete) {
        //将我收的数据保存起来,因为总数是3073个字节
        [self.handshake appendData:data];
    }
    
    NSLog(@"%zd",data.length);
    
    //handshke 服务气端情况
    //          1.按照官方文档c0,c1,c2
    //          2.一起发3073个字节
    //          3.先发一部分,再发一部分,每部分大小不确定,总数正确
    switch (_rtmpStatus) {
        case SGRtmpSessionStatusHandshake0:{
            uint8_t s0;
            [data getBytes:&s0 length:1];
            if (s0 == 0x03) {//s0
                self.rtmpStatus = SGRtmpSessionStatusHandshake1;
                if (data.length > 1) {//后面还有数据,但不确定长度
                    data = [data subdataWithRange:NSMakeRange(1, data.length -1)];
                    self.handshake = data.mutableCopy;
                }else{
                    break;
                }
            }else{
                NSLog(@"握手失败");
                break;
            }
        }
        case SGRtmpSessionStatusHandshake1:{
            
            if (self.handshake.length >= kRTMPSignatureSize) {//s1
                [self handshake1];
                
                if (self.handshake.length > kRTMPSignatureSize) {//>
                    NSData *subData = [self.handshake subdataWithRange:NSMakeRange(kRTMPSignatureSize, self.handshake.length - kRTMPSignatureSize)];
                    self.handshake = subData.mutableCopy;
                }else{// =
                    self.handshake = [NSMutableData data];
                    break;
                }
            }else{//<
                break;
            }
        }
            
        case SGRtmpSessionStatusHandshake2:{//s2
            if (data.length >= kRTMPSignatureSize) {
                NSLog(@"握手完成");
                self.rtmpStatus = SGRtmpSessionStatusHandshakeComplete;
 
            }
            break;
        }
        default:

            break;
    }
}

- (void)writeData:(NSData *)data{
    if (data.length == 0) {
        return;
    }
    [self.session writeData:data];

}
@end


握手部分相对来说比较恶心,虽然很简单,处理起来很麻烦,仔细打上断点试试.主要是解析服务器端数据比较绕,建议先从3073字节的那种情况做处理,其他的俩种情况,自己可以根据思路写一套逻辑.握手看明白了,后面就简单多了.

下面附上测试代码:

@interface ViewController ()
@property (nonatomic,strong) SGRtmpSession *session;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (SGRtmpSession *)session{
    if (_session == nil) {
        _session = [[SGRtmpSession alloc] init];
        _session.url = @"rtmp://192.168.1.106/live/2005";
    }
    return _session;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self.session connect];
}

输出结果:

2016-08-02 22:23:15.764 SGRtmpPublisher[650:225659] scheme:rtmp
2016-08-02 22:23:15.766 SGRtmpPublisher[650:225659] host:192.168.1.106
2016-08-02 22:23:15.766 SGRtmpPublisher[650:225659] app:live
2016-08-02 22:23:15.766 SGRtmpPublisher[650:225659] playPath:2005
2016-08-02 22:23:15.766 SGRtmpPublisher[650:225659] port:0
2016-08-02 22:23:15.884 SGRtmpPublisher[650:225659] 连接成功
2016-08-02 22:23:15.885 SGRtmpPublisher[650:225659] rtmpStatus-----1
2016-08-02 22:23:15.886 SGRtmpPublisher[650:225659] 可以发送字节
2016-08-02 22:23:15.886 SGRtmpPublisher[650:225659] rtmpStatus-----2
2016-08-02 22:23:15.886 SGRtmpPublisher[650:225659] 可以发送字节
2016-08-02 22:23:15.898 SGRtmpPublisher[650:225659] 有字节可读
2016-08-02 22:23:15.898 SGRtmpPublisher[650:225659] 1537
2016-08-02 22:23:15.899 SGRtmpPublisher[650:225659] rtmpStatus-----3
2016-08-02 22:23:15.899 SGRtmpPublisher[650:225659] rtmpStatus-----4
2016-08-02 22:23:15.899 SGRtmpPublisher[650:225659] 可以发送字节
2016-08-02 22:23:15.900 SGRtmpPublisher[650:225659] 有字节可读
2016-08-02 22:23:15.900 SGRtmpPublisher[650:225659] 1536
2016-08-02 22:23:15.900 SGRtmpPublisher[650:225659] 握手完成
2016-08-02 22:23:15.900 SGRtmpPublisher[650:225659] rtmpStatus-----5

上一篇下一篇

猜你喜欢

热点阅读