obs-studio/plugins/decklink/decklink-device-instance.cpp
jp9000 ae733230b7 decklink: Use audio/video timestamps from SDK
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.
2015-07-01 20:03:10 -07:00

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(), &currentPacket);
}
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(), &currentFrame);
}
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;
}