ae733230b7
System timestamps were being used instead of timestamps from the audio/video input. This would cause potential desync as well as incremental buffering when using devices with the blackmagic video source. Using the timestamps direct from the SDK itself fixes those issues, and causes audio/video to play back properly and in sync.
202 lines
4.9 KiB
C++
202 lines
4.9 KiB
C++
#include "decklink-device-instance.hpp"
|
|
|
|
#include <util/platform.h>
|
|
#include <util/threading.h>
|
|
|
|
#include <sstream>
|
|
|
|
#define LOG(level, message, ...) blog(level, "%s: " message, \
|
|
obs_source_get_name(this->decklink->GetSource()), ##__VA_ARGS__)
|
|
|
|
DeckLinkDeviceInstance::DeckLinkDeviceInstance(DeckLink *decklink_,
|
|
DeckLinkDevice *device_) :
|
|
currentFrame(), currentPacket(), decklink(decklink_), device(device_)
|
|
{
|
|
currentFrame.format = VIDEO_FORMAT_UYVY;
|
|
|
|
currentPacket.samples_per_sec = 48000;
|
|
currentPacket.speakers = SPEAKERS_STEREO;
|
|
currentPacket.format = AUDIO_FORMAT_16BIT;
|
|
}
|
|
|
|
void DeckLinkDeviceInstance::HandleAudioPacket(
|
|
IDeckLinkAudioInputPacket *audioPacket,
|
|
const uint64_t timestamp)
|
|
{
|
|
if (audioPacket == nullptr)
|
|
return;
|
|
|
|
void *bytes;
|
|
if (audioPacket->GetBytes(&bytes) != S_OK) {
|
|
LOG(LOG_WARNING, "Failed to get audio packet data");
|
|
return;
|
|
}
|
|
|
|
currentPacket.data[0] = (uint8_t *)bytes;
|
|
currentPacket.frames = (uint32_t)audioPacket->GetSampleFrameCount();
|
|
currentPacket.timestamp = timestamp;
|
|
|
|
obs_source_output_audio(decklink->GetSource(), ¤tPacket);
|
|
}
|
|
|
|
void DeckLinkDeviceInstance::HandleVideoFrame(
|
|
IDeckLinkVideoInputFrame *videoFrame, const uint64_t timestamp)
|
|
{
|
|
if (videoFrame == nullptr)
|
|
return;
|
|
|
|
void *bytes;
|
|
if (videoFrame->GetBytes(&bytes) != S_OK) {
|
|
LOG(LOG_WARNING, "Failed to get video frame data");
|
|
return;
|
|
}
|
|
|
|
currentFrame.data[0] = (uint8_t *)bytes;
|
|
currentFrame.linesize[0] = (uint32_t)videoFrame->GetRowBytes();
|
|
currentFrame.width = (uint32_t)videoFrame->GetWidth();
|
|
currentFrame.height = (uint32_t)videoFrame->GetHeight();
|
|
currentFrame.timestamp = timestamp;
|
|
|
|
video_format_get_parameters(VIDEO_CS_601, VIDEO_RANGE_PARTIAL,
|
|
currentFrame.color_matrix, currentFrame.color_range_min,
|
|
currentFrame.color_range_max);
|
|
|
|
obs_source_output_video(decklink->GetSource(), ¤tFrame);
|
|
}
|
|
|
|
bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_)
|
|
{
|
|
if (mode != nullptr)
|
|
return false;
|
|
if (mode_ == nullptr)
|
|
return false;
|
|
|
|
LOG(LOG_INFO, "Starting capture...");
|
|
|
|
if (!device->GetInput(&input))
|
|
return false;
|
|
|
|
input->SetCallback(this);
|
|
|
|
const BMDDisplayMode displayMode = mode_->GetDisplayMode();
|
|
const HRESULT videoResult = input->EnableVideoInput(displayMode,
|
|
bmdFormat8BitYUV, bmdVideoInputFlagDefault);
|
|
|
|
if (videoResult != S_OK) {
|
|
LOG(LOG_ERROR, "Failed to enable video input");
|
|
input->SetCallback(nullptr);
|
|
return false;
|
|
}
|
|
|
|
const HRESULT audioResult = input->EnableAudioInput(
|
|
bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger,
|
|
2);
|
|
|
|
if (audioResult != S_OK)
|
|
LOG(LOG_WARNING, "Failed to enable audio input; continuing...");
|
|
|
|
if (input->StartStreams() != S_OK) {
|
|
LOG(LOG_ERROR, "Failed to start streams");
|
|
input->SetCallback(nullptr);
|
|
input->DisableVideoInput();
|
|
input->DisableAudioInput();
|
|
return false;
|
|
}
|
|
|
|
mode = mode_;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DeckLinkDeviceInstance::StopCapture(void)
|
|
{
|
|
if (mode == nullptr || input == nullptr)
|
|
return false;
|
|
|
|
LOG(LOG_INFO, "Stopping capture of '%s'...",
|
|
GetDevice()->GetDisplayName().c_str());
|
|
|
|
input->StopStreams();
|
|
input->SetCallback(nullptr);
|
|
input->DisableVideoInput();
|
|
input->DisableAudioInput();
|
|
|
|
mode = nullptr;
|
|
|
|
return true;
|
|
}
|
|
|
|
#define TIME_BASE 1000000000
|
|
|
|
HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::VideoInputFrameArrived(
|
|
IDeckLinkVideoInputFrame *videoFrame,
|
|
IDeckLinkAudioInputPacket *audioPacket)
|
|
{
|
|
BMDTimeValue videoTS = 0;
|
|
BMDTimeValue videoDur = 0;
|
|
BMDTimeValue audioTS = 0;
|
|
|
|
videoFrame->GetStreamTime(&videoTS, &videoDur, TIME_BASE);
|
|
audioPacket->GetPacketTime(&audioTS, TIME_BASE);
|
|
|
|
if (videoTS >= 0)
|
|
HandleVideoFrame(videoFrame, (uint64_t)videoTS);
|
|
if (audioTS >= 0)
|
|
HandleAudioPacket(audioPacket, (uint64_t)audioTS);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::VideoInputFormatChanged(
|
|
BMDVideoInputFormatChangedEvents events,
|
|
IDeckLinkDisplayMode *newMode,
|
|
BMDDetectedVideoInputFormatFlags detectedSignalFlags)
|
|
{
|
|
UNUSED_PARAMETER(events);
|
|
UNUSED_PARAMETER(newMode);
|
|
UNUSED_PARAMETER(detectedSignalFlags);
|
|
|
|
// There is no implementation for automatic format detection, so this
|
|
// method goes unused.
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE DeckLinkDeviceInstance::AddRef(void)
|
|
{
|
|
return os_atomic_inc_long(&refCount);
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::QueryInterface(REFIID iid,
|
|
LPVOID *ppv)
|
|
{
|
|
HRESULT result = E_NOINTERFACE;
|
|
|
|
*ppv = nullptr;
|
|
|
|
CFUUIDBytes unknown = CFUUIDGetUUIDBytes(IUnknownUUID);
|
|
if (memcmp(&iid, &unknown, sizeof(REFIID)) == 0) {
|
|
*ppv = this;
|
|
AddRef();
|
|
result = S_OK;
|
|
} else if (memcmp(&iid, &IID_IDeckLinkNotificationCallback,
|
|
sizeof(REFIID)) == 0) {
|
|
*ppv = (IDeckLinkNotificationCallback *)this;
|
|
AddRef();
|
|
result = S_OK;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE DeckLinkDeviceInstance::Release(void)
|
|
{
|
|
const long newRefCount = os_atomic_dec_long(&refCount);
|
|
if (newRefCount == 0) {
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
return newRefCount;
|
|
}
|