nvcodec: Add support for CUDA IPC

Adding cudaipc{src,sink} element for CUDA IPC support.

Implementation note:
* For the communication between end points, Win32 named-pipe
and unix domain socket will be used on Windows and Linux respectively.

* cudaipcsink behaves as a server, and all GPU resources will be owned by
the server process and exported for other processes, then cudaipcsrc
(client) will import each exported handle.

* User can select IPC mode via "ipc-mode" property of cudaipcsink.
There are two IPC mode, one is "legacy" which uses legacy CUDA IPC
method and the other is "mmap" which uses CUDA virtual memory API
with OS's resource handle sharing method such as DuplicateHandle()
on Windows. The "mmap" mode might be better than "legacy" in terms
of stability since it relies on OS's resource management but
it would consume more GPU memory than "legacy" mode.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4510>
This commit is contained in:
Seungha Yang 2023-04-19 01:56:20 +09:00 committed by GStreamer Marge Bot
parent 7b1e4d6051
commit 7b6023d9cf
21 changed files with 6221 additions and 2 deletions

View file

@ -0,0 +1,554 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaipc.h"
#include <gst/cuda/gstcuda-private.h>
#include <string.h>
#include <cctype>
#include <locale>
#include <codecvt>
#include <algorithm>
#include <mutex>
#ifdef G_OS_WIN32
#include <windows.h>
#endif
#define GST_CUDA_IPC_MAGIC_NUMBER 0xC0DA10C0
constexpr guint GST_CUDA_IPC_PKT_HAVE_DATA_PAYLOAD_MIN_SIZE =
sizeof (GstClockTime) + sizeof (CUipcMemHandle) +
sizeof (GstCudaIpcMemLayout) + sizeof (guint8);
constexpr guint GST_CUDA_IPC_PKT_RELEASE_DATA_PAYLOAD_SIZE =
sizeof (CUipcMemHandle);
bool
gst_cuda_ipc_pkt_identify (std::vector < guint8 > &buf,
GstCudaIpcPacketHeader & header)
{
g_return_val_if_fail (buf.size () >= GST_CUDA_IPC_PKT_HEADER_SIZE, false);
memcpy (&header, &buf[0], GST_CUDA_IPC_PKT_HEADER_SIZE);
if (header.magic != GST_CUDA_IPC_MAGIC_NUMBER)
return false;
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE + header.payload_size);
return true;
}
bool
gst_cuda_ipc_pkt_build_config (std::vector < guint8 > &buf,
GstCudaPid pid, gboolean use_mmap, GstCaps * caps)
{
GstCudaIpcPacketHeader header;
guint8 *ptr;
gchar *caps_str = nullptr;
guint caps_size = 0;
g_return_val_if_fail (GST_IS_CAPS (caps), false);
caps_str = gst_caps_serialize (caps, GST_SERIALIZE_FLAG_NONE);
if (!caps_str)
return false;
caps_size = strlen (caps_str) + 1;
header.type = GstCudaIpcPktType::CONFIG;
header.magic = GST_CUDA_IPC_MAGIC_NUMBER;
header.payload_size = sizeof (GstCudaPid) + sizeof (gboolean) + caps_size;
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE + header.payload_size);
ptr = &buf[0];
memcpy (ptr, &header, GST_CUDA_IPC_PKT_HEADER_SIZE);
ptr += GST_CUDA_IPC_PKT_HEADER_SIZE;
*((GstCudaPid *) ptr) = pid;
ptr += sizeof (GstCudaPid);
*((gboolean *) ptr) = use_mmap;
ptr += sizeof (gboolean);
strcpy ((char *) ptr, caps_str);
g_free (caps_str);
return true;
}
bool
gst_cuda_ipc_pkt_parse_config (std::vector < guint8 > &buf, GstCudaPid * pid,
gboolean * use_mmap, GstCaps ** caps)
{
GstCudaIpcPacketHeader header;
const guint8 *ptr;
std::string str;
g_return_val_if_fail (buf.size () > GST_CUDA_IPC_PKT_HEADER_SIZE, false);
g_return_val_if_fail (caps, false);
ptr = &buf[0];
memcpy (&header, ptr, GST_CUDA_IPC_PKT_HEADER_SIZE);
if (header.type != GstCudaIpcPktType::CONFIG ||
header.magic != GST_CUDA_IPC_MAGIC_NUMBER ||
header.payload_size <= sizeof (GstCudaPid) + sizeof (gboolean)) {
return false;
}
ptr += GST_CUDA_IPC_PKT_HEADER_SIZE;
memcpy (pid, ptr, sizeof (GstCudaPid));
ptr += sizeof (GstCudaPid);
memcpy (use_mmap, ptr, sizeof (gboolean));
ptr += sizeof (gboolean);
*caps = gst_caps_from_string ((const gchar *) ptr);
if (*caps == nullptr)
return false;
return true;
}
void
gst_cuda_ipc_pkt_build_need_data (std::vector < guint8 > &buf)
{
GstCudaIpcPacketHeader header;
header.type = GstCudaIpcPktType::NEED_DATA;
header.magic = GST_CUDA_IPC_MAGIC_NUMBER;
header.payload_size = 0;
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE);
memcpy (&buf[0], &header, GST_CUDA_IPC_PKT_HEADER_SIZE);
}
/* *INDENT-OFF* */
bool
gst_cuda_ipc_pkt_build_have_data (std::vector < guint8 > &buf, GstClockTime pts,
const GstVideoInfo & info, const CUipcMemHandle & handle, GstCaps * caps)
{
GstCudaIpcPacketHeader header;
GstCudaIpcMemLayout layout;
guint8 *ptr;
gchar *caps_str = nullptr;
guint caps_size = 1;
if (caps) {
caps_str = gst_caps_serialize (caps, GST_SERIALIZE_FLAG_NONE);
if (!caps_str)
return false;
caps_size += strlen (caps_str) + 1;
}
header.type = GstCudaIpcPktType::HAVE_DATA;
header.magic = GST_CUDA_IPC_MAGIC_NUMBER;
header.payload_size = sizeof (GstClockTime) + sizeof (CUipcMemHandle) +
sizeof (GstCudaIpcMemLayout) + caps_size;
layout.size = layout.max_size = info.size;
layout.pitch = info.stride[0];
for (guint i = 0; i < 4; i++)
layout.offset[i] = info.offset[i];
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE + header.payload_size);
ptr = &buf[0];
memcpy (ptr, &header, GST_CUDA_IPC_PKT_HEADER_SIZE);
ptr += GST_CUDA_IPC_PKT_HEADER_SIZE;
memcpy (ptr, &pts, sizeof (GstClockTime));
ptr += sizeof (GstClockTime);
memcpy (ptr, &layout, sizeof (GstCudaIpcMemLayout));
ptr += sizeof (GstCudaIpcMemLayout);
memcpy (ptr, &handle, sizeof (CUipcMemHandle));
ptr += sizeof (CUipcMemHandle);
if (caps) {
*ptr = 1;
ptr++;
strcpy ((char *) ptr, caps_str);
} else {
*ptr = 0;
}
g_free (caps_str);
return true;
}
/* *INDENT-ON* */
bool
gst_cuda_ipc_pkt_parse_have_data (const std::vector < guint8 > &buf,
GstClockTime & pts, GstCudaIpcMemLayout & layout, CUipcMemHandle & handle,
GstCaps ** caps)
{
GstCudaIpcPacketHeader header;
const guint8 *ptr;
std::string str;
g_return_val_if_fail (buf.size () >=
GST_CUDA_IPC_PKT_HEADER_SIZE +
GST_CUDA_IPC_PKT_HAVE_DATA_PAYLOAD_MIN_SIZE, false);
g_return_val_if_fail (caps, false);
ptr = &buf[0];
memcpy (&header, ptr, GST_CUDA_IPC_PKT_HEADER_SIZE);
if (header.type != GstCudaIpcPktType::HAVE_DATA ||
header.magic != GST_CUDA_IPC_MAGIC_NUMBER ||
header.payload_size < GST_CUDA_IPC_PKT_HAVE_DATA_PAYLOAD_MIN_SIZE) {
return false;
}
ptr += GST_CUDA_IPC_PKT_HEADER_SIZE;
memcpy (&pts, ptr, sizeof (GstClockTime));
ptr += sizeof (GstClockTime);
memcpy (&layout, ptr, sizeof (GstCudaIpcMemLayout));
ptr += sizeof (GstCudaIpcMemLayout);
memcpy (&handle, ptr, sizeof (CUipcMemHandle));
ptr += sizeof (CUipcMemHandle);
if (*ptr) {
ptr++;
*caps = gst_caps_from_string ((const gchar *) ptr);
if (*caps == nullptr)
return false;
}
return true;
}
/* *INDENT-OFF* */
bool
gst_cuda_ipc_pkt_build_have_mmap_data (std::vector<guint8> & buf,
GstClockTime pts, const GstVideoInfo & info, guint32 max_size,
GstCudaSharableHandle handle, GstCaps * caps)
{
GstCudaIpcPacketHeader header;
GstCudaIpcMemLayout layout;
guint8 *ptr;
gchar *caps_str = nullptr;
guint caps_size = 1;
if (caps) {
caps_str = gst_caps_serialize (caps, GST_SERIALIZE_FLAG_NONE);
if (!caps_str)
return false;
caps_size = strlen (caps_str) + 1;
}
header.type = GstCudaIpcPktType::HAVE_MMAP_DATA;
header.magic = GST_CUDA_IPC_MAGIC_NUMBER;
header.payload_size = sizeof (GstClockTime) + sizeof (GstCudaIpcMemLayout) +
sizeof (GstCudaSharableHandle) + caps_size;
layout.size = info.size;
layout.max_size = max_size;
layout.pitch = info.stride[0];
for (guint i = 0; i < 4; i++)
layout.offset[i] = info.offset[i];
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE + header.payload_size);
ptr = &buf[0];
memcpy (ptr, &header, GST_CUDA_IPC_PKT_HEADER_SIZE);
ptr += GST_CUDA_IPC_PKT_HEADER_SIZE;
memcpy (ptr, &pts, sizeof (GstClockTime));
ptr += sizeof (GstClockTime);
memcpy (ptr, &layout, sizeof (GstCudaIpcMemLayout));
ptr += sizeof (GstCudaIpcMemLayout);
*((GstCudaSharableHandle *) ptr) = handle;
ptr += sizeof (GstCudaSharableHandle);
if (caps) {
*ptr = 1;
ptr++;
strcpy ((char *) ptr, caps_str);
} else {
*ptr = 0;
}
g_free (caps_str);
return true;
}
/* *INDENT-ON* */
bool
gst_cuda_ipc_pkt_parse_have_mmap_data (const std::vector < guint8 > &buf,
GstClockTime & pts, GstCudaIpcMemLayout & layout,
GstCudaSharableHandle * handle, GstCaps ** caps)
{
GstCudaIpcPacketHeader header;
const guint8 *ptr;
std::string str;
g_return_val_if_fail (buf.size () >
GST_CUDA_IPC_PKT_HEADER_SIZE +
sizeof (GstClockTime) + sizeof (GstCudaIpcMemLayout) +
sizeof (GstCudaSharableHandle), false);
g_return_val_if_fail (caps, false);
ptr = &buf[0];
memcpy (&header, ptr, GST_CUDA_IPC_PKT_HEADER_SIZE);
if (header.type != GstCudaIpcPktType::HAVE_MMAP_DATA ||
header.magic != GST_CUDA_IPC_MAGIC_NUMBER ||
header.payload_size <=
sizeof (GstClockTime) + sizeof (GstCudaIpcMemLayout) +
sizeof (GstCudaSharableHandle)) {
return false;
}
ptr += GST_CUDA_IPC_PKT_HEADER_SIZE;
memcpy (&pts, ptr, sizeof (GstClockTime));
ptr += sizeof (GstClockTime);
memcpy (&layout, ptr, sizeof (GstCudaIpcMemLayout));
ptr += sizeof (GstCudaIpcMemLayout);
*handle = *((GstCudaSharableHandle *) ptr);
ptr += sizeof (GstCudaSharableHandle);
if (*ptr) {
ptr++;
*caps = gst_caps_from_string ((const gchar *) ptr);
if (*caps == nullptr)
return false;
}
return true;
}
void
gst_cuda_ipc_pkt_build_read_done (std::vector < guint8 > &buf)
{
GstCudaIpcPacketHeader header;
header.type = GstCudaIpcPktType::READ_DONE;
header.magic = GST_CUDA_IPC_MAGIC_NUMBER;
header.payload_size = 0;
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE);
memcpy (&buf[0], &header, GST_CUDA_IPC_PKT_HEADER_SIZE);
}
void
gst_cuda_ipc_pkt_build_release_data (std::vector < guint8 > &buf,
const CUipcMemHandle & handle)
{
GstCudaIpcPacketHeader header;
guint8 *ptr;
header.type = GstCudaIpcPktType::RELEASE_DATA;
header.magic = GST_CUDA_IPC_MAGIC_NUMBER;
header.payload_size = sizeof (CUipcMemHandle);
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE +
GST_CUDA_IPC_PKT_RELEASE_DATA_PAYLOAD_SIZE);
ptr = &buf[0];
memcpy (ptr, &header, GST_CUDA_IPC_PKT_HEADER_SIZE);
ptr += GST_CUDA_IPC_PKT_HEADER_SIZE;
memcpy (ptr, &handle, GST_CUDA_IPC_PKT_RELEASE_DATA_PAYLOAD_SIZE);
}
bool
gst_cuda_ipc_pkt_parse_release_data (std::vector < guint8 > &buf,
CUipcMemHandle & handle)
{
g_return_val_if_fail (buf.size () >=
GST_CUDA_IPC_PKT_HEADER_SIZE + GST_CUDA_IPC_PKT_RELEASE_DATA_PAYLOAD_SIZE,
false);
memcpy (&handle, &buf[0] + GST_CUDA_IPC_PKT_HEADER_SIZE,
GST_CUDA_IPC_PKT_RELEASE_DATA_PAYLOAD_SIZE);
return true;
}
void
gst_cuda_ipc_pkt_build_release_mmap_data (std::vector < guint8 > &buf,
GstCudaSharableHandle handle)
{
GstCudaIpcPacketHeader header;
guint8 *ptr;
header.type = GstCudaIpcPktType::RELEASE_MMAP_DATA;
header.magic = GST_CUDA_IPC_MAGIC_NUMBER;
header.payload_size = sizeof (GstCudaSharableHandle);
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE + header.payload_size);
ptr = &buf[0];
memcpy (ptr, &header, GST_CUDA_IPC_PKT_HEADER_SIZE);
ptr += GST_CUDA_IPC_PKT_HEADER_SIZE;
*((GstCudaSharableHandle *) ptr) = handle;
}
bool
gst_cuda_ipc_pkt_parse_release_mmap_data (std::vector < guint8 > &buf,
GstCudaSharableHandle * handle)
{
guint8 *ptr;
g_return_val_if_fail (buf.size () >=
GST_CUDA_IPC_PKT_HEADER_SIZE + sizeof (GstCudaSharableHandle), false);
ptr = &buf[0];
ptr += GST_CUDA_IPC_PKT_HEADER_SIZE;
*handle = *((GstCudaSharableHandle *) ptr);
return true;
}
void
gst_cuda_ipc_pkt_build_eos (std::vector < guint8 > &buf)
{
GstCudaIpcPacketHeader header;
header.type = GstCudaIpcPktType::EOS;
header.magic = GST_CUDA_IPC_MAGIC_NUMBER;
header.payload_size = 0;
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE);
memcpy (&buf[0], &header, GST_CUDA_IPC_PKT_HEADER_SIZE);
}
void
gst_cuda_ipc_pkt_build_fin (std::vector < guint8 > &buf)
{
GstCudaIpcPacketHeader header;
header.type = GstCudaIpcPktType::FIN;
header.magic = GST_CUDA_IPC_MAGIC_NUMBER;
header.payload_size = 0;
buf.resize (GST_CUDA_IPC_PKT_HEADER_SIZE);
memcpy (&buf[0], &header, GST_CUDA_IPC_PKT_HEADER_SIZE);
}
std::string
gst_cuda_ipc_mem_handle_to_string (const CUipcMemHandle & handle)
{
std::string dump (68, '\0');
guint val[16];
for (guint i = 0; i < 16; i++) {
val[i] = *((guint *) (handle.reserved + i * 4));
}
sprintf (&dump[0], "%x%x%x%x-%x%x%x%x-%x%x%x%x-%x%x%x%x", val[0],
val[1], val[2], val[3], val[4], val[5], val[6], val[7], val[8],
val[9], val[10], val[11], val[12], val[13], val[14], val[15]);
return dump;
}
bool
gst_cuda_ipc_clock_is_system (GstClock * clock)
{
GstClockType clock_type = GST_CLOCK_TYPE_MONOTONIC;
GstClock *mclock;
if (G_OBJECT_TYPE (clock) != GST_TYPE_SYSTEM_CLOCK)
return false;
g_object_get (clock, "clock-type", &clock_type, nullptr);
if (clock_type != GST_CLOCK_TYPE_MONOTONIC)
return false;
mclock = gst_clock_get_master (clock);
if (!mclock)
return true;
gst_object_unref (mclock);
return false;
}
#ifdef G_OS_WIN32
/* *INDENT-OFF* */
static inline void rtrim(std::string &s) {
s.erase (std::find_if (s.rbegin(), s.rend(),
[](unsigned char ch) {
return !std::isspace (ch);
}).base (), s.end ());
}
/* *INDENT-ON* */
std::string
gst_cuda_ipc_win32_error_to_string (guint err)
{
wchar_t buffer[1024];
if (!FormatMessageW (FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_SYSTEM, nullptr, err, 0, buffer, 1024, nullptr)) {
return std::string ("");
}
std::wstring_convert < std::codecvt_utf8 < wchar_t >, wchar_t >converter;
std::string ret = converter.to_bytes (buffer);
rtrim (ret);
return ret;
}
#else
std::string
gst_cuda_ipc_win32_error_to_string (guint err)
{
return std::string ("");
}
#endif /* G_OS_WIN32 */
bool
gst_cuda_ipc_handle_is_equal (const CUipcMemHandle & handle,
const CUipcMemHandle & other)
{
if (memcmp (&handle, &other, sizeof (CUipcMemHandle)) == 0)
return true;
return false;
}

View file

@ -0,0 +1,184 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/cuda/gstcuda.h>
#include <string>
#include <vector>
#ifdef G_OS_WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <unistd.h>
#endif
/*
* Communication Sequence
*
* +--------+ +--------+
* | client | | server |
* +--------+ +--------+
* | |
* | |
* +<---------- CONFIG ------------|
* | |
* +--------- NEED-DATA ---------->|
* | +-------+
* | | Export
* | | CUDA memory
* | +<------+
* +<-------- HAVE-DATA -----------|
* +--------+ |
* Import | |
* CUDA memory | |
* +------->+ |
* |--------- READ-DONE ---------->|
* +--------+ |
* Release | |
* CUDA memory | |
* +--------| |
* |-------- RELEASE-DATA -------->|
* | |
* +--------- NEED-DATA ---------->|
* | |
* |<----------- EOS --------------|
* +--------+ |
* Cleanup all | |
* shared resources | |
* +--------| |
* |------------ FIN ------------->|
*/
enum class GstCudaIpcPktType : guint8
{
UNKNOWN,
CONFIG,
NEED_DATA,
HAVE_DATA,
READ_DONE,
RELEASE_DATA,
HAVE_MMAP_DATA,
RELEASE_MMAP_DATA,
EOS,
FIN,
};
#ifdef G_OS_WIN32
typedef HANDLE GstCudaSharableHandle;
typedef DWORD GstCudaPid;
#define GST_CUDA_OS_HANDLE_FORMAT "p"
#else
typedef int GstCudaSharableHandle;
typedef pid_t GstCudaPid;
#define GST_CUDA_OS_HANDLE_FORMAT "d"
struct OVERLAPPED
{
gpointer dummy;
};
#endif
#pragma pack(push, 1)
struct GstCudaIpcPacketHeader
{
GstCudaIpcPktType type;
guint32 payload_size;
guint32 magic;
};
struct GstCudaIpcMemLayout
{
guint32 size;
guint32 max_size;
guint32 pitch;
guint32 offset[4];
};
#pragma pack(pop)
constexpr guint GST_CUDA_IPC_PKT_HEADER_SIZE = sizeof (GstCudaIpcPacketHeader);
bool gst_cuda_ipc_pkt_identify (std::vector<guint8> & buf,
GstCudaIpcPacketHeader & header);
bool gst_cuda_ipc_pkt_build_config (std::vector<guint8> & buf,
GstCudaPid pid,
gboolean use_mmap,
GstCaps * caps);
bool gst_cuda_ipc_pkt_parse_config (std::vector<guint8> & buf,
GstCudaPid * pid,
gboolean * use_mmap,
GstCaps ** caps);
void gst_cuda_ipc_pkt_build_need_data (std::vector<guint8> & buf);
bool gst_cuda_ipc_pkt_build_have_data (std::vector<guint8> & buf,
GstClockTime pts,
const GstVideoInfo & info,
const CUipcMemHandle & handle,
GstCaps * caps);
bool gst_cuda_ipc_pkt_parse_have_data (const std::vector<guint8> & buf,
GstClockTime & pts,
GstCudaIpcMemLayout & layout,
CUipcMemHandle & handle,
GstCaps ** caps);
bool gst_cuda_ipc_pkt_build_have_mmap_data (std::vector<guint8> & buf,
GstClockTime pts,
const GstVideoInfo & info,
guint32 max_size,
GstCudaSharableHandle handle,
GstCaps * caps);
bool gst_cuda_ipc_pkt_parse_have_mmap_data (const std::vector<guint8> & buf,
GstClockTime & pts,
GstCudaIpcMemLayout & layout,
GstCudaSharableHandle * handle,
GstCaps ** caps);
void gst_cuda_ipc_pkt_build_read_done (std::vector<guint8> & buf);
void gst_cuda_ipc_pkt_build_release_data (std::vector<guint8> & buf,
const CUipcMemHandle & handle);
bool gst_cuda_ipc_pkt_parse_release_data (std::vector<guint8> & buf,
CUipcMemHandle & handle);
void gst_cuda_ipc_pkt_build_release_mmap_data (std::vector<guint8> & buf,
GstCudaSharableHandle handle);
bool gst_cuda_ipc_pkt_parse_release_mmap_data (std::vector<guint8> & buf,
GstCudaSharableHandle * handle);
void gst_cuda_ipc_pkt_build_eos (std::vector<guint8> & buf);
void gst_cuda_ipc_pkt_build_fin (std::vector<guint8> & buf);
std::string gst_cuda_ipc_mem_handle_to_string (const CUipcMemHandle & handle);
bool gst_cuda_ipc_clock_is_system (GstClock * clock);
std::string gst_cuda_ipc_win32_error_to_string (guint err);
bool gst_cuda_ipc_handle_is_equal (const CUipcMemHandle & handle,
const CUipcMemHandle & other);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,144 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/cuda/gstcuda.h>
#include "gstcudaipc.h"
#include <memory>
#include <vector>
G_BEGIN_DECLS
#define GST_TYPE_CUDA_IPC_CLIENT (gst_cuda_ipc_client_get_type())
#define GST_CUDA_IPC_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CUDA_IPC_CLIENT,GstCudaIpcClient))
#define GST_CUDA_IPC_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_CUDA_IPC_CLIENT,GstCudaIpcClientClass))
#define GST_CUDA_IPC_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_CUDA_IPC_CLIENT,GstCudaIpcClientClass))
#define GST_IS_CUDA_IPC_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CUDA_IPC_CLIENT))
#define GST_IS_CUDA_IPC_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_CUDA_IPC_CLIENT))
struct GstCudaIpcClientConn;
struct GstCudaIpcClientPrivate;
enum GstCudaIpcIOMode
{
GST_CUDA_IPC_IO_COPY,
GST_CUDA_IPC_IO_IMPORT,
};
#define GST_TYPE_CUDA_IPC_IO_MODE (gst_cuda_ipc_io_mode_get_type ())
GType gst_cuda_ipc_io_mode_get_type (void);
struct GstCudaIpcClient
{
GstObject parent;
GstCudaContext *context;
GstCudaStream *stream;
GstCudaIpcIOMode io_mode;
guint buffer_size;
GstCudaIpcClientPrivate *priv;
};
struct GstCudaIpcClientClass
{
GstObjectClass parent_class;
bool (*send_msg) (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn);
bool (*wait_msg) (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn);
void (*terminate) (GstCudaIpcClient * client);
void (*invoke) (GstCudaIpcClient * client);
void (*set_flushing) (GstCudaIpcClient * client,
bool flushing);
void (*loop) (GstCudaIpcClient * client);
bool (*config) (GstCudaIpcClient * client,
GstCudaPid pid,
gboolean use_mmap);
};
GType gst_cuda_ipc_client_get_type (void);
GstFlowReturn gst_cuda_ipc_client_get_sample (GstCudaIpcClient * client,
GstSample ** sample);
void gst_cuda_ipc_client_set_flushing (GstCudaIpcClient * client,
bool flushing);
GstCaps * gst_cuda_ipc_client_get_caps (GstCudaIpcClient * client);
GstFlowReturn gst_cuda_ipc_client_run (GstCudaIpcClient * client);
void gst_cuda_ipc_client_stop (GstCudaIpcClient * client);
/* subclass methods */
void gst_cuda_ipc_client_new_connection (GstCudaIpcClient * client,
std::shared_ptr<GstCudaIpcClientConn> conn);
void gst_cuda_ipc_client_send_msg_finish (GstCudaIpcClient * client,
bool result);
void gst_cuda_ipc_client_wait_msg_finish (GstCudaIpcClient * client,
bool result);
void gst_cuda_ipc_client_have_mmap_data (GstCudaIpcClient * client,
GstClockTime pts,
const GstCudaIpcMemLayout & layout,
GstCaps * caps,
GstCudaSharableHandle server_handle,
GstCudaSharableHandle client_handle);
void gst_cuda_ipc_client_on_idle (GstCudaIpcClient * client);
void gst_cuda_ipc_client_abort (GstCudaIpcClient * client);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstCudaIpcClient, gst_object_unref)
G_END_DECLS
struct GstCudaIpcClientConn : public OVERLAPPED
{
GstCudaIpcClientConn ()
{
client_msg.resize (GST_CUDA_IPC_PKT_HEADER_SIZE);
server_msg.resize (GST_CUDA_IPC_PKT_HEADER_SIZE);
}
virtual ~GstCudaIpcClientConn()
{
gst_clear_object (&context);
}
GstCudaIpcClient *client;
GstCudaContext *context = nullptr;
GstCudaIpcPktType type;
std::vector<guint8> client_msg;
std::vector<guint8> server_msg;
};

View file

@ -0,0 +1,432 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaipcclient_unix.h"
#include <string>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <gio/gunixsocketaddress.h>
#include <gio/gunixconnection.h>
GST_DEBUG_CATEGORY_EXTERN (cuda_ipc_client_debug);
#define GST_CAT_DEFAULT cuda_ipc_client_debug
/* *INDENT-OFF* */
struct GstCudaIpcClientConnUnix : public GstCudaIpcClientConn
{
GstCudaIpcClientConnUnix (GSocketConnection * socket_conn,
GCancellable * cancel)
{
conn = socket_conn;
cancellable = (GCancellable *) g_object_ref (cancel);
istream = g_io_stream_get_input_stream (G_IO_STREAM (conn));
ostream = g_io_stream_get_output_stream (G_IO_STREAM (conn));
}
~GstCudaIpcClientConnUnix ()
{
g_cancellable_cancel (cancellable);
g_object_unref (conn);
g_object_unref (cancellable);
}
GSocketConnection *conn;
GInputStream *istream;
GOutputStream *ostream;
GCancellable *cancellable;
};
struct GstCudaIpcClientUnixPrivate
{
GstCudaIpcClientUnixPrivate ()
{
main_context = g_main_context_new ();
main_loop = g_main_loop_new (main_context, FALSE);
cancellable = g_cancellable_new ();
}
~GstCudaIpcClientUnixPrivate ()
{
g_main_loop_unref (main_loop);
g_main_context_unref (main_context);
}
std::string address;
GstClockTime timeout;
std::mutex lock;
std::condition_variable cond;
GMainLoop *main_loop;
GMainContext *main_context;
GCancellable *cancellable;
bool flushing = false;
};
/* *INDENT-ON* */
struct _GstCudaIpcClientUnix
{
GstCudaIpcClient parent;
GstCudaIpcClientUnixPrivate *priv;
};
static void gst_cuda_ipc_client_unix_finalize (GObject * object);
static bool gst_cuda_ipc_client_unix_send_msg (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn);
static bool gst_cuda_ipc_client_unix_wait_msg (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn);
static void gst_cuda_ipc_client_unix_terminate (GstCudaIpcClient * client);
static void gst_cuda_ipc_client_unix_invoke (GstCudaIpcClient * client);
static void gst_cuda_ipc_client_unix_set_flushing (GstCudaIpcClient * client,
bool flushing);
static void gst_cuda_ipc_client_unix_loop (GstCudaIpcClient * client);
#define gst_cuda_ipc_client_unix_parent_class parent_class
G_DEFINE_TYPE (GstCudaIpcClientUnix,
gst_cuda_ipc_client_unix, GST_TYPE_CUDA_IPC_CLIENT);
static void
gst_cuda_ipc_client_unix_class_init (GstCudaIpcClientUnixClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstCudaIpcClientClass *client_class = GST_CUDA_IPC_CLIENT_CLASS (klass);
object_class->finalize = gst_cuda_ipc_client_unix_finalize;
client_class->send_msg =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_unix_send_msg);
client_class->wait_msg =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_unix_wait_msg);
client_class->terminate =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_unix_terminate);
client_class->invoke = GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_unix_invoke);
client_class->set_flushing =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_unix_set_flushing);
client_class->loop = GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_unix_loop);
}
static void
gst_cuda_ipc_client_unix_init (GstCudaIpcClientUnix * self)
{
self->priv = new GstCudaIpcClientUnixPrivate ();
}
static void
gst_cuda_ipc_client_unix_finalize (GObject * object)
{
GstCudaIpcClientUnix *self = GST_CUDA_IPC_CLIENT_UNIX (object);
GST_DEBUG_OBJECT (self, "finalize");
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_cuda_ipc_client_unix_send_msg_finish (GObject * source,
GAsyncResult * result, GstCudaIpcClientConnUnix * conn)
{
GstCudaIpcClient *client = conn->client;
gsize size;
GError *err = nullptr;
bool ret = true;
if (!g_output_stream_write_all_finish (conn->ostream, result, &size, &err)) {
GST_WARNING_OBJECT (client, "Write failed with %s", err->message);
g_clear_error (&err);
ret = false;
}
gst_cuda_ipc_client_send_msg_finish (client, ret);
}
static bool
gst_cuda_ipc_client_unix_send_msg (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn)
{
GstCudaIpcClientConnUnix *unix_conn =
static_cast < GstCudaIpcClientConnUnix * >(conn);
g_output_stream_write_all_async (unix_conn->ostream, &conn->client_msg[0],
conn->client_msg.size (), G_PRIORITY_DEFAULT, unix_conn->cancellable,
(GAsyncReadyCallback) gst_cuda_ipc_client_unix_send_msg_finish,
unix_conn);
return true;
}
static void
gst_cuda_ipc_client_unix_finish_have_mmap_data (GstCudaIpcClient * client,
GstCudaIpcClientConnUnix * conn)
{
GstClockTime pts;
GstCudaIpcMemLayout layout;
GstCudaSharableHandle server_handle = 0;
GstCudaSharableHandle client_handle = 0;
GstCaps *caps = nullptr;
GError *err = nullptr;
if (!gst_cuda_ipc_pkt_parse_have_mmap_data (conn->server_msg, pts,
layout, &server_handle, &caps)) {
GST_ERROR_OBJECT (client, "Couldn't parse MMAP-DATA");
goto error;
}
client_handle =
g_unix_connection_receive_fd (G_UNIX_CONNECTION (conn->conn),
conn->cancellable, &err);
if (err) {
GST_ERROR_OBJECT (client, "Couldn't get fd, %s", err->message);
goto error;
}
gst_cuda_ipc_client_have_mmap_data (client, pts, layout, caps,
server_handle, client_handle);
return;
error:
gst_cuda_ipc_client_wait_msg_finish (client, false);
}
static void
gst_cuda_ipc_client_unix_payload_finish (GObject * source,
GAsyncResult * result, GstCudaIpcClientConnUnix * conn)
{
GstCudaIpcClient *client = conn->client;
gsize size;
GError *err = nullptr;
bool ret = true;
GstCudaIpcPacketHeader header;
if (!g_input_stream_read_all_finish (conn->istream, result, &size, &err)) {
GST_WARNING_OBJECT (client, "Read failed with %s", err->message);
g_clear_error (&err);
ret = false;
} else if (!gst_cuda_ipc_pkt_identify (conn->server_msg, header)) {
GST_ERROR_OBJECT (client, "Broken header");
ret = false;
} else if (header.type == GstCudaIpcPktType::HAVE_MMAP_DATA) {
gst_cuda_ipc_client_unix_finish_have_mmap_data (client, conn);
return;
}
gst_cuda_ipc_client_wait_msg_finish (client, ret);
}
static void
gst_cuda_ipc_client_unix_wait_finish (GObject * source,
GAsyncResult * result, GstCudaIpcClientConnUnix * conn)
{
GstCudaIpcClient *client = conn->client;
gsize size;
GError *err = nullptr;
GstCudaIpcPacketHeader header;
if (!g_input_stream_read_all_finish (conn->istream, result, &size, &err)) {
GST_WARNING_OBJECT (client, "Read failed with %s", err->message);
g_clear_error (&err);
gst_cuda_ipc_client_wait_msg_finish (client, false);
return;
}
if (!gst_cuda_ipc_pkt_identify (conn->server_msg, header)) {
GST_ERROR_OBJECT (client, "Broken header");
gst_cuda_ipc_client_wait_msg_finish (client, false);
return;
}
if (header.payload_size == 0) {
gst_cuda_ipc_client_wait_msg_finish (client, true);
return;
}
GST_LOG_OBJECT (client, "Reading payload");
g_input_stream_read_all_async (conn->istream,
&conn->server_msg[0] + GST_CUDA_IPC_PKT_HEADER_SIZE, header.payload_size,
G_PRIORITY_DEFAULT, conn->cancellable,
(GAsyncReadyCallback) gst_cuda_ipc_client_unix_payload_finish, conn);
}
static bool
gst_cuda_ipc_client_unix_wait_msg (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn)
{
GstCudaIpcClientConnUnix *unix_conn =
static_cast < GstCudaIpcClientConnUnix * >(conn);
g_input_stream_read_all_async (unix_conn->istream,
&conn->server_msg[0], GST_CUDA_IPC_PKT_HEADER_SIZE,
G_PRIORITY_DEFAULT, unix_conn->cancellable,
(GAsyncReadyCallback) gst_cuda_ipc_client_unix_wait_finish, unix_conn);
return true;
}
static void
gst_cuda_ipc_client_unix_terminate (GstCudaIpcClient * client)
{
GstCudaIpcClientUnix *self = GST_CUDA_IPC_CLIENT_UNIX (client);
GstCudaIpcClientUnixPrivate *priv = self->priv;
g_main_loop_quit (priv->main_loop);
}
static gboolean
gst_cuda_ipc_client_unix_invoke_func (GstCudaIpcClient * client)
{
gst_cuda_ipc_client_on_idle (client);
return G_SOURCE_REMOVE;
}
static void
gst_cuda_ipc_client_unix_invoke (GstCudaIpcClient * client)
{
GstCudaIpcClientUnix *self = GST_CUDA_IPC_CLIENT_UNIX (client);
GstCudaIpcClientUnixPrivate *priv = self->priv;
g_main_context_invoke (priv->main_context,
(GSourceFunc) gst_cuda_ipc_client_unix_invoke_func, client);
}
static void
gst_cuda_ipc_client_unix_set_flushing (GstCudaIpcClient * client, bool flushing)
{
GstCudaIpcClientUnix *self = GST_CUDA_IPC_CLIENT_UNIX (client);
GstCudaIpcClientUnixPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
priv->flushing = flushing;
priv->cond.notify_all ();
}
static void
gst_cuda_ipc_client_unix_loop (GstCudaIpcClient * client)
{
GstCudaIpcClientUnix *self = GST_CUDA_IPC_CLIENT_UNIX (client);
GstCudaIpcClientUnixPrivate *priv = self->priv;
GSocketConnection *socket_conn;
GSocketClient *socket_client;
GSocketAddress *addr;
GError *err = nullptr;
GstClockTime start_time = gst_util_get_timestamp ();
g_main_context_push_thread_default (priv->main_context);
std::unique_lock < std::mutex > lk (priv->lock);
socket_client = g_socket_client_new ();
addr = g_unix_socket_address_new (priv->address.c_str ());
do {
GstClockTime diff;
if (priv->flushing) {
GST_DEBUG_OBJECT (self, "We are flushing");
gst_cuda_ipc_client_abort (client);
return;
}
socket_conn = g_socket_client_connect (socket_client,
G_SOCKET_CONNECTABLE (addr), priv->cancellable, &err);
if (socket_conn)
break;
if (err->code == G_IO_ERROR_CANCELLED) {
GST_DEBUG_OBJECT (self, "Operation cancelled");
g_clear_error (&err);
break;
} else {
GST_DEBUG_OBJECT (self, "Connection failed with error %s", err->message);
g_clear_error (&err);
}
if (priv->timeout > 0) {
diff = gst_util_get_timestamp () - start_time;
if (diff > priv->timeout) {
GST_WARNING_OBJECT (self, "Timeout");
break;
}
}
GST_DEBUG_OBJECT (self, "Sleep for next retry");
priv->cond.wait_for (lk, std::chrono::milliseconds (100));
} while (true);
lk.unlock ();
g_object_unref (socket_client);
g_object_unref (addr);
if (socket_conn) {
GST_DEBUG_OBJECT (self, "Connection established");
auto conn = std::make_shared < GstCudaIpcClientConnUnix > (socket_conn,
priv->cancellable);
gst_cuda_ipc_client_new_connection (client, conn);
} else {
GST_WARNING_OBJECT (self, "Connection failed");
gst_cuda_ipc_client_abort (client);
}
GST_DEBUG_OBJECT (self, "Starting loop");
g_main_loop_run (priv->main_loop);
GST_DEBUG_OBJECT (self, "Exit loop");
g_cancellable_cancel (priv->cancellable);
g_main_context_pop_thread_default (priv->main_context);
}
GstCudaIpcClient *
gst_cuda_ipc_client_new (const gchar * address, GstCudaContext * context,
GstCudaStream * stream, GstCudaIpcIOMode io_mode, guint timeout,
guint buffer_size)
{
GstCudaIpcClient *client;
GstCudaIpcClientUnix *self;
g_return_val_if_fail (address, nullptr);
g_return_val_if_fail (GST_IS_CUDA_CONTEXT (context), nullptr);
self = (GstCudaIpcClientUnix *)
g_object_new (GST_TYPE_CUDA_IPC_CLIENT_UNIX, nullptr);
gst_object_ref_sink (self);
self->priv->address = address;
self->priv->timeout = timeout * GST_SECOND;
client = GST_CUDA_IPC_CLIENT (self);
client->context = (GstCudaContext *) gst_object_ref (context);
if (stream)
client->stream = gst_cuda_stream_ref (stream);
client->io_mode = io_mode;
client->buffer_size = buffer_size;
return client;
}

View file

@ -0,0 +1,37 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "gstcudaipcclient.h"
G_BEGIN_DECLS
#define GST_TYPE_CUDA_IPC_CLIENT_UNIX (gst_cuda_ipc_client_unix_get_type())
G_DECLARE_FINAL_TYPE (GstCudaIpcClientUnix, gst_cuda_ipc_client_unix,
GST, CUDA_IPC_CLIENT_UNIX, GstCudaIpcClient);
GstCudaIpcClient * gst_cuda_ipc_client_new (const gchar * address,
GstCudaContext * context,
GstCudaStream * stream,
GstCudaIpcIOMode io_mode,
guint timeout,
guint buffer_size);
G_END_DECLS

View file

@ -0,0 +1,481 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaipcclient_win32.h"
#include <string>
#include <mutex>
#include <condition_variable>
#include <chrono>
GST_DEBUG_CATEGORY_EXTERN (cuda_ipc_client_debug);
#define GST_CAT_DEFAULT cuda_ipc_client_debug
/* *INDENT-OFF* */
struct GstCudaIpcClientConnWin32 : public GstCudaIpcClientConn
{
GstCudaIpcClientConnWin32 (HANDLE pipe) : pipe (pipe)
{
OVERLAPPED *parent = static_cast<OVERLAPPED *> (this);
parent->Internal = 0;
parent->InternalHigh = 0;
parent->Offset = 0;
parent->OffsetHigh = 0;
}
~GstCudaIpcClientConnWin32 ()
{
if (pipe != INVALID_HANDLE_VALUE) {
CancelIo (pipe);
CloseHandle (pipe);
}
}
HANDLE pipe;
};
struct GstCudaIpcClientWin32Private
{
GstCudaIpcClientWin32Private ()
{
wakeup_event = CreateEvent (nullptr, FALSE, FALSE, nullptr);
cancellable = CreateEvent (nullptr, TRUE, FALSE, nullptr);
}
~GstCudaIpcClientWin32Private ()
{
CloseHandle (wakeup_event);
CloseHandle (cancellable);
if (server_process)
CloseHandle (server_process);
}
std::string address;
GstClockTime timeout;
std::mutex lock;
std::condition_variable cond;
HANDLE wakeup_event;
HANDLE cancellable;
HANDLE server_process = nullptr;
guint last_err = ERROR_SUCCESS;
bool flushing = false;
};
/* *INDENT-ON* */
struct _GstCudaIpcClientWin32
{
GstCudaIpcClient parent;
GstCudaIpcClientWin32Private *priv;
};
static void gst_cuda_ipc_client_win32_finalize (GObject * object);
static bool gst_cuda_ipc_client_win32_send_msg (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn);
static bool gst_cuda_ipc_client_win32_wait_msg (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn);
static void gst_cuda_ipc_client_win32_terminate (GstCudaIpcClient * client);
static void gst_cuda_ipc_client_win32_invoke (GstCudaIpcClient * client);
static void gst_cuda_ipc_client_win32_set_flushing (GstCudaIpcClient * client,
bool flushing);
static void gst_cuda_ipc_client_win32_loop (GstCudaIpcClient * client);
static bool gst_cuda_ipc_client_win32_config (GstCudaIpcClient * client,
GstCudaPid pid, gboolean use_mmap);
#define gst_cuda_ipc_client_win32_parent_class parent_class
G_DEFINE_TYPE (GstCudaIpcClientWin32,
gst_cuda_ipc_client_win32, GST_TYPE_CUDA_IPC_CLIENT);
static void
gst_cuda_ipc_client_win32_class_init (GstCudaIpcClientWin32Class * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstCudaIpcClientClass *client_class = GST_CUDA_IPC_CLIENT_CLASS (klass);
object_class->finalize = gst_cuda_ipc_client_win32_finalize;
client_class->send_msg =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_win32_send_msg);
client_class->wait_msg =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_win32_wait_msg);
client_class->terminate =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_win32_terminate);
client_class->invoke = GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_win32_invoke);
client_class->set_flushing =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_win32_set_flushing);
client_class->loop = GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_win32_loop);
client_class->config = GST_DEBUG_FUNCPTR (gst_cuda_ipc_client_win32_config);
}
static void
gst_cuda_ipc_client_win32_init (GstCudaIpcClientWin32 * self)
{
self->priv = new GstCudaIpcClientWin32Private ();
}
static void
gst_cuda_ipc_client_win32_finalize (GObject * object)
{
GstCudaIpcClientWin32 *self = GST_CUDA_IPC_CLIENT_WIN32 (object);
GST_DEBUG_OBJECT (self, "finalize");
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void WINAPI
gst_cuda_ipc_client_win32_send_msg_finish (DWORD error_code, DWORD size,
OVERLAPPED * overlap)
{
GstCudaIpcClientConnWin32 *win32_conn =
static_cast < GstCudaIpcClientConnWin32 * >(overlap);
GstCudaIpcClient *client = win32_conn->client;
bool ret = true;
if (error_code != ERROR_SUCCESS) {
auto err = gst_cuda_ipc_win32_error_to_string (error_code);
GST_WARNING_OBJECT (client, "WriteFileEx callback failed with 0x%x (%s)",
(guint) error_code, err.c_str ());
ret = false;
}
gst_cuda_ipc_client_send_msg_finish (client, ret);
}
static bool
gst_cuda_ipc_client_win32_send_msg (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn)
{
GstCudaIpcClientConnWin32 *win32_conn =
static_cast < GstCudaIpcClientConnWin32 * >(conn);
if (!WriteFileEx (win32_conn->pipe, &conn->client_msg[0],
conn->client_msg.size (), win32_conn,
gst_cuda_ipc_client_win32_send_msg_finish)) {
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_WARNING_OBJECT (client, "WriteFileEx failed with 0x%x (%s)",
last_err, err.c_str ());
return false;
}
return true;
}
static void
gst_cuda_ipc_client_win32_finish_have_mmap_data (GstCudaIpcClient * client,
GstCudaIpcClientConnWin32 * win32_conn)
{
GstCudaIpcClientWin32 *self = GST_CUDA_IPC_CLIENT_WIN32 (client);
GstCudaIpcClientWin32Private *priv = self->priv;
GstClockTime pts;
GstCudaIpcMemLayout layout;
GstCudaSharableHandle server_handle = nullptr;
GstCudaSharableHandle client_handle = nullptr;
GstCaps *caps = nullptr;
if (!priv->server_process) {
GST_ERROR_OBJECT (self, "Server process handle is not available");
goto error;
}
if (!gst_cuda_ipc_pkt_parse_have_mmap_data (win32_conn->server_msg, pts,
layout, &server_handle, &caps)) {
GST_ERROR_OBJECT (self, "Couldn't parse MMAP-DATA");
goto error;
}
if (!DuplicateHandle (priv->server_process, server_handle,
GetCurrentProcess (), &client_handle, 0, FALSE,
DUPLICATE_SAME_ACCESS)) {
DWORD error_code = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (error_code);
GST_ERROR_OBJECT (self, "Couldn't duplicate handle, 0x%x (%s)",
(guint) error_code, err.c_str ());
goto error;
}
gst_cuda_ipc_client_have_mmap_data (client, pts, layout, caps,
server_handle, client_handle);
return;
error:
gst_cuda_ipc_client_wait_msg_finish (client, false);
}
static void WINAPI
gst_cuda_ipc_client_win32_payload_finish (DWORD error_code, DWORD size,
OVERLAPPED * overlap)
{
GstCudaIpcClientConnWin32 *win32_conn =
static_cast < GstCudaIpcClientConnWin32 * >(overlap);
GstCudaIpcClient *client = win32_conn->client;
GstCudaIpcPacketHeader header;
bool ret = true;
if (error_code != ERROR_SUCCESS) {
auto err = gst_cuda_ipc_win32_error_to_string (error_code);
GST_WARNING_OBJECT (client, "ReadFileEx callback failed with 0x%x (%s)",
(guint) error_code, err.c_str ());
ret = false;
} else if (!gst_cuda_ipc_pkt_identify (win32_conn->server_msg, header)) {
GST_ERROR_OBJECT (client, "Broken header");
ret = false;
} else if (header.type == GstCudaIpcPktType::HAVE_MMAP_DATA) {
gst_cuda_ipc_client_win32_finish_have_mmap_data (client, win32_conn);
return;
}
gst_cuda_ipc_client_wait_msg_finish (client, ret);
}
static void WINAPI
gst_cuda_ipc_client_win32_wait_msg_finish (DWORD error_code, DWORD size,
OVERLAPPED * overlap)
{
GstCudaIpcClientConnWin32 *win32_conn =
static_cast < GstCudaIpcClientConnWin32 * >(overlap);
GstCudaIpcClient *client = win32_conn->client;
GstCudaIpcPacketHeader header;
if (error_code != ERROR_SUCCESS) {
auto err = gst_cuda_ipc_win32_error_to_string (error_code);
GST_WARNING_OBJECT (client, "ReadFileEx callback failed with 0x%x (%s)",
(guint) error_code, err.c_str ());
gst_cuda_ipc_client_wait_msg_finish (client, false);
return;
}
if (!gst_cuda_ipc_pkt_identify (win32_conn->server_msg, header)) {
GST_ERROR_OBJECT (client, "Broken header");
gst_cuda_ipc_client_wait_msg_finish (client, false);
return;
}
if (header.payload_size == 0) {
gst_cuda_ipc_client_wait_msg_finish (client, true);
return;
}
GST_LOG_OBJECT (client, "Reading payload");
if (!ReadFileEx (win32_conn->pipe, &win32_conn->server_msg[0] +
GST_CUDA_IPC_PKT_HEADER_SIZE, header.payload_size, win32_conn,
gst_cuda_ipc_client_win32_payload_finish)) {
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_WARNING_OBJECT (client, "ReadFileEx failed with 0x%x (%s)",
last_err, err.c_str ());
gst_cuda_ipc_client_wait_msg_finish (client, false);
}
}
static bool
gst_cuda_ipc_client_win32_wait_msg (GstCudaIpcClient * client,
GstCudaIpcClientConn * conn)
{
GstCudaIpcClientConnWin32 *win32_conn =
static_cast < GstCudaIpcClientConnWin32 * >(conn);
if (!ReadFileEx (win32_conn->pipe, &conn->server_msg[0],
GST_CUDA_IPC_PKT_HEADER_SIZE, win32_conn,
gst_cuda_ipc_client_win32_wait_msg_finish)) {
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_WARNING_OBJECT (client, "ReadFileEx failed with 0x%x (%s)",
last_err, err.c_str ());
return false;
}
return true;
}
static void
gst_cuda_ipc_client_win32_terminate (GstCudaIpcClient * client)
{
GstCudaIpcClientWin32 *self = GST_CUDA_IPC_CLIENT_WIN32 (client);
GstCudaIpcClientWin32Private *priv = self->priv;
SetEvent (priv->cancellable);
}
static void
gst_cuda_ipc_client_win32_invoke (GstCudaIpcClient * client)
{
GstCudaIpcClientWin32 *self = GST_CUDA_IPC_CLIENT_WIN32 (client);
GstCudaIpcClientWin32Private *priv = self->priv;
SetEvent (priv->wakeup_event);
}
static void
gst_cuda_ipc_client_win32_set_flushing (GstCudaIpcClient * client,
bool flushing)
{
GstCudaIpcClientWin32 *self = GST_CUDA_IPC_CLIENT_WIN32 (client);
GstCudaIpcClientWin32Private *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
priv->flushing = flushing;
priv->cond.notify_all ();
}
static bool
gst_cuda_ipc_client_win32_config (GstCudaIpcClient * client, GstCudaPid pid,
gboolean use_mmap)
{
GstCudaIpcClientWin32 *self = GST_CUDA_IPC_CLIENT_WIN32 (client);
GstCudaIpcClientWin32Private *priv = self->priv;
if (!use_mmap)
return TRUE;
if (priv->server_process) {
GST_WARNING_OBJECT (self, "Have server process already");
CloseHandle (priv->server_process);
}
priv->server_process = OpenProcess (PROCESS_DUP_HANDLE, FALSE, pid);
if (priv->server_process)
return true;
DWORD error_code = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (error_code);
GST_ERROR_OBJECT (self, "Couldn't open server process, 0x%x (%s)",
(guint) error_code, err.c_str ());
return false;
}
static void
gst_cuda_ipc_client_win32_loop (GstCudaIpcClient * client)
{
GstCudaIpcClientWin32 *self = GST_CUDA_IPC_CLIENT_WIN32 (client);
GstCudaIpcClientWin32Private *priv = self->priv;
DWORD mode = PIPE_READMODE_MESSAGE;
guint wait_ret;
HANDLE pipe = INVALID_HANDLE_VALUE;
GstClockTime start_time = gst_util_get_timestamp ();
std::unique_lock < std::mutex > lk (priv->lock);
do {
GstClockTime diff;
if (priv->flushing) {
GST_DEBUG_OBJECT (self, "We are flushing");
gst_cuda_ipc_client_abort (client);
return;
}
pipe = CreateFileA (priv->address.c_str (),
GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, nullptr);
if (pipe != INVALID_HANDLE_VALUE)
break;
if (priv->timeout > 0) {
diff = gst_util_get_timestamp () - start_time;
if (diff > priv->timeout) {
GST_WARNING_OBJECT (self, "Timeout");
gst_cuda_ipc_client_abort (client);
return;
}
}
/* Retry per 100ms */
GST_DEBUG_OBJECT (self, "Sleep for next retry");
priv->cond.wait_for (lk, std::chrono::milliseconds (100));
} while (true);
lk.unlock ();
g_assert (pipe != INVALID_HANDLE_VALUE);
if (!SetNamedPipeHandleState (pipe, &mode, nullptr, nullptr)) {
priv->last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (priv->last_err);
GST_WARNING_OBJECT (self, "SetNamedPipeHandleState failed with 0x%x (%s)",
priv->last_err, err.c_str ());
CloseHandle (pipe);
gst_cuda_ipc_client_abort (client);
return;
}
auto conn = std::make_shared < GstCudaIpcClientConnWin32 > (pipe);
gst_cuda_ipc_client_new_connection (client, conn);
HANDLE waitables[] = { priv->cancellable, priv->wakeup_event };
do {
/* Enters alertable thread state and wait for I/O completion event
* or cancellable event */
wait_ret = WaitForMultipleObjectsEx (G_N_ELEMENTS (waitables), waitables,
FALSE, INFINITE, TRUE);
if (wait_ret == WAIT_OBJECT_0) {
GST_DEBUG ("Operation cancelled");
return;
}
switch (wait_ret) {
case WAIT_IO_COMPLETION:
break;
case WAIT_OBJECT_0 + 1:
gst_cuda_ipc_client_on_idle (client);
break;
default:
GST_WARNING ("Unexpected wait return 0x%x", wait_ret);
gst_cuda_ipc_client_abort (client);
return;
}
} while (true);
}
GstCudaIpcClient *
gst_cuda_ipc_client_new (const gchar * address, GstCudaContext * context,
GstCudaStream * stream, GstCudaIpcIOMode io_mode, guint timeout,
guint buffer_size)
{
GstCudaIpcClient *client;
GstCudaIpcClientWin32 *self;
g_return_val_if_fail (address, nullptr);
g_return_val_if_fail (GST_IS_CUDA_CONTEXT (context), nullptr);
self = (GstCudaIpcClientWin32 *)
g_object_new (GST_TYPE_CUDA_IPC_CLIENT_WIN32, nullptr);
gst_object_ref_sink (self);
self->priv->address = address;
self->priv->timeout = timeout * GST_SECOND;
client = GST_CUDA_IPC_CLIENT (self);
client->context = (GstCudaContext *) gst_object_ref (context);
if (stream)
client->stream = gst_cuda_stream_ref (stream);
client->io_mode = io_mode;
client->buffer_size = buffer_size;
return client;
}

View file

@ -0,0 +1,37 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "gstcudaipcclient.h"
G_BEGIN_DECLS
#define GST_TYPE_CUDA_IPC_CLIENT_WIN32 (gst_cuda_ipc_client_win32_get_type())
G_DECLARE_FINAL_TYPE (GstCudaIpcClientWin32, gst_cuda_ipc_client_win32,
GST, CUDA_IPC_CLIENT_WIN32, GstCudaIpcClient);
GstCudaIpcClient * gst_cuda_ipc_client_new (const gchar * address,
GstCudaContext * context,
GstCudaStream * stream,
GstCudaIpcIOMode io_mode,
guint timeout,
guint buffer_size);
G_END_DECLS

View file

@ -0,0 +1,683 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaipcserver.h"
#include <unordered_map>
#include <mutex>
#include <atomic>
#include <gst/cuda/gstcuda-private.h>
GST_DEBUG_CATEGORY (cuda_ipc_server_debug);
#define GST_CAT_DEFAULT cuda_ipc_server_debug
GType
gst_cuda_ipc_mode_get_type (void)
{
static GType type = 0;
static const GEnumValue ipc_modes[] = {
{GST_CUDA_IPC_LEGACY, "Legacy IPC mode", "legacy"},
{GST_CUDA_IPC_MMAP, "Memory Map", "mmap"},
{0, nullptr, nullptr}
};
GST_CUDA_CALL_ONCE_BEGIN {
type = g_enum_register_static ("GstCudaIpcMode", ipc_modes);
} GST_CUDA_CALL_ONCE_END;
return type;
}
struct GstCudaIpcServerPrivate
{
GstCudaIpcServerPrivate ()
{
shutdown = false;
aborted = false;
}
std::mutex lock;
guint64 seq_num = 0;
guint next_conn_id = 0;
std::unordered_map < guint,
std::shared_ptr < GstCudaIpcServerConn >> conn_map;
GThread *loop_thread = nullptr;
std::atomic < bool >shutdown;
std::atomic < bool >aborted;
std::shared_ptr < GstCudaIpcServerData > data;
};
static void gst_cuda_ipc_server_dispose (GObject * object);
static void gst_cuda_ipc_server_finalize (GObject * object);
#define gst_cuda_ipc_server_parent_class parent_class
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstCudaIpcServer,
gst_cuda_ipc_server, GST_TYPE_OBJECT,
GST_DEBUG_CATEGORY_INIT (cuda_ipc_server_debug, "cudaipcserver",
0, "cudaipcserver"));
static void
gst_cuda_ipc_server_class_init (GstCudaIpcServerClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gst_cuda_ipc_server_finalize;
object_class->dispose = gst_cuda_ipc_server_dispose;
}
static void
gst_cuda_ipc_server_init (GstCudaIpcServer * self)
{
self->priv = new GstCudaIpcServerPrivate ();
}
static void
gst_cuda_ipc_server_dispose (GObject * object)
{
GstCudaIpcServer *self = GST_CUDA_IPC_SERVER (object);
GstCudaIpcServerPrivate *priv = self->priv;
GstCudaIpcServerClass *klass = GST_CUDA_IPC_SERVER_GET_CLASS (self);
GST_DEBUG_OBJECT (self, "dispose");
g_assert (klass->terminate);
klass->terminate (self);
g_clear_pointer (&priv->loop_thread, g_thread_join);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_cuda_ipc_server_finalize (GObject * object)
{
GstCudaIpcServer *self = GST_CUDA_IPC_SERVER (object);
GST_DEBUG_OBJECT (self, "finalize");
gst_clear_object (&self->context);
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
GstFlowReturn
gst_cuda_ipc_server_send_data (GstCudaIpcServer * server, GstSample * sample,
const GstVideoInfo & info, const CUipcMemHandle & handle, GstClockTime pts)
{
GstCudaIpcServerPrivate *priv;
GstCudaIpcServerClass *klass;
g_return_val_if_fail (GST_IS_CUDA_IPC_SERVER (server), GST_FLOW_ERROR);
g_return_val_if_fail (GST_IS_SAMPLE (sample), GST_FLOW_ERROR);
if (server->ipc_mode != GST_CUDA_IPC_LEGACY) {
GST_ERROR_OBJECT (server, "Invalid call");
return GST_FLOW_ERROR;
}
priv = server->priv;
klass = GST_CUDA_IPC_SERVER_GET_CLASS (server);
GST_LOG_OBJECT (server, "Sending data");
std::unique_lock < std::mutex > lk (priv->lock);
if (priv->aborted) {
GST_DEBUG_OBJECT (server, "Was aborted");
return GST_FLOW_ERROR;
}
auto data = std::make_shared < GstCudaIpcServerData > ();
data->sample = gst_sample_ref (sample);
data->info = info;
data->handle = handle;
data->pts = pts;
data->seq_num = priv->seq_num;
priv->seq_num++;
priv->data = data;
lk.unlock ();
klass->invoke (server);
return GST_FLOW_OK;
}
GstFlowReturn
gst_cuda_ipc_server_send_mmap_data (GstCudaIpcServer * server,
GstSample * sample, const GstVideoInfo & info, GstCudaSharableHandle handle,
GstClockTime pts)
{
GstCudaIpcServerPrivate *priv;
GstCudaIpcServerClass *klass;
g_return_val_if_fail (GST_IS_CUDA_IPC_SERVER (server), GST_FLOW_ERROR);
g_return_val_if_fail (GST_IS_SAMPLE (sample), GST_FLOW_ERROR);
if (server->ipc_mode != GST_CUDA_IPC_MMAP) {
GST_ERROR_OBJECT (server, "Invalid call");
return GST_FLOW_ERROR;
}
priv = server->priv;
klass = GST_CUDA_IPC_SERVER_GET_CLASS (server);
GST_LOG_OBJECT (server, "Sending data");
std::unique_lock < std::mutex > lk (priv->lock);
if (priv->aborted) {
GST_DEBUG_OBJECT (server, "Was aborted");
return GST_FLOW_ERROR;
}
auto data = std::make_shared < GstCudaIpcServerData > ();
data->sample = gst_sample_ref (sample);
data->info = info;
data->os_handle = handle;
data->pts = pts;
data->seq_num = priv->seq_num;
priv->seq_num++;
priv->data = data;
lk.unlock ();
klass->invoke (server);
return GST_FLOW_OK;
}
static gpointer
gst_cuda_ipc_server_loop_thread_func (GstCudaIpcServer * self)
{
GstCudaIpcServerPrivate *priv = self->priv;
GstCudaIpcServerClass *klass = GST_CUDA_IPC_SERVER_GET_CLASS (self);
g_assert (klass->loop);
GST_DEBUG_OBJECT (self, "Start loop thread");
klass->loop (self);
priv->conn_map.clear ();
GST_DEBUG_OBJECT (self, "Exit loop thread");
return nullptr;
}
void
gst_cuda_ipc_server_run (GstCudaIpcServer * server)
{
GstCudaIpcServerPrivate *priv;
g_return_if_fail (GST_IS_CUDA_IPC_SERVER (server));
priv = server->priv;
GST_DEBUG_OBJECT (server, "Running");
std::unique_lock < std::mutex > lk (priv->lock);
if (priv->loop_thread) {
GST_DEBUG_OBJECT (server, "Already running");
return;
}
GST_DEBUG_OBJECT (server, "Spawning thread");
priv->loop_thread = g_thread_new ("cuda-ipc-server",
(GThreadFunc) gst_cuda_ipc_server_loop_thread_func, server);
}
void
gst_cuda_ipc_server_stop (GstCudaIpcServer * server)
{
GstCudaIpcServerPrivate *priv;
GstCudaIpcServerClass *klass;
g_return_if_fail (GST_IS_CUDA_IPC_SERVER (server));
priv = server->priv;
klass = GST_CUDA_IPC_SERVER_GET_CLASS (server);
GST_DEBUG_OBJECT (server, "Stopping");
priv->shutdown = true;
klass->invoke (server);
g_clear_pointer (&priv->loop_thread, g_thread_join);
GST_DEBUG_OBJECT (server, "Stopped");
}
static void
gst_cuda_ipc_server_close_connection (GstCudaIpcServer * self,
GstCudaIpcServerConn * conn)
{
GstCudaIpcServerPrivate *priv = self->priv;
GstCudaIpcServerClass *klass = GST_CUDA_IPC_SERVER_GET_CLASS (self);
GST_DEBUG_OBJECT (self, "Closing conn-id %u", conn->id);
priv->conn_map.erase (conn->id);
if (priv->shutdown && priv->conn_map.empty ()) {
GST_DEBUG_OBJECT (self, "All connection were closed");
klass->terminate (self);
}
}
static void
gst_cuda_ipc_server_send_msg (GstCudaIpcServer * self,
GstCudaIpcServerConn * conn)
{
GstCudaIpcServerClass *klass = GST_CUDA_IPC_SERVER_GET_CLASS (self);
if (!klass->send_msg (self, conn)) {
GST_WARNING_OBJECT (self, "Send msg failed");
gst_cuda_ipc_server_close_connection (self, conn);
}
}
static void
gst_cuda_ipc_server_config_data (GstCudaIpcServer * self,
GstCudaIpcServerConn * conn)
{
GstCaps *caps = gst_sample_get_caps (conn->data->sample);
gst_caps_replace (&conn->caps, caps);
gst_cuda_ipc_pkt_build_config (conn->server_msg, self->pid,
self->ipc_mode == GST_CUDA_IPC_MMAP, conn->caps);
conn->type = GstCudaIpcPktType::CONFIG;
GST_LOG_OBJECT (self, "Sending CONFIG, conn-id %u", conn->id);
gst_cuda_ipc_server_send_msg (self, conn);
}
void
gst_cuda_ipc_server_on_incoming_connection (GstCudaIpcServer * server,
std::shared_ptr < GstCudaIpcServerConn > conn)
{
GstCudaIpcServerPrivate *priv = server->priv;
priv->lock.lock ();
conn->server = server;
conn->id = priv->next_conn_id;
conn->context = (GstCudaContext *) gst_object_ref (server->context);
conn->data = priv->data;
priv->next_conn_id++;
priv->lock.unlock ();
/* *INDENT-OFF* */
priv->conn_map.insert ({conn->id, conn});
/* *INDENT-ON* */
if (conn->data) {
conn->configured = true;
gst_cuda_ipc_server_config_data (server, conn.get ());
} else {
GST_DEBUG_OBJECT (server, "Have no config data yet, waiting for data");
}
}
static void
gst_cuda_ipc_server_have_data (GstCudaIpcServer * self,
GstCudaIpcServerConn * conn)
{
GstCudaIpcServerClass *klass = GST_CUDA_IPC_SERVER_GET_CLASS (self);
GstCaps *caps;
GstBuffer *buffer;
GstCudaMemory *cmem;
if (!conn->data) {
GST_ERROR_OBJECT (self, "Have no data to send, conn-id: %u", conn->id);
gst_cuda_ipc_server_close_connection (self, conn);
return;
}
conn->pending_have_data = false;
conn->seq_num = conn->data->seq_num + 1;
caps = gst_sample_get_caps (conn->data->sample);
if (!conn->caps || !gst_caps_is_equal (conn->caps, caps)) {
GST_DEBUG_OBJECT (self, "Sending caps %" GST_PTR_FORMAT " to conn-id %u",
caps, conn->id);
gst_caps_replace (&conn->caps, caps);
} else {
caps = nullptr;
}
buffer = gst_sample_get_buffer (conn->data->sample);
cmem = (GstCudaMemory *) gst_buffer_peek_memory (buffer, 0);
if (self->ipc_mode == GST_CUDA_IPC_LEGACY) {
auto handle_dump = gst_cuda_ipc_mem_handle_to_string (conn->data->handle);
GST_LOG_OBJECT (self, "Sending HAVE-DATA with handle %s, conn-id: %u",
handle_dump.c_str (), conn->id);
if (!gst_cuda_ipc_pkt_build_have_data (conn->server_msg, conn->data->pts,
conn->data->info, conn->data->handle, caps)) {
GST_ERROR_OBJECT (self, "Couldn't build HAVE-DATA pkt, conn-id: %u",
conn->id);
gst_cuda_ipc_server_close_connection (self, conn);
return;
}
conn->type = GstCudaIpcPktType::HAVE_DATA;
} else {
guint max_size = cmem->mem.maxsize;
GST_LOG_OBJECT (self, "Sending HAVE-MMAP-DATA with handle %"
GST_CUDA_OS_HANDLE_FORMAT ", conn-id: %u", conn->data->os_handle,
conn->id);
if (!gst_cuda_ipc_pkt_build_have_mmap_data (conn->server_msg,
conn->data->pts, conn->data->info, max_size, conn->data->os_handle,
caps)) {
GST_ERROR_OBJECT (self, "Couldn't build HAVE-MMAP-DATA pkt, conn-id: %u",
conn->id);
gst_cuda_ipc_server_close_connection (self, conn);
return;
}
conn->type = GstCudaIpcPktType::HAVE_MMAP_DATA;
if (klass->send_mmap_msg) {
if (!klass->send_mmap_msg (self, conn, conn->data->os_handle)) {
GST_WARNING_OBJECT (self, "Send msg failed");
gst_cuda_ipc_server_close_connection (self, conn);
}
return;
}
}
gst_cuda_ipc_server_send_msg (self, conn);
}
static void
gst_cuda_ipc_server_wait_msg (GstCudaIpcServer * self,
GstCudaIpcServerConn * conn)
{
GstCudaIpcServerClass *klass = GST_CUDA_IPC_SERVER_GET_CLASS (self);
if (!klass->wait_msg (self, conn)) {
GST_WARNING_OBJECT (self, "Wait msg failed, conn-id: %u", conn->id);
gst_cuda_ipc_server_close_connection (self, conn);
}
}
static bool
gst_cuda_ipc_server_on_release_data (GstCudaIpcServer * self,
GstCudaIpcServerConn * conn)
{
bool found = false;
if (self->ipc_mode == GST_CUDA_IPC_LEGACY) {
CUipcMemHandle handle;
if (!gst_cuda_ipc_pkt_parse_release_data (conn->client_msg, handle)) {
GST_ERROR_OBJECT (self, "Couldn't parse RELEASE-DATA, conn-id: %u",
conn->id);
return false;
}
auto handle_dump = gst_cuda_ipc_mem_handle_to_string (handle);
GST_LOG_OBJECT (self, "RELEASE-DATA %s, conn-id: %u",
handle_dump.c_str (), conn->id);
for (auto it = conn->peer_handles.begin (); it != conn->peer_handles.end ();
it++) {
CUipcMemHandle & tmp = (*it)->handle;
if (gst_cuda_ipc_handle_is_equal (tmp, handle)) {
found = true;
conn->peer_handles.erase (it);
break;
}
}
if (!found) {
GST_WARNING_OBJECT (self,
"Unexpected memory handle to remove %s, conn-id: %u",
handle_dump.c_str (), conn->id);
return false;
}
} else {
GstCudaSharableHandle handle;
if (!gst_cuda_ipc_pkt_parse_release_mmap_data (conn->client_msg, &handle)) {
GST_ERROR_OBJECT (self, "Couldn't parse RELEASE-MMAP-DATA, conn-id: %u",
conn->id);
return false;
}
GST_LOG_OBJECT (self, "RELEASE-MMAP-DATA %" GST_CUDA_OS_HANDLE_FORMAT
", conn-id %u", handle, conn->id);
for (auto it = conn->peer_handles.begin (); it != conn->peer_handles.end ();
it++) {
if ((*it)->os_handle == handle) {
found = true;
conn->peer_handles.erase (it);
break;
}
}
if (!found) {
GST_WARNING_OBJECT (self,
"Unexpected memory handle to remove %" GST_CUDA_OS_HANDLE_FORMAT
", conn-id: %u", handle, conn->id);
return false;
}
}
GST_LOG_OBJECT (self, "Client is holding %" G_GSIZE_FORMAT " handles",
conn->peer_handles.size ());
return true;
}
void
gst_cuda_ipc_server_wait_msg_finish (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn, bool result)
{
GstCudaIpcPacketHeader header;
if (!result) {
GST_WARNING_OBJECT (server, "Wait msg failed, conn->id: %u", conn->id);
gst_cuda_ipc_server_close_connection (server, conn);
return;
}
if (!gst_cuda_ipc_pkt_identify (conn->client_msg, header)) {
GST_ERROR_OBJECT (server, "Broken header, conn-id: %u", conn->id);
gst_cuda_ipc_server_close_connection (server, conn);
return;
}
switch (header.type) {
case GstCudaIpcPktType::NEED_DATA:
GST_LOG_OBJECT (server, "NEED-DATA, conn-id: %u", conn->id);
if (!conn->data) {
GST_LOG_OBJECT (server, "Wait for available data, conn-id: %u",
conn->id);
conn->pending_have_data = true;
gst_cuda_ipc_server_on_idle (server);
return;
}
gst_cuda_ipc_server_have_data (server, conn);
break;
case GstCudaIpcPktType::READ_DONE:
GST_LOG_OBJECT (server, "READ-DONE, conn-id: %u", conn->id);
if (!conn->data) {
GST_ERROR_OBJECT (server, "Unexpected READ-DATA, conn-id: %u",
conn->id);
gst_cuda_ipc_server_close_connection (server, conn);
return;
}
conn->peer_handles.push_back (conn->data);
conn->data = nullptr;
gst_cuda_ipc_server_wait_msg (server, conn);
break;
case GstCudaIpcPktType::RELEASE_DATA:
case GstCudaIpcPktType::RELEASE_MMAP_DATA:
GST_LOG_OBJECT (server, "RELEASE-DATA, conn-id: %u", conn->id);
if (!gst_cuda_ipc_server_on_release_data (server, conn))
gst_cuda_ipc_server_close_connection (server, conn);
else
gst_cuda_ipc_server_wait_msg (server, conn);
break;
case GstCudaIpcPktType::FIN:
GST_DEBUG_OBJECT (server, "FIN, conn-id %u", conn->id);
gst_cuda_ipc_server_close_connection (server, conn);
break;
default:
GST_ERROR_OBJECT (server, "Unexpected packet, conn-id: %u", conn->id);
gst_cuda_ipc_server_close_connection (server, conn);
break;
}
}
void
gst_cuda_ipc_server_send_msg_finish (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn, bool result)
{
if (!result) {
GST_WARNING_OBJECT (server, "Send msg failed, conn-id %u", conn->id);
gst_cuda_ipc_server_close_connection (server, conn);
return;
}
switch (conn->type) {
case GstCudaIpcPktType::CONFIG:
GST_DEBUG_OBJECT (server, "Sent CONFIG-DATA, conn-id %u", conn->id);
gst_cuda_ipc_server_wait_msg (server, conn);
break;
case GstCudaIpcPktType::HAVE_DATA:
GST_LOG_OBJECT (server, "Sent HAVE-DATA, conn-id %u", conn->id);
gst_cuda_ipc_server_wait_msg (server, conn);
break;
case GstCudaIpcPktType::HAVE_MMAP_DATA:
GST_LOG_OBJECT (server, "Sent HAVE-MMAP-DATA, conn-id %u", conn->id);
gst_cuda_ipc_server_wait_msg (server, conn);
break;
case GstCudaIpcPktType::EOS:
GST_DEBUG_OBJECT (server, "Sent EOS, conn-id %u", conn->id);
gst_cuda_ipc_server_wait_msg (server, conn);
break;
default:
GST_ERROR_OBJECT (server, "Unexpected msg type %d", (gint) conn->type);
gst_cuda_ipc_server_close_connection (server, conn);
break;
}
}
static void
gst_cuda_ipc_server_eos (GstCudaIpcServer * self, GstCudaIpcServerConn * conn)
{
gst_cuda_ipc_pkt_build_eos (conn->server_msg);
conn->eos = true;
conn->type = GstCudaIpcPktType::EOS;
gst_cuda_ipc_server_send_msg (self, conn);
}
void
gst_cuda_ipc_server_on_idle (GstCudaIpcServer * server)
{
GstCudaIpcServerClass *klass = GST_CUDA_IPC_SERVER_GET_CLASS (server);
GstCudaIpcServerPrivate *priv = server->priv;
GST_LOG_OBJECT (server, "idle");
if (priv->shutdown) {
GST_DEBUG_OBJECT (server, "We are stopping");
if (priv->conn_map.empty ()) {
GST_DEBUG_OBJECT (server, "All connections were closed");
klass->terminate (server);
return;
}
std::vector < std::shared_ptr < GstCudaIpcServerConn >> to_send_eos;
/* *INDENT-OFF* */
for (auto it : priv->conn_map) {
auto conn = it.second;
if (conn->eos || !conn->pending_have_data)
continue;
to_send_eos.push_back (conn);
}
for (auto it : to_send_eos) {
GST_DEBUG_OBJECT (server, "Sending EOS to conn-id: %u", it->id);
gst_cuda_ipc_server_eos (server, it.get ());
}
GST_DEBUG_OBJECT (server, "Have %" G_GSIZE_FORMAT " alive connections",
priv->conn_map.size());
for (auto it : priv->conn_map) {
auto conn = it.second;
GST_DEBUG_OBJECT (server, "conn-id %u"
" peer handle size %" G_GSIZE_FORMAT, conn->id,
conn->peer_handles.size ());
}
/* *INDENT-ON* */
return;
}
if (priv->conn_map.empty ()) {
GST_LOG_OBJECT (server, "Have no connection");
return;
}
std::unique_lock < std::mutex > lk (priv->lock);
if (!priv->data)
return;
/* *INDENT-OFF* */
std::vector < std::shared_ptr < GstCudaIpcServerConn >> to_config_data;
std::vector < std::shared_ptr < GstCudaIpcServerConn >> to_send_have_data;
for (auto it : priv->conn_map) {
auto conn = it.second;
if (!conn->configured) {
conn->configured = true;
conn->data = priv->data;
to_config_data.push_back (conn);
} else if (conn->pending_have_data && conn->seq_num <= priv->data->seq_num) {
conn->data = priv->data;
to_send_have_data.push_back (conn);
}
}
lk.unlock ();
for (auto it: to_config_data)
gst_cuda_ipc_server_config_data (server, it.get ());
for (auto it: to_send_have_data)
gst_cuda_ipc_server_have_data (server, it.get ());
/* *INDENT-ON* */
}
void
gst_cuda_ipc_server_abort (GstCudaIpcServer * server)
{
GstCudaIpcServerPrivate *priv = server->priv;
priv->aborted = true;
}

View file

@ -0,0 +1,168 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/cuda/gstcuda.h>
#include "gstcudaipc.h"
#include <memory>
#include <vector>
#include <string.h>
G_BEGIN_DECLS
#define GST_TYPE_CUDA_IPC_SERVER (gst_cuda_ipc_server_get_type())
#define GST_CUDA_IPC_SERVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CUDA_IPC_SERVER,GstCudaIpcServer))
#define GST_CUDA_IPC_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_CUDA_IPC_SERVER,GstCudaIpcServerClass))
#define GST_CUDA_IPC_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_CUDA_IPC_SERVER,GstCudaIpcServerClass))
#define GST_IS_CUDA_IPC_SERVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CUDA_IPC_SERVER))
#define GST_IS_CUDA_IPC_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_CUDA_IPC_SERVER))
struct GstCudaIpcServerData;
struct GstCudaIpcServerConn;
struct GstCudaIpcServerPrivate;
enum GstCudaIpcMode
{
GST_CUDA_IPC_LEGACY,
GST_CUDA_IPC_MMAP,
};
#define GST_TYPE_CUDA_IPC_MODE (gst_cuda_ipc_mode_get_type())
GType gst_cuda_ipc_mode_get_type (void);
struct GstCudaIpcServer
{
GstObject parent;
GstCudaContext *context;
GstCudaIpcMode ipc_mode;
GstCudaPid pid;
GstCudaIpcServerPrivate *priv;
};
struct GstCudaIpcServerClass
{
GstObjectClass parent_class;
void (*loop) (GstCudaIpcServer * server);
void (*terminate) (GstCudaIpcServer * server);
void (*invoke) (GstCudaIpcServer * server);
bool (*wait_msg) (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn);
bool (*send_msg) (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn);
bool (*send_mmap_msg) (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn,
GstCudaSharableHandle handle);
};
GType gst_cuda_ipc_server_get_type (void);
GstFlowReturn gst_cuda_ipc_server_send_data (GstCudaIpcServer * server,
GstSample * sample,
const GstVideoInfo & info,
const CUipcMemHandle & handle,
GstClockTime pts);
GstFlowReturn gst_cuda_ipc_server_send_mmap_data (GstCudaIpcServer * server,
GstSample * sample,
const GstVideoInfo & info,
GstCudaSharableHandle handle,
GstClockTime pts);
void gst_cuda_ipc_server_stop (GstCudaIpcServer * server);
/* subclass methods */
void gst_cuda_ipc_server_run (GstCudaIpcServer * server);
void gst_cuda_ipc_server_wait_msg_finish (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn,
bool result);
void gst_cuda_ipc_server_send_msg_finish (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn,
bool result);
void gst_cuda_ipc_server_on_incoming_connection (GstCudaIpcServer * server,
std::shared_ptr<GstCudaIpcServerConn> conn);
void gst_cuda_ipc_server_on_idle (GstCudaIpcServer * server);
void gst_cuda_ipc_server_abort (GstCudaIpcServer * server);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstCudaIpcServer, gst_object_unref)
G_END_DECLS
struct GstCudaIpcServerData
{
~GstCudaIpcServerData ()
{
if (sample)
gst_sample_unref (sample);
}
GstSample *sample;
GstVideoInfo info;
CUipcMemHandle handle;
GstCudaSharableHandle os_handle;
GstClockTime pts;
guint64 seq_num;
};
struct GstCudaIpcServerConn : public OVERLAPPED
{
GstCudaIpcServerConn ()
{
client_msg.resize (GST_CUDA_IPC_PKT_HEADER_SIZE);
server_msg.resize (GST_CUDA_IPC_PKT_HEADER_SIZE);
}
virtual ~GstCudaIpcServerConn()
{
gst_clear_object (&context);
gst_clear_caps (&caps);
}
GstCudaIpcServer *server;
GstCudaContext *context = nullptr;
GstCudaIpcPktType type;
std::vector<guint8> client_msg;
std::vector<guint8> server_msg;
std::shared_ptr<GstCudaIpcServerData> data;
std::vector<std::shared_ptr<GstCudaIpcServerData>> peer_handles;
GstCaps *caps = nullptr;
guint64 seq_num = 0;
guint id;
bool eos = false;
bool pending_have_data = false;
bool configured = false;
};

View file

@ -0,0 +1,400 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaipcserver_unix.h"
#include <string.h>
#include <string>
#include <gio/gunixsocketaddress.h>
#include <gio/gunixconnection.h>
#include <glib/gstdio.h>
GST_DEBUG_CATEGORY_EXTERN (cuda_ipc_server_debug);
#define GST_CAT_DEFAULT cuda_ipc_server_debug
/* *INDENT-OFF* */
struct GstCudaIpcServerConnUnix : public GstCudaIpcServerConn
{
GstCudaIpcServerConnUnix (GSocketConnection * conn)
{
socket_conn = (GSocketConnection *) g_object_ref (conn);
istream = g_io_stream_get_input_stream (G_IO_STREAM (socket_conn));
ostream = g_io_stream_get_output_stream (G_IO_STREAM (socket_conn));
}
~GstCudaIpcServerConnUnix ()
{
g_clear_object (&socket_conn);
}
/* Holds ref */
GSocketConnection *socket_conn;
/* Owned by socket_conn */
GInputStream *istream;
GOutputStream *ostream;
};
struct GstCudaIpcServerUnixPrivate
{
GstCudaIpcServerUnixPrivate ()
{
main_context = g_main_context_new ();
main_loop = g_main_loop_new (main_context, FALSE);
cancellable = g_cancellable_new ();
}
~GstCudaIpcServerUnixPrivate ()
{
g_main_loop_unref (main_loop);
g_main_context_unref (main_context);
g_object_unref (cancellable);
}
std::string address;
GMainLoop *main_loop;
GMainContext *main_context;
GCancellable *cancellable;
};
/* *INDENT-ON* */
struct _GstCudaIpcServerUnix
{
GstCudaIpcServer parent;
GstCudaIpcServerUnixPrivate *priv;
};
static void gst_cuda_ipc_server_unix_finalize (GObject * object);
static void gst_cuda_ipc_server_unix_loop (GstCudaIpcServer * server);
static void gst_cuda_ipc_server_unix_terminate (GstCudaIpcServer * server);
static void gst_cuda_ipc_server_unix_invoke (GstCudaIpcServer * server);
static bool gst_cuda_ipc_server_unix_wait_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn);
static bool gst_cuda_ipc_server_unix_send_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn);
static bool gst_cuda_ipc_server_unix_send_mmap_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn, GstCudaSharableHandle handle);
#define gst_cuda_ipc_server_unix_parent_class parent_class
G_DEFINE_TYPE (GstCudaIpcServerUnix,
gst_cuda_ipc_server_unix, GST_TYPE_CUDA_IPC_SERVER);
static void
gst_cuda_ipc_server_unix_class_init (GstCudaIpcServerUnixClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstCudaIpcServerClass *server_class = GST_CUDA_IPC_SERVER_CLASS (klass);
object_class->finalize = gst_cuda_ipc_server_unix_finalize;
server_class->loop = GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_unix_loop);
server_class->terminate =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_unix_terminate);
server_class->invoke = GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_unix_invoke);
server_class->wait_msg =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_unix_wait_msg);
server_class->send_msg =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_unix_send_msg);
server_class->send_mmap_msg =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_unix_send_mmap_msg);
}
static void
gst_cuda_ipc_server_unix_init (GstCudaIpcServerUnix * self)
{
self->priv = new GstCudaIpcServerUnixPrivate ();
}
static void
gst_cuda_ipc_server_unix_finalize (GObject * object)
{
GstCudaIpcServerUnix *self = GST_CUDA_IPC_SERVER_UNIX (object);
GST_DEBUG_OBJECT (self, "finalize");
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_cuda_ipc_server_unix_terminate (GstCudaIpcServer * server)
{
GstCudaIpcServerUnix *self = GST_CUDA_IPC_SERVER_UNIX (server);
GstCudaIpcServerUnixPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "terminate");
g_main_loop_quit (priv->main_loop);
}
static gboolean
gst_cuda_ipc_server_invoke_func (GstCudaIpcServer * server)
{
gst_cuda_ipc_server_on_idle (server);
return G_SOURCE_REMOVE;
}
static void
gst_cuda_ipc_server_unix_invoke (GstCudaIpcServer * server)
{
GstCudaIpcServerUnix *self = GST_CUDA_IPC_SERVER_UNIX (server);
GstCudaIpcServerUnixPrivate *priv = self->priv;
g_main_context_invoke (priv->main_context,
(GSourceFunc) gst_cuda_ipc_server_invoke_func, server);
}
static void
gst_cuda_ipc_server_unix_payload_finish (GObject * source,
GAsyncResult * result, GstCudaIpcServerConnUnix * conn)
{
GstCudaIpcServer *server = conn->server;
gsize size;
GError *err = nullptr;
bool ret = true;
if (!g_input_stream_read_all_finish (conn->istream, result, &size, &err)) {
GST_WARNING_OBJECT (server, "Read failed with %s, conn-id: %u",
err->message, conn->id);
g_clear_error (&err);
ret = false;
}
gst_cuda_ipc_server_wait_msg_finish (server, conn, ret);
}
static void
gst_cuda_ipc_server_unix_wait_msg_finish (GObject * source,
GAsyncResult * result, GstCudaIpcServerConnUnix * conn)
{
GstCudaIpcServerUnix *self = GST_CUDA_IPC_SERVER_UNIX (conn->server);
GstCudaIpcServerUnixPrivate *priv = self->priv;
gsize size;
GError *err = nullptr;
GstCudaIpcPacketHeader header;
if (!g_input_stream_read_all_finish (conn->istream, result, &size, &err)) {
GST_WARNING_OBJECT (self, "Read failed with %s, conn-id: %u",
err->message, conn->id);
g_clear_error (&err);
gst_cuda_ipc_server_wait_msg_finish (conn->server, conn, false);
return;
}
if (!gst_cuda_ipc_pkt_identify (conn->client_msg, header)) {
GST_ERROR_OBJECT (self, "Broken header, conn-id: %u", conn->id);
gst_cuda_ipc_server_wait_msg_finish (conn->server, conn, false);
return;
}
if (header.payload_size == 0) {
gst_cuda_ipc_server_wait_msg_finish (conn->server, conn, true);
return;
}
GST_LOG_OBJECT (self, "Reading payload");
g_input_stream_read_all_async (conn->istream, &conn->client_msg[0] +
GST_CUDA_IPC_PKT_HEADER_SIZE, header.payload_size, G_PRIORITY_DEFAULT,
priv->cancellable,
(GAsyncReadyCallback) gst_cuda_ipc_server_unix_payload_finish, conn);
}
static bool
gst_cuda_ipc_server_unix_wait_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn)
{
GstCudaIpcServerUnix *self = GST_CUDA_IPC_SERVER_UNIX (conn->server);
GstCudaIpcServerUnixPrivate *priv = self->priv;
GstCudaIpcServerConnUnix *unix_conn =
static_cast < GstCudaIpcServerConnUnix * >(conn);
GST_LOG_OBJECT (self, "Waiting for client message");
g_input_stream_read_all_async (unix_conn->istream, &conn->client_msg[0],
GST_CUDA_IPC_PKT_HEADER_SIZE, G_PRIORITY_DEFAULT, priv->cancellable,
(GAsyncReadyCallback) gst_cuda_ipc_server_unix_wait_msg_finish,
unix_conn);
return true;
}
static void
gst_cuda_ipc_server_unix_send_msg_finish (GObject * source,
GAsyncResult * result, GstCudaIpcServerConnUnix * conn)
{
GstCudaIpcServerUnix *self = GST_CUDA_IPC_SERVER_UNIX (conn->server);
gsize size;
GError *err = nullptr;
if (!g_output_stream_write_all_finish (conn->ostream, result, &size, &err)) {
GST_WARNING_OBJECT (self, "Write failed with %s, conn-id: %u",
err->message, conn->id);
g_clear_error (&err);
gst_cuda_ipc_server_send_msg_finish (conn->server, conn, false);
return;
}
GST_LOG_OBJECT (self, "Sent message");
gst_cuda_ipc_server_send_msg_finish (conn->server, conn, true);
}
static bool
gst_cuda_ipc_server_unix_send_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn)
{
GstCudaIpcServerUnix *self = GST_CUDA_IPC_SERVER_UNIX (conn->server);
GstCudaIpcServerUnixPrivate *priv = self->priv;
GstCudaIpcServerConnUnix *unix_conn =
static_cast < GstCudaIpcServerConnUnix * >(conn);
GST_LOG_OBJECT (self, "Sending message");
g_output_stream_write_all_async (unix_conn->ostream, &conn->server_msg[0],
conn->server_msg.size (), G_PRIORITY_DEFAULT, priv->cancellable,
(GAsyncReadyCallback) gst_cuda_ipc_server_unix_send_msg_finish,
unix_conn);
return true;
}
static bool
gst_cuda_ipc_server_unix_send_mmap_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn, GstCudaSharableHandle handle)
{
GstCudaIpcServerUnix *self = GST_CUDA_IPC_SERVER_UNIX (conn->server);
GstCudaIpcServerUnixPrivate *priv = self->priv;
GstCudaIpcServerConnUnix *unix_conn =
static_cast < GstCudaIpcServerConnUnix * >(conn);
GError *err = nullptr;
GST_LOG_OBJECT (self, "Sending mmap message");
if (!g_output_stream_write_all (unix_conn->ostream, &conn->server_msg[0],
conn->server_msg.size (), nullptr, priv->cancellable, &err)) {
GST_WARNING_OBJECT (self, "Couldn't write mmap data, %s", err->message);
g_clear_error (&err);
return false;
}
if (!g_unix_connection_send_fd (G_UNIX_CONNECTION (unix_conn->socket_conn),
handle, priv->cancellable, &err)) {
GST_WARNING ("Couldn't send fd, %s", err->message);
g_clear_error (&err);
return false;
}
gst_cuda_ipc_server_send_msg_finish (server, conn, true);
return true;
}
static gboolean
gst_cuda_ipc_server_unix_on_incoming (GSocketService * service,
GSocketConnection * socket_conn, GObject * source_obj,
GstCudaIpcServer * self)
{
GST_DEBUG_OBJECT (self, "Got new connection");
auto conn = std::make_shared < GstCudaIpcServerConnUnix > (socket_conn);
gst_cuda_ipc_server_on_incoming_connection (self, conn);
return TRUE;
}
static void
gst_cuda_ipc_server_unix_loop (GstCudaIpcServer * server)
{
GstCudaIpcServerUnix *self = GST_CUDA_IPC_SERVER_UNIX (server);
GstCudaIpcServerUnixPrivate *priv = self->priv;
GError *err = nullptr;
GSocketAddress *addr;
GSocketService *service;
gboolean ret;
g_main_context_push_thread_default (priv->main_context);
service = g_socket_service_new ();
addr = g_unix_socket_address_new (priv->address.c_str ());
GST_DEBUG_OBJECT (self, "Creating service with address \"%s\"",
priv->address.c_str ());
ret = g_socket_listener_add_address (G_SOCKET_LISTENER (service),
addr, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, nullptr,
nullptr, &err);
g_object_unref (addr);
if (!ret) {
GST_ERROR_OBJECT (self, "Setup failed, error: %s", err->message);
g_clear_error (&err);
g_clear_object (&service);
gst_cuda_ipc_server_abort (server);
} else {
g_signal_connect (service,
"incoming", G_CALLBACK (gst_cuda_ipc_server_unix_on_incoming), self);
g_socket_service_start (service);
}
GST_DEBUG_OBJECT (self, "Starting loop");
g_main_loop_run (priv->main_loop);
GST_DEBUG_OBJECT (self, "Loop stopped");
if (service) {
g_cancellable_cancel (priv->cancellable);
g_unlink (priv->address.c_str ());
g_clear_object (&service);
}
g_main_context_pop_thread_default (priv->main_context);
}
GstCudaIpcServer *
gst_cuda_ipc_server_new (const gchar * address, GstCudaContext * context,
GstCudaIpcMode ipc_mode)
{
GstCudaIpcServerUnix *self;
GstCudaIpcServer *server;
g_return_val_if_fail (address, nullptr);
g_return_val_if_fail (GST_IS_CUDA_CONTEXT (context), nullptr);
self = (GstCudaIpcServerUnix *)
g_object_new (GST_TYPE_CUDA_IPC_SERVER_UNIX, nullptr);
gst_object_ref_sink (self);
self->priv->address = address;
server = GST_CUDA_IPC_SERVER (self);
server->context = (GstCudaContext *) gst_object_ref (context);
server->ipc_mode = ipc_mode;
server->pid = getpid ();
gst_cuda_ipc_server_run (server);
return server;
}

View file

@ -0,0 +1,34 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "gstcudaipcserver.h"
G_BEGIN_DECLS
#define GST_TYPE_CUDA_IPC_SERVER_UNIX (gst_cuda_ipc_server_unix_get_type())
G_DECLARE_FINAL_TYPE (GstCudaIpcServerUnix, gst_cuda_ipc_server_unix,
GST, CUDA_IPC_SERVER_UNIX, GstCudaIpcServer);
GstCudaIpcServer * gst_cuda_ipc_server_new (const gchar * address,
GstCudaContext * context,
GstCudaIpcMode ipc_mode);
G_END_DECLS

View file

@ -0,0 +1,440 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaipcserver_win32.h"
#include <string.h>
#include <string>
GST_DEBUG_CATEGORY_EXTERN (cuda_ipc_server_debug);
#define GST_CAT_DEFAULT cuda_ipc_server_debug
/* *INDENT-OFF* */
struct GstCudaIpcServerConnWin32 : public GstCudaIpcServerConn
{
GstCudaIpcServerConnWin32 (HANDLE pipe_handle) : pipe (pipe_handle)
{
OVERLAPPED *parent = static_cast<OVERLAPPED *> (this);
parent->Internal = 0;
parent->InternalHigh = 0;
parent->Offset = 0;
parent->OffsetHigh = 0;
}
virtual ~GstCudaIpcServerConnWin32 ()
{
if (pipe != INVALID_HANDLE_VALUE) {
CancelIo (pipe);
DisconnectNamedPipe (pipe);
CloseHandle (pipe);
}
}
HANDLE pipe;
};
struct GstCudaIpcServerWin32Private
{
GstCudaIpcServerWin32Private ()
{
cancellable = CreateEvent (nullptr, TRUE, FALSE, nullptr);
wakeup_event = CreateEvent (nullptr, FALSE, FALSE, nullptr);
}
~GstCudaIpcServerWin32Private ()
{
CloseHandle (cancellable);
CloseHandle (wakeup_event);
}
std::string address;
HANDLE cancellable;
HANDLE wakeup_event;
};
/* *INDENT-ON* */
struct _GstCudaIpcServerWin32
{
GstCudaIpcServer parent;
GstCudaIpcServerWin32Private *priv;
};
static void gst_cuda_ipc_server_win32_finalize (GObject * object);
static void gst_cuda_ipc_server_win32_loop (GstCudaIpcServer * server);
static void gst_cuda_ipc_server_win32_terminate (GstCudaIpcServer * server);
static void gst_cuda_ipc_server_win32_invoke (GstCudaIpcServer * server);
static bool gst_cuda_ipc_server_win32_wait_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn);
static bool gst_cuda_ipc_server_win32_send_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn);
#define gst_cuda_ipc_server_win32_parent_class parent_class
G_DEFINE_TYPE (GstCudaIpcServerWin32,
gst_cuda_ipc_server_win32, GST_TYPE_CUDA_IPC_SERVER);
static void
gst_cuda_ipc_server_win32_class_init (GstCudaIpcServerWin32Class * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstCudaIpcServerClass *server_class = GST_CUDA_IPC_SERVER_CLASS (klass);
object_class->finalize = gst_cuda_ipc_server_win32_finalize;
server_class->loop = GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_win32_loop);
server_class->terminate =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_win32_terminate);
server_class->invoke = GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_win32_invoke);
server_class->wait_msg =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_win32_wait_msg);
server_class->send_msg =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_server_win32_send_msg);
}
static void
gst_cuda_ipc_server_win32_init (GstCudaIpcServerWin32 * self)
{
self->priv = new GstCudaIpcServerWin32Private ();
}
static void
gst_cuda_ipc_server_win32_finalize (GObject * object)
{
GstCudaIpcServerWin32 *self = GST_CUDA_IPC_SERVER_WIN32 (object);
GST_DEBUG_OBJECT (self, "finalize");
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_cuda_ipc_server_win32_terminate (GstCudaIpcServer * server)
{
GstCudaIpcServerWin32 *self = GST_CUDA_IPC_SERVER_WIN32 (server);
GstCudaIpcServerWin32Private *priv = self->priv;
GST_DEBUG_OBJECT (self, "terminate");
SetEvent (priv->cancellable);
}
static void
gst_cuda_ipc_server_win32_invoke (GstCudaIpcServer * server)
{
GstCudaIpcServerWin32 *self = GST_CUDA_IPC_SERVER_WIN32 (server);
GstCudaIpcServerWin32Private *priv = self->priv;
SetEvent (priv->wakeup_event);
}
static void WINAPI
gst_cuda_ipc_server_win32_payload_finish (DWORD error_code, DWORD size,
OVERLAPPED * overlap)
{
GstCudaIpcServerConnWin32 *win32_conn =
static_cast < GstCudaIpcServerConnWin32 * >(overlap);
GstCudaIpcServerWin32 *self = GST_CUDA_IPC_SERVER_WIN32 (win32_conn->server);
bool ret = true;
if (error_code != ERROR_SUCCESS) {
auto err = gst_cuda_ipc_win32_error_to_string (error_code);
GST_WARNING_OBJECT (self, "ReadFileEx callback failed with 0x%x (%s)",
(guint) error_code, err.c_str ());
ret = false;
}
gst_cuda_ipc_server_wait_msg_finish (win32_conn->server, win32_conn, ret);
}
static void WINAPI
gst_cuda_ipc_server_win32_wait_msg_finish (DWORD error_code, DWORD size,
OVERLAPPED * overlap)
{
GstCudaIpcServerConnWin32 *win32_conn =
static_cast < GstCudaIpcServerConnWin32 * >(overlap);
GstCudaIpcServerWin32 *self = GST_CUDA_IPC_SERVER_WIN32 (win32_conn->server);
GstCudaIpcPacketHeader header;
if (error_code != ERROR_SUCCESS) {
auto err = gst_cuda_ipc_win32_error_to_string (error_code);
GST_WARNING_OBJECT (self, "ReadFileEx callback failed with 0x%x (%s)",
(guint) error_code, err.c_str ());
gst_cuda_ipc_server_wait_msg_finish (win32_conn->server, win32_conn, false);
return;
}
if (!gst_cuda_ipc_pkt_identify (win32_conn->client_msg, header)) {
GST_ERROR_OBJECT (self, "Broken header");
gst_cuda_ipc_server_wait_msg_finish (win32_conn->server, win32_conn, false);
return;
}
if (header.payload_size == 0) {
gst_cuda_ipc_server_wait_msg_finish (win32_conn->server, win32_conn, true);
return;
}
GST_LOG_OBJECT (self, "Reading payload");
if (!ReadFileEx (win32_conn->pipe, &win32_conn->client_msg[0] +
GST_CUDA_IPC_PKT_HEADER_SIZE, header.payload_size, win32_conn,
gst_cuda_ipc_server_win32_payload_finish)) {
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_WARNING_OBJECT (self, "ReadFileEx failed with 0x%x (%s)",
last_err, err.c_str ());
gst_cuda_ipc_server_wait_msg_finish (win32_conn->server, win32_conn, false);
}
}
static bool
gst_cuda_ipc_server_win32_wait_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn)
{
GstCudaIpcServerWin32 *self = GST_CUDA_IPC_SERVER_WIN32 (server);
GstCudaIpcServerConnWin32 *win32_conn =
static_cast < GstCudaIpcServerConnWin32 * >(conn);
GST_LOG_OBJECT (self, "Waiting for client message");
if (!ReadFileEx (win32_conn->pipe, &conn->client_msg[0],
GST_CUDA_IPC_PKT_HEADER_SIZE, win32_conn,
gst_cuda_ipc_server_win32_wait_msg_finish)) {
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_WARNING_OBJECT (self, "ReadFileEx failed with 0x%x (%s)",
last_err, err.c_str ());
return false;
}
return true;
}
static void WINAPI
gst_cuda_ipc_server_win32_send_msg_finish (DWORD error_code, DWORD size,
OVERLAPPED * overlap)
{
GstCudaIpcServerConnWin32 *win32_conn =
static_cast < GstCudaIpcServerConnWin32 * >(overlap);
GstCudaIpcServerWin32 *self = GST_CUDA_IPC_SERVER_WIN32 (win32_conn->server);
bool ret = true;
if (error_code != ERROR_SUCCESS) {
auto err = gst_cuda_ipc_win32_error_to_string (error_code);
GST_WARNING_OBJECT (self, "ReadFileEx callback failed with 0x%x (%s)",
(guint) error_code, err.c_str ());
ret = false;
}
GST_LOG_OBJECT (self, "Sent message");
gst_cuda_ipc_server_send_msg_finish (win32_conn->server, win32_conn, ret);
}
static bool
gst_cuda_ipc_server_win32_send_msg (GstCudaIpcServer * server,
GstCudaIpcServerConn * conn)
{
GstCudaIpcServerWin32 *self = GST_CUDA_IPC_SERVER_WIN32 (server);
GstCudaIpcServerConnWin32 *win32_conn =
static_cast < GstCudaIpcServerConnWin32 * >(conn);
GST_LOG_OBJECT (self, "Sending message");
if (!WriteFileEx (win32_conn->pipe, &conn->server_msg[0],
conn->server_msg.size (), win32_conn,
gst_cuda_ipc_server_win32_send_msg_finish)) {
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_WARNING_OBJECT (self, "WriteFileEx failed with 0x%x (%s)",
last_err, err.c_str ());
return false;
}
return true;
}
static HANDLE
gst_cuda_ipc_server_win32_create_pipe (GstCudaIpcServerWin32 * self,
OVERLAPPED * overlap, bool &io_pending)
{
GstCudaIpcServerWin32Private *priv = self->priv;
HANDLE pipe = CreateNamedPipeA (priv->address.c_str (),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, 1024, 1024, 5000, nullptr);
if (pipe == INVALID_HANDLE_VALUE) {
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_ERROR_OBJECT (self, "CreateNamedPipeA failed with 0x%x (%s)",
last_err, err.c_str ());
return INVALID_HANDLE_VALUE;
}
if (ConnectNamedPipe (pipe, overlap)) {
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_ERROR_OBJECT (self, "ConnectNamedPipe failed with 0x%x (%s)",
last_err, err.c_str ());
return INVALID_HANDLE_VALUE;
}
io_pending = false;
guint last_err = GetLastError ();
switch (last_err) {
case ERROR_IO_PENDING:
io_pending = true;
break;
case ERROR_PIPE_CONNECTED:
SetEvent (overlap->hEvent);
break;
default:
{
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_ERROR_OBJECT (self, "ConnectNamedPipe failed with 0x%x (%s)",
last_err, err.c_str ());
CloseHandle (pipe);
return INVALID_HANDLE_VALUE;
}
}
return pipe;
}
static void
gst_cuda_ipc_server_win32_loop (GstCudaIpcServer * server)
{
GstCudaIpcServerWin32 *self = GST_CUDA_IPC_SERVER_WIN32 (server);
GstCudaIpcServerWin32Private *priv = self->priv;
bool io_pending = false;
guint wait_ret;
HANDLE pipe;
OVERLAPPED overlap;
GST_DEBUG_OBJECT (self, "Entering loop");
memset (&overlap, 0, sizeof (OVERLAPPED));
overlap.hEvent = CreateEvent (nullptr, TRUE, TRUE, nullptr);
pipe = gst_cuda_ipc_server_win32_create_pipe (self, &overlap, io_pending);
if (pipe == INVALID_HANDLE_VALUE) {
CloseHandle (overlap.hEvent);
gst_cuda_ipc_server_abort (server);
return;
}
HANDLE waitables[] =
{ overlap.hEvent, priv->wakeup_event, priv->cancellable };
do {
wait_ret = WaitForMultipleObjectsEx (G_N_ELEMENTS (waitables), waitables,
FALSE, INFINITE, TRUE);
if (wait_ret == WAIT_OBJECT_0 + 2) {
GST_DEBUG_OBJECT (self, "Operation cancelled");
goto out;
}
switch (wait_ret) {
case WAIT_OBJECT_0:
{
DWORD n_bytes;
if (io_pending
&& !GetOverlappedResult (pipe, &overlap, &n_bytes, FALSE)) {
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_WARNING_OBJECT (self, "GetOverlappedResult failed with 0x%x (%s)",
last_err, err.c_str ());
CloseHandle (pipe);
pipe = INVALID_HANDLE_VALUE;
break;
}
auto conn = std::make_shared < GstCudaIpcServerConnWin32 > (pipe);
pipe = INVALID_HANDLE_VALUE;
gst_cuda_ipc_server_on_incoming_connection (server, conn);
pipe = gst_cuda_ipc_server_win32_create_pipe (self,
&overlap, io_pending);
break;
}
case WAIT_IO_COMPLETION:
break;
case WAIT_OBJECT_0 + 1:
gst_cuda_ipc_server_on_idle (server);
break;
default:
{
guint last_err = GetLastError ();
auto err = gst_cuda_ipc_win32_error_to_string (last_err);
GST_ERROR_OBJECT (self,
"WaitForMultipleObjectsEx return 0x%x, last error 0x%x (%s)",
wait_ret, last_err, err.c_str ());
gst_cuda_ipc_server_abort (server);
goto out;
}
}
} while (true);
out:
if (pipe != INVALID_HANDLE_VALUE) {
CancelIo (pipe);
DisconnectNamedPipe (pipe);
CloseHandle (pipe);
}
CloseHandle (overlap.hEvent);
GST_DEBUG_OBJECT (self, "Exit loop");
}
GstCudaIpcServer *
gst_cuda_ipc_server_new (const gchar * address, GstCudaContext * context,
GstCudaIpcMode ipc_mode)
{
GstCudaIpcServerWin32 *self;
GstCudaIpcServer *server;
g_return_val_if_fail (address, nullptr);
g_return_val_if_fail (GST_IS_CUDA_CONTEXT (context), nullptr);
self = (GstCudaIpcServerWin32 *)
g_object_new (GST_TYPE_CUDA_IPC_SERVER_WIN32, nullptr);
gst_object_ref_sink (self);
self->priv->address = address;
server = GST_CUDA_IPC_SERVER (self);
server->context = (GstCudaContext *) gst_object_ref (context);
server->ipc_mode = ipc_mode;
server->pid = GetCurrentProcessId ();
gst_cuda_ipc_server_run (server);
return server;
}

View file

@ -0,0 +1,34 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "gstcudaipcserver.h"
G_BEGIN_DECLS
#define GST_TYPE_CUDA_IPC_SERVER_WIN32 (gst_cuda_ipc_server_win32_get_type())
G_DECLARE_FINAL_TYPE (GstCudaIpcServerWin32, gst_cuda_ipc_server_win32,
GST, CUDA_IPC_SERVER_WIN32, GstCudaIpcServer);
GstCudaIpcServer * gst_cuda_ipc_server_new (const gchar * address,
GstCudaContext * context,
GstCudaIpcMode ipc_mode);
G_END_DECLS

View file

@ -0,0 +1,716 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-cudaipcsink
* @title: cudaipcsink
* @short_description: CUDA Inter Process Communication (IPC) sink
*
* cudaipcsink exports CUDA memory for connected cudaipcsrc elements to be able
* to import it
*
* ## Example launch line
* ```
* gst-launch-1.0 videotestsrc ! cudaupload ! cudaipcsink
* ```
*
* Since: 1.24
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaipcsink.h"
#include "gstcudaformat.h"
#include <gst/cuda/gstcuda-private.h>
#include <mutex>
#include <string>
#include <vector>
#ifdef G_OS_WIN32
#include "gstcudaipcserver_win32.h"
#else
#include "gstcudaipcserver_unix.h"
#endif
GST_DEBUG_CATEGORY_STATIC (cuda_ipc_sink_debug);
#define GST_CAT_DEFAULT cuda_ipc_sink_debug
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
(GST_CAPS_FEATURE_MEMORY_CUDA_MEMORY, GST_CUDA_FORMATS) "; "
GST_VIDEO_CAPS_MAKE (GST_CUDA_FORMATS)));
enum
{
PROP_0,
PROP_DEVICE_ID,
PROP_ADDRESS,
PROP_IPC_MODE,
};
#define DEFAULT_DEVICE_ID -1
#ifdef G_OS_WIN32
#define DEFAULT_ADDRESS "\\\\.\\pipe\\gst.cuda.ipc"
#else
#define DEFAULT_ADDRESS "/tmp/gst.cuda.ipc"
#endif
#define DEFAULT_IPC_MODE GST_CUDA_IPC_LEGACY
/* *INDENT-OFF* */
struct GstCudaIpcSinkPrivate
{
GstCudaContext *context = nullptr;
GstCudaStream *stream = nullptr;
GstBufferPool *fallback_pool = nullptr;
GstVideoInfo info;
GstCudaIpcServer *server = nullptr;
GstCaps *caps = nullptr;
GstSample *prepared_sample = nullptr;
GstVideoInfo mem_info;
CUipcMemHandle prepared_handle;
GstCudaSharableHandle prepared_os_handle;
std::mutex lock;
/* properties */
gint device_id = DEFAULT_DEVICE_ID;
std::string address = DEFAULT_ADDRESS;
GstCudaIpcMode ipc_mode = DEFAULT_IPC_MODE;
GstCudaIpcMode configured_ipc_mode = DEFAULT_IPC_MODE;
};
/* *INDENT-ON* */
struct _GstCudaIpcSink
{
GstBaseSink parent;
GstCudaIpcSinkPrivate *priv;
};
static void gst_cuda_ipc_sink_finalize (GObject * object);
static void gst_cuda_ipc_sink_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_win32_video_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstClock *gst_cuda_ipc_sink_provide_clock (GstElement * elem);
static void gst_cuda_ipc_sink_set_context (GstElement * elem,
GstContext * context);
static gboolean gst_cuda_ipc_sink_start (GstBaseSink * sink);
static gboolean gst_cuda_ipc_sink_stop (GstBaseSink * sink);
static gboolean gst_cuda_ipc_sink_set_caps (GstBaseSink * sink, GstCaps * caps);
static void gst_cuda_ipc_sink_get_time (GstBaseSink * sink,
GstBuffer * buf, GstClockTime * start, GstClockTime * end);
static gboolean gst_cuda_ipc_sink_propose_allocation (GstBaseSink * sink,
GstQuery * query);
static gboolean gst_cuda_ipc_sink_query (GstBaseSink * sink, GstQuery * query);
static GstFlowReturn gst_cuda_ipc_sink_prepare (GstBaseSink * sink,
GstBuffer * buf);
static GstFlowReturn gst_cuda_ipc_sink_render (GstBaseSink * sink,
GstBuffer * buf);
#define gst_cuda_ipc_sink_parent_class parent_class
G_DEFINE_TYPE (GstCudaIpcSink, gst_cuda_ipc_sink, GST_TYPE_BASE_SINK);
static void
gst_cuda_ipc_sink_class_init (GstCudaIpcSinkClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstBaseSinkClass *sink_class = GST_BASE_SINK_CLASS (klass);
object_class->finalize = gst_cuda_ipc_sink_finalize;
object_class->set_property = gst_cuda_ipc_sink_set_property;
object_class->get_property = gst_win32_video_sink_get_property;
g_object_class_install_property (object_class, PROP_DEVICE_ID,
g_param_spec_int ("cuda-device-id", "CUDA Device ID",
"CUDA device id to use (-1 = auto)", -1, G_MAXINT, DEFAULT_DEVICE_ID,
(GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY |
G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class, PROP_ADDRESS,
g_param_spec_string ("address", "Address",
"Server address. Specifies name of WIN32 named pipe "
"or unix domain socket path on Linux",
DEFAULT_ADDRESS, (GParamFlags) (G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY)));
g_object_class_install_property (object_class, PROP_IPC_MODE,
g_param_spec_enum ("ipc-mode", "IPC Mode",
"IPC mode to use", GST_TYPE_CUDA_IPC_MODE, DEFAULT_IPC_MODE,
(GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY |
G_PARAM_STATIC_STRINGS)));
gst_element_class_set_static_metadata (element_class,
"CUDA IPC Sink", "Sink/Video",
"Send CUDA memory to peer cudaipcsrc elements",
"Seungha Yang <seungha@centricular.com>");
gst_element_class_add_static_pad_template (element_class, &sink_template);
element_class->provide_clock =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_provide_clock);
element_class->set_context =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_set_context);
sink_class->start = GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_start);
sink_class->stop = GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_stop);
sink_class->set_caps = GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_set_caps);
sink_class->propose_allocation =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_propose_allocation);
sink_class->query = GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_query);
sink_class->get_times = GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_get_time);
sink_class->prepare = GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_prepare);
sink_class->render = GST_DEBUG_FUNCPTR (gst_cuda_ipc_sink_render);
GST_DEBUG_CATEGORY_INIT (cuda_ipc_sink_debug, "cudaipcsink",
0, "cudaipcsink");
}
static void
gst_cuda_ipc_sink_init (GstCudaIpcSink * self)
{
self->priv = new GstCudaIpcSinkPrivate ();
GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_REQUIRE_CLOCK);
}
static void
gst_cuda_ipc_sink_finalize (GObject * object)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (object);
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_cuda_ipc_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (object);
GstCudaIpcSinkPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
switch (prop_id) {
case PROP_DEVICE_ID:
priv->device_id = g_value_get_int (value);
break;
case PROP_ADDRESS:
{
const gchar *address = g_value_get_string (value);
priv->address.clear ();
if (address)
priv->address = address;
break;
}
case PROP_IPC_MODE:
priv->ipc_mode = (GstCudaIpcMode) g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_win32_video_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (object);
GstCudaIpcSinkPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
switch (prop_id) {
case PROP_DEVICE_ID:
g_value_set_int (value, priv->device_id);
break;
case PROP_ADDRESS:
g_value_set_string (value, priv->address.c_str ());
break;
case PROP_IPC_MODE:
g_value_set_enum (value, priv->ipc_mode);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstClock *
gst_cuda_ipc_sink_provide_clock (GstElement * elem)
{
return gst_system_clock_obtain ();
}
static void
gst_cuda_ipc_sink_set_context (GstElement * elem, GstContext * context)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (elem);
GstCudaIpcSinkPrivate *priv = self->priv;
gst_cuda_handle_set_context (elem, context, priv->device_id, &priv->context);
GST_ELEMENT_CLASS (parent_class)->set_context (elem, context);
}
static gboolean
gst_cuda_ipc_sink_start (GstBaseSink * sink)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (sink);
GstCudaIpcSinkPrivate *priv = self->priv;
gboolean virtual_memory = FALSE;
gboolean os_handle = FALSE;
GST_DEBUG_OBJECT (self, "Start");
if (!gst_cuda_ensure_element_context (GST_ELEMENT_CAST (self),
priv->device_id, &priv->context)) {
GST_ERROR_OBJECT (self, "Couldn't get CUDA context");
return FALSE;
}
g_object_get (priv->context, "virtual-memory", &virtual_memory,
"os-handle", &os_handle, nullptr);
GST_DEBUG_OBJECT (self,
"virtual-memory: %d, OS-handle: %d, requested IPC mode: %d",
virtual_memory, os_handle, priv->ipc_mode);
priv->configured_ipc_mode = priv->ipc_mode;
if (priv->configured_ipc_mode == GST_CUDA_IPC_MMAP &&
(!virtual_memory || !os_handle)) {
GST_ELEMENT_WARNING (self, RESOURCE, SETTINGS, ("Not supported IPC mode"),
("MMAP mode IPC is not supported by device"));
priv->configured_ipc_mode = GST_CUDA_IPC_LEGACY;
}
GST_DEBUG_OBJECT (self, "Selected IPC mode: %d", priv->configured_ipc_mode);
priv->server = gst_cuda_ipc_server_new (priv->address.c_str (),
priv->context, priv->configured_ipc_mode);
if (!priv->server) {
gst_clear_object (&priv->context);
GST_ERROR_OBJECT (self, "Couldn't create server object");
return FALSE;
}
priv->stream = gst_cuda_stream_new (priv->context);
return TRUE;
}
static gboolean
gst_cuda_ipc_sink_stop (GstBaseSink * sink)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (sink);
GstCudaIpcSinkPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Stop");
if (priv->server)
gst_cuda_ipc_server_stop (priv->server);
gst_clear_object (&priv->server);
GST_DEBUG_OBJECT (self, "Server cleared");
if (priv->fallback_pool) {
gst_buffer_pool_set_active (priv->fallback_pool, FALSE);
gst_clear_object (&priv->fallback_pool);
}
gst_clear_sample (&priv->prepared_sample);
gst_clear_cuda_stream (&priv->stream);
gst_clear_object (&priv->context);
return TRUE;
}
static void
gst_cuda_ipc_sink_get_time (GstBaseSink * sink, GstBuffer * buf,
GstClockTime * start, GstClockTime * end)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (sink);
GstCudaIpcSinkPrivate *priv = self->priv;
GstClockTime timestamp;
timestamp = GST_BUFFER_PTS (buf);
if (!GST_CLOCK_TIME_IS_VALID (timestamp))
timestamp = GST_BUFFER_DTS (buf);
if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
*start = timestamp;
if (GST_BUFFER_DURATION_IS_VALID (buf)) {
*end = timestamp + GST_BUFFER_DURATION (buf);
} else if (priv->info.fps_n > 0) {
*end = timestamp +
gst_util_uint64_scale_int (GST_SECOND, priv->info.fps_d,
priv->info.fps_n);
} else if (sink->segment.rate < 0) {
*end = timestamp;
}
}
}
static gboolean
gst_cuda_ipc_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (sink);
GstCudaIpcSinkPrivate *priv = self->priv;
GstStructure *config;
GstCaps *new_caps;
GstStructure *s;
const gchar *str;
GST_DEBUG_OBJECT (self, "New caps %" GST_PTR_FORMAT, caps);
if (!gst_video_info_from_caps (&priv->info, caps)) {
GST_ERROR_OBJECT (self, "Invalid caps %" GST_PTR_FORMAT, caps);
return FALSE;
}
s = gst_caps_get_structure (caps, 0);
/* Takes values we know it's always deserializable */
new_caps = gst_caps_new_empty_simple ("video/x-raw");
gst_caps_set_simple (new_caps, "format", G_TYPE_STRING,
gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&priv->info)),
"width", G_TYPE_INT, priv->info.width,
"height", G_TYPE_INT, priv->info.height,
"framerate", GST_TYPE_FRACTION, priv->info.fps_n, priv->info.fps_d,
"pixel-aspect-ratio", GST_TYPE_FRACTION, priv->info.par_n,
priv->info.par_d, nullptr);
str = gst_structure_get_string (s, "colorimetry");
if (str)
gst_caps_set_simple (new_caps, "colorimetry", G_TYPE_STRING, str, nullptr);
str = gst_structure_get_string (s, "mastering-display-info");
if (str) {
gst_caps_set_simple (new_caps, "mastering-display-info", G_TYPE_STRING,
str, nullptr);
}
str = gst_structure_get_string (s, "content-light-level");
if (str) {
gst_caps_set_simple (new_caps, "content-light-level", G_TYPE_STRING,
str, nullptr);
}
gst_caps_set_features_simple (new_caps,
gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_CUDA_MEMORY, nullptr));
gst_clear_caps (&priv->caps);
priv->caps = new_caps;
if (priv->fallback_pool) {
gst_buffer_pool_set_active (priv->fallback_pool, FALSE);
gst_object_unref (priv->fallback_pool);
}
priv->fallback_pool = gst_cuda_buffer_pool_new (priv->context);
config = gst_buffer_pool_get_config (priv->fallback_pool);
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
gst_buffer_pool_config_set_params (config, priv->caps,
GST_VIDEO_INFO_SIZE (&priv->info), 0, 0);
if (priv->stream)
gst_buffer_pool_config_set_cuda_stream (config, priv->stream);
if (priv->configured_ipc_mode == GST_CUDA_IPC_MMAP) {
gst_buffer_pool_config_set_cuda_alloc_method (config,
GST_CUDA_MEMORY_ALLOC_MMAP);
}
if (!gst_buffer_pool_set_config (priv->fallback_pool, config)) {
GST_ERROR_OBJECT (self, "Couldn't set pool config");
gst_clear_object (&priv->fallback_pool);
return FALSE;
}
if (!gst_buffer_pool_set_active (priv->fallback_pool, TRUE)) {
GST_ERROR_OBJECT (self, "Couldn't active pool");
gst_clear_object (&priv->fallback_pool);
return FALSE;
}
return TRUE;
}
static gboolean
gst_cuda_ipc_sink_propose_allocation (GstBaseSink * sink, GstQuery * query)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (sink);
GstCudaIpcSinkPrivate *priv = self->priv;
GstCaps *caps;
GstBufferPool *pool = nullptr;
GstVideoInfo info;
guint size;
gboolean need_pool;
gst_query_parse_allocation (query, &caps, &need_pool);
if (!caps) {
GST_WARNING_OBJECT (sink, "No caps specified");
return FALSE;
}
if (!gst_video_info_from_caps (&info, caps)) {
GST_WARNING_OBJECT (sink, "Invalid caps %" GST_PTR_FORMAT, caps);
return FALSE;
}
/* the normal size of a frame */
size = info.size;
if (need_pool) {
GstStructure *config;
pool = gst_cuda_buffer_pool_new (priv->context);
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_add_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
if (priv->stream)
gst_buffer_pool_config_set_cuda_stream (config, priv->stream);
if (priv->configured_ipc_mode == GST_CUDA_IPC_MMAP) {
gst_buffer_pool_config_set_cuda_alloc_method (config,
GST_CUDA_MEMORY_ALLOC_MMAP);
}
size = GST_VIDEO_INFO_SIZE (&info);
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
if (!gst_buffer_pool_set_config (pool, config)) {
GST_ERROR_OBJECT (pool, "Couldn't set config");
gst_object_unref (pool);
return FALSE;
}
}
gst_query_add_allocation_pool (query, pool, size, 0, 0);
gst_clear_object (&pool);
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
return TRUE;
}
static gboolean
gst_cuda_ipc_sink_query (GstBaseSink * sink, GstQuery * query)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (sink);
GstCudaIpcSinkPrivate *priv = self->priv;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONTEXT:
if (gst_cuda_handle_context_query (GST_ELEMENT (self), query,
priv->context)) {
return TRUE;
}
break;
default:
break;
}
return GST_BASE_SINK_CLASS (parent_class)->query (sink, query);
}
static GstFlowReturn
gst_cuda_ipc_sink_prepare (GstBaseSink * sink, GstBuffer * buf)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (sink);
GstCudaIpcSinkPrivate *priv = self->priv;
GstBuffer *cuda_buf;
GstMemory *mem;
GstCudaMemory *cmem;
GstMapInfo info;
CUresult ret;
CUdeviceptr ptr;
std::string handle_dump;
gst_clear_sample (&priv->prepared_sample);
cuda_buf = buf;
mem = gst_buffer_peek_memory (cuda_buf, 0);
if (!gst_is_cuda_memory (mem) ||
GST_CUDA_MEMORY_CAST (mem)->context != priv->context) {
if (gst_buffer_pool_acquire_buffer (priv->fallback_pool, &cuda_buf,
nullptr) != GST_FLOW_OK) {
GST_ERROR_OBJECT (self, "Couldn't acquire fallback buffer");
return GST_FLOW_ERROR;
}
if (!gst_cuda_buffer_copy (cuda_buf, GST_CUDA_BUFFER_COPY_CUDA,
&priv->info, buf, GST_CUDA_BUFFER_COPY_SYSTEM, &priv->info,
priv->context, priv->stream)) {
GST_ERROR_OBJECT (self, "Couldn't copy memory");
goto error;
}
mem = gst_buffer_peek_memory (cuda_buf, 0);
} else {
GstCudaMemory *cmem = GST_CUDA_MEMORY_CAST (mem);
GstCudaMemoryAllocMethod alloc_method =
gst_cuda_memory_get_alloc_method (cmem);
/* Copy into fallback pool if the memory belongs to fixed size buffer pool
* (e.g., decoder output) or mem allocation type is different */
if (gst_cuda_memory_is_from_fixed_pool (mem) ||
(priv->configured_ipc_mode == GST_CUDA_IPC_MMAP &&
alloc_method != GST_CUDA_MEMORY_ALLOC_MMAP) ||
(priv->configured_ipc_mode == GST_CUDA_IPC_LEGACY &&
alloc_method != GST_CUDA_MEMORY_ALLOC_MALLOC)) {
if (gst_buffer_pool_acquire_buffer (priv->fallback_pool, &cuda_buf,
nullptr) != GST_FLOW_OK) {
GST_ERROR_OBJECT (self, "Couldn't acquire fallback buffer");
return GST_FLOW_ERROR;
}
if (!gst_cuda_buffer_copy (cuda_buf, GST_CUDA_BUFFER_COPY_CUDA,
&priv->info, buf, GST_CUDA_BUFFER_COPY_CUDA, &priv->info,
priv->context, priv->stream)) {
GST_ERROR_OBJECT (self, "Couldn't copy memory");
goto error;
}
mem = gst_buffer_peek_memory (cuda_buf, 0);
}
}
cmem = GST_CUDA_MEMORY_CAST (mem);
priv->mem_info = cmem->info;
if (!gst_memory_map (mem, &info, (GstMapFlags) (GST_MAP_READ | GST_MAP_CUDA))) {
GST_ERROR_OBJECT (self, "Couldn't map memory");
goto error;
}
ptr = (CUdeviceptr) info.data;
gst_memory_unmap (mem, &info);
gst_cuda_memory_sync (cmem);
if (priv->configured_ipc_mode == GST_CUDA_IPC_MMAP) {
if (!gst_cuda_memory_export (cmem, (void *) &priv->prepared_os_handle)) {
GST_ERROR_OBJECT (self, "Couldn't export memory");
goto error;
}
} else {
if (!gst_cuda_context_push (cmem->context)) {
GST_ERROR_OBJECT (self, "Couldn't push context");
goto error;
}
ret = CuIpcGetMemHandle (&priv->prepared_handle, ptr);
gst_cuda_context_pop (nullptr);
if (!gst_cuda_result (ret)) {
GST_ERROR_OBJECT (self, "Couldn't get IPC handle");
goto error;
}
handle_dump = gst_cuda_ipc_mem_handle_to_string (priv->prepared_handle);
GST_TRACE_OBJECT (self, "Exported handle value for %" G_GUINTPTR_FORMAT
" %s", ptr, handle_dump.c_str ());
}
priv->prepared_sample = gst_sample_new (cuda_buf,
priv->caps, nullptr, nullptr);
if (cuda_buf != buf)
gst_buffer_unref (cuda_buf);
return GST_FLOW_OK;
error:
if (cuda_buf != buf)
gst_buffer_unref (cuda_buf);
return GST_FLOW_ERROR;
}
static GstFlowReturn
gst_cuda_ipc_sink_render (GstBaseSink * sink, GstBuffer * buf)
{
GstCudaIpcSink *self = GST_CUDA_IPC_SINK (sink);
GstCudaIpcSinkPrivate *priv = self->priv;
GstClockTime pts;
GstClockTime now_system;
GstClockTime buf_pts;
GstClockTime buffer_clock = GST_CLOCK_TIME_NONE;
GstFlowReturn ret;
if (!priv->prepared_sample) {
GST_ERROR_OBJECT (self, "Have no prepared sample");
return GST_FLOW_ERROR;
}
pts = now_system = gst_util_get_timestamp ();
buf_pts = GST_BUFFER_PTS (buf);
if (!GST_CLOCK_TIME_IS_VALID (buf_pts))
buf_pts = GST_BUFFER_DTS (buf);
if (GST_CLOCK_TIME_IS_VALID (buf_pts)) {
buffer_clock = gst_segment_to_running_time (&sink->segment,
GST_FORMAT_TIME, buf_pts) +
GST_ELEMENT_CAST (sink)->base_time + gst_base_sink_get_latency (sink);
}
if (GST_CLOCK_TIME_IS_VALID (buffer_clock)) {
GstClock *clock = gst_element_get_clock (GST_ELEMENT_CAST (sink));
if (!gst_cuda_ipc_clock_is_system (clock)) {
GstClockTime now_gst = gst_clock_get_time (clock);
GstClockTimeDiff converted = buffer_clock;
converted -= now_gst;
converted += now_system;
if (converted < 0) {
/* Shouldn't happen */
GST_WARNING_OBJECT (self, "Negative buffer clock");
pts = 0;
} else {
pts = converted;
}
} else {
/* buffer clock is already system time */
pts = buffer_clock;
}
gst_object_unref (clock);
}
if (priv->ipc_mode == GST_CUDA_IPC_LEGACY) {
ret = gst_cuda_ipc_server_send_data (priv->server, priv->prepared_sample,
priv->mem_info, priv->prepared_handle, pts);
} else {
ret = gst_cuda_ipc_server_send_mmap_data (priv->server,
priv->prepared_sample, priv->mem_info, priv->prepared_os_handle, pts);
}
return ret;
}

View file

@ -0,0 +1,33 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/base/gstbasesink.h>
#include <gst/video/video.h>
#include <gst/cuda/gstcuda.h>
G_BEGIN_DECLS
#define GST_TYPE_CUDA_IPC_SINK (gst_cuda_ipc_sink_get_type())
G_DECLARE_FINAL_TYPE (GstCudaIpcSink, gst_cuda_ipc_sink,
GST, CUDA_IPC_SINK, GstBaseSink);
G_END_DECLS

View file

@ -0,0 +1,566 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-cudaipcsrc
* @title: cudaipcsrc
* @short_description: CUDA Inter Process Communication (IPC) src
*
* cudaipcsrc imports CUDA memory exported by peer cudaipcsrc element
*
* ## Example launch line
* ```
* gst-launch-1.0 cudaipcsrc ! queue ! cudadownload ! videoconvert ! queue ! autovideosink
* ```
*
* Since: 1.24
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaipcsrc.h"
#include "gstcudaformat.h"
#include <mutex>
#include <string>
#ifdef G_OS_WIN32
#include "gstcudaipcclient_win32.h"
#else
#include "gstcudaipcclient_unix.h"
#endif
GST_DEBUG_CATEGORY_STATIC (gst_cuda_ipc_src_debug);
#define GST_CAT_DEFAULT gst_cuda_ipc_src_debug
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
(GST_CAPS_FEATURE_MEMORY_CUDA_MEMORY,
GST_CUDA_FORMATS)));
enum
{
PROP_0,
PROP_DEVICE_ID,
PROP_ADDRESS,
PROP_PROCESSING_DEADLINE,
PROP_IO_MODE,
PROP_CONN_TIMEOUT,
PROP_BUFFER_SIZE,
};
#define DEFAULT_DEVICE_ID -1
#ifdef G_OS_WIN32
#define DEFAULT_ADDRESS "\\\\.\\pipe\\gst.cuda.ipc"
#else
#define DEFAULT_ADDRESS "/tmp/gst.cuda.ipc"
#endif
#define DEFAULT_PROCESSING_DEADLINE (20 * GST_MSECOND)
#define DEFAULT_IO_MODE GST_CUDA_IPC_IO_COPY
#define DEFAULT_CONN_TIMEOUT 5
#define DEFAULT_BUFFER_SIZE 3
/* *INDENT-OFF* */
struct GstCudaIpcSrcPrivate
{
GstCudaContext *context = nullptr;
GstCudaStream *stream = nullptr;
GstCudaIpcClient *client = nullptr;
GstCaps *caps = nullptr;
GstVideoInfo info;
std::mutex lock;
bool flushing = false;
/* properties */
gint device_id = DEFAULT_DEVICE_ID;
std::string address = DEFAULT_ADDRESS;
GstClockTime processing_deadline = DEFAULT_PROCESSING_DEADLINE;
GstCudaIpcIOMode io_mode = DEFAULT_IO_MODE;
guint conn_timeout = DEFAULT_CONN_TIMEOUT;
guint buffer_size = DEFAULT_BUFFER_SIZE;
};
/* *INDENT-ON* */
struct _GstCudaIpcSrc
{
GstBaseSrc parent;
GstCudaIpcSrcPrivate *priv;
};
static void gst_cuda_ipc_src_finalize (GObject * object);
static void gst_cuda_ipc_src_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_win32_video_src_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstClock *gst_cuda_ipc_src_provide_clock (GstElement * elem);
static void gst_cuda_ipc_src_set_context (GstElement * elem,
GstContext * context);
static gboolean gst_cuda_ipc_src_start (GstBaseSrc * src);
static gboolean gst_cuda_ipc_src_stop (GstBaseSrc * src);
static gboolean gst_cuda_ipc_src_unlock (GstBaseSrc * src);
static gboolean gst_cuda_ipc_src_unlock_stop (GstBaseSrc * src);
static gboolean gst_cuda_ipc_src_query (GstBaseSrc * src, GstQuery * query);
static GstCaps *gst_cuda_ipc_src_get_caps (GstBaseSrc * src, GstCaps * filter);
static GstCaps *gst_cuda_ipc_src_fixate (GstBaseSrc * src, GstCaps * caps);
static GstFlowReturn gst_cuda_ipc_src_create (GstBaseSrc * src, guint64 offset,
guint size, GstBuffer ** buf);
#define gst_cuda_ipc_src_parent_class parent_class
G_DEFINE_TYPE (GstCudaIpcSrc, gst_cuda_ipc_src, GST_TYPE_BASE_SRC);
static void
gst_cuda_ipc_src_class_init (GstCudaIpcSrcClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstBaseSrcClass *src_class = GST_BASE_SRC_CLASS (klass);
object_class->finalize = gst_cuda_ipc_src_finalize;
object_class->set_property = gst_cuda_ipc_src_set_property;
object_class->get_property = gst_win32_video_src_get_property;
g_object_class_install_property (object_class, PROP_DEVICE_ID,
g_param_spec_int ("cuda-device-id", "CUDA Device ID",
"CUDA device id to use (-1 = auto)", -1, G_MAXINT, DEFAULT_DEVICE_ID,
(GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY |
G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class, PROP_ADDRESS,
g_param_spec_string ("address", "Address",
"Server address. Specifies name of WIN32 named pipe "
"or unix domain socket path on Linux",
DEFAULT_ADDRESS, (GParamFlags) (G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY)));
g_object_class_install_property (object_class, PROP_PROCESSING_DEADLINE,
g_param_spec_uint64 ("processing-deadline", "Processing deadline",
"Maximum processing time for a buffer in nanoseconds", 0, G_MAXUINT64,
DEFAULT_PROCESSING_DEADLINE, (GParamFlags) (G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_PLAYING)));
g_object_class_install_property (object_class, PROP_IO_MODE,
g_param_spec_enum ("io-mode", "IO Mode",
"Memory I/O mode to use. This option will be ignored if the selected "
"IPC mode is mmap",
GST_TYPE_CUDA_IPC_IO_MODE, DEFAULT_IO_MODE,
(GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY |
G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class, PROP_CONN_TIMEOUT,
g_param_spec_uint ("connection-timeout", "Connection Timeout",
"Connection timeout in seconds (0 = never timeout)", 0, G_MAXINT,
DEFAULT_CONN_TIMEOUT,
(GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY |
G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class, PROP_BUFFER_SIZE,
g_param_spec_uint ("buffer-size", "Buffer Size",
"Size of internal buffer", 1, G_MAXINT,
DEFAULT_BUFFER_SIZE,
(GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY |
G_PARAM_STATIC_STRINGS)));
gst_element_class_set_static_metadata (element_class,
"CUDA IPC Src", "Source/Video",
"Receive CUDA memory from the cudaipcsrc element",
"Seungha Yang <seungha@centricular.com>");
gst_element_class_add_static_pad_template (element_class, &src_template);
element_class->provide_clock =
GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_provide_clock);
element_class->set_context = GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_set_context);
src_class->start = GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_start);
src_class->stop = GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_stop);
src_class->unlock = GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_unlock);
src_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_unlock_stop);
src_class->query = GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_query);
src_class->get_caps = GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_get_caps);
src_class->fixate = GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_fixate);
src_class->create = GST_DEBUG_FUNCPTR (gst_cuda_ipc_src_create);
GST_DEBUG_CATEGORY_INIT (gst_cuda_ipc_src_debug, "cudaipcsrc",
0, "cudaipcsrc");
gst_type_mark_as_plugin_api (GST_TYPE_CUDA_IPC_IO_MODE,
(GstPluginAPIFlags) 0);
}
static void
gst_cuda_ipc_src_init (GstCudaIpcSrc * self)
{
gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME);
gst_base_src_set_live (GST_BASE_SRC (self), TRUE);
self->priv = new GstCudaIpcSrcPrivate ();
GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_REQUIRE_CLOCK);
}
static void
gst_cuda_ipc_src_finalize (GObject * object)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (object);
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_cuda_ipc_src_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (object);
GstCudaIpcSrcPrivate *priv = self->priv;
std::unique_lock < std::mutex > lk (priv->lock);
switch (prop_id) {
case PROP_DEVICE_ID:
priv->device_id = g_value_get_int (value);
break;
case PROP_ADDRESS:
{
const gchar *address = g_value_get_string (value);
priv->address.clear ();
if (address)
priv->address = address;
break;
}
case PROP_PROCESSING_DEADLINE:
{
GstClockTime prev_val, new_val;
prev_val = priv->processing_deadline;
new_val = g_value_get_uint64 (value);
priv->processing_deadline = new_val;
if (prev_val != new_val) {
lk.unlock ();
GST_DEBUG_OBJECT (self, "Posting latency message");
gst_element_post_message (GST_ELEMENT_CAST (self),
gst_message_new_latency (GST_OBJECT_CAST (self)));
}
break;
}
case PROP_IO_MODE:
priv->io_mode = (GstCudaIpcIOMode) g_value_get_enum (value);
break;
case PROP_CONN_TIMEOUT:
priv->conn_timeout = g_value_get_uint (value);
break;
case PROP_BUFFER_SIZE:
priv->buffer_size = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_win32_video_src_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (object);
GstCudaIpcSrcPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
switch (prop_id) {
case PROP_DEVICE_ID:
g_value_set_int (value, priv->device_id);
break;
case PROP_ADDRESS:
g_value_set_string (value, priv->address.c_str ());
break;
case PROP_PROCESSING_DEADLINE:
g_value_set_uint64 (value, priv->processing_deadline);
break;
case PROP_IO_MODE:
g_value_set_enum (value, priv->io_mode);
break;
case PROP_CONN_TIMEOUT:
g_value_set_uint (value, priv->conn_timeout);
break;
case PROP_BUFFER_SIZE:
g_value_set_uint (value, priv->buffer_size);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstClock *
gst_cuda_ipc_src_provide_clock (GstElement * elem)
{
return gst_system_clock_obtain ();
}
static void
gst_cuda_ipc_src_set_context (GstElement * elem, GstContext * context)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (elem);
GstCudaIpcSrcPrivate *priv = self->priv;
gst_cuda_handle_set_context (elem, context, priv->device_id, &priv->context);
GST_ELEMENT_CLASS (parent_class)->set_context (elem, context);
}
static gboolean
gst_cuda_ipc_src_start (GstBaseSrc * src)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (src);
GstCudaIpcSrcPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Start");
if (!gst_cuda_ensure_element_context (GST_ELEMENT_CAST (self),
priv->device_id, &priv->context)) {
GST_ERROR_OBJECT (self, "Couldn't get CUDA context");
return FALSE;
}
priv->stream = gst_cuda_stream_new (priv->context);
std::lock_guard < std::mutex > lk (priv->lock);
priv->client = gst_cuda_ipc_client_new (priv->address.c_str (), priv->context,
priv->stream, priv->io_mode, priv->conn_timeout, priv->buffer_size - 1);
return TRUE;
}
static gboolean
gst_cuda_ipc_src_stop (GstBaseSrc * src)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (src);
GstCudaIpcSrcPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
GST_DEBUG_OBJECT (self, "Stop");
if (priv->client)
gst_cuda_ipc_client_stop (priv->client);
gst_clear_object (&priv->client);
gst_clear_cuda_stream (&priv->stream);
gst_clear_object (&priv->context);
gst_clear_caps (&priv->caps);
return TRUE;
}
static gboolean
gst_cuda_ipc_src_unlock (GstBaseSrc * src)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (src);
GstCudaIpcSrcPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Unlock");
std::lock_guard < std::mutex > lk (priv->lock);
priv->flushing = true;
if (priv->client)
gst_cuda_ipc_client_set_flushing (priv->client, true);
return TRUE;
}
static gboolean
gst_cuda_ipc_src_unlock_stop (GstBaseSrc * src)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (src);
GstCudaIpcSrcPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Unlock stop");
std::lock_guard < std::mutex > lk (priv->lock);
priv->flushing = false;
if (priv->client)
gst_cuda_ipc_client_set_flushing (priv->client, false);
return TRUE;
}
static gboolean
gst_cuda_ipc_src_query (GstBaseSrc * src, GstQuery * query)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (src);
GstCudaIpcSrcPrivate *priv = self->priv;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_LATENCY:
{
std::lock_guard < std::mutex > lk (priv->lock);
if (GST_CLOCK_TIME_IS_VALID (priv->processing_deadline)) {
gst_query_set_latency (query, TRUE, priv->processing_deadline,
GST_CLOCK_TIME_NONE);
} else {
gst_query_set_latency (query, TRUE, 0, 0);
}
return TRUE;
}
case GST_QUERY_CONTEXT:
if (gst_cuda_handle_context_query (GST_ELEMENT (self), query,
priv->context)) {
return TRUE;
}
break;
default:
break;
}
return GST_BASE_SRC_CLASS (parent_class)->query (src, query);
}
static GstCaps *
gst_cuda_ipc_src_get_caps (GstBaseSrc * src, GstCaps * filter)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (src);
GstCudaIpcSrcPrivate *priv = self->priv;
GstCudaIpcClient *client = nullptr;
GstCaps *caps = nullptr;
GST_DEBUG_OBJECT (self, "Get caps");
priv->lock.lock ();
if (priv->caps)
caps = gst_caps_ref (priv->caps);
else if (priv->client)
client = (GstCudaIpcClient *) gst_object_ref (priv->client);
priv->lock.unlock ();
if (!caps && client)
caps = gst_cuda_ipc_client_get_caps (priv->client);
if (!caps)
caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (src));
if (filter) {
GstCaps *tmp = gst_caps_intersect_full (filter,
caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
caps = tmp;
}
gst_clear_object (&client);
GST_DEBUG_OBJECT (self, "Returning caps %" GST_PTR_FORMAT, caps);
return caps;
}
static GstCaps *
gst_cuda_ipc_src_fixate (GstBaseSrc * src, GstCaps * caps)
{
/* We don't negotiate with server. In here, we do fixate resolution to
* 320 x 240 (same as default of videotestsrc) which makes a little more
* sense than 1x1 */
caps = gst_caps_make_writable (caps);
for (guint i = 0; i < gst_caps_get_size (caps); i++) {
GstStructure *s = gst_caps_get_structure (caps, i);
gst_structure_fixate_field_nearest_int (s, "width", 320);
gst_structure_fixate_field_nearest_int (s, "height", 240);
}
return gst_caps_fixate (caps);
}
static GstFlowReturn
gst_cuda_ipc_src_create (GstBaseSrc * src, guint64 offset, guint size,
GstBuffer ** buf)
{
GstCudaIpcSrc *self = GST_CUDA_IPC_SRC (src);
GstCudaIpcSrcPrivate *priv = self->priv;
GstFlowReturn ret;
GstSample *sample = nullptr;
GstCaps *caps;
GstClock *clock;
bool is_system_clock = true;
GstClockTime pts;
GstClockTime base_time;
GstClockTime now_system;
GstClockTime now_gst;
GstClockTime remote_pts;
GstBuffer *buffer;
ret = gst_cuda_ipc_client_run (priv->client);
if (ret != GST_FLOW_OK)
return ret;
ret = gst_cuda_ipc_client_get_sample (priv->client, &sample);
if (ret != GST_FLOW_OK)
return ret;
now_system = gst_util_get_timestamp ();
clock = gst_element_get_clock (GST_ELEMENT_CAST (self));
now_gst = gst_clock_get_time (clock);
base_time = GST_ELEMENT_CAST (self)->base_time;
is_system_clock = gst_cuda_ipc_clock_is_system (clock);
gst_object_unref (clock);
buffer = gst_sample_get_buffer (sample);
remote_pts = GST_BUFFER_PTS (buffer);
if (!is_system_clock) {
GstClockTimeDiff now_pts = now_gst - base_time + remote_pts - now_system;
if (now_pts >= 0)
pts = now_pts;
else
pts = 0;
} else {
if (remote_pts >= base_time) {
pts = remote_pts - base_time;
} else {
GST_WARNING_OBJECT (self,
"Remote clock is smaller than our base time, remote %"
GST_TIME_FORMAT ", base_time %" GST_TIME_FORMAT,
GST_TIME_ARGS (remote_pts), GST_TIME_ARGS (base_time));
pts = 0;
}
}
GST_BUFFER_PTS (buffer) = pts;
std::unique_lock < std::mutex > lk (priv->lock);
caps = gst_sample_get_caps (sample);
if (!priv->caps || !gst_caps_is_equal (priv->caps, caps)) {
gst_caps_replace (&priv->caps, caps);
lk.unlock ();
gst_base_src_set_caps (src, priv->caps);
}
*buf = gst_buffer_ref (buffer);
gst_sample_unref (sample);
return GST_FLOW_OK;
}

View file

@ -0,0 +1,33 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/base/gstbasesrc.h>
#include <gst/video/video.h>
#include <gst/cuda/gstcuda.h>
G_BEGIN_DECLS
#define GST_TYPE_CUDA_IPC_SRC (gst_cuda_ipc_src_get_type())
G_DECLARE_FINAL_TYPE (GstCudaIpcSrc, gst_cuda_ipc_src,
GST, CUDA_IPC_SRC, GstBaseSrc);
G_END_DECLS

View file

@ -0,0 +1,28 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
G_BEGIN_DECLS
void gst_cuda_ipc_client_deinit (void);
G_END_DECLS

View file

@ -3,6 +3,11 @@ nvcodec_sources = [
'gstcudaconverter.c',
'gstcudaconvertscale.c',
'gstcudafilter.c',
'gstcudaipc.cpp',
'gstcudaipcclient.cpp',
'gstcudaipcserver.cpp',
'gstcudaipcsink.cpp',
'gstcudaipcsrc.cpp',
'gstcudamemorycopy.c',
'gstcuvidloader.c',
'gstnvav1dec.cpp',
@ -28,6 +33,16 @@ nvmm_sources = [
'gstcudanvmm.c',
]
nvcodec_win32_sources = [
'gstcudaipcclient_win32.cpp',
'gstcudaipcserver_win32.cpp',
]
nvcodec_unix_sources = [
'gstcudaipcclient_unix.cpp',
'gstcudaipcserver_unix.cpp',
]
if get_option('nvcodec').disabled()
subdir_done()
endif
@ -41,6 +56,7 @@ endif
plugin_incdirs = [configinc, cuda_stubinc]
extra_args = ['-DGST_USE_UNSTABLE_API']
extra_deps = []
if gstgl_dep.found()
extra_args += ['-DHAVE_CUDA_GST_GL']
@ -60,6 +76,13 @@ if host_system == 'linux'
extra_args += ['-DHAVE_NVCODEC_NVMM']
nvcodec_sources += nvmm_sources
endif
gio_unix_dep = dependency('gio-unix-2.0', required : get_option('nvcodec'))
extra_deps += [gio_dep, gio_unix_dep]
nvcodec_sources += nvcodec_unix_sources
else
nvcodec_sources += nvcodec_win32_sources
endif
if cc.get_id() != 'msvc'
@ -82,7 +105,9 @@ gstnvcodec = library('gstnvcodec',
c_args : gst_plugins_bad_args + extra_args,
cpp_args : gst_plugins_bad_args + extra_args,
include_directories : plugin_incdirs,
dependencies : [gstbase_dep, gstvideo_dep, gstpbutils_dep, gstgl_dep, gstglproto_dep, gmodule_dep, gstcodecs_dep, gstd3d11_dep, gstcuda_dep],
dependencies : [gstbase_dep, gstvideo_dep, gstpbutils_dep, gstgl_dep,
gstglproto_dep, gmodule_dep, gstcodecs_dep,
gstd3d11_dep, gstcuda_dep] + extra_deps,
override_options : ['cpp_std=c++14'],
install : true,
install_dir : plugins_install_dir,

View file

@ -49,6 +49,9 @@
#endif
#include "gstnvh264encoder.h"
#include "gstnvh265encoder.h"
#include "gstcudaipcsink.h"
#include "gstcudaipcsrc.h"
#include "gstnvcodecutils.h"
GST_DEBUG_CATEGORY (gst_nvcodec_debug);
GST_DEBUG_CATEGORY (gst_nvdec_debug);
@ -61,6 +64,12 @@ GST_DEBUG_CATEGORY (gst_cuda_nvmm_debug);
#define GST_CAT_DEFAULT gst_nvcodec_debug
static void
plugin_deinit (gpointer data)
{
gst_cuda_ipc_client_deinit ();
}
static gboolean
plugin_init (GstPlugin * plugin)
{
@ -245,8 +254,12 @@ plugin_init (GstPlugin * plugin)
}
gst_cuda_memory_copy_register (plugin, GST_RANK_NONE);
gst_cuda_filter_plugin_init (plugin);
gst_element_register (plugin,
"cudaipcsink", GST_RANK_NONE, GST_TYPE_CUDA_IPC_SINK);
gst_element_register (plugin,
"cudaipcsrc", GST_RANK_NONE, GST_TYPE_CUDA_IPC_SRC);
gst_cuda_memory_init_once ();
#ifdef HAVE_NVCODEC_NVMM
@ -255,6 +268,10 @@ plugin_init (GstPlugin * plugin)
}
#endif
g_object_set_data_full (G_OBJECT (plugin),
"plugin-nvcodec-shutdown", (gpointer) "shutdown-data",
(GDestroyNotify) plugin_deinit);
return TRUE;
}