summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.cc
blob: 95dbaf364c00bfe387789a1f983c046d65781822 (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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/*
 *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */
#include "modules/rtp_rtcp/source/rtp_packetizer_av1.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>

#include "api/array_view.h"
#include "api/video/video_frame_type.h"
#include "modules/rtp_rtcp/source/leb128.h"
#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
#include "rtc_base/byte_buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"

namespace webrtc {
namespace {
constexpr int kAggregationHeaderSize = 1;
// when there are 3 or less OBU (fragments) in a packet, size of the last one
// can be omited.
constexpr int kMaxNumObusToOmitSize = 3;
constexpr uint8_t kObuSizePresentBit = 0b0'0000'010;
constexpr int kObuTypeSequenceHeader = 1;
constexpr int kObuTypeTemporalDelimiter = 2;
constexpr int kObuTypeTileList = 8;
constexpr int kObuTypePadding = 15;

bool ObuHasExtension(uint8_t obu_header) {
  return obu_header & 0b0'0000'100;
}

bool ObuHasSize(uint8_t obu_header) {
  return obu_header & kObuSizePresentBit;
}

int ObuType(uint8_t obu_header) {
  return (obu_header & 0b0'1111'000) >> 3;
}

// Given `remaining_bytes` free bytes left in a packet, returns max size of an
// OBU fragment that can fit into the packet.
// i.e. MaxFragmentSize + Leb128Size(MaxFragmentSize) <= remaining_bytes.
int MaxFragmentSize(int remaining_bytes) {
  if (remaining_bytes <= 1) {
    return 0;
  }
  for (int i = 1;; ++i) {
    if (remaining_bytes < (1 << 7 * i) + i) {
      return remaining_bytes - i;
    }
  }
}

}  // namespace

RtpPacketizerAv1::RtpPacketizerAv1(rtc::ArrayView<const uint8_t> payload,
                                   RtpPacketizer::PayloadSizeLimits limits,
                                   VideoFrameType frame_type,
                                   bool is_last_frame_in_picture)
    : frame_type_(frame_type),
      obus_(ParseObus(payload)),
      packets_(Packetize(obus_, limits)),
      is_last_frame_in_picture_(is_last_frame_in_picture) {}

std::vector<RtpPacketizerAv1::Obu> RtpPacketizerAv1::ParseObus(
    rtc::ArrayView<const uint8_t> payload) {
  std::vector<Obu> result;
  rtc::ByteBufferReader payload_reader(
      reinterpret_cast<const char*>(payload.data()), payload.size());
  while (payload_reader.Length() > 0) {
    Obu obu;
    payload_reader.ReadUInt8(&obu.header);
    obu.size = 1;
    if (ObuHasExtension(obu.header)) {
      if (payload_reader.Length() == 0) {
        RTC_DLOG(LS_ERROR) << "Malformed AV1 input: expected extension_header, "
                              "no more bytes in the buffer. Offset: "
                           << (payload.size() - payload_reader.Length());
        return {};
      }
      payload_reader.ReadUInt8(&obu.extension_header);
      ++obu.size;
    }
    if (!ObuHasSize(obu.header)) {
      obu.payload = rtc::MakeArrayView(
          reinterpret_cast<const uint8_t*>(payload_reader.Data()),
          payload_reader.Length());
      payload_reader.Consume(payload_reader.Length());
    } else {
      uint64_t size = 0;
      if (!payload_reader.ReadUVarint(&size) ||
          size > payload_reader.Length()) {
        RTC_DLOG(LS_ERROR) << "Malformed AV1 input: declared size " << size
                           << " is larger than remaining buffer size "
                           << payload_reader.Length();
        return {};
      }
      obu.payload = rtc::MakeArrayView(
          reinterpret_cast<const uint8_t*>(payload_reader.Data()), size);
      payload_reader.Consume(size);
    }
    obu.size += obu.payload.size();
    // Skip obus that shouldn't be transfered over rtp.
    int obu_type = ObuType(obu.header);
    if (obu_type != kObuTypeTemporalDelimiter &&  //
        obu_type != kObuTypeTileList &&           //
        obu_type != kObuTypePadding) {
      result.push_back(obu);
    }
  }
  return result;
}

int RtpPacketizerAv1::AdditionalBytesForPreviousObuElement(
    const Packet& packet) {
  if (packet.packet_size == 0) {
    // Packet is still empty => no last OBU element, no need to reserve space
    // for it.
    return 0;
  }
  if (packet.num_obu_elements > kMaxNumObusToOmitSize) {
    // There is so many obu elements in the packet, all of them must be
    // prepended with the length field. That imply space for the length of the
    // last obu element is already reserved.
    return 0;
  }
  // No space was reserved for length field of the last OBU element, but that
  // element becoming non-last, so it now requires explicit length field.
  // Calculate how many bytes are needed to store the length in leb128 format.
  return Leb128Size(packet.last_obu_size);
}

std::vector<RtpPacketizerAv1::Packet> RtpPacketizerAv1::Packetize(
    rtc::ArrayView<const Obu> obus,
    PayloadSizeLimits limits) {
  std::vector<Packet> packets;
  if (obus.empty()) {
    return packets;
  }
  // Ignore certian edge cases where packets should be very small. They are
  // inpractical but adds complexity to handle.
  if (limits.max_payload_len - limits.last_packet_reduction_len < 3 ||
      limits.max_payload_len - limits.first_packet_reduction_len < 3) {
    RTC_DLOG(LS_ERROR) << "Failed to packetize AV1 frame: requested packet "
                          "size is unreasonable small.";
    return packets;
  }
  // Aggregation header is present in all packets.
  limits.max_payload_len -= kAggregationHeaderSize;

  // Assemble packets. Push to current packet as much as it can hold before
  // considering next one. That would normally cause uneven distribution across
  // packets, specifically last one would be generally smaller.
  packets.emplace_back(/*first_obu_index=*/0);
  int packet_remaining_bytes =
      limits.max_payload_len - limits.first_packet_reduction_len;
  for (size_t obu_index = 0; obu_index < obus.size(); ++obu_index) {
    const bool is_last_obu = obu_index == obus.size() - 1;
    const Obu& obu = obus[obu_index];

    // Putting `obu` into the last packet would make last obu element stored in
    // that packet not last. All not last OBU elements must be prepend with the
    // element length. AdditionalBytesForPreviousObuElement calculates how many
    // bytes are needed to store that length.
    int previous_obu_extra_size =
        AdditionalBytesForPreviousObuElement(packets.back());
    int min_required_size =
        packets.back().num_obu_elements >= kMaxNumObusToOmitSize ? 2 : 1;
    if (packet_remaining_bytes < previous_obu_extra_size + min_required_size) {
      // Start a new packet.
      packets.emplace_back(/*first_obu_index=*/obu_index);
      packet_remaining_bytes = limits.max_payload_len;
      previous_obu_extra_size = 0;
    }
    Packet& packet = packets.back();
    // Start inserting current obu into the packet.
    packet.packet_size += previous_obu_extra_size;
    packet_remaining_bytes -= previous_obu_extra_size;
    packet.num_obu_elements++;

    bool must_write_obu_element_size =
        packet.num_obu_elements > kMaxNumObusToOmitSize;
    // Can fit all of the obu into the packet?
    int required_bytes = obu.size;
    if (must_write_obu_element_size) {
      required_bytes += Leb128Size(obu.size);
    }
    int available_bytes = packet_remaining_bytes;
    if (is_last_obu) {
      // If this packet would be the last packet, available size is smaller.
      if (packets.size() == 1) {
        available_bytes += limits.first_packet_reduction_len;
        available_bytes -= limits.single_packet_reduction_len;
      } else {
        available_bytes -= limits.last_packet_reduction_len;
      }
    }
    if (required_bytes <= available_bytes) {
      // Insert the obu into the packet unfragmented.
      packet.last_obu_size = obu.size;
      packet.packet_size += required_bytes;
      packet_remaining_bytes -= required_bytes;
      continue;
    }

    // Fragment the obu.
    int max_first_fragment_size = must_write_obu_element_size
                                      ? MaxFragmentSize(packet_remaining_bytes)
                                      : packet_remaining_bytes;
    // Because available_bytes might be different than
    // packet_remaining_bytes it might happen that max_first_fragment_size >=
    // obu.size. Also, since checks above verified `obu` should not be put
    // completely into the `packet`, leave at least 1 byte for later packet.
    int first_fragment_size = std::min(obu.size - 1, max_first_fragment_size);
    if (first_fragment_size == 0) {
      // Rather than writing 0-size element at the tail of the packet,
      // 'uninsert' the `obu` from the `packet`.
      packet.num_obu_elements--;
      packet.packet_size -= previous_obu_extra_size;
    } else {
      packet.packet_size += first_fragment_size;
      if (must_write_obu_element_size) {
        packet.packet_size += Leb128Size(first_fragment_size);
      }
      packet.last_obu_size = first_fragment_size;
    }

    // Add middle fragments that occupy all of the packet.
    // These are easy because
    // - one obu per packet imply no need to store the size of the obu.
    // - this packets are nor the first nor the last packets of the frame, so
    // packet capacity is always limits.max_payload_len.
    int obu_offset;
    for (obu_offset = first_fragment_size;
         obu_offset + limits.max_payload_len < obu.size;
         obu_offset += limits.max_payload_len) {
      packets.emplace_back(/*first_obu_index=*/obu_index);
      Packet& packet = packets.back();
      packet.num_obu_elements = 1;
      packet.first_obu_offset = obu_offset;
      int middle_fragment_size = limits.max_payload_len;
      packet.last_obu_size = middle_fragment_size;
      packet.packet_size = middle_fragment_size;
    }

    // Add the last fragment of the obu.
    int last_fragment_size = obu.size - obu_offset;
    // Check for corner case where last fragment of the last obu is too large
    // to fit into last packet, but may fully fit into semi-last packet.
    if (is_last_obu &&
        last_fragment_size >
            limits.max_payload_len - limits.last_packet_reduction_len) {
      // Split last fragments into two.
      RTC_DCHECK_GE(last_fragment_size, 2);
      // Try to even packet sizes rather than payload sizes across the last
      // two packets.
      int semi_last_fragment_size =
          (last_fragment_size + limits.last_packet_reduction_len) / 2;
      // But leave at least one payload byte for the last packet to avoid
      // weird scenarios where size of the fragment is zero and rtp payload has
      // nothing except for an aggregation header.
      if (semi_last_fragment_size >= last_fragment_size) {
        semi_last_fragment_size = last_fragment_size - 1;
      }
      last_fragment_size -= semi_last_fragment_size;

      packets.emplace_back(/*first_obu_index=*/obu_index);
      Packet& packet = packets.back();
      packet.num_obu_elements = 1;
      packet.first_obu_offset = obu_offset;
      packet.last_obu_size = semi_last_fragment_size;
      packet.packet_size = semi_last_fragment_size;
      obu_offset += semi_last_fragment_size;
    }
    packets.emplace_back(/*first_obu_index=*/obu_index);
    Packet& last_packet = packets.back();
    last_packet.num_obu_elements = 1;
    last_packet.first_obu_offset = obu_offset;
    last_packet.last_obu_size = last_fragment_size;
    last_packet.packet_size = last_fragment_size;
    packet_remaining_bytes = limits.max_payload_len - last_fragment_size;
  }
  return packets;
}

uint8_t RtpPacketizerAv1::AggregationHeader() const {
  const Packet& packet = packets_[packet_index_];
  uint8_t aggregation_header = 0;

  // Set Z flag: first obu element is continuation of the previous OBU.
  bool first_obu_element_is_fragment = packet.first_obu_offset > 0;
  if (first_obu_element_is_fragment)
    aggregation_header |= (1 << 7);

  // Set Y flag: last obu element will be continuated in the next packet.
  int last_obu_offset =
      packet.num_obu_elements == 1 ? packet.first_obu_offset : 0;
  bool last_obu_is_fragment =
      last_obu_offset + packet.last_obu_size <
      obus_[packet.first_obu + packet.num_obu_elements - 1].size;
  if (last_obu_is_fragment)
    aggregation_header |= (1 << 6);

  // Set W field: number of obu elements in the packet (when not too large).
  if (packet.num_obu_elements <= kMaxNumObusToOmitSize)
    aggregation_header |= packet.num_obu_elements << 4;

  // Set N flag: beginning of a new coded video sequence.
  // Encoder may produce key frame without a sequence header, thus double check
  // incoming frame includes the sequence header. Since Temporal delimiter is
  // already filtered out, sequence header should be the first obu when present.
  if (frame_type_ == VideoFrameType::kVideoFrameKey && packet_index_ == 0 &&
      ObuType(obus_.front().header) == kObuTypeSequenceHeader) {
    aggregation_header |= (1 << 3);
  }
  return aggregation_header;
}

bool RtpPacketizerAv1::NextPacket(RtpPacketToSend* packet) {
  if (packet_index_ >= packets_.size()) {
    return false;
  }
  const Packet& next_packet = packets_[packet_index_];

  RTC_DCHECK_GT(next_packet.num_obu_elements, 0);
  RTC_DCHECK_LT(next_packet.first_obu_offset,
                obus_[next_packet.first_obu].size);
  RTC_DCHECK_LE(
      next_packet.last_obu_size,
      obus_[next_packet.first_obu + next_packet.num_obu_elements - 1].size);

  uint8_t* const rtp_payload =
      packet->AllocatePayload(kAggregationHeaderSize + next_packet.packet_size);
  uint8_t* write_at = rtp_payload;

  *write_at++ = AggregationHeader();

  int obu_offset = next_packet.first_obu_offset;
  // Store all OBU elements except the last one.
  for (int i = 0; i < next_packet.num_obu_elements - 1; ++i) {
    const Obu& obu = obus_[next_packet.first_obu + i];
    size_t fragment_size = obu.size - obu_offset;
    write_at += WriteLeb128(fragment_size, write_at);
    if (obu_offset == 0) {
      *write_at++ = obu.header & ~kObuSizePresentBit;
    }
    if (obu_offset <= 1 && ObuHasExtension(obu.header)) {
      *write_at++ = obu.extension_header;
    }
    int payload_offset =
        std::max(0, obu_offset - (ObuHasExtension(obu.header) ? 2 : 1));
    size_t payload_size = obu.payload.size() - payload_offset;
    if (!obu.payload.empty() && payload_size > 0) {
      memcpy(write_at, obu.payload.data() + payload_offset, payload_size);
    }
    write_at += payload_size;
    // All obus are stored from the beginning, except, may be, the first one.
    obu_offset = 0;
  }
  // Store the last OBU element.
  const Obu& last_obu =
      obus_[next_packet.first_obu + next_packet.num_obu_elements - 1];
  int fragment_size = next_packet.last_obu_size;
  RTC_DCHECK_GT(fragment_size, 0);
  if (next_packet.num_obu_elements > kMaxNumObusToOmitSize) {
    write_at += WriteLeb128(fragment_size, write_at);
  }
  if (obu_offset == 0 && fragment_size > 0) {
    *write_at++ = last_obu.header & ~kObuSizePresentBit;
    --fragment_size;
  }
  if (obu_offset <= 1 && ObuHasExtension(last_obu.header) &&
      fragment_size > 0) {
    *write_at++ = last_obu.extension_header;
    --fragment_size;
  }
  RTC_DCHECK_EQ(write_at - rtp_payload + fragment_size,
                kAggregationHeaderSize + next_packet.packet_size);
  int payload_offset =
      std::max(0, obu_offset - (ObuHasExtension(last_obu.header) ? 2 : 1));
  memcpy(write_at, last_obu.payload.data() + payload_offset, fragment_size);
  write_at += fragment_size;

  RTC_DCHECK_EQ(write_at - rtp_payload,
                kAggregationHeaderSize + next_packet.packet_size);

  ++packet_index_;
  bool is_last_packet_in_frame = packet_index_ == packets_.size();
  packet->SetMarker(is_last_packet_in_frame && is_last_frame_in_picture_);
  return true;
}

}  // namespace webrtc