summaryrefslogtreecommitdiff
path: root/SmartDeviceLink/SDLRTPH264Packetizer.m
blob: 8c14c6b9e5a436612b32f8450ba23a40b9292d2f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//
//  SDLRTPH264Packetizer.m
//  SmartDeviceLink-iOS
//
//  Created by Sho Amano on 4/11/17.
//  Copyright © 2017 Xevo Inc. All rights reserved.
//

/*
 * Note for testing.
 * The RTP stream generated by this packetizer can be tested with GStreamer (1.4 or later).
 * Assuming that "VideoStreamPort" is configured as 5050 in smartDeviceLink.ini, here is the
 * GStreamer pipeline that receives the stream, decode it and render it:
 *
 * $ gst-launch-1.0 souphttpsrc location=http://127.0.0.1:5050 ! "application/x-rtp-stream" ! rtpstreamdepay ! "application/x-rtp,media=(string)video,clock-rate=90000,encoding-name=(string)H264" ! rtph264depay ! "video/x-h264, stream-format=(string)avc, alignment=(string)au" ! avdec_h264 ! autovideosink sync=false
 */

#import <Foundation/Foundation.h>
#import "SDLRTPH264Packetizer.h"
#import "SDLLogMacros.h"

const NSUInteger FRAME_LENGTH_LEN = 2;
const NSUInteger MAX_RTP_PACKET_SIZE = 65535;   // because length field is two bytes (RFC 4571)
const NSUInteger RTP_HEADER_LEN = 12;
const UInt8 DEFAULT_PAYLOAD_TYPE = 96;
const NSUInteger FU_INDICATOR_LEN = 1;
const NSUInteger FU_HEADER_LEN = 1;
const UInt8 TYPE_FU_A = 0x1C;
const NSUInteger CLOCK_RATE = 90000;    // we use 90 kHz clock rate ([5.1] in RFC 6184)

// write 2-byte value in network byte order
static inline void writeShortInNBO(UInt8 *p, UInt16 value) {
    p[0] = (value >> 8) & 0xFF;
    p[1] = value & 0xFF;
}

// write 4-byte value in network byte order
static inline void writeLongInNBO(UInt8 *p, UInt32 value) {
    p[0] = (value >> 24) & 0xFF;
    p[1] = (value >> 16) & 0xFF;
    p[2] = (value >> 8) & 0xFF;
    p[3] = value & 0xFF;
}

NS_ASSUME_NONNULL_BEGIN

@interface SDLRTPH264Packetizer ()
@property (assign, nonatomic) UInt32 initialTimestamp;
@property (assign, nonatomic) UInt16 sequenceNum;
@end

@implementation SDLRTPH264Packetizer

- (instancetype)init {
    if (self = [super init]) {
        _payloadType = DEFAULT_PAYLOAD_TYPE;
        // initial value of the sequence number and timestamp should be random ([5.1] in RFC3550)
        _initialTimestamp = arc4random_uniform(UINT32_MAX);
        _sequenceNum = (UInt16)arc4random_uniform(UINT16_MAX);
        _ssrc = arc4random_uniform(UINT32_MAX);
    }

    return self;
}

- (void)setPayloadType:(UInt8)payloadType {
    if (payloadType <= 127) {
        _payloadType = payloadType;
    } else {
        _payloadType = DEFAULT_PAYLOAD_TYPE;
    }
}

- (nullable NSArray<NSData *> *)createPackets:(NSArray<NSData *> *)nalUnits
                        presentationTimestamp:(double)presentationTimestamp {
    NSMutableArray *rtpFrames = [NSMutableArray array];
    NSUInteger nalUnitsCount = [nalUnits count];

    for (NSUInteger i = 0; i < nalUnitsCount; i++) {
        NSData *nalUnit = nalUnits[i];
        NSUInteger nalUnitLength = [nalUnit length];
        BOOL isLast = (i + 1) == nalUnitsCount;

        if (RTP_HEADER_LEN + nalUnitLength > MAX_RTP_PACKET_SIZE) {
            // Split into multiple Fragmentation Units ([5.8] in RFC 6184)
            UInt8 firstByte;
            [nalUnit getBytes:&firstByte length:1];
            BOOL isFirstFragment = YES;
            BOOL isLastFragment = NO;
            NSUInteger offset = 1;  // we have already read the first byte

            while (offset < nalUnitLength) {
                NSUInteger payloadLength = MAX_RTP_PACKET_SIZE - (RTP_HEADER_LEN + FU_INDICATOR_LEN + FU_HEADER_LEN);
                if (nalUnitLength - offset <= payloadLength) {
                    payloadLength = nalUnitLength - offset;
                    isLastFragment = YES;
                }
                NSUInteger packetSize = RTP_HEADER_LEN + FU_INDICATOR_LEN + FU_HEADER_LEN + payloadLength;
                NSUInteger frameSize = FRAME_LENGTH_LEN + packetSize;

                UInt8 *buffer = malloc(frameSize);
                if (buffer == NULL) {
                    SDLLogE(@"malloc() error");
                    return nil;
                }
                UInt8 *p = buffer;

                p += [self writeFrameHeader:p packetSize:packetSize];
                p += [self writeRTPHeader:p marker:isLast pts:presentationTimestamp];

                // FU indicator
                *p++ = (firstByte & 0xE0) | TYPE_FU_A;
                // FU header
                *p++ = (isFirstFragment ? 0x80 : isLastFragment ? 0x40 : 0) | (firstByte & 0x1F);
                // FU payload
                [nalUnit getBytes:p range:NSMakeRange(offset, payloadLength)];
                offset += payloadLength;

                NSData *rtpFrame = [NSData dataWithBytesNoCopy:buffer length:frameSize];
                [rtpFrames addObject:rtpFrame];

                isFirstFragment = NO;
            }
        } else {
            // Use Single NAL Unit Packet ([5.6] in RFC 6184)
            NSUInteger packetSize = RTP_HEADER_LEN + nalUnitLength;
            NSUInteger frameSize = FRAME_LENGTH_LEN + packetSize;

            UInt8 *buffer = malloc(frameSize);
            if (buffer == NULL) {
                SDLLogE(@"malloc() error");
                return nil;
            }
            UInt8 *p = buffer;

            p += [self writeFrameHeader:p packetSize:packetSize];
            p += [self writeRTPHeader:p marker:isLast pts:presentationTimestamp];
            [nalUnit getBytes:p length:nalUnitLength];

            NSData *rtpFrame = [NSData dataWithBytesNoCopy:buffer length:frameSize];
            [rtpFrames addObject:rtpFrame];
        }
    }

    return rtpFrames;
}

/**
 * Write RTP Frame header (length field) to supplied buffer.
 *
 * @param p the buffer in which a header is written.
 * @param packetSize size of a RTP packet that follows to this frame header.
 *
 * @return number of bytes written, which is always 2.
 */
- (NSUInteger)writeFrameHeader:(UInt8 *)p packetSize:(NSUInteger)packetSize {
    NSAssert(packetSize <= MAX_RTP_PACKET_SIZE, @"RTP packet is too big");
    writeShortInNBO(p, (UInt16)packetSize);
    return FRAME_LENGTH_LEN;
}

/**
 * Write RTP header to supplied buffer.
 *
 * @param p the buffer in which a header is written.
 * @param isLast whether this is the last packet of an access unit.
 * @param pts presentation timestamp in seconds.
 *
 * @return number of bytes written, which is always 12.
 */
- (NSUInteger)writeRTPHeader:(UInt8 *)p marker:(BOOL)isLast pts:(double)pts {
    UInt32 ptsIn90kHz = pts * CLOCK_RATE;

    // Version = 2, Padding = 0, Extension = 0, CSRC count = 0
    p[0] = 0x80;
    // Marker = isLast, Payload type = self.payloadType
    p[1] = (isLast ? 0x80 : 0) | (self.payloadType & 0x7F);
    writeShortInNBO(p + 2, self.sequenceNum);
    writeLongInNBO(p + 4, self.initialTimestamp + ptsIn90kHz);
    writeLongInNBO(p + 8, self.ssrc);

    self.sequenceNum++;
    return RTP_HEADER_LEN;
}

@end

NS_ASSUME_NONNULL_END