/***************************************************************************** * decklink.cpp: BlackMagic DeckLink SDI output module ***************************************************************************** * Copyright (C) 2012-2013 Rafaël Carré * Copyright (C) 2009 Michael Niedermayer * Copyright (c) 2009 Baptiste Coudurier * * Authors: Rafaël Carré * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /* * TODO: test non stereo audio */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #ifdef HAVE_ARPA_INET_H #include #endif #include "../access/vlc_decklink.h" #include "../stream_out/sdi/V210.hpp" #include "../stream_out/sdi/Ancillary.hpp" #include "../stream_out/sdi/DBMHelper.hpp" #include "../stream_out/sdi/SDIGenerator.hpp" #ifndef _WIN32 #include #endif #include #if BLACKMAGIC_DECKLINK_API_VERSION < 0x0b010000 #define IID_IDeckLinkProfileAttributes IID_IDeckLinkAttributes #define IDeckLinkProfileAttributes IDeckLinkAttributes #define bmdVideoConnectionUnspecified 0 #endif #define FRAME_SIZE 1920 #define CHANNELS_MAX 6 #if 0 static const int pi_channels_maps[CHANNELS_MAX+1] = { 0, AOUT_CHAN_CENTER, AOUT_CHANS_STEREO, AOUT_CHANS_3_0, AOUT_CHANS_4_0, AOUT_CHANS_5_0, AOUT_CHANS_5_1, }; #endif #define NOSIGNAL_INDEX_TEXT N_("Timelength after which we assume there is no signal.") #define NOSIGNAL_INDEX_LONGTEXT N_(\ "Timelength after which we assume there is no signal.\n"\ "After this delay we black out the video."\ ) #define AFD_INDEX_TEXT N_("Active Format Descriptor value") #define AR_INDEX_TEXT N_("Aspect Ratio") #define AR_INDEX_LONGTEXT N_("Aspect Ratio of the source picture.") #define AFDLINE_INDEX_TEXT N_("Active Format Descriptor line") #define AFDLINE_INDEX_LONGTEXT N_("VBI line on which to output Active Format Descriptor.") #define NOSIGNAL_IMAGE_TEXT N_("Picture to display on input signal loss") #define CARD_INDEX_TEXT N_("Output card") #define CARD_INDEX_LONGTEXT N_(\ "DeckLink output card, if multiple exist. " \ "The cards are numbered from 0.") #define MODE_TEXT N_("Desired output mode") #define MODE_LONGTEXT N_(\ "Desired output mode for DeckLink output. " \ "This value should be a FOURCC code in textual " \ "form, e.g. \"ntsc\".") #define RATE_TEXT N_("Audio samplerate (Hz)") #define RATE_LONGTEXT N_(\ "Audio sampling rate (in hertz) for DeckLink output. " \ "0 disables audio output.") #define CHANNELS_TEXT N_("Number of audio channels") #define CHANNELS_LONGTEXT N_(\ "Number of output channels for DeckLink output. " \ "Must be 2, 8 or 16. 0 disables audio output.") #define VIDEO_CONNECTION_TEXT N_("Video connection") #define VIDEO_CONNECTION_LONGTEXT N_(\ "Video connection for DeckLink output.") #define VIDEO_TENBITS_TEXT N_("10 bits") #define VIDEO_TENBITS_LONGTEXT N_(\ "Use 10 bits per pixel for video frames.") #define CFG_PREFIX "decklink-output-" #define VIDEO_CFG_PREFIX "decklink-vout-" #define AUDIO_CFG_PREFIX "decklink-aout-" /* Video Connections */ static const char *const ppsz_videoconns[] = { "sdi", "hdmi", "opticalsdi", "component", "composite", "svideo" }; static const char *const ppsz_videoconns_text[] = { N_("SDI"), N_("HDMI"), N_("Optical SDI"), N_("Component"), N_("Composite"), N_("S-video"), }; static const BMDVideoConnection rgbmd_videoconns[] = { bmdVideoConnectionSDI, bmdVideoConnectionHDMI, bmdVideoConnectionOpticalSDI, bmdVideoConnectionComponent, bmdVideoConnectionComposite, bmdVideoConnectionSVideo, }; static_assert(ARRAY_SIZE(rgbmd_videoconns) == ARRAY_SIZE(ppsz_videoconns), "videoconn arrays messed up"); static_assert(ARRAY_SIZE(rgbmd_videoconns) == ARRAY_SIZE(ppsz_videoconns_text), "videoconn arrays messed up"); static const int rgi_afd_values[] = { 0, 2, 3, 4, 8, 9, 10, 11, 13, 14, 15, }; static const char * const rgsz_afd_text[] = { /* Note: Skip further translation - too technical */ N_("Undefined"), "Box 16:9 (top aligned)", "Box 14:9 (top aligned)", "Box > 16:9 (centre aligned)", "Same as coded frame (full frame)", "4:3 (centre aligned)", "16:9 (centre aligned)", "14:9 (centre aligned)", "4:3 (with shoot and protect 14:9 centre)", "16:9 (with shoot and protect 14:9 centre)", "16:9 (with shoot and protect 4:3 centre)", }; static_assert(ARRAY_SIZE(rgi_afd_values) == ARRAY_SIZE(rgsz_afd_text), "afd arrays messed up"); static const int rgi_ar_values[] = { 0, 1, }; static const char * const rgsz_ar_text[] = { "4:3", "16:9", }; static_assert(ARRAY_SIZE(rgi_ar_values) == ARRAY_SIZE(rgsz_ar_text), "afd arrays messed up"); namespace { /* Only one audio output module and one video output module * can be used per process. * We use a static mutex in audio/video submodules entry points. */ struct decklink_sys_t { /* With LOCK */ IDeckLinkOutput *p_output; /* * Synchronizes aout and vout modules: * vout module waits until aout has been initialized. * That means video-only output is NOT supported. */ vlc_mutex_t lock; vlc_cond_t cond; uint8_t users; bool b_videomodule; bool b_recycling; //int i_channels; int i_rate; BMDTimeScale timescale; BMDTimeValue frameduration; /* XXX: workaround card clock drift */ vlc_tick_t offset; /* !With LOCK */ vlc_timer_t drain_timer; /* single video module exclusive */ struct { bool tenbits; uint8_t afd, ar; int nosignal_delay; picture_t *pic_nosignal; } video; }; } // namespace /***************************************************************************** * Local prototypes. *****************************************************************************/ static int OpenVideo (vout_display_t *, video_format_t *, vlc_video_context *); static void CloseVideo (vout_display_t *); static int OpenAudio (vlc_object_t *); static void CloseAudio (vlc_object_t *); /***************************************************************************** * Module descriptor *****************************************************************************/ vlc_module_begin() set_shortname(N_("DecklinkOutput")) set_description(N_("Output module to write to Blackmagic SDI card")) set_section(N_("DeckLink General Options"), NULL) add_integer(CFG_PREFIX "card-index", 0, CARD_INDEX_TEXT, CARD_INDEX_LONGTEXT) add_submodule () set_description (N_("DeckLink Video Output module")) set_subcategory(SUBCAT_VIDEO_VOUT) set_callback_display(OpenVideo, 0) set_section(N_("DeckLink Video Options"), NULL) add_string(VIDEO_CFG_PREFIX "video-connection", "sdi", VIDEO_CONNECTION_TEXT, VIDEO_CONNECTION_LONGTEXT) change_string_list(ppsz_videoconns, ppsz_videoconns_text) add_string(VIDEO_CFG_PREFIX "mode", "", MODE_TEXT, MODE_LONGTEXT) add_bool(VIDEO_CFG_PREFIX "tenbits", true, VIDEO_TENBITS_TEXT, VIDEO_TENBITS_LONGTEXT) add_integer(VIDEO_CFG_PREFIX "nosignal-delay", 5, NOSIGNAL_INDEX_TEXT, NOSIGNAL_INDEX_LONGTEXT) add_integer(VIDEO_CFG_PREFIX "afd-line", 16, AFDLINE_INDEX_TEXT, AFDLINE_INDEX_LONGTEXT) add_integer_with_range(VIDEO_CFG_PREFIX "afd", 8, 0, 16, AFD_INDEX_TEXT, nullptr) change_integer_list(rgi_afd_values, rgsz_afd_text) add_integer_with_range(VIDEO_CFG_PREFIX "ar", 1, 0, 1, AR_INDEX_TEXT, AR_INDEX_LONGTEXT) change_integer_list(rgi_ar_values, rgsz_ar_text) add_loadfile(VIDEO_CFG_PREFIX "nosignal-image", NULL, NOSIGNAL_IMAGE_TEXT, nullptr) add_submodule () set_description (N_("DeckLink Audio Output module")) set_subcategory(SUBCAT_AUDIO_AOUT) set_capability("audio output", 0) set_callbacks (OpenAudio, CloseAudio) set_section(N_("DeckLink Audio Options"), NULL) add_integer(AUDIO_CFG_PREFIX "audio-rate", 48000, RATE_TEXT, RATE_LONGTEXT) add_integer(AUDIO_CFG_PREFIX "audio-channels", 2, CHANNELS_TEXT, CHANNELS_LONGTEXT) vlc_module_end () /* Protects decklink_sys_t creation/deletion */ static vlc::threads::mutex sys_lock; static decklink_sys_t *HoldDLSys(vlc_object_t *obj, int i_cat) { vlc_object_t *libvlc = VLC_OBJECT(vlc_object_instance(obj)); decklink_sys_t *sys; sys_lock.lock(); if (var_Type(libvlc, "decklink-sys") == VLC_VAR_ADDRESS) { sys = (decklink_sys_t*)var_GetAddress(libvlc, "decklink-sys"); sys->users++; if(i_cat == VIDEO_ES) { while(sys->b_videomodule) { sys_lock.unlock(); msg_Info(obj, "Waiting for previous vout module to exit"); vlc_tick_sleep(VLC_TICK_FROM_MS(100)); sys_lock.lock(); } } } else { sys = (decklink_sys_t*)malloc(sizeof(*sys)); if (sys) { sys->p_output = NULL; sys->offset = 0; sys->users = 1; sys->b_videomodule = (i_cat == VIDEO_ES); sys->b_recycling = false; sys->i_rate = var_InheritInteger(obj, AUDIO_CFG_PREFIX "audio-rate"); if(sys->i_rate > 0) sys->i_rate = -1; vlc_mutex_init(&sys->lock); vlc_cond_init(&sys->cond); var_Create(libvlc, "decklink-sys", VLC_VAR_ADDRESS); var_SetAddress(libvlc, "decklink-sys", (void*)sys); } } sys_lock.unlock(); return sys; } static void ReleaseDLSys(vlc_object_t *obj, int i_cat) { vlc_object_t *libvlc = VLC_OBJECT(vlc_object_instance(obj)); sys_lock.lock(); struct decklink_sys_t *sys = (struct decklink_sys_t*)var_GetAddress(libvlc, "decklink-sys"); if (--sys->users == 0) { msg_Dbg(obj, "Destroying decklink data"); if (sys->p_output) { sys->p_output->StopScheduledPlayback(0, NULL, 0); sys->p_output->DisableVideoOutput(); sys->p_output->DisableAudioOutput(); sys->p_output->Release(); } /* Clean video specific */ if (sys->video.pic_nosignal) picture_Release(sys->video.pic_nosignal); free(sys); var_Destroy(libvlc, "decklink-sys"); } else if (i_cat == VIDEO_ES) { sys->b_videomodule = false; sys->b_recycling = true; } sys_lock.unlock(); } static BMDVideoConnection getVConn(vout_display_t *vd, int mask) { BMDVideoConnection conn = bmdVideoConnectionUnspecified; char *psz = var_InheritString(vd, VIDEO_CFG_PREFIX "video-connection"); if (psz) { for(size_t i=0; ilock); /* wait until aout is ready */ msg_Info(vd, "Waiting for DeckLink audio input module to start"); while (sys->i_rate == -1) vlc_cond_wait(&sys->cond, &sys->lock); int i_card_index = var_InheritInteger(vd, CFG_PREFIX "card-index"); char *mode = var_InheritString(vd, VIDEO_CFG_PREFIX "mode"); if(mode) { if (strnlen(mode, 5) > 4) { msg_Err(vd, "Invalid mode %s", mode); free(mode); goto error; } strncpy(wanted_mode.str, mode, 4); free(mode); } if (i_card_index < 0) { msg_Err(vd, "Invalid card index %d", i_card_index); goto error; } decklink_iterator = CreateDeckLinkIteratorInstance(); if (!decklink_iterator) { msg_Err(vd, "DeckLink drivers not found."); goto error; } for(int i = 0; i <= i_card_index; ++i) { if (p_card) p_card->Release(); result = decklink_iterator->Next(&p_card); CHECK("Card not found"); } decklink_str_t tmp_name; char *psz_model_name; result = p_card->GetModelName(&tmp_name); CHECK("Unknown model name"); psz_model_name = DECKLINK_STRDUP(tmp_name); DECKLINK_FREE(tmp_name); msg_Dbg(vd, "Opened DeckLink PCI card %s", psz_model_name); free(psz_model_name); /* Read attributes */ result = p_card->QueryInterface(IID_IDeckLinkProfileAttributes, &pv); CHECK("Could not get IDeckLinkAttributes"); p_attributes = static_cast(pv); #ifdef _WIN32 LONGLONG iconn; #else int64_t iconn; #endif BMDVideoConnection vconn; result = p_attributes->GetInt(BMDDeckLinkVideoOutputConnections, &iconn); /* reads mask */ CHECK("Could not get BMDDeckLinkVideoOutputConnections"); result = p_card->QueryInterface(IID_IDeckLinkOutput, &pv); CHECK("No outputs"); sys->p_output = static_cast(pv); result = p_card->QueryInterface(IID_IDeckLinkConfiguration, &pv); CHECK("Could not get config interface"); p_config = static_cast(pv); /* Now configure card */ vconn = getVConn(vd, iconn); if (vconn == bmdVideoConnectionUnspecified) { msg_Err(vd, "Invalid video connection specified"); goto error; } result = p_config->SetInt(bmdDeckLinkConfigVideoOutputConnection, vconn); CHECK("Could not set video output connection"); p_display_mode = Decklink::Helper::MatchDisplayMode(VLC_OBJECT(vd), sys->p_output, vd->source, wanted_mode.id); if(p_display_mode == NULL) { msg_Err(vd, "Could not negotiate a compatible display mode"); goto error; } else { union { BMDDisplayMode id; char str[4]; } mode; mode.id = p_display_mode->GetDisplayMode(); msg_Dbg(vd, "Selected mode '%4.4s'", mode.str); BMDPixelFormat pixelFormat = sys->video.tenbits ? bmdFormat10BitYUV : bmdFormat8BitYUV; BMDVideoOutputFlags flags = bmdVideoOutputVANC; if (mode.id == bmdModeNTSC || mode.id == bmdModeNTSC2398 || mode.id == bmdModePAL) { flags = bmdVideoOutputVITC; } #ifdef _WIN32 BOOL supported; #else bool supported; #endif #if BLACKMAGIC_DECKLINK_API_VERSION < 0x0b010000 BMDDisplayModeSupport support = bmdDisplayModeNotSupported; result = sys->p_output->DoesSupportVideoMode(mode.id, pixelFormat, flags, &support, NULL); supported = (support != bmdDisplayModeNotSupported); #else result = sys->p_output->DoesSupportVideoMode(vconn, mode.id, pixelFormat, bmdSupportedVideoModeDefault, NULL, &supported); #endif CHECK("Does not support video mode"); if (!supported) { msg_Err(vd, "Video mode not supported"); goto error; } if (p_display_mode->GetWidth() <= 0 || p_display_mode->GetWidth() & 1) { msg_Err(vd, "Unknown video mode specified."); goto error; } result = p_display_mode->GetFrameRate(&sys->frameduration, &sys->timescale); CHECK("Could not read frame rate"); result = sys->p_output->EnableVideoOutput(mode.id, flags); CHECK("Could not enable video output"); video_format_Copy(fmt, vd->source); fmt->i_width = fmt->i_visible_width = p_display_mode->GetWidth(); fmt->i_height = fmt->i_visible_height = p_display_mode->GetHeight(); fmt->i_x_offset = 0; fmt->i_y_offset = 0; fmt->i_sar_num = 0; fmt->i_sar_den = 0; fmt->i_chroma = !sys->video.tenbits ? VLC_CODEC_UYVY : VLC_CODEC_I422_10L; /* we will convert to v210 */ fmt->i_frame_rate = (unsigned) sys->frameduration; fmt->i_frame_rate_base = (unsigned) sys->timescale; } if (/*decklink_sys->i_channels > 0 &&*/ sys->i_rate > 0) { result = sys->p_output->EnableAudioOutput( BMDAudioSampleRate(sys->i_rate), bmdAudioSampleType16bitInteger, /*decklink_sys->i_channels*/ 2, bmdAudioOutputStreamTimestamped); CHECK("Could not start audio output"); } /* start */ result = sys->p_output->StartScheduledPlayback( samples_from_vlc_tick(vlc_tick_now(), sys->timescale), sys->timescale, 1.0); CHECK("Could not start playback"); p_config->Release(); p_display_mode->Release(); p_card->Release(); p_attributes->Release(); decklink_iterator->Release(); return VLC_SUCCESS; error: if (sys->p_output) { sys->p_output->Release(); sys->p_output = NULL; } if (p_card) p_card->Release(); if (p_config) p_config->Release(); if (p_attributes) p_attributes->Release(); if (decklink_iterator) decklink_iterator->Release(); if (p_display_mode) p_display_mode->Release(); video_format_Clean(fmt); return VLC_EGENERIC; #undef CHECK } /***************************************************************************** * Video *****************************************************************************/ static void PrepareVideo(vout_display_t *vd, picture_t *picture, const vlc_render_subpicture *, vlc_tick_t date) { decklink_sys_t *sys = (decklink_sys_t *) vd->sys; vlc_tick_t now = vlc_tick_now(); if (!picture) return; if (now - date > vlc_tick_from_sec( sys->video.nosignal_delay )) { msg_Dbg(vd, "no signal"); if (sys->video.pic_nosignal) { picture = sys->video.pic_nosignal; } else { if (sys->video.tenbits) { // I422_10L plane_t *y = &picture->p[0]; memset(y->p_pixels, 0x0, y->i_lines * y->i_pitch); for (int i = 1; i < picture->i_planes; i++) { plane_t *p = &picture->p[i]; size_t len = p->i_lines * p->i_pitch / 2; int16_t *data = (int16_t*)p->p_pixels; for (size_t j = 0; j < len; j++) // XXX: SIMD data[j] = 0x200; } } else { // UYVY size_t len = picture->p[0].i_lines * picture->p[0].i_pitch; for (size_t i = 0; i < len; i+= 2) { // XXX: SIMD picture->p[0].p_pixels[i+0] = 0x80; picture->p[0].p_pixels[i+1] = 0; } } } date = now; } HRESULT result; int w, h, stride, length; w = vd->fmt->i_width; h = vd->fmt->i_height; IDeckLinkMutableVideoFrame *pDLVideoFrame; result = sys->p_output->CreateVideoFrame(w, h, w*3, sys->video.tenbits ? bmdFormat10BitYUV : bmdFormat8BitYUV, bmdFrameFlagDefault, &pDLVideoFrame); if (result != S_OK) { msg_Err(vd, "Failed to create video frame:0x%" PRIHR, result); pDLVideoFrame = NULL; goto end; } void *frame_bytes; pDLVideoFrame->GetBytes((void**)&frame_bytes); stride = pDLVideoFrame->GetRowBytes(); if (sys->video.tenbits) { IDeckLinkVideoFrameAncillary *vanc; int line; void *buf; result = sys->p_output->CreateAncillaryData( sys->video.tenbits ? bmdFormat10BitYUV : bmdFormat8BitYUV, &vanc); if (result != S_OK) { msg_Err(vd, "Failed to create vanc:0x%" PRIHR, result); goto end; } line = var_InheritInteger(vd, VIDEO_CFG_PREFIX "afd-line"); result = vanc->GetBufferForVerticalBlankingLine(line, &buf); if (result != S_OK) { msg_Err(vd, "Failed to get VBI line %d:0x%" PRIHR, line, result); goto end; } sdi::AFD afd(sys->video.afd, sys->video.ar); afd.FillBuffer(reinterpret_cast(buf), stride); sdi::V210::Convert(picture, stride, frame_bytes); result = pDLVideoFrame->SetAncillaryData(vanc); vanc->Release(); if (result != S_OK) { msg_Err(vd, "Failed to set vanc:0x%" PRIHR, result); goto end; } } else for(int y = 0; y < h; ++y) { uint8_t *dst = (uint8_t *)frame_bytes + stride * y; const uint8_t *src = (const uint8_t *)picture->p[0].p_pixels + picture->p[0].i_pitch * y; memcpy(dst, src, w * 2 /* bpp */); } // compute frame duration in CLOCK_FREQ units length = (sys->frameduration * CLOCK_FREQ) / sys->timescale; date -= sys->offset; result = sys->p_output->ScheduleVideoFrame(pDLVideoFrame, date, length, CLOCK_FREQ); if (result != S_OK) { msg_Err(vd, "Dropped Video frame %" PRId64 ":0x%" PRIHR, date, result); goto end; } now = vlc_tick_now() - sys->offset; BMDTimeValue decklink_now; double speed; sys->p_output->GetScheduledStreamTime (CLOCK_FREQ, &decklink_now, &speed); if ((now - decklink_now) > 400000) { /* XXX: workaround card clock drift */ sys->offset += 50000; msg_Err(vd, "Delaying: offset now %" PRId64, sys->offset); } end: if (pDLVideoFrame) pDLVideoFrame->Release(); } static int ControlVideo(vout_display_t *vd, int query) { (void) vd; switch (query) { case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED: case VOUT_DISPLAY_CHANGE_ZOOM: case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: case VOUT_DISPLAY_CHANGE_SOURCE_CROP: return VLC_SUCCESS; } return VLC_EGENERIC; } static const auto ops = []{ struct vlc_display_operations ops {}; ops.close = CloseVideo; ops.prepare = PrepareVideo; ops.control = ControlVideo; return ops; }(); static int OpenVideo(vout_display_t *vd, video_format_t *fmtp, vlc_video_context *context) { VLC_UNUSED(context); decklink_sys_t *sys = HoldDLSys(VLC_OBJECT(vd), VIDEO_ES); if(!sys) return VLC_ENOMEM; bool b_init; vlc_mutex_lock(&sys->lock); b_init = !sys->b_recycling; vlc_mutex_unlock(&sys->lock); if( b_init ) { sys->video.tenbits = var_InheritBool(vd, VIDEO_CFG_PREFIX "tenbits"); sys->video.nosignal_delay = var_InheritInteger(vd, VIDEO_CFG_PREFIX "nosignal-delay"); sys->video.afd = var_InheritInteger(vd, VIDEO_CFG_PREFIX "afd"); sys->video.ar = var_InheritInteger(vd, VIDEO_CFG_PREFIX "ar"); sys->video.pic_nosignal = NULL; if (OpenDecklink(vd, sys, fmtp) != VLC_SUCCESS) { CloseVideo(vd); return VLC_EGENERIC; } char *pic_file = var_InheritString(vd, VIDEO_CFG_PREFIX "nosignal-image"); if (pic_file) { sys->video.pic_nosignal = sdi::Generator::Picture(VLC_OBJECT(vd), pic_file, fmtp); if (!sys->video.pic_nosignal) msg_Err(vd, "Could not create no signal picture"); free(pic_file); } } vd->ops = &ops; vd->sys = (void *) sys; return VLC_SUCCESS; } static void CloseVideo(vout_display_t *vd) { ReleaseDLSys(VLC_OBJECT(vd), VIDEO_ES); } /***************************************************************************** * Audio *****************************************************************************/ static void DrainReset(audio_output_t *aout) { decklink_sys_t *sys = (decklink_sys_t *) aout->sys; if (sys->drain_timer != NULL) { vlc_timer_destroy(sys->drain_timer); sys->drain_timer = NULL; } } static void Flush(audio_output_t *aout) { decklink_sys_t *sys = (decklink_sys_t *) aout->sys; vlc_mutex_lock(&sys->lock); IDeckLinkOutput *p_output = sys->p_output; vlc_mutex_unlock(&sys->lock); if (!p_output) return; DrainReset(aout); if (sys->p_output->FlushBufferedAudioSamples() == E_FAIL) msg_Err(aout, "Flush failed"); } static void DrainTimerCb(void *data) { audio_output_t *aout = (audio_output_t *) data; aout_DrainedReport(aout); } static void Drain(audio_output_t *aout) { decklink_sys_t *sys = (decklink_sys_t *) aout->sys; vlc_mutex_lock(&sys->lock); IDeckLinkOutput *p_output = sys->p_output; vlc_mutex_unlock(&sys->lock); if (!p_output) return; assert(sys->drain_timer == NULL); uint32_t samples; sys->p_output->GetBufferedAudioSampleFrameCount(&samples); if (samples == 0) { aout_DrainedReport(aout); return; } /* Create and arm a timer to notify when drained */ int ret = vlc_timer_create(&sys->drain_timer, DrainTimerCb, aout); if (ret != 0) { aout_DrainedReport(aout); return; } vlc_timer_schedule(sys->drain_timer, false, vlc_tick_from_samples(samples, sys->i_rate), VLC_TIMER_FIRE_ONCE); } static int TimeGet(audio_output_t *, vlc_tick_t* restrict) { /* synchronization is handled by the card */ return -1; } static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt) { decklink_sys_t *sys = (decklink_sys_t *) aout->sys; if (sys->i_rate == 0) return VLC_EGENERIC; fmt->i_format = VLC_CODEC_S16N; fmt->i_channels = 2; //decklink_sys->i_channels; fmt->i_physical_channels = AOUT_CHANS_STEREO; //pi_channels_maps[fmt->i_channels]; fmt->channel_type = AUDIO_CHANNEL_TYPE_BITMAP; fmt->i_rate = sys->i_rate; fmt->i_bitspersample = 16; fmt->i_blockalign = fmt->i_channels * fmt->i_bitspersample /8 ; fmt->i_frame_length = FRAME_SIZE; return VLC_SUCCESS; } static void PlayAudio(audio_output_t *aout, block_t *audio, vlc_tick_t systempts) { decklink_sys_t *sys = (decklink_sys_t *) aout->sys; vlc_mutex_lock(&sys->lock); IDeckLinkOutput *p_output = sys->p_output; audio->i_pts -= sys->offset; vlc_mutex_unlock(&sys->lock); if (!p_output) { block_Release(audio); return; } uint32_t sampleFrameCount = audio->i_buffer / (2 * 2 /*decklink_sys->i_channels*/); uint32_t written; HRESULT result = p_output->ScheduleAudioSamples( audio->p_buffer, sampleFrameCount, systempts, CLOCK_FREQ, &written); if (result != S_OK) msg_Err(aout, "Failed to schedule audio sample:0x%" PRIHR, result); else if (sampleFrameCount != written) msg_Err(aout, "Written only %d samples out of %d", written, sampleFrameCount); block_Release(audio); } static int OpenAudio(vlc_object_t *p_this) { audio_output_t *aout = (audio_output_t *)p_this; decklink_sys_t *sys = HoldDLSys(p_this, AUDIO_ES); if(!sys) return VLC_ENOMEM; aout->sys = sys; vlc_mutex_lock(&sys->lock); //decklink_sys->i_channels = var_InheritInteger(vd, AUDIO_CFG_PREFIX "audio-channels"); sys->i_rate = var_InheritInteger(aout, AUDIO_CFG_PREFIX "audio-rate"); vlc_cond_signal(&sys->cond); vlc_mutex_unlock(&sys->lock); sys->drain_timer = NULL; aout->play = PlayAudio; aout->start = Start; aout->flush = Flush; aout->drain = Drain; aout->time_get = TimeGet; aout->pause = NULL; aout->stop = Flush; aout->mute_set = NULL; aout->volume_set= NULL; return VLC_SUCCESS; } static void CloseAudio(vlc_object_t *p_this) { decklink_sys_t *sys = (decklink_sys_t *) ((audio_output_t *)p_this)->sys; vlc_mutex_lock(&sys->lock); vlc_mutex_unlock(&sys->lock); ReleaseDLSys(p_this, AUDIO_ES); }