diff --git a/include/upipe/udict.h b/include/upipe/udict.h index f8c8f6a7b..6f1113b02 100644 --- a/include/upipe/udict.h +++ b/include/upipe/udict.h @@ -148,7 +148,9 @@ enum udict_type { /** p.afd */ UDICT_TYPE_PIC_AFD, /** p.cea_708 */ - UDICT_TYPE_PIC_CEA_708 + UDICT_TYPE_PIC_CEA_708, + /** p.s12m */ + UDICT_TYPE_PIC_S12M }; /** @This defines standard commands which udict modules may implement. */ diff --git a/include/upipe/uref_pic.h b/include/upipe/uref_pic.h index 1b0fb09e4..6e1cc9574 100644 --- a/include/upipe/uref_pic.h +++ b/include/upipe/uref_pic.h @@ -57,6 +57,7 @@ UREF_ATTR_VOID_SH(pic, bf, UDICT_TYPE_PIC_BF, bottom field present) UREF_ATTR_VOID_SH(pic, tff, UDICT_TYPE_PIC_TFF, top field first) UREF_ATTR_SMALL_UNSIGNED_SH(pic, afd, UDICT_TYPE_PIC_AFD, active format description) UREF_ATTR_OPAQUE_SH(pic, cea_708, UDICT_TYPE_PIC_CEA_708, cea-708 captions) +UREF_ATTR_OPAQUE_SH(pic, s12m, UDICT_TYPE_PIC_S12M, SMPTE 12M timecode) UREF_ATTR_UNSIGNED(pic, original_height, "p.original_height", original picture height before chunking) /** @This returns a new uref pointing to a new ubuf pointing to a picture. diff --git a/lib/upipe-av/upipe_avcodec_decode.c b/lib/upipe-av/upipe_avcodec_decode.c index e1263a0b8..08dfa7dcd 100644 --- a/lib/upipe-av/upipe_avcodec_decode.c +++ b/lib/upipe-av/upipe_avcodec_decode.c @@ -1093,6 +1093,11 @@ static void upipe_avcdec_output_pic(struct upipe *upipe, struct upump **upump_p) if (side_data) uref_pic_set_cea_708(uref, side_data->data, side_data->size); + side_data = av_frame_get_side_data(frame, AV_FRAME_DATA_S12M_TIMECODE); + if (side_data) { + uref_pic_set_s12m(uref, side_data->data, side_data->size); + } + /* various time-related attributes */ upipe_avcdec_set_time_attributes(upipe, uref); diff --git a/lib/upipe-blackmagic/upipe_blackmagic_sink.cpp b/lib/upipe-blackmagic/upipe_blackmagic_sink.cpp index 56a3875bd..9249e17a7 100644 --- a/lib/upipe-blackmagic/upipe_blackmagic_sink.cpp +++ b/lib/upipe-blackmagic/upipe_blackmagic_sink.cpp @@ -77,6 +77,73 @@ extern "C" { static const unsigned max_samples = (uint64_t)48000 * 1001 / 24000; static const size_t audio_buf_size = max_samples * DECKLINK_CHANNELS * sizeof(int32_t); +inline static unsigned bcd2uint(uint8_t bcd) +{ + unsigned low = bcd & 0xf; + unsigned high = bcd >> 4; + if (low > 9 || high > 9) + return 0; + return low + 10*high; +} + +class upipe_bmd_sink_timecode : public IDeckLinkTimecode +{ +public: + upipe_bmd_sink_timecode(uint32_t _BCD) : BCD(_BCD) { } + + virtual BMDTimecodeBCD STDMETHODCALLTYPE GetBCD (void) { + return BCD; + } + + virtual HRESULT STDMETHODCALLTYPE GetComponents(uint8_t *hours, uint8_t *minutes, uint8_t *seconds, uint8_t *frames) { + *hours = bcd2uint( BCD & 0x3f); + *minutes = bcd2uint((BCD >> 8) & 0x7f); + *seconds = bcd2uint((BCD >> 16) & 0x7f); + *frames = bcd2uint((BCD >> 24) & 0x3f); + return S_OK; + } + + virtual BMDTimecodeFlags STDMETHODCALLTYPE GetFlags() { + return !!(BCD & (1 << 30)) ? bmdTimecodeIsDropFrame : bmdTimecodeFlagDefault; + } + + virtual HRESULT STDMETHODCALLTYPE GetTimecodeUserBits(BMDTimecodeUserBits *userBits) { + *userBits = GetBCD(); + return S_OK; + } + + virtual HRESULT GetString (const char **timecode) { + uint8_t h, m, s, f, drop = (this->GetFlags() == bmdTimecodeIsDropFrame); + GetComponents(&h, &m, &s, &f); + + if (!(*timecode = (const char*)calloc(16, sizeof(char)))) { + return S_FALSE; + } + + snprintf((char*)*timecode, 16, "%02u:%02u:%02u%c%02u", h, m, s, drop ? ';' : ':', f); + return S_OK; + } + + virtual ULONG STDMETHODCALLTYPE AddRef(void) { + return uatomic_fetch_add(&refcount, 1) + 1; + } + + virtual ULONG STDMETHODCALLTYPE Release(void) { + uint32_t new_ref = uatomic_fetch_sub(&refcount, 1) - 1; + if (new_ref == 0) + delete this; + return new_ref; + } + + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { + return E_NOINTERFACE; + } + +private: + uint32_t BCD; + uatomic_uint32_t refcount; +}; + class upipe_bmd_sink_frame : public IDeckLinkVideoFrame { public: @@ -117,8 +184,14 @@ class upipe_bmd_sink_frame : public IDeckLinkVideoFrame } virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format, - IDeckLinkTimecode **timecode) { - *timecode = NULL; + IDeckLinkTimecode **_timecode) { + timecode->AddRef(); + *_timecode = timecode; + return S_FALSE; + } + + virtual HRESULT STDMETHODCALLTYPE SetTimecode(upipe_bmd_sink_timecode &_timecode) { + timecode = &_timecode; return S_FALSE; } @@ -159,6 +232,7 @@ class upipe_bmd_sink_frame : public IDeckLinkVideoFrame uatomic_uint32_t refcount; IDeckLinkVideoFrameAncillary *frame_anc; + upipe_bmd_sink_timecode *timecode; public: uint64_t pts; @@ -307,6 +381,9 @@ struct upipe_bmd_sink { /** pass through teletext */ uatomic_uint32_t ttx; + /** pass through timecode */ + uatomic_uint32_t timecode; + /** last frame output */ upipe_bmd_sink_frame *video_frame; }; @@ -787,8 +864,17 @@ static upipe_bmd_sink_frame *get_video_frame(struct upipe *upipe, uref_free(subpic); } - video_frame->SetAncillaryData(ancillary); + if (uatomic_load(&upipe_bmd_sink->timecode)) { + const uint32_t *tc_data; + size_t tc_data_size; + // bmdVideoOutputRP188 + if (ubase_check(uref_pic_get_s12m(uref, (const uint8_t**)&tc_data, &tc_data_size))) { + upipe_bmd_sink_timecode timecode(tc_data[1]); + video_frame->SetTimecode(timecode); + } + } + video_frame->SetAncillaryData(ancillary); video_frame->AddRef(); // we're gonna buffer this frame upipe_bmd_sink->video_frame = video_frame; @@ -1609,6 +1695,8 @@ static int upipe_bmd_sink_set_option(struct upipe *upipe, uatomic_store(&upipe_bmd_sink->cc, strcmp(v, "0")); } else if (!strcmp(k, "teletext")) { uatomic_store(&upipe_bmd_sink->ttx, strcmp(v, "0")); + } else if (!strcmp(k, "timecode")) { + uatomic_store(&upipe_bmd_sink->timecode, strcmp(v, "0")); } else return UBASE_ERR_INVALID; diff --git a/lib/upipe-blackmagic/upipe_blackmagic_source.cpp b/lib/upipe-blackmagic/upipe_blackmagic_source.cpp index 0168fbb94..9eba34202 100644 --- a/lib/upipe-blackmagic/upipe_blackmagic_source.cpp +++ b/lib/upipe-blackmagic/upipe_blackmagic_source.cpp @@ -502,6 +502,12 @@ HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived( else if (upipe_bmd_src->tff) uref_pic_set_tff(uref); + IDeckLinkTimecode *timecode; + if (VideoFrame->GetTimecode(bmdTimecodeRP188Any, &timecode) == S_OK) { + uint32_t bcd = timecode->GetBCD(); + uref_pic_set_s12m(uref, (uint8_t*)&bcd, sizeof(bcd)); + } + if (!uqueue_push(&upipe_bmd_src->uqueue, uref)) uref_free(uref); } diff --git a/lib/upipe/udict_inline.c b/lib/upipe/udict_inline.c index 63e8f0ab4..a3db43f88 100644 --- a/lib/upipe/udict_inline.c +++ b/lib/upipe/udict_inline.c @@ -102,7 +102,8 @@ static const struct inline_shorthand inline_shorthands[] = { { "p.bf", UDICT_TYPE_VOID }, { "p.tff", UDICT_TYPE_VOID }, { "p.afd", UDICT_TYPE_SMALL_UNSIGNED }, - { "p.cea_708", UDICT_TYPE_OPAQUE } + { "p.cea_708", UDICT_TYPE_OPAQUE }, + { "p.s12m", UDICT_TYPE_OPAQUE } }; /** @This stores the size of the value of basic attribute types. */