2021-06-04 11:21:18 +00:00
|
|
|
#include "h26x.hh"
|
2021-06-01 07:54:23 +00:00
|
|
|
|
2022-09-07 11:10:24 +00:00
|
|
|
#include "socket.hh"
|
2022-08-17 10:57:49 +00:00
|
|
|
|
2022-09-07 11:10:24 +00:00
|
|
|
#include "rtp.hh"
|
|
|
|
#include "frame_queue.hh"
|
2022-08-17 10:57:49 +00:00
|
|
|
#include "debug.hh"
|
2019-09-05 07:16:48 +00:00
|
|
|
|
2021-06-02 07:25:40 +00:00
|
|
|
|
2019-03-30 10:22:57 +00:00
|
|
|
#include <cstdint>
|
|
|
|
#include <cstring>
|
2019-08-12 06:25:17 +00:00
|
|
|
#include <iostream>
|
2019-09-05 07:16:48 +00:00
|
|
|
#include <unordered_map>
|
|
|
|
#include <queue>
|
2023-06-01 08:20:56 +00:00
|
|
|
#include <algorithm>
|
2019-03-30 10:22:57 +00:00
|
|
|
|
|
|
|
|
2023-05-05 09:34:59 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
#include <ws2def.h>
|
|
|
|
#include <ws2ipdef.h>
|
|
|
|
#else
|
|
|
|
#include <netinet/in.h>
|
2021-06-01 07:54:23 +00:00
|
|
|
#include <sys/socket.h>
|
|
|
|
#endif
|
|
|
|
|
2019-09-11 08:12:10 +00:00
|
|
|
|
2019-09-11 07:06:52 +00:00
|
|
|
#define PTR_DIFF(a, b) ((ptrdiff_t)((char *)(a) - (char *)(b)))
|
|
|
|
|
2022-08-31 13:22:21 +00:00
|
|
|
// see https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
|
2019-09-11 07:06:52 +00:00
|
|
|
#define haszero64_le(v) (((v) - 0x0101010101010101) & ~(v) & 0x8080808080808080UL)
|
|
|
|
#define haszero32_le(v) (((v) - 0x01010101) & ~(v) & 0x80808080UL)
|
|
|
|
|
|
|
|
#define haszero64_be(v) (((v) - 0x1010101010101010) & ~(v) & 0x0808080808080808UL)
|
|
|
|
#define haszero32_be(v) (((v) - 0x10101010) & ~(v) & 0x08080808UL)
|
|
|
|
|
2020-01-09 08:10:58 +00:00
|
|
|
#ifndef __LITTLE_ENDIAN
|
|
|
|
#define __LITTLE_ENDIAN 1337
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef __BYTE_ORDER
|
|
|
|
#define __BYTE_ORDER __LITTLE_ENDIAN
|
|
|
|
#endif
|
|
|
|
|
2021-09-15 11:01:24 +00:00
|
|
|
constexpr int GARBAGE_COLLECTION_INTERVAL_MS = 100;
|
|
|
|
|
2022-08-18 11:34:58 +00:00
|
|
|
// any value less than 30 minutes is ok here, since that is how long it takes to go through all timestamps
|
|
|
|
constexpr int TIME_TO_KEEP_TRACK_OF_PREVIOUS_FRAMES_MS = 5000;
|
|
|
|
|
2023-06-13 10:59:38 +00:00
|
|
|
// how many RTP timestamps get saved for duplicate detection
|
|
|
|
// there is 90 000 timestamps in one second -> 5 sec is 450 000
|
|
|
|
constexpr int RECEIVED_FRAMES = 450000;
|
2023-06-01 08:20:56 +00:00
|
|
|
|
2022-08-31 13:22:21 +00:00
|
|
|
static inline uint8_t determine_start_prefix_precense(uint32_t value, bool& additional_byte)
|
2019-09-11 07:06:52 +00:00
|
|
|
{
|
2022-01-06 02:18:10 +00:00
|
|
|
additional_byte = false;
|
2019-09-11 07:06:52 +00:00
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
2022-08-31 13:22:21 +00:00
|
|
|
uint16_t cur_ls = (value >> 16) & 0xffff;
|
|
|
|
uint16_t cur_ms = (value >> 0) & 0xffff;
|
2019-09-11 07:06:52 +00:00
|
|
|
|
2022-08-31 13:22:21 +00:00
|
|
|
// zeros in more significant bytes
|
|
|
|
bool ms4z = (cur_ms == 0);
|
|
|
|
bool ms2z = (((cur_ms >> 8) & 0xff) == 0);
|
2019-09-11 07:06:52 +00:00
|
|
|
|
2022-08-31 13:22:21 +00:00
|
|
|
// possible start code end in less significant bytes
|
|
|
|
bool ls2s = ((cur_ls & 0xff) == 0x01);
|
|
|
|
bool ls4s = (cur_ls == 0x0100);
|
|
|
|
|
|
|
|
#else
|
|
|
|
uint16_t cur_ls = (value >> 0) & 0xffff;
|
|
|
|
uint16_t cur_ms = (value >> 16) & 0xffff;
|
|
|
|
|
|
|
|
bool ms4z = ( cur_ms == 0);
|
|
|
|
bool ms2z = ((cur_ms & 0xff) == 0);
|
|
|
|
bool ls2s = (((cur_ls >> 8) & 0xff) == 0x01);
|
|
|
|
bool ls4s = ( cur_ls == 0x0001);
|
|
|
|
|
2019-09-11 07:06:52 +00:00
|
|
|
#endif
|
|
|
|
|
2022-08-31 13:22:21 +00:00
|
|
|
if (ms4z) {
|
2019-09-11 07:06:52 +00:00
|
|
|
/* 0x00000001 */
|
2022-08-31 13:22:21 +00:00
|
|
|
if (ls4s)
|
2019-09-11 07:06:52 +00:00
|
|
|
return 4;
|
|
|
|
|
|
|
|
/* "value" definitely has a start code (0x000001XX), but at this
|
|
|
|
* point we can't know for sure whether it's 3 or 4 bytes long.
|
|
|
|
*
|
|
|
|
* Return 5 to indicate that start length could not be determined
|
|
|
|
* and that caller must check previous dword's last byte for 0x00 */
|
2022-08-31 13:22:21 +00:00
|
|
|
if (ls2s)
|
2019-09-11 07:06:52 +00:00
|
|
|
return 5;
|
2022-08-31 13:22:21 +00:00
|
|
|
} else if (ms2z && ls4s) {
|
2019-09-11 07:06:52 +00:00
|
|
|
/* 0xXX000001 */
|
2022-01-06 02:18:10 +00:00
|
|
|
additional_byte = true;
|
2019-09-11 07:06:52 +00:00
|
|
|
return 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-08-23 06:47:07 +00:00
|
|
|
uvgrtp::formats::h26x::h26x(std::shared_ptr<uvgrtp::socket> socket, std::shared_ptr<uvgrtp::rtp> rtp, int rce_flags) :
|
|
|
|
media(socket, rtp, rce_flags),
|
2021-09-15 10:08:09 +00:00
|
|
|
queued_(),
|
|
|
|
frames_(),
|
2023-06-02 09:49:09 +00:00
|
|
|
received_frames_(),
|
2023-06-13 10:59:38 +00:00
|
|
|
received_info_(),
|
2022-06-07 12:35:49 +00:00
|
|
|
fragments_(UINT16_MAX + 1, nullptr),
|
2022-08-18 11:34:58 +00:00
|
|
|
dropped_ts_(),
|
|
|
|
completed_ts_(),
|
2021-09-15 11:01:24 +00:00
|
|
|
rtp_ctx_(rtp),
|
2022-06-06 14:13:17 +00:00
|
|
|
last_garbage_collection_(uvgrtp::clock::hrc::now()),
|
2023-05-05 10:19:37 +00:00
|
|
|
discard_until_key_frame_(true)
|
2021-09-15 10:08:09 +00:00
|
|
|
{}
|
2021-06-14 09:31:37 +00:00
|
|
|
|
|
|
|
uvgrtp::formats::h26x::~h26x()
|
2022-03-03 11:28:10 +00:00
|
|
|
{
|
|
|
|
for (auto& frame : queued_)
|
|
|
|
{
|
2022-08-31 07:06:37 +00:00
|
|
|
(void)uvgrtp::frame::dealloc_frame(frame);
|
2022-03-03 11:28:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
queued_.clear();
|
2022-08-31 07:06:37 +00:00
|
|
|
|
|
|
|
for (auto& fragment : fragments_)
|
|
|
|
{
|
|
|
|
if (fragment != nullptr)
|
|
|
|
{
|
|
|
|
(void)uvgrtp::frame::dealloc_frame(fragment);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fragments_.clear();
|
2022-03-03 11:28:10 +00:00
|
|
|
}
|
2021-06-14 09:31:37 +00:00
|
|
|
|
2020-01-07 07:54:33 +00:00
|
|
|
/* NOTE: the area 0 - len (ie data[0] - data[len - 1]) must be addressable
|
2020-10-06 03:20:22 +00:00
|
|
|
* Do not add offset to "data" ptr before passing it to find_h26x_start_code()! */
|
2021-02-19 01:13:43 +00:00
|
|
|
ssize_t uvgrtp::formats::h26x::find_h26x_start_code(
|
2020-09-08 05:19:38 +00:00
|
|
|
uint8_t *data,
|
|
|
|
size_t len,
|
|
|
|
size_t offset,
|
2022-08-31 13:22:21 +00:00
|
|
|
uint8_t& start_len)
|
2019-03-30 10:22:57 +00:00
|
|
|
{
|
2022-08-31 13:22:21 +00:00
|
|
|
if (data == nullptr || len < offset || len < 1)
|
|
|
|
{
|
|
|
|
UVG_LOG_WARN("Invalid parameter found for start code lookup");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool prev_had_zero = false;
|
|
|
|
bool cur_has_zero = false;
|
|
|
|
size_t pos = offset;
|
|
|
|
size_t last_byte_position = len - (len % 8) - 1;
|
2019-09-11 07:06:52 +00:00
|
|
|
|
|
|
|
/* We can get rid of the bounds check when looping through
|
|
|
|
* non-zero 8 byte chunks by setting the last byte to zero.
|
|
|
|
*
|
|
|
|
* This added zero will make the last 8 byte zero check to fail
|
2020-02-04 10:07:05 +00:00
|
|
|
* and when we get out of the loop we can check if we've reached the end */
|
2022-08-31 13:22:21 +00:00
|
|
|
uint8_t temp_last_byte = data[last_byte_position];
|
|
|
|
data[last_byte_position] = 0;
|
|
|
|
|
|
|
|
uint32_t prev_value32 = UINT32_MAX;
|
|
|
|
uint32_t cur_value32 = UINT32_MAX;
|
|
|
|
|
|
|
|
uint64_t prefetch64 = UINT64_MAX;
|
2019-09-11 07:06:52 +00:00
|
|
|
|
2022-11-23 11:43:00 +00:00
|
|
|
while (pos + 4 <= len) {
|
2019-09-11 07:06:52 +00:00
|
|
|
|
2022-08-31 13:22:21 +00:00
|
|
|
if (!prev_had_zero)
|
2022-08-25 09:11:12 +00:00
|
|
|
{
|
2022-08-31 13:22:21 +00:00
|
|
|
// since we know that start code prefix has zeros, we find the next dword that has zeros
|
2022-11-23 08:25:35 +00:00
|
|
|
while (!cur_has_zero && pos + 8 <= len)
|
2022-08-31 13:22:21 +00:00
|
|
|
{
|
|
|
|
prefetch64 = *(uint64_t*)(data + pos);
|
2019-09-11 07:06:52 +00:00
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
2022-08-31 13:22:21 +00:00
|
|
|
cur_has_zero = haszero64_le(prefetch64);
|
2019-09-11 07:06:52 +00:00
|
|
|
#else
|
2022-08-31 13:22:21 +00:00
|
|
|
cur_has_zero = haszero64_be(prefetch64);
|
2019-09-11 07:06:52 +00:00
|
|
|
#endif
|
2022-08-31 13:22:21 +00:00
|
|
|
if (!cur_has_zero)
|
|
|
|
{
|
|
|
|
pos += 8;
|
|
|
|
}
|
2022-08-25 09:11:12 +00:00
|
|
|
}
|
2019-03-30 10:22:57 +00:00
|
|
|
}
|
|
|
|
|
2022-11-23 11:43:00 +00:00
|
|
|
if (pos + 4 <= len)
|
2022-08-31 13:22:21 +00:00
|
|
|
{
|
|
|
|
cur_value32 = *(uint32_t*)(data + pos);
|
2019-09-11 07:06:52 +00:00
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
2022-08-31 13:22:21 +00:00
|
|
|
cur_has_zero = haszero32_le(cur_value32);
|
2019-09-11 07:06:52 +00:00
|
|
|
#else
|
2022-08-31 13:22:21 +00:00
|
|
|
cur_has_zero = haszero32_be(cur_value32);
|
2019-09-11 07:06:52 +00:00
|
|
|
#endif
|
2022-08-31 13:22:21 +00:00
|
|
|
}
|
2019-03-30 10:22:57 +00:00
|
|
|
|
2022-11-23 11:43:00 +00:00
|
|
|
if ((prev_had_zero || cur_has_zero) && pos + 4 <= len)
|
2022-08-31 13:22:21 +00:00
|
|
|
{
|
|
|
|
/* Previous dword had zeros but this doesn't. The only way there might be a start code
|
|
|
|
* is if the most significant byte of current dword is 0x01 */
|
2022-10-24 11:54:10 +00:00
|
|
|
if (prev_had_zero) {
|
2022-08-31 13:22:21 +00:00
|
|
|
/* previous dword: 0xXX000000 or 0xXXXX0000 and current dword 0x01XXXXXX */
|
2019-09-11 07:06:52 +00:00
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
2022-08-31 13:22:21 +00:00
|
|
|
if (((cur_value32 >> 0) & 0xff) == 0x01 && ((prev_value32 >> 16) & 0xffff) == 0) {
|
|
|
|
start_len = (((prev_value32 >> 8) & 0xffffff) == 0) ? 4 : 3;
|
2019-09-11 07:06:52 +00:00
|
|
|
#else
|
2022-08-31 13:22:21 +00:00
|
|
|
if (((cur_value32 >> 24) & 0xff) == 0x01 && ((prev_value32 >> 0) & 0xffff) == 0) {
|
|
|
|
start_len = (((prev_value32 >> 0) & 0xffffff) == 0) ? 4 : 3;
|
2019-09-11 07:06:52 +00:00
|
|
|
#endif
|
2022-08-31 13:22:21 +00:00
|
|
|
data[last_byte_position] = temp_last_byte;
|
|
|
|
return pos + 1;
|
|
|
|
}
|
2019-09-11 07:06:52 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 13:22:21 +00:00
|
|
|
// find out if the current value as a whole contains start code prefix
|
2022-01-06 02:18:10 +00:00
|
|
|
bool additional_byte = false;
|
2022-08-31 13:22:21 +00:00
|
|
|
uint8_t ret = determine_start_prefix_precense(cur_value32, additional_byte);
|
2022-08-25 09:11:12 +00:00
|
|
|
start_len = ret;
|
|
|
|
if (ret > 0) {
|
2019-09-11 07:06:52 +00:00
|
|
|
if (ret == 5) {
|
2022-08-31 13:22:21 +00:00
|
|
|
// ret 5 means we don't know how long the start code is so we check it
|
|
|
|
|
2019-09-11 07:06:52 +00:00
|
|
|
ret = 3;
|
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
2022-08-31 13:22:21 +00:00
|
|
|
start_len = (((prev_value32 >> 24) & 0xff) == 0) ? 4 : 3;
|
2019-09-11 07:06:52 +00:00
|
|
|
#else
|
2022-08-31 13:22:21 +00:00
|
|
|
start_len = (((prev_value32 >> 0) & 0xff) == 0) ? 4 : 3;
|
2019-09-11 07:06:52 +00:00
|
|
|
#endif
|
|
|
|
}
|
2022-08-31 13:22:21 +00:00
|
|
|
if (additional_byte)
|
|
|
|
{
|
|
|
|
--start_len;
|
|
|
|
}
|
|
|
|
data[last_byte_position] = temp_last_byte;
|
2019-09-11 07:06:52 +00:00
|
|
|
return pos + ret;
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:22:21 +00:00
|
|
|
// see if the start code prefix is split between previous and this dword
|
2019-09-11 07:06:52 +00:00
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
2022-08-31 13:22:21 +00:00
|
|
|
uint16_t cur_ls = (cur_value32 >> 16) & 0xffff; // current less significant word
|
|
|
|
uint16_t cur_ms = (cur_value32 >> 0) & 0xffff; // corrent more significant word
|
|
|
|
uint16_t prev_ls = (prev_value32 >> 16) & 0xffff; // previous less significant word
|
|
|
|
|
|
|
|
// previous has 4 zeros
|
|
|
|
bool p4z = (prev_ls == 0);
|
|
|
|
|
|
|
|
// previous has 2 zeros
|
|
|
|
bool p2z = (((prev_ls >> 8) & 0xff) == 0);
|
|
|
|
|
|
|
|
// current has 2 bytes of possible start code
|
2022-09-07 11:10:24 +00:00
|
|
|
//bool c2s = (((cur_ms >> 8) & 0xff) == 0x01);
|
2022-08-31 13:22:21 +00:00
|
|
|
|
2022-09-19 06:34:02 +00:00
|
|
|
// current has 4 bytes of possible start code
|
2022-08-31 13:22:21 +00:00
|
|
|
bool c4s = (cur_ms == 0x0100); // current starts with 0001
|
|
|
|
|
2022-09-19 06:34:02 +00:00
|
|
|
// current has 6 bytes of start code
|
|
|
|
bool c6s = (cur_ms == 0x0000 && (cur_ls & 0xff) == 0x01); // current is 0000 01XX
|
2022-08-31 13:22:21 +00:00
|
|
|
|
2019-09-11 07:06:52 +00:00
|
|
|
#else
|
2022-08-31 13:22:21 +00:00
|
|
|
uint16_t cur_ls = (cur_value32 >> 0) & 0xffff;
|
|
|
|
uint16_t cur_ms = (cur_value32 >> 16) & 0xffff;
|
|
|
|
uint16_t prev_ls = (prev_value32 >> 0) & 0xffff;
|
|
|
|
|
|
|
|
bool p4z = (prev_ls == 0);
|
|
|
|
bool p2z = ((prev_ls & 0xff) == 0);
|
2022-09-07 11:10:24 +00:00
|
|
|
//bool c2s = ((cur_ms & 0xff) == 0x01);
|
2022-08-31 13:22:21 +00:00
|
|
|
bool c4s = (cur_ms == 0x0001);
|
|
|
|
bool c6s = (cur_ms == 0x0000 && ((cur_ls >> 8) & 0xff) == 0x01);
|
2019-09-11 07:06:52 +00:00
|
|
|
#endif
|
2022-08-31 13:22:21 +00:00
|
|
|
// all possible start code modes between two bytes
|
2022-09-07 09:44:06 +00:00
|
|
|
if (p4z && c4s) {
|
|
|
|
// previous dword 0xXXXX0000 and current dword is 0x0001XXXX
|
|
|
|
start_len = 4;
|
|
|
|
data[last_byte_position] = temp_last_byte;
|
|
|
|
return pos + 2;
|
2022-08-31 13:22:21 +00:00
|
|
|
} else if (p2z) {
|
|
|
|
// Previous dword was 0xXXXXXX00
|
|
|
|
|
|
|
|
if (c6s) {
|
|
|
|
// Current dword is 0x000001XX
|
2019-09-11 07:06:52 +00:00
|
|
|
start_len = 4;
|
2022-08-31 13:22:21 +00:00
|
|
|
data[last_byte_position] = temp_last_byte;
|
2019-09-11 07:06:52 +00:00
|
|
|
return pos + 3;
|
|
|
|
}
|
2022-08-31 13:22:21 +00:00
|
|
|
else if (c4s) {
|
|
|
|
// Current dword is 0x0001XXXX
|
2019-09-11 07:06:52 +00:00
|
|
|
start_len = 3;
|
2022-08-31 13:22:21 +00:00
|
|
|
data[last_byte_position] = temp_last_byte;
|
2019-09-11 07:06:52 +00:00
|
|
|
return pos + 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-31 13:22:21 +00:00
|
|
|
|
|
|
|
prev_had_zero = cur_has_zero;
|
2022-03-01 09:56:47 +00:00
|
|
|
pos += get_start_code_range();
|
2022-08-31 13:22:21 +00:00
|
|
|
prev_value32 = cur_value32;
|
2019-03-30 10:22:57 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 13:22:21 +00:00
|
|
|
data[last_byte_position] = temp_last_byte;
|
2019-03-30 10:22:57 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-09-15 08:02:26 +00:00
|
|
|
rtp_error_t uvgrtp::formats::h26x::frame_getter(uvgrtp::frame::rtp_frame** frame)
|
2021-09-09 09:26:57 +00:00
|
|
|
{
|
2021-09-15 10:08:09 +00:00
|
|
|
if (queued_.size()) {
|
|
|
|
*frame = queued_.front();
|
|
|
|
queued_.pop_front();
|
2021-09-09 09:26:57 +00:00
|
|
|
return RTP_PKT_READY;
|
|
|
|
}
|
|
|
|
|
|
|
|
return RTP_NOT_FOUND;
|
|
|
|
}
|
|
|
|
|
2023-05-05 10:19:37 +00:00
|
|
|
rtp_error_t uvgrtp::formats::h26x::push_media_frame(sockaddr_in& addr, sockaddr_in6& addr6, uint8_t* data, size_t data_len, int rtp_flags)
|
2022-03-25 11:01:04 +00:00
|
|
|
{
|
2022-03-28 11:15:47 +00:00
|
|
|
rtp_error_t ret = RTP_OK;
|
2022-03-25 11:01:04 +00:00
|
|
|
|
|
|
|
if (!data || !data_len)
|
|
|
|
return RTP_INVALID_VALUE;
|
|
|
|
|
|
|
|
if ((ret = fqueue_->init_transaction(data)) != RTP_OK) {
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("Invalid frame queue or failed to initialize transaction!");
|
2022-03-25 11:01:04 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-03-31 07:05:41 +00:00
|
|
|
size_t payload_size = rtp_ctx_->get_payload_size();
|
2022-03-29 12:05:43 +00:00
|
|
|
|
2022-03-28 11:15:47 +00:00
|
|
|
// find all the locations of NAL units using Start Code Lookup (SCL)
|
|
|
|
std::vector<nal_info> nals;
|
2022-03-29 10:21:20 +00:00
|
|
|
bool should_aggregate = false;
|
2022-04-01 05:24:37 +00:00
|
|
|
|
2023-08-09 11:47:43 +00:00
|
|
|
rtp_format_t fmt = rtp_ctx_->get_payload();
|
|
|
|
|
|
|
|
if ((rtp_flags & RTP_NO_H26X_SCL) || (fmt == RTP_FORMAT_V3C)) {
|
2022-04-01 05:24:37 +00:00
|
|
|
nal_info nal;
|
|
|
|
nal.offset = 0;
|
|
|
|
nal.prefix_len = 0;
|
|
|
|
nal.size = data_len;
|
|
|
|
nal.aggregate = false;
|
|
|
|
|
|
|
|
nals.push_back(nal);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
scl(data, data_len, payload_size, nals, should_aggregate);
|
|
|
|
}
|
2022-03-25 11:01:04 +00:00
|
|
|
|
2022-03-28 11:15:47 +00:00
|
|
|
if (nals.empty())
|
2022-03-25 11:01:04 +00:00
|
|
|
{
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("Did not find any NAL units in frame. Cannot send.");
|
2022-03-28 11:15:47 +00:00
|
|
|
return RTP_INVALID_VALUE;
|
2022-03-25 11:01:04 +00:00
|
|
|
}
|
2022-03-29 10:21:20 +00:00
|
|
|
|
|
|
|
if (should_aggregate) // an aggregate packet is possible
|
2022-03-25 11:01:04 +00:00
|
|
|
{
|
2022-03-29 10:21:20 +00:00
|
|
|
// use aggregation function that also may just send the packets as Single NAL units
|
|
|
|
// if aggregates have not been implemented
|
|
|
|
|
|
|
|
for (auto& nal : nals)
|
|
|
|
{
|
|
|
|
if (nal.aggregate)
|
|
|
|
{
|
|
|
|
if ((ret = add_aggregate_packet(data + nal.offset, nal.size)) != RTP_OK)
|
|
|
|
{
|
2022-03-31 11:16:37 +00:00
|
|
|
clear_aggregation_info();
|
2022-03-29 10:21:20 +00:00
|
|
|
fqueue_->deinit_transaction();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-25 11:01:04 +00:00
|
|
|
|
2022-03-29 10:21:20 +00:00
|
|
|
(void)finalize_aggregation_pkt();
|
|
|
|
}
|
2022-03-25 11:01:04 +00:00
|
|
|
|
2022-03-31 07:05:41 +00:00
|
|
|
for (auto& nal : nals) // non-aggregatable NAL units
|
2022-03-28 11:15:47 +00:00
|
|
|
{
|
2022-03-31 07:05:41 +00:00
|
|
|
if (!nal.aggregate || !should_aggregate)
|
2022-03-28 11:15:47 +00:00
|
|
|
{
|
2022-03-31 07:05:41 +00:00
|
|
|
// single NAL unit uses the NAL unit header as the payload header meaning that it does not
|
|
|
|
// add anything extra to the packet and we can just compare the NAL size with the payload size allowed
|
|
|
|
if (nal.size <= payload_size) // send as a single NAL unit packet
|
|
|
|
{
|
|
|
|
ret = single_nal_unit(data + nal.offset, nal.size);
|
|
|
|
}
|
|
|
|
else // send divided based on payload_size
|
2022-03-25 11:01:04 +00:00
|
|
|
{
|
2022-03-29 12:05:43 +00:00
|
|
|
ret = fu_division(&data[nal.offset], nal.size, payload_size);
|
2022-03-31 07:05:41 +00:00
|
|
|
}
|
2022-03-29 10:21:20 +00:00
|
|
|
|
2022-03-31 07:05:41 +00:00
|
|
|
if (ret != RTP_OK)
|
|
|
|
{
|
2022-03-31 11:16:37 +00:00
|
|
|
clear_aggregation_info();
|
2022-03-31 07:05:41 +00:00
|
|
|
fqueue_->deinit_transaction();
|
|
|
|
return ret;
|
2022-03-25 11:01:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-29 10:21:20 +00:00
|
|
|
// actually send the packets
|
2023-05-05 10:19:37 +00:00
|
|
|
ret = fqueue_->flush_queue(addr, addr6);
|
2022-03-29 10:21:20 +00:00
|
|
|
clear_aggregation_info();
|
|
|
|
|
2022-03-25 11:01:04 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-03-29 10:21:20 +00:00
|
|
|
rtp_error_t uvgrtp::formats::h26x::add_aggregate_packet(uint8_t* data, size_t data_len)
|
|
|
|
{
|
|
|
|
// the default implementation is to just use single NAL units and don't do the aggregate packet
|
|
|
|
return single_nal_unit(data, data_len);
|
2019-10-11 06:11:21 +00:00
|
|
|
}
|
|
|
|
|
2022-03-29 10:21:20 +00:00
|
|
|
rtp_error_t uvgrtp::formats::h26x::finalize_aggregation_pkt()
|
2021-06-14 09:31:37 +00:00
|
|
|
{
|
|
|
|
return RTP_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void uvgrtp::formats::h26x::clear_aggregation_info()
|
2021-06-14 10:29:35 +00:00
|
|
|
{}
|
|
|
|
|
2022-03-29 10:21:20 +00:00
|
|
|
rtp_error_t uvgrtp::formats::h26x::single_nal_unit(uint8_t* data, size_t data_len)
|
|
|
|
{
|
2022-03-31 07:05:41 +00:00
|
|
|
// single NAL unit packets use NAL header directly as payload header so the packet is
|
|
|
|
// correct as is
|
2022-03-29 10:21:20 +00:00
|
|
|
rtp_error_t ret = RTP_OK;
|
|
|
|
if ((ret = fqueue_->enqueue_message(data, data_len)) != RTP_OK) {
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("Failed to enqueue single h26x NAL Unit packet!");
|
2022-03-29 10:21:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-03-28 11:15:47 +00:00
|
|
|
rtp_error_t uvgrtp::formats::h26x::divide_frame_to_fus(uint8_t* data, size_t& data_left,
|
2022-03-31 07:05:41 +00:00
|
|
|
size_t payload_size, uvgrtp::buf_vec& buffers, uint8_t fu_headers[])
|
2021-06-14 10:29:35 +00:00
|
|
|
{
|
2022-03-30 07:04:04 +00:00
|
|
|
if (data_left <= payload_size)
|
|
|
|
{
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("Cannot use FU division for packets smaller than payload size");
|
2022-03-30 07:04:04 +00:00
|
|
|
return RTP_GENERIC_ERROR;
|
|
|
|
}
|
|
|
|
|
2021-06-14 10:29:35 +00:00
|
|
|
rtp_error_t ret = RTP_OK;
|
|
|
|
|
2022-03-31 07:05:41 +00:00
|
|
|
// the FU structure has both payload header and an fu header
|
|
|
|
size_t fu_payload_size = payload_size - get_payload_header_size() - get_fu_header_size();
|
|
|
|
|
|
|
|
// skip NAL header of data since it is incorporated in payload and fu headers (which are repeated
|
|
|
|
// for each packet, but NAL header is only at the beginning of NAL unit)
|
|
|
|
size_t data_pos = get_nal_header_size();
|
|
|
|
data_left -= get_nal_header_size();
|
2022-03-30 07:04:04 +00:00
|
|
|
|
2022-03-31 07:05:41 +00:00
|
|
|
while (data_left > fu_payload_size) {
|
2022-03-30 07:04:04 +00:00
|
|
|
|
2022-03-31 07:05:41 +00:00
|
|
|
/* This seems to work by always using the payload headers in first and fu headers in the second index
|
|
|
|
* of buffer (and modifying those) and replacing the payload in third, then sending all.
|
|
|
|
* The headers for first fragment are already in buffers.at(1) */
|
2022-03-30 07:04:04 +00:00
|
|
|
|
|
|
|
// set the payload for this fragment
|
2022-03-31 07:05:41 +00:00
|
|
|
buffers.at(2).first = fu_payload_size;
|
2021-06-14 10:29:35 +00:00
|
|
|
buffers.at(2).second = &data[data_pos];
|
|
|
|
|
|
|
|
if ((ret = fqueue_->enqueue_message(buffers)) != RTP_OK) {
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("Queueing the FU packet failed!");
|
2021-06-14 10:29:35 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-03-31 07:05:41 +00:00
|
|
|
data_pos += fu_payload_size;
|
|
|
|
data_left -= fu_payload_size;
|
2021-06-14 10:29:35 +00:00
|
|
|
|
2022-03-30 07:04:04 +00:00
|
|
|
buffers.at(1).second = &fu_headers[1]; // middle fragment header
|
2021-06-14 10:29:35 +00:00
|
|
|
}
|
|
|
|
|
2022-03-30 07:04:04 +00:00
|
|
|
buffers.at(1).second = &fu_headers[2]; // last fragment header
|
2021-06-14 10:29:35 +00:00
|
|
|
|
2022-03-30 07:04:04 +00:00
|
|
|
// set payload for the last fragment
|
2021-06-14 10:29:35 +00:00
|
|
|
buffers.at(2).first = data_left;
|
|
|
|
buffers.at(2).second = &data[data_pos];
|
|
|
|
|
2022-08-16 08:30:55 +00:00
|
|
|
// send the last fragment
|
|
|
|
if ((ret = fqueue_->enqueue_message(buffers)) != RTP_OK) {
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("Failed to send the last fragment of an H26x frame!");
|
2022-08-16 08:30:55 +00:00
|
|
|
}
|
|
|
|
|
2021-06-14 10:29:35 +00:00
|
|
|
return ret;
|
2021-06-14 11:14:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void uvgrtp::formats::h26x::initialize_fu_headers(uint8_t nal_type, uint8_t fu_headers[])
|
|
|
|
{
|
|
|
|
fu_headers[0] = (uint8_t)((1 << 7) | nal_type);
|
|
|
|
fu_headers[1] = nal_type;
|
|
|
|
fu_headers[2] = (uint8_t)((1 << 6) | nal_type);
|
2021-06-15 07:12:43 +00:00
|
|
|
}
|
2021-06-24 09:41:53 +00:00
|
|
|
|
2022-03-01 09:56:47 +00:00
|
|
|
uvgrtp::frame::rtp_frame* uvgrtp::formats::h26x::allocate_rtp_frame_with_startcode(bool add_start_code,
|
2022-02-28 14:23:03 +00:00
|
|
|
uvgrtp::frame::rtp_header& header, size_t payload_size_without_startcode, size_t& fptr)
|
|
|
|
{
|
|
|
|
uvgrtp::frame::rtp_frame* complete = uvgrtp::frame::alloc_rtp_frame();
|
|
|
|
|
|
|
|
complete->payload_len = payload_size_without_startcode;
|
|
|
|
|
2022-03-01 09:56:47 +00:00
|
|
|
if (add_start_code) {
|
2022-02-28 14:23:03 +00:00
|
|
|
complete->payload_len += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
complete->payload = new uint8_t[complete->payload_len];
|
|
|
|
|
2022-08-25 09:11:12 +00:00
|
|
|
if (add_start_code && complete->payload_len >= 4) {
|
2022-02-28 14:23:03 +00:00
|
|
|
complete->payload[0] = 0;
|
|
|
|
complete->payload[1] = 0;
|
|
|
|
complete->payload[2] = 0;
|
|
|
|
complete->payload[3] = 1;
|
|
|
|
fptr += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
complete->header = header; // copy
|
|
|
|
|
|
|
|
return complete;
|
|
|
|
}
|
|
|
|
|
2022-08-23 06:47:07 +00:00
|
|
|
void uvgrtp::formats::h26x::prepend_start_code(int rce_flags, uvgrtp::frame::rtp_frame** out)
|
2021-06-24 09:41:53 +00:00
|
|
|
{
|
2023-08-09 11:47:43 +00:00
|
|
|
rtp_format_t fmt = rtp_ctx_->get_payload();
|
|
|
|
if (fmt == RTP_FORMAT_V3C) {
|
|
|
|
return;
|
|
|
|
}
|
2022-09-20 11:14:58 +00:00
|
|
|
if (!(rce_flags & RCE_NO_H26X_PREPEND_SC)) {
|
2021-06-24 09:41:53 +00:00
|
|
|
uint8_t* pl = new uint8_t[(*out)->payload_len + 4];
|
|
|
|
|
|
|
|
pl[0] = 0;
|
|
|
|
pl[1] = 0;
|
|
|
|
pl[2] = 0;
|
|
|
|
pl[3] = 1;
|
|
|
|
|
|
|
|
std::memcpy(pl + 4, (*out)->payload, (*out)->payload_len);
|
|
|
|
delete[](*out)->payload;
|
|
|
|
|
|
|
|
(*out)->payload = pl;
|
|
|
|
(*out)->payload_len += 4;
|
|
|
|
}
|
2021-09-09 07:41:08 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 07:47:25 +00:00
|
|
|
bool uvgrtp::formats::h26x::is_frame_late(uvgrtp::formats::h26x_info_t& hinfo, size_t max_delay)
|
2021-09-09 07:41:08 +00:00
|
|
|
{
|
|
|
|
return (uvgrtp::clock::hrc::diff_now(hinfo.sframe_time) >= max_delay);
|
2021-09-09 09:26:57 +00:00
|
|
|
}
|
|
|
|
|
2022-08-25 09:11:12 +00:00
|
|
|
size_t uvgrtp::formats::h26x::drop_frame(uint32_t ts)
|
2021-09-09 09:26:57 +00:00
|
|
|
{
|
2022-08-25 09:11:12 +00:00
|
|
|
size_t total_cleaned = 0;
|
2021-09-15 11:01:24 +00:00
|
|
|
if (frames_.find(ts) == frames_.end())
|
|
|
|
{
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("Tried to drop a non-existing frame");
|
2022-06-06 11:29:55 +00:00
|
|
|
return total_cleaned;
|
2021-09-15 11:01:24 +00:00
|
|
|
}
|
|
|
|
|
2022-08-17 13:13:20 +00:00
|
|
|
/*
|
2022-06-06 11:29:55 +00:00
|
|
|
uint16_t s_seq = frames_.at(ts).s_seq;
|
|
|
|
uint16_t e_seq = frames_.at(ts).e_seq;
|
2021-09-15 11:01:24 +00:00
|
|
|
|
2022-08-17 13:13:20 +00:00
|
|
|
UVG_LOG_INFO("Dropping frame. Ts: %lu, Seq: %u <-> %u, received/expected: %lli/%lli",
|
|
|
|
ts, s_seq, e_seq, frames_[ts].received_packet_seqs.size(), calculate_expected_fus(ts));
|
|
|
|
*/
|
2021-09-09 09:26:57 +00:00
|
|
|
|
2022-06-06 11:29:55 +00:00
|
|
|
for (auto& fragment_seq : frames_[ts].received_packet_seqs)
|
|
|
|
{
|
|
|
|
total_cleaned += fragments_[fragment_seq]->payload_len + sizeof(uvgrtp::frame::rtp_frame);
|
|
|
|
free_fragment(fragment_seq);
|
2021-09-15 11:01:24 +00:00
|
|
|
}
|
|
|
|
|
2022-08-18 11:34:58 +00:00
|
|
|
dropped_ts_[ts] = frames_.at(ts).sframe_time;
|
2021-09-15 10:08:09 +00:00
|
|
|
frames_.erase(ts);
|
2021-09-15 11:01:24 +00:00
|
|
|
|
2022-06-06 14:13:17 +00:00
|
|
|
discard_until_key_frame_ = true;
|
|
|
|
|
2021-09-15 11:01:24 +00:00
|
|
|
return total_cleaned;
|
2021-09-09 10:03:27 +00:00
|
|
|
}
|
|
|
|
|
2022-03-25 10:58:09 +00:00
|
|
|
rtp_error_t uvgrtp::formats::h26x::handle_aggregation_packet(uvgrtp::frame::rtp_frame** out,
|
2022-08-23 06:47:07 +00:00
|
|
|
uint8_t payload_header_size, int rce_flags)
|
2021-09-09 10:03:27 +00:00
|
|
|
{
|
|
|
|
uvgrtp::buf_vec nalus;
|
|
|
|
|
|
|
|
size_t size = 0;
|
|
|
|
auto* frame = *out;
|
|
|
|
|
2022-03-29 12:05:43 +00:00
|
|
|
for (size_t i = payload_header_size; i < frame->payload_len;
|
2022-03-25 10:58:09 +00:00
|
|
|
i += ntohs(*(uint16_t*)&frame->payload[i]) + sizeof(uint16_t)) {
|
2021-09-09 10:03:27 +00:00
|
|
|
|
2022-03-22 07:03:50 +00:00
|
|
|
uint16_t packet_size = ntohs(*(uint16_t*)&frame->payload[i]);
|
|
|
|
size += packet_size;
|
|
|
|
|
|
|
|
if (size <= (*out)->payload_len) {
|
|
|
|
nalus.push_back(std::make_pair(packet_size, &frame->payload[i] + sizeof(uint16_t)));
|
|
|
|
}
|
|
|
|
else {
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("The received aggregation packet claims to be larger than packet!");
|
2022-03-25 10:58:09 +00:00
|
|
|
return RTP_GENERIC_ERROR;
|
2022-03-22 07:03:50 +00:00
|
|
|
}
|
2021-09-09 10:03:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < nalus.size(); ++i) {
|
2022-02-28 14:23:03 +00:00
|
|
|
size_t fptr = 0;
|
2022-08-23 06:47:07 +00:00
|
|
|
|
2022-09-20 11:14:58 +00:00
|
|
|
bool prepend_startcode = !(rce_flags & RCE_NO_H26X_PREPEND_SC);
|
2022-02-28 14:23:03 +00:00
|
|
|
uvgrtp::frame::rtp_frame* retframe =
|
2022-08-23 06:47:07 +00:00
|
|
|
allocate_rtp_frame_with_startcode(prepend_startcode, (*out)->header, nalus[i].first, fptr);
|
2022-02-28 14:23:03 +00:00
|
|
|
|
2021-09-09 10:03:27 +00:00
|
|
|
std::memcpy(
|
2022-02-28 14:23:03 +00:00
|
|
|
retframe->payload + fptr,
|
2021-09-09 10:03:27 +00:00
|
|
|
nalus[i].second,
|
|
|
|
nalus[i].first
|
|
|
|
);
|
|
|
|
|
2021-09-15 10:08:09 +00:00
|
|
|
queued_.push_back(retframe);
|
2021-09-09 10:03:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return RTP_MULTIPLE_PKTS_READY;
|
2021-09-15 08:02:26 +00:00
|
|
|
}
|
|
|
|
|
2023-06-01 08:20:56 +00:00
|
|
|
bool uvgrtp::formats::h26x::is_duplicate_frame(uint32_t timestamp, uint16_t seq_num)
|
|
|
|
{
|
2023-06-13 10:59:38 +00:00
|
|
|
if (received_info_.find(timestamp) != received_info_.end()) {
|
|
|
|
if (std::find(received_info_.at(timestamp).begin(), received_info_.at(timestamp).end(), seq_num) != received_info_.at(timestamp).end()) {
|
|
|
|
UVG_LOG_WARN("duplicate ts and seq num received, discarding frame");
|
2023-06-01 08:20:56 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2023-06-02 09:49:09 +00:00
|
|
|
pkt_stats stats = {timestamp, seq_num};
|
2023-06-01 08:20:56 +00:00
|
|
|
|
2023-06-13 10:59:38 +00:00
|
|
|
// Save the received ts and seq num
|
|
|
|
if (received_info_.find(timestamp) == received_info_.end()) {
|
|
|
|
received_info_.insert({timestamp, {seq_num}});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
received_info_.at(timestamp).push_back(seq_num);
|
|
|
|
}
|
2023-06-02 09:49:09 +00:00
|
|
|
received_frames_.push_back(stats);
|
|
|
|
|
|
|
|
if (received_frames_.size() > RECEIVED_FRAMES) {
|
2023-06-13 10:59:38 +00:00
|
|
|
received_info_.erase(received_frames_.front().ts);
|
2023-06-02 09:49:09 +00:00
|
|
|
received_frames_.pop_front();
|
2023-06-01 08:20:56 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-07-24 10:09:27 +00:00
|
|
|
rtp_error_t uvgrtp::formats::h26x::packet_handler(void* args, int rce_flags, uint8_t* read_ptr, size_t size, uvgrtp::frame::rtp_frame** out)
|
2021-09-15 08:02:26 +00:00
|
|
|
{
|
2023-06-26 05:57:41 +00:00
|
|
|
(void)args;
|
|
|
|
(void)read_ptr;
|
|
|
|
(void)size;
|
2022-06-06 14:13:17 +00:00
|
|
|
uvgrtp::frame::rtp_frame* frame = *out;
|
2022-06-02 14:07:17 +00:00
|
|
|
|
2023-06-02 09:49:09 +00:00
|
|
|
if (is_duplicate_frame(frame->header.timestamp, frame->header.seq)) {
|
2023-06-13 10:59:38 +00:00
|
|
|
(void)uvgrtp::frame::dealloc_frame(*out);
|
|
|
|
*out = nullptr;
|
2023-06-02 09:49:09 +00:00
|
|
|
return RTP_GENERIC_ERROR;
|
|
|
|
}
|
|
|
|
|
2022-06-02 14:07:17 +00:00
|
|
|
// aggregate, start, middle, end or single NAL
|
|
|
|
uvgrtp::formats::FRAG_TYPE frag_type = get_fragment_type(frame);
|
2022-08-18 11:34:58 +00:00
|
|
|
|
|
|
|
// first we check that this packet does not belong to a frame that has been dropped or completed
|
|
|
|
if (dropped_ts_.find(frame->header.timestamp) != dropped_ts_.end()) {
|
2022-08-19 09:02:27 +00:00
|
|
|
UVG_LOG_DEBUG("Received an RTP packet belonging to a dropped frame! Timestamp: %lu, seq: %u",
|
2022-08-18 11:34:58 +00:00
|
|
|
frame->header.timestamp, frame->header.seq);
|
2022-08-31 07:06:37 +00:00
|
|
|
(void)uvgrtp::frame::dealloc_frame(frame); // free fragment memory
|
2022-08-18 11:34:58 +00:00
|
|
|
return RTP_GENERIC_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (completed_ts_.find(frame->header.timestamp) != completed_ts_.end()) {
|
2022-08-19 09:02:27 +00:00
|
|
|
UVG_LOG_DEBUG("Received an RTP packet belonging to a completed frame! Timestamp: %lu, seq: %u",
|
2022-08-18 11:34:58 +00:00
|
|
|
frame->header.timestamp, frame->header.seq);
|
2022-08-31 07:06:37 +00:00
|
|
|
(void)uvgrtp::frame::dealloc_frame(frame); // free fragment memory
|
2022-08-18 11:34:58 +00:00
|
|
|
return RTP_GENERIC_ERROR;
|
|
|
|
}
|
|
|
|
|
2022-06-02 14:07:17 +00:00
|
|
|
if (frag_type == uvgrtp::formats::FRAG_TYPE::FT_AGGR) {
|
2022-03-25 05:56:36 +00:00
|
|
|
// handle aggregate packets (packets with multiple NAL units in them)
|
2022-08-23 06:47:07 +00:00
|
|
|
return handle_aggregation_packet(out, get_payload_header_size(), rce_flags);
|
2022-03-25 05:56:36 +00:00
|
|
|
}
|
2023-05-31 10:09:03 +00:00
|
|
|
else if (frag_type == uvgrtp::formats::FRAG_TYPE::FT_STAP_B) {
|
2023-06-01 08:20:56 +00:00
|
|
|
// Handle H264 STAP-B packet, RFC 6184 5.7.1
|
|
|
|
// DON is made up of the 16 bits after STAP-B header.
|
|
|
|
// Commented out to prevent werrors, DON is not currently used anywhere.
|
|
|
|
/* uint16_t don = ((uint16_t)frame->payload[1] << 8) | frame->payload[2]; */
|
2023-05-31 10:30:59 +00:00
|
|
|
|
|
|
|
// payload_header_size + 2 comes from DON field in STAP-B packets being 16 bits long
|
2023-05-31 07:13:32 +00:00
|
|
|
return handle_aggregation_packet(out, get_payload_header_size()+2, rce_flags);
|
|
|
|
}
|
2022-06-02 14:07:17 +00:00
|
|
|
else if (frag_type == uvgrtp::formats::FRAG_TYPE::FT_NOT_FRAG) { // Single NAL unit
|
|
|
|
|
|
|
|
// TODO: Check if previous dependencies have been sent forward
|
2022-11-24 07:11:43 +00:00
|
|
|
|
|
|
|
// TODO: We should detect duplicate packets, but there are legitimate situations
|
|
|
|
// where single NAL units have same timestamps
|
|
|
|
//completed_ts_[frame->header.timestamp] = std::chrono::high_resolution_clock::now();
|
2022-06-02 14:07:17 +00:00
|
|
|
|
2022-06-02 12:21:50 +00:00
|
|
|
// nothing special needs to be done, just possibly add start codes back
|
2022-08-23 06:47:07 +00:00
|
|
|
prepend_start_code(rce_flags, out);
|
2021-09-15 08:02:26 +00:00
|
|
|
return RTP_PKT_READY;
|
|
|
|
}
|
2022-06-02 14:07:17 +00:00
|
|
|
else if (frag_type == uvgrtp::formats::FRAG_TYPE::FT_INVALID) {
|
2022-03-25 05:56:36 +00:00
|
|
|
// something is wrong
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_WARN("invalid frame received!");
|
2021-09-15 08:02:26 +00:00
|
|
|
(void)uvgrtp::frame::dealloc_frame(*out);
|
|
|
|
*out = nullptr;
|
|
|
|
return RTP_GENERIC_ERROR;
|
|
|
|
}
|
|
|
|
|
2022-06-02 12:21:50 +00:00
|
|
|
// We have received a fragment. Rest of the function deals with fragmented frames
|
|
|
|
|
2022-06-02 14:07:17 +00:00
|
|
|
// Fragment timestamp, all fragments of the same frame have the same timestamp
|
2022-06-06 14:13:17 +00:00
|
|
|
uint32_t fragment_ts = frame->header.timestamp;
|
2022-03-25 05:56:36 +00:00
|
|
|
|
2022-06-02 14:07:17 +00:00
|
|
|
// Fragment sequence number, determines the order of the fragments within frame
|
2022-06-06 11:29:55 +00:00
|
|
|
uint16_t fragment_seq = frame->header.seq;
|
2022-06-18 15:50:05 +00:00
|
|
|
|
|
|
|
uvgrtp::formats::NAL_TYPE nal_type = get_nal_type(frame); // Intra, inter or some other type of frame
|
2022-03-25 05:56:36 +00:00
|
|
|
|
2022-06-02 14:07:17 +00:00
|
|
|
// Initialize new frame if this is the first packet with this timestamp
|
2022-06-02 12:21:50 +00:00
|
|
|
if (frames_.find(fragment_ts) == frames_.end()) {
|
2022-06-18 15:50:05 +00:00
|
|
|
initialize_new_fragmented_frame(fragment_ts, nal_type);
|
2021-09-15 08:02:26 +00:00
|
|
|
}
|
2022-06-06 11:29:55 +00:00
|
|
|
else if (frames_[fragment_ts].received_packet_seqs.find(fragment_seq) !=
|
|
|
|
frames_[fragment_ts].received_packet_seqs.end()) {
|
|
|
|
|
|
|
|
// we have already received this seq
|
2022-08-18 11:34:58 +00:00
|
|
|
UVG_LOG_DEBUG("Detected duplicate fragment, dropping! Fragment ts: %lu, Seq: %u",
|
|
|
|
fragment_ts, fragment_seq);
|
2022-06-06 11:29:55 +00:00
|
|
|
(void)uvgrtp::frame::dealloc_frame(frame); // free fragment memory
|
|
|
|
*out = nullptr;
|
|
|
|
return RTP_GENERIC_ERROR;
|
|
|
|
}
|
2021-09-15 08:02:26 +00:00
|
|
|
|
2022-06-02 12:21:50 +00:00
|
|
|
const uint8_t sizeof_fu_headers = (uint8_t)get_payload_header_size() +
|
|
|
|
get_fu_header_size();
|
2022-03-31 07:05:41 +00:00
|
|
|
|
2022-06-18 15:50:05 +00:00
|
|
|
if (frames_[fragment_ts].nal_type != nal_type)
|
|
|
|
{
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("The fragment has different NAL type fragments before!");
|
2022-08-31 07:06:37 +00:00
|
|
|
(void)uvgrtp::frame::dealloc_frame(frame); // free fragment memory
|
2022-06-18 15:50:05 +00:00
|
|
|
return RTP_GENERIC_ERROR;
|
|
|
|
}
|
|
|
|
|
2022-06-06 11:29:55 +00:00
|
|
|
// keep track of fragments belonging to this frame in case we need to delete them
|
|
|
|
frames_[fragment_ts].received_packet_seqs.insert(fragment_seq);
|
2022-06-02 12:21:50 +00:00
|
|
|
frames_[fragment_ts].total_size += (frame->payload_len - sizeof_fu_headers);
|
2021-09-15 08:02:26 +00:00
|
|
|
|
2022-06-06 11:29:55 +00:00
|
|
|
if (fragments_[fragment_seq] != nullptr)
|
|
|
|
{
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_WARN("Found an existing fragment with same sequence number %u! Fragment ts: %lu, current ts: %lu",
|
2022-06-06 11:29:55 +00:00
|
|
|
fragment_seq, fragments_[fragment_seq]->header.timestamp, fragment_ts);
|
2021-09-15 08:02:26 +00:00
|
|
|
|
2022-06-06 11:29:55 +00:00
|
|
|
free_fragment(fragment_seq);
|
|
|
|
}
|
2021-09-15 08:02:26 +00:00
|
|
|
|
2022-06-06 11:29:55 +00:00
|
|
|
// save the fragment for later reconstruction
|
|
|
|
fragments_[fragment_seq] = frame;
|
2022-06-02 12:21:50 +00:00
|
|
|
|
2022-06-06 11:29:55 +00:00
|
|
|
// if this is first or last, save it to help with reconstruction
|
|
|
|
if (frag_type == uvgrtp::formats::FRAG_TYPE::FT_START) {
|
|
|
|
frames_[fragment_ts].s_seq = fragment_seq;
|
|
|
|
frames_[fragment_ts].start_received = true;
|
2021-09-15 08:02:26 +00:00
|
|
|
}
|
2022-06-06 11:29:55 +00:00
|
|
|
else if (frag_type == uvgrtp::formats::FRAG_TYPE::FT_END) {
|
|
|
|
frames_[fragment_ts].e_seq = fragment_seq;
|
|
|
|
frames_[fragment_ts].end_received = true;
|
2021-09-15 08:02:26 +00:00
|
|
|
}
|
|
|
|
|
2022-06-02 12:21:50 +00:00
|
|
|
// have the first and last fragment arrived so we can possibly start reconstructing the frame?
|
2022-06-06 11:29:55 +00:00
|
|
|
if (frames_[fragment_ts].start_received && frames_[fragment_ts].end_received) {
|
2022-06-02 12:21:50 +00:00
|
|
|
size_t received = calculate_expected_fus(fragment_ts);
|
2021-09-15 08:02:26 +00:00
|
|
|
|
2022-06-02 12:21:50 +00:00
|
|
|
// have we received every fragment and can the frame can be reconstructed?
|
2022-06-06 11:29:55 +00:00
|
|
|
if (received == frames_[fragment_ts].received_packet_seqs.size()) {
|
2022-06-02 12:21:50 +00:00
|
|
|
|
2022-08-23 06:47:07 +00:00
|
|
|
bool enable_reference_discarding = (rce_flags & RCE_H26X_DEPENDENCY_ENFORCEMENT);
|
2022-06-06 14:13:17 +00:00
|
|
|
// here we discard inter frames if their references were not received correctly
|
2022-06-17 05:26:18 +00:00
|
|
|
if (discard_until_key_frame_ && enable_reference_discarding) {
|
2022-06-06 14:13:17 +00:00
|
|
|
if (nal_type == uvgrtp::formats::NAL_TYPE::NT_INTER) {
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_WARN("Dropping h26x frame because of missing reference. Timestamp: %lu. Seq: %u - %u",
|
2022-06-06 14:13:17 +00:00
|
|
|
fragment_ts, frames_[fragment_ts].s_seq, frames_[fragment_ts].e_seq);
|
2021-09-15 08:02:26 +00:00
|
|
|
|
2022-06-06 14:13:17 +00:00
|
|
|
drop_frame(fragment_ts);
|
2022-06-06 11:29:55 +00:00
|
|
|
return RTP_GENERIC_ERROR;
|
|
|
|
}
|
2022-06-06 14:13:17 +00:00
|
|
|
else if (nal_type == uvgrtp::formats::NAL_TYPE::NT_INTRA) {
|
2022-06-06 11:29:55 +00:00
|
|
|
|
2022-06-06 14:13:17 +00:00
|
|
|
// we don't have to discard anymore
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_INFO("Found a key frame at ts %lu", fragment_ts);
|
2022-06-06 14:13:17 +00:00
|
|
|
discard_until_key_frame_ = false;
|
|
|
|
}
|
2021-09-15 08:02:26 +00:00
|
|
|
}
|
|
|
|
|
2022-08-23 06:47:07 +00:00
|
|
|
return reconstruction(out, rce_flags, fragment_ts, sizeof_fu_headers);
|
2021-09-15 08:02:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-02 12:21:50 +00:00
|
|
|
// make sure uvgRTP does not reserve increasing amounts of memory because some frames are not completed
|
2022-06-17 07:33:22 +00:00
|
|
|
garbage_collect_lost_frames(rtp_ctx_->get_pkt_max_delay());
|
2022-06-02 12:21:50 +00:00
|
|
|
return RTP_OK; // no frame was completed, but everything went ok for this fragment
|
2021-09-15 08:02:26 +00:00
|
|
|
}
|
|
|
|
|
2022-06-17 07:33:22 +00:00
|
|
|
void uvgrtp::formats::h26x::garbage_collect_lost_frames(size_t timout)
|
2021-09-15 11:01:24 +00:00
|
|
|
{
|
|
|
|
if (uvgrtp::clock::hrc::diff_now(last_garbage_collection_) >= GARBAGE_COLLECTION_INTERVAL_MS) {
|
2022-08-25 09:11:12 +00:00
|
|
|
size_t total_cleaned = 0;
|
2021-09-15 11:01:24 +00:00
|
|
|
std::vector<uint32_t> to_remove;
|
|
|
|
|
|
|
|
// first find all frames that have been waiting for too long
|
|
|
|
for (auto& gc_frame : frames_) {
|
2022-06-17 07:33:22 +00:00
|
|
|
if (uvgrtp::clock::hrc::diff_now(gc_frame.second.sframe_time) > timout) {
|
2022-09-12 12:10:26 +00:00
|
|
|
#ifndef __RTP_SILENT__
|
2022-08-18 11:34:58 +00:00
|
|
|
uint16_t s_seq = gc_frame.second.s_seq;
|
|
|
|
uint16_t e_seq = gc_frame.second.e_seq;
|
|
|
|
UVG_LOG_WARN("Found an old frame that has not been completed. Ts: %lu, Seq: %u <-> %u, received/expected: %lli/%lli",
|
|
|
|
gc_frame.first, s_seq, e_seq, gc_frame.second.received_packet_seqs.size(), calculate_expected_fus(gc_frame.first));
|
2022-09-12 12:10:26 +00:00
|
|
|
#endif
|
2021-09-15 11:01:24 +00:00
|
|
|
to_remove.push_back(gc_frame.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove old frames
|
|
|
|
for (auto& old_frame : to_remove) {
|
|
|
|
|
|
|
|
total_cleaned += drop_frame(old_frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (total_cleaned > 0) {
|
2022-08-18 11:34:58 +00:00
|
|
|
UVG_LOG_DEBUG("Garbage collection cleaned %d bytes!", total_cleaned);
|
|
|
|
}
|
|
|
|
|
|
|
|
// we keep track of old frames, so we don't send duplicate frames forward twice
|
|
|
|
std::vector<uint32_t> to_remove_completed;
|
|
|
|
for (auto& invalid : completed_ts_) {
|
|
|
|
if (uvgrtp::clock::hrc::diff_now(invalid.second) > TIME_TO_KEEP_TRACK_OF_PREVIOUS_FRAMES_MS)
|
|
|
|
{
|
|
|
|
to_remove_completed.push_back(invalid.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto& old_invalid : to_remove_completed)
|
|
|
|
{
|
|
|
|
completed_ts_.erase(old_invalid);
|
|
|
|
}
|
|
|
|
|
|
|
|
// we keep track of old dopped, so we don't send invalid frames forward again
|
|
|
|
to_remove_completed.clear();
|
|
|
|
for (auto& invalid : dropped_ts_) {
|
|
|
|
if (uvgrtp::clock::hrc::diff_now(invalid.second) > TIME_TO_KEEP_TRACK_OF_PREVIOUS_FRAMES_MS)
|
|
|
|
{
|
|
|
|
to_remove_completed.push_back(invalid.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto& old_invalid : to_remove_completed)
|
|
|
|
{
|
|
|
|
dropped_ts_.erase(old_invalid);
|
2021-09-15 11:01:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
last_garbage_collection_ = uvgrtp::clock::hrc::now();
|
|
|
|
}
|
2022-03-25 05:56:36 +00:00
|
|
|
}
|
|
|
|
|
2022-06-18 15:50:05 +00:00
|
|
|
void uvgrtp::formats::h26x::initialize_new_fragmented_frame(uint32_t ts, NAL_TYPE nal_type)
|
2022-03-25 05:56:36 +00:00
|
|
|
{
|
2022-06-18 15:50:05 +00:00
|
|
|
frames_[ts].nal_type = nal_type;
|
2022-06-06 11:29:55 +00:00
|
|
|
frames_[ts].s_seq = 0;
|
|
|
|
frames_[ts].start_received = false;
|
|
|
|
frames_[ts].e_seq = 0;
|
|
|
|
frames_[ts].end_received = false;
|
2022-03-25 05:56:36 +00:00
|
|
|
|
|
|
|
frames_[ts].sframe_time = uvgrtp::clock::hrc::now();
|
|
|
|
frames_[ts].total_size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t uvgrtp::formats::h26x::calculate_expected_fus(uint32_t ts)
|
|
|
|
{
|
2022-06-06 11:29:55 +00:00
|
|
|
size_t expected = 0;
|
2022-03-25 05:56:36 +00:00
|
|
|
size_t s_seq = frames_[ts].s_seq;
|
|
|
|
size_t e_seq = frames_[ts].e_seq;
|
|
|
|
|
2022-06-06 11:29:55 +00:00
|
|
|
if (frames_[ts].start_received && frames_[ts].end_received)
|
|
|
|
{
|
|
|
|
if (s_seq > e_seq) {
|
|
|
|
expected = (UINT16_MAX - s_seq) + e_seq + 2;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
expected = e_seq - s_seq + 1;
|
|
|
|
}
|
|
|
|
}
|
2022-03-25 05:56:36 +00:00
|
|
|
|
|
|
|
return expected;
|
|
|
|
}
|
2022-03-28 11:15:47 +00:00
|
|
|
|
2022-06-06 11:29:55 +00:00
|
|
|
void uvgrtp::formats::h26x::free_fragment(uint16_t sequence_number)
|
|
|
|
{
|
|
|
|
if (fragments_[sequence_number] == nullptr)
|
|
|
|
{
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("Tried to free an already freed fragment with seq: %u", sequence_number);
|
2022-06-06 11:29:55 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
(void)uvgrtp::frame::dealloc_frame(fragments_[sequence_number]); // free fragment memory
|
|
|
|
fragments_[sequence_number] = nullptr;
|
|
|
|
}
|
|
|
|
|
2022-03-29 10:21:20 +00:00
|
|
|
void uvgrtp::formats::h26x::scl(uint8_t* data, size_t data_len, size_t packet_size,
|
|
|
|
std::vector<nal_info>& nals, bool& can_be_aggregated)
|
2022-03-28 11:15:47 +00:00
|
|
|
{
|
|
|
|
uint8_t start_len = 0;
|
|
|
|
ssize_t offset = find_h26x_start_code(data, data_len, 0, start_len);
|
|
|
|
|
2022-03-31 07:05:41 +00:00
|
|
|
packet_size -= get_payload_header_size(); // aggregate packet has a payload header
|
2022-03-29 10:21:20 +00:00
|
|
|
|
2022-03-31 11:48:59 +00:00
|
|
|
while (offset > -1) {
|
2022-03-28 11:15:47 +00:00
|
|
|
nal_info nal;
|
2022-03-31 11:48:59 +00:00
|
|
|
nal.offset = size_t(offset);
|
2022-03-28 11:15:47 +00:00
|
|
|
nal.prefix_len = start_len;
|
|
|
|
nal.size = 0; // set after all NALs have been found
|
2022-03-29 10:21:20 +00:00
|
|
|
nal.aggregate = false; // determined with size calculations
|
|
|
|
|
2022-03-28 11:15:47 +00:00
|
|
|
|
|
|
|
nals.push_back(nal);
|
|
|
|
offset = find_h26x_start_code(data, data_len, offset, start_len);
|
|
|
|
}
|
|
|
|
|
2022-03-29 10:21:20 +00:00
|
|
|
size_t aggregate_size = 0;
|
|
|
|
int aggregatable_packets = 0;
|
|
|
|
|
2022-03-28 11:15:47 +00:00
|
|
|
// calculate the sizes of NAL units
|
2022-03-31 11:48:59 +00:00
|
|
|
for (size_t i = 0; i < nals.size(); ++i)
|
2022-03-28 11:15:47 +00:00
|
|
|
{
|
|
|
|
if (nals.size() > i + 1)
|
|
|
|
{
|
|
|
|
// take the difference of next NAL unit location and current one,
|
|
|
|
// minus size of start code prefix of next NAL unit
|
|
|
|
nals.at(i).size = nals[i + 1].offset - nals[i].offset - nals[i + 1].prefix_len;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// last NAL unit, the length is offset to end
|
|
|
|
nals.at(i).size = data_len - nals[i].offset;
|
|
|
|
}
|
2022-03-29 10:21:20 +00:00
|
|
|
|
2022-03-31 07:05:41 +00:00
|
|
|
// each NAL unit added to aggregate packet needs the size added which has to be taken into account
|
|
|
|
// when calculating the aggregate packet
|
|
|
|
// (NOTE: This is not enough for MTAP in h264, but I doubt uvgRTP will support it)
|
|
|
|
if (aggregate_size + nals.at(i).size + sizeof(uint16_t) <= packet_size)
|
2022-03-29 10:21:20 +00:00
|
|
|
{
|
2022-03-31 07:05:41 +00:00
|
|
|
aggregate_size += nals.at(i).size + sizeof(uint16_t);
|
2022-03-29 10:21:20 +00:00
|
|
|
nals.at(i).aggregate = true;
|
|
|
|
++aggregatable_packets;
|
|
|
|
}
|
2022-03-28 11:15:47 +00:00
|
|
|
}
|
2022-03-29 10:21:20 +00:00
|
|
|
|
|
|
|
can_be_aggregated = (aggregatable_packets >= 2);
|
2022-06-06 14:13:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rtp_error_t uvgrtp::formats::h26x::reconstruction(uvgrtp::frame::rtp_frame** out,
|
2022-08-23 06:47:07 +00:00
|
|
|
int rce_flags, uint32_t frame_timestamp, const uint8_t sizeof_fu_headers)
|
2022-06-06 14:13:17 +00:00
|
|
|
{
|
|
|
|
uvgrtp::frame::rtp_frame* frame = *out;
|
|
|
|
|
2022-08-18 11:34:58 +00:00
|
|
|
//LOG_DEBUG("Reconstructing frame. Ts: %lu, Seq: %u -> %u", frame_timestamp,
|
|
|
|
// frames_.at(frame_timestamp).s_seq, frames_.at(frame_timestamp).e_seq);
|
|
|
|
|
2022-06-06 14:13:17 +00:00
|
|
|
// Reconstruction of frame from fragments
|
|
|
|
size_t fptr = 0;
|
|
|
|
|
|
|
|
// allocating the frame with start code ready saves a copy operation for the frame
|
2023-08-09 11:47:43 +00:00
|
|
|
bool start_code = !(rce_flags & RCE_NO_H26X_PREPEND_SC);
|
|
|
|
if (rtp_ctx_->get_payload() == RTP_FORMAT_V3C) {
|
|
|
|
start_code = false;
|
|
|
|
}
|
|
|
|
uvgrtp::frame::rtp_frame* complete = allocate_rtp_frame_with_startcode(start_code,
|
2022-06-06 14:13:17 +00:00
|
|
|
frame->header, get_nal_header_size() + frames_[frame_timestamp].total_size, fptr);
|
|
|
|
|
|
|
|
// construct the NAL header from fragment header of current fragment
|
|
|
|
get_nal_header_from_fu_headers(fptr, frame->payload, complete->payload); // NAL header
|
|
|
|
fptr += get_nal_header_size();
|
|
|
|
|
|
|
|
uint16_t next_from_last = frames_.at(frame_timestamp).e_seq + 1;
|
|
|
|
for (uint16_t i = frames_.at(frame_timestamp).s_seq; i != next_from_last; ++i)
|
|
|
|
{
|
|
|
|
if (fragments_[i] == nullptr)
|
|
|
|
{
|
2022-08-17 04:54:40 +00:00
|
|
|
UVG_LOG_ERROR("Missing fragment in reconstruction. Seq range: %u - %u. Missing seq %u",
|
2022-06-06 14:13:17 +00:00
|
|
|
frames_.at(frame_timestamp).s_seq, frames_.at(frame_timestamp).e_seq, i);
|
|
|
|
return RTP_GENERIC_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy everything expect fu headers (which repeat for every fu)
|
|
|
|
std::memcpy(
|
|
|
|
&complete->payload[fptr],
|
|
|
|
&fragments_[i]->payload[sizeof_fu_headers],
|
|
|
|
fragments_[i]->payload_len - sizeof_fu_headers
|
|
|
|
);
|
|
|
|
fptr += fragments_[i]->payload_len - sizeof_fu_headers;
|
|
|
|
free_fragment(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
*out = complete; // save result to output
|
2022-08-18 11:34:58 +00:00
|
|
|
|
|
|
|
// keep track of completed frames so we don't accept the same frame again
|
|
|
|
completed_ts_[frame_timestamp] = frames_.at(frame_timestamp).sframe_time;
|
2022-06-06 14:13:17 +00:00
|
|
|
frames_.erase(frame_timestamp); // erase data structures for this frame
|
|
|
|
return RTP_PKT_READY; // indicate that we have a frame ready
|
|
|
|
}
|