diff options
author | Sebastian Dröge <sebastian@centricular.com> | 2015-08-03 19:42:56 +0300 |
---|---|---|
committer | Sebastian Dröge <sebastian@centricular.com> | 2015-08-03 19:42:56 +0300 |
commit | d3dbb69c8ea317eacd3e23100b063381dbe206db (patch) | |
tree | 333dfc81551b1397c52990bada8cf2c130100f0b /sys | |
parent | bd30d516a89cc74d24bf1f5e1c11315b070d82c6 (diff) | |
download | gstreamer-plugins-bad-d3dbb69c8ea317eacd3e23100b063381dbe206db.tar.gz |
winscreencap: Properly timestamp buffers with the current clock running time instead of doing magic
Also implement framerate handling correctly by borrowing the code from
ximagesrc. GstBaseSrc::get_times() can't be used for that, we have to
implement proper waiting ourselves.
Diffstat (limited to 'sys')
-rw-r--r-- | sys/winscreencap/gstdx9screencapsrc.c | 139 | ||||
-rw-r--r-- | sys/winscreencap/gstdx9screencapsrc.h | 3 | ||||
-rw-r--r-- | sys/winscreencap/gstgdiscreencapsrc.c | 144 | ||||
-rw-r--r-- | sys/winscreencap/gstgdiscreencapsrc.h | 4 |
4 files changed, 182 insertions, 108 deletions
diff --git a/sys/winscreencap/gstdx9screencapsrc.c b/sys/winscreencap/gstdx9screencapsrc.c index ade21c7ad..9a856e0e2 100644 --- a/sys/winscreencap/gstdx9screencapsrc.c +++ b/sys/winscreencap/gstdx9screencapsrc.c @@ -76,15 +76,16 @@ static void gst_dx9screencapsrc_set_property (GObject * object, static void gst_dx9screencapsrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); -static GstCaps * gst_dx9screencapsrc_fixate (GstBaseSrc * bsrc, GstCaps * caps); +static GstCaps *gst_dx9screencapsrc_fixate (GstBaseSrc * bsrc, GstCaps * caps); static gboolean gst_dx9screencapsrc_set_caps (GstBaseSrc * bsrc, GstCaps * caps); -static GstCaps *gst_dx9screencapsrc_get_caps (GstBaseSrc * bsrc, GstCaps * filter); +static GstCaps *gst_dx9screencapsrc_get_caps (GstBaseSrc * bsrc, + GstCaps * filter); static gboolean gst_dx9screencapsrc_start (GstBaseSrc * bsrc); static gboolean gst_dx9screencapsrc_stop (GstBaseSrc * bsrc); -static void gst_dx9screencapsrc_get_times (GstBaseSrc * basesrc, - GstBuffer * buffer, GstClockTime * start, GstClockTime * end); +static gboolean gst_dx9screencapsrc_unlock (GstBaseSrc * bsrc); + static GstFlowReturn gst_dx9screencapsrc_create (GstPushSrc * src, GstBuffer ** buf); @@ -106,11 +107,11 @@ gst_dx9screencapsrc_class_init (GstDX9ScreenCapSrcClass * klass) go_class->set_property = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_set_property); go_class->get_property = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_get_property); - bs_class->get_times = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_get_times); bs_class->get_caps = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_get_caps); bs_class->set_caps = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_set_caps); bs_class->start = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_start); bs_class->stop = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_stop); + bs_class->unlock = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_unlock); bs_class->fixate = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_fixate); ps_class->create = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_create); @@ -153,7 +154,6 @@ static void gst_dx9screencapsrc_init (GstDX9ScreenCapSrc * src) { /* Set src element inital values... */ - src->frames = 0; src->surface = NULL; src->d3d9_device = NULL; src->capture_x = 0; @@ -309,7 +309,7 @@ gst_dx9screencapsrc_get_caps (GstBaseSrc * bsrc, GstCaps * filter) { GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (bsrc); RECT rect_dst; - GstCaps * caps; + GstCaps *caps; if (src->monitor >= IDirect3D9_GetAdapterCount (g_d3d9) || FAILED (IDirect3D9_GetAdapterDisplayMode (g_d3d9, src->monitor, @@ -352,7 +352,8 @@ gst_dx9screencapsrc_get_caps (GstBaseSrc * bsrc, GstCaps * filter) "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL); if (filter) { - GstCaps * tmp = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + GstCaps *tmp = + gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (caps); caps = tmp; } @@ -367,6 +368,8 @@ gst_dx9screencapsrc_start (GstBaseSrc * bsrc) D3DPRESENT_PARAMETERS d3dpp; HRESULT res; + src->frame_number = -1; + ZeroMemory (&d3dpp, sizeof (D3DPRESENT_PARAMETERS)); d3dpp.Windowed = TRUE; d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; @@ -380,8 +383,6 @@ gst_dx9screencapsrc_start (GstBaseSrc * bsrc) d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; - src->frames = 0; - res = IDirect3D9_CreateDevice (g_d3d9, src->monitor, D3DDEVTYPE_HAL, GetDesktopWindow (), D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &src->d3d9_device); @@ -407,21 +408,19 @@ gst_dx9screencapsrc_stop (GstBaseSrc * bsrc) return TRUE; } -static void -gst_dx9screencapsrc_get_times (GstBaseSrc * basesrc, - GstBuffer * buffer, GstClockTime * start, GstClockTime * end) +static gboolean +gst_dx9screencapsrc_unlock (GstBaseSrc * bsrc) { - GstClockTime timestamp; - - timestamp = GST_BUFFER_TIMESTAMP (buffer); - - if (GST_CLOCK_TIME_IS_VALID (timestamp)) { - GstClockTime duration = GST_BUFFER_DURATION (buffer); + GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (bsrc); - if (GST_CLOCK_TIME_IS_VALID (duration)) - *end = timestamp + duration; - *start = timestamp; + GST_OBJECT_LOCK (src); + if (src->clock_id) { + GST_DEBUG_OBJECT (src, "Waking up waiting clock"); + gst_clock_id_unschedule (src->clock_id); } + GST_OBJECT_UNLOCK (src); + + return TRUE; } static GstFlowReturn @@ -432,12 +431,12 @@ gst_dx9screencapsrc_create (GstPushSrc * push_src, GstBuffer ** buf) gint new_buf_size, i; gint width, height, stride; GstClock *clock; - GstClockTime time = GST_CLOCK_TIME_NONE; - GstClockTime buf_time; + GstClockTime buf_time, buf_dur; D3DLOCKED_RECT locked_rect; LPBYTE p_dst, p_src; HRESULT hres; GstMapInfo map; + guint64 frame_number; if (G_UNLIKELY (!src->d3d9_device)) { GST_ELEMENT_ERROR (src, CORE, NEGOTIATION, (NULL), @@ -447,30 +446,82 @@ gst_dx9screencapsrc_create (GstPushSrc * push_src, GstBuffer ** buf) clock = gst_element_get_clock (GST_ELEMENT (src)); if (clock != NULL) { + GstClockTime time, base_time; + /* Calculate sync time. */ - GstClockTime base_time; - GstClockTime frame_time = - gst_util_uint64_scale_int (src->frames * GST_SECOND, - src->rate_denominator, src->rate_numerator); time = gst_clock_get_time (clock); base_time = gst_element_get_base_time (GST_ELEMENT (src)); - buf_time = MAX (time - base_time, frame_time); + buf_time = time - base_time; + + if (src->rate_numerator) { + frame_number = gst_util_uint64_scale (buf_time, + src->rate_numerator, GST_SECOND * src->rate_denominator); + } else { + frame_number = -1; + } } else { buf_time = GST_CLOCK_TIME_NONE; + frame_number = -1; + } + + if (frame_number != -1 && frame_number == src->frame_number) { + GstClockID id; + GstClockReturn ret; + + /* Need to wait for the next frame */ + frame_number += 1; + + /* Figure out what the next frame time is */ + buf_time = gst_util_uint64_scale (frame_number, + src->rate_denominator * GST_SECOND, src->rate_numerator); + + id = gst_clock_new_single_shot_id (clock, + buf_time + gst_element_get_base_time (GST_ELEMENT (src))); + GST_OBJECT_LOCK (src); + src->clock_id = id; + GST_OBJECT_UNLOCK (src); + + GST_DEBUG_OBJECT (src, "Waiting for next frame time %" G_GUINT64_FORMAT, + buf_time); + ret = gst_clock_id_wait (id, NULL); + GST_OBJECT_LOCK (src); + + gst_clock_id_unref (id); + src->clock_id = NULL; + if (ret == GST_CLOCK_UNSCHEDULED) { + /* Got woken up by the unlock function */ + GST_OBJECT_UNLOCK (src); + return GST_FLOW_FLUSHING; + } + GST_OBJECT_UNLOCK (src); + + /* Duration is a complete 1/fps frame duration */ + buf_dur = + gst_util_uint64_scale_int (GST_SECOND, src->rate_denominator, + src->rate_numerator); + } else if (frame_number != -1) { + GstClockTime next_buf_time; + + GST_DEBUG_OBJECT (src, "No need to wait for next frame time %" + G_GUINT64_FORMAT " next frame = %" G_GINT64_FORMAT " prev = %" + G_GINT64_FORMAT, buf_time, frame_number, src->frame_number); + next_buf_time = gst_util_uint64_scale (frame_number + 1, + src->rate_denominator * GST_SECOND, src->rate_numerator); + /* Frame duration is from now until the next expected capture time */ + buf_dur = next_buf_time - buf_time; + } else { + buf_dur = GST_CLOCK_TIME_NONE; } + src->frame_number = frame_number; height = (src->src_rect.bottom - src->src_rect.top); width = (src->src_rect.right - src->src_rect.left); new_buf_size = width * 4 * height; - if (G_UNLIKELY (src->rate_numerator == 0 && src->frames == 1)) { - GST_DEBUG_OBJECT (src, "eos: 0 framerate, frame %d", (gint) src->frames); - return GST_FLOW_EOS; - } GST_LOG_OBJECT (src, - "creating buffer of %d bytes with %dx%d image for frame %d", - new_buf_size, width, height, (gint) src->frames); + "creating buffer of %d bytes with %dx%d image", + new_buf_size, width, height); /* Do screen capture and put it into buffer... * Aquire front buffer, and lock it @@ -506,22 +557,7 @@ gst_dx9screencapsrc_create (GstPushSrc * push_src, GstBuffer ** buf) IDirect3DSurface9_UnlockRect (src->surface); GST_BUFFER_TIMESTAMP (new_buf) = buf_time; - if (src->rate_numerator) { - GST_BUFFER_DURATION (new_buf) = - gst_util_uint64_scale_int (GST_SECOND, - src->rate_denominator, src->rate_numerator); - - if (clock) { - GST_BUFFER_DURATION (new_buf) = MAX (GST_BUFFER_DURATION (new_buf), - gst_clock_get_time (clock) - time); - } - } else { - GST_BUFFER_DURATION (new_buf) = GST_CLOCK_TIME_NONE; - } - - GST_BUFFER_OFFSET (new_buf) = src->frames; - src->frames++; - GST_BUFFER_OFFSET_END (new_buf) = src->frames; + GST_BUFFER_DURATION (new_buf) = buf_dur; if (clock != NULL) gst_object_unref (clock); @@ -529,4 +565,3 @@ gst_dx9screencapsrc_create (GstPushSrc * push_src, GstBuffer ** buf) *buf = new_buf; return GST_FLOW_OK; } - diff --git a/sys/winscreencap/gstdx9screencapsrc.h b/sys/winscreencap/gstdx9screencapsrc.h index 915d00826..07622a983 100644 --- a/sys/winscreencap/gstdx9screencapsrc.h +++ b/sys/winscreencap/gstdx9screencapsrc.h @@ -63,7 +63,8 @@ struct _GstDX9ScreenCapSrc /* Runtime variables */ RECT screen_rect; RECT src_rect; - gint64 frames; + guint64 frame_number; + GstClockID clock_id; D3DDISPLAYMODE disp_mode; IDirect3DSurface9 *surface; diff --git a/sys/winscreencap/gstgdiscreencapsrc.c b/sys/winscreencap/gstgdiscreencapsrc.c index 6eea0fbb8..92d9ec211 100644 --- a/sys/winscreencap/gstgdiscreencapsrc.c +++ b/sys/winscreencap/gstgdiscreencapsrc.c @@ -76,15 +76,15 @@ static void gst_gdiscreencapsrc_set_property (GObject * object, guint prop_id, static void gst_gdiscreencapsrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); -static GstCaps * gst_gdiscreencapsrc_fixate (GstBaseSrc * bsrc, GstCaps * caps); +static GstCaps *gst_gdiscreencapsrc_fixate (GstBaseSrc * bsrc, GstCaps * caps); static gboolean gst_gdiscreencapsrc_set_caps (GstBaseSrc * bsrc, GstCaps * caps); -static GstCaps *gst_gdiscreencapsrc_get_caps (GstBaseSrc * bsrc, GstCaps * filter); +static GstCaps *gst_gdiscreencapsrc_get_caps (GstBaseSrc * bsrc, + GstCaps * filter); static gboolean gst_gdiscreencapsrc_start (GstBaseSrc * bsrc); static gboolean gst_gdiscreencapsrc_stop (GstBaseSrc * bsrc); +static gboolean gst_gdiscreencapsrc_unlock (GstBaseSrc * bsrc); -static void gst_gdiscreencapsrc_get_times (GstBaseSrc * basesrc, - GstBuffer * buffer, GstClockTime * start, GstClockTime * end); static GstFlowReturn gst_gdiscreencapsrc_create (GstPushSrc * src, GstBuffer ** buf); @@ -109,11 +109,11 @@ gst_gdiscreencapsrc_class_init (GstGDIScreenCapSrcClass * klass) go_class->set_property = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_set_property); go_class->get_property = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_get_property); - bs_class->get_times = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_get_times); bs_class->get_caps = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_get_caps); bs_class->set_caps = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_set_caps); bs_class->start = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_start); bs_class->stop = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_stop); + bs_class->unlock = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_unlock); bs_class->fixate = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_fixate); ps_class->create = GST_DEBUG_FUNCPTR (gst_gdiscreencapsrc_create); @@ -160,8 +160,6 @@ static void gst_gdiscreencapsrc_init (GstGDIScreenCapSrc * src) { /* Set src element inital values... */ - - src->frames = 0; src->dibMem = NULL; src->hBitmap = (HBITMAP) INVALID_HANDLE_VALUE; src->memDC = (HDC) INVALID_HANDLE_VALUE; @@ -372,9 +370,10 @@ gst_gdiscreencapsrc_get_caps (GstBaseSrc * bsrc, GstCaps * filter) "height", G_TYPE_INT, rect_dst.bottom - rect_dst.top, "framerate", GST_TYPE_FRACTION_RANGE, 1, G_MAXINT, G_MAXINT, 1, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL); - + if (filter) { - GstCaps * tmp = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + GstCaps *tmp = + gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (caps); caps = tmp; } @@ -387,7 +386,7 @@ gst_gdiscreencapsrc_start (GstBaseSrc * bsrc) { GstGDIScreenCapSrc *src = GST_GDISCREENCAPSRC (bsrc); - src->frames = 0; + src->frame_number = -1; return TRUE; } @@ -397,26 +396,22 @@ gst_gdiscreencapsrc_stop (GstBaseSrc * bsrc) { GstGDIScreenCapSrc *src = GST_GDISCREENCAPSRC (bsrc); - src->frames = 0; - return TRUE; } -static void -gst_gdiscreencapsrc_get_times (GstBaseSrc * basesrc, GstBuffer * buffer, - GstClockTime * start, GstClockTime * end) +static gboolean +gst_gdiscreencapsrc_unlock (GstBaseSrc * bsrc) { - GstClockTime timestamp; - - timestamp = GST_BUFFER_TIMESTAMP (buffer); - - if (GST_CLOCK_TIME_IS_VALID (timestamp)) { - GstClockTime duration = GST_BUFFER_DURATION (buffer); + GstGDIScreenCapSrc *src = GST_GDISCREENCAPSRC (bsrc); - if (GST_CLOCK_TIME_IS_VALID (duration)) - *end = timestamp + duration; - *start = timestamp; + GST_OBJECT_LOCK (src); + if (src->clock_id) { + GST_DEBUG_OBJECT (src, "Waking up waiting clock"); + gst_clock_id_unschedule (src->clock_id); } + GST_OBJECT_UNLOCK (src); + + return TRUE; } static GstFlowReturn @@ -426,63 +421,104 @@ gst_gdiscreencapsrc_create (GstPushSrc * push_src, GstBuffer ** buf) GstBuffer *new_buf; gint new_buf_size; GstClock *clock; - GstClockTime time = GST_CLOCK_TIME_NONE; - GstClockTime base_time; + GstClockTime buf_time, buf_dur; + guint64 frame_number; if (G_UNLIKELY (!src->info.bmiHeader.biWidth || !src->info.bmiHeader.biHeight)) { GST_ELEMENT_ERROR (src, CORE, NEGOTIATION, (NULL), ("format wasn't negotiated before create function")); return GST_FLOW_NOT_NEGOTIATED; - } else if (G_UNLIKELY (src->rate_numerator == 0 && src->frames == 1)) { - GST_DEBUG_OBJECT (src, "eos: 0 framerate, frame %d", (gint) src->frames); - return GST_FLOW_EOS; } new_buf_size = GST_ROUND_UP_4 (src->info.bmiHeader.biWidth * 3) * (-src->info.bmiHeader.biHeight); GST_LOG_OBJECT (src, - "creating buffer of %d bytes with %dx%d image for frame %d", + "creating buffer of %d bytes with %dx%d image", new_buf_size, (gint) src->info.bmiHeader.biWidth, - (gint) (-src->info.bmiHeader.biHeight), (gint) src->frames); + (gint) (-src->info.bmiHeader.biHeight)); new_buf = gst_buffer_new_and_alloc (new_buf_size); + clock = gst_element_get_clock (GST_ELEMENT (src)); - if (clock) { + if (clock != NULL) { + GstClockTime time, base_time; + /* Calculate sync time. */ - GstClockTime frame_time = - gst_util_uint64_scale_int (src->frames * GST_SECOND, - src->rate_denominator, src->rate_numerator); time = gst_clock_get_time (clock); base_time = gst_element_get_base_time (GST_ELEMENT (src)); - GST_BUFFER_TIMESTAMP (new_buf) = MAX (time - base_time, frame_time); + buf_time = time - base_time; + + if (src->rate_numerator) { + frame_number = gst_util_uint64_scale (buf_time, + src->rate_numerator, GST_SECOND * src->rate_denominator); + } else { + frame_number = -1; + } } else { - GST_BUFFER_TIMESTAMP (new_buf) = GST_CLOCK_TIME_NONE; + buf_time = GST_CLOCK_TIME_NONE; + frame_number = -1; } - /* Do screen capture and put it into buffer... */ - gst_gdiscreencapsrc_screen_capture (src, new_buf); - - if (src->rate_numerator) { - GST_BUFFER_DURATION (new_buf) = - gst_util_uint64_scale_int (GST_SECOND, - src->rate_denominator, src->rate_numerator); - if (clock) { - GST_BUFFER_DURATION (new_buf) = - MAX (GST_BUFFER_DURATION (new_buf), - gst_clock_get_time (clock) - time); + if (frame_number != -1 && frame_number == src->frame_number) { + GstClockID id; + GstClockReturn ret; + + /* Need to wait for the next frame */ + frame_number += 1; + + /* Figure out what the next frame time is */ + buf_time = gst_util_uint64_scale (frame_number, + src->rate_denominator * GST_SECOND, src->rate_numerator); + + id = gst_clock_new_single_shot_id (clock, + buf_time + gst_element_get_base_time (GST_ELEMENT (src))); + GST_OBJECT_LOCK (src); + src->clock_id = id; + GST_OBJECT_UNLOCK (src); + + GST_DEBUG_OBJECT (src, "Waiting for next frame time %" G_GUINT64_FORMAT, + buf_time); + ret = gst_clock_id_wait (id, NULL); + GST_OBJECT_LOCK (src); + + gst_clock_id_unref (id); + src->clock_id = NULL; + if (ret == GST_CLOCK_UNSCHEDULED) { + /* Got woken up by the unlock function */ + GST_OBJECT_UNLOCK (src); + return GST_FLOW_FLUSHING; } + GST_OBJECT_UNLOCK (src); + + /* Duration is a complete 1/fps frame duration */ + buf_dur = + gst_util_uint64_scale_int (GST_SECOND, src->rate_denominator, + src->rate_numerator); + } else if (frame_number != -1) { + GstClockTime next_buf_time; + + GST_DEBUG_OBJECT (src, "No need to wait for next frame time %" + G_GUINT64_FORMAT " next frame = %" G_GINT64_FORMAT " prev = %" + G_GINT64_FORMAT, buf_time, frame_number, src->frame_number); + next_buf_time = gst_util_uint64_scale (frame_number + 1, + src->rate_denominator * GST_SECOND, src->rate_numerator); + /* Frame duration is from now until the next expected capture time */ + buf_dur = next_buf_time - buf_time; } else { - /* NONE means forever */ - GST_BUFFER_DURATION (new_buf) = GST_CLOCK_TIME_NONE; + buf_dur = GST_CLOCK_TIME_NONE; } - gst_object_unref (clock); + src->frame_number = frame_number; - GST_BUFFER_OFFSET (new_buf) = src->frames; - src->frames++; - GST_BUFFER_OFFSET_END (new_buf) = src->frames; + GST_BUFFER_TIMESTAMP (new_buf) = buf_time; + GST_BUFFER_DURATION (new_buf) = buf_dur; + + /* Do screen capture and put it into buffer... */ + gst_gdiscreencapsrc_screen_capture (src, new_buf); + + gst_object_unref (clock); *buf = new_buf; return GST_FLOW_OK; diff --git a/sys/winscreencap/gstgdiscreencapsrc.h b/sys/winscreencap/gstgdiscreencapsrc.h index 492785bbb..085e53288 100644 --- a/sys/winscreencap/gstgdiscreencapsrc.h +++ b/sys/winscreencap/gstgdiscreencapsrc.h @@ -62,7 +62,9 @@ struct _GstGDIScreenCapSrc /* Runtime variables */ RECT screen_rect; RECT src_rect; - gint64 frames; + guint64 frame_number; + GstClockID clock_id; + BITMAPINFO info; BYTE *dibMem; HBITMAP hBitmap; |