Commit 109ce9e1 by 景炳强

remove backups

parent bd4904ad
#pragma once
#include <atomic>
#include <functional>
#include "DeckLinkAPI.h"
#include "Utils/CustomEvents.h"
#include "Utils/ComPtr.h"
class DeckLinkDeviceDiscovery : public IDeckLinkDeviceNotificationCallback
{
public:
DeckLinkDeviceDiscovery(QObject* owner);
virtual ~DeckLinkDeviceDiscovery();
// IUnknown interface
virtual HRESULT QueryInterface(REFIID riid, LPVOID* ppv) override;
virtual ULONG AddRef() override;
virtual ULONG Release() override;
// IDeckLinkDeviceArrivalNotificationCallback interface
virtual HRESULT DeckLinkDeviceArrived(IDeckLink* deckLinkDevice) override;
virtual HRESULT DeckLinkDeviceRemoved(IDeckLink* deckLinkDevice) override;
bool Enable();
void Disable();
private:
std::atomic<ULONG> RefCount;
QObject* Owner;
ComPtr<IDeckLinkDiscovery> DeckLinkDiscovery;
};
class DeckLinkDeviceDiscoveryEvent : public QEvent
{
private:
ComPtr<IDeckLink> DeckLink;
public:
DeckLinkDeviceDiscoveryEvent(QEvent::Type type, IDeckLink* deckLinkDevice) : QEvent(type), DeckLink(deckLinkDevice) {}
virtual ~DeckLinkDeviceDiscoveryEvent() {};
ComPtr<IDeckLink> Get() const { return DeckLink; }
};
\ No newline at end of file
#pragma once
#include <atomic>
#include <functional>
#include <QString>
#include <QObject>
#include "DeckLinkAPI.h"
#include "Utils/CustomEvents.h"
#include "Utils/ComPtr.h"
#include "Utils/Image.h"
class DeckLinkInputDevice : public QObject, public IDeckLinkInputCallback
{
Q_OBJECT
public:
using DeckLinkDisplayModeQueryFunc = std::function<void(IDeckLinkDisplayMode*)>;
DeckLinkInputDevice(QObject* parent, ComPtr<IDeckLink>& deckLink);
virtual ~DeckLinkInputDevice();
// IUnknown interface
virtual HRESULT QueryInterface(REFIID riid, LPVOID* ppv) override;
virtual ULONG AddRef() override;
virtual ULONG Release() override;
// IDeckLinkInputCallback interface
virtual HRESULT VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode* newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags) override;
virtual HRESULT VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioPacket) override;
// Other methods
bool Initialize(void);
HRESULT GetDeviceName(QString& deviceName);
bool IsCapturing(void) const { return bCurrentlyCapturing; }
bool SupportsFormatDetection(void) const { return bSupportsFormatDetection; }
BMDVideoConnection GetVideoConnections(void) const { return (BMDVideoConnection)SupportedInputConnections; }
bool IsActive(void);
bool StartCapture(BMDDisplayMode displayMode, IDeckLinkScreenPreviewCallback* screenPreviewCallback, bool applyDetectedInputMode);
void StopCapture(void);
void QuerySupportedVideoModes(DeckLinkDisplayModeQueryFunc func);
HRESULT SetInputVideoConnection(BMDVideoConnection connection);
ComPtr<IDeckLink> GetDeckLinkInstance(void) const { return DeckLink; }
ComPtr<IDeckLinkInput> GetDeckLinkInput(void) const { return DeckLinkInput; }
ComPtr<IDeckLinkConfiguration> GetDeckLinkConfiguration(void) const { return DeckLinkConfig; }
signals:
void ArrivedFrame(ComPtr<IDeckLinkVideoInputFrame> videoFrame);
private:
std::atomic<ULONG> RefCount;
QObject* Owner;
//
ComPtr<IDeckLink> DeckLink;
ComPtr<IDeckLinkInput> DeckLinkInput;
ComPtr<IDeckLinkConfiguration> DeckLinkConfig;
//
bool bSupportsFormatDetection;
bool bCurrentlyCapturing;
bool bApplyDetectedInputMode;
bool bLastValidFrameStatus;
int64_t SupportedInputConnections;
BMDVideoConnection SelectedInputConnection;
//
};
class DeckLinkInputFormatChangedEvent : public QEvent
{
public:
DeckLinkInputFormatChangedEvent(BMDDisplayMode displayMode) : QEvent(kVideoFormatChangedEvent), DisplayMode(displayMode){}
virtual ~DeckLinkInputFormatChangedEvent(){};
BMDDisplayMode GetDisplayMode() const { return DisplayMode; }
private:
BMDDisplayMode DisplayMode;
};
namespace
{
inline bool IsDeviceActive(ComPtr<IDeckLink>& deckLink)
{
ComPtr<IDeckLinkProfileAttributes> deckLinkAttributes(IID_IDeckLinkProfileAttributes, deckLink);
int64_t intAttribute;
if (!deckLinkAttributes)
return false;
if (deckLinkAttributes->GetInt(BMDDeckLinkDuplex, &intAttribute) != S_OK)
return false;
return ((BMDDuplexMode)intAttribute) != bmdDuplexInactive;
}
}
#pragma once
#include <QCheckBox>
#include <QComboBox>
#include <QFormLayout>
#include <QLabel>
#include <functional>
#include "DeckLinkInputDevice.h"
#include "DeckLinkOpenGLWidget.h"
#include "Utils/ComPtr.h"
#include "NDI/NDIOutputThread.h"
#include "Threads/CaptureThread.h"
class DeckLinkInputPage : public QWidget
{
Q_OBJECT
public:
DeckLinkInputPage();
virtual ~DeckLinkInputPage();
void SetPreviewSize(QSize previewSize);
void customEvent(QEvent* event) override;
void StartCapture(void);
void AddDevice(ComPtr<IDeckLink>& deckLink, bool deviceIsActive);
void RemoveDevice(ComPtr<IDeckLink>& deckLink);
void EnableDevice(ComPtr<IDeckLink>& deckLink, bool enable);
bool ReleaseDeviceIfSelected(ComPtr<IDeckLink>& deckLink);
DeckLinkOpenGLWidget* GetPreviewView(void) const { return PreviewView; }
ComPtr<DeckLinkInputDevice> GetSelectedDevice(void) const { return SelectedDevice; }
CaptureThread* GetCapture() { return Capture.get(); }
public slots:
void InputDeviceChanged(int selectedDeviceIndex);
void InputConnectionChanged(int selectedConnectionIndex);
void VideoFormatChanged(int selectedVideoFormatIndex);
void AutoDetectChanged(int autoDetectState);
void RequestedDeviceGranted(ComPtr<IDeckLink>& device);
signals:
void RequestDeckLink(ComPtr<IDeckLink>& device);
void RequestDeckLinkIfAvailable(ComPtr<IDeckLink>& device);
void RelinquishDeckLink(ComPtr<IDeckLink>& device);
private slots:
void ObjectNameChanged(const QString& newName);
private:
void RestartCapture(void);
void DetectedVideoFormatChanged(BMDDisplayMode displayMode);
void SelectedDeviceChanged(void);
void RefreshInputConnectionMenu(void);
void RefreshDisplayModeMenu(void);
ComPtr<DeckLinkInputDevice> SelectedDevice;
DeckLinkOpenGLWidget* PreviewView;
std::shared_ptr<CaptureThread> Capture;
std::unique_ptr<NDIOutputThread> NDIOutput;
QFormLayout* FormLayout;
QComboBox* DeviceListCombo;
QComboBox* InputConnectionCombo;
QComboBox* VideoFormatCombo;
QCheckBox* AutoDetectCheckBox;
QLabel* NDINameLabel;
};
\ No newline at end of file
#pragma once
#include <atomic>
#include <vector>
#include <Utils/ComPtr.h>
#include "DeckLinkAPI.h"
class DeckLinkInputVideoFrame : public IDeckLinkVideoFrame
{
public:
DeckLinkInputVideoFrame();
DeckLinkInputVideoFrame(int w, int h, BMDFrameFlags flags);
DeckLinkInputVideoFrame(ComPtr<IDeckLinkVideoInputFrame> frame);
DeckLinkInputVideoFrame(IDeckLinkVideoInputFrame* frame);
virtual ~DeckLinkInputVideoFrame();
// IDeckLinkVideoFrame interface
virtual long STDMETHODCALLTYPE GetWidth();
virtual long STDMETHODCALLTYPE GetHeight();
virtual long STDMETHODCALLTYPE GetRowBytes();
virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer);
virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags();
virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat();
// Dummy implementations of remaining methend in IDeckLinkVideoFrame
virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary) { return E_NOTIMPL; }
virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode) { return E_NOTIMPL; }
// IUnknown interface
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
private:
std::atomic<ULONG> RefCount;
//
int width;
int height;
int rowBytes;
BMDPixelFormat pixelFormat;
BMDFrameFlags frameFlags;
std::vector<uint8_t> buffer;
};
#pragma once
#include <mutex>
#include <QOpenGLWidget>
#include <QWidget>
#include <DeckLinkAPI.h>
#include "Utils/ComPtr.h"
#include "DeckLinkPreviewOverlay.h"
#include "ScreenPreviewCallback.h"
class DeckLinkOpenGLOverlayWidget;
class DeckLinkOpenGLWidget : public QOpenGLWidget
{
Q_OBJECT
public:
DeckLinkOpenGLWidget(QWidget* parent = nullptr);
virtual ~DeckLinkOpenGLWidget();
IDeckLinkScreenPreviewCallback* GetDelegate();
DeckLinkPreviewOverlay* GetOverlay();
void Clear();
protected:
// QOpenGLWidget
void initializeGL() override;
void paintGL() override;
void resizeGL(int w, int h) override;
private slots:
void SetFrame(ComPtr<IDeckLinkVideoFrame> frame);
private:
ComPtr<ScreenPreviewCallback> Delegate;
ComPtr<IDeckLinkGLScreenPreviewHelper> DeckLinkScreenPreviewHelper;
DeckLinkOpenGLOverlayWidget* OverlayWidget;
};
class DeckLinkOpenGLOverlayWidget : public QWidget
{
Q_OBJECT
public:
DeckLinkOpenGLOverlayWidget(QWidget* parent = nullptr);
DeckLinkPreviewOverlay* GetDelegate();
virtual void paintEvent(QPaintEvent* event) override;
private:
DeckLinkPreviewOverlay* Delegate;
};
\ No newline at end of file
#pragma once
#include <atomic>
#include <functional>
#include <condition_variable>
#include <list>
#include <memory>
#include <QMutex>
#include <QThread>
#include <QString>
#include <QObject>
#include "DeckLinkAPI.h"
#include "Utils/CustomEvents.h"
#include "Utils/Common.h"
#include "Utils/SampleQueue.h"
#include "Utils/ComPtr.h"
#include "Utils/Platform.h"
#include "DeckLinkOutputVideoFrame.h"
#include "Utils/Image.h"
class DeckLinkOutputDevice : public QObject, public IDeckLinkVideoOutputCallback
{
Q_OBJECT
enum class PlaybackState { Idle, Starting, Prerolling, Running, Stopping, Stopped };
using ScheduledFrameCompletedCallback = std::function<void(ComPtr<DeckLinkOutputVideoFrame>)>;
using ScheduledFramesList = std::list<ComPtr<DeckLinkOutputVideoFrame>>;
public:
DeckLinkOutputDevice(ComPtr<IDeckLink>& decklink, int videoPrerollSize);
virtual ~DeckLinkOutputDevice() = default;
// IUnknown interface
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv) override;
ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
// IDeckLinkVideoOutputCallback interface
HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result) override;
HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped() override;
// Other methods
bool StartPlayback(BMDDisplayMode displayMode, bool enable3D, BMDPixelFormat pixelFormat, bool requireReferenceLocked, IDeckLinkScreenPreviewCallback* screenPreviewCallback);
void StopPlayback(void);
void CancelWaitForReference();
BMDTimeScale getFrameTimescale(void) const { return frameTimescale; }
bool getReferenceSignalMode(BMDDisplayMode* mode);
bool isPlaybackActive(void);
void onScheduledFrameCompleted(const ScheduledFrameCompletedCallback& callback) { scheduledFrameCompletedCallback = callback; }
ComPtr<IDeckLink> GetDeckLinkInstance(void) const { return deckLink; }
ComPtr<IDeckLinkOutput> getDeckLinkOutput(void) const { return deckLinkOutput; }
public slots:
void AddFrame(std::shared_ptr<Image> image);
private:
std::atomic<ULONG> RefCount;
PlaybackState state;
//
ComPtr<IDeckLink> deckLink;
ComPtr<IDeckLinkOutput> deckLinkOutput;
BMDVideoConnection SelectedOutputConnection;
//
SampleQueue<std::shared_ptr<Image>> outputVideoFrameQueue;
//ScheduledFramesList scheduledFramesList;
//
uint32_t videoPrerollSize;
//
BMDTimeValue frameDuration;
BMDTimeScale frameTimescale;
//
bool seenFirstVideoFrame;
BMDTimeValue startPlaybackTime;
//
//std::mutex mutex;
//std::condition_variable playbackStoppedCondition;
//
std::thread scheduleVideoFramesThread;
//
ScheduledFrameCompletedCallback scheduledFrameCompletedCallback;
// Private methods
void scheduleVideoFramesFunc(void);
bool waitForReferenceSignalToLock(void);
void checkEndOfPreroll(void);
};
#pragma once
#include <QCheckBox>
#include <QComboBox>
#include <QFormLayout>
#include <QLabel>
#include <functional>
#include "DeckLinkOutputDevice.h"
#include "DeckLinkInputPage.h"
#include "DeckLinkOpenGLWidget.h"
#include "Utils/ComPtr.h"
#include "Threads/ProcessThread.h"
class DeckLinkOutputPage : public QWidget
{
Q_OBJECT
public:
DeckLinkOutputPage();
virtual ~DeckLinkOutputPage();
void SetPreviewSize(QSize previewSize);
void customEvent(QEvent* event) override;
void StartOutput(void);
void AddDevice(ComPtr<IDeckLink>& deckLink, bool deviceIsActive);
void RemoveDevice(ComPtr<IDeckLink>& deckLink);
void EnableDevice(ComPtr<IDeckLink>& deckLink, bool enable);
bool ReleaseDeviceIfSelected(ComPtr<IDeckLink>& deckLink);
DeckLinkOpenGLWidget* GetPreviewView(void) const { return PreviewView; }
ComPtr<DeckLinkOutputDevice> GetSelectedDevice(void) const { return SelectedDevice; }
void BindInputPage(DeckLinkInputPage* inputPage, std::shared_ptr<ProcessThread> process) {
BindingInputPage = inputPage;
Process = process;
}
public slots:
void OutputDeviceChanged(int selectedDeviceIndex);
void VideoFormatChanged(int selectedVideoFormatIndex);
void VideoPixelFormatChanged(int selectedVideoPixelFormatIndex);
void RequestedDeviceGranted(ComPtr<IDeckLink>& device);
void ObjectNameChanged(const QString& newName);
signals:
void RequestDeckLink(ComPtr<IDeckLink>& device);
void RequestDeckLinkIfAvailable(ComPtr<IDeckLink>& device);
void RelinquishDeckLink(ComPtr<IDeckLink>& device);
private:
void RestartOutput(void);
void SelectedDeviceChanged(void);
void RefreshDisplayModeMenu(void);
DeckLinkInputPage* BindingInputPage;
std::shared_ptr<ProcessThread> Process;
ComPtr<DeckLinkOutputDevice> SelectedDevice;
DeckLinkOpenGLWidget* PreviewView;
QFormLayout* FormLayout;
QComboBox* DeviceListCombo;
QComboBox* VideoFormatCombo;
QComboBox* VideoPixelFormatCombo;
QLineEdit* PortNumLineEdit;
};
\ No newline at end of file
#pragma once
#include <atomic>
#include <vector>
#include "Utils/ComPtr.h"
#include "DeckLinkAPI.h"
#include "Utils/Image.h"
class DeckLinkOutputVideoFrame : public IDeckLinkVideoFrame
{
public:
DeckLinkOutputVideoFrame();
DeckLinkOutputVideoFrame(int w, int h, BMDFrameFlags flags, BMDPixelFormat pixelFormat);
DeckLinkOutputVideoFrame(Image& image);
DeckLinkOutputVideoFrame(std::shared_ptr<Image> image);
DeckLinkOutputVideoFrame(Image&& image);
virtual ~DeckLinkOutputVideoFrame();
// IDeckLinkVideoFrame interface
virtual long STDMETHODCALLTYPE GetWidth();
virtual long STDMETHODCALLTYPE GetHeight();
virtual long STDMETHODCALLTYPE GetRowBytes();
virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buf);
virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags();
virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat();
// Dummy implementations of remaining method in IDeckLinkVideoFrame
virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary) { return E_NOTIMPL; }
virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode) { return E_NOTIMPL; }
// IUnknown interface
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
void setVideoStreamTime(const BMDTimeValue time) { videoStreamTime = time; }
void setVideoFrameDuration(const BMDTimeValue time) { videoFrameDuration = time; }
void setInputFrameStartReferenceTime(const BMDTimeValue time) { inputFrameStartReferenceTime = time; }
void setInputFrameArrivedReferenceTime(const BMDTimeValue time) { inputFrameArrivedReferenceTime = time; }
void setOutputFrameScheduledReferenceTime(const BMDTimeValue time) { outputFrameScheduledReferenceTime = time; }
void setOutputFrameCompletedReferenceTime(const BMDTimeValue time) { outputFrameCompletedReferenceTime = time; }
void setOutputCompletionResult(const BMDOutputFrameCompletionResult result) { outputFrameCompletionResult = result; }
BMDTimeValue getVideoStreamTime() { return videoStreamTime; }
BMDTimeValue getVideoFrameDuration() { return videoFrameDuration; }
BMDTimeValue getInputFrameStartReferenceTime() { return inputFrameStartReferenceTime; }
BMDTimeValue getInputFrameArrivedReferenceTime() { return inputFrameArrivedReferenceTime; }
BMDTimeValue getOutputFrameScheduledReferenceTime() { return outputFrameScheduledReferenceTime; }
BMDTimeValue getOutputFrameCompletedReferenceTime() { return outputFrameCompletedReferenceTime; }
BMDOutputFrameCompletionResult getOutputCompletionResult() { return outputFrameCompletionResult; }
private:
std::atomic<ULONG> RefCount;
//
int width;
int height;
int rowBytes;
BMDPixelFormat pixelFormat;
BMDFrameFlags frameFlags;
std::vector<uint8_t> buffer;
// Timecode
BMDTimeValue videoStreamTime;
BMDTimeValue videoFrameDuration;
BMDTimeValue inputFrameStartReferenceTime;
BMDTimeValue inputFrameArrivedReferenceTime;
BMDTimeValue outputFrameScheduledReferenceTime;
BMDTimeValue outputFrameCompletedReferenceTime;
BMDOutputFrameCompletionResult outputFrameCompletionResult;
};
#pragma once
#include <QObject>
#include <QPaintDevice>
#include <mutex>
#include "BlackMagicDesign/DeckLinkPreviewVideoFrame.h"
#include "Utils/ComPtr.h"
#include "DeckLinkAPI.h"
class DeckLinkPreviewOverlay : public QObject
{
Q_OBJECT
public:
explicit DeckLinkPreviewOverlay(QObject* parent = nullptr);
void SetFrame(ComPtr<IDeckLinkVideoFrame> frame);
void Clear();
void SetDeviceLabel(const QString& label);
void EnableTimeCode(bool enable);
void EnableDeviceLabel(bool enable);
bool IsSignalValid(void);
void Paint(QPaintDevice* paintDevice);
signals:
void UpdatePreview();
private:
std::mutex Mutex;
QString TimeCode;
QString DeviceLabel;
bool bSignalValid;
bool bEnableTimeCode;
bool bEnableDeviceLabel;
};
\ No newline at end of file
#pragma once
#include <atomic>
#include <vector>
#include <Utils/ComPtr.h>
#include "DeckLinkAPI.h"
class DeckLinkPreviewVideoFrame : public IDeckLinkVideoFrame
{
public:
DeckLinkPreviewVideoFrame();
DeckLinkPreviewVideoFrame(IDeckLinkVideoFrame* videoFrame);
virtual ~DeckLinkPreviewVideoFrame();
// IDeckLinkVideoFrame interface
virtual long STDMETHODCALLTYPE GetWidth();
virtual long STDMETHODCALLTYPE GetHeight();
virtual long STDMETHODCALLTYPE GetRowBytes();
virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer);
virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags();
virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat();
// Dummy implementations of remaining methend in IDeckLinkVideoFrame
virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary) { return E_NOTIMPL; }
virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode) { return E_NOTIMPL; }
// IUnknown interface
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
private:
std::atomic<ULONG> RefCount;
//
int width;
int height;
int rowBytes;
BMDPixelFormat pixelFormat;
BMDFrameFlags frameFlags;
std::vector<uint8_t> buffer;
};
#pragma once
#include <atomic>
#include <functional>
#include <DeckLinkAPI.h>
#include "Utils/Platform.h"
#include "Utils/CustomEvents.h"
#include "Utils/ComPtr.h"
class ProfileCallback : public IDeckLinkProfileCallback
{
using ProfileChangingCallback = std::function<void(ComPtr<IDeckLinkProfile>)>;
public:
ProfileCallback(QObject* owner);
virtual ~ProfileCallback(){};
// IDeckLinkProfileCallback interface
virtual HRESULT ProfileChanging(IDeckLinkProfile* profileToBeActivated, dlbool_t streamsWillBeForcedToStop) override;
virtual HRESULT ProfileActivated(IDeckLinkProfile* activatedProfile) override;
// IUnknown interface
virtual HRESULT QueryInterface(REFIID riid, LPVOID* ppv) override;
virtual ULONG AddRef() override;
virtual ULONG Release() override;
void OnProfileChanging(const ProfileChangingCallback& callback) { mProfileChangingCallback = callback; }
private:
std::atomic<ULONG> RefCount;
QObject* Owner;
ProfileChangingCallback mProfileChangingCallback;
};
class ProfileActivatedEvent : public QEvent
{
private:
ComPtr<IDeckLinkProfile> DeckLinkProfile;
public:
ProfileActivatedEvent(IDeckLinkProfile* deckLinkProfile) : QEvent(kProfileActivatedEvent), DeckLinkProfile(deckLinkProfile) {}
virtual ~ProfileActivatedEvent(){};
ComPtr<IDeckLinkProfile> GetDeckLinkProfile() const { return DeckLinkProfile; }
};
\ No newline at end of file
#pragma once
#include <chrono>
#include "DeckLinkAPI.h"
namespace ReferenceTime
{
using ReferenceDuration = std::chrono::microseconds; // Capture reference times in microseconds
constexpr BMDTimeScale kTimescale = (BMDTimeScale)ReferenceDuration(std::chrono::seconds(1)).count();
constexpr int kTicksPerMilliSec = ReferenceDuration(std::chrono::milliseconds(1)).count();
static inline BMDTimeValue getSteadyClockUptimeCOunt(void)
{
auto now = std::chrono::steady_clock::now();
auto referenceTime = std::chrono::time_point_cast<ReferenceDuration>(now);
return referenceTime.time_since_epoch().count();
}
}
#pragma once
#include <atomic>
#include <QObject>
#include "Utils/ComPtr.h"
#include "BlackMagicDesign/DeckLinkPreviewVideoFrame.h"
#include "DeckLinkAPI.h"
class ScreenPreviewCallback : public QObject, public IDeckLinkScreenPreviewCallback
{
Q_OBJECT
public:
ScreenPreviewCallback();
// IUnknown
virtual HRESULT QueryInterface(REFIID riid, LPVOID* ppv) override;
virtual ULONG AddRef() override;
virtual ULONG Release() override;
// IDeckLinkScreenPreviewCallback
virtual HRESULT DrawFrame(IDeckLinkVideoFrame* theFrame) override;
signals:
void FrameArrived(ComPtr<IDeckLinkVideoFrame> frame);
private:
std::atomic<ULONG> RefCount;
};
\ No newline at end of file
#pragma once
#include <QtWidgets/QMainWindow>
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QGridLayout>
#include <array>
#include <map>
#include <memory>
#include "BlackMagicDesign/DeckLinkDeviceDiscovery.h"
#include "BlackMagicDesign/DeckLinkInputPage.h"
#include "BlackMagicDesign/ProfileCallback.h"
#include "DeckLinkAPI.h"
#include "ui_MomentaMedia.h"
static const int kPreviewDevicesCount = 4;
class MomentaMedia : public QMainWindow
{
enum class DeviceState {Inactive, Available, Selected};
Q_OBJECT
public:
MomentaMedia(QWidget *parent = Q_NULLPTR);
~MomentaMedia();
void customEvent(QEvent* event) override;
void closeEvent(QCloseEvent* event) override;
void Setup();
void StartCapture(int deviceIndex);
void RefreshDisplayModeMenu(int deviceIndex);
void RefreshInputConnectionMenu(int deviceIndex);
void AddDevice(ComPtr<IDeckLink>& deckLink);
void RemoveDevice(ComPtr<IDeckLink>& deckLink);
void HaltStream(ComPtr<IDeckLinkProfile> profile);
void UpdateProfile(ComPtr<IDeckLinkProfile>& newProfile);
bool IsDeviceAvailable(ComPtr<IDeckLink>& device);
public slots:
void RequestInputDevice(DeckLinkInputPage* page, ComPtr<IDeckLink>& deckLink);
void RequestInputDeviceIfAvailable(DeckLinkInputPage* page, ComPtr<IDeckLink>& deckLink);
void RelinquishInputDevice(ComPtr<IDeckLink>& device);
void RequestOutputDevice(DeckLinkOutputPage* page, ComPtr<IDeckLink>& deckLink);
void RequestOutputDeviceIfAvailable(DeckLinkOutputPage* page, ComPtr<IDeckLink>& deckLink);
void RelinquishOutputDevice(ComPtr<IDeckLink>& device);
private slots:
void DeviceLabelEnableChanged(bool enabled);
void TimecodeEnableChanged(bool enabled);
private:
Ui::MomentaMediaClass ui;
QGridLayout* PreviewLayout;
ComPtr<DeckLinkDeviceDiscovery> DeckLinkDiscovery;
ProfileCallback* pProfileCallback;
std::array<DeckLinkInputPage*, kPreviewDevicesCount> InputDevicePages;
std::array<DeckLinkOutputPage*, kPreviewDevicesCount> OutputDevicePages;
std::array<std::shared_ptr<ProcessThread>, kPreviewDevicesCount> ProcessThreads;
std::map<ComPtr<IDeckLink>, DeviceState> InputDevices;
std::map<ComPtr<IDeckLink>, DeviceState> OutputDevices;
};
#pragma once
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <queue>
#include "Processing.NDI.Advanced.h"
#include "Utils/Image.h"
#include "Utils/SampleQueue.h"
class NDIOutputThread : public QThread
{
Q_OBJECT
public:
NDIOutputThread(const QString& Name, int w, int h);
~NDIOutputThread();
void SetNDISenderName(const QString& Name);
QString GetNDISenderName() const;
void SetNDIImageSize(int w, int h);
int GetWidth() { return width; }
int GetHeight() { return height; }
bool IsSending() const { return isSending; }
bool CheckValid() const { return width > 0 && height > 0 && !NDISenderName.isEmpty(); }
void Clear();
public slots:
void AddFrame(std::shared_ptr<Image> frame);
private:
NDIOutputThread() : NDISenderName(""), width(-1), height(-1), Instance(nullptr), isSending(false) {}
bool Init();
virtual void run() override;
QString NDISenderName;
int width;
int height;
bool isSending;
SampleQueue<std::shared_ptr<Image>> taskQueue;
NDIlib_send_instance_t Instance;
NDIlib_video_frame_v2_t Frame;
};
\ No newline at end of file
//----------------------------------------
// Orbit Profiler
// Copyright Pierric Gimmig 2013-2017
//----------------------------------------
#pragma once
//-----------------------------------------------------------------------------
// Orbit API
//
// Simply include this header in your project, there is no lib and no cpp file.
//
// MANUAL INSTRUMENTATION:
// The main feature of Orbit is its ability to dynamically instrument functions
// without having to recompile or even relaunch your application. However,
// if you still want to manually instrument your code, you can.
//
// Use ORBIT_SCOPE or ORBIT_START/ORBIT_STOP *macros* only.
// DO NOT use OrbitScope(...)/OrbitStart(...)/OrbitStop() directly.
// NOTE: You need to use string literals. Dynamic strings are not supported yet.
//
// DLL LOADING:
// If you want to control the loading of Orbit64.dll, i.e. you don't want to have
// to manually inject it into your application, use the OrbitAPI class. Calling
// OrbitAPI::Connect(...) will load the dll and try to connect to the Orbit instance
// specified in parameters. Note that Orbit64.dll needs to be next to your
// exectutable. You can also change the code below (OrbitAPI::Init()).
//-----------------------------------------------------------------------------
#define ORBIT_SCOPE( name ) OrbitScope ORBIT_UNIQUE( ORB )( ORBIT_LITERAL( name ) )
#define ORBIT_START( name ) OrbitStart( ORBIT_LITERAL( name ) )
#define ORBIT_STOP OrbitStop()
#define ORBIT_SEND( ptr, num ) OrbitSendData( ptr, num )
//-----------------------------------------------------------------------------
struct OrbitAPI
{
static inline bool Connect( const char* a_Host, int a_Port = 1789 );
static inline bool IsConnected();
private:
static inline void Init();
typedef void( *InitRemote )( char* );
typedef bool( *GetBool )();
static inline HINSTANCE & GetHandle();
static inline InitRemote & GetInitRemote();
static inline GetBool & GetIsConnected();
};
//-----------------------------------------------------------------------------
#define ORBIT_NOOP static volatile int x; x;
#define ORBIT_LITERAL(x) ("" x)
//-----------------------------------------------------------------------------
#pragma optimize ("", off) // On purpose to prevent compiler from stripping functions
// NOTE: Do not use these directly, use above macros instead
__declspec( noinline ) inline void OrbitStart( const char* ) { ORBIT_NOOP; }
__declspec( noinline ) inline void OrbitStop() { ORBIT_NOOP; }
__declspec( noinline ) inline void OrbitLog( const char* ) { ORBIT_NOOP; }
__declspec( noinline ) inline void OrbitSendData( void*, int ) { ORBIT_NOOP; }
#pragma optimize ("", on)
//-----------------------------------------------------------------------------
struct OrbitScope
{
OrbitScope( const char * a_Name ) { OrbitStart( a_Name ); }
~OrbitScope() { OrbitStop(); }
};
//-----------------------------------------------------------------------------
#define ORBIT_CONCAT_IND(x, y) (x##y)
#define ORBIT_CONCAT(x, y) ORBIT_CONCAT_IND(x, y)
#define ORBIT_UNIQUE(x) ORBIT_CONCAT(x, __COUNTER__)
//-----------------------------------------------------------------------------
void OrbitAPI::Init()
{
HINSTANCE & dllHandle = GetHandle();
if( !dllHandle )
{
const TCHAR * dllName = sizeof(size_t) == 4 ? TEXT("Orbit32.dll") : TEXT("Orbit64.dll");
if( dllHandle = LoadLibrary( dllName ) )
{
GetInitRemote() = (InitRemote)GetProcAddress( dllHandle, "OrbitInitRemote" );
}
}
}
//-----------------------------------------------------------------------------
bool OrbitAPI::Connect( const char* a_Host, int a_Port )
{
Init();
if( GetHandle() && GetInitRemote() )
{
char host[256] = { 0 };
sprintf_s( host, sizeof( host ), "%s:%i", a_Host, a_Port );
GetInitRemote()( host );
return IsConnected();
}
return false;
}
//-----------------------------------------------------------------------------
bool OrbitAPI::IsConnected(){ return GetIsConnected() ? GetIsConnected()() : false; }
//-----------------------------------------------------------------------------
HINSTANCE & OrbitAPI::GetHandle(){ static HINSTANCE s_DllHandle; return s_DllHandle; }
OrbitAPI::InitRemote & OrbitAPI::GetInitRemote(){ static InitRemote s_InitRemote; return s_InitRemote; }
OrbitAPI::GetBool & OrbitAPI::GetIsConnected(){ static GetBool s_IsConnected; return s_IsConnected; }
#pragma once
#include <QThread>
#include <QMutex>
#include "Utils/Image.h"
#include "Utils/SampleQueue.h"
class CaptureThread : public QThread
{
Q_OBJECT
public:
CaptureThread();
~CaptureThread();
public slots:
void AddFrame(ComPtr<IDeckLinkVideoInputFrame> videoFrame);
signals:
void PushFrame(std::shared_ptr<Image> image);
private:
SampleQueue<ComPtr<IDeckLinkVideoInputFrame>> taskQueue;
void run() override;
};
\ No newline at end of file
#pragma once
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QUdpSocket>
#include <QJsonDocument>
#include "Utils/SampleQueue.h"
#include "Utils/Image.h"
const QString MODE_CLEAR = "no_mode";
const QString MODE_CROP = "crop_roi";
const QString MODE_STOP = "stop";
const QString MODE_ACK = "checked_ok";
class RoiMessage
{
public:
RoiMessage() : w(800), h(1080)
{
x = 1920 / 2 - w / 2;
y = 1080 / 2 - h / 2;
}
RoiMessage(QByteArray& data)
{
QJsonDocument document = QJsonDocument::fromJson(data);
QJsonObject object;
if(document.isObject())
{
object = document.object();
mode = object.value("signal").toString();
QJsonArray roi = object.value("roi").toArray();
int minx = roi[0].toInt();
int miny = roi[1].toInt();
int maxx = roi[2].toInt();
int maxy = roi[3].toInt();
id = object.value("id").toInt();
width = object.value("width").toInt();
height = object.value("height").toInt();
w = maxx - minx;
h = maxy - miny;
x = minx;
y = miny;
}
}
RoiMessage(QByteArray&& data)
{
QJsonDocument document = QJsonDocument::fromJson(data);
QJsonObject object;
if (document.isObject())
{
object = document.object();
mode = object.value("signal").toString();
QJsonArray roi = object.value("roi").toArray();
int minx = roi[0].toInt();
int miny = roi[1].toInt();
int maxx = roi[2].toInt();
int maxy = roi[3].toInt();
id = object.value("id").toInt();
width = object.value("width").toInt();
height = object.value("height").toInt();
w = maxx - minx;
h = maxy - miny;
x = minx;
y = miny;
}
}
RoiMessage(const RoiMessage& other) : x(other.x), y(other.y), w(other.w), h(other.h)
{
}
RoiMessage(RoiMessage&& other)
{
x = other.x;
y = other.y;
w = other.w;
h = other.h;
}
RoiMessage operator=(const RoiMessage& other)
{
x = other.x;
y = other.y;
w = other.w;
h = other.h;
return *this;
}
RoiMessage operator=(RoiMessage&& other)
{
x = other.x;
y = other.y;
w = other.w;
h = other.h;
return *this;
}
bool IsValid()
{
return x > 0 && y > 0 && w > 0 && h > 0;
}
int X() { return x; }
int Y() { return y; }
int Width() { return w; }
int Height() { return h; }
private:
int x;
int y;
int w;
int h;
QString mode;
int id = 0;
int width;
int height;
};
class ProcessThread : public QThread
{
Q_OBJECT
public:
ProcessThread();
~ProcessThread();
void SetUpUDP(const QString hostAddr, const QString hostPort);
public slots:
void AddFrame(std::shared_ptr<Image> image);
void ReadDatagrams();
signals:
void PushFrame(std::shared_ptr<Image> image);
protected:
void run() override;
private:
//SampleQueue<Image> taskPrerollQueue;
SampleQueue<std::shared_ptr<Image>> taskImageQueue;
SampleQueue<RoiMessage> taskROIQueue;
QUdpSocket* udpSocket;
//
uint32_t videoPrerollSize;
RoiMessage lastReceiveMessage;
void Clear();
};
\ No newline at end of file
#pragma once
#include <cstddef>
#include <guiddef.h>
#include <winerror.h>
#include <QDebug>
#define START_TIME_COUNTER_BASE(Name) auto startTime##Name = std::chrono::high_resolution_clock::now();
#define END_TIME_COUNTER_BASE(Name) auto endTime##Name = std::chrono::high_resolution_clock::now(); \
float totalTime##Name = std::chrono::duration<float, std::milli>(endTime##Name - startTime##Name).count(); \
qDebug() << __FUNCTION__ << " \t " << #Name << " time: \t" << totalTime##Name << "ms";
#if 0
#define START_GL_TIME_COUNTER START_TIME_COUNTER_BASE(OpenGL)
#define END_GL_TIME_COUNTER END_TIME_COUNTER_BASE(OpenGL)
#else
#define START_GL_TIME_COUNTER
#define END_GL_TIME_COUNTER
#endif
#if 0
#define START_WAIT_TIME_COUNTER auto startWaitTime = std::chrono::high_resolution_clock::now();
#define END_WAIT_TIME_COUNTER auto endWaitTime = std::chrono::high_resolution_clock::now(); \
float totalWaitTime = std::chrono::duration<float, std::milli>(endWaitTime - startWaitTime).count(); \
qDebug() << __FUNCTION__ << " \t WaitFor:\t" << totalWaitTime << "ms";
#define START_TIME_COUNTER auto startTime = std::chrono::high_resolution_clock::now();
#define END_TIME_COUNTER auto endTime = std::chrono::high_resolution_clock::now(); \
float totalTime = std::chrono::duration<float, std::milli>(endTime - startTime).count(); \
qDebug() << __FUNCTION__ << " \t totalTime:\t" << totalTime << "ms";
#else
#define START_WAIT_TIME_COUNTER
#define END_WAIT_TIME_COUNTER
#define START_TIME_COUNTER
#define END_TIME_COUNTER
#endif
#if 0
#define START_SLOT_TIME_COUNTER auto startSlotTime = std::chrono::high_resolution_clock::now();
#define END_SLOT_TIME_COUNTER auto endSlotTime = std::chrono::high_resolution_clock::now(); \
float totalSlotTime = std::chrono::duration<float, std::milli>(endSlotTime - startSlotTime).count(); \
qDebug() << __FUNCTION__ << " \t Slot time: \t" << totalSlotTime << "ms";
#else
#define START_SLOT_TIME_COUNTER
#define END_SLOT_TIME_COUNTER
#endif
//#define DEBUG_FUNCTION(LOG, Param, ...) qDebug() << __FUNCTION__ << ##LOG << ##Param;
#define DEBUG_FUNCTION(LOG, Param, ...)
template <typename T>
class ComPtr
{
template<typename U>
friend class ComPtr;
public:
constexpr ComPtr();
constexpr ComPtr(std::nullptr_t);
explicit ComPtr(T* Ptr);
ComPtr(const ComPtr<T>& Other);
ComPtr(ComPtr<T>&& Other);
template<typename U>
ComPtr(REFIID IId, ComPtr<U> Other);
~ComPtr();
ComPtr<T>& operator=(std::nullptr_t);
ComPtr<T>& operator=(T* Ptr);
ComPtr<T>& operator=(const ComPtr<T>& Other);
ComPtr<T>& operator=(ComPtr<T>&& Other);
T* Get() const;
T** GetAddressOf();
T** ReleaseAndGetAddressOf();
const T* operator->() const;
T* operator->();
const T& operator*() const;
T& operator*();
explicit operator bool() const;
bool operator==(const ComPtr<T>& Other) const;
bool operator<(const ComPtr<T>& Other) const;
void Release();
private:
T* Ptr;
};
template <typename T>
constexpr ComPtr<T>::ComPtr() : Ptr(nullptr)
{
}
template <typename T>
constexpr ComPtr<T>::ComPtr(std::nullptr_t) : Ptr(nullptr)
{
}
template <typename T>
ComPtr<T>::ComPtr(T* ptr) : Ptr(ptr)
{
if (Ptr)
Ptr->AddRef();
}
template <typename T>
ComPtr<T>::ComPtr(const ComPtr<T>& Other) : Ptr(Other.Ptr)
{
if (Ptr)
Ptr->AddRef();
}
template <typename T>
ComPtr<T>::ComPtr(ComPtr<T>&& Other) : Ptr(Other.Ptr)
{
Other.Ptr = nullptr;
}
template <typename T>
template <typename U>
ComPtr<T>::ComPtr(const IID& IId, ComPtr<U> Other)
{
if(Other.Ptr)
{
if (Other.Ptr->QueryInterface(IId, (void**)&Ptr) != S_OK)
Ptr = nullptr;
}
}
template <typename T>
ComPtr<T>::~ComPtr()
{
Release();
}
template <typename T>
ComPtr<T>& ComPtr<T>::operator=(std::nullptr_t)
{
Release();
Ptr = nullptr;
return *this;
}
template <typename T>
ComPtr<T>& ComPtr<T>::operator=(T* ptr)
{
if (ptr)
ptr->AddRef();
Release();
Ptr = ptr;
return *this;
}
template <typename T>
ComPtr<T>& ComPtr<T>::operator=(const ComPtr<T>& Other)
{
return (*this = Other.Ptr);
}
template <typename T>
ComPtr<T>& ComPtr<T>::operator=(ComPtr<T>&& Other)
{
Release();
Ptr = Other.Ptr;
Other.Ptr = nullptr;
return *this;
}
template <typename T>
T* ComPtr<T>::Get() const
{
return Ptr;
}
template <typename T>
T** ComPtr<T>::GetAddressOf()
{
return &Ptr;
}
template <typename T>
T** ComPtr<T>::ReleaseAndGetAddressOf()
{
Release();
return &Ptr;
}
template <typename T>
const T* ComPtr<T>::operator->() const
{
return Ptr;
}
template <typename T>
T* ComPtr<T>::operator->()
{
return Ptr;
}
template <typename T>
T& ComPtr<T>::operator*()
{
return *Ptr;
}
template <typename T>
const T& ComPtr<T>::operator*() const
{
return *Ptr;
}
template <typename T>
ComPtr<T>::operator bool() const
{
return Ptr != nullptr;
}
template <typename T>
void ComPtr<T>::Release()
{
if (Ptr)
Ptr->Release();
}
template <typename T>
bool ComPtr<T>::operator==(const ComPtr<T>& Other) const
{
return Ptr == Other.Ptr;
}
template <typename T>
bool ComPtr<T>::operator<(const ComPtr<T>& Other) const
{
return Ptr < Other.Ptr;
}
template<class T, class... Args>
ComPtr<T> MakeComPtr(Args&&... args)
{
ComPtr<T> Temp(new T(args...));
// ComPtr takes ownership of reference count, so release reference count added by raw pointer constructor.
Temp.Release();
return std::move(Temp);
}
\ No newline at end of file
#pragma once
#include "DeckLinkAPI.h"
long GetRowBytesFromPixelFormat(long width, BMDPixelFormat pixelFormat);
\ No newline at end of file
#pragma once
#include <QEvent>
// Define custom event type
static const QEvent::Type kAddDeviceEvent = static_cast<QEvent::Type>(QEvent::User + 1);
static const QEvent::Type kRemoveDeviceEvent = static_cast<QEvent::Type>(QEvent::User + 2);
static const QEvent::Type kVideoFormatChangedEvent = static_cast<QEvent::Type>(QEvent::User + 3);
static const QEvent::Type kProfileActivatedEvent = static_cast<QEvent::Type>(QEvent::User + 4);
static const QEvent::Type kErrorRestartingCaptureEvent = static_cast<QEvent::Type>(QEvent::User + 5);
\ No newline at end of file
#pragma once
#include <condition_variable>
#include <functional>
#include <mutex>
#include <queue>
#include <vector>
class DispatchQueue
{
using DispatchFunction = std::function<void(void)>;
public:
DispatchQueue(size_t numThreads);
virtual ~DispatchQueue();
template<class F, class... Args>
void dispatch(F&& fn, Args&&... args);
private:
std::vector<std::thread> workerThreads;
std::queue<DispatchFunction> functionQueue;
std::condition_variable condition;
std::mutex mutex;
bool cancelWorkers;
void workerThread(void);
};
DispatchQueue::DispatchQueue(size_t numThreads) : cancelWorkers(false)
{
for(size_t i = 0; i < numThreads; i++)
{
workerThreads.emplace_back(&DispatchQueue::workerThread, this);
}
}
DispatchQueue::~DispatchQueue()
{
// Stop all threads once they have completed current job
{
std::lock_guard<std::mutex> locker(mutex);
cancelWorkers = true;
}
condition.notify_all();
for(auto& worker : workerThreads)
{
worker.join();
}
}
template <class F, class ... Args>
void DispatchQueue::dispatch(F&& fn, Args&&... args)
{
using DispathFunctionBinding = decltype(std::bind(std::declval<F>(), std::declval<Args>()...));
{
std::lock_guard<std::mutex> locker(mutex);
functionQueue.push(DispathFunctionBinding(std::forward<F>(fn), std::forward<Args>(args)...));
}
condition.notify_one();
}
void DispatchQueue::workerThread()
{
while (true)
{
{
std::unique_lock<std::mutex> locker(mutex);
condition.wait(locker, [&] {return !functionQueue.empty() || cancelWorkers; });
}
while (true)
{
DispatchFunction func;
{
std::lock_guard<std::mutex> locker(mutex);
if (functionQueue.empty())
break;
func = std::move(functionQueue.front());
functionQueue.pop();
}
func();
}
if (cancelWorkers)
// Exit thread
break;
}
}
#pragma once
#include <QtCore/QtCore>
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "DeckLinkAPI.h"
#include "BlackMagicDesign/DeckLinkInputVideoFrame.h"
class Image : public QObject
{
Q_OBJECT
public:
Image();
Image(IDeckLinkVideoInputFrame* videoFrame);
Image(ComPtr<DeckLinkInputVideoFrame> videoFrame);
Image(ComPtr<IDeckLinkVideoInputFrame> videoFrame);
Image(const Image& other);
Image(Image&& other);
~Image();
Image& operator=(const Image& other);
Image& operator=(Image&& other);
cv::Mat GetMat();
void SetMat(cv::Mat& inMat);
uint8_t* GetBytes() const;
int GetSize() const { return mat.rows * mat.cols * 4; }
bool IsValid() const;
int GetWidth() { return mat.cols; }
int GetHegiht() { return mat.rows; }
BMDPixelFormat GetPixelFormat() { return (BMDPixelFormat)bmdFormat8BitBGRA; }
BMDFrameFlags GetFlags() { return bmdFrameFlagDefault; }
void setVideoStreamTime(const BMDTimeValue time) { videoStreamTime = time; }
void setVideoFrameDuration(const BMDTimeValue time) { videoFrameDuration = time; }
void setInputFrameStartReferenceTime(const BMDTimeValue time) { inputFrameStartReferenceTime = time; }
void setInputFrameArrivedReferenceTime(const BMDTimeValue time) { inputFrameArrivedReferenceTime = time; }
void setOutputFrameScheduledReferenceTime(const BMDTimeValue time) { outputFrameScheduledReferenceTime = time; }
void setOutputFrameCompletedReferenceTime(const BMDTimeValue time) { outputFrameCompletedReferenceTime = time; }
void setOutputCompletionResult(const BMDOutputFrameCompletionResult result) { outputFrameCompletionResult = result; }
BMDTimeValue getVideoStreamTime() { return videoStreamTime; }
BMDTimeValue getVideoFrameDuration() { return videoFrameDuration; }
BMDTimeValue getInputFrameStartReferenceTime() { return inputFrameStartReferenceTime; }
BMDTimeValue getInputFrameArrivedReferenceTime() { return inputFrameArrivedReferenceTime; }
BMDTimeValue getOutputFrameScheduledReferenceTime() { return outputFrameScheduledReferenceTime; }
BMDTimeValue getOutputFrameCompletedReferenceTime() { return outputFrameCompletedReferenceTime; }
BMDOutputFrameCompletionResult getOutputCompletionResult() { return outputFrameCompletionResult; }
void GetMatByRoi(cv::Rect roi, cv::Mat& mat);
void Fill(void* dst, int dstSize)
{
void* buf;
if(inVideoFrame->GetBytes(&buf) == S_OK)
{
memcpy_s(dst, dstSize, buf, inVideoFrame->GetRowBytes() * inVideoFrame->GetHeight());
}
}
private:
cv::Mat mat;
ComPtr<DeckLinkInputVideoFrame> inVideoFrame;
// Timecode
BMDTimeValue videoStreamTime;
BMDTimeValue videoFrameDuration;
BMDTimeValue inputFrameStartReferenceTime;
BMDTimeValue inputFrameArrivedReferenceTime;
BMDTimeValue outputFrameScheduledReferenceTime;
BMDTimeValue outputFrameCompletedReferenceTime;
BMDOutputFrameCompletionResult outputFrameCompletionResult;
};
HRESULT ConvertDeckLinkVideoFrame2Mat(ComPtr<DeckLinkInputVideoFrame> videoFrame, cv::Mat& imageFrame);
\ No newline at end of file
#pragma once
#include <deque>
#include <mutex>
#include "DeckLinkAPI.h"
class LatencyStatistics
{
public:
LatencyStatistics(int maxRollingSamples);
virtual ~LatencyStatistics() {}
void Reset(void);
void AddSample(const BMDTimeValue latency);
BMDTimeValue getMinimun(void);
BMDTimeValue getMaximum(void);
std::pair<BMDTimeValue, BMDTimeValue> getMeanAndStdDev(void);
BMDTimeValue getRollingAverage(void);
private:
std::mutex mutex;
// For calculating rolling average
int maxRollingSamples;
std::deque<BMDTimeValue> rollingSamples;
BMDTimeValue rollingAggregateLatency;
// Minimum/Maximum latency
BMDTimeValue maxLatency;
BMDTimeValue minLatency;
// Welford mean/variance calculations
BMDTimeValue mean;
BMDTimeValue m2;
int sampleCount;
};
\ No newline at end of file
#pragma once
#include <comdef.h>
#include <comutil.h>
#include <string>
#include <QString>
#include <functional>
#include <stdint.h>
#include <DeckLinkAPI.h>
#include "ComPtr.h"
HRESULT GetDeckLinkDiscoveryInstance(ComPtr<IDeckLinkDiscovery>& deckLinkDiscovery);
HRESULT GetDeckLinkOpenGLScreenPreviewHelper(ComPtr<IDeckLinkGLScreenPreviewHelper>& screenPreviewHelper);
#define dlbool_t BOOL
#define dlstring_t BSTR
// DeckLink String conversion functions
const auto DeleteString = ::SysFreeString;
const auto DlToStdString = [](dlstring_t dl_str) -> std::string
{
int wlen = static_cast<int>(::SysStringLen(dl_str));
int mblen = ::WideCharToMultiByte(CP_ACP, 0, static_cast<wchar_t*>(dl_str), wlen, nullptr, 0, nullptr, nullptr);
std::string ret_str(static_cast<size_t>(mblen), '\0');
mblen = ::WideCharToMultiByte(CP_ACP, 0, static_cast<wchar_t*>(dl_str), wlen, &ret_str[0], mblen, nullptr, nullptr);
return ret_str;
};
const auto StdToDlString = [](std::string std_str) -> dlstring_t
{
int wlen = ::MultiByteToWideChar(CP_ACP, 0, std_str.data(), static_cast<int>(std_str.length()), nullptr, 0);
dlstring_t ret_str = ::SysAllocStringLen(nullptr, static_cast<UINT>(wlen));
::MultiByteToWideChar(CP_ACP, 0, std_str.data(), static_cast<int>(std_str.length()), ret_str, wlen);
return ret_str;
};
const auto DlToQString = [](dlstring_t dl_str) -> QString {return QString::fromUtf16(reinterpret_cast<ushort*>(dl_str)); };
\ No newline at end of file
#pragma once
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <QDebug>
#include <chrono>
template<typename T>
class SampleQueue
{
public:
SampleQueue();
virtual ~SampleQueue();
void Push(const T& sample);
void Push(T&& sample);
bool Pop(T& sample);
bool WaitFor(T& sample);
bool WaitUntil(T& sample, int timeout);
void CancelWaiters(void);
void Reset(void);
long Size();
private:
std::queue<T> queue;
std::condition_variable queueCondition;
std::mutex mutex;
bool waitCancelled;
};
template <typename T>
SampleQueue<T>::SampleQueue() : waitCancelled(false)
{
}
template <typename T>
SampleQueue<T>::~SampleQueue()
{
CancelWaiters();
}
template <typename T>
void SampleQueue<T>::Push(T&& sample)
{
{
std::lock_guard<std::mutex> locker(mutex);
queue.push(sample);
}
queueCondition.notify_one();
}
template <typename T>
void SampleQueue<T>::Push(const T& sample)
{
{
std::lock_guard<std::mutex> locker(mutex);
queue.push(std::move(sample));
}
queueCondition.notify_one();
}
template <typename T>
bool SampleQueue<T>::Pop(T& sample)
{
// Non-blocking queue pop
std::lock_guard<std::mutex> locker(mutex);
if (queue.empty())
return false;
sample = std::move(queue.front());
queue.pop();
return true;
}
template <typename T>
bool SampleQueue<T>::WaitFor(T& sample)
{
// Blocking wait for sample
std::unique_lock<std::mutex> locker(mutex);
queueCondition.wait(locker, [&] {return !queue.empty() || waitCancelled; });
if (waitCancelled)
return false;
else if(!queue.empty())
{
sample = std::move(queue.front());
queue.pop();
}
return true;
}
template <typename T>
bool SampleQueue<T>::WaitUntil(T& sample, int timeout)
{
// Blocking wait for sample
std::unique_lock<std::mutex> locker(mutex);
auto delay = std::chrono::system_clock::now() + std::chrono::milliseconds(timeout);
queueCondition.wait_until(locker, delay, [&] {return !queue.empty() || waitCancelled; });
if (waitCancelled || queue.empty())
return false;
else if(!queue.empty())
{
sample = std::move(queue.front());
queue.pop();
}
return true;
}
template <typename T>
void SampleQueue<T>::CancelWaiters()
{
{
// signal cancel flag to terminate wait condition
std::lock_guard<std::mutex> locker(mutex);
waitCancelled = true;
}
queueCondition.notify_all();
}
template <typename T>
void SampleQueue<T>::Reset()
{
std::lock_guard<std::mutex> locker(mutex);
while (!queue.empty())
queue.pop();
waitCancelled = false;
}
template <typename T>
long SampleQueue<T>::Size()
{
std::lock_guard<std::mutex> locker(mutex);
return queue.size();
}
#include <QCoreApplication>
#include "Utils/Platform.h"
#include "BlackMagicDesign/DeckLinkDeviceDiscovery.h"
DeckLinkDeviceDiscovery::DeckLinkDeviceDiscovery(QObject* owner) : RefCount(1), Owner(owner)
{
GetDeckLinkDiscoveryInstance(DeckLinkDiscovery);
}
DeckLinkDeviceDiscovery::~DeckLinkDeviceDiscovery()
{
if(DeckLinkDiscovery)
{
// Uninstall device arrival notifications and release discovery object
DeckLinkDiscovery->UninstallDeviceNotifications();
}
}
// IUnknown methods
HRESULT DeckLinkDeviceDiscovery::QueryInterface(REFIID riid, LPVOID* ppv)
{
HRESULT result = S_OK;
if(ppv == nullptr)
{
return E_INVALIDARG;
}
// Obtain the IUnknown interface and compare it the provided REFIID
if(riid == IID_IUnknown)
{
*ppv = this;
AddRef();
}
else if(riid == IID_IDeckLinkDeviceNotificationCallback)
{
*ppv = static_cast<IDeckLinkDeviceNotificationCallback*>(this);
AddRef();
}
else
{
*ppv = nullptr;
result = E_NOINTERFACE;
}
return result;
}
ULONG DeckLinkDeviceDiscovery::AddRef()
{
return ++RefCount;
}
ULONG DeckLinkDeviceDiscovery::Release()
{
ULONG newRefValue = --RefCount;
if (newRefValue == 0)
delete this;
return newRefValue;
}
// IDeckLinkDeviceArrivalNotificationCallback methods
HRESULT DeckLinkDeviceDiscovery::DeckLinkDeviceArrived(IDeckLink* deckLinkDevice)
{
// Notify owner that new device has been added.
QCoreApplication::postEvent(Owner, new DeckLinkDeviceDiscoveryEvent(kAddDeviceEvent, deckLinkDevice));
return S_OK;
}
HRESULT DeckLinkDeviceDiscovery::DeckLinkDeviceRemoved(IDeckLink* deckLinkDevice)
{
// Notify owner that device has been removed.
QCoreApplication::postEvent(Owner, new DeckLinkDeviceDiscoveryEvent(kRemoveDeviceEvent, deckLinkDevice));
return S_OK;
}
// Other methods
bool DeckLinkDeviceDiscovery::Enable()
{
HRESULT result = E_FAIL;
// Install device arrival notifications
if (DeckLinkDiscovery)
result = DeckLinkDiscovery->InstallDeviceNotifications(this);
return result == S_OK;
}
void DeckLinkDeviceDiscovery::Disable()
{
// Uninstall device arrival notifications
if (DeckLinkDiscovery)
DeckLinkDiscovery->UninstallDeviceNotifications();
}
#include <QCoreApplication>
#include <QMessageBox>
#include <QTextStream>
#include "Utils/Platform.h"
#include "BlackMagicDesign/DeckLinkInputDevice.h"
DeckLinkInputDevice::DeckLinkInputDevice(QObject* parent, ComPtr<IDeckLink>& device)
: RefCount(1),
Owner(parent),
DeckLink(device),
DeckLinkInput(IID_IDeckLinkInput, device),
DeckLinkConfig(IID_IDeckLinkConfiguration, device),
bSupportsFormatDetection(false),
bCurrentlyCapturing(false),
bApplyDetectedInputMode(false),
bLastValidFrameStatus(false),
SupportedInputConnections(bmdVideoConnectionUnspecified),
SelectedInputConnection(bmdVideoConnectionUnspecified)
{
}
DeckLinkInputDevice::~DeckLinkInputDevice()
{
if (bCurrentlyCapturing)
StopCapture();
}
// IUnknown methods
HRESULT DeckLinkInputDevice::QueryInterface(REFIID riid, LPVOID* ppv)
{
HRESULT result = S_OK;
if (ppv == nullptr)
return E_INVALIDARG;
// Obtain the IUnknown interface and compare it the provided REFIID
if(riid == IID_IUnknown)
{
*ppv = this;
AddRef();
}
else if(riid == IID_IDeckLinkInputCallback)
{
*ppv = (IDeckLinkInputCallback*)this;
AddRef();
}
else
{
*ppv = nullptr;
result = E_NOINTERFACE;
}
return result;
}
ULONG DeckLinkInputDevice::AddRef()
{
return ++RefCount;
}
ULONG DeckLinkInputDevice::Release()
{
ULONG newRefValue = --RefCount;
if (newRefValue == 0)
delete this;
return newRefValue;
}
// IDeckLinkInputCallback methods
HRESULT DeckLinkInputDevice::VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioPacket)
{
// Since this application only previews, everything is driven from IDeckLinkScreenPreviewCallback::DrawFrame
ComPtr<IDeckLinkVideoInputFrame> frame = ComPtr<IDeckLinkVideoInputFrame>(videoFrame);
emit ArrivedFrame(frame);
return S_OK;
}
HRESULT DeckLinkInputDevice::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode* newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags)
{
HRESULT result;
BMDPixelFormat pixelFormat;
BMDDisplayMode displayMode = newDisplayMode->GetDisplayMode();
// Unexpected callback when auto-detect mode not enabled
if (!bApplyDetectedInputMode)
return E_FAIL;
if (detectedSignalFlags & bmdDetectedVideoInputRGB444)
{
if (detectedSignalFlags & bmdDetectedVideoInput8BitDepth)
pixelFormat = bmdFormat8BitARGB;
else if (detectedSignalFlags & bmdDetectedVideoInput10BitDepth)
pixelFormat = bmdFormat10BitRGB;
else if (detectedSignalFlags & bmdDetectedVideoInput12BitDepth)
pixelFormat = bmdFormat12BitRGB;
else
// Invalid color depth for RGB
return E_INVALIDARG;
}
else if (detectedSignalFlags & bmdDetectedVideoInputYCbCr422)
{
if (detectedSignalFlags & bmdDetectedVideoInput8BitDepth)
pixelFormat = bmdFormat8BitYUV;
else if (detectedSignalFlags & bmdDetectedVideoInput10BitDepth)
pixelFormat = bmdFormat10BitYUV;
else
// Invalid color depth for YUV
return E_INVALIDARG;
}
else
// Unexpected detected video input format flags
return E_INVALIDARG;
// Restart streams if either display mode or colorspace has changed
if (notificationEvents & (bmdVideoInputDisplayModeChanged | bmdVideoInputColorspaceChanged))
{
// Stop the capture
DeckLinkInput->StopStreams();
// Set the video input mode
result = DeckLinkInput->EnableVideoInput(displayMode, pixelFormat, bmdVideoInputEnableFormatDetection);
if (result == S_OK)
// Start the capture
result = DeckLinkInput->StartStreams();
if (result != S_OK)
// Let owner know we couldn`t restart capture with detected input video mode
QCoreApplication::postEvent(Owner, new QEvent(kErrorRestartingCaptureEvent));
else
QCoreApplication::postEvent(Owner, new DeckLinkInputFormatChangedEvent(displayMode));
}
return S_OK;
}
// Other methods
bool DeckLinkInputDevice::Initialize()
{
ComPtr<IDeckLinkProfileAttributes> deckLinkAttributes(IID_IDeckLinkProfileAttributes, DeckLink);
dlbool_t attributeFlag;
// Get input interface
if (!DeckLinkInput)
// This may occur if device does not have input interface, for instance DeckLink Mini Monitor.
return false;
// Get Configuration interface so we can change input connector
// We hold onto IDeckLinkConfiguration until destructor to retain input connector setting
if (!DeckLinkConfig)
return false;
// Get attributes interface
if (!deckLinkAttributes)
return false;
// Check if input mode detection is supported.
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInputFormatDetection, &attributeFlag) == S_OK)
bSupportsFormatDetection = attributeFlag;
else
bSupportsFormatDetection = false;
// Get the supported input connections for the device
if (deckLinkAttributes->GetInt(BMDDeckLinkVideoInputConnections, &SupportedInputConnections) != S_OK)
SupportedInputConnections = 0;
return true;
}
bool DeckLinkInputDevice::StartCapture(BMDDisplayMode displayMode, IDeckLinkScreenPreviewCallback* screenPreviewCallback, bool applyDetectedInputMode)
{
BMDVideoInputFlags videoInputFlags = bmdVideoInputFlagDefault;
bApplyDetectedInputMode = applyDetectedInputMode;
// Enable input video mode detection if the device supports it
if (bSupportsFormatDetection && bApplyDetectedInputMode)
videoInputFlags |= bmdVideoInputEnableFormatDetection;
// Set the screen preview
DeckLinkInput->SetScreenPreviewCallback(screenPreviewCallback);
// Set capture callback
DeckLinkInput->SetCallback(this);
// Set the video input mode
if (DeckLinkInput->EnableVideoInput(displayMode, bmdFormat10BitYUV, videoInputFlags) != S_OK)
return false;
// Set the capture
if (DeckLinkInput->StartStreams() != S_OK)
return false;
bCurrentlyCapturing = true;
return true;
}
void DeckLinkInputDevice::StopCapture()
{
if (DeckLinkInput)
{
// Stop the capture
DeckLinkInput->StopStreams();
DeckLinkInput->DisableVideoInput();
// Delete the callbacks
DeckLinkInput->SetScreenPreviewCallback(nullptr);
DeckLinkInput->SetCallback(nullptr);
}
bCurrentlyCapturing = false;
}
void DeckLinkInputDevice::QuerySupportedVideoModes(DeckLinkDisplayModeQueryFunc func)
{
ComPtr<IDeckLinkDisplayModeIterator> displayModeIterator;
ComPtr<IDeckLinkDisplayMode> displayMode;
if (DeckLinkInput->GetDisplayModeIterator(displayModeIterator.ReleaseAndGetAddressOf()) != S_OK)
return;
// Iterate through each supported display mode for the input connection
while(displayModeIterator->Next(displayMode.ReleaseAndGetAddressOf()) == S_OK)
{
dlbool_t supported = false;
BMDDisplayMode mode = displayMode->GetDisplayMode();
if ((DeckLinkInput->DoesSupportVideoMode(SelectedInputConnection, mode, bmdFormatUnspecified, bmdNoVideoInputConversion, bmdSupportedVideoModeDefault, NULL, &supported) == S_OK) && supported)
{
func(displayMode.Get());
}
}
}
HRESULT DeckLinkInputDevice::SetInputVideoConnection(BMDVideoConnection connection)
{
HRESULT result = DeckLinkConfig->SetInt(bmdDeckLinkConfigVideoInputConnection, (int64_t)connection);
if (result != S_OK)
return result;
SelectedInputConnection = connection;
return S_OK;
}
bool DeckLinkInputDevice::IsActive()
{
return IsDeviceActive(DeckLink);
}
#include "BlackMagicDesign/DeckLinkInputVideoFrame.h"
#include "Utils/Common.h"
#include <chrono>
#include <QDebug>
DeckLinkInputVideoFrame::DeckLinkInputVideoFrame() : RefCount(1), width(-1), height(-1), rowBytes(-1), frameFlags(bmdFrameFlagDefault), pixelFormat(bmdFormat8BitBGRA)
{
}
DeckLinkInputVideoFrame::DeckLinkInputVideoFrame(int w, int h, BMDFrameFlags flags) : RefCount(1), width(w), height(h), frameFlags(flags), pixelFormat(bmdFormat8BitBGRA)
{
rowBytes = GetRowBytesFromPixelFormat(width, pixelFormat);
buffer.resize(height * rowBytes);
}
DeckLinkInputVideoFrame::DeckLinkInputVideoFrame(ComPtr<IDeckLinkVideoInputFrame> frame) : RefCount(1), width(frame->GetWidth()), height(frame->GetHeight()), rowBytes(frame->GetRowBytes()), frameFlags(frame->GetFlags()), pixelFormat(bmdFormat8BitBGRA)
{
rowBytes = GetRowBytesFromPixelFormat(width, pixelFormat);
buffer.resize(height * rowBytes);
ComPtr<IDeckLinkVideoConversion> deckLinkVideoConversion = nullptr;
HRESULT result = CoCreateInstance(CLSID_CDeckLinkVideoConversion, nullptr, CLSCTX_ALL, IID_IDeckLinkVideoConversion, (void**)deckLinkVideoConversion.GetAddressOf());
if (!deckLinkVideoConversion)
return;
deckLinkVideoConversion->ConvertFrame(frame.Get(), this);
}
DeckLinkInputVideoFrame::DeckLinkInputVideoFrame(IDeckLinkVideoInputFrame* frame) : RefCount(1), width(frame->GetWidth()), height(frame->GetHeight()), frameFlags(frame->GetFlags()), pixelFormat(bmdFormat8BitBGRA)
{
rowBytes = GetRowBytesFromPixelFormat(width, pixelFormat);
buffer.resize(height * rowBytes);
ComPtr<IDeckLinkVideoConversion> deckLinkVideoConversion = nullptr;
HRESULT result = CoCreateInstance(CLSID_CDeckLinkVideoConversion, nullptr, CLSCTX_ALL, IID_IDeckLinkVideoConversion, (void**)deckLinkVideoConversion.GetAddressOf());
if (!deckLinkVideoConversion)
return;
deckLinkVideoConversion->ConvertFrame(frame, this);
}
DeckLinkInputVideoFrame::~DeckLinkInputVideoFrame()
{
buffer.clear();
}
ULONG DeckLinkInputVideoFrame::AddRef()
{
return ++RefCount;
}
ULONG DeckLinkInputVideoFrame::Release()
{
ULONG newRefValue = --RefCount;
if (newRefValue == 0)
delete this;
return newRefValue;
}
HRESULT DeckLinkInputVideoFrame::QueryInterface(REFIID riid, LPVOID* ppv)
{
HRESULT result = E_NOINTERFACE;
if (ppv == nullptr)
return E_INVALIDARG;
// Initialize the return result
*ppv = nullptr;
// Obtain the IUnknown interface and compare it the provided REFIID
if (riid == IID_IUnknown)
{
*ppv = this;
AddRef();
result = S_OK;
}
else if (riid == IID_IDeckLinkVideoFrame)
{
*ppv = (IDeckLinkVideoFrame*)this;
AddRef();
result = S_OK;
}
return result;
}
long DeckLinkInputVideoFrame::GetWidth()
{
return (long)width;
}
long DeckLinkInputVideoFrame::GetHeight()
{
return (long)height;
}
BMDFrameFlags DeckLinkInputVideoFrame::GetFlags()
{
return frameFlags;
}
long DeckLinkInputVideoFrame::GetRowBytes()
{
return rowBytes;
}
BMDPixelFormat DeckLinkInputVideoFrame::GetPixelFormat()
{
return pixelFormat;
}
HRESULT DeckLinkInputVideoFrame::GetBytes(void** buf)
{
if(buffer.empty())
{
*buf = nullptr;
return E_FAIL;
}
else
{
*buf = buffer.data();
return S_OK;
}
}
\ No newline at end of file
#include "Utils/Platform.h"
#include "BlackMagicDesign/DeckLinkOpenGLWidget.h"
#include <QPainter>
#include <QFontMetrics>
#include <QFontDatabase>
#include <QDebug>
#include <QOpenGLFunctions>
///
/// DeckLinkOpenGLOverlay
///
DeckLinkOpenGLOverlayWidget::DeckLinkOpenGLOverlayWidget(QWidget* parent) : QWidget(parent)
{
Delegate = new DeckLinkPreviewOverlay(this);
}
DeckLinkPreviewOverlay* DeckLinkOpenGLOverlayWidget::GetDelegate()
{
return Delegate;
}
void DeckLinkOpenGLOverlayWidget::paintEvent(QPaintEvent* event)
{
Delegate->Paint(this);
}
///
/// DeckLinkOpenGLWidget
///
DeckLinkOpenGLWidget::DeckLinkOpenGLWidget(QWidget* parent) : QOpenGLWidget(parent)
{
GetDeckLinkOpenGLScreenPreviewHelper(DeckLinkScreenPreviewHelper);
Delegate = MakeComPtr<ScreenPreviewCallback>();
connect(Delegate.Get(), &ScreenPreviewCallback::FrameArrived, this, &DeckLinkOpenGLWidget::SetFrame, Qt::QueuedConnection);
OverlayWidget = new DeckLinkOpenGLOverlayWidget(this);
}
DeckLinkOpenGLWidget::~DeckLinkOpenGLWidget()
{
}
/// QOpenGLWidget methods
void DeckLinkOpenGLWidget::initializeGL()
{
if(DeckLinkScreenPreviewHelper)
{
DeckLinkScreenPreviewHelper->InitializeGL();
}
}
void DeckLinkOpenGLWidget::paintGL()
{
START_GL_TIME_COUNTER
if(DeckLinkScreenPreviewHelper)
{
DeckLinkScreenPreviewHelper->PaintGL();
}
END_GL_TIME_COUNTER
}
void DeckLinkOpenGLWidget::resizeGL(int w, int h)
{
OverlayWidget->resize(w, h);
QOpenGLFunctions* f = context()->functions();
f->glViewport(0, 0, w, h);
}
/// Other methods
IDeckLinkScreenPreviewCallback* DeckLinkOpenGLWidget::GetDelegate()
{
return Delegate.Get();
}
DeckLinkPreviewOverlay* DeckLinkOpenGLWidget::GetOverlay()
{
return OverlayWidget->GetDelegate();
}
void DeckLinkOpenGLWidget::Clear()
{
GetOverlay()->Clear();
if (Delegate)
Delegate->DrawFrame(nullptr);
}
void DeckLinkOpenGLWidget::SetFrame(ComPtr<IDeckLinkVideoFrame> frame)
{
START_SLOT_TIME_COUNTER
if(DeckLinkScreenPreviewHelper)
{
DeckLinkScreenPreviewHelper->SetFrame(frame.Get());
GetOverlay()->SetFrame(frame);
update();
OverlayWidget->update();
}
END_SLOT_TIME_COUNTER
}
#include <QStandardItemModel>
#include <QStandardItem>
#include <QToolBox>
#include <QMessageBox>
#include <QLineEdit>
#include "MomentaMedia.h"
#include "BlackMagicDesign/DeckLinkOutputPage.h"
#include "Utils/Platform.h"
namespace
{
const int kComboMinimumWidth = 185;
const std::vector<std::pair<BMDVideoConnection, QString>> kVideoOutputConnections = {
std::make_pair(bmdVideoConnectionSDI, QString("SDI")),
std::make_pair(bmdVideoConnectionHDMI, QString("HDMI")),
std::make_pair(bmdVideoConnectionOpticalSDI, QString("Optical SDI")),
std::make_pair(bmdVideoConnectionComponent, QString("Component")),
std::make_pair(bmdVideoConnectionComposite, QString("Composite")),
std::make_pair(bmdVideoConnectionSVideo, QString("S-Video")),
};
const std::vector<std::pair<BMDPixelFormat, QString>> kVideoOutputPixelFormat = {
std::make_pair(bmdFormat8BitYUV, QString("8BitYUV")),
std::make_pair(bmdFormat8BitARGB, QString("8BitARGB")),
std::make_pair(bmdFormat8BitBGRA, QString("8BitBGRA")),
std::make_pair(bmdFormat10BitYUV, QString("10BitYUV")),
std::make_pair(bmdFormat10BitRGB, QString("10BitRGB")),
std::make_pair(bmdFormat10BitRGBX, QString("10BitRGBX")),
std::make_pair(bmdFormat10BitRGBXLE, QString("10BitRGBXLE")),
std::make_pair(bmdFormat12BitRGB, QString("12BitRGB")),
std::make_pair(bmdFormat12BitRGBLE, QString("12BitRGBLE")),
};
template<class T>
T* findParent(QWidget* widget)
{
T* result = nullptr;
do
{
widget = widget->parentWidget();
result = qobject_cast<T*>(widget);
} while (widget && !result);
return result;
}
}
DeckLinkOutputPage::DeckLinkOutputPage() : SelectedDevice(nullptr), Process(nullptr)
{
FormLayout = new QFormLayout(this);
DeviceListCombo = new QComboBox();
DeviceListCombo->setMinimumWidth(kComboMinimumWidth);
FormLayout->addRow("Output Device:", DeviceListCombo);
DeviceListCombo->addItem("None");
VideoFormatCombo = new QComboBox();
VideoFormatCombo->setMinimumWidth(kComboMinimumWidth);
VideoFormatCombo->setEnabled(false);
FormLayout->addRow("Video Format", VideoFormatCombo);
VideoPixelFormatCombo = new QComboBox();
VideoPixelFormatCombo->setMinimumWidth(kComboMinimumWidth);
VideoPixelFormatCombo->setEnabled(false);
FormLayout->addRow("Video Pixel Format:", VideoPixelFormatCombo);
PreviewView = new DeckLinkOpenGLWidget(dynamic_cast<QWidget*>(this));
PreviewView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
PortNumLineEdit = new QLineEdit();
PortNumLineEdit->setMinimumWidth(kComboMinimumWidth);
FormLayout->addRow("PortNum", PortNumLineEdit);
connect(DeviceListCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DeckLinkOutputPage::OutputDeviceChanged);
connect(VideoFormatCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DeckLinkOutputPage::VideoFormatChanged);
connect(VideoPixelFormatCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DeckLinkOutputPage::VideoPixelFormatChanged);
connect(this, &QObject::objectNameChanged, this, &DeckLinkOutputPage::ObjectNameChanged);
}
DeckLinkOutputPage::~DeckLinkOutputPage()
{
delete FormLayout;
SelectedDevice->StopPlayback();
Process->exit();
}
void DeckLinkOutputPage::SetPreviewSize(QSize previewSize)
{
PreviewView->resize(previewSize);
PreviewView->Clear();
}
void DeckLinkOutputPage::customEvent(QEvent* event)
{
switch (event->type())
{
case kVideoFormatChangedEvent:
{
event->ignore();
}
case kErrorRestartingCaptureEvent:
{
event->ignore();
}
break;
default:
break;
}
}
void DeckLinkOutputPage::StartOutput()
{
if (!SelectedDevice)
return;
BMDDisplayMode displayMode = bmdModeHD1080p50;
//displayMode = (BMDDisplayMode)VideoFormatCombo->currentData().value<unsigned int>();
BMDPixelFormat pixelFormat = bmdFormat10BitYUV;
//pixelFormat = (BMDPixelFormat)VideoPixelFormatCombo->currentData().value<unsigned int>();
SelectedDevice->StartPlayback(displayMode, false, pixelFormat, false, PreviewView->GetDelegate());
}
void DeckLinkOutputPage::AddDevice(ComPtr<IDeckLink>& deckLink, bool deviceIsActive)
{
dlstring_t deviceNameStr;
QString deviceName;
if (deckLink->GetDisplayName(&deviceNameStr) == S_OK)
{
deviceName = DlToQString(deviceNameStr);
DeleteString(deviceNameStr);
}
else
return;
int index = DeviceListCombo->findData(QVariant::fromValue((void*)deckLink.Get()));
if(index == -1)
{
DeviceListCombo->addItem(deviceName, QVariant::fromValue((void*)deckLink.Get()));
EnableDevice(deckLink, deviceIsActive);
}
if(!SelectedDevice)
{
// Request deckLink object from parent widget if it`s not already in use by anther page
emit RequestDeckLinkIfAvailable(deckLink);
}
}
void DeckLinkOutputPage::RemoveDevice(ComPtr<IDeckLink>& deckLink)
{
// Find the combo box entry and remove entry
int indexToRemove = DeviceListCombo->findData(QVariant::fromValue((void*)deckLink.Get()));
bool removingCurrentDevice = false;
if(indexToRemove > 0)
{
removingCurrentDevice = (indexToRemove == DeviceListCombo->currentIndex());
// Prevent signal on removeItem, so that we don`t refresh connector/video mode for removed device
bool blocked = DeviceListCombo->blockSignals(true);
DeviceListCombo->removeItem(indexToRemove);
DeviceListCombo->blockSignals(blocked);
}
if (removingCurrentDevice)
DeviceListCombo->setCurrentIndex(0);
}
void DeckLinkOutputPage::EnableDevice(ComPtr<IDeckLink>& deckLink, bool enable)
{
if (deckLink == nullptr)
return;
int index = DeviceListCombo->findData(QVariant::fromValue((void*)deckLink.Get()));
if(index >= 0)
{
QStandardItemModel* model = qobject_cast<QStandardItemModel*>(DeviceListCombo->model());
QStandardItem* item = model->item(index);
item->setFlags(enable ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled);
}
SelectedDeviceChanged();
}
bool DeckLinkOutputPage::ReleaseDeviceIfSelected(ComPtr<IDeckLink>& deckLink)
{
if((SelectedDevice.Get() != nullptr) && (SelectedDevice->GetDeckLinkInstance().Get() == deckLink.Get()))
{
// Device is selected, stop and release it
SelectedDevice->StopPlayback();
SelectedDevice = nullptr;
DeviceListCombo->setCurrentIndex(0);
return true;
}
return false;
}
void DeckLinkOutputPage::OutputDeviceChanged(int selectedDeviceIndex)
{
START_SLOT_TIME_COUNTER
if (selectedDeviceIndex == -1)
return;
if(SelectedDevice)
{
ComPtr<IDeckLink> existingDevice = SelectedDevice->GetDeckLinkInstance();
// Stop and release existing selected device
SelectedDevice->StopPlayback();
SelectedDevice = nullptr;
// Notify parent widget that device is available
emit RelinquishDeckLink(existingDevice);
}
QVariant selectedDeviceVariant = DeviceListCombo->itemData(selectedDeviceIndex);
ComPtr<IDeckLink> deckLink((IDeckLink*)selectedDeviceVariant.value<void*>());
if(deckLink)
{
// Request deckLink object from parent widget
emit RequestDeckLink(deckLink);
}
else
{
// Update UI since "None" was selected
SelectedDeviceChanged();
}
END_SLOT_TIME_COUNTER
}
void DeckLinkOutputPage::RequestedDeviceGranted(ComPtr<IDeckLink>& device)
{
START_SLOT_TIME_COUNTER
SelectedDevice = MakeComPtr<DeckLinkOutputDevice>(device, 1);
// Register profile callback with newly selected device`s profile manager
// TODO: Connect
connect(BindingInputPage->GetCapture(), &CaptureThread::PushFrame, Process.get(), &ProcessThread::AddFrame);
connect(Process.get(), &ProcessThread::PushFrame, SelectedDevice.Get(), &DeckLinkOutputDevice::AddFrame);
if(Process->isRunning())
Process->exit();
Process->SetUpUDP("127.0.0.1", PortNumLineEdit->text());
Process->start();
SelectedDeviceChanged();
StartOutput();
END_SLOT_TIME_COUNTER
}
void DeckLinkOutputPage::ObjectNameChanged(const QString& newName)
{
START_SLOT_TIME_COUNTER
PortNumLineEdit->setText(QString::number(5000 + QString(newName.at(newName.size() - 1)).toInt()));
END_SLOT_TIME_COUNTER
}
void DeckLinkOutputPage::VideoFormatChanged(int selectedVideoFormatIndex)
{
START_SLOT_TIME_COUNTER
END_SLOT_TIME_COUNTER
}
void DeckLinkOutputPage::VideoPixelFormatChanged(int selectedVideoPixelFormatIndex)
{
START_SLOT_TIME_COUNTER
END_SLOT_TIME_COUNTER
}
void DeckLinkOutputPage::RestartOutput()
{
START_SLOT_TIME_COUNTER
END_SLOT_TIME_COUNTER
}
void DeckLinkOutputPage::SelectedDeviceChanged()
{
int indexToSelect = 0;
bool active = true;
if(SelectedDevice)
{
indexToSelect = DeviceListCombo->findData(QVariant::fromValue((void*)SelectedDevice->GetDeckLinkInstance().Get()));
active = SelectedDevice->isPlaybackActive();
}
else
{
PreviewView->Clear();
}
// Select the item in the combo box,but we don`t want to trigger any further processing
bool blocked = DeviceListCombo->blockSignals(true);
DeviceListCombo->setCurrentIndex(indexToSelect);
DeviceListCombo->blockSignals(blocked);
// Update the input connector popup menu which will in turn update the video format popup menu
// Update the toolbox title and the overlay
QToolBox* toolBox = findParent<QToolBox>(this);
int pageIndex = toolBox->indexOf(this);
QString title = QString("Output %1: %2%3").arg(pageIndex + 1).arg(DeviceListCombo->itemText(indexToSelect)).arg(active ? "" : " [inactive]");
toolBox->setItemText(pageIndex, title);
PreviewView->GetOverlay()->SetDeviceLabel(title);
}
void DeckLinkOutputPage::RefreshDisplayModeMenu()
{
}
#include "BlackMagicDesign/DeckLinkOutputVideoFrame.h"
#include <chrono>
#include <QDebug>
#include "Utils/Common.h"
#include <immintrin.h>
#include <cstdint>
DeckLinkOutputVideoFrame::DeckLinkOutputVideoFrame() : RefCount(1), width(-1), height(-1), rowBytes(-1), frameFlags(bmdFrameFlagDefault), pixelFormat(bmdFormat8BitBGRA)
{
}
DeckLinkOutputVideoFrame::DeckLinkOutputVideoFrame(int w, int h, BMDFrameFlags flags, BMDPixelFormat pixelFormat) : RefCount(1), width(w), height(h), frameFlags(bmdFrameFlagDefault), pixelFormat(pixelFormat)
{
rowBytes = GetRowBytesFromPixelFormat(width, pixelFormat);
buffer.resize(height * rowBytes);
}
DeckLinkOutputVideoFrame::DeckLinkOutputVideoFrame(Image& image)
: RefCount(1),
width(image.GetWidth()),
height(image.GetHegiht()),
frameFlags(image.GetFlags()),
pixelFormat(image.GetPixelFormat())
{
rowBytes = GetRowBytesFromPixelFormat(width, pixelFormat);
buffer.resize(height * rowBytes);
memcpy(buffer.data(), image.GetBytes(), height * rowBytes);
}
DeckLinkOutputVideoFrame::DeckLinkOutputVideoFrame(std::shared_ptr<Image> image)
: RefCount(1),
width(image->GetWidth()),
height(image->GetHegiht()),
frameFlags(image->GetFlags()),
pixelFormat(image->GetPixelFormat())
{
rowBytes = GetRowBytesFromPixelFormat(width, pixelFormat);
buffer.resize(height * rowBytes);
memcpy(buffer.data(), image->GetBytes(), height * rowBytes);
}
DeckLinkOutputVideoFrame::DeckLinkOutputVideoFrame(Image&& image)
: RefCount(1),
width(image.GetWidth()),
height(image.GetHegiht()),
frameFlags(image.GetFlags()),
pixelFormat(image.GetPixelFormat())
{
rowBytes = GetRowBytesFromPixelFormat(width, pixelFormat);
buffer.resize(height * rowBytes);
memcpy(buffer.data(), image.GetBytes(), height * rowBytes);
}
DeckLinkOutputVideoFrame::~DeckLinkOutputVideoFrame()
{
buffer.clear();
}
ULONG DeckLinkOutputVideoFrame::AddRef()
{
return ++RefCount;
}
ULONG DeckLinkOutputVideoFrame::Release()
{
ULONG newRefValue = --RefCount;
if (newRefValue == 0)
delete this;
return newRefValue;
}
HRESULT DeckLinkOutputVideoFrame::QueryInterface(REFIID riid, LPVOID* ppv)
{
HRESULT result = E_NOINTERFACE;
if (ppv == nullptr)
return E_INVALIDARG;
// Initialize the return result
*ppv = nullptr;
// Obtain the IUnknown interface and compare it the provided REFIID
if(riid == IID_IUnknown)
{
*ppv = this;
AddRef();
result = S_OK;
}
else if(riid == IID_IDeckLinkVideoFrame)
{
*ppv = (IDeckLinkVideoFrame*)this;
AddRef();
result = S_OK;
}
return result;
}
long DeckLinkOutputVideoFrame::GetWidth()
{
return (long)width;
}
long DeckLinkOutputVideoFrame::GetHeight()
{
return (long)height;
}
BMDFrameFlags DeckLinkOutputVideoFrame::GetFlags()
{
return frameFlags;
}
BMDPixelFormat DeckLinkOutputVideoFrame::GetPixelFormat()
{
return pixelFormat;
}
long DeckLinkOutputVideoFrame::GetRowBytes()
{
return rowBytes;
}
HRESULT DeckLinkOutputVideoFrame::GetBytes(void** buf)
{
if(buffer.empty())
{
*buf = nullptr;
return E_FAIL;
}
else
{
*buf = buffer.data();
return S_OK;
}
}
#include <QBrush>
#include <QFont>
#include <QFontDatabase>
#include <QFontMetrics>
#include <QPainter>
#include "Utils/Platform.h"
#include "BlackMagicDesign/DeckLinkPreviewVideoFrame.h"
#include "BlackMagicDesign/DeckLinkPreviewOverlay.h"
DeckLinkPreviewOverlay::DeckLinkPreviewOverlay(QObject* parent) : QObject(parent), bEnableTimeCode(false), bEnableDeviceLabel(false)
{
}
void DeckLinkPreviewOverlay::SetFrame(ComPtr<IDeckLinkVideoFrame> frame)
{
bool validTimeCode = false;
{
std::lock_guard<std::mutex> lock(Mutex);
if(frame)
{
bSignalValid = (frame->GetFlags() & bmdFrameHasNoInputSource) == 0;
if(bSignalValid)
{
// Get the timecode attached to this frame
ComPtr<IDeckLinkTimecode> timecode;
HRESULT result = frame->GetTimecode(bmdTimecodeRP188Any, timecode.ReleaseAndGetAddressOf());
if(result == S_OK)
{
dlstring_t timecodeStr;
result = timecode->GetString(&timecodeStr);
if(result == S_OK)
{
TimeCode = DlToQString(timecodeStr);
DeleteString(timecodeStr);
validTimeCode = true;
}
}
}
}
if (!validTimeCode)
TimeCode = "00:00:00:00";
}
emit UpdatePreview();
}
void DeckLinkPreviewOverlay::Clear()
{
{
std::lock_guard<std::mutex> lock(Mutex);
bSignalValid = false;
}
}
void DeckLinkPreviewOverlay::SetDeviceLabel(const QString& label)
{
{
std::lock_guard<std::mutex> lock(Mutex);
DeviceLabel = label;
}
if (bEnableDeviceLabel)
emit UpdatePreview();
}
void DeckLinkPreviewOverlay::EnableTimeCode(bool enable)
{
{
std::lock_guard<std::mutex> lock(Mutex);
bEnableTimeCode = enable;
}
emit UpdatePreview();
}
void DeckLinkPreviewOverlay::EnableDeviceLabel(bool enable)
{
{
std::lock_guard<std::mutex> lock(Mutex);
bEnableDeviceLabel = enable;
}
emit UpdatePreview();
}
bool DeckLinkPreviewOverlay::IsSignalValid()
{
std::lock_guard<std::mutex> lock(Mutex);
return bSignalValid;
}
void DeckLinkPreviewOverlay::Paint(QPaintDevice* paintDevice)
{
QPainter painter;
painter.begin(paintDevice);
QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
QBrush brush(QColor(0, 0, 0, 128));
{
std::lock_guard<std::mutex> lock(Mutex);
if(!bSignalValid)
{
font.setPixelSize(paintDevice->height() / 12);
QFontMetrics metrics(font, paintDevice);
QString text("No Signal");
painter.setPen(QColor(Qt::red));
painter.setFont(font);
painter.drawText((paintDevice->width() - metrics.width(text)) / 2, (paintDevice->height() - metrics.height()) / 2 + metrics.ascent(), text);
}
if(bEnableDeviceLabel)
{
font.setPixelSize(paintDevice->height() / 16);
QFontMetrics metrics(font, paintDevice);
QRect box(0, 0, paintDevice->width(), metrics.height() + 4);
painter.fillRect(box, brush);
painter.setPen(QColor(Qt::white));
painter.setFont(font);
painter.drawText((paintDevice->width() - metrics.width(DeviceLabel)) / 2, box.top() + 2 + metrics.ascent(), DeviceLabel);
}
if(bEnableTimeCode)
{
font.setPixelSize(paintDevice->height() / 16);
QFontMetrics metrics(font, paintDevice);
QRect box(0, paintDevice->height() - (metrics.height() + 4), paintDevice->width(), metrics.height() + 4);
painter.fillRect(box, brush);
painter.setPen(QColor(Qt::white));
painter.setFont(font);
painter.drawText((paintDevice->width() - metrics.width(TimeCode)) / 2, box.top() + 2 + metrics.ascent(), TimeCode);
}
}
painter.end();
}
#include "BlackMagicDesign/DeckLinkPreviewVideoFrame.h"
#include "Utils/Common.h"
#include <chrono>
#include <QDebug>
DeckLinkPreviewVideoFrame::DeckLinkPreviewVideoFrame() : RefCount(1), width(-1), height(-1), rowBytes(-1), frameFlags(bmdFrameFlagDefault), pixelFormat(bmdFormat8BitBGRA)
{
}
DeckLinkPreviewVideoFrame::DeckLinkPreviewVideoFrame(IDeckLinkVideoFrame* frame) : RefCount(1), pixelFormat(bmdFormat8BitBGRA)
{
if (frame == nullptr)
return;
width = frame->GetWidth();
height = frame->GetHeight();
frameFlags = frame->GetFlags();
rowBytes = GetRowBytesFromPixelFormat(width, pixelFormat);
buffer.resize(height * rowBytes);
ComPtr<IDeckLinkVideoConversion> deckLinkVideoConversion = nullptr;
HRESULT result = CoCreateInstance(CLSID_CDeckLinkVideoConversion, nullptr, CLSCTX_ALL, IID_IDeckLinkVideoConversion, (void**)deckLinkVideoConversion.GetAddressOf());
if (!deckLinkVideoConversion)
return;
deckLinkVideoConversion->ConvertFrame(frame, this);
}
DeckLinkPreviewVideoFrame::~DeckLinkPreviewVideoFrame()
{
buffer.clear();
}
ULONG DeckLinkPreviewVideoFrame::AddRef()
{
return ++RefCount;
}
ULONG DeckLinkPreviewVideoFrame::Release()
{
ULONG newRefValue = --RefCount;
if (newRefValue == 0)
delete this;
return newRefValue;
}
HRESULT DeckLinkPreviewVideoFrame::QueryInterface(REFIID riid, LPVOID* ppv)
{
HRESULT result = E_NOINTERFACE;
if (ppv == nullptr)
return E_INVALIDARG;
// Initialize the return result
*ppv = nullptr;
// Obtain the IUnknown interface and compare it the provided REFIID
if (riid == IID_IUnknown)
{
*ppv = this;
AddRef();
result = S_OK;
}
else if (riid == IID_IDeckLinkVideoFrame)
{
*ppv = (IDeckLinkVideoFrame*)this;
AddRef();
result = S_OK;
}
return result;
}
long DeckLinkPreviewVideoFrame::GetWidth()
{
return (long)width;
}
long DeckLinkPreviewVideoFrame::GetHeight()
{
return (long)height;
}
BMDFrameFlags DeckLinkPreviewVideoFrame::GetFlags()
{
return frameFlags;
}
long DeckLinkPreviewVideoFrame::GetRowBytes()
{
return rowBytes;
}
BMDPixelFormat DeckLinkPreviewVideoFrame::GetPixelFormat()
{
return pixelFormat;
}
HRESULT DeckLinkPreviewVideoFrame::GetBytes(void** buf)
{
if (buffer.empty())
{
*buf = nullptr;
return E_FAIL;
}
else
{
*buf = buffer.data();
return S_OK;
}
}
\ No newline at end of file
#include <QCoreApplication>
#include "BlackMagicDesign/ProfileCallback.h"
#include "Utils/ComPtr.h"
ProfileCallback::ProfileCallback(QObject* owner) : RefCount(1), Owner(owner), mProfileChangingCallback(nullptr)
{
}
// IUnknown methods
HRESULT ProfileCallback::QueryInterface(REFIID riid, LPVOID* ppv)
{
HRESULT result = S_OK;
if (ppv == nullptr)
return E_INVALIDARG;
// Obtain the IUnknown interface and compare it the provided REFIID
if(riid == IID_IUnknown)
{
*ppv = this;
AddRef();
}
else if(riid == IID_IDeckLinkProfileCallback)
{
*ppv = static_cast<IDeckLinkProfileCallback*>(this);
AddRef();
}
else
{
*ppv = nullptr;
result = E_NOINTERFACE;
}
return result;
}
ULONG ProfileCallback::AddRef()
{
return ++RefCount;
}
ULONG ProfileCallback::Release()
{
ULONG newRefValue = --RefCount;
if (newRefValue == 0)
delete this;
return newRefValue;
}
// IDeckLinkProfileCallback
HRESULT ProfileCallback::ProfileChanging(IDeckLinkProfile* profileToBeActivated, dlbool_t streamsWillBeForcedToStop)
{
// When streamWillBeForcedToStop is true, the profile to be activated is incompatible with the current
// profile and capture will be stopped by the DeckLink driver. It is better to notify the
// subscriber to gracefully stop capture, so that the UI is set to a known state.
if ((streamsWillBeForcedToStop) && (mProfileChangingCallback != nullptr))
mProfileChangingCallback(ComPtr<IDeckLinkProfile>(profileToBeActivated));
return S_OK;
}
HRESULT ProfileCallback::ProfileActivated(IDeckLinkProfile* activatedProfile)
{
// New profile activated, inform subscriber to update UI
QCoreApplication::postEvent(Owner, new ProfileActivatedEvent(activatedProfile));
return S_OK;
}
#include "BlackMagicDesign/ScreenPreviewCallback.h"
#include "BlackMagicDesign/DeckLinkPreviewVideoFrame.h"
#include "Utils/Platform.h"
#include <QDebug>
///
/// ScreenPreviewCallback
///
ScreenPreviewCallback::ScreenPreviewCallback() : RefCount(1)
{
}
/// IUnknown methods
HRESULT ScreenPreviewCallback::QueryInterface(REFIID riid, LPVOID* ppv)
{
HRESULT result = S_OK;
if (ppv == nullptr)
return E_INVALIDARG;
// Obtain the IUnknown interface and compare it the provided REFIID
if(riid == IID_IUnknown)
{
*ppv = this;
AddRef();
}
else if(riid == IID_IDeckLinkScreenPreviewCallback)
{
*ppv = static_cast<IDeckLinkScreenPreviewCallback*>(this);
AddRef();
}
else
{
*ppv = nullptr;
result = E_NOINTERFACE;
}
return result;
}
ULONG ScreenPreviewCallback::AddRef()
{
return ++RefCount;
}
ULONG ScreenPreviewCallback::Release()
{
ULONG newRefValue = --RefCount;
if (newRefValue == 0)
delete this;
return newRefValue;
}
/// IDeckLinkScreenPreviewCallback methods
HRESULT ScreenPreviewCallback::DrawFrame(IDeckLinkVideoFrame* theFrame)
{
START_GL_TIME_COUNTER
emit FrameArrived(ComPtr<IDeckLinkVideoFrame>(theFrame));
END_GL_TIME_COUNTER
return S_OK;
}
#include <csignal>
#include <cstddef>
#include <cstring>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <atomic>
#include "NDI/NDIOutputThread.h"
NDIOutputThread::NDIOutputThread(const QString& Name, int w, int h) : NDISenderName(Name), width(w), height(h), isSending(false), Instance(nullptr)
{
}
NDIOutputThread::~NDIOutputThread()
{
free(Frame.p_data);
NDIlib_send_destroy(Instance);
}
void NDIOutputThread::SetNDISenderName(const QString& Name)
{
NDISenderName = Name;
}
QString NDIOutputThread::GetNDISenderName() const
{
return NDISenderName;
}
void NDIOutputThread::SetNDIImageSize(int w, int h)
{
if (w <= 0 || h <= 0)
return;
width = w;
height = h;
}
bool NDIOutputThread::Init()
{
// Init NDIlib with static lib
if (!CheckValid())
return false;
if(Instance != nullptr)
{
free(Frame.p_data);
NDIlib_send_destroy(Instance);
}
NDIlib_send_create_t NDI_Send_Create_Desc;
std::string strname = NDISenderName.toStdString();
NDI_Send_Create_Desc.p_ndi_name = strname.c_str();
Instance = NDIlib_send_create(&NDI_Send_Create_Desc);
if (!Instance) return false;
// Provide a meta-data registration that allows people to know what we are. Note that this is optional.
// Note that it is possible for senders to also register their preferred video formats.
NDIlib_metadata_frame_t NDI_connection_type;
NDIlib_send_add_connection_metadata(Instance, &NDI_connection_type);
Frame.xres = width;
Frame.yres = height;
Frame.FourCC = NDIlib_FourCC_type_BGRA;
Frame.p_data = (uint8_t*)malloc(Frame.xres * Frame.yres * 4);
Frame.line_stride_in_bytes = Frame.xres * 4;
Frame.frame_rate_D = 1000;
Frame.frame_rate_N = 25000;
Frame.frame_format_type = NDIlib_frame_format_type_progressive;
Frame.picture_aspect_ratio = 16.0 / 9;
return true;
}
void NDIOutputThread::run()
{
if (!Init())
return;
while(true)
{
START_WAIT_TIME_COUNTER
std::shared_ptr<Image> frame;
if(taskQueue.WaitFor(frame))
{
END_WAIT_TIME_COUNTER
START_TIME_COUNTER
if(frame->IsValid())
{
frame->Fill(Frame.p_data, Frame.xres * Frame.yres * 4);
NDIlib_send_send_video_v2(Instance, &Frame);
}
taskQueue.Pop(frame);
END_TIME_COUNTER
}
}
}
void NDIOutputThread::Clear()
{
taskQueue.Reset();
}
void NDIOutputThread::AddFrame(std::shared_ptr<Image> frame)
{
START_SLOT_TIME_COUNTER
if (!frame->IsValid())
return;
taskQueue.Push(frame);
END_SLOT_TIME_COUNTER
}
#include "Threads/CaptureThread.h"
CaptureThread::CaptureThread()
{
}
CaptureThread::~CaptureThread()
{
}
void CaptureThread::AddFrame(ComPtr<IDeckLinkVideoInputFrame> videoFrame)
{
START_SLOT_TIME_COUNTER
if (videoFrame == nullptr)
return;
if (videoFrame->GetWidth() <=0 || videoFrame->GetHeight() <= 0)
return;
if (videoFrame->GetWidth() != 1920 || videoFrame->GetHeight() != 1080)
return;
taskQueue.Push(videoFrame);
END_SLOT_TIME_COUNTER
}
void CaptureThread::run()
{
while(true)
{
START_WAIT_TIME_COUNTER
ComPtr<IDeckLinkVideoInputFrame> videoFrame;
if(taskQueue.WaitFor(videoFrame))
{
END_WAIT_TIME_COUNTER
if (videoFrame.Get() != nullptr)
{
std::shared_ptr<Image> image = std::make_shared<Image>(videoFrame);
emit PushFrame(image);
}
DEBUG_FUNCTION("taskQeueue Size: ", taskQueue.Size())
}
while (taskQueue.Size() > 30)
{
taskQueue.Pop(videoFrame);
}
}
}
\ No newline at end of file
#include "Threads/ProcessThread.h"
#include "opencv2/opencv.hpp"
ProcessThread::ProcessThread()
{
}
ProcessThread::~ProcessThread()
{
udpSocket->close();
udpSocket->deleteLater();
}
void ProcessThread::SetUpUDP(const QString hostAddr, const QString hostPort)
{
udpSocket = new QUdpSocket(this);
udpSocket->bind(QHostAddress(hostAddr), hostPort.toInt());
connect(udpSocket, SIGNAL(readyRead()), this, SLOT(ReadDatagrams()), Qt::DirectConnection);
}
void ProcessThread::AddFrame(std::shared_ptr<Image> image)
{
START_SLOT_TIME_COUNTER
if (image->IsValid())
taskImageQueue.Push(image);
END_SLOT_TIME_COUNTER
}
void ProcessThread::ReadDatagrams()
{
START_SLOT_TIME_COUNTER
QHostAddress inClientAddr;
quint16 inClientPort;
QByteArray data;
data.clear();
while(udpSocket->hasPendingDatagrams())
{
data.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(data.data(), data.size(), &inClientAddr, &inClientPort);
}
if(!data.isEmpty())
{
taskROIQueue.Push(RoiMessage(data));
}
END_SLOT_TIME_COUNTER
}
void ProcessThread::run()
{
while (true)
{
START_WAIT_TIME_COUNTER
std::shared_ptr<Image> image;
RoiMessage roi;
if(taskImageQueue.WaitFor(image))
{
END_WAIT_TIME_COUNTER
if(taskROIQueue.WaitUntil(roi, 1))
{
lastReceiveMessage = roi;
}
else
{
roi = lastReceiveMessage;
}
START_TIME_COUNTER_BASE(OpenCV)
cv::Rect cvroi(roi.X(), roi.Y(), roi.Width(), roi.Height());
cv::Mat mat = image->GetMat().clone();
cv::UMat umat4Image(mat.rows, mat.cols, CV_8UC3);
cv::UMat umat4RotatedImage(810, 1080, CV_8UC3);
cv::cvtColor(mat, umat4Image, cv::COLOR_BGRA2BGR);
cv::UMat umat4ClippedImage = umat4Image(cvroi);
cv::rotate(umat4ClippedImage, umat4RotatedImage, cv::ROTATE_90_CLOCKWISE);
cv::UMat umat4ResizedImage;
cv::resize(umat4RotatedImage, umat4ResizedImage, cv::Size(1440, 1080));
cv::UMat umat4FinalImage = cv::UMat::zeros(cv::Size(1920, 1080), CV_8UC3);
umat4ResizedImage.copyTo(umat4FinalImage(cv::Rect(240, 0, 1440, 1080)));
cv::Mat finalmat;
cv::cvtColor(umat4FinalImage, finalmat, cv::COLOR_BGR2BGRA);
//image->GetMatByRoi(cvroi, mat);
//mat.copyTo(umat4Image);
//cv::rotate(umat4Image, umat4RotatedImage, cv::ROTATE_90_CLOCKWISE);
//cv::resize(umat4RotatedImage, umat4FinalImage, cv::Size(1920, 1080));
//umat4FinalImage.copyTo(mat);
image->SetMat(finalmat);
END_TIME_COUNTER_BASE(OpenCV)
emit PushFrame(image);
DEBUG_FUNCTION("taskImageQueue Size: ", taskImageQueue.Size())
}
while(taskImageQueue.Size() > 30)
{
taskImageQueue.Pop(image);
}
while (taskROIQueue.Size() > 30)
{
taskROIQueue.Pop(roi);
}
}
}
void ProcessThread::Clear()
{
taskROIQueue.Reset();
taskImageQueue.Reset();
}
#include "Utils/Common.h"
long GetRowBytesFromPixelFormat(long width, BMDPixelFormat pixelFormat)
{
long rowBytes = 0;
// calc Frame row bytes with pixelformat
switch (pixelFormat)
{
case bmdFormat8BitYUV:
rowBytes = width * 16 / 8;
break;
case bmdFormat10BitYUV:
rowBytes = ((width + 47) / 48) * 128;
break;
case bmdFormat8BitARGB:
case bmdFormat8BitBGRA:
rowBytes = width * 32 / 8;
break;
case bmdFormat10BitRGB:
case bmdFormat10BitRGBX:
case bmdFormat10BitRGBXLE:
rowBytes = ((width + 63) / 64) * 256;
break;
case bmdFormat12BitRGB:
case bmdFormat12BitRGBLE:
rowBytes = width * 36 / 8;
break;
default:
break;
}
return rowBytes;
}
\ No newline at end of file
#include "Utils/Image.h"
#include "Utils/ComPtr.h"
#include "opencv2/core.hpp"
Image::Image() : mat()
{
}
Image::Image(IDeckLinkVideoInputFrame* videoFrame)
{
if(videoFrame->GetWidth() != 1920 || videoFrame->GetHeight() != 1080)
{
return;
}
inVideoFrame = MakeComPtr<DeckLinkInputVideoFrame>(videoFrame);
ConvertDeckLinkVideoFrame2Mat(inVideoFrame, mat);
}
Image::Image(ComPtr<DeckLinkInputVideoFrame> videoFrame)
{
if (videoFrame->GetWidth() != 1920 || videoFrame->GetHeight() != 1080)
{
return;
}
inVideoFrame = videoFrame;
ConvertDeckLinkVideoFrame2Mat(videoFrame, mat);
}
Image::Image(ComPtr<IDeckLinkVideoInputFrame> videoFrame)
{
if (videoFrame->GetWidth() != 1920 || videoFrame->GetHeight() != 1080)
{
return;
}
inVideoFrame = MakeComPtr<DeckLinkInputVideoFrame>(videoFrame.Get());
ConvertDeckLinkVideoFrame2Mat(inVideoFrame, mat);
}
Image::Image(const Image& other)
{
mat = other.mat;
}
Image::Image(Image&& other)
{
mat = other.mat;
}
Image& Image::operator=(const Image& other)
{
mat = other.mat;
return *this;
}
Image& Image::operator=(Image&& other)
{
mat = other.mat;
return *this;
}
Image::~Image()
{
}
bool Image::IsValid() const
{
return !mat.empty();
}
uint8_t* Image::GetBytes() const
{
if (IsValid())
return mat.data;
else
return nullptr;
}
cv::Mat Image::GetMat()
{
return mat;
}
void Image::SetMat(cv::Mat& inMat)
{
mat = inMat.clone();
}
void Image::GetMatByRoi(cv::Rect roi, cv::Mat& outMat)
{
int w = GetWidth();
int h = GetHegiht();
if (roi.x + roi.width > w) roi.x = w - roi.width;
if (roi.x < 0) roi.x = 0;
if (roi.x + roi.width > w) roi.width = w - roi.x;
if (roi.y + roi.height > h) roi.y = h - roi.height;
if (roi.y < 0) roi.y = 0;
if (roi.y + roi.height > h) roi.height = h - roi.y;
outMat = mat(roi);
}
HRESULT ConvertDeckLinkVideoFrame2Mat(ComPtr<DeckLinkInputVideoFrame> videoFrame, cv::Mat& imageFrame)
{
if(videoFrame.Get() != nullptr)
{
void* buffer;
if (FAILED(videoFrame->GetBytes(&buffer)))
return S_FALSE;
cv::Mat bgra = cv::Mat(videoFrame->GetHeight(), videoFrame->GetWidth(), CV_8UC4, buffer);
bgra.copyTo(imageFrame);
return S_OK;
}
return S_FALSE;
}
\ No newline at end of file
#include "Utils/LatencyStatistics.h"
#include "algorithm"
#include "cmath"
#include <limits>
LatencyStatistics::LatencyStatistics(int maxRollingSamples) : maxRollingSamples(maxRollingSamples)
{
if (maxRollingSamples < 1)
throw std::invalid_argument("Unexpected value for rolling average size.");
Reset();
}
void LatencyStatistics::Reset()
{
std::lock_guard<std::mutex> locker(mutex);
rollingSamples.clear();
rollingAggregateLatency = 0;
maxLatency = (std::numeric_limits<BMDTimeValue>::min)();
minLatency = (std::numeric_limits<BMDTimeValue>::max)();
mean = 0;
m2 = 0;
sampleCount = 0;
}
void LatencyStatistics::AddSample(const BMDTimeValue latency)
{
BMDTimeValue delta;
BMDTimeValue delta2;
std::lock_guard<std::mutex> locker(mutex);
// Add to rolling average list
if(rollingSamples.size() >= (size_t)maxRollingSamples)
{
rollingAggregateLatency -= rollingSamples.front();
rollingSamples.pop_front();
}
rollingAggregateLatency += latency;
rollingSamples.push_back(latency);
// Check minimum and maximum latency
maxLatency = max(maxLatency, latency);
minLatency = min(minLatency, latency);
// Compute new mean/variance (Welford`s algorithm)
delta = latency - mean;
mean += delta / ++sampleCount;
delta2 = latency - mean;
m2 += delta + delta2;
}
BMDTimeValue LatencyStatistics::getMinimun()
{
std::lock_guard<std::mutex> locker(mutex);
return minLatency;
}
BMDTimeValue LatencyStatistics::getMaximum()
{
std::lock_guard<std::mutex> locker(mutex);
return maxLatency;
}
std::pair<BMDTimeValue, BMDTimeValue> LatencyStatistics::getMeanAndStdDev()
{
std::lock_guard<std::mutex> locker(mutex);
if (sampleCount < 2)
return std::make_pair((BMDTimeValue)0, (BMDTimeValue)0);
return std::make_pair(mean, (BMDTimeValue)std::sqrt(m2 / (sampleCount - 1)));
}
BMDTimeValue LatencyStatistics::getRollingAverage()
{
std::lock_guard<std::mutex> locker(mutex);
if (rollingSamples.empty())
return (BMDTimeValue)0;
return rollingAggregateLatency / rollingSamples.size();
}
#include "Utils/Platform.h"
HRESULT GetDeckLinkDiscoveryInstance(ComPtr<IDeckLinkDiscovery>& deckLinkDiscovery)
{
HRESULT result = S_OK;
// Create an IDeckLinkIterator object to enumerate all DeckLink cards in the system
result = CoCreateInstance(CLSID_CDeckLinkDiscovery, nullptr, CLSCTX_ALL, IID_IDeckLinkDiscovery, (void**)deckLinkDiscovery.ReleaseAndGetAddressOf());
if(FAILED(result))
{
fprintf(stderr, "A DeckLink discovery interface could not be created. The DeckLink drivers may not be installed.\n");
}
return result;
}
HRESULT GetDeckLinkOpenGLScreenPreviewHelper(ComPtr<IDeckLinkGLScreenPreviewHelper>& screenPreviewHelper)
{
HRESULT result = S_OK;
// Create an IDeckLinkIterator object to enumerate all DeckLink cards in the system
result = CoCreateInstance(CLSID_CDeckLinkGL3ScreenPreviewHelper, nullptr, CLSCTX_ALL, IID_IDeckLinkGLScreenPreviewHelper, (void**)screenPreviewHelper.ReleaseAndGetAddressOf());
if(FAILED(result))
{
fprintf(stderr, "A DeckLink OpenGL screen preview helper interface could not be created.\n");
}
return result;
}
#include "MomentaMedia.h"
#include "stdafx.h"
#include <QtWidgets/QApplication>
#include "Utils/ComPtr.h"
#include "BlackMagicDesign/DeckLinkInputVideoFrame.h"
#include "DeckLinkAPI.h"
#include <objbase.h>
#include <Orbit.h>
int main(int argc, char *argv[])
{
OrbitScope("MomentaMedia");
QApplication a(argc, argv);
HRESULT hr = ::CoInitialize(nullptr);
if (!SUCCEEDED(hr))
return hr;
qRegisterMetaType<ComPtr<DeckLinkInputVideoFrame>>("ComPtr<DeckLinkInputVideoFrame>");
qRegisterMetaType<ComPtr<IDeckLinkVideoFrame>>("ComPtr<IDeckLinkVideoFrame>");
qRegisterMetaType<ComPtr<IDeckLinkVideoInputFrame>>("ComPtr<IDeckLinkVideoInputFrame>");
qRegisterMetaType<IDeckLinkVideoInputFrame*>("IDeckLinkVideoInputFrame*");
qRegisterMetaType<Image>("Image");
qRegisterMetaType<Image>("Image&");
qRegisterMetaType<std::shared_ptr<Image>>("std::shared_ptr<Image>");
MomentaMedia w;
w.show();
int exitResult = a.exec();
::CoUninitialize();
return a.exec();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment