Hello experts,
I’ve attached my RTSP servsr’s creation code below
SERVER CODE
#include <gst/gst.h>
#include <gst/rtsp-server/rtsp-server.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv)
{
// ------------------------ Start GStreamer RTSP ------------------------
gst_init(&argc, &argv);
GstRTSPServer *server = gst_rtsp_server_new();
gst_rtsp_server_set_service(server, "8080");
GstRTSPMountPoints *mounts = gst_rtsp_server_get_mount_points(server);
const gchar *pipeline =
"( "
"v4l2src device=/dev/video0 ! image/jpeg,width=640,height=480 ! "
"rtpjpegpay name=pay0 pt=96 "
"alsasrc device=hw:0,0 ! audioconvert ! audioresample ! "
"rnnoise aec-enable=true fb-enable=true "
"vad-threshold=0.8 smoothing=0.8 noise-gain=-20 res-suppress-db=-10 res-ratio=2 aec-mu=0.7 aec-filter-len=4800 ! "
"audioamplify amplification=10 ! opusenc bitrate=128000 ! rtpopuspay name=pay1 pt=97 "
")";
GstRTSPMediaFactory *factory = gst_rtsp_media_factory_new();
gst_rtsp_media_factory_set_launch(factory, pipeline);
gst_rtsp_media_factory_set_shared(factory, TRUE);
gst_rtsp_mount_points_add_factory(mounts, "/idp", factory);
g_object_unref(mounts);
gst_rtsp_server_attach(server, NULL);
printf("RTSP stream (Video + Audio) at: rtsp://<IP>:8080/idp\n");
GMainLoop *loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(loop);
return 0;
}
And my rnnoise implementation code is below
/* gstrnnoise_aec.c
*
- RNNoise GStreamer plugin with improved time-domain AEC (block NLMS),
- residual echo suppression and simple feedback suppression.
- Single-channel, 48 kHz, 10 ms frames (480 samples).
- Build with:
- gcc -O3 -fPIC -Wall -shared -DPACKAGE=““rnnoise”” -DPACKAGE_VERSION=““1.0”” \
pkg-config --cflags gstreamer-1.0 gstreamer-base-1.0 gstreamer-audio-1.0\- gstrnnoise_aec.c -o libgstrnnoise_aec.so \
pkg-config --libs gstreamer-1.0 gstreamer-base-1.0 gstreamer-audio-1.0\- -lrnnoise -lm
*/
#include <gst/gst.h>
#include <gst/base/gstbasetransform.h>
#include <gst/audio/audio.h>
#include <rnnoise.h>
#include <math.h>
#include <stdint.h>
#include <string.h>
#include <glib.h>
#define FRAME_LEN 480
/* Forward prototypes */
typedef struct _GstRnnoise GstRnnoise;
static GstFlowReturn gst_rnnoise_ref_chain(GstPad *pad, GstObject *parent, GstBuffer *buf);
#define GST_TYPE_RNNOISE (gst_rnnoise_get_type())
G_DECLARE_FINAL_TYPE(GstRnnoise, gst_rnnoise, GST, RNNOISE, GstBaseTransform)
struct _GstRnnoise {
GstBaseTransform parent;
DenoiseState *st;
/* user-configurable */
gfloat vad_threshold;
gfloat noise_gain_db;
gfloat smoothing;
/* smoothing state */
float last_frame[FRAME_LEN];
gboolean smoothing_init;
/* AEC params */
gboolean aec_enable;
gfloat aec_mu;
gint aec_filter_len;
gfloat aec_leakage;
gfloat aec_coef_clip;
/* AEC state */
float *h;
float *x_buf;
int x_buf_len;
int x_buf_pos;
/* energy estimates */
float x_energy_est;
float x_energy_alpha;
float d_energy_est;
float dd_corr_est;
float dt_threshold;
/* residual echo suppression */
gfloat res_echo_suppress_db;
float res_noise_gate_ratio;
/* feedback suppression */
gboolean fb_enable;
float fb_hp_z1;
float fb_hp_x1;
float fb_hp_alpha;
/* most recent ref frame */
float ref_frame[FRAME_LEN];
gboolean ref_ready;
GMutex ref_lock;
/* the ref pad (per instance) */
GstPad *refpad;
};
G_DEFINE_TYPE(GstRnnoise, gst_rnnoise, GST_TYPE_BASE_TRANSFORM)
/* -------------------------- Capabilities -------------------------- */
static GstStaticPadTemplate src_template =
GST_STATIC_PAD_TEMPLATE(“src”,
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS("audio/x-raw, "
"format = (string) S16LE, "
"rate = (int) 48000, "
“channels = (int) 1”)
);
static GstStaticPadTemplate sink_template =
GST_STATIC_PAD_TEMPLATE(“sink”,
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS("audio/x-raw, "
"format = (string) S16LE, "
"rate = (int) 48000, "
“channels = (int) 1”)
);
static GstStaticPadTemplate ref_template =
GST_STATIC_PAD_TEMPLATE(“ref”,
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS("audio/x-raw, "
"format = (string) S16LE, "
"rate = (int) 48000, "
“channels = (int) 1”)
);
/* -------------------------- Properties ---------------------------- */
enum {
PROP_0,
PROP_VAD_THRESHOLD,
PROP_NOISE_GAIN,
PROP_SMOOTHING,
PROP_AEC_ENABLE,
PROP_AEC_MU,
PROP_AEC_FILTER_LEN,
PROP_AEC_LEAKAGE,
PROP_AEC_CLIP,
PROP_RES_SUPPRESS_DB,
PROP_RES_RATIO,
PROP_FB_ENABLE,
};
static void gst_rnnoise_set_property(GObject *obj, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
GstRnnoise *self = GST_RNNOISE(obj);
switch (prop_id) {
case PROP_VAD_THRESHOLD:
self->vad_threshold = g_value_get_float(value);
break;
case PROP_NOISE_GAIN:
self->noise_gain_db = g_value_get_float(value);
break;
case PROP_SMOOTHING:
self->smoothing = g_value_get_float(value);
break;
case PROP_AEC_ENABLE:
self->aec_enable = g_value_get_boolean(value);
break;
case PROP_AEC_MU:
self->aec_mu = g_value_get_float(value);
break;
case PROP_AEC_FILTER_LEN: {
int newlen = g_value_get_int(value);
if (newlen < 64) newlen = 64;
if (newlen > 4800) newlen = 4800;
self->aec_filter_len = newlen;
break;
}
case PROP_AEC_LEAKAGE:
self->aec_leakage = g_value_get_float(value);
break;
case PROP_AEC_CLIP:
self->aec_coef_clip = g_value_get_float(value);
break;
case PROP_RES_SUPPRESS_DB:
self->res_echo_suppress_db = g_value_get_float(value);
break;
case PROP_RES_RATIO:
self->res_noise_gate_ratio = g_value_get_float(value);
break;
case PROP_FB_ENABLE:
self->fb_enable = g_value_get_boolean(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
}
}
static void gst_rnnoise_get_property(GObject *obj, guint prop_id,
GValue *value, GParamSpec *pspec)
{
GstRnnoise *self = GST_RNNOISE(obj);
switch (prop_id) {
case PROP_VAD_THRESHOLD:
g_value_set_float(value, self->vad_threshold);
break;
case PROP_NOISE_GAIN:
g_value_set_float(value, self->noise_gain_db);
break;
case PROP_SMOOTHING:
g_value_set_float(value, self->smoothing);
break;
case PROP_AEC_ENABLE:
g_value_set_boolean(value, self->aec_enable);
break;
case PROP_AEC_MU:
g_value_set_float(value, self->aec_mu);
break;
case PROP_AEC_FILTER_LEN:
g_value_set_int(value, self->aec_filter_len);
break;
case PROP_AEC_LEAKAGE:
g_value_set_float(value, self->aec_leakage);
break;
case PROP_AEC_CLIP:
g_value_set_float(value, self->aec_coef_clip);
break;
case PROP_RES_SUPPRESS_DB:
g_value_set_float(value, self->res_echo_suppress_db);
break;
case PROP_RES_RATIO:
g_value_set_float(value, self->res_noise_gate_ratio);
break;
case PROP_FB_ENABLE:
g_value_set_boolean(value, self->fb_enable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
}
}
/* -------------------------- Init / Finalize ----------------------- */
static void aec_alloc_filter(GstRnnoise *self);
static void gst_rnnoise_init(GstRnnoise *self)
{
self->st = rnnoise_create(NULL);
self->vad_threshold = 0.10f;
self->noise_gain_db = 0.0f;
self->smoothing = 0.15f;
self->smoothing_init = FALSE;
memset(self->last_frame, 0, sizeof(self->last_frame));
self->aec_enable = FALSE;
self->aec_mu = 0.6f;
self->aec_filter_len = 480;
self->aec_leakage = 0.9995f;
self->aec_coef_clip = 1e4f;
self->h = NULL;
self->x_buf = NULL;
self->x_buf_len = 0;
self->x_buf_pos = 0;
self->x_energy_est = 1e-8f;
self->x_energy_alpha = 0.2f;
self->d_energy_est = 1e-8f;
self->dd_corr_est = 0.0f;
self->dt_threshold = 0.1f;
self->res_echo_suppress_db = -18.0f;
self->res_noise_gate_ratio = 0.5f;
self->fb_enable = TRUE;
self->fb_hp_z1 = 0.0f;
self->fb_hp_x1 = 0.0f;
self->fb_hp_alpha = 0.995f;
memset(self->ref_frame, 0, sizeof(self->ref_frame));
self->ref_ready = FALSE;
g_mutex_init(&self->ref_lock);
self->refpad = NULL;
/* create per-instance ref pad and set chain function */
self->refpad = gst_pad_new_from_static_template(&ref_template, "ref");
if (G_UNLIKELY(!self->refpad)) {
GST_WARNING_OBJECT(self, "failed to create ref pad");
} else {
gst_pad_set_chain_function(self->refpad, GST_DEBUG_FUNCPTR(gst_rnnoise_ref_chain));
gst_element_add_pad(GST_ELEMENT(self), self->refpad);
}
}
static void gst_rnnoise_finalize(GObject *object)
{
GstRnnoise *self = GST_RNNOISE(object);
if (self->st)
rnnoise_destroy(self->st);
if (self->h)
g_free(self->h);
if (self->x_buf)
g_free(self->x_buf);
if (self->refpad)
self->refpad = NULL;
g_mutex_clear(&self->ref_lock);
G_OBJECT_CLASS(gst_rnnoise_parent_class)->finalize(object);
}
/* -------------------- AEC helpers ------------------------------- */
/* allocate filter arrays */
static void aec_alloc_filter(GstRnnoise *self)
{
if (self->h)
return;
int L = self->aec_filter_len;
self->h = g_malloc0(sizeof(float) * L);
int frame = FRAME_LEN;
self->x_buf_len = L + frame + 16;
self->x_buf = g_malloc0(sizeof(float) * self->x_buf_len);
self->x_buf_pos = 0;
self->x_energy_est = 1e-8f;
self->d_energy_est = 1e-8f;
self->dd_corr_est = 0.0f;
}
/* push ref frame into circular buffer */
static void aec_push_ref_frame(GstRnnoise *self, const float *ref)
{
if (!self->h || !self->x_buf)
aec_alloc_filter(self);
for (int i = 0; i < FRAME_LEN; i++) {
self->x_buf[self->x_buf_pos++] = ref[i];
if (self->x_buf_pos >= self->x_buf_len) self->x_buf_pos = 0;
}
}
/* return x at offset: 0 newest, 1 previous, … */
static inline float aec_x_at(const GstRnnoise *self, int offset)
{
int idx = self->x_buf_pos - 1 - offset;
if (idx < 0) idx += self->x_buf_len;
return self->x_buf[idx];
}
/* block-normalized NLMS with leakage and simple double-talk freeze */
static void aec_process_frame(GstRnnoise *self, float *d)
{
if (!self->aec_enable || !self->h || !self->x_buf)
return;
const int L = self->aec_filter_len;
const int frame = FRAME_LEN;
const float mu_norm = self->aec_mu;
const float leakage = self->aec_leakage;
const float eps = 1e-8f;
/* compute frame energies and cross-correlation (aligned newest->last sample) */
float x_energy = 0.0f, d_energy = 0.0f, cross = 0.0f;
for (int n = 0; n < frame; n++) {
float xn = aec_x_at(self, frame - 1 - n);
x_energy += xn * xn;
d_energy += d[n] * d[n];
cross += d[n] * xn;
}
/* smooth estimates */
self->x_energy_est = (1.0f - self->x_energy_alpha) * self->x_energy_est + self->x_energy_alpha * x_energy;
self->d_energy_est = (1.0f - self->x_energy_alpha) * self->d_energy_est + self->x_energy_alpha * d_energy;
self->dd_corr_est = (1.0f - self->x_energy_alpha) * self->dd_corr_est + self->x_energy_alpha * fabsf(cross);
gboolean double_talk = FALSE;
if (self->d_energy_est > eps) {
float ratio = self->dd_corr_est / (self->d_energy_est + eps);
if (ratio < self->dt_threshold) double_talk = TRUE;
}
/* apply leakage & clip */
for (int k = 0; k < L; k++) {
self->h[k] *= leakage;
if (self->h[k] > self->aec_coef_clip) self->h[k] = self->aec_coef_clip;
if (self->h[k] < -self->aec_coef_clip) self->h[k] = -self->aec_coef_clip;
}
float norm = self->x_energy_est + eps;
/* for each sample compute output and optionally adapt */
for (int n = 0; n < frame; n++) {
float y = 0.0f;
for (int k = 0; k < L; k++) {
int offset = (frame - 1 - n) + k;
float xk = aec_x_at(self, offset);
y += self->h[k] * xk;
}
float e = d[n] - y;
if (!double_talk) {
float coef = mu_norm * e / norm;
for (int k = 0; k < L; k++) {
int offset = (frame - 1 - n) + k;
float xk = aec_x_at(self, offset);
self->h[k] += coef * xk;
}
}
d[n] = e;
}
}
/* ------------------------- Ref pad chain ------------------------- */
static GstFlowReturn gst_rnnoise_ref_chain(GstPad *pad, GstObject *parent, GstBuffer *buf)
{
GstRnnoise *self = GST_RNNOISE(parent);
GstMapInfo map;
if (!gst_buffer_map(buf, &map, GST_MAP_READ)) {
gst_buffer_unref(buf);
return GST_FLOW_ERROR;
}
if (map.size % sizeof(int16_t) != 0) {
gst_buffer_unmap(buf, &map);
gst_buffer_unref(buf);
return GST_FLOW_ERROR;
}
int frames = map.size / sizeof(int16_t);
int16_t *pcm = (int16_t *)map.data;
int offset = 0;
while (offset + FRAME_LEN <= frames) {
float tmp[FRAME_LEN];
for (int j = 0; j < FRAME_LEN; j++)
tmp[j] = (float)pcm[offset + j];
g_mutex_lock(&self->ref_lock);
memcpy(self->ref_frame, tmp, sizeof(tmp));
self->ref_ready = TRUE;
if (self->aec_enable) aec_push_ref_frame(self, tmp);
g_mutex_unlock(&self->ref_lock);
offset += FRAME_LEN;
}
gst_buffer_unmap(buf, &map);
gst_buffer_unref(buf);
return GST_FLOW_OK;
}
/* --------------------- Feedback & Residual suppression ---------- */
/* simple HPF-based feedback suppression */
static void apply_feedback_suppression(GstRnnoise *self, float *frame)
{
if (!self->fb_enable) return;
for (int n = 0; n < FRAME_LEN; n++) {
float x = frame[n];
float y = self->fb_hp_alpha * (self->fb_hp_z1 + x - self->fb_hp_x1);
self->fb_hp_x1 = x;
self->fb_hp_z1 = y;
frame[n] = y;
}
}
/* residual echo suppression (time-domain gate) */
static void residual_echo_suppress(GstRnnoise *self, const float *orig_d, float *processed)
{
float mic_pow = 1e-8f, echo_pow = 1e-8f;
for (int i = 0; i < FRAME_LEN; i++) {
mic_pow += orig_d[i] * orig_d[i];
float est_echo = orig_d[i] - processed[i];
echo_pow += est_echo * est_echo;
}
float ratio = echo_pow / mic_pow;
if (ratio > self->res_noise_gate_ratio) {
float atten_lin = powf(10.0f, self->res_echo_suppress_db / 20.0f);
for (int i = 0; i < FRAME_LEN; i++)
processed[i] *= atten_lin;
}
}
/* -------------------- transform_ip (main processing) -------------- */
static GstFlowReturn gst_rnnoise_transform_ip(GstBaseTransform *base, GstBuffer *buf)
{
GstRnnoise *self = GST_RNNOISE(base);
GstMapInfo map;
if (!gst_buffer_map(buf, &map, GST_MAP_READWRITE))
return GST_FLOW_ERROR;
if (map.size % sizeof(int16_t) != 0) {
gst_buffer_unmap(buf, &map);
return GST_FLOW_ERROR;
}
int16_t *pcm = (int16_t *)map.data;
int frames = map.size / sizeof(int16_t);
float gain = powf(10.0f, self->noise_gain_db / 20.0f);
if (self->aec_enable && !self->h)
aec_alloc_filter(self);
for (int i = 0; i + FRAME_LEN <= frames; i += FRAME_LEN) {
float input[FRAME_LEN];
float after_aec[FRAME_LEN];
float rn_out[FRAME_LEN];
for (int j = 0; j < FRAME_LEN; j++)
input[j] = (float)pcm[i + j];
/* copy for residual measurement */
for (int j = 0; j < FRAME_LEN; j++)
after_aec[j] = input[j];
/* AEC pre-denoise */
if (self->aec_enable) {
gboolean have_ref = FALSE;
g_mutex_lock(&self->ref_lock);
have_ref = self->ref_ready;
g_mutex_unlock(&self->ref_lock);
if (have_ref)
aec_process_frame(self, after_aec);
}
/* RNNoise */
rnnoise_process_frame(self->st, rn_out, after_aec);
/* VAD gating placeholder (rnnoise VAD not exposed in some builds) */
(void) self->vad_threshold;
/* residual suppression */
residual_echo_suppress(self, input, rn_out);
/* feedback suppression */
apply_feedback_suppression(self, rn_out);
/* gain */
for (int j = 0; j < FRAME_LEN; j++)
rn_out[j] *= gain;
/* smoothing */
if (!self->smoothing_init) {
for (int j = 0; j < FRAME_LEN; j++)
self->last_frame[j] = rn_out[j];
self->smoothing_init = TRUE;
}
for (int j = 0; j < FRAME_LEN; j++) {
float sm = self->smoothing;
if (sm < 0.0f) sm = 0.0f;
if (sm > 1.0f) sm = 1.0f;
rn_out[j] = self->last_frame[j] * (1.0f - sm) + rn_out[j] * sm;
self->last_frame[j] = rn_out[j];
}
/* write back */
for (int j = 0; j < FRAME_LEN; j++) {
float v = rn_out[j];
if (v > 32767.0f) v = 32767.0f;
if (v < -32768.0f) v = -32768.0f;
pcm[i + j] = (int16_t)lrintf(v);
}
}
gst_buffer_unmap(buf, &map);
return GST_FLOW_OK;
}
/* --------------------------- class init -------------------------- */
static void gst_rnnoise_class_init(GstRnnoiseClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS(klass);
gobject_class->finalize = gst_rnnoise_finalize;
gobject_class->set_property = gst_rnnoise_set_property;
gobject_class->get_property = gst_rnnoise_get_property;
g_object_class_install_property(
gobject_class, PROP_VAD_THRESHOLD,
g_param_spec_float("vad-threshold", "VAD Threshold",
"Frame discarded if VAD < threshold",
0.0, 1.0, 0.10,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_NOISE_GAIN,
g_param_spec_float("noise-gain", "Noise Gain (dB)",
"Output gain after RNNoise",
-30.0, 30.0, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_SMOOTHING,
g_param_spec_float("smoothing", "Smoothing",
"Smooth output to reduce pumping (0–1)",
0.0, 1.0, 0.15,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_AEC_ENABLE,
g_param_spec_boolean("aec-enable", "Enable AEC",
"Enable adaptive echo cancellation (requires ref pad)",
FALSE, G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_AEC_MU,
g_param_spec_float("aec-mu", "AEC step size (mu)",
"Normalized NLMS adaptation step size",
0.0, 2.0, 0.6,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_AEC_FILTER_LEN,
g_param_spec_int("aec-filter-len", "AEC filter length",
"Adaptive filter length in samples (48kHz). Set before running.",
64, 4800, 480,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_AEC_LEAKAGE,
g_param_spec_float("aec-leakage", "AEC leakage",
"Leakage factor per frame for coefficients (0.0-1.0)",
0.9, 1.0, 0.9995f,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_AEC_CLIP,
g_param_spec_float("aec-clip", "AEC coef clip",
"Absolute clip value for adaptive coefficients",
0.0, 1e8, 1e4,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_RES_SUPPRESS_DB,
g_param_spec_float("res-suppress-db", "Residual echo suppress dB",
"Attenuation applied when residual echo detected (negative dB)",
-60.0, 0.0, -18.0,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_RES_RATIO,
g_param_spec_float("res-ratio", "Residual ratio",
"EchoPower/MicPower ratio threshold to trigger suppression",
0.0, 10.0, 0.5,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class, PROP_FB_ENABLE,
g_param_spec_boolean("fb-enable", "Enable feedback suppression",
"Apply simple feedback suppression HPF",
TRUE, G_PARAM_READWRITE));
gst_element_class_set_static_metadata(
element_class,
"RNNoise Noise Suppressor with NLMS AEC",
"Filter/Effect/Audio",
"Applies RNNoise with optional NLMS AEC, residual echo suppression and feedback HPF",
"ChatGPT");
gst_element_class_add_static_pad_template(element_class, &sink_template);
gst_element_class_add_static_pad_template(element_class, &src_template);
gst_element_class_add_static_pad_template(element_class, &ref_template);
trans_class->transform_ip = gst_rnnoise_transform_ip;
trans_class->passthrough_on_same_caps = FALSE;
}
/* --------------------------- plugin -------------------------------- */
#ifndef PACKAGE
#define PACKAGE “rnnoise”
#endif
#ifndef PACKAGE_VERSION
#define PACKAGE_VERSION “1.0”
#endif
static gboolean plugin_init(GstPlugin *plugin)
{
return gst_element_register(plugin, “rnnoise”, GST_RANK_NONE, gst_rnnoise_get_type());
}
GST_PLUGIN_DEFINE(
GST_VERSION_MAJOR,
GST_VERSION_MINOR,
rnnoise,
“RNNoise noise reduction plugin with NLMS AEC”,
plugin_init,
PACKAGE_VERSION,
“LGPL”,
PACKAGE,
“https://github.com/xiph/rnnoise”
)
Now I want to implent a two-way-communication using this RTSP servers stream.
Also the 2nd sender is having the same things implented. But I’m unable to remove the Echo from the feed, That is the 1st sender’s audio is coming back to 1st sender’s speaker itself. Kindly help me implement AEC on this.
I’m running this on AM625 having Yocto Linux and my kernel version is 6.6.32-g6de6e418c80e-dirty
Please help me with this.
Kind Regards,
Aditya T