Add method to get source/sink type.

Also provide convenience method to enumerate all sinks connected to a source.
This commit is contained in:
Peter Johnson
2016-11-15 22:18:32 -08:00
parent 6446b9ef10
commit 3381340eb5
17 changed files with 253 additions and 34 deletions

View File

@@ -85,6 +85,25 @@ enum CS_PropertyType {
CS_PROP_ENUM = 8
};
//
// Source types
//
enum CS_SourceType {
CS_SOURCE_UNKNOWN = 0,
CS_SOURCE_USB = 1,
CS_SOURCE_HTTP = 2,
CS_SOURCE_CV = 4
};
//
// Sink types
//
enum CS_SinkType {
CS_SINK_UNKNOWN = 0,
CS_SINK_MJPEG = 2,
CS_SINK_CV = 4
};
//
// Listener event types
//
@@ -160,6 +179,7 @@ CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode,
//
// Source Functions
//
CS_SourceType CS_GetSourceType(CS_Source source, CS_Status* status);
char* CS_GetSourceName(CS_Source source, CS_Status* status);
char* CS_GetSourceDescription(CS_Source source, CS_Status* status);
uint64_t CS_GetSourceLastFrameTime(CS_Source source, CS_Status* status);
@@ -184,6 +204,8 @@ CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height,
CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status);
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
CS_Status* status);
CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count,
CS_Status* status);
CS_Source CS_CopySource(CS_Source source, CS_Status* status);
void CS_ReleaseSource(CS_Source source, CS_Status* status);
@@ -218,6 +240,7 @@ CS_Sink CS_CreateCvSinkCallback(const char* name, void* data,
//
// Sink Functions
//
CS_SinkType CS_GetSinkType(CS_Sink sink, CS_Status* status);
char* CS_GetSinkName(CS_Sink sink, CS_Status* status);
char* CS_GetSinkDescription(CS_Sink sink, CS_Status* status);
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);

View File

@@ -165,6 +165,7 @@ CS_Source CreateCvSource(llvm::StringRef name, const VideoMode& mode,
//
// Source Functions
//
CS_SourceType GetSourceType(CS_Source source, CS_Status* status);
std::string GetSourceName(CS_Source source, CS_Status* status);
llvm::StringRef GetSourceName(CS_Source source,
llvm::SmallVectorImpl<char>& buf,
@@ -190,6 +191,8 @@ bool SetSourceResolution(CS_Source source, int width, int height,
bool SetSourceFPS(CS_Source source, int fps, CS_Status* status);
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
CS_Status* status);
llvm::ArrayRef<CS_Sink> EnumerateSourceSinks(
CS_Source source, llvm::SmallVectorImpl<CS_Sink>& vec, CS_Status* status);
CS_Source CopySource(CS_Source source, CS_Status* status);
void ReleaseSource(CS_Source source, CS_Status* status);
@@ -223,6 +226,7 @@ CS_Sink CreateCvSinkCallback(llvm::StringRef name,
//
// Sink Functions
//
CS_SinkType GetSinkType(CS_Sink sink, CS_Status* status);
std::string GetSinkName(CS_Sink sink, CS_Status* status);
llvm::StringRef GetSinkName(CS_Sink sink, llvm::SmallVectorImpl<char>& buf,
CS_Status* status);

View File

@@ -83,6 +83,13 @@ class VideoSource {
friend class VideoSink;
public:
enum Type {
kUnknown = CS_SOURCE_UNKNOWN,
kUSB = CS_SOURCE_USB,
kHTTP = CS_SOURCE_HTTP,
kCv = CS_SOURCE_CV
};
VideoSource() noexcept : m_handle(0) {}
VideoSource(const VideoSource& source);
VideoSource(VideoSource&& other) noexcept;
@@ -99,6 +106,9 @@ class VideoSource {
bool operator!=(const VideoSource& other) const { return !(*this == other); }
/// Get the type of the source.
Type GetType() const;
/// Get the name of the source. The name is an arbitrary identifier
/// provided when the source is created, and should be unique.
std::string GetName() const;
@@ -158,6 +168,10 @@ class VideoSource {
CS_Status GetLastStatus() const { return m_status; }
/// Enumerate all sinks connected to this source.
/// @return Vector of sinks.
std::vector<VideoSink> EnumerateSinks();
/// Enumerate all existing sources.
/// @return Vector of sources.
static std::vector<VideoSource> EnumerateSources();
@@ -260,8 +274,15 @@ class CvSource : public VideoSource {
/// A sink for video that accepts a sequence of frames.
class VideoSink {
friend class VideoEvent;
friend class VideoSource;
public:
enum Type {
kUnknown = CS_SINK_UNKNOWN,
kMJPEG = CS_SINK_MJPEG,
kCv = CS_SINK_CV
};
VideoSink() noexcept : m_handle(0) {}
VideoSink(const VideoSink& sink);
VideoSink(VideoSink&& sink) noexcept;
@@ -278,6 +299,9 @@ class VideoSink {
bool operator!=(const VideoSink& other) const { return !(*this == other); }
/// Get the type of the sink.
Type GetType() const;
/// Get the name of the sink. The name is an arbitrary identifier
/// provided when the sink is created, and should be unique.
std::string GetName() const;

View File

@@ -93,6 +93,11 @@ inline VideoSource::~VideoSource() {
if (m_handle != 0) ReleaseSource(m_handle, &m_status);
}
inline VideoSource::Type VideoSource::GetType() const {
m_status = 0;
return static_cast<VideoSource::Type>(GetSourceType(m_handle, &m_status));
}
inline std::string VideoSource::GetName() const {
m_status = 0;
return GetSourceName(m_handle, &m_status);
@@ -236,6 +241,11 @@ inline VideoSink::~VideoSink() {
if (m_handle != 0) ReleaseSink(m_handle, &m_status);
}
inline VideoSink::Type VideoSink::GetType() const {
m_status = 0;
return static_cast<VideoSink::Type>(GetSinkType(m_handle, &m_status));
}
inline std::string VideoSink::GetName() const {
m_status = 0;
return GetSinkName(m_handle, &m_status);

View File

@@ -398,6 +398,20 @@ JNIEXPORT jint JNICALL Java_edu_wpi_cscore_CameraServerJNI_createCvSource
return val;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getSourceType
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_edu_wpi_cscore_CameraServerJNI_getSourceType
(JNIEnv *env, jclass, jint source)
{
CS_Status status = 0;
auto val = cs::GetSourceType(source, &status);
CheckStatus(env, status);
return val;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getSourceName
@@ -582,6 +596,21 @@ JNIEXPORT jobjectArray JNICALL Java_edu_wpi_cscore_CameraServerJNI_enumerateSour
return jarr;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: enumerateSourceSinks
* Signature: (I)[I
*/
JNIEXPORT jintArray JNICALL Java_edu_wpi_cscore_CameraServerJNI_enumerateSourceSinks
(JNIEnv *env, jclass, jint source)
{
CS_Status status = 0;
llvm::SmallVector<CS_Sink, 16> buf;
auto arr = cs::EnumerateSourceSinks(source, buf, &status);
if (!CheckStatus(env, status)) return nullptr;
return MakeJIntArray(env, arr);
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: copySource
@@ -732,6 +761,20 @@ JNIEXPORT jint JNICALL Java_edu_wpi_cscore_CameraServerJNI_createCvSink
return val;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getSinkType
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_edu_wpi_cscore_CameraServerJNI_getSinkType
(JNIEnv *env, jclass, jint sink)
{
CS_Status status = 0;
auto val = cs::GetSinkType(sink, &status);
CheckStatus(env, status);
return val;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getSinkName

View File

@@ -110,6 +110,7 @@ public class CameraServerJNI {
//
// Source Functions
//
public static native int getSourceType(int source);
public static native String getSourceName(int source);
public static native String getSourceDescription(int source);
public static native long getSourceLastFrameTime(int source);
@@ -122,6 +123,7 @@ public class CameraServerJNI {
public static native boolean setSourceResolution(int source, int width, int height);
public static native boolean setSourceFPS(int source, int fps);
public static native VideoMode[] enumerateSourceVideoModes(int source);
public static native int[] enumerateSourceSinks(int source);
public static native int copySource(int source);
public static native void releaseSource(int source);
@@ -146,6 +148,7 @@ public class CameraServerJNI {
//
// Sink Functions
//
public static native int getSinkType(int sink);
public static native String getSinkName(int sink);
public static native String getSinkDescription(int sink);
public static native void setSinkSource(int sink, int source);

View File

@@ -11,6 +11,20 @@ package edu.wpi.cscore;
/// consist of multiple images (e.g. from a stereo or depth camera); these
/// are called channels.
public class VideoSink {
public enum Type {
kUnknown(0), kMJPEG(2), kCv(4);
private int value;
private Type(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
static final Type[] m_typeValues = Type.values();
protected VideoSink(int handle) {
m_handle = handle;
}
@@ -42,6 +56,11 @@ public class VideoSink {
return m_handle;
}
/// Get the type of the sink.
public Type getType() {
return m_typeValues[CameraServerJNI.getSinkType(m_handle)];
}
/// Get the name of the sink. The name is an arbitrary identifier
/// provided when the sink is created, and should be unique.
public String getName() {

View File

@@ -11,6 +11,20 @@ package edu.wpi.cscore;
/// consist of multiple images (e.g. from a stereo or depth camera); these
/// are called channels.
public class VideoSource {
public enum Type {
kUnknown(0), kUSB(1), kHTTP(2), kCv(4);
private int value;
private Type(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
static final Type[] m_typeValues = Type.values();
protected VideoSource(int handle) {
m_handle = handle;
}
@@ -42,6 +56,11 @@ public class VideoSource {
return m_handle;
}
/// Get the type of the source.
public Type getType() {
return m_typeValues[CameraServerJNI.getSourceType(m_handle)];
}
/// Get the name of the source. The name is an arbitrary identifier
/// provided when the source is created, and should be unique.
public String getName() {
@@ -129,6 +148,17 @@ public class VideoSource {
return CameraServerJNI.enumerateSourceVideoModes(m_handle);
}
/// Enumerate all sinks connected to this source.
/// @return Vector of sinks.
public VideoSink[] enumerateSinks() {
int[] handles = CameraServerJNI.enumerateSourceSinks(m_handle);
VideoSink[] rv = new VideoSink[handles.length];
for (int i=0; i<handles.length; i++) {
rv[i] = new VideoSink(handles[i]);
}
return rv;
}
/// Enumerate all existing sources.
/// @return Vector of sources.
public static VideoSource[] enumerateSources() {

View File

@@ -97,20 +97,20 @@ namespace cs {
CS_Sink CreateCvSink(llvm::StringRef name, CS_Status* status) {
auto sink = std::make_shared<CvSinkImpl>(name);
return Sinks::GetInstance().Allocate(SinkData::kCv, sink);
return Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
}
CS_Sink CreateCvSinkCallback(llvm::StringRef name,
std::function<void(uint64_t time)> processFrame,
CS_Status* status) {
auto sink = std::make_shared<CvSinkImpl>(name, processFrame);
return Sinks::GetInstance().Allocate(SinkData::kCv, sink);
return Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
}
void SetSinkDescription(CS_Sink sink, llvm::StringRef description,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->type != SinkData::kCv) {
if (!data || data->type != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return;
}
@@ -119,7 +119,7 @@ void SetSinkDescription(CS_Sink sink, llvm::StringRef description,
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->type != SinkData::kCv) {
if (!data || data->type != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return 0;
}
@@ -128,7 +128,7 @@ uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->type != SinkData::kCv) {
if (!data || data->type != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
@@ -138,7 +138,7 @@ std::string GetSinkError(CS_Sink sink, CS_Status* status) {
llvm::StringRef GetSinkError(CS_Sink sink, llvm::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->type != SinkData::kCv) {
if (!data || data->type != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return llvm::StringRef{};
}
@@ -147,7 +147,7 @@ llvm::StringRef GetSinkError(CS_Sink sink, llvm::SmallVectorImpl<char>& buf,
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->type != SinkData::kCv) {
if (!data || data->type != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return;
}

View File

@@ -157,12 +157,12 @@ namespace cs {
CS_Source CreateCvSource(llvm::StringRef name, const VideoMode& mode,
CS_Status* status) {
auto source = std::make_shared<CvSourceImpl>(name, mode);
return Sources::GetInstance().Allocate(SourceData::kCv, source);
return Sources::GetInstance().Allocate(CS_SOURCE_CV, source);
}
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->type != SourceData::kCv) {
if (!data || data->type != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
@@ -172,7 +172,7 @@ void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
void NotifySourceError(CS_Source source, llvm::StringRef msg,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->type != SourceData::kCv) {
if (!data || data->type != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
@@ -181,7 +181,7 @@ void NotifySourceError(CS_Source source, llvm::StringRef msg,
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->type != SourceData::kCv) {
if (!data || data->type != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
@@ -191,7 +191,7 @@ void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
void SetSourceDescription(CS_Source source, llvm::StringRef description,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->type != SourceData::kCv) {
if (!data || data->type != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
@@ -203,7 +203,7 @@ CS_Property CreateSourceProperty(CS_Source source, llvm::StringRef name,
int step, int defaultValue, int value,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->type != SourceData::kCv) {
if (!data || data->type != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return -1;
}
@@ -216,7 +216,7 @@ CS_Property CreateSourcePropertyCallback(
int maximum, int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->type != SourceData::kCv) {
if (!data || data->type != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return -1;
}
@@ -229,7 +229,7 @@ void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
llvm::ArrayRef<std::string> choices,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->type != SourceData::kCv) {
if (!data || data->type != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}

View File

@@ -71,16 +71,10 @@ class Handle {
};
struct SourceData {
enum Type {
kUnknown = 0,
kUSB,
kHTTP,
kCv
};
SourceData(Type type_, std::shared_ptr<SourceImpl> source_)
SourceData(CS_SourceType type_, std::shared_ptr<SourceImpl> source_)
: type{type_}, refCount{0}, source{source_} {}
Type type;
CS_SourceType type;
std::atomic_int refCount;
std::shared_ptr<SourceImpl> source;
};
@@ -89,15 +83,10 @@ typedef StaticUnlimitedHandleResource<Handle, SourceData, Handle::kSource>
Sources;
struct SinkData {
enum Type {
kUnknown = 0,
kHTTP,
kCv
};
explicit SinkData(Type type_, std::shared_ptr<SinkImpl> sink_)
explicit SinkData(CS_SinkType type_, std::shared_ptr<SinkImpl> sink_)
: type{type_}, refCount{0}, sourceHandle{0}, sink{sink_} {}
Type type;
CS_SinkType type;
std::atomic_int refCount;
std::atomic<CS_Source> sourceHandle;
std::shared_ptr<SinkImpl> sink;

View File

@@ -696,7 +696,7 @@ CS_Sink CreateMJPEGServer(llvm::StringRef name, llvm::StringRef listenAddress,
name, desc.str(),
std::unique_ptr<wpi::NetworkAcceptor>(
new wpi::TCPAcceptor(port, str.c_str(), Logger::GetInstance())));
return Sinks::GetInstance().Allocate(SinkData::kHTTP, sink);
return Sinks::GetInstance().Allocate(CS_SINK_MJPEG, sink);
}
} // namespace cs

View File

@@ -1360,7 +1360,7 @@ CS_Source CreateUSBCameraDev(llvm::StringRef name, int dev, CS_Status* status) {
CS_Source CreateUSBCameraPath(llvm::StringRef name, llvm::StringRef path,
CS_Status* status) {
auto source = std::make_shared<USBCameraImpl>(name, path);
return Sources::GetInstance().Allocate(SourceData::kUSB, source);
return Sources::GetInstance().Allocate(CS_SOURCE_USB, source);
}
std::vector<USBCameraInfo> EnumerateUSBCameras(CS_Status* status) {

View File

@@ -55,6 +55,10 @@ class UnlimitedHandleResource {
template <typename T>
llvm::ArrayRef<T> GetAll(llvm::SmallVectorImpl<T>& vec);
// @param func functor with (THandle, const TStruct&) parameters
template <typename F>
void ForEach(F func);
private:
THandle MakeHandle(size_t i) {
return THandle{static_cast<int>(i),
@@ -128,12 +132,19 @@ template <typename T>
llvm::ArrayRef<T>
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::GetAll(
llvm::SmallVectorImpl<T>& vec) {
ForEach([&](THandle handle, const TStruct& data) { vec.push_back(handle); });
return vec;
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
template <typename F>
void UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::ForEach(
F func) {
std::lock_guard<TMutex> sync(m_handleMutex);
size_t i;
for (i = 0; i < m_structures.size(); i++) {
if (m_structures[i] != nullptr) vec.push_back(MakeHandle(i));
if (m_structures[i] != nullptr) func(MakeHandle(i), *(m_structures[i]));
}
return vec;
}
template <typename THandle, typename TStruct, int typeValue,

View File

@@ -80,6 +80,10 @@ CS_Source CS_CreateHTTPCamera(const char* name, const char* url,
return cs::CreateHTTPCamera(name, url, status);
}
CS_SourceType CS_GetSourceType(CS_Source source, CS_Status* status) {
return cs::GetSourceType(source, status);
}
char* CS_GetSourceName(CS_Source source, CS_Status* status) {
llvm::SmallString<128> buf;
auto str = cs::GetSourceName(source, buf, status);
@@ -168,6 +172,17 @@ CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
return out;
}
CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count,
CS_Status* status) {
llvm::SmallVector<CS_Sink, 32> buf;
auto handles = cs::EnumerateSourceSinks(source, buf, status);
CS_Sink* sinks =
static_cast<CS_Sink*>(std::malloc(handles.size() * sizeof(CS_Sink)));
*count = handles.size();
std::copy(handles.begin(), handles.end(), sinks);
return sinks;
}
CS_Source CS_CopySource(CS_Source source, CS_Status* status) {
return cs::CopySource(source, status);
}
@@ -176,6 +191,10 @@ void CS_ReleaseSource(CS_Source source, CS_Status* status) {
return cs::ReleaseSource(source, status);
}
CS_SinkType CS_GetSinkType(CS_Sink sink, CS_Status* status) {
return cs::GetSinkType(sink, status);
}
char* CS_GetSinkName(CS_Sink sink, CS_Status* status) {
llvm::SmallString<128> buf;
auto str = cs::GetSinkName(sink, buf, status);

View File

@@ -152,6 +152,15 @@ CS_Source CreateHTTPCamera(llvm::StringRef name, llvm::StringRef url,
// Source Functions
//
CS_SourceType GetSourceType(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return CS_SOURCE_UNKNOWN;
}
return data->type;
}
std::string GetSourceName(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
@@ -299,6 +308,20 @@ std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
return data->source->EnumerateVideoModes(status);
}
llvm::ArrayRef<CS_Sink> EnumerateSourceSinks(
CS_Source source, llvm::SmallVectorImpl<CS_Sink>& vec, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return llvm::ArrayRef<CS_Sink>{};
}
vec.clear();
Sinks::GetInstance().ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
if (source == data.sourceHandle.load()) vec.push_back(sinkHandle);
});
return vec;
}
CS_Source CopySource(CS_Source source, CS_Status* status) {
if (source == 0) return 0;
auto data = Sources::GetInstance().Get(source);
@@ -325,6 +348,15 @@ void ReleaseSource(CS_Source source, CS_Status* status) {
// Sink Functions
//
CS_SinkType GetSinkType(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return CS_SINK_UNKNOWN;
}
return data->type;
}
std::string GetSinkName(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {

View File

@@ -21,6 +21,18 @@ std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
return properties;
}
std::vector<VideoSink> VideoSource::EnumerateSinks() {
llvm::SmallVector<CS_Sink, 16> handles_buf;
CS_Status status = 0;
auto handles = EnumerateSourceSinks(m_handle, handles_buf, &status);
std::vector<VideoSink> sinks;
sinks.reserve(handles.size());
for (int handle : handles)
sinks.emplace_back(VideoSink{handle});
return sinks;
}
std::vector<VideoSource> VideoSource::EnumerateSources() {
llvm::SmallVector<CS_Source, 16> handles_buf;
CS_Status status = 0;