]> git.sesse.net Git - vlc/blob - modules/video_output/decklink.cpp
Decklink output
[vlc] / modules / video_output / decklink.cpp
1 /*****************************************************************************
2  * decklink.cpp: BlackMagic DeckLink SDI output module
3  *****************************************************************************
4  * Copyright (C) 2012-2013 Rafaël Carré
5  * Copyright (C) 2009 Michael Niedermayer <michaelni@gmx.at>
6  * Copyright (c) 2009 Baptiste Coudurier <baptiste dot coudurier at gmail dot com>
7  *
8  * Authors: Rafaël Carré <funman@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*
26  * TODO:
27  *  - test non stereo audio
28  *  - inherit aout/vout settings from corresponding module
29  *  (allow to change settings between successive runs per instance)
30  *  - allow several instances per process
31  *  - get rid of process-wide destructor
32  */
33
34 #define __STDC_FORMAT_MACROS
35 #define __STDC_CONSTANT_MACROS
36
37 #ifdef HAVE_CONFIG_H
38 # include "config.h"
39 #endif
40
41 #include <stdint.h>
42
43 #include <vlc_common.h>
44 #include <vlc_plugin.h>
45 #include <vlc_threads.h>
46
47 #include <vlc_vout_display.h>
48 #include <vlc_picture_pool.h>
49
50 #include <vlc_block.h>
51 #include <vlc_atomic.h>
52 #include <vlc_aout.h>
53 #include <arpa/inet.h>
54
55 #include <DeckLinkAPI.h>
56 #include <DeckLinkAPIDispatch.cpp>
57
58 #define FRAME_SIZE 1920
59 #define CHANNELS_MAX 6
60
61 #if 0
62 static const int pi_channels_maps[CHANNELS_MAX+1] =
63 {
64     0,
65     AOUT_CHAN_CENTER,
66     AOUT_CHANS_STEREO,
67     AOUT_CHANS_3_0,
68     AOUT_CHANS_4_0,
69     AOUT_CHANS_5_0,
70     AOUT_CHANS_5_1,
71 };
72 #endif
73
74 #define CARD_INDEX_TEXT N_("Output card")
75 #define CARD_INDEX_LONGTEXT N_(\
76     "DeckLink output card, if multiple exist. " \
77     "The cards are numbered from 0.")
78
79 #define MODE_TEXT N_("Desired output mode")
80 #define MODE_LONGTEXT N_(\
81     "Desired output mode for DeckLink output. " \
82     "This value should be a FOURCC code in textual " \
83     "form, e.g. \"ntsc\".")
84
85 #define AUDIO_CONNECTION_TEXT N_("Audio connection")
86 #define AUDIO_CONNECTION_LONGTEXT N_(\
87     "Audio connection for DeckLink output.")
88
89
90 #define RATE_TEXT N_("Audio sampling rate in Hz")
91 #define RATE_LONGTEXT N_(\
92     "Audio sampling rate (in hertz) for DeckLink output. " \
93     "0 disables audio output.")
94
95 #define CHANNELS_TEXT N_("Number of audio channels")
96 #define CHANNELS_LONGTEXT N_(\
97     "Number of output channels for DeckLink output. " \
98     "Must be 2, 8 or 16. 0 disables audio output.")
99
100 #define VIDEO_CONNECTION_TEXT N_("Video connection")
101 #define VIDEO_CONNECTION_LONGTEXT N_(\
102     "Video connection for DeckLink output.")
103
104 #define VIDEO_TENBITS_TEXT N_("10 bits")
105 #define VIDEO_TENBITS_LONGTEXT N_(\
106     "Use 10 bits per pixel for video frames.")
107
108 #define CFG_PREFIX "decklink-output-"
109 #define VIDEO_CFG_PREFIX "decklink-vout-"
110 #define AUDIO_CFG_PREFIX "decklink-aout-"
111
112
113
114 static const char *const ppsz_videoconns[] = {
115     "sdi", "hdmi", "opticalsdi", "component", "composite", "svideo"
116 };
117 static const char *const ppsz_videoconns_text[] = {
118     N_("SDI"), N_("HDMI"), N_("Optical SDI"), N_("Component"), N_("Composite"), N_("S-video")
119 };
120
121 static const char *const ppsz_audioconns[] = {
122     "embedded", "aesebu", "analog"
123 };
124 static const char *const ppsz_audioconns_text[] = {
125     N_("Embedded"), N_("AES/EBU"), N_("Analog")
126 };
127
128
129 struct vout_display_sys_t
130 {
131     picture_pool_t *pool;
132     bool tenbits;
133 };
134
135 /* Only one audio output module and one video output module
136  * can be used per process.
137  * We use a static mutex in audio/video submodules entry points.  */
138 static struct
139 {
140     IDeckLink *p_card;
141     IDeckLinkOutput *p_output;
142     IDeckLinkConfiguration *p_config;
143     IDeckLinkDisplayModeIterator *p_display_iterator;
144     IDeckLinkIterator *decklink_iterator;
145
146     //int i_channels;
147     int i_rate;
148
149     int i_width;
150     int i_height;
151
152     BMDTimeScale timescale;
153     BMDTimeValue frameduration;
154
155     /* XXX: workaround card clock drift */
156     mtime_t offset;
157 } decklink_sys = {
158     NULL, NULL, NULL, NULL, NULL,
159     0, 0,
160     -1, -1,
161     0, 0,
162     0,
163 };
164
165 /*****************************************************************************
166  * Local prototypes.
167  *****************************************************************************/
168
169 static int  OpenVideo           (vlc_object_t *);
170 static void CloseVideo          (vlc_object_t *);
171 static int  OpenAudio           (vlc_object_t *);
172 static void CloseAudio          (vlc_object_t *);
173
174 /*****************************************************************************
175  * Module descriptor
176  *****************************************************************************/
177
178 vlc_module_begin()
179     set_shortname(N_("DecklinkOutput"))
180     set_description(N_("output module to write to Blackmagic SDI card"))
181     set_section(N_("Decklink General Options"), NULL)
182     add_integer(CFG_PREFIX "card-index", 0,
183                 CARD_INDEX_TEXT, CARD_INDEX_LONGTEXT, true)
184
185     add_submodule ()
186     set_description (N_("Decklink Video Output module"))
187     set_category(CAT_VIDEO)
188     set_subcategory(SUBCAT_VIDEO_VOUT)
189     set_capability("vout display", 0)
190     set_callbacks (OpenVideo, CloseVideo)
191     set_section(N_("Decklink Video Options"), NULL)
192     add_string(VIDEO_CFG_PREFIX "video-connection", "sdi",
193                 VIDEO_CONNECTION_TEXT, VIDEO_CONNECTION_LONGTEXT, true)
194                 change_string_list(ppsz_videoconns, ppsz_videoconns_text)
195     add_string(VIDEO_CFG_PREFIX "mode", "pal ",
196                 MODE_TEXT, MODE_LONGTEXT, true)
197     add_bool(VIDEO_CFG_PREFIX "tenbits", false,
198                 VIDEO_TENBITS_TEXT, VIDEO_TENBITS_LONGTEXT, true)
199
200
201     add_submodule ()
202     set_description (N_("Decklink Audio Output module"))
203     set_category(CAT_AUDIO)
204     set_subcategory(SUBCAT_AUDIO_AOUT)
205     set_capability("audio output", 0)
206     set_callbacks (OpenAudio, CloseAudio)
207     set_section(N_("Decklink Audio Options"), NULL)
208     add_string(AUDIO_CFG_PREFIX "audio-connection", "embedded",
209                 AUDIO_CONNECTION_TEXT, AUDIO_CONNECTION_LONGTEXT, true)
210                 change_string_list(ppsz_audioconns, ppsz_audioconns_text)
211     add_integer(AUDIO_CFG_PREFIX "audio-rate", 48000,
212                 RATE_TEXT, RATE_LONGTEXT, true)
213     add_integer(AUDIO_CFG_PREFIX "audio-channels", 2,
214                 CHANNELS_TEXT, CHANNELS_LONGTEXT, true)
215 vlc_module_end ()
216
217 // Connection mode
218 static BMDAudioConnection getAConn(vlc_object_t *p_this)
219 {
220     BMDAudioConnection conn = bmdAudioConnectionEmbedded;
221     char *psz = var_InheritString(p_this, AUDIO_CFG_PREFIX "audio-connection");
222     if (!psz)
223         goto end;
224
225     if (!strcmp(psz, "embedded"))
226         conn = bmdAudioConnectionEmbedded;
227     else if (!strcmp(psz, "aesebu"))
228         conn = bmdAudioConnectionAESEBU;
229     else if (!strcmp(psz, "analog"))
230         conn = bmdAudioConnectionAnalog;
231
232 end:
233     free(psz);
234     return conn;
235 }
236
237 static BMDVideoConnection getVConn(vlc_object_t *p_this)
238 {
239     BMDVideoConnection conn = bmdVideoConnectionSDI;
240     char *psz = var_InheritString(p_this, VIDEO_CFG_PREFIX "video-connection");
241     if (!psz)
242         goto end;
243
244          if (!strcmp(psz, "sdi"))
245         conn = bmdVideoConnectionSDI;
246     else if (!strcmp(psz, "hdmi"))
247         conn = bmdVideoConnectionHDMI;
248     else if (!strcmp(psz, "opticalsdi"))
249         conn = bmdVideoConnectionOpticalSDI;
250     else if (!strcmp(psz, "component"))
251         conn = bmdVideoConnectionComponent;
252     else if (!strcmp(psz, "composite"))
253         conn = bmdVideoConnectionComposite;
254     else if (!strcmp(psz, "svideo"))
255         conn = bmdVideoConnectionSVideo;
256
257 end:
258     free(psz);
259     return conn;
260 }
261
262 /*****************************************************************************
263  *
264  *****************************************************************************/
265
266 static atomic_uint initialized = ATOMIC_VAR_INIT(0);
267
268 static void CloseDecklink(void) __attribute__((destructor));
269 static void CloseDecklink(void)
270 {
271     if (!atomic_load(&initialized))
272         return;
273
274     decklink_sys.p_output->StopScheduledPlayback(0, NULL, 0);
275     decklink_sys.p_output->DisableVideoOutput();
276     decklink_sys.p_output->DisableAudioOutput();
277
278     if (decklink_sys.decklink_iterator)
279         decklink_sys.decklink_iterator->Release();
280
281     if (decklink_sys.p_display_iterator)
282         decklink_sys.p_display_iterator->Release();
283
284     if (decklink_sys.p_config)
285         decklink_sys.p_config->Release();
286
287     if (decklink_sys.p_output)
288         decklink_sys.p_output->Release();
289
290     if (decklink_sys.p_card)
291         decklink_sys.p_card->Release();
292 }
293
294 static int OpenDecklink(vlc_object_t *p_this)
295 {
296     vout_display_t *vd = (vout_display_t *)p_this;
297     vout_display_sys_t *sys = vd->sys;
298 #define CHECK(message) do { \
299     if (result != S_OK) \
300     { \
301         msg_Err(p_this, message ": 0x%X", result); \
302         goto error; \
303     } \
304 } while(0)
305
306     HRESULT result;
307     IDeckLinkDisplayMode *p_display_mode = NULL;
308     static vlc_mutex_t lock = VLC_STATIC_MUTEX;
309
310     vlc_mutex_lock(&lock);
311
312     if (atomic_load(&initialized)) {
313         /* already initialized */
314         vlc_mutex_unlock(&lock);
315         return VLC_SUCCESS;
316     }
317
318     //decklink_sys.i_channels = var_InheritInteger(p_this, AUDIO_CFG_PREFIX "audio-channels");
319     decklink_sys.i_rate = var_InheritInteger(p_this, AUDIO_CFG_PREFIX "audio-rate");
320     int i_card_index = var_InheritInteger(p_this, CFG_PREFIX "card-index");
321     BMDVideoConnection vconn = getVConn(p_this);
322     BMDAudioConnection aconn = getAConn(p_this);
323     char *mode = var_InheritString(p_this, VIDEO_CFG_PREFIX "mode");
324     size_t len = mode ? strlen(mode) : 0;
325     if (!mode || len > 4)
326     {
327         free(mode);
328         msg_Err(p_this, "Missing or invalid mode");
329         goto error;
330     }
331
332     BMDDisplayMode wanted_mode_id;
333     memset(&wanted_mode_id, ' ', 4);
334     strncpy((char*)&wanted_mode_id, mode, 4);
335     free(mode);
336
337     if (i_card_index < 0)
338     {
339         msg_Err(p_this, "Invalid card index %d", i_card_index);
340         goto error;
341     }
342
343     decklink_sys.decklink_iterator = CreateDeckLinkIteratorInstance();
344     if (!decklink_sys.decklink_iterator)
345     {
346         msg_Err(p_this, "DeckLink drivers not found.");
347         goto error;
348     }
349
350     for(int i = 0; i <= i_card_index; ++i)
351     {
352         if (decklink_sys.p_card)
353             decklink_sys.p_card->Release();
354         result = decklink_sys.decklink_iterator->Next(&decklink_sys.p_card);
355         CHECK("Card not found");
356     }
357
358     const char *psz_model_name;
359     result = decklink_sys.p_card->GetModelName(&psz_model_name);
360     CHECK("Unknown model name");
361
362     msg_Dbg(p_this, "Opened DeckLink PCI card %s", psz_model_name);
363
364     result = decklink_sys.p_card->QueryInterface(IID_IDeckLinkOutput,
365         (void**)&decklink_sys.p_output);
366     CHECK("No outputs");
367
368     result = decklink_sys.p_card->QueryInterface(IID_IDeckLinkConfiguration,
369         (void**)&decklink_sys.p_config);
370     CHECK("Could not get config interface");
371
372     if (vconn)
373     {
374         result = decklink_sys.p_config->SetInt(
375             bmdDeckLinkConfigVideoOutputConnection, vconn);
376         CHECK("Could not set video output connection");
377     }
378
379     if (aconn)
380     {
381         result = decklink_sys.p_config->SetInt(
382             bmdDeckLinkConfigAudioInputConnection, aconn);
383         CHECK("Could not set audio output connection");
384     }
385
386     result = decklink_sys.p_output->GetDisplayModeIterator(&decklink_sys.p_display_iterator);
387     CHECK("Could not enumerate display modes");
388
389     for (; ; p_display_mode->Release())
390     {
391         int w, h;
392         result = decklink_sys.p_display_iterator->Next(&p_display_mode);
393         if (result != S_OK)
394             break;
395
396         BMDDisplayMode mode_id = ntohl(p_display_mode->GetDisplayMode());
397
398         const char *psz_mode_name;
399         result = p_display_mode->GetName(&psz_mode_name);
400         CHECK("Could not get display mode name");
401
402         result = p_display_mode->GetFrameRate(&decklink_sys.frameduration,
403             &decklink_sys.timescale);
404         CHECK("Could not get frame rate");
405
406         w = p_display_mode->GetWidth();
407         h = p_display_mode->GetHeight();
408         msg_Dbg(p_this, "Found mode '%4.4s': %s (%dx%d, %.3f fps)",
409                 (char*)&mode_id, psz_mode_name, w, h,
410                 double(decklink_sys.timescale) / decklink_sys.frameduration);
411         msg_Dbg(p_this, "scale %d dur %d", (int)decklink_sys.timescale,
412             (int)decklink_sys.frameduration);
413
414         if (wanted_mode_id != mode_id)
415             continue;
416
417         decklink_sys.i_width = w;
418         decklink_sys.i_height = h;
419
420         p_display_mode->Release();
421         p_display_mode = NULL;
422
423         mode_id = htonl(mode_id);
424
425         BMDVideoOutputFlags flags = bmdVideoOutputVANC;
426         if (mode_id == bmdModeNTSC ||
427             mode_id == bmdModeNTSC2398 ||
428             mode_id == bmdModePAL)
429         {
430             flags = bmdVideoOutputVITC;
431         }
432
433         BMDDisplayModeSupport support;
434         IDeckLinkDisplayMode *resultMode;
435
436         result = decklink_sys.p_output->DoesSupportVideoMode(mode_id,
437             sys->tenbits ? bmdFormat10BitYUV : bmdFormat8BitYUV,
438             flags, &support, &resultMode);
439         CHECK("Does not support video mode");
440         if (support == bmdDisplayModeNotSupported)
441         {
442             msg_Err(p_this, "Video mode not supported");
443                 goto error;
444         }
445
446         result = decklink_sys.p_output->EnableVideoOutput(mode_id, flags);
447         CHECK("Could not enable video output");
448
449         break;
450     }
451
452     if (decklink_sys.i_width < 0)
453     {
454         msg_Err(p_this, "Unknown video mode specified.");
455         goto error;
456     }
457
458     /* audio */
459     if (/*decklink_sys.i_channels > 0 &&*/ decklink_sys.i_rate > 0)
460     {
461         result = decklink_sys.p_output->EnableAudioOutput(
462             decklink_sys.i_rate,
463             bmdAudioSampleType16bitInteger,
464             /*decklink_sys.i_channels*/ 2,
465             bmdAudioOutputStreamTimestamped);
466     }
467     else
468     {
469         result = decklink_sys.p_output->DisableAudioOutput();
470     }
471     CHECK("Could not enable audio output");
472
473
474     /* start */
475     result = decklink_sys.p_output->StartScheduledPlayback(
476         (mdate() * decklink_sys.timescale) / CLOCK_FREQ, decklink_sys.timescale, 1.0);
477     CHECK("Could not start playback");
478
479     atomic_store(&initialized, 1);
480
481     vlc_mutex_unlock(&lock);
482     return VLC_SUCCESS;
483
484
485 error:
486     if (decklink_sys.decklink_iterator)
487         decklink_sys.decklink_iterator->Release();
488     if (decklink_sys.p_display_iterator)
489         decklink_sys.p_display_iterator->Release();
490     if (p_display_mode)
491         p_display_mode->Release();
492
493     vlc_mutex_unlock(&lock);
494     return VLC_EGENERIC;
495 #undef CHECK
496 }
497
498 /*****************************************************************************
499  * Video
500  *****************************************************************************/
501
502 static picture_pool_t *PoolVideo(vout_display_t *vd, unsigned requested_count)
503 {
504     vout_display_sys_t *sys = vd->sys;
505     if (!sys->pool)
506         sys->pool = picture_pool_NewFromFormat(&vd->fmt, requested_count);
507     return sys->pool;
508 }
509
510 static inline void put_le32(uint8_t **p, uint32_t d)
511 {
512     SetDWLE(*p, d);
513     (*p) += 4;
514 }
515
516 static inline int clip(int a)
517 {
518     if      (a < 4) return 4;
519     else if (a > 1019) return 1019;
520     else               return a;
521 }
522
523 static void v210_convert(void *frame_bytes, picture_t *pic, int dst_stride)
524 {
525     int width = pic->format.i_width;
526     int height = pic->format.i_height;
527     int line_padding = dst_stride - ((width * 8 + 11) / 12) * 4;
528     int h, w;
529     uint8_t *data = (uint8_t*)frame_bytes;
530
531     const uint16_t *y = (const uint16_t*)pic->p[0].p_pixels;
532     const uint16_t *u = (const uint16_t*)pic->p[1].p_pixels;
533     const uint16_t *v = (const uint16_t*)pic->p[2].p_pixels;
534
535 #define WRITE_PIXELS(a, b, c)           \
536     do {                                \
537         val =   clip(*a++);             \
538         val |= (clip(*b++) << 10) |     \
539                (clip(*c++) << 20);      \
540         put_le32(&data, val);           \
541     } while (0)
542
543     for (h = 0; h < height; h++) {
544         uint32_t val = 0;
545         for (w = 0; w < width - 5; w += 6) {
546             WRITE_PIXELS(u, y, v);
547             WRITE_PIXELS(y, u, y);
548             WRITE_PIXELS(v, y, u);
549             WRITE_PIXELS(y, v, y);
550         }
551         if (w < width - 1) {
552             WRITE_PIXELS(u, y, v);
553
554             val = clip(*y++);
555             if (w == width - 2)
556                 put_le32(&data, val);
557 #undef WRITE_PIXELS
558         }
559         if (w < width - 3) {
560             val |= (clip(*u++) << 10) | (clip(*y++) << 20);
561             put_le32(&data, val);
562
563             val = clip(*v++) | (clip(*y++) << 10);
564             put_le32(&data, val);
565         }
566
567         memset(data, 0, line_padding);
568         data += line_padding;
569
570         y += pic->p[0].i_pitch / 2 - width;
571         u += pic->p[1].i_pitch / 2 - width / 2;
572         v += pic->p[2].i_pitch / 2 - width / 2;
573     }
574 }
575
576 static void DisplayVideo(vout_display_t *vd, picture_t *picture, subpicture_t *)
577 {
578     vout_display_sys_t *sys = vd->sys;
579
580     if (!picture)
581         return;
582
583     HRESULT result;
584     int w, h, stride, length;
585     mtime_t now;
586     w = decklink_sys.i_width;
587     h = decklink_sys.i_height;
588
589     IDeckLinkMutableVideoFrame *pDLVideoFrame;
590     result = decklink_sys.p_output->CreateVideoFrame(w, h, w*3,
591         sys->tenbits ? bmdFormat10BitYUV : bmdFormat8BitYUV,
592         bmdFrameFlagDefault, &pDLVideoFrame);
593
594     if (result != S_OK) {
595         msg_Err(vd, "Failed to create video frame: 0x%X", result);
596         pDLVideoFrame = NULL;
597         goto end;
598     }
599
600     void *frame_bytes;
601     pDLVideoFrame->GetBytes((void**)&frame_bytes);
602     stride = pDLVideoFrame->GetRowBytes();
603
604     if (sys->tenbits)
605         v210_convert(frame_bytes, picture, stride);
606     else for(int y = 0; y < h; ++y) {
607         uint8_t *dst = (uint8_t *)frame_bytes + stride * y;
608         const uint8_t *src = (const uint8_t *)picture->p[0].p_pixels +
609             picture->p[0].i_pitch * y;
610         memcpy(dst, src, w * 2 /* bpp */);
611     }
612
613
614     // compute frame duration in CLOCK_FREQ units
615     length = (decklink_sys.frameduration * CLOCK_FREQ) / decklink_sys.timescale;
616
617     picture->date -= decklink_sys.offset;
618     result = decklink_sys.p_output->ScheduleVideoFrame(pDLVideoFrame,
619         picture->date, length, CLOCK_FREQ);
620
621     if (result != S_OK) {
622         msg_Err(vd, "Dropped Video frame %"PRId64 ": 0x%x",
623             picture->date, result);
624         goto end;
625     }
626
627     now = mdate() - decklink_sys.offset;
628
629     BMDTimeValue decklink_now;
630     double speed;
631     decklink_sys.p_output->GetScheduledStreamTime (CLOCK_FREQ, &decklink_now, &speed);
632
633     if ((now - decklink_now) > 400000) {
634         /* XXX: workaround card clock drift */
635         decklink_sys.offset += 50000;
636         msg_Err(vd, "Delaying: offset now %"PRId64"", decklink_sys.offset);
637     }
638
639 end:
640     if (pDLVideoFrame)
641         pDLVideoFrame->Release();
642     picture_Release(picture);
643 }
644
645 static int ControlVideo(vout_display_t *vd, int query, va_list args)
646 {
647     VLC_UNUSED(vd);
648     const vout_display_cfg_t *cfg;
649
650     switch (query) {
651     case VOUT_DISPLAY_CHANGE_FULLSCREEN:
652         cfg = va_arg(args, const vout_display_cfg_t *);
653         return cfg->is_fullscreen ? VLC_EGENERIC : VLC_SUCCESS;
654     default:
655         return VLC_EGENERIC;
656     }
657 }
658
659 static atomic_uint video_lock = ATOMIC_VAR_INIT(0);
660 static int OpenVideo(vlc_object_t *p_this)
661 {
662     vout_display_t *vd = (vout_display_t *)p_this;
663     vout_display_sys_t *sys;
664
665     if (atomic_exchange(&video_lock, 1)) {
666         msg_Err(vd, "Decklink video module already busy");
667         return VLC_EGENERIC;
668     }
669
670     vd->sys = sys = (vout_display_sys_t*)malloc(sizeof(*sys));
671     if (!sys)
672         return VLC_ENOMEM;
673
674     if (OpenDecklink(p_this) != VLC_SUCCESS)
675         goto error;
676
677     if (decklink_sys.i_width & 1) {
678         msg_Err(vd, "Invalid width %d", decklink_sys.i_width);
679         goto error;
680     }
681
682     sys->pool = NULL;
683
684     sys->tenbits = var_InheritBool(p_this, VIDEO_CFG_PREFIX "tenbits");
685     vd->fmt.i_chroma = sys->tenbits
686         ? VLC_CODEC_I422_10L /* we will convert to v210 */
687         : VLC_CODEC_UYVY;
688     //video_format_FixRgb(&(vd->fmt));
689
690     vd->fmt.i_width = decklink_sys.i_width;
691     vd->fmt.i_height = decklink_sys.i_height;
692
693     vd->info.has_hide_mouse = true;
694     vd->pool    = PoolVideo;
695     vd->prepare = NULL;
696     vd->display = DisplayVideo;
697     vd->control = ControlVideo;
698     vd->manage  = NULL;
699     vout_display_SendEventFullscreen(vd, false);
700
701     return VLC_SUCCESS;
702
703 error:
704     free(sys);
705     return VLC_EGENERIC;
706 }
707
708 static void CloseVideo(vlc_object_t *p_this)
709 {
710     vout_display_t *vd = (vout_display_t *)p_this;
711     vout_display_sys_t *sys = vd->sys;
712
713     if (sys->pool)
714         picture_pool_Delete(sys->pool);
715
716     free(sys);
717
718     atomic_fetch_sub(&video_lock, 1);
719 }
720
721 /*****************************************************************************
722  * Audio
723  *****************************************************************************/
724
725 static void Flush (audio_output_t *aout, bool drain)
726 {
727     if (!atomic_load(&initialized))
728         return;
729
730     if (drain) {
731         uint32_t samples;
732         decklink_sys.p_output->GetBufferedAudioSampleFrameCount(&samples);
733         msleep(CLOCK_FREQ * samples / decklink_sys.i_rate);
734     } else if (decklink_sys.p_output->FlushBufferedAudioSamples() == E_FAIL)
735         msg_Err(aout, "Flush failed");
736 }
737
738 static int TimeGet(audio_output_t *, mtime_t* restrict)
739 {
740     /* synchronization is handled by the card */
741     return -1;
742 }
743
744 static int Start(audio_output_t *, audio_sample_format_t *restrict fmt)
745 {
746     fmt->i_format = VLC_CODEC_S16N;
747     fmt->i_channels = 2; //decklink_sys.i_channels;
748     fmt->i_physical_channels = AOUT_CHANS_STEREO; //pi_channels_maps[fmt->i_channels];
749     fmt->i_rate = decklink_sys.i_rate;
750     fmt->i_bitspersample = 16;
751     fmt->i_blockalign = fmt->i_channels * fmt->i_bitspersample /8 ;
752     fmt->i_frame_length  = FRAME_SIZE;
753
754     return VLC_SUCCESS;
755 }
756
757 static void PlayAudio(audio_output_t *aout, block_t *audio)
758 {
759     if (!atomic_load(&initialized))
760         return;
761
762     audio->i_pts -= decklink_sys.offset;
763
764     uint32_t sampleFrameCount = audio->i_buffer / (2 * 2 /*decklink_sys.i_channels*/);
765     uint32_t written;
766     HRESULT result = decklink_sys.p_output->ScheduleAudioSamples(
767             audio->p_buffer, sampleFrameCount, audio->i_pts, CLOCK_FREQ, &written);
768
769     if (result != S_OK)
770         msg_Err(aout, "Failed to schedule audio sample: 0x%X", result);
771     else if (sampleFrameCount != written)
772         msg_Err(aout, "Written only %d samples out of %d", written, sampleFrameCount);
773
774     block_Release(audio);
775 }
776
777 static atomic_uint audio_lock = ATOMIC_VAR_INIT(0);
778 static int OpenAudio(vlc_object_t *p_this)
779 {
780     audio_output_t *aout = (audio_output_t *)p_this;
781
782     if (atomic_exchange(&audio_lock, 1)) {
783         msg_Err(aout, "Decklink audio module already busy");
784         return VLC_EGENERIC;
785     }
786
787     aout->play      = PlayAudio;
788     aout->start     = Start;
789     aout->flush     = Flush;
790     aout->time_get  = TimeGet;
791
792     aout->pause     = NULL;
793     aout->stop      = NULL;
794     aout->mute_set  = NULL;
795     aout->volume_set= NULL;
796
797     return VLC_SUCCESS;
798 }
799
800 static void CloseAudio(vlc_object_t *)
801 {
802     atomic_fetch_sub(&audio_lock, 1);
803 }