Skip to content

Commit

Permalink
rx/timing_parser: export the result by st20_rx_tp_meta (#694)
Browse files Browse the repository at this point in the history
test with:
./build/app/RxVideoTimingParserSample

Signed-off-by: Frank Du <[email protected]>
  • Loading branch information
frankdjx authored Jan 9, 2024
1 parent 9403e62 commit 14b557d
Show file tree
Hide file tree
Showing 20 changed files with 662 additions and 173 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* dpdk: upgrade dpdk version to 23.11.
* st22: add interlaced support.
* 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

## Changelog for 23.12

Expand Down
7 changes: 7 additions & 0 deletions app/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ executable('TxVideoSplitSample', video_tx_split_sample_sources,
dependencies: [asan_dep, mtl, ws2_32_dep, mman_dep]
)

executable('RxVideoTimingParserSample', rx_st20p_timing_parser_sample_sources,
c_args : app_c_args,
link_args: app_ld_args,
# asan should be always the first dep
dependencies: [asan_dep, mtl, libpthread, ws2_32_dep]
)

# Legacy video samples app
executable('TxVideoSample', video_tx_sample_sources,
c_args : app_c_args,
Expand Down
1 change: 1 addition & 0 deletions app/sample/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rx_st20p_tx_st20p_downsample_merge_fwd_sources = files('fwd/rx_st20p_tx_st20p_do

# misc
video_tx_split_sample_sources = files('tx_video_split_sample.c', 'sample_util.c')
rx_st20p_timing_parser_sample_sources = files('rx_st20p_timing_parser_sample.c', 'sample_util.c')

# ext frame
pipeline_tx_st20_ext_frame_sample_sources = files('ext_frame/tx_st20_pipeline_ext_frame_sample.c', 'sample_util.c')
Expand Down
262 changes: 262 additions & 0 deletions app/sample/rx_st20p_timing_parser_sample.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
/* SPDX-License-Identifier: BSD-3-Clause
* Copyright(c) 2023 Intel Corporation
*/

#include <limits.h>

#include "sample_util.h"

struct rx_tp_stat {
/* stat report */
int32_t cinst_max;
int32_t cinst_min;
int32_t vrx_max;
int32_t vrx_min;
int32_t ipt_max;
int32_t ipt_min;
int32_t fpt_max;
int32_t fpt_min;
int32_t latency_max;
int32_t latency_min;
int32_t rtp_offset_max;
int32_t rtp_offset_min;
int32_t rtp_ts_delta_max;
int32_t rtp_ts_delta_min;
/* result */
uint32_t compliant_result[ST_RX_TP_COMPLIANT_MAX];
};

struct rx_timing_parser_sample_ctx {
int idx;
int fb_cnt;
st20p_rx_handle handle;

bool stop;
pthread_t frame_thread;
int fb_recv;

struct st20_rx_tp_pass pass;
bool pass_get; /* pass critical is only available */

/* stat report */
struct rx_tp_stat stat;
};

static void rx_st20p_tp_stat_init(struct rx_tp_stat* stat) {
memset(stat, 0, sizeof(*stat));

stat->cinst_max = INT_MIN;
stat->cinst_min = INT_MAX;
stat->vrx_max = INT_MIN;
stat->vrx_min = INT_MAX;
stat->ipt_max = INT_MIN;
stat->ipt_min = INT_MAX;
stat->fpt_min = INT_MIN;
stat->fpt_min = INT_MAX;
stat->latency_max = INT_MIN;
stat->latency_min = INT_MAX;
stat->rtp_offset_max = INT_MIN;
stat->rtp_offset_min = INT_MAX;
stat->rtp_ts_delta_max = INT_MIN;
stat->rtp_ts_delta_min = INT_MAX;
}

static void rx_st20p_tp_stat_print(struct rx_timing_parser_sample_ctx* s,
struct rx_tp_stat* stat) {
int idx = s->idx;
info("%s(%d), COMPLIANT NARROW %d WIDE %d FAILED %d!\n", __func__, idx,
stat->compliant_result[ST_RX_TP_COMPLIANT_NARROW],
stat->compliant_result[ST_RX_TP_COMPLIANT_WIDE],
stat->compliant_result[ST_RX_TP_COMPLIANT_FAILED]);
info("%s(%d), CINST MIN %d MAX %d!\n", __func__, idx, stat->cinst_min, stat->cinst_max);
info("%s(%d), VRX MIN %d MAX %d!\n", __func__, idx, stat->vrx_min, stat->vrx_max);
info("%s(%d), IPT MIN %d MAX %d!\n", __func__, idx, stat->ipt_min, stat->ipt_max);
info("%s(%d), FPT MIN %d MAX %d!\n", __func__, idx, stat->fpt_min, stat->fpt_max);
info("%s(%d), LATENCY MIN %d MAX %d!\n", __func__, idx, stat->latency_min,
stat->latency_max);
info("%s(%d), RTP OFFSET MIN %d MAX %d!\n", __func__, idx, stat->rtp_offset_min,
stat->rtp_offset_max);
info("%s(%d), RTP TS DELTA MIN %d MAX %d!\n", __func__, idx, stat->rtp_ts_delta_min,
stat->rtp_ts_delta_max);
}

static int rx_st20p_tp_consume(struct rx_timing_parser_sample_ctx* s,
struct st20_rx_tp_meta* tp) {
if (tp->compliant != ST_RX_TP_COMPLIANT_NARROW) {
dbg("%s(%d), compliant failed %d cause: %s, frame idx %d\n", __func__, s->idx,
tp->compliant, tp->failed_cause, s->fb_recv);
}

/* update stat */
struct rx_tp_stat* stat = &s->stat;
stat->vrx_min = ST_MIN(tp->vrx_min, stat->vrx_min);
stat->vrx_max = ST_MAX(tp->vrx_max, stat->vrx_max);
stat->cinst_min = ST_MIN(tp->cinst_min, stat->cinst_min);
stat->cinst_max = ST_MAX(tp->cinst_max, stat->cinst_max);
stat->ipt_min = ST_MIN(tp->ipt_min, stat->ipt_min);
stat->ipt_max = ST_MAX(tp->ipt_max, stat->ipt_max);
stat->fpt_min = ST_MIN(tp->fpt, stat->fpt_min);
stat->fpt_max = ST_MAX(tp->fpt, stat->fpt_max);
stat->latency_min = ST_MIN(tp->latency, stat->latency_min);
stat->latency_max = ST_MAX(tp->latency, stat->latency_max);
stat->rtp_offset_min = ST_MIN(tp->rtp_offset, stat->rtp_offset_min);
stat->rtp_offset_max = ST_MAX(tp->rtp_offset, stat->rtp_offset_max);
stat->rtp_ts_delta_min = ST_MIN(tp->rtp_ts_delta, stat->rtp_ts_delta_min);
stat->rtp_ts_delta_max = ST_MAX(tp->rtp_ts_delta, stat->rtp_ts_delta_max);

stat->compliant_result[tp->compliant]++;

return 0;
}

static void* rx_st20p_tp_thread(void* arg) {
struct rx_timing_parser_sample_ctx* s = arg;
st20p_rx_handle handle = s->handle;
struct st_frame* frame;
int idx = s->idx;
int ret;

info("%s(%d), start\n", __func__, idx);
while (!s->stop) {
frame = st20p_rx_get_frame(handle);
if (!frame) { /* no frame */
warn("%s(%d), get frame time out\n", __func__, idx);
continue;
}
if (!s->pass_get) {
ret = st20p_rx_timing_parser_critical(handle, &s->pass);
if (ret >= 0) {
s->pass_get = true;
info("%s(%d), pass critical, cinst narrow %d wide %d, vrx narrow %d wide %d\n",
__func__, idx, s->pass.cinst_max_narrow, s->pass.cinst_max_wide,
s->pass.vrx_max_narrow, s->pass.vrx_max_wide);
}
}
rx_st20p_tp_consume(s, frame->tp);

s->fb_recv++;
st20p_rx_put_frame(handle, frame);
}
info("%s(%d), stop\n", __func__, idx);

return NULL;
}

int main(int argc, char** argv) {
struct st_sample_context ctx;
int ret;

/* init sample(st) dev */
memset(&ctx, 0, sizeof(ctx));
ret = rx_sample_parse_args(&ctx, argc, argv);
if (ret < 0) return ret;

ctx.st = mtl_init(&ctx.param);
if (!ctx.st) {
err("%s: mtl_init fail\n", __func__);
return -EIO;
}

uint32_t session_num = ctx.sessions;
struct rx_timing_parser_sample_ctx* app[session_num];

// create and register rx session
for (int i = 0; i < session_num; i++) {
app[i] = malloc(sizeof(struct rx_timing_parser_sample_ctx));
if (!app[i]) {
err("%s(%d), app context malloc fail\n", __func__, i);
ret = -ENOMEM;
goto error;
}
memset(app[i], 0, sizeof(struct rx_timing_parser_sample_ctx));
app[i]->idx = i;
app[i]->stop = false;
app[i]->fb_cnt = ctx.framebuff_cnt;
rx_st20p_tp_stat_init(&app[i]->stat);

struct st20p_rx_ops ops_rx;
memset(&ops_rx, 0, sizeof(ops_rx));
ops_rx.name = "st20p_test";
ops_rx.priv = app[i]; // app handle register to lib
ops_rx.port.num_port = 1;
memcpy(ops_rx.port.sip_addr[MTL_SESSION_PORT_P], ctx.rx_sip_addr[MTL_PORT_P],
MTL_IP_ADDR_LEN);
snprintf(ops_rx.port.port[MTL_SESSION_PORT_P], MTL_PORT_MAX_LEN, "%s",
ctx.param.port[MTL_PORT_P]);
ops_rx.port.udp_port[MTL_SESSION_PORT_P] = ctx.udp_port + i * 2;
ops_rx.port.payload_type = ctx.payload_type;
ops_rx.width = ctx.width;
ops_rx.height = ctx.height;
ops_rx.fps = ctx.fps;
ops_rx.interlaced = ctx.interlaced;
ops_rx.transport_fmt = ctx.fmt;
ops_rx.output_fmt = ctx.output_fmt;
ops_rx.device = ST_PLUGIN_DEVICE_AUTO;
ops_rx.framebuff_cnt = app[i]->fb_cnt;
ops_rx.flags = ST20P_RX_FLAG_BLOCK_GET;
ops_rx.flags |= ST20P_RX_FLAG_TIMING_PARSER_META;

st20p_rx_handle rx_handle = st20p_rx_create(ctx.st, &ops_rx);
if (!rx_handle) {
err("%s(%d), st20p_rx_create fail\n", __func__, i);
ret = -EIO;
goto error;
}
app[i]->handle = rx_handle;

ret = pthread_create(&app[i]->frame_thread, NULL, rx_st20p_tp_thread, app[i]);
if (ret < 0) {
err("%s(%d), thread create fail %d\n", __func__, ret, i);
ret = -EIO;
goto error;
}
}

// start rx
ret = mtl_start(ctx.st);

int cnt = 0;
while (!ctx.exit) {
sleep(1);
cnt++;
if ((cnt % 10) == 0) {
for (int i = 0; i < session_num; i++) {
rx_st20p_tp_stat_print(app[i], &app[i]->stat);
rx_st20p_tp_stat_init(&app[i]->stat);
}
}
}

// stop app thread
for (int i = 0; i < session_num; i++) {
app[i]->stop = true;
pthread_join(app[i]->frame_thread, NULL);
info("%s(%d), received frames %d\n", __func__, i, app[i]->fb_recv);
}

// stop rx
ret = mtl_stop(ctx.st);

// check result
for (int i = 0; i < session_num; i++) {
if (app[i]->fb_recv <= 0) {
err("%s(%d), error, no received frames %d\n", __func__, i, app[i]->fb_recv);
ret = -EIO;
}
}

error:
for (int i = 0; i < session_num; i++) {
if (app[i]) {
if (app[i]->handle) st20p_rx_free(app[i]->handle);
free(app[i]);
}
}

/* release sample(st) dev */
if (ctx.st) {
mtl_uninit(ctx.st);
ctx.st = NULL;
}
return ret;
}
1 change: 1 addition & 0 deletions app/sample/sample_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#endif

#define ST_MAX(a, b) ((a) > (b) ? (a) : (b))
#define ST_MIN(a, b) ((a) < (b) ? (a) : (b))

enum sample_udp_mode {
/* client/server mode */
Expand Down
1 change: 1 addition & 0 deletions app/src/app_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#define UTC_OFFSET (37) /* 2022/07 */

#define ST_MAX(a, b) ((a) > (b) ? (a) : (b))
#define ST_MIN(a, b) ((a) < (b) ? (a) : (b))

struct st_display {
char name[36];
Expand Down
2 changes: 1 addition & 1 deletion app/src/rx_st20p_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ static int app_rx_st20p_init(struct st_app_context* ctx,
/* always try to enable DMA offload */
ops.flags = ST20P_RX_FLAG_DMA_OFFLOAD;
if (st20p && st20p->enable_rtcp) ops.flags |= ST20P_RX_FLAG_ENABLE_RTCP;
if (ctx->enable_timing_parser) ops.flags |= ST20P_RX_FLAG_ENABLE_TIMING_PARSER;
if (ctx->enable_timing_parser) ops.flags |= ST20P_RX_FLAG_TIMING_PARSER_STAT;

st_pthread_mutex_init(&s->st20p_wake_mutex, NULL);
st_pthread_cond_init(&s->st20p_wake_cond, NULL);
Expand Down
2 changes: 1 addition & 1 deletion app/src/rx_video_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ static int app_rx_video_init(struct st_app_context* ctx, st_json_video_session_t
ops.flags |= ST20_RX_FLAG_ENABLE_RTCP;
ops.rtcp.nack_interval_us = 250;
}
if (ctx->enable_timing_parser) ops.flags |= ST20_RX_FLAG_ENABLE_TIMING_PARSER;
if (ctx->enable_timing_parser) ops.flags |= ST20_RX_FLAG_TIMING_PARSER_STAT;

st_pthread_mutex_init(&s->st20_wake_mutex, NULL);
st_pthread_cond_init(&s->st20_wake_cond, NULL);
Expand Down
9 changes: 9 additions & 0 deletions doc/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,15 @@ Intel® Media SDK: Leverage IMTL's robust capabilities within Intel® Media SDK
In addition to the built-in RxTxApp, IMTL also provides numerous sample codes that demonstrate how to construct simple test programs using its APIs. For more details, please refer to [sample](../app/sample/). We also provide some very useful forward application demo, detail can be found at [fwd](../app/sample/fwd/).
### 6.16 RX Timing Parser
To verify the compliance of incoming ST2110-20 RX streams with the ST2110 standard, IMTL provides several utilities for analysis. To support this functionality, if the Network Interface Card (NIC) supports the hardware time synchronization feature, IMTL will read the RX timestamp directly from the NIC's hardware to obtain accurate timing.
The fallback method is to read the time when IMTL processes the packet. However, it's important to note that this fallback method cannot guarantee timing accuracy, potentially rendering the parsed results unreliable.
The simplest method is to enable the built-in status report. An application can activate the feature by setting the flag `ST20_RX_FLAG_TIMING_PARSER_STAT` or `ST20P_RX_FLAG_TIMING_PARSER_STAT`. Subsequently, IMTL will engage the Timing Parser module and include the results in the status log.
For applications requiring access to the timing parser results for each frame, the flag `ST20_RX_FLAG_TIMING_PARSER_META` or `ST20P_RX_FLAG_TIMING_PARSER_META` can be enabled. This allows an application to retrieve detailed parsing results via the `struct st20_rx_tp_meta` found within the RX meta structure for each frame. Reference sample code is available at [rx_st20p_timing_parser_sample.c](../app/sample/rx_st20p_timing_parser_sample.c).
## 7. Misc
### 7.1 Logging
Expand Down
Loading

0 comments on commit 14b557d

Please sign in to comment.