diff --git a/include/upipe/Makefile.am b/include/upipe/Makefile.am index c215f472f..12e3f85d5 100644 --- a/include/upipe/Makefile.am +++ b/include/upipe/Makefile.am @@ -89,6 +89,7 @@ pkginclude_HEADERS = \ urefcount.h \ urefcount_helper.h \ uref_attr.h \ + uref_attr_s12m.h \ uref_block_flow.h \ uref_block.h \ uref_clock.h \ diff --git a/include/upipe/udict.h b/include/upipe/udict.h index b16b2f121..fc42cdd3e 100644 --- a/include/upipe/udict.h +++ b/include/upipe/udict.h @@ -150,7 +150,9 @@ enum udict_type { /** p.cea_708 */ UDICT_TYPE_PIC_CEA_708, /** p.bar_data */ - UDICT_TYPE_PIC_BAR_DATA + UDICT_TYPE_PIC_BAR_DATA, + /** p.s12m */ + UDICT_TYPE_PIC_S12M, }; /** @This defines standard commands which udict modules may implement. */ diff --git a/include/upipe/uref_attr_s12m.h b/include/upipe/uref_attr_s12m.h new file mode 100644 index 000000000..49c7df766 --- /dev/null +++ b/include/upipe/uref_attr_s12m.h @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2024 Open Broadcast Systems Ltd + * + * Authors: James Darnley + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + * @short Upipe uref s12m attributes handling + * + * Helper functions to handle the s12m timecode attributes. See the SMPTE + * standards 12M-1 and 314M for more details. + */ + +#ifndef _UPIPE_UREF_ATTR_S12M_H_ +/** @hidden */ +#define _UPIPE_UREF_ATTR_S12M_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include "upipe/uref_pic.h" + +#include +#include + +/** @this returns a uint32_t read from possibly unaligned data + * + * Timecode packs and the count are native endian values. + */ +static inline uint32_t uref_attr_s12m_read(const uint8_t *data) +{ + /* TODO: big endian */ + return data[0] | data[1] << 8 | data[2] << 16 | (uint32_t)data[3] << 24; +} + +/** @This returns true if the data and size given repesents a valid s12m + * attribute. + */ +static inline bool uref_attr_s12m_check(const uint8_t *data, const size_t size) +{ + /* Size must be at least 4 for a single uint32_t. */ + if (size < 4) + return false; + const uint32_t count = uref_attr_s12m_read(data); + /* The count should be followed by that number of uint32_t. */ + if (size < sizeof(uint32_t) * (count+1)) + return false; + return true; +} + +/** @This returns true if the integer values are permissible in a timecode. + */ +static inline bool uref_attr_s12m_validate_integers(const uint32_t hours, + const uint32_t minutes, const uint32_t seconds, const uint32_t frames) +{ + if (hours > 23) + return false; + if (minutes > 59) + return false; + if (seconds > 59) + return false; + if (frames > 29) + return false; + return true; +} + +/** @This returns true if the decimal values are permissible in a timecode. + */ +static inline bool uref_attr_s12m_validate_decimals(const uint32_t hours_10s, + const uint32_t hours_1s, const uint32_t minutes_10s, const uint32_t minutes_1s, + const uint32_t seconds_10s, const uint32_t seconds_1s, const uint32_t frames_10s, + const uint32_t frames_1s) +{ + return uref_attr_s12m_validate_integers(10*hours_10s + hours_1s, + 10*minutes_10s + minutes_1s, 10*seconds_10s + seconds_1s, + 10*frames_10s + frames_1s); +} + +/** @This returns a new timecode pack from the integer components and the drop + * frame flag. Does not validate values. + * + * @param hours 0-23 + * @param minutes 0-59 + * @param seconds 0-59 + * @param frames 0 to framerate + * @param drop true or false + * @return timecode pack + */ + +static inline uint32_t uref_attr_s12m_from_integers(const uint32_t hours, + const uint32_t minutes, const uint32_t seconds, const uint32_t frames, + const uint32_t drop) +{ + return drop << 30 + | hours % 10 + | hours / 10 << 4 + | minutes % 10 << 8 + | minutes / 10 << 12 + | seconds % 10 << 16 + | seconds / 10 << 20 + | frames % 10 << 24 + | frames / 10 << 28; +} + +/** @This splits a timecode pack into the integer components and the drop frame + * flag. Does not validate. + * values. + * + * @param timecode timecode pack + * @param hours pointer + * @param minutes pointer + * @param seconds pointer + * @param frames pointer + * @param drop pointer + */ + +static inline void uref_attr_s12m_to_integers(const uint32_t timecode, + uint8_t *hours, uint8_t *minutes, uint8_t *seconds, uint8_t *frames, + bool *drop) +{ + *hours = 10 * (timecode >> 4 & 0x3) + (timecode & 0xf); + *minutes = 10 * (timecode >> 12 & 0x7) + (timecode >> 8 & 0xf); + *seconds = 10 * (timecode >> 20 & 0x7) + (timecode >> 16 & 0xf); + *frames = 10 * (timecode >> 28 & 0x3) + (timecode >> 24 & 0xf); + *drop = (timecode & 1<<30) == 1<<30; +} + +/** @This returns a new timecode pack from the decimal components and the drop + * frame flag. Does not validate values. + * + * @param hours_10s 0-2 + * @param hours_1s 0-9 + * @param minutes_10s 0-5 + * @param minutes_1s 0-9 + * @param seconds_10s 0-5 + * @param seconds_1s 0-9 + * @param frames_10s 0 to tens of framerate + * @param frames_1s 0-9 + * @param drop true or false + * @return timecode pack + */ + +static inline uint32_t uref_attr_s12m_from_decimals(const uint32_t hours_10s, + const uint32_t hours_1s, const uint32_t minutes_10s, const uint32_t minutes_1s, + const uint32_t seconds_10s, const uint32_t seconds_1s, const uint32_t frames_10s, + const uint32_t frames_1s, const uint32_t drop) +{ + return drop << 30 + | hours_1s + | hours_10s << 4 + | minutes_1s << 8 + | minutes_10s << 12 + | seconds_1s << 16 + | seconds_10s << 20 + | frames_1s << 24 + | frames_10s << 28; +} + +/** @This splits a timecode pack into the decimal components and the drop frame + * flag. Does not validate values. + * + * @pack timecode timecode pack + * @param hours_10s pointer + * @param hours_1s pointer + * @param minutes_10s pointer + * @param minutes_1s pointer + * @param seconds_10s pointer + * @param seconds_1s pointer + * @param frames_10s pointer + * @param frames_1s pointer + * @param drop pointer + */ + +static inline void uref_attr_s12m_to_decimals(const uint32_t timecode, + uint8_t *hours_10s, uint8_t *hours_1s, uint8_t *minutes_10s, uint8_t *minutes_1s, + uint8_t *seconds_10s, uint8_t *seconds_1s, uint8_t *frames_10s, uint8_t *frames_1s, + bool *drop) +{ + *hours_10s = timecode >> 4 & 0x3; + *hours_1s = timecode & 0xf; + *minutes_10s = timecode >> 12 & 0x7; + *minutes_1s = timecode >> 8 & 0xf; + *seconds_10s = timecode >> 20 & 0x7; + *seconds_1s = timecode >> 16 & 0xf; + *frames_10s = timecode >> 28 & 0x3; + *frames_1s = timecode >> 24 & 0xf; + *drop = (timecode & 1 << 30) == 1 << 30; +} + +/** @This gets the field flag in a timecode pack + * + * @param timecode timecode pack + * @param is_pal get the flag in the pal location instead of ntsc location + * @return the flag as a boolean + */ + +static inline bool uref_attr_s12m_get_field_flag(const uint32_t timecode, const bool is_pal) +{ + if (is_pal) + return (timecode & 1 << 7) == 1 << 7; + else + return (timecode & 1 << 23) == 1 << 23; +} + +/** @This sets the field flag in a timecode pack + * + * @param timecode old timecode pack + * @param is_pal set the flag in the pal location instead of ntsc location + * @param field field flag as a boolean + * @return new timecode pack + */ + +static inline uint32_t uref_attr_s12m_set_field_flag(const uint32_t timecode, const bool is_pal, const bool field) +{ + if (is_pal) { + if (field) + return timecode | 1 << 7; + else + return timecode & ~(1 << 7); + } else { + if (field) + return timecode | 1 << 23; + else + return timecode & ~(1 << 23); + } +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/upipe/uref_pic.h b/include/upipe/uref_pic.h index 490a1c8d8..63d6e5639 100644 --- a/include/upipe/uref_pic.h +++ b/include/upipe/uref_pic.h @@ -58,6 +58,7 @@ 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, bar_data, UDICT_TYPE_PIC_BAR_DATA, afd bar data) +UREF_ATTR_OPAQUE_SH(pic, s12m, UDICT_TYPE_PIC_S12M, SMPTE 12M timecode compatible with ffmpeg AV_FRAME_DATA_S12M_TIMECODE) UREF_ATTR_UNSIGNED(pic, original_height, "p.original_height", original picture height before chunking) UREF_ATTR_VOID(pic, c_not_y, "p.c_not_y", whether ancillary data is found in chroma space) diff --git a/lib/upipe-av/upipe_avcodec_decode.c b/lib/upipe-av/upipe_avcodec_decode.c index 71b2d5bd3..819fe20ec 100644 --- a/lib/upipe-av/upipe_avcodec_decode.c +++ b/lib/upipe-av/upipe_avcodec_decode.c @@ -31,6 +31,7 @@ #include "upipe/uclock.h" #include "upipe/ubuf.h" #include "upipe/uref.h" +#include "upipe/uref_attr_s12m.h" #include "upipe/uref_pic.h" #include "upipe/uref_flow.h" #include "upipe/uref_pic_flow.h" @@ -1324,6 +1325,13 @@ 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); +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(56, 20, 100) + side_data = av_frame_get_side_data(frame, AV_FRAME_DATA_S12M_TIMECODE); + if (side_data && uref_attr_s12m_check(side_data->data, side_data->size)) { + uref_pic_set_s12m(uref, side_data->data, side_data->size); + } +#endif + /* 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 e9d334086..8fec50b4a 100644 --- a/lib/upipe-blackmagic/upipe_blackmagic_sink.cpp +++ b/lib/upipe-blackmagic/upipe_blackmagic_sink.cpp @@ -34,6 +34,7 @@ #include "upipe/uprobe.h" #include "upipe/uclock.h" #include "upipe/uref.h" +#include "upipe/uref_attr_s12m.h" #include "upipe/uref_block.h" #include "upipe/uref_pic.h" #include "upipe/uref_pic_flow.h" @@ -78,6 +79,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: @@ -118,8 +186,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; } @@ -160,6 +234,7 @@ class upipe_bmd_sink_frame : public IDeckLinkVideoFrame uatomic_uint32_t refcount; IDeckLinkVideoFrameAncillary *frame_anc; + upipe_bmd_sink_timecode *timecode; public: uint64_t pts; @@ -325,6 +400,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; @@ -987,8 +1065,18 @@ static upipe_bmd_sink_frame *get_video_frame(struct upipe *upipe, pthread_mutex_unlock(&upipe_bmd_sink->lock); #endif - video_frame->SetAncillaryData(ancillary); + if (uatomic_load(&upipe_bmd_sink->timecode)) { + const uint8_t *tc_data; + size_t tc_data_size; + // bmdVideoOutputRP188 + if (ubase_check(uref_pic_get_s12m(uref, &tc_data, &tc_data_size)) + && uref_attr_s12m_check(tc_data, tc_data_size)) { + upipe_bmd_sink_timecode timecode(uref_attr_s12m_read(tc_data + sizeof(uint32_t))); + video_frame->SetTimecode(timecode); + } + } + video_frame->SetAncillaryData(ancillary); video_frame->AddRef(); // we're gonna buffer this frame upipe_bmd_sink->video_frame = video_frame; @@ -1706,8 +1794,9 @@ static int upipe_bmd_open_vid(struct upipe *upipe) displayMode->GetFrameRate(&timeValue, &timeScale); upipe_bmd_sink->ticks_per_frame = UCLOCK_FREQ * timeValue / timeScale; + /* TODO: use timecode option to set bit */ result = deckLinkOutput->EnableVideoOutput(displayMode->GetDisplayMode(), - bmdVideoOutputVANC); + bmdVideoOutputVANC|bmdVideoOutputVITC|bmdVideoOutputRP188); if (result != S_OK) { upipe_err(upipe, "Failed to enable video output. Is another application using the card?\n"); @@ -1883,6 +1972,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 447e9d6a9..d96f04469 100644 --- a/lib/upipe-blackmagic/upipe_blackmagic_source.cpp +++ b/lib/upipe-blackmagic/upipe_blackmagic_source.cpp @@ -32,6 +32,7 @@ #include "upipe/uprobe.h" #include "upipe/uclock.h" #include "upipe/uref.h" +#include "upipe/uref_attr_s12m.h" #include "upipe/uref_pic.h" #include "upipe/uref_pic_flow.h" #include "upipe/uref_sound_flow.h" @@ -545,6 +546,22 @@ HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived( else if (upipe_bmd_src->tff) uref_pic_set_tff(uref); + IDeckLinkTimecode *timecode; + if (VideoFrame->GetTimecode(bmdTimecodeRP188Any, &timecode) == S_OK) { + uint8_t hours, minutes, seconds, frames; + timecode->GetComponents(&hours, &minutes, &seconds, &frames); + bool drop = timecode->GetFlags() & bmdTimecodeIsDropFrame; + + uint32_t timecode_s12m = uref_attr_s12m_from_integers(hours, + minutes, seconds, frames, !!drop); + timecode_s12m = uref_attr_s12m_set_field_flag(timecode_s12m, + upipe_bmd_src->fps.den != 1001, + timecode->GetFlags() & bmdTimecodeFieldMark); + + uint32_t bcd[2] = { 1, timecode_s12m }; + 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 656fcc218..dd9a5ae3e 100644 --- a/lib/upipe/udict_inline.c +++ b/lib/upipe/udict_inline.c @@ -103,6 +103,7 @@ static const struct inline_shorthand inline_shorthands[] = { { "p.afd", UDICT_TYPE_SMALL_UNSIGNED }, { "p.cea_708", UDICT_TYPE_OPAQUE }, { "p.bar_data", UDICT_TYPE_OPAQUE }, + { "p.s12m", UDICT_TYPE_OPAQUE }, }; /** @This stores the size of the value of basic attribute types. */