Skip to content

Commit

Permalink
st40/tx: add interlaced support (#708)
Browse files Browse the repository at this point in the history
also add empty frame(ANC_Count: 0) support

Signed-off-by: Frank Du <[email protected]>
  • Loading branch information
frankdjx authored Jan 16, 2024
1 parent 7aa9ba8 commit 821802c
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 12 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* log: add custom log printer, see mtl_set_log_printer.
* rx/timing_parser: add timing_parser stat report for RX video, `--rx_timing_parser` in RxTxApp to enable.
* pipeline: add block get mode support, see `ST20P_TX_FLAG_BLOCK_GET`/`ST20P_RX_FLAG_BLOCK_GET`/`ST22P_TX_FLAG_BLOCK_GET`/`ST22P_RX_FLAG_BLOCK_GET`.
* rx/timing_parser: add support to export the timing_parser to app, see `app/sample/rx_st20p_timing_parser_sample.c` for usage
* rx/timing_parser: add support to export the timing_parser to app, see `app/sample/rx_st20p_timing_parser_sample.c` for usage.
* st40: add interlaced support.

## Changelog for 23.12

Expand Down
12 changes: 12 additions & 0 deletions app/src/parse_json.c
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,12 @@ static int st_json_parse_tx_anc(int idx, json_object* anc_obj,
return -ST_JSON_NOT_VALID;
}

/* parse anc interlaced */
json_object* anc_interlaced = st_json_object_object_get(anc_obj, "interlaced");
if (anc_interlaced) {
anc->info.interlaced = json_object_get_boolean(anc_interlaced);
}

/* parse anc url */
ret = parse_url(anc_obj, "ancillary_url", anc->info.anc_url);
if (ret < 0) return ret;
Expand Down Expand Up @@ -1121,6 +1127,12 @@ static int st_json_parse_rx_anc(int idx, json_object* anc_obj,
anc->base.payload_type = ST_APP_PAYLOAD_TYPE_ANCILLARY;
}

/* parse anc interlaced */
json_object* anc_interlaced = st_json_object_object_get(anc_obj, "interlaced");
if (anc_interlaced) {
anc->info.interlaced = json_object_get_boolean(anc_interlaced);
}

/* parse enable rtcp */
anc->enable_rtcp =
json_object_get_boolean(st_json_object_object_get(anc_obj, "enable_rtcp"));
Expand Down
1 change: 1 addition & 0 deletions app/src/parse_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ typedef struct st_json_ancillary_info {
enum st40_type type;
enum anc_format anc_format;
enum st_fps anc_fps;
bool interlaced;

char anc_url[ST_APP_URL_MAX_LEN];
} st_json_ancillary_info_t;
Expand Down
1 change: 1 addition & 0 deletions app/src/rx_ancillary_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ static int app_rx_anc_init(struct st_app_context* ctx, st_json_ancillary_session
}
ops.rtp_ring_size = 1024;
ops.payload_type = anc ? anc->base.payload_type : ST_APP_PAYLOAD_TYPE_ANCILLARY;
ops.interlaced = anc ? anc->info.interlaced : false;
ops.notify_rtp_ready = app_rx_anc_rtp_ready;
if (anc && anc->enable_rtcp) ops.flags |= ST40_RX_FLAG_ENABLE_RTCP;
st_pthread_mutex_init(&s->st40_wake_mutex, NULL);
Expand Down
1 change: 1 addition & 0 deletions app/src/tx_ancillary_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ static int app_tx_anc_init(struct st_app_context* ctx, st_json_ancillary_session
ops.fps = anc ? anc->info.anc_fps : ST_FPS_P59_94;
s->st40_pcap_input = false;
ops.type = anc ? anc->info.type : ST40_TYPE_FRAME_LEVEL;
ops.interlaced = anc ? anc->info.interlaced : false;
ops.payload_type = anc ? anc->base.payload_type : ST_APP_PAYLOAD_TYPE_ANCILLARY;
/* select rtp type for pcap file or tx_video_rtp_ring_size */
if (strstr(s->st40_source_url, ".pcap")) {
Expand Down
8 changes: 8 additions & 0 deletions doc/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,14 @@ For applications requiring access to the timing parser results for each frame, t
It also features a sample timing parser UI constructed using the IMTL Python bindings, which can be found at [rx_timing_parser.py](../python/example/rx_timing_parser.py).
### 6.17 ST40(ancillary) Interlaced support
In iMTL, similar to st20 video, each field is treated as an individual frame, with fields being transmitted separately over the network, to avoid the need for new APIs specifically for fields.
For instance, with a 1080i50 format, you should use the following session parameters when creating a session: `interlaced: true, fps: ST_FPS_P50`.
For transmission (TX), users can specify whether the current field is the first or second by using the `second_field` flag within the `struct st40_tx_frame_meta`. For reception (RX), RTP passthrough mode is the only supported, it's application's duty to check the if it's the first or second by inspecting the F bits in rfc8331 header.
## 7. Misc
### 7.1 Logging
Expand Down
6 changes: 6 additions & 0 deletions include/st40_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ struct st40_tx_frame_meta {
uint64_t timestamp;
/** epoch */
uint64_t epoch;
/** Second field type indicate for interlaced mode, set by user */
bool second_field;
};

/**
Expand All @@ -241,6 +243,8 @@ struct st40_tx_ops {
enum st_fps fps;
/** Mandatory. 7 bits payload type define in RFC3550 */
uint8_t payload_type;
/** Mandatory. interlaced or not */
bool interlaced;

/** Optional. Synchronization source defined in RFC3550, if zero the session will assign
* a random value */
Expand Down Expand Up @@ -314,6 +318,8 @@ struct st40_rx_ops {

/** Mandatory. 7 bits payload type define in RFC3550 */
uint8_t payload_type;
/** Mandatory. interlaced or not */
bool interlaced;

/** Optional. source filter IP address of multicast */
uint8_t mcast_sip_addr[MTL_SESSION_PORT_MAX][MTL_IP_ADDR_LEN];
Expand Down
8 changes: 8 additions & 0 deletions lib/src/st2110/st_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,7 @@ struct st_tx_ancillary_session_impl {
int inflight_cnt[MTL_SESSION_PORT_MAX]; /* for stats */
struct rte_ring* packet_ring;
bool time_measure;
bool second_field;

uint32_t max_pkt_len; /* max data len(byte) for each pkt */

Expand Down Expand Up @@ -1020,6 +1021,9 @@ struct st_tx_ancillary_session_impl {
uint32_t stat_max_notify_frame_us;
/* for tasklet session time measure */
struct mt_stat_u64 stat_time;
/* interlace */
uint32_t stat_interlace_first_field;
uint32_t stat_interlace_second_field;
};

struct st_tx_ancillary_sessions_mgr {
Expand Down Expand Up @@ -1074,6 +1078,10 @@ struct st_rx_ancillary_session_impl {
uint32_t stat_max_notify_rtp_us;
/* for tasklet session time measure */
struct mt_stat_u64 stat_time;
/* for interlace */
uint32_t stat_interlace_first_field;
uint32_t stat_interlace_second_field;
int stat_pkts_wrong_interlace_dropped;
};

struct st_rx_ancillary_sessions_mgr {
Expand Down
32 changes: 31 additions & 1 deletion lib/src/st2110/st_rx_ancillary_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ static int rx_ancillary_session_handle_pkt(struct mtl_main_impl* impl,
rte_pktmbuf_mtod_offset(mbuf, struct st_rfc3550_rtp_hdr*, hdr_offset);
uint16_t seq_id = ntohs(rtp->seq_number);
uint8_t payload_type = rtp->payload_type;
struct st40_rfc8331_rtp_hdr* rfc8331 = (struct st40_rfc8331_rtp_hdr*)rtp;
MTL_MAY_UNUSED(s_port);

if (payload_type != ops->payload_type) {
Expand All @@ -110,6 +111,23 @@ static int rx_ancillary_session_handle_pkt(struct mtl_main_impl* impl,
}
}

/* check interlace */
if (s->ops.interlaced) {
if (!(rfc8331->f & 0x2)) {
s->stat_pkts_wrong_interlace_dropped++;
return -EINVAL;
}
if (rfc8331->f & 0x1)
s->stat_interlace_second_field++;
else
s->stat_interlace_first_field++;
} else {
if (rfc8331->f) {
s->stat_pkts_wrong_interlace_dropped++;
return -EINVAL;
}
}

/* set if it is first pkt */
if (unlikely(s->latest_seq_id == -1)) s->latest_seq_id = seq_id - 1;
/* drop old packet */
Expand Down Expand Up @@ -393,7 +411,8 @@ static int rx_ancillary_session_attach(struct mtl_main_impl* impl,
}

s->attached = true;
info("%s(%d), succ\n", __func__, idx);
info("%s(%d), flags 0x%x, %s\n", __func__, idx, ops->flags,
ops->interlaced ? "interlace" : "progressive");
return 0;
}

Expand Down Expand Up @@ -425,6 +444,17 @@ static void rx_ancillary_session_stat(struct st_rx_ancillary_session_impl* s) {
s->st40_stat_pkts_wrong_ssrc_dropped);
s->st40_stat_pkts_wrong_ssrc_dropped = 0;
}
if (s->stat_pkts_wrong_interlace_dropped) {
notice("RX_ANC_SESSION(%d): wrong hdr interlace dropped pkts %d\n", idx,
s->stat_pkts_wrong_interlace_dropped);
s->stat_pkts_wrong_interlace_dropped = 0;
}
if (s->ops.interlaced) {
notice("RX_ANC_SESSION(%d): interlace first field %u second field %u\n", idx,
s->stat_interlace_first_field, s->stat_interlace_second_field);
s->stat_interlace_first_field = 0;
s->stat_interlace_second_field = 0;
}
if (s->time_measure) {
struct mt_stat_u64* stat_time = &s->stat_time;
if (stat_time->cnt) {
Expand Down
73 changes: 65 additions & 8 deletions lib/src/st2110/st_tx_ancillary_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,16 @@ static uint64_t tx_ancillary_pacing_required_tai(struct st_tx_ancillary_session_

static int tx_ancillary_session_sync_pacing(struct mtl_main_impl* impl,
struct st_tx_ancillary_session_impl* s,
bool sync, uint64_t required_tai) {
bool sync, uint64_t required_tai,
bool second_field) {
struct st_tx_ancillary_session_pacing* pacing = &s->pacing;
double frame_time = pacing->frame_time;
/* always use MTL_PORT_P for ptp now */
uint64_t ptp_time = mt_get_ptp_time(impl, MTL_PORT_P);
uint64_t next_epochs = pacing->cur_epochs + 1;
uint64_t epochs;
double to_epoch;
bool interlaced = s->ops.interlaced;

if (required_tai) {
uint64_t ptp_epochs = ptp_time / frame_time;
Expand All @@ -287,6 +289,16 @@ static int tx_ancillary_session_sync_pacing(struct mtl_main_impl* impl,
}
}

if (interlaced) {
if (second_field) { /* align to odd epoch */
if (!(epochs & 0x1)) epochs++;
s->stat_interlace_second_field++;
} else { /* align to even epoch */
if (epochs & 0x1) epochs++;
s->stat_interlace_first_field++;
}
}

to_epoch = tx_ancillary_pacing_time(pacing, epochs) - ptp_time;
if (to_epoch < 0) {
/* time bigger than the assigned epoch time */
Expand Down Expand Up @@ -322,6 +334,9 @@ static int tx_ancillary_session_init_next_meta(struct st_tx_ancillary_session_im

memset(meta, 0, sizeof(*meta));
meta->fps = ops->fps;
if (ops->interlaced) { /* init second_field but user still can customize also */
meta->second_field = s->second_field;
}
/* point to next epoch */
meta->epoch = pacing->cur_epochs + 1;
meta->tfmt = ST10_TIMESTAMP_FMT_TAI;
Expand Down Expand Up @@ -454,8 +469,17 @@ static int tx_ancillary_session_build_packet(struct st_tx_ancillary_session_impl
pkt->pkt_len = pkt->data_len;
rtp->length = htons(payload_size);
rtp->anc_count = idx - s->st40_pkt_idx;
rtp->f = 0b00;
if (s->ops.interlaced) {
if (frame_info->tc_meta.second_field)
rtp->f = 0b11;
else
rtp->f = 0b10;
} else {
rtp->f = 0b00;
}
if (idx == anc_count) rtp->base.marker = 1;
dbg("%s(%d), anc_count %d, payload_size %d\n", __func__, s->idx, anc_count,
payload_size);

udp->dgram_len = htons(pkt->pkt_len - pkt->l2_len - pkt->l3_len);
ipv4->total_length = htons(pkt->pkt_len - pkt->l2_len);
Expand Down Expand Up @@ -533,8 +557,17 @@ static int tx_ancillary_session_build_rtp_packet(struct st_tx_ancillary_session_
pkt->pkt_len = pkt->data_len;
rtp->length = htons(payload_size);
rtp->anc_count = idx - anc_idx;
rtp->f = 0b00;
if (s->ops.interlaced) {
if (frame_info->tc_meta.second_field)
rtp->f = 0b11;
else
rtp->f = 0b10;
} else {
rtp->f = 0b00;
}
if (idx == anc_count) rtp->base.marker = 1;
dbg("%s(%d), anc_count %d, payload_size %d\n", __func__, s->idx, anc_count,
payload_size);
return idx;
}

Expand Down Expand Up @@ -562,7 +595,12 @@ static int tx_ancillary_session_rtp_update_packet(struct mtl_main_impl* impl,
s->st40_pkt_idx = 0;
rte_atomic32_inc(&s->st40_stat_frame_cnt);
s->st40_rtp_time = rtp->tmstamp;
tx_ancillary_session_sync_pacing(impl, s, false, 0);
bool second_field = false;
if (s->ops.interlaced) {
struct st40_rfc8331_rtp_hdr* rfc8331 = (struct st40_rfc8331_rtp_hdr*)rtp;
second_field = (rfc8331->f == 0b11) ? true : false;
}
tx_ancillary_session_sync_pacing(impl, s, false, 0, second_field);
}
if (s->ops.flags & ST40_TX_FLAG_USER_TIMESTAMP) {
s->pacing.rtp_time_stamp = ntohl(rtp->tmstamp);
Expand Down Expand Up @@ -613,7 +651,12 @@ static int tx_ancillary_session_build_packet_chain(struct mtl_main_impl* impl,
s->st40_pkt_idx = 0;
rte_atomic32_inc(&s->st40_stat_frame_cnt);
s->st40_rtp_time = rtp->base.tmstamp;
tx_ancillary_session_sync_pacing(impl, s, false, 0);
bool second_field = false;
if (s->ops.interlaced) {
struct st40_rfc8331_rtp_hdr* rfc8331 = (struct st40_rfc8331_rtp_hdr*)&udp[1];
second_field = (rfc8331->f == 0b11) ? true : false;
}
tx_ancillary_session_sync_pacing(impl, s, false, 0, second_field);
}
if (s->ops.flags & ST40_TX_FLAG_USER_TIMESTAMP) {
s->pacing.rtp_time_stamp = ntohl(rtp->base.tmstamp);
Expand Down Expand Up @@ -744,9 +787,11 @@ static int tx_ancillary_session_tasklet_frame(struct mtl_main_impl* impl,
/* how do we split if it need two or more pkts? */
dbg("%s(%d), st40_total_pkts %d total_udw %d meta_num %u src %p\n", __func__, idx,
s->st40_total_pkts, total_udw, src->meta_num, src);
if (s->st40_total_pkts < 1) {
if (s->st40_total_pkts > 1) {
err("%s(%d), frame %u invalid st40_total_pkts %d\n", __func__, idx, next_frame_idx,
s->st40_total_pkts);
s->stat_build_ret_code = -STI_FRAME_APP_ERR_TX_FRAME;
return MTL_TASKLET_ALL_DONE;
}
}

Expand All @@ -756,13 +801,18 @@ static int tx_ancillary_session_tasklet_frame(struct mtl_main_impl* impl,
/* user timestamp control if any */
uint64_t required_tai = tx_ancillary_pacing_required_tai(s, frame->tc_meta.tfmt,
frame->tc_meta.timestamp);
tx_ancillary_session_sync_pacing(impl, s, false, required_tai);
bool second_field = frame->tc_meta.second_field;
tx_ancillary_session_sync_pacing(impl, s, false, required_tai, second_field);
if (ops->flags & ST40_TX_FLAG_USER_TIMESTAMP &&
(frame->ta_meta.tfmt == ST10_TIMESTAMP_FMT_MEDIA_CLK)) {
pacing->rtp_time_stamp = (uint32_t)frame->tc_meta.timestamp;
}
frame->tc_meta.tfmt = ST10_TIMESTAMP_FMT_TAI;
frame->tc_meta.timestamp = pacing->cur_epoch_time;
/* init to next field */
if (ops->interlaced) {
s->second_field = second_field ? false : true;
}
s->calculate_time_cursor = false; /* clear */
}

Expand Down Expand Up @@ -1407,7 +1457,8 @@ static int tx_ancillary_session_attach(struct mtl_main_impl* impl,
return ret;
}

info("%s(%d), succ\n", __func__, idx);
info("%s(%d), type %d flags 0x%x, %s\n", __func__, idx, ops->type, ops->flags,
ops->interlaced ? "interlace" : "progressive");
return 0;
}

Expand Down Expand Up @@ -1445,6 +1496,12 @@ static void tx_ancillary_session_stat(struct st_tx_ancillary_session_impl* s) {
if (frame_cnt <= 0) {
warn("TX_ANC_SESSION(%d): build ret %d\n", idx, s->stat_build_ret_code);
}
if (s->ops.interlaced) {
notice("TX_ANC_SESSION(%d): interlace first field %u second field %u\n", idx,
s->stat_interlace_first_field, s->stat_interlace_second_field);
s->stat_interlace_first_field = 0;
s->stat_interlace_second_field = 0;
}

if (s->stat_error_user_timestamp) {
notice("TX_ANC_SESSION(%d): error user timestamp %u\n", idx,
Expand Down
Loading

0 comments on commit 821802c

Please sign in to comment.