/* * FILE: video_display/decklink.cpp * AUTHORS: Martin Benes * Lukas Hejtmanek * Petr Holub * Milos Liska * Jiri Matela * Dalibor Matura <255899@mail.muni.cz> * Ian Wesley-Smith * Colin Perkins * * Copyright (c) 2005-2010 CESNET z.s.p.o. * Copyright (c) 2001-2003 University of Southern California * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * * This product includes software developed by the University of Southern * California Information Sciences Institute. This product also includes * software developed by CESNET z.s.p.o. * * 4. Neither the name of the University, Institute, CESNET nor the names of * its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * */ #ifdef HAVE_CONFIG_H #include "config.h" #include "config_unix.h" #include "config_win32.h" #endif // HAVE_CONFIG_H #define MODULE_NAME "[Decklink display] " #ifdef __cplusplus extern "C" { #endif #include "host.h" #include "debug.h" #include "video_codec.h" #include "tv.h" #include "video_display/decklink.h" #include "debug.h" #include "video_capture.h" #include "audio/audio.h" #include "audio/utils.h" #ifdef WIN32 #include "DeckLinkAPI_h.h" #else #include "DeckLinkAPI.h" #endif #include "DeckLinkAPIVersion.h" #ifdef __cplusplus } // END of extern "C" #endif #ifdef WIN32 #include #endif #ifdef HAVE_MACOSX #define STRING CFStringRef #elif WIN32 #define STRING BSTR #else #define STRING const char * #endif #ifndef WIN32 #define STDMETHODCALLTYPE #endif enum link { LINK_UNSPECIFIED, LINK_3G, LINK_DUAL }; // defined int video_capture/decklink.cpp void print_output_modes(IDeckLink *); static int blackmagic_api_version_check(STRING *current_version); #define MAX_DEVICES 4 class PlaybackDelegate : public IDeckLinkVideoOutputCallback // , public IDeckLinkAudioOutputCallback { struct state_decklink * s; int i; public: PlaybackDelegate (struct state_decklink* owner, int index); // IUnknown needs only a dummy implementation virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *ppv) {return E_NOINTERFACE;} virtual ULONG STDMETHODCALLTYPE AddRef () {return 1;} virtual ULONG STDMETHODCALLTYPE Release () {return 1;} virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result) { completedFrame->Release(); return S_OK; } virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped (){ return S_OK; } //virtual HRESULT RenderAudioSamples (bool preroll); }; class DeckLinkTimecode : public IDeckLinkTimecode{ BMDTimecodeBCD timecode; public: DeckLinkTimecode() : timecode(0) {} /* IDeckLinkTimecode */ virtual BMDTimecodeBCD STDMETHODCALLTYPE GetBCD (void) { return timecode; } virtual HRESULT STDMETHODCALLTYPE GetComponents (/* out */ uint8_t *hours, /* out */ uint8_t *minutes, /* out */ uint8_t *seconds, /* out */ uint8_t *frames) { *frames = (timecode & 0xf) + ((timecode & 0xf0) >> 4) * 10; *seconds = ((timecode & 0xf00) >> 8) + ((timecode & 0xf000) >> 12) * 10; *minutes = ((timecode & 0xf0000) >> 16) + ((timecode & 0xf00000) >> 20) * 10; *hours = ((timecode & 0xf000000) >> 24) + ((timecode & 0xf0000000) >> 28) * 10; return S_OK; } virtual HRESULT STDMETHODCALLTYPE GetString (/* out */ STRING *timecode) { return E_FAIL; } virtual BMDTimecodeFlags STDMETHODCALLTYPE GetFlags (void) { return bmdTimecodeFlagDefault; } virtual HRESULT STDMETHODCALLTYPE GetTimecodeUserBits (/* out */ BMDTimecodeUserBits *userBits) { if (!userBits) return E_POINTER; else return S_OK; } /* IUnknown */ virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *ppv) {return E_NOINTERFACE;} virtual ULONG STDMETHODCALLTYPE AddRef () {return 1;} virtual ULONG STDMETHODCALLTYPE Release () {return 1;} void STDMETHODCALLTYPE SetBCD(BMDTimecodeBCD timecode) { this->timecode = timecode; } }; class DeckLinkFrame; class DeckLinkFrame : public IDeckLinkMutableVideoFrame { long ref; long width; long height; long rawBytes; BMDPixelFormat pixelFormat; char *data; IDeckLinkTimecode *timecode; protected: DeckLinkFrame(long w, long h, long rb, BMDPixelFormat pf); virtual ~DeckLinkFrame(); public: static DeckLinkFrame *Create(long width, long height, long rawBytes, BMDPixelFormat pixelFormat); /* IUnknown */ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**); virtual ULONG STDMETHODCALLTYPE AddRef(); virtual ULONG STDMETHODCALLTYPE Release(); /* IDeckLinkVideoFrame */ long STDMETHODCALLTYPE GetWidth (void); long STDMETHODCALLTYPE GetHeight (void); long STDMETHODCALLTYPE GetRowBytes (void); BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat (void); BMDFrameFlags STDMETHODCALLTYPE GetFlags (void); HRESULT STDMETHODCALLTYPE GetBytes (/* out */ void **buffer); HRESULT STDMETHODCALLTYPE GetTimecode (/* in */ BMDTimecodeFormat format, /* out */ IDeckLinkTimecode **timecode); HRESULT STDMETHODCALLTYPE GetAncillaryData (/* out */ IDeckLinkVideoFrameAncillary **ancillary); /* IDeckLinkMutableVideoFrame */ HRESULT STDMETHODCALLTYPE SetFlags(BMDFrameFlags); HRESULT STDMETHODCALLTYPE SetTimecode(BMDTimecodeFormat, IDeckLinkTimecode*); HRESULT STDMETHODCALLTYPE SetTimecodeFromComponents(BMDTimecodeFormat, uint8_t, uint8_t, uint8_t, uint8_t, BMDTimecodeFlags); HRESULT STDMETHODCALLTYPE SetAncillaryData(IDeckLinkVideoFrameAncillary*); HRESULT STDMETHODCALLTYPE SetTimecodeUserBits(BMDTimecodeFormat, BMDTimecodeUserBits); }; class DeckLink3DFrame : public DeckLinkFrame, public IDeckLinkVideoFrame3DExtensions { private: DeckLink3DFrame(long w, long h, long rb, BMDPixelFormat pf); ~DeckLink3DFrame(); long ref; long width; long height; long rawBytes; BMDPixelFormat pixelFormat; DeckLinkFrame *rightEye; public: static DeckLink3DFrame *Create(long width, long height, long rawBytes, BMDPixelFormat pixelFormat); /* IUnknown */ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**); ULONG STDMETHODCALLTYPE AddRef(); ULONG STDMETHODCALLTYPE Release(); /* IDeckLinkVideoFrame3DExtensions */ BMDVideo3DPackingFormat STDMETHODCALLTYPE Get3DPackingFormat(); HRESULT STDMETHODCALLTYPE GetFrameForRightEye(IDeckLinkVideoFrame**); }; #define DECKLINK_MAGIC DISPLAY_DECKLINK_ID struct device_state { PlaybackDelegate *delegate; IDeckLink *deckLink; IDeckLinkOutput *deckLinkOutput; IDeckLinkMutableVideoFrame *deckLinkFrame; IDeckLinkConfiguration* deckLinkConfiguration; }; struct state_decklink { uint32_t magic; struct timeval tv; struct device_state state[MAX_DEVICES]; BMDTimeValue frameRateDuration; BMDTimeScale frameRateScale; DeckLinkTimecode *timecode; struct audio_frame audio; struct video_frame *frame; unsigned long int frames; unsigned long int frames_last; bool stereo; bool initialized; bool emit_timecode; int devices_cnt; unsigned int play_audio:1; int output_audio_channel_count; BMDPixelFormat pixelFormat; enum link link; /* * Fast mode means partially incorrect moder when there are data * continually written to same video frame instaed to separate ones. */ bool fast; }; static void show_help(void); static void show_help(void) { IDeckLinkIterator* deckLinkIterator; IDeckLink* deckLink; int numDevices = 0; HRESULT result; printf("Decklink (output) options:\n"); printf("\t-d decklink:[:timecode][:3G|:dual-link][:3D[:HDMI3DPacking=]][:fast]\n"); printf("\t\tcoma-separated numbers of output devices\n"); // Create an IDeckLinkIterator object to enumerate all DeckLink cards in the system #ifdef WIN32 result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void **) &deckLinkIterator); if (FAILED(result)) #else deckLinkIterator = CreateDeckLinkIteratorInstance(); if (deckLinkIterator == NULL) #endif { fprintf(stderr, "\nA DeckLink iterator could not be created. The DeckLink drivers may not be installed or are outdated.\n"); fprintf(stderr, "This UltraGrid version was compiled with DeckLink drivers %s. You should have at least this version.\n\n", BLACKMAGIC_DECKLINK_API_VERSION_STRING); return; } // Enumerate all cards in this system while (deckLinkIterator->Next(&deckLink) == S_OK) { STRING deviceNameString = NULL; const char *deviceNameCString; // *** Print the model name of the DeckLink card result = deckLink->GetModelName((STRING *) &deviceNameString); #ifdef HAVE_MACOSX deviceNameCString = (char *) malloc(128); CFStringGetCString(deviceNameString, (char *) deviceNameCString, 128, kCFStringEncodingMacRoman); #elif WIN32 deviceNameCString = (char *) malloc(128); wcstombs((char *) deviceNameCString, deviceNameString, 128); #else deviceNameCString = deviceNameString; #endif if (result == S_OK) { printf("\ndevice: %d.) %s \n\n",numDevices, deviceNameCString); print_output_modes(deckLink); #ifdef HAVE_MACOSX CFRelease(deviceNameString); #endif free((void *)deviceNameCString); } // Increment the total number of DeckLink cards found numDevices++; // Release the IDeckLink instance when we've finished with it to prevent leaks deckLink->Release(); } deckLinkIterator->Release(); // If no DeckLink cards were found in the system, inform the user if (numDevices == 0) { printf("\nNo Blackmagic Design devices were found.\n"); return; } printf("\nHDMI 3D packing can be one of following (optional for HDMI 1.4, mandatory for pre-1.4 HDMI):\n"); printf("\tSideBySideHalf\n"); printf("\tLineByLine\n"); printf("\tTopAndBottom\n"); printf("\tFramePacking\n"); printf("\tLeftOnly\n"); printf("\tRightOnly\n"); printf("\n"); printf("Fast mode has lower latency at the expense of incorrect displaying (frame rewriting).\n"); printf("\n"); } struct video_frame * display_decklink_getf(void *state) { struct state_decklink *s = (struct state_decklink *)state; assert(s->magic == DECKLINK_MAGIC); if (s->initialized) { if(s->stereo) { IDeckLinkVideoFrame *deckLinkFrameRight; assert(s->devices_cnt == 1); if(!s->fast) { s->state[0].deckLinkFrame = DeckLink3DFrame::Create(s->frame->tiles[0].width, s->frame->tiles[0].height, s->frame->tiles[0].linesize, s->pixelFormat); } s->state[0].deckLinkFrame->GetBytes((void **) &s->frame->tiles[0].data); dynamic_cast(s->state[0].deckLinkFrame)->GetFrameForRightEye(&deckLinkFrameRight); deckLinkFrameRight->GetBytes((void **) &s->frame->tiles[1].data); // release immedieatelly (parent still holds the reference) deckLinkFrameRight->Release(); } else { for(int i = 0; i < s->devices_cnt; ++i) { if(!s->fast) { s->state[i].deckLinkFrame = DeckLinkFrame::Create(s->frame->tiles[0].width, s->frame->tiles[0].height, s->frame->tiles[0].linesize, s->pixelFormat); } s->state[i].deckLinkFrame->GetBytes((void **) &s->frame->tiles[i].data); } } } /* stub -- real frames are taken with get_sub_frame call */ return s->frame; } static void update_timecode(DeckLinkTimecode *tc, double fps) { const float epsilon = 0.005; int shifted; uint8_t hours, minutes, seconds, frames; BMDTimecodeBCD bcd; bool dropFrame = false; if(ceil(fps) - fps > epsilon) { /* NTSCi drop framecode */ dropFrame = true; } tc->GetComponents (&hours, &minutes, &seconds, &frames); frames++; if((double) frames > fps - epsilon) { frames = 0; seconds++; if(seconds >= 60) { seconds = 0; minutes++; if(dropFrame) { if(minutes % 10 != 0) seconds = 2; } if(minutes >= 60) { minutes = 0; hours++; if(hours >= 24) { hours = 0; } } } } bcd = (frames % 10) | (frames / 10) << 4 | (seconds % 10) << 8 | (seconds / 10) << 12 | (minutes % 10) << 16 | (minutes / 10) << 20 | (hours % 10) << 24 | (hours / 10) << 28; tc->SetBCD(bcd); } int display_decklink_putf(void *state, struct video_frame *frame) { int tmp; struct state_decklink *s = (struct state_decklink *)state; struct timeval tv; UNUSED(frame); assert(s->magic == DECKLINK_MAGIC); gettimeofday(&tv, NULL); #ifdef WIN32 long unsigned int i; #else uint32_t i; #endif s->state[0].deckLinkOutput->GetBufferedVideoFrameCount(&i); //if (i > 2) if (0) fprintf(stderr, "Frame dropped!\n"); else { for (int j = 0; j < s->devices_cnt; ++j) { if(s->emit_timecode) { s->state[j].deckLinkFrame->SetTimecode( bmdTimecodeRP188Any, s->timecode); } #ifdef DECKLINK_LOW_LATENCY s->state[j].deckLinkOutput->DisplayVideoFrameSync(s->state[j].deckLinkFrame); if(!s->fast) { s->state[j].deckLinkFrame->Release(); } #else s->state[j].deckLinkOutput->ScheduleVideoFrame(s->state[j].deckLinkFrame, s->frames * s->frameRateDuration, s->frameRateDuration, s->frameRateScale); #endif /* DECKLINK_LOW_LATENCY */ if(!s->fast) { s->state[j].deckLinkFrame = NULL; } } s->frames++; if(s->emit_timecode) { update_timecode(s->timecode, s->frame->fps); } } gettimeofday(&tv, NULL); double seconds = tv_diff(tv, s->tv); if (seconds > 5) { double fps = (s->frames - s->frames_last) / seconds; fprintf(stdout, "[Decklink display] %lu frames in %g seconds = %g FPS\n", s->frames - s->frames_last, seconds, fps); s->tv = tv; s->frames_last = s->frames; } return TRUE; } static BMDDisplayMode get_mode(IDeckLinkOutput *deckLinkOutput, struct video_desc desc, BMDTimeValue *frameRateDuration, BMDTimeScale *frameRateScale) { IDeckLinkDisplayModeIterator *displayModeIterator; IDeckLinkDisplayMode* deckLinkDisplayMode; BMDDisplayMode displayMode = bmdModeUnknown; // Populate the display mode combo with a list of display modes supported by the installed DeckLink card if (FAILED(deckLinkOutput->GetDisplayModeIterator(&displayModeIterator))) { fprintf(stderr, "Fatal: cannot create display mode iterator [decklink].\n"); return (BMDDisplayMode) -1; } while (displayModeIterator->Next(&deckLinkDisplayMode) == S_OK) { STRING modeNameString; const char *modeNameCString; if (deckLinkDisplayMode->GetName(&modeNameString) == S_OK) { #ifdef HAVE_MACOSX modeNameCString = (char *) malloc(128); CFStringGetCString(modeNameString, (char *) modeNameCString, 128, kCFStringEncodingMacRoman); #elif defined WIN32 modeNameCString = (char *) malloc(128); wcstombs((char *) modeNameCString, modeNameString, 128); #else modeNameCString = modeNameString; #endif if (deckLinkDisplayMode->GetWidth() == desc.width && deckLinkDisplayMode->GetHeight() == desc.height) { double displayFPS; BMDFieldDominance dominance; bool interlaced; dominance = deckLinkDisplayMode->GetFieldDominance(); if (dominance == bmdLowerFieldFirst || dominance == bmdUpperFieldFirst) interlaced = true; else // progressive, psf, unknown interlaced = false; deckLinkDisplayMode->GetFrameRate(frameRateDuration, frameRateScale); displayFPS = (double) *frameRateScale / *frameRateDuration; if(fabs(desc.fps - displayFPS) < 0.01 && (desc.interlacing == INTERLACED_MERGED ? interlaced : !interlaced) ) { printf("DeckLink - selected mode: %s\n", modeNameCString); displayMode = deckLinkDisplayMode->GetDisplayMode(); break; } } } } displayModeIterator->Release(); return displayMode; } int display_decklink_reconfigure(void *state, struct video_desc desc) { struct state_decklink *s = (struct state_decklink *)state; bool modeFound = false; BMDDisplayMode displayMode; BMDDisplayModeSupport supported; int h_align = 0; assert(s->magic == DECKLINK_MAGIC); s->frame->color_spec = desc.color_spec; s->frame->interlacing = desc.interlacing; s->frame->fps = desc.fps; switch (desc.color_spec) { case UYVY: s->pixelFormat = bmdFormat8BitYUV; break; case v210: s->pixelFormat = bmdFormat10BitYUV; break; case RGBA: s->pixelFormat = bmdFormat8BitBGRA; break; default: fprintf(stderr, "[DeckLink] Unsupported pixel format!\n"); } for(int i = 0; i < s->devices_cnt; ++i) { s->state[0].deckLinkOutput->StopScheduledPlayback (0, NULL, 0); } if(s->stereo) { for (int i = 0; i < 2; ++i) { struct tile *tile = vf_get_tile(s->frame, i); tile->width = desc.width; tile->height = desc.height; tile->linesize = vc_get_linesize(tile->width, s->frame->color_spec); tile->data_len = tile->linesize * tile->height; } displayMode = get_mode(s->state[0].deckLinkOutput, desc, &s->frameRateDuration, &s->frameRateScale); if(displayMode == (BMDDisplayMode) -1) goto error; s->state[0].deckLinkOutput->DoesSupportVideoMode(displayMode, s->pixelFormat, bmdVideoOutputDualStream3D, &supported, NULL); if(supported == bmdDisplayModeNotSupported) { fprintf(stderr, "[decklink] Requested parameters combination not supported - %dx%d@%f.\n", desc.width, desc.height, (double)desc.fps); goto error; } if(s->fast) { if(s->state[0].deckLinkFrame) { s->state[0].deckLinkFrame->Release(); } s->state[0].deckLinkFrame = DeckLink3DFrame::Create(s->frame->tiles[0].width, s->frame->tiles[0].height, s->frame->tiles[0].linesize, s->pixelFormat); } s->state[0].deckLinkOutput->EnableVideoOutput(displayMode, bmdVideoOutputDualStream3D); #ifndef DECKLINK_LOW_LATENCY s->state[0].deckLinkOutput->StartScheduledPlayback(0, s->frameRateScale, (double) s->frameRateDuration); #endif /* ! defined DECKLINK_LOW_LATENCY */ } else { if(desc.tile_count > s->devices_cnt) { fprintf(stderr, "[decklink] Expected at most %d streams. Got %d.\n", s->devices_cnt, desc.tile_count); goto error; } for(int i = 0; i < s->devices_cnt; ++i) { BMDVideoOutputFlags outputFlags= bmdVideoOutputFlagDefault; struct tile *tile = vf_get_tile(s->frame, i); tile->width = desc.width; tile->height = desc.height; tile->linesize = vc_get_linesize(tile->width, s->frame->color_spec); tile->data_len = tile->linesize * tile->height; displayMode = get_mode(s->state[i].deckLinkOutput, desc, &s->frameRateDuration, &s->frameRateScale); if(displayMode == (BMDDisplayMode) -1) goto error; if(s->emit_timecode) { outputFlags = bmdVideoOutputRP188; } s->state[i].deckLinkOutput->DoesSupportVideoMode(displayMode, s->pixelFormat, outputFlags, &supported, NULL); if(supported == bmdDisplayModeNotSupported) { fprintf(stderr, "[decklink] Requested parameters " "combination not supported - %d * %dx%d@%f, timecode %s.\n", desc.tile_count, tile->width, tile->height, desc.fps, (outputFlags & bmdVideoOutputRP188 ? "ON": "OFF")); goto error; } if(s->fast) { if(s->state[i].deckLinkFrame) { s->state[i].deckLinkFrame->Release(); } s->state[i].deckLinkFrame = DeckLinkFrame::Create(s->frame->tiles[0].width, s->frame->tiles[0].height, s->frame->tiles[0].linesize, s->pixelFormat); } s->state[i].deckLinkOutput->EnableVideoOutput(displayMode, outputFlags); } for(int i = 0; i < s->devices_cnt; ++i) { #ifndef DECKLINK_LOW_LATENCY s->state[i].deckLinkOutput->StartScheduledPlayback(0, s->frameRateScale, (double) s->frameRateDuration); #endif /* ! defined DECKLINK_LOW_LATENCY */ } } s->initialized = true; return TRUE; error: return FALSE; } static int blackmagic_api_version_check(STRING *current_version) { int ret = TRUE; *current_version = NULL; IDeckLinkAPIInformation *APIInformation = NULL; HRESULT result; #ifdef WIN32 result = CoCreateInstance(CLSID_CDeckLinkAPIInformation, NULL, CLSCTX_ALL, IID_IDeckLinkAPIInformation, (void **) &APIInformation); if(FAILED(result)) { #else APIInformation = CreateDeckLinkAPIInformationInstance(); if(APIInformation == NULL) { #endif return FALSE; } int64_t value; HRESULT res; res = APIInformation->GetInt(BMDDeckLinkAPIVersion, &value); if(res != S_OK) { APIInformation->Release(); return FALSE; } if(BLACKMAGIC_DECKLINK_API_VERSION > value) { // this is safe comparision, for internal structure please see SDK documentation APIInformation->GetString(BMDDeckLinkAPIVersion, current_version); ret = FALSE; } APIInformation->Release(); return ret; } void *display_decklink_init(char *fmt, unsigned int flags) { struct state_decklink *s; IDeckLinkIterator* deckLinkIterator; HRESULT result; int cardIdx[MAX_DEVICES]; int dnum = 0; IDeckLinkConfiguration* deckLinkConfiguration = NULL; // for Decklink Studio which has switchable XLR - analog 3 and 4 or AES/EBU 3,4 and 5,6 BMDAudioOutputAnalogAESSwitch audioConnection = (BMDAudioOutputAnalogAESSwitch) 0; BMDVideo3DPackingFormat HDMI3DPacking = (BMDVideo3DPackingFormat) 0; #ifndef WIN32 // Initialize COM on this thread if(FAILED(result)) { fprintf(stderr, "Initialize of COM failed - result = " "08x.\n", result); return NULL; } #endif // WIN32 STRING current_version; if(!blackmagic_api_version_check(¤t_version)) { fprintf(stderr, "\nThe DeckLink drivers may not be installed or are outdated.\n"); fprintf(stderr, "This UltraGrid version was compiled against DeckLink drivers %s. You should have at least this version.\n\n", BLACKMAGIC_DECKLINK_API_VERSION_STRING); fprintf(stderr, "Vendor download page is http://http://www.blackmagic-design.com/support/ \n"); if(current_version) { const char *currentVersionCString; #ifdef HAVE_MACOSX currentVersionCString = (char *) malloc(128); CFStringGetCString(current_version, (char *) currentVersionCString, 128, kCFStringEncodingMacRoman); #elif defined WIN32 currentVersionCString = (char *) malloc(128); wcstombs((char *) currentVersionCString, current_version, 128); #else currentVersionCString = current_version; #endif fprintf(stderr, "Currently installed version is: %s\n", currentVersionCString); #ifdef HAVE_MACOSX CFRelease(current_version); #endif free((void *)currentVersionCString); } else { fprintf(stderr, "No installed drivers detected\n"); } fprintf(stderr, "\n"); return NULL; } s = (struct state_decklink *)calloc(1, sizeof(struct state_decklink)); s->magic = DECKLINK_MAGIC; s->stereo = FALSE; s->emit_timecode = false; s->link = LINK_UNSPECIFIED; s->fast = false; if(fmt == NULL) { cardIdx[0] = 0; s->devices_cnt = 1; fprintf(stderr, "Card number unset, using first found (see -d decklink:help)!\n"); } else if (strcmp(fmt, "help") == 0) { show_help(); return NULL; } else { char *tmp = strdup(fmt); char *ptr; char *saveptr1 = 0ul, *saveptr2 = 0ul; ptr = strtok_r(tmp, ":", &saveptr1); char *devices = strdup(ptr); s->devices_cnt = 0; ptr = strtok_r(devices, ",", &saveptr2); do { cardIdx[s->devices_cnt] = atoi(ptr); ++s->devices_cnt; } while ((ptr = strtok_r(NULL, ",", &saveptr2))); free(devices); while((ptr = strtok_r(NULL, ":", &saveptr1))) { if(strcasecmp(ptr, "3D") == 0) { s->stereo = true; } else if(strcasecmp(ptr, "timecode") == 0) { s->emit_timecode = true; } else if(strcasecmp(ptr, "3G") == 0) { s->link = LINK_3G; } else if(strcasecmp(ptr, "dual-link") == 0) { s->link = LINK_DUAL; } else if(strncasecmp(ptr, "HDMI3DPacking=", strlen("HDMI3DPacking=")) == 0) { char *packing = ptr + strlen("HDMI3DPacking="); if(strcasecmp(packing, "SideBySideHalf") == 0) { HDMI3DPacking = bmdVideo3DPackingSidebySideHalf; } else if(strcasecmp(packing, "LineByLine") == 0) { HDMI3DPacking = bmdVideo3DPackingLinebyLine; } else if(strcasecmp(packing, "TopAndBottom") == 0) { HDMI3DPacking = bmdVideo3DPackingTopAndBottom; } else if(strcasecmp(packing, "FramePacking") == 0) { HDMI3DPacking = bmdVideo3DPackingFramePacking; } else if(strcasecmp(packing, "LeftOnly") == 0) { HDMI3DPacking = bmdVideo3DPackingRightOnly; } else if(strcasecmp(packing, "RightOnly") == 0) { HDMI3DPacking = bmdVideo3DPackingLeftOnly; } else { fprintf(stderr, "[DeckLink] Warning: HDMI 3D packing %s.\n", packing); } } else if(strcasecmp(ptr, "fast") == 0) { s->fast = true; } else { fprintf(stderr, "[DeckLink] Warning: unknown options in config string.\n"); } } free (tmp); } assert(!s->stereo || s->devices_cnt == 1); gettimeofday(&s->tv, NULL); // Initialize the DeckLink API #ifdef WIN32 result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void **) &deckLinkIterator); if (FAILED(result)) #else deckLinkIterator = CreateDeckLinkIteratorInstance(); if (!deckLinkIterator) #endif { fprintf(stderr, "\nA DeckLink iterator could not be created. The DeckLink drivers may not be installed or are outdated.\n"); fprintf(stderr, "This UltraGrid version was compiled with DeckLink drivers %s. You should have at least this version.\n\n", BLACKMAGIC_DECKLINK_API_VERSION_STRING); return NULL; } for(int i = 0; i < s->devices_cnt; ++i) { s->state[i].delegate = NULL; s->state[i].deckLink = NULL; s->state[i].deckLinkOutput = NULL; s->state[i].deckLinkFrame = NULL; s->state[i].deckLinkConfiguration = NULL; } // Connect to the first DeckLink instance IDeckLink *deckLink; while (deckLinkIterator->Next(&deckLink) == S_OK) { bool found = false; for(int i = 0; i < s->devices_cnt; ++i) { if (dnum == cardIdx[i]){ s->state[i].deckLink = deckLink; found = true; } } if(!found && deckLink != NULL) deckLink->Release(); dnum++; } for(int i = 0; i < s->devices_cnt; ++i) { if(s->state[i].deckLink == NULL) { fprintf(stderr, "No DeckLink PCI card #%d found\n", cardIdx[i]); return NULL; } } if(flags & (DISPLAY_FLAG_AUDIO_EMBEDDED | DISPLAY_FLAG_AUDIO_AESEBU | DISPLAY_FLAG_AUDIO_ANALOG)) { s->play_audio = TRUE; switch(flags & (DISPLAY_FLAG_AUDIO_EMBEDDED | DISPLAY_FLAG_AUDIO_AESEBU | DISPLAY_FLAG_AUDIO_ANALOG)) { case DISPLAY_FLAG_AUDIO_EMBEDDED: audioConnection = (BMDAudioOutputAnalogAESSwitch) 0; break; case DISPLAY_FLAG_AUDIO_AESEBU: audioConnection = bmdAudioOutputSwitchAESEBU; break; case DISPLAY_FLAG_AUDIO_ANALOG: audioConnection = bmdAudioOutputSwitchAnalog; break; default: fprintf(stderr, "[Decklink display] [Fatal] Unsupporetd audio connection.\n"); abort(); } s->audio.data = NULL; } else { s->play_audio = FALSE; } if(s->stereo) { s->frame = vf_alloc(2); } else { s->frame = vf_alloc(s->devices_cnt); } if(s->emit_timecode) { s->timecode = new DeckLinkTimecode; } else { s->timecode = NULL; } for(int i = 0; i < s->devices_cnt; ++i) { // Obtain the audio/video output interface (IDeckLinkOutput) if (s->state[i].deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&s->state[i].deckLinkOutput) != S_OK) { if(s->state[i].deckLinkOutput != NULL) s->state[i].deckLinkOutput->Release(); s->state[i].deckLink->Release(); return NULL; } // Query the DeckLink for its configuration interface result = s->state[i].deckLink->QueryInterface(IID_IDeckLinkConfiguration, (void**)&deckLinkConfiguration); s->state[i].deckLinkConfiguration = deckLinkConfiguration; if (result != S_OK) { printf("Could not obtain the IDeckLinkConfiguration interface: %08x\n", (int) result); return NULL; } #ifdef DECKLINK_LOW_LATENCY HRESULT res = deckLinkConfiguration->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true); if(res != S_OK) { fprintf(stderr, "[DeckLink display] Unable to set to low-latency mode.\n"); } #endif /* DECKLINK_LOW_LATENCY */ if(HDMI3DPacking != 0) { HRESULT res = deckLinkConfiguration->SetInt(bmdDeckLinkConfigHDMI3DPackingFormat, HDMI3DPacking); if(res != S_OK) { fprintf(stderr, "[DeckLink display] Unable set 3D packing.\n"); } } if(s->link != LINK_UNSPECIFIED) { HRESULT res = deckLinkConfiguration->SetFlag(bmdDeckLinkConfig3GBpsVideoOutput, s->link == LINK_3G ? true : false); if(res != S_OK) { fprintf(stderr, "[DeckLink display] Unable set output SDI standard.\n"); } } if(s->play_audio == FALSE || i != 0) { //TODO: figure out output from multiple streams s->state[i].deckLinkOutput->DisableAudioOutput(); } else { /* Actually no action is required to set audio connection because Blackmagic card plays audio through all its outputs (AES/SDI/analog) .... */ printf("[Decklink playback] Audio output set to: "); switch(audioConnection) { case 0: printf("embedded"); break; case bmdAudioOutputSwitchAESEBU: printf("AES/EBU"); break; case bmdAudioOutputSwitchAnalog: printf("analog"); break; } printf(".\n"); /* * .... one exception is a card that has switchable cables between AES/EBU and analog. (But this applies only for channels 3 and above.) */ if (audioConnection != 0) { // not embedded HRESULT res = deckLinkConfiguration->SetInt(bmdDeckLinkConfigAudioOutputAESAnalogSwitch, audioConnection); if(res == S_OK) { // has switchable channels printf("[Decklink playback] Card with switchable audio channels detected. Switched to correct format.\n"); } else if(res == E_NOTIMPL) { // normal case - without switchable channels } else { fprintf(stderr, "[Decklink playback] Unable to switch audio output for channels 3 or above although \n" "card shall support it. Check if it is ok. Continuing anyway.\n"); } } } s->state[i].delegate = new PlaybackDelegate(s, i); // Provide this class as a delegate to the audio and video output interfaces #ifndef DECKLINK_LOW_LATENCY if(!s->fast) { s->state[i].deckLinkOutput->SetScheduledFrameCompletionCallback(s->state[i].delegate); } #endif /* ! defined DECKLINK_LOW_LATENCY */ //s->state[i].deckLinkOutput->DisableAudioOutput(); } s->frames = 0; s->initialized = false; return (void *)s; } void display_decklink_run(void *state) { UNUSED(state); } void display_decklink_finish(void *state) { UNUSED(state); } void display_decklink_done(void *state) { debug_msg("display_decklink_done\n"); /* TOREMOVE */ struct state_decklink *s = (struct state_decklink *)state; HRESULT result; assert (s != NULL); delete s->timecode; for (int i = 0; i < s->devices_cnt; ++i) { result = s->state[i].deckLinkOutput->StopScheduledPlayback (0, NULL, 0); if (result != S_OK) { fprintf(stderr, MODULE_NAME "Cannot stop playback: %08x\n", (int) result); } if(s->play_audio && i == 0) { result = s->state[i].deckLinkOutput->DisableAudioOutput(); if (result != S_OK) { fprintf(stderr, MODULE_NAME "Could disable audio output: %08x\n", (int) result); } } result = s->state[i].deckLinkOutput->DisableVideoOutput(); if (result != S_OK) { fprintf(stderr, MODULE_NAME "Could disable video output: %08x\n", (int) result); } if(s->state[i].deckLinkConfiguration != NULL) { s->state[i].deckLinkConfiguration->Release(); } if(s->state[i].deckLinkOutput != NULL) { s->state[i].deckLinkOutput->Release(); } if(s->state[i].deckLinkFrame != NULL) { s->state[i].deckLinkFrame->Release(); } if(s->state[i].deckLink != NULL) { s->state[i].deckLink->Release(); } if(s->state[i].delegate != NULL) { delete s->state[i].delegate; } } vf_free(s->frame); free(s); } display_type_t *display_decklink_probe(void) { display_type_t *dtype; dtype = (display_type_t *) malloc(sizeof(display_type_t)); if (dtype != NULL) { dtype->id = DISPLAY_DECKLINK_ID; dtype->name = "decklink"; dtype->description = "Blackmagick DeckLink card"; } return dtype; } int display_decklink_get_property(void *state, int property, void *val, size_t *len) { struct state_decklink *s = (struct state_decklink *)state; codec_t codecs[] = {v210, UYVY, RGBA}; switch (property) { case DISPLAY_PROPERTY_CODECS: if(sizeof(codecs) <= *len) { memcpy(val, codecs, sizeof(codecs)); } else { return FALSE; } *len = sizeof(codecs); break; case DISPLAY_PROPERTY_RSHIFT: *(int *) val = 16; *len = sizeof(int); break; case DISPLAY_PROPERTY_GSHIFT: *(int *) val = 8; *len = sizeof(int); break; case DISPLAY_PROPERTY_BSHIFT: *(int *) val = 0; *len = sizeof(int); break; case DISPLAY_PROPERTY_BUF_PITCH: *(int *) val = PITCH_DEFAULT; *len = sizeof(int); break; case DISPLAY_PROPERTY_VIDEO_MODE: if(s->devices_cnt == 1 && !s->stereo) *(int *) val = DISPLAY_PROPERTY_VIDEO_MERGED; else *(int *) val = DISPLAY_PROPERTY_VIDEO_SEPARATE_TILES; break; default: return FALSE; } return TRUE; } PlaybackDelegate::PlaybackDelegate (struct state_decklink * owner, int index) : s(owner), i(index) { } /* * AUDIO */ struct audio_frame * display_decklink_get_audio_frame(void *state) { struct state_decklink *s = (struct state_decklink *)state; if(!s->play_audio) return NULL; return &s->audio; } void display_decklink_put_audio_frame(void *state, struct audio_frame *frame) { struct state_decklink *s = (struct state_decklink *)state; unsigned int sampleFrameCount = s->audio.data_len / (s->audio.bps * s->audio.ch_count); #ifdef WIN32 unsigned long int sampleFramesWritten; #else unsigned int sampleFramesWritten; #endif /* we got probably count that cannot be played directly (probably 1) */ if(s->output_audio_channel_count != s->audio.ch_count) { assert(s->audio.ch_count == 1); /* only reasonable value so far */ if (sampleFrameCount * s->output_audio_channel_count * frame->bps > frame->max_size) { fprintf(stderr, "[decklink] audio buffer overflow!\n"); sampleFrameCount = frame->max_size / (s->output_audio_channel_count * frame->bps); frame->data_len = sampleFrameCount * (frame->ch_count * frame->bps); } audio_frame_multiply_channel(frame, s->output_audio_channel_count); } s->state[0].deckLinkOutput->ScheduleAudioSamples (s->audio.data, sampleFrameCount, 0, 0, &sampleFramesWritten); if(sampleFramesWritten != sampleFrameCount) fprintf(stderr, "[decklink] audio buffer underflow!\n"); } int display_decklink_reconfigure_audio(void *state, int quant_samples, int channels, int sample_rate) { struct state_decklink *s = (struct state_decklink *)state; BMDAudioSampleType sample_type; if(s->audio.data != NULL) free(s->audio.data); s->audio.bps = quant_samples / 8; s->audio.sample_rate = sample_rate; s->output_audio_channel_count = s->audio.ch_count = channels; if (s->audio.ch_count != 1 && s->audio.ch_count != 2 && s->audio.ch_count != 8 && s->audio.ch_count != 16) { fprintf(stderr, "[decklink] requested channel count isn't supported: " "%d\n", s->audio.ch_count); s->play_audio = FALSE; return FALSE; } /* toggle one channel to supported two */ if(s->audio.ch_count == 1) { s->output_audio_channel_count = 2; } if((quant_samples != 16 && quant_samples != 32) || sample_rate != 48000) { fprintf(stderr, "[decklink] audio format isn't supported: " "samples: %d, sample rate: %d\n", quant_samples, sample_rate); s->play_audio = FALSE; return FALSE; } switch(quant_samples) { case 16: sample_type = bmdAudioSampleType16bitInteger; break; case 32: sample_type = bmdAudioSampleType32bitInteger; break; default: return FALSE; } s->state[0].deckLinkOutput->EnableAudioOutput(bmdAudioSampleRate48kHz, sample_type, s->output_audio_channel_count, bmdAudioOutputStreamContinuous); s->state[0].deckLinkOutput->StartScheduledPlayback(0, s->frameRateScale, s->frameRateDuration); s->audio.max_size = 5 * (quant_samples / 8) * s->audio.ch_count * sample_rate; s->audio.data = (char *) malloc (s->audio.max_size); return TRUE; } #ifndef WIN32 bool operator==(const REFIID & first, const REFIID & second){ return (first.byte0 == second.byte0) && (first.byte1 == second.byte1) && (first.byte2 == second.byte2) && (first.byte3 == second.byte3) && (first.byte4 == second.byte4) && (first.byte5 == second.byte5) && (first.byte6 == second.byte6) && (first.byte7 == second.byte7) && (first.byte8 == second.byte8) && (first.byte9 == second.byte9) && (first.byte10 == second.byte10) && (first.byte11 == second.byte11) && (first.byte12 == second.byte12) && (first.byte13 == second.byte13) && (first.byte14 == second.byte14) && (first.byte15 == second.byte15); } #endif HRESULT DeckLinkFrame::QueryInterface(REFIID id, void**frame) { return E_NOINTERFACE; } ULONG DeckLinkFrame::AddRef() { return ++ref; } ULONG DeckLinkFrame::Release() { if(--ref == 0) delete this; return ref; } DeckLinkFrame::DeckLinkFrame(long w, long h, long rb, BMDPixelFormat pf) : width(w), height(h), rawBytes(rb), pixelFormat(pf), ref(1l) { data = new char[rb * h]; timecode = NULL; } DeckLinkFrame *DeckLinkFrame::Create(long width, long height, long rawBytes, BMDPixelFormat pixelFormat) { return new DeckLinkFrame(width, height, rawBytes, pixelFormat); } DeckLinkFrame::~DeckLinkFrame() { delete[] data; } long DeckLinkFrame::GetWidth () { return width; } long DeckLinkFrame::GetHeight () { return height; } long DeckLinkFrame::GetRowBytes () { return rawBytes; } BMDPixelFormat DeckLinkFrame::GetPixelFormat () { return pixelFormat; } BMDFrameFlags DeckLinkFrame::GetFlags () { return bmdFrameFlagDefault; } HRESULT DeckLinkFrame::GetBytes (/* out */ void **buffer) { *buffer = static_cast(data); return S_OK; } HRESULT DeckLinkFrame::GetTimecode (/* in */ BMDTimecodeFormat format, /* out */ IDeckLinkTimecode **timecode) { *timecode = dynamic_cast(this->timecode); return S_OK; } HRESULT DeckLinkFrame::GetAncillaryData (/* out */ IDeckLinkVideoFrameAncillary **ancillary) { return S_FALSE; } /* IDeckLinkMutableVideoFrame */ HRESULT DeckLinkFrame::SetFlags (/* in */ BMDFrameFlags newFlags) { return E_FAIL; } HRESULT DeckLinkFrame::SetTimecode (/* in */ BMDTimecodeFormat format, /* in */ IDeckLinkTimecode *timecode) { if(this->timecode) this->timecode->Release(); this->timecode = timecode; return S_OK; } HRESULT DeckLinkFrame::SetTimecodeFromComponents (/* in */ BMDTimecodeFormat format, /* in */ uint8_t hours, /* in */ uint8_t minutes, /* in */ uint8_t seconds, /* in */ uint8_t frames, /* in */ BMDTimecodeFlags flags) { return E_FAIL; } HRESULT DeckLinkFrame::SetAncillaryData (/* in */ IDeckLinkVideoFrameAncillary *ancillary) { return E_FAIL; } HRESULT DeckLinkFrame::SetTimecodeUserBits (/* in */ BMDTimecodeFormat format, /* in */ BMDTimecodeUserBits userBits) { return E_FAIL; } DeckLink3DFrame::DeckLink3DFrame(long w, long h, long rb, BMDPixelFormat pf) : DeckLinkFrame(w, h, rb, pf), ref(1l) { rightEye = DeckLinkFrame::Create(w, h, rb, pf); } DeckLink3DFrame *DeckLink3DFrame::Create(long width, long height, long rawBytes, BMDPixelFormat pixelFormat) { DeckLink3DFrame *frame = new DeckLink3DFrame(width, height, rawBytes, pixelFormat); return frame; } DeckLink3DFrame::~DeckLink3DFrame() { rightEye->Release(); } ULONG DeckLink3DFrame::AddRef() { return DeckLinkFrame::AddRef(); } ULONG DeckLink3DFrame::Release() { return DeckLinkFrame::Release(); } HRESULT DeckLink3DFrame::QueryInterface(REFIID id, void**frame) { HRESULT result = E_NOINTERFACE; if(id == IID_IDeckLinkVideoFrame3DExtensions) { this->AddRef(); *frame = dynamic_cast(this); result = S_OK; } return result; } BMDVideo3DPackingFormat DeckLink3DFrame::Get3DPackingFormat() { return bmdVideo3DPackingLeftOnly; } HRESULT DeckLink3DFrame::GetFrameForRightEye(IDeckLinkVideoFrame ** frame) { *frame = rightEye; rightEye->AddRef(); return S_OK; }