From b7963a6f84a3dafe6a4c895f3d7d1647a51d18c5 Mon Sep 17 00:00:00 2001 From: odgriff79 Date: Wed, 1 Apr 2026 07:51:00 +0100 Subject: [PATCH] Add DNxHR (VC3 CID 1270-1274) support for Avid OP-Atom wrapping Adds support for DNxHR codecs (444/HQX/HQ/SQ/LB) in bmxtranswrap, raw2bmx, and mxf2raw. DNxHR is the resolution-independent successor to DNxHD, supporting arbitrary frame dimensions. Based on Thomas Gritzan's (Phygon) original DNxHR branch, manually ported to bmx v1.6 codebase with additional fixes for Avid Media Composer compatibility. Fixes applied beyond the original Phygon patch: - CodingEquations: write as straight SMPTE UL for DNxHR, not AUID half-swapped (Avid DNxHR uses UL format, unlike DNxHD which uses AAF AUID format in the CodingEquations property) - Add TransferCharacteristic (BT.709) and ColorPrimaries (ITU709) to CDCI descriptor for DNxHR entries - Remove SignalStandard and AvidResolutionID tags for DNxHR (neither Avid-native nor Resolve-generated DNxHR atoms have these) - Override generic AAF-KLV EssenceContainer label in file descriptor with the Avid DNxHR EC label for DNxHR entries - VideoLineMap set to {1, 0} for progressive DNxHR content Files changed: - include/bmx/EssenceType.h: 5 new enum values - include/bmx/essence_parser/VC3EssenceParser.h: remove fixed min size - include/bmx/mxf_helper/VC3MXFDescriptorHelper.h: frame size setters - src/common/EssenceType.cpp: 5 new type info entries - src/essence_parser/VC3EssenceParser.cpp: variable frame size parsing - src/mxf_helper/VC3MXFDescriptorHelper.cpp: Avid DNxHR labels + fixes - src/avid_mxf/AvidTrack.cpp: sample rate + track factory entries - apps/bmxtranswrap/bmxtranswrap.cpp: DNxHR case handling - apps/raw2bmx/raw2bmx.cpp: CLI options + CID mapping + frame size --- apps/bmxtranswrap/bmxtranswrap.cpp | 18 ++ apps/raw2bmx/raw2bmx.cpp | 140 ++++++++++++++ include/bmx/EssenceType.h | 6 + include/bmx/essence_parser/VC3EssenceParser.h | 3 - .../bmx/mxf_helper/VC3MXFDescriptorHelper.h | 4 + src/avid_mxf/AvidTrack.cpp | 10 + src/common/EssenceType.cpp | 5 + src/essence_parser/VC3EssenceParser.cpp | 178 ++++++++++++------ src/mxf_helper/VC3MXFDescriptorHelper.cpp | 156 +++++++++++++-- 9 files changed, 447 insertions(+), 73 deletions(-) diff --git a/apps/bmxtranswrap/bmxtranswrap.cpp b/apps/bmxtranswrap/bmxtranswrap.cpp index 88c74029..560fdc0a 100644 --- a/apps/bmxtranswrap/bmxtranswrap.cpp +++ b/apps/bmxtranswrap/bmxtranswrap.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -4325,6 +4326,15 @@ int main(int argc, const char** argv) if (afd) clip_track->SetAFD(afd); break; + case VC3_DNXHR_444: + case VC3_DNXHR_HQX: + case VC3_DNXHR_HQ: + case VC3_DNXHR_SQ: + case VC3_DNXHR_LB: + if (afd) + clip_track->SetAFD(afd); + clip_track->SetComponentDepth(input_picture_info->component_depth); + break; case WAVE_PCM: clip_track->SetSamplingRate(output_sound_info->sampling_rate); clip_track->SetQuantizationBits(output_sound_info->bits_per_sample); @@ -4460,6 +4470,14 @@ int main(int argc, const char** argv) if (BMX_OPT_PROP_IS_SET(user_rdd36_opaque)) rdd36_helper->SetIsOpaque(user_rdd36_opaque); } + + VC3MXFDescriptorHelper *vc3_helper = dynamic_cast(pict_helper); + if (vc3_helper) { + if (input_picture_info->display_width > 0) + vc3_helper->SetFrameWidth(input_picture_info->display_width); + if (input_picture_info->display_height > 0) + vc3_helper->SetFrameHeight(input_picture_info->display_height); + } } else if (sound_helper) { if (BMX_OPT_PROP_IS_SET(user_ref_image_edit_rate)) sound_helper->SetReferenceImageEditRate(user_ref_image_edit_rate); diff --git a/apps/raw2bmx/raw2bmx.cpp b/apps/raw2bmx/raw2bmx.cpp index d786035b..601a4c5e 100644 --- a/apps/raw2bmx/raw2bmx.cpp +++ b/apps/raw2bmx/raw2bmx.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include @@ -175,6 +176,7 @@ struct RawInput BMX_OPT_PROP_DECL(uint8_t, afd); BMX_OPT_PROP_DECL(uint32_t, component_depth); uint32_t input_height; + uint32_t input_width; bool have_avci_header; bool d10_fixed_frame_size; BMX_OPT_PROP_DECL(MXFSignalStandard, signal_standard); @@ -853,6 +855,11 @@ static void usage(const char *cmd) printf(" --vc3_720p_1258 Raw VC3/DNxHD 1280x720p 45 Mbps input file\n"); printf(" --vc3_1080p_1259 Raw VC3/DNxHD 1920x1080p 85 Mbps input file\n"); printf(" --vc3_1080i_1260 Raw VC3/DNxHD 1920x1080i 85 Mbps input file\n"); + printf(" --vc3_dnxhr_444 Raw VC3/DNxHR 4:4:4 12-bit input file\n"); + printf(" --vc3_dnxhr_hqx Raw VC3/DNxHR High Quality 12-bit input file\n"); + printf(" --vc3_dnxhr_hq Raw VC3/DNxHR High Quality input file\n"); + printf(" --vc3_dnxhr_sq Raw VC3/DNxHR Standard Quality input file\n"); + printf(" --vc3_dnxhr_lb Raw VC3/DNxHR Low Bandwidth input file\n"); printf(" --pcm Raw PCM audio input file\n"); printf(" --wave Wave PCM audio input file\n"); printf(" --anc Raw ST 436 Ancillary data. Requires the --anc-const option or frame wrapped in KLV and the --klv option\n"); @@ -2095,6 +2102,23 @@ int main(int argc, const char** argv) cmdln_index++; continue; // skip input reset at the end } + else if (strcmp(argv[cmdln_index], "--width") == 0) + { + if (cmdln_index + 1 >= argc) + { + usage_ref(argv[0]); + fprintf(stderr, "Missing argument for Option '%s'\n", argv[cmdln_index]); + return 1; + } + if (!parse_int(argv[cmdln_index + 1], &value) || value == 0) { + usage_ref(argv[0]); + fprintf(stderr, "Invalid value '%s' for Option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]); + return 1; + } + input.input_width = value; + cmdln_index++; + continue; // skip input reset at the end + } else if (strcmp(argv[cmdln_index], "--height") == 0) { if (cmdln_index + 1 >= argc) @@ -4035,6 +4059,71 @@ int main(int argc, const char** argv) inputs.push_back(input); cmdln_index++; } + else if (strcmp(argv[cmdln_index], "--vc3_dnxhr_444") == 0) + { + if (cmdln_index + 1 >= argc) + { + usage_ref(argv[0]); + fprintf(stderr, "Missing argument for input '%s'\n", argv[cmdln_index]); + return 1; + } + input.essence_type = VC3_DNXHR_444; + input.filename = argv[cmdln_index + 1]; + inputs.push_back(input); + cmdln_index++; + } + else if (strcmp(argv[cmdln_index], "--vc3_dnxhr_hqx") == 0) + { + if (cmdln_index + 1 >= argc) + { + usage_ref(argv[0]); + fprintf(stderr, "Missing argument for input '%s'\n", argv[cmdln_index]); + return 1; + } + input.essence_type = VC3_DNXHR_HQX; + input.filename = argv[cmdln_index + 1]; + inputs.push_back(input); + cmdln_index++; + } + else if (strcmp(argv[cmdln_index], "--vc3_dnxhr_hq") == 0) + { + if (cmdln_index + 1 >= argc) + { + usage_ref(argv[0]); + fprintf(stderr, "Missing argument for input '%s'\n", argv[cmdln_index]); + return 1; + } + input.essence_type = VC3_DNXHR_HQ; + input.filename = argv[cmdln_index + 1]; + inputs.push_back(input); + cmdln_index++; + } + else if (strcmp(argv[cmdln_index], "--vc3_dnxhr_sq") == 0) + { + if (cmdln_index + 1 >= argc) + { + usage_ref(argv[0]); + fprintf(stderr, "Missing argument for input '%s'\n", argv[cmdln_index]); + return 1; + } + input.essence_type = VC3_DNXHR_SQ; + input.filename = argv[cmdln_index + 1]; + inputs.push_back(input); + cmdln_index++; + } + else if (strcmp(argv[cmdln_index], "--vc3_dnxhr_lb") == 0) + { + if (cmdln_index + 1 >= argc) + { + usage_ref(argv[0]); + fprintf(stderr, "Missing argument for input '%s'\n", argv[cmdln_index]); + return 1; + } + input.essence_type = VC3_DNXHR_LB; + input.filename = argv[cmdln_index + 1]; + inputs.push_back(input); + cmdln_index++; + } else if (strcmp(argv[cmdln_index], "--pcm") == 0) { if (cmdln_index + 1 >= argc) @@ -4274,6 +4363,16 @@ int main(int argc, const char** argv) } } + // change default component depth for VC-3/DNxHR 12-bit + for (i = 0; i < inputs.size(); i++) { + RawInput *input = &inputs[i]; + if ((input->essence_type == VC3_DNXHR_444 || + input->essence_type == VC3_DNXHR_HQX) && + !BMX_OPT_PROP_IS_SET(input->component_depth)) + { + BMX_OPT_PROP_SET(input->component_depth, 12); + } + } // extract essence info for (i = 0; i < inputs.size(); i++) { @@ -4426,6 +4525,13 @@ int main(int argc, const char** argv) } else { vc3_parser->ParseFrameInfo(input->raw_reader->GetSampleData(), input->raw_reader->GetSampleDataSize()); + if (input->input_width == 0) + input->input_width = vc3_parser->GetFrameWidth(); + if (input->input_height == 0) + input->input_height = vc3_parser->GetFrameHeight(); + + input->raw_reader->SetFixedSampleSize(vc3_parser->GetFrameSize()); + switch (vc3_parser->GetCompressionId()) { case 1235: @@ -4470,6 +4576,22 @@ int main(int argc, const char** argv) case 1260: input->essence_type = VC3_1080I_1260; break; + // DNxHR + case 1270: + input->essence_type = VC3_DNXHR_444; + break; + case 1271: + input->essence_type = VC3_DNXHR_HQX; + break; + case 1272: + input->essence_type = VC3_DNXHR_HQ; + break; + case 1273: + input->essence_type = VC3_DNXHR_SQ; + break; + case 1274: + input->essence_type = VC3_DNXHR_LB; + break; default: log_error("Unknown VC3 essence type\n"); throw false; @@ -5609,6 +5731,11 @@ int main(int argc, const char** argv) case VC3_720P_1258: case VC3_1080P_1259: case VC3_1080I_1260: + case VC3_DNXHR_444: + case VC3_DNXHR_HQX: + case VC3_DNXHR_HQ: + case VC3_DNXHR_SQ: + case VC3_DNXHR_LB: if (BMX_OPT_PROP_IS_SET(input->afd)) clip_track->SetAFD(input->afd); break; @@ -5712,6 +5839,14 @@ int main(int argc, const char** argv) if (BMX_OPT_PROP_IS_SET(input->rdd36_opaque)) rdd36_helper->SetIsOpaque(input->rdd36_opaque); } + + VC3MXFDescriptorHelper *vc3_helper = dynamic_cast(pict_helper); + if (vc3_helper) { + if (input->input_width > 0) + vc3_helper->SetFrameWidth(input->input_width); + if (input->input_height > 0) + vc3_helper->SetFrameHeight(input->input_height); + } } else if (sound_helper) { if (BMX_OPT_PROP_IS_SET(output_sound_info->ref_image_edit_rate)) sound_helper->SetReferenceImageEditRate(output_sound_info->ref_image_edit_rate); @@ -5762,6 +5897,11 @@ int main(int argc, const char** argv) case VC3_720P_1258: case VC3_1080P_1259: case VC3_1080I_1260: + case VC3_DNXHR_444: + case VC3_DNXHR_HQX: + case VC3_DNXHR_HQ: + case VC3_DNXHR_SQ: + case VC3_DNXHR_LB: case UNC_SD: case UNC_HD_1080I: case UNC_HD_1080P: diff --git a/include/bmx/EssenceType.h b/include/bmx/EssenceType.h index bddab831..4b55a489 100644 --- a/include/bmx/EssenceType.h +++ b/include/bmx/EssenceType.h @@ -136,6 +136,12 @@ typedef enum VC3_720P_1258, VC3_1080P_1259, VC3_1080I_1260, + // VC-3, DNxHR + VC3_DNXHR_444, // 1270, RGB 4:4:4, 12-bit + VC3_DNXHR_HQX, // 1271, YCbCr 4:2:2, 12-bit + VC3_DNXHR_HQ, // 1272, YCbCr 4:2:2, 8-bit + VC3_DNXHR_SQ, // 1273, YCbCr 4:2:2, 8-bit + VC3_DNXHR_LB, // 1274, YCbCr 4:2:2, 8-bit // Avid MJPEG MJPEG_2_1, MJPEG_3_1, diff --git a/include/bmx/essence_parser/VC3EssenceParser.h b/include/bmx/essence_parser/VC3EssenceParser.h index 676425b0..ca5ff9e4 100644 --- a/include/bmx/essence_parser/VC3EssenceParser.h +++ b/include/bmx/essence_parser/VC3EssenceParser.h @@ -36,9 +36,6 @@ #include -#define VC3_PARSER_MIN_DATA_SIZE 44 - - namespace bmx { diff --git a/include/bmx/mxf_helper/VC3MXFDescriptorHelper.h b/include/bmx/mxf_helper/VC3MXFDescriptorHelper.h index 69c9ee39..741a5bb3 100644 --- a/include/bmx/mxf_helper/VC3MXFDescriptorHelper.h +++ b/include/bmx/mxf_helper/VC3MXFDescriptorHelper.h @@ -61,6 +61,8 @@ class VC3MXFDescriptorHelper : public PictureMXFDescriptorHelper public: // configure and create new descriptor virtual void SetEssenceType(EssenceType essence_type); + virtual void SetFrameWidth(uint32_t frame_width); + virtual void SetFrameHeight(uint32_t frame_height); virtual mxfpp::FileDescriptor* CreateFileDescriptor(mxfpp::HeaderMetadata *header_metadata); virtual void UpdateFileDescriptor(); @@ -73,6 +75,8 @@ class VC3MXFDescriptorHelper : public PictureMXFDescriptorHelper private: size_t mEssenceIndex; + BMX_OPT_PROP_DECL(uint32_t, mFrameWidth); + BMX_OPT_PROP_DECL(uint32_t, mFrameHeight); }; diff --git a/src/avid_mxf/AvidTrack.cpp b/src/avid_mxf/AvidTrack.cpp index 31d8ab89..64146138 100644 --- a/src/avid_mxf/AvidTrack.cpp +++ b/src/avid_mxf/AvidTrack.cpp @@ -142,6 +142,11 @@ static const AvidSampleRateSupport AVID_SAMPLE_RATE_SUPPORT[] = {VC3_720P_1258, {{-1, -1}, {0, 0}}}, {VC3_1080P_1259, {{-1, -1}, {0, 0}}}, {VC3_1080I_1260, {{-1, -1}, {0, 0}}}, + {VC3_DNXHR_444, {{-1, -1}, {0, 0}}}, + {VC3_DNXHR_HQX, {{-1, -1}, {0, 0}}}, + {VC3_DNXHR_HQ, {{-1, -1}, {0, 0}}}, + {VC3_DNXHR_SQ, {{-1, -1}, {0, 0}}}, + {VC3_DNXHR_LB, {{-1, -1}, {0, 0}}}, {UNC_SD, {{25, 1}, {30000, 1001}, {0, 0}}}, {UNC_HD_1080I, {{25, 1}, {30000, 1001}, {0, 0}}}, {UNC_HD_1080P, {{25, 1}, {30000, 1001}, {30, 1}, {50, 1}, {60000, 1001}, {60, 1}, {0, 0}}}, @@ -253,6 +258,11 @@ AvidTrack* AvidTrack::OpenNew(AvidClip *clip, File *file, uint32_t track_index, case VC3_720P_1258: case VC3_1080P_1259: case VC3_1080I_1260: + case VC3_DNXHR_444: + case VC3_DNXHR_HQX: + case VC3_DNXHR_HQ: + case VC3_DNXHR_SQ: + case VC3_DNXHR_LB: return new AvidVC3Track(clip, track_index, essence_type, file); case UNC_SD: case UNC_HD_1080I: diff --git a/src/common/EssenceType.cpp b/src/common/EssenceType.cpp index 8bb4b591..4fd76918 100644 --- a/src/common/EssenceType.cpp +++ b/src/common/EssenceType.cpp @@ -134,6 +134,11 @@ static const EssenceTypeInfo ESSENCE_TYPE_INFO[] = {VC3_720P_1258, PICTURE_ESSENCE, "VC3 720p 1258", "VC3_720p_1258"}, {VC3_1080P_1259, PICTURE_ESSENCE, "VC3 1080p 1259", "VC3_1080p_1259"}, {VC3_1080I_1260, PICTURE_ESSENCE, "VC3 1080i 1260", "VC3_1080i_1260"}, + {VC3_DNXHR_444, PICTURE_ESSENCE, "VC3 DNxHR 444", "VC3_DNXHR_444"}, + {VC3_DNXHR_HQX, PICTURE_ESSENCE, "VC3 DNxHR HQX", "VC3_DNXHR_HQX"}, + {VC3_DNXHR_HQ, PICTURE_ESSENCE, "VC3 DNxHR HQ", "VC3_DNXHR_HQ"}, + {VC3_DNXHR_SQ, PICTURE_ESSENCE, "VC3 DNxHR SQ", "VC3_DNXHR_SQ"}, + {VC3_DNXHR_LB, PICTURE_ESSENCE, "VC3 DNxHR LB", "VC3_DNXHR_LB"}, {MJPEG_2_1, PICTURE_ESSENCE, "MJPEG 2:1", "MJPEG_2_1"}, {MJPEG_3_1, PICTURE_ESSENCE, "MJPEG 3:1", "MJPEG_3_1"}, {MJPEG_10_1, PICTURE_ESSENCE, "MJPEG 10:1", "MJPEG_10_1"}, diff --git a/src/essence_parser/VC3EssenceParser.cpp b/src/essence_parser/VC3EssenceParser.cpp index ee0af8b0..4d69ad90 100644 --- a/src/essence_parser/VC3EssenceParser.cpp +++ b/src/essence_parser/VC3EssenceParser.cpp @@ -33,6 +33,8 @@ #include "config.h" #endif +#include + #include #include "EssenceParserUtils.h" #include @@ -43,9 +45,9 @@ using namespace std; using namespace bmx; -#define HEADER_PREFIX_HVN0 0x000000028000LL -#define HEADER_PREFIX_S 0x000002800000LL - +#define VC3_MIN_HEADER_SIZE 0x280 +#define VC3_OFFSETS_START 0x168 +#define VARIABLE 0 typedef struct { @@ -55,38 +57,41 @@ typedef struct uint16_t frame_height; uint8_t bit_depth; uint32_t frame_size; + Rational packet_scale; } CompressionParameters; static const CompressionParameters COMPRESSION_PARAMETERS[] = { - {1235, true, 1920, 1080, 10, 917504}, - {1237, true, 1920, 1080, 8, 606208}, - {1238, true, 1920, 1080, 8, 917504}, - {1241, false, 1920, 1080, 10, 917504}, - {1242, false, 1920, 1080, 8, 606208}, - {1243, false, 1920, 1080, 8, 917504}, - {1244, false, 1920, 1080, 8, 606208}, - {1250, true, 1280, 720, 10, 458752}, - {1251, true, 1280, 720, 8, 458752}, - {1252, true, 1280, 720, 8, 303104}, - {1253, true, 1920, 1080, 8, 188416}, - {1258, true, 1280, 720, 8, 212992}, - {1259, true, 1920, 1080, 8, 417792}, - {1260, false, 1920, 1080, 8, 417792}, + {1235, true, 1920, 1080, 10, 917504, ZERO_RATIONAL}, + {1237, true, 1920, 1080, 8, 606208, ZERO_RATIONAL}, + {1238, true, 1920, 1080, 8, 917504, ZERO_RATIONAL}, + {1241, false, 1920, 1080, 10, 917504, ZERO_RATIONAL}, + {1242, false, 1920, 1080, 8, 606208, ZERO_RATIONAL}, + {1243, false, 1920, 1080, 8, 917504, ZERO_RATIONAL}, + {1244, false, 1920, 1080, 8, 606208, ZERO_RATIONAL}, + {1250, true, 1280, 720, 10, 458752, ZERO_RATIONAL}, + {1251, true, 1280, 720, 8, 458752, ZERO_RATIONAL}, + {1252, true, 1280, 720, 8, 303104, ZERO_RATIONAL}, + {1253, true, 1920, 1080, 8, 188416, ZERO_RATIONAL}, + {1258, true, 1280, 720, 8, 212992, ZERO_RATIONAL}, + {1259, true, 1920, 1080, 8, 417792, ZERO_RATIONAL}, + {1260, false, 1920, 1080, 8, 417792, ZERO_RATIONAL}, + {1270, false, VARIABLE, VARIABLE, 12, VARIABLE, {0xe000, 0xff}}, // DNxHR 444 12-bit + {1271, false, VARIABLE, VARIABLE, 12, VARIABLE, {0x7000, 0xff}}, // DNxHR HQX 12-bit + {1272, false, VARIABLE, VARIABLE, 8, VARIABLE, {0x7000, 0xff}}, // DNxHR HQ + {1273, false, VARIABLE, VARIABLE, 8, VARIABLE, {0x4a00, 0xff}}, // DNxHR SQ + {1274, false, VARIABLE, VARIABLE, 8, VARIABLE, {0x1700, 0xff}}, // DNxHR LB }; +static uint32_t get_hr_frame_size(CompressionParameters const& cp, uint32_t w, uint32_t h) +{ + BMX_CHECK(cp.frame_size == VARIABLE); + BMX_CHECK(w > 0 && h > 0); + uint32_t result = ((w + 15) / 16) * ((h + 15) / 16) * cp.packet_scale.numerator / cp.packet_scale.denominator; + result = (result + 2048) & ~0xFFF; -static uint64_t get_uint64(const unsigned char *data) -{ - return (((uint64_t)data[0]) << 56) | - (((uint64_t)data[1]) << 48) | - (((uint64_t)data[2]) << 40) | - (((uint64_t)data[3]) << 32) | - (((uint64_t)data[4]) << 24) | - (((uint64_t)data[5]) << 16) | - (((uint64_t)data[6]) << 8) | - (uint64_t)data[7]; + return max(result, (uint32_t)8192); } static uint32_t get_uint32(const unsigned char *data) @@ -103,6 +108,49 @@ static uint16_t get_uint16(const unsigned char *data) (uint16_t)data[1]; } +static bool vc3_is_header(const unsigned char *data, uint32_t data_size) +{ + if (data_size < VC3_MIN_HEADER_SIZE) return false; + + uint8_t version = data[4]; // 1 or 2 up to full HD, 3 for larger resolutions + uint8_t interlaced = data[5]; // 1, 2 or 3 + + if (version >= 1 && version <= 3 && interlaced >= 1 && interlaced <= 3) + { + uint32_t header_size = get_uint32(data); + uint32_t offsets_size = get_uint32(data + VC3_OFFSETS_START); + uint32_t nb_offsets = get_uint16(data + VC3_OFFSETS_START + 4); + + return (((version < 3 && header_size == VC3_MIN_HEADER_SIZE) + || (version == 3 && header_size >= VC3_MIN_HEADER_SIZE)) + && header_size == VC3_OFFSETS_START + 4 + offsets_size + && offsets_size == nb_offsets * 4 + 4); + } + + return false; +} + +static uint8_t vc3_get_interlaced(const unsigned char *data) +{ + return data[5] & 0x03; +} + +static uint16_t vc3_get_width(const unsigned char *data) +{ + return get_uint16(data + 0x1a); +} + +static uint16_t vc3_get_height(const unsigned char *data) +{ + bool interlaced = vc3_get_interlaced(data) != 1; + return get_uint16(data + 0x18) << interlaced; +} + +static uint32_t vc3_get_compression_id(const unsigned char *data) +{ + return get_uint32(data + 0x28); +} + VC3EssenceParser::VC3EssenceParser() @@ -123,14 +171,11 @@ uint32_t VC3EssenceParser::ParseFrameStart(const unsigned char *data, uint32_t d { BMX_CHECK(data_size != ESSENCE_PARSER_NULL_OFFSET); - uint64_t state = 0; - uint32_t i; - for (i = 0; i < data_size; i++) { - state = (state << 8) | data[i]; - if ((state & 0xffffffff0000LL) == HEADER_PREFIX_S && - (state & 0x000000000003LL) < 3) // coding unit is progressive frame or field 1 + for (uint32_t i = 0; i < data_size; i++) { + if (vc3_is_header(data + i, data_size - i) && + vc3_get_interlaced(data + i) < 3) // coding unit is progressive frame or field 1 { - return i - 5; + return i; } } @@ -145,21 +190,31 @@ uint32_t VC3EssenceParser::ParseFrameSize(const unsigned char *data, uint32_t da { BMX_CHECK(data_size != ESSENCE_PARSER_NULL_OFFSET); - if (data_size < VC3_PARSER_MIN_DATA_SIZE) + if (data_size < VC3_MIN_HEADER_SIZE) return ESSENCE_PARSER_NULL_OFFSET; - // check header prefix - uint64_t prefix = get_uint64(data) >> 24; - BMX_CHECK( (prefix & 0xffffffff00LL) == HEADER_PREFIX_HVN0 && - ((prefix & 0x00000000ffLL) == 1 || (prefix & 0x00000000ffLL) == 2)); + // check header + BMX_CHECK(vc3_is_header(data, data_size)); - uint32_t compression_id = get_uint32(data + 40); + uint32_t compression_id = vc3_get_compression_id(data); - size_t i; - for (i = 0; i < BMX_ARRAY_SIZE(COMPRESSION_PARAMETERS); i++) + for (size_t i = 0; i < BMX_ARRAY_SIZE(COMPRESSION_PARAMETERS); i++) { - if (compression_id == COMPRESSION_PARAMETERS[i].compression_id) - return COMPRESSION_PARAMETERS[i].frame_size; + if (compression_id == COMPRESSION_PARAMETERS[i].compression_id) { + uint32_t frame_size = COMPRESSION_PARAMETERS[i].frame_size; + + if (frame_size == VARIABLE) + { + uint32_t w = vc3_get_width(data); + uint32_t h = vc3_get_height(data); + frame_size = get_hr_frame_size(COMPRESSION_PARAMETERS[i], w, h); + } + + if (data_size >= frame_size) + return frame_size; + else + return ESSENCE_PARSER_NULL_OFFSET; + } } return ESSENCE_PARSER_NULL_FRAME_SIZE; @@ -168,15 +223,11 @@ uint32_t VC3EssenceParser::ParseFrameSize(const unsigned char *data, uint32_t da void VC3EssenceParser::ParseFrameInfo(const unsigned char *data, uint32_t data_size) { BMX_CHECK(data_size != ESSENCE_PARSER_NULL_OFFSET); - BMX_CHECK(data_size >= VC3_PARSER_MIN_DATA_SIZE); - - // check header prefix - uint64_t prefix = get_uint64(data) >> 24; - BMX_CHECK( (prefix & 0xffffffff00LL) == HEADER_PREFIX_HVN0 && - ((prefix & 0x00000000ffLL) == 1 || (prefix & 0x00000000ffLL) == 2)); + BMX_CHECK(data_size >= VC3_MIN_HEADER_SIZE); + BMX_CHECK(vc3_is_header(data, data_size)); // compression id - mCompressionId = get_uint32(data + 40); + mCompressionId = vc3_get_compression_id(data); size_t param_index; for (param_index = 0; param_index < BMX_ARRAY_SIZE(COMPRESSION_PARAMETERS); param_index++) { @@ -185,25 +236,36 @@ void VC3EssenceParser::ParseFrameInfo(const unsigned char *data, uint32_t data_s } BMX_CHECK(param_index < BMX_ARRAY_SIZE(COMPRESSION_PARAMETERS)); + CompressionParameters const& cp = COMPRESSION_PARAMETERS[param_index]; + // Note: found that an Avid MC v3.0 file containing 1252 720p50 had FFC=01h and SST=1; // SST should be 0 for progressive scan. DNxHD_Compliance_Issue_To_Licensees-1.doc // states that some Avid bitstreams may have SST incorrectly set to 1080i // Ignore the bitstream information and use the scan type associated with the compression id - mIsProgressive = COMPRESSION_PARAMETERS[param_index].is_progressive; + mIsProgressive = cp.is_progressive; // image geometry + mFrameWidth = vc3_get_width(data); + mFrameHeight = vc3_get_height(data); + // Note: DNxHD_Compliance_Issue_To_Licensees-1.doc states that some Avid bitstreams, // e.g. produced by Avid Media Composer 3.0, may have ALPF incorrectly set to 1080 for // 1080i sources. Ignore the bitstream information and use the frame height associated // with the compression id - mFrameHeight = COMPRESSION_PARAMETERS[param_index].frame_height; - mFrameWidth = get_uint16(data + 26); - BMX_CHECK(mFrameWidth == COMPRESSION_PARAMETERS[param_index].frame_width); + if (cp.frame_height != VARIABLE) { + mFrameHeight = cp.frame_height; + } + uint32_t sbd_bits = get_bits(data, data_size, 33 * 8, 3); BMX_CHECK(sbd_bits == 2 || sbd_bits == 1); mBitDepth = (sbd_bits == 2 ? 10 : 8); - BMX_CHECK(mBitDepth == COMPRESSION_PARAMETERS[param_index].bit_depth); - mFrameSize = COMPRESSION_PARAMETERS[param_index].frame_size; -} + if (cp.frame_size == VARIABLE) { + mFrameSize = get_hr_frame_size(cp, mFrameWidth, mFrameHeight); + } else { + mFrameSize = cp.frame_size; + } + BMX_CHECK(cp.frame_width == VARIABLE || mFrameWidth == cp.frame_width); + BMX_CHECK(mBitDepth >= 8 && mBitDepth <= cp.bit_depth); +} diff --git a/src/mxf_helper/VC3MXFDescriptorHelper.cpp b/src/mxf_helper/VC3MXFDescriptorHelper.cpp index 55053cd4..dd793344 100644 --- a/src/mxf_helper/VC3MXFDescriptorHelper.cpp +++ b/src/mxf_helper/VC3MXFDescriptorHelper.cpp @@ -33,6 +33,7 @@ #include "config.h" #endif +#include #include #include @@ -46,6 +47,20 @@ using namespace std; using namespace bmx; using namespace mxfpp; +/* + * MXF AVID labels for DNxHR + */ +#define MXF_AVID_BASE_L(b8, b9, b10, b11, b12, b13, b14, b15, b16) \ + {0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, b8, b9, b10, b11, b12, b13, b14, b15, b16} + +#define MXF_AVID_DNXHR_EC_L MXF_AVID_BASE_L(0x0a, 0x0d, 0x01, 0x03, 0x01, 0x02, 0x11, 0x02, 0x00) +#define MXF_DNXHR_CMDEF_L(byte14) MXF_AVID_BASE_L(0x0d, 0x04, 0x01, 0x02, 0x02, 0x71, byte14, 0x00, 0x00) + +static const mxfUL MXF_CMDEF_L(VC3_DNXHR_444) = MXF_DNXHR_CMDEF_L(0x24); +static const mxfUL MXF_CMDEF_L(VC3_DNXHR_HQX) = MXF_DNXHR_CMDEF_L(0x25); +static const mxfUL MXF_CMDEF_L(VC3_DNXHR_HQ) = MXF_DNXHR_CMDEF_L(0x26); +static const mxfUL MXF_CMDEF_L(VC3_DNXHR_SQ) = MXF_DNXHR_CMDEF_L(0x27); +static const mxfUL MXF_CMDEF_L(VC3_DNXHR_LB) = MXF_DNXHR_CMDEF_L(0x28); typedef struct @@ -67,6 +82,8 @@ typedef struct mxfUL avid_ec_label; } SupportedEssence; +enum { VARI = 0 }; + static const SupportedEssence SUPPORTED_ESSENCE[] = { {MXF_CMDEF_L(VC3_1080P_1235), VC3_1080P_1235, 1235, 10, 2, 917504, 1920, 1080, 1920, 1080, {42, 0}, MXF_FULL_FRAME, MXF_SIGNAL_STANDARD_SMPTE274M, MXF_CMDEF_L(DNxHD), MXF_EC_L(DNxHD1080p1235ClipWrapped)}, @@ -83,9 +100,45 @@ static const SupportedEssence SUPPORTED_ESSENCE[] = {MXF_CMDEF_L(VC3_720P_1258), VC3_720P_1258, 1258, 8, 2, 212992, 960, 720, 1280, 720, {26, 0}, MXF_FULL_FRAME, MXF_SIGNAL_STANDARD_SMPTE296M, MXF_CMDEF_L(DNxHD), MXF_EC_L(DNxHD720p1258ClipWrapped)}, {MXF_CMDEF_L(VC3_1080P_1259), VC3_1080P_1259, 1259, 8, 2, 417792, 1440, 1080, 1920, 1080, {42, 0}, MXF_FULL_FRAME, MXF_SIGNAL_STANDARD_SMPTE274M, MXF_CMDEF_L(DNxHD), MXF_EC_L(DNxHD1080p1259ClipWrapped)}, {MXF_CMDEF_L(VC3_1080I_1260), VC3_1080I_1260, 1260, 8, 2, 417792, 1440, 540, 1920, 540, {21, 584}, MXF_SEPARATE_FIELDS, MXF_SIGNAL_STANDARD_SMPTE274M, MXF_CMDEF_L(DNxHD), MXF_EC_L(DNxHD1080i1260ClipWrapped)}, + {MXF_CMDEF_L(VC3_DNXHR_444), VC3_DNXHR_444, 1270, 12, 1, VARI, VARI, VARI, VARI, VARI, {1, 0}, MXF_FULL_FRAME, MXF_SIGNAL_STANDARD_SMPTE274M, MXF_CMDEF_L(VC3_DNXHR_444), MXF_AVID_DNXHR_EC_L}, + {MXF_CMDEF_L(VC3_DNXHR_HQX), VC3_DNXHR_HQX, 1271, 12, 2, VARI, VARI, VARI, VARI, VARI, {1, 0}, MXF_FULL_FRAME, MXF_SIGNAL_STANDARD_SMPTE274M, MXF_CMDEF_L(VC3_DNXHR_HQX), MXF_AVID_DNXHR_EC_L}, + {MXF_CMDEF_L(VC3_DNXHR_HQ), VC3_DNXHR_HQ, 1272, 8, 2, VARI, VARI, VARI, VARI, VARI, {1, 0}, MXF_FULL_FRAME, MXF_SIGNAL_STANDARD_SMPTE274M, MXF_CMDEF_L(VC3_DNXHR_HQ), MXF_AVID_DNXHR_EC_L}, + {MXF_CMDEF_L(VC3_DNXHR_SQ), VC3_DNXHR_SQ, 1273, 8, 2, VARI, VARI, VARI, VARI, VARI, {1, 0}, MXF_FULL_FRAME, MXF_SIGNAL_STANDARD_SMPTE274M, MXF_CMDEF_L(VC3_DNXHR_SQ), MXF_AVID_DNXHR_EC_L}, + {MXF_CMDEF_L(VC3_DNXHR_LB), VC3_DNXHR_LB, 1274, 8, 2, VARI, VARI, VARI, VARI, VARI, {1, 0}, MXF_FULL_FRAME, MXF_SIGNAL_STANDARD_SMPTE274M, MXF_CMDEF_L(VC3_DNXHR_LB), MXF_AVID_DNXHR_EC_L}, +}; + +typedef struct +{ + int32_t resolution_id; + Rational packet_scale; +} CompressionParams; + +static const CompressionParams COMPRESSION_PARAMS[] = +{ + {1270, {0xe000, 0xff}}, // DNxHR 444 12-bit + {1271, {0x7000, 0xff}}, // DNxHR HQX 12-bit + {1272, {0x7000, 0xff}}, // DNxHR HQ + {1273, {0x4a00, 0xff}}, // DNxHR SQ + {1274, {0x1700, 0xff}}, // DNxHR LB }; +static uint32_t get_hr_frame_size(int32_t resolution_id, uint32_t w, uint32_t h) +{ + size_t param_index; + for (param_index = 0; param_index < BMX_ARRAY_SIZE(COMPRESSION_PARAMS); param_index++) + { + if (COMPRESSION_PARAMS[param_index].resolution_id == resolution_id) + break; + } + BMX_CHECK(param_index < BMX_ARRAY_SIZE(COMPRESSION_PARAMS)); + + Rational packet_scale = COMPRESSION_PARAMS[param_index].packet_scale; + uint32_t result = ((w + 15) / 16) * ((h + 15) / 16) * packet_scale.numerator / packet_scale.denominator; + result = (result + 2048) & ~0xFFF; + return max(result, (uint32_t)8192); +} + EssenceType VC3MXFDescriptorHelper::IsSupported(FileDescriptor *file_descriptor, mxfUL alternative_ec_label) { @@ -152,6 +205,8 @@ VC3MXFDescriptorHelper::VC3MXFDescriptorHelper() { mEssenceIndex = 0; mEssenceType = SUPPORTED_ESSENCE[0].essence_type; + BMX_OPT_PROP_DEFAULT(mFrameWidth, 0); + BMX_OPT_PROP_DEFAULT(mFrameHeight, 0); } VC3MXFDescriptorHelper::~VC3MXFDescriptorHelper() @@ -185,6 +240,15 @@ void VC3MXFDescriptorHelper::Initialize(FileDescriptor *file_descriptor, uint16_ } } } + + if (SUPPORTED_ESSENCE[mEssenceIndex].stored_width == VARI) { + GenericPictureEssenceDescriptor *picture_descriptor = dynamic_cast(file_descriptor); + BMX_ASSERT(picture_descriptor); + BMX_CHECK(picture_descriptor->haveDisplayWidth() && picture_descriptor->haveDisplayHeight()); + + BMX_OPT_PROP_SET(mFrameWidth, picture_descriptor->getDisplayWidth()); + BMX_OPT_PROP_SET(mFrameHeight, picture_descriptor->getDisplayHeight()); + } } void VC3MXFDescriptorHelper::SetEssenceType(EssenceType essence_type) @@ -195,7 +259,11 @@ void VC3MXFDescriptorHelper::SetEssenceType(EssenceType essence_type) for (i = 0; i < BMX_ARRAY_SIZE(SUPPORTED_ESSENCE); i++) { if (SUPPORTED_ESSENCE[i].essence_type == essence_type) { mEssenceIndex = i; - mAvidResolutionId = SUPPORTED_ESSENCE[i].resolution_id; + // DNxHR: do NOT write Avid ResolutionID (golden doesn't have it) + if (SUPPORTED_ESSENCE[i].frame_size == VARI) + mAvidResolutionId = 0; + else + mAvidResolutionId = SUPPORTED_ESSENCE[i].resolution_id; break; } } @@ -204,6 +272,16 @@ void VC3MXFDescriptorHelper::SetEssenceType(EssenceType essence_type) PictureMXFDescriptorHelper::SetEssenceType(essence_type); } +void VC3MXFDescriptorHelper::SetFrameWidth(uint32_t frame_width) +{ + BMX_OPT_PROP_SET(mFrameWidth, frame_width); +} + +void VC3MXFDescriptorHelper::SetFrameHeight(uint32_t frame_height) +{ + BMX_OPT_PROP_SET(mFrameHeight, frame_height); +} + FileDescriptor* VC3MXFDescriptorHelper::CreateFileDescriptor(mxfpp::HeaderMetadata *header_metadata) { mFileDescriptor = new CDCIEssenceDescriptor(header_metadata); @@ -218,15 +296,33 @@ void VC3MXFDescriptorHelper::UpdateFileDescriptor() CDCIEssenceDescriptor *cdci_descriptor = dynamic_cast(mFileDescriptor); BMX_ASSERT(cdci_descriptor); + // DNxHR: override the generic AAF-KLV EC label with the actual DNxHR EC label + // (parent sets AvidAAFKLVEssenceContainer for all Avid flavour, but DNxHR needs its own) + if ((mFlavour & MXFDESC_AVID_FLAVOUR) && SUPPORTED_ESSENCE[mEssenceIndex].frame_size == VARI) { + mFileDescriptor->setEssenceContainer(SUPPORTED_ESSENCE[mEssenceIndex].avid_ec_label); + } + if ((mFlavour & MXFDESC_AVID_FLAVOUR)) cdci_descriptor->setPictureEssenceCoding(SUPPORTED_ESSENCE[mEssenceIndex].avid_pc_label); else cdci_descriptor->setPictureEssenceCoding(SUPPORTED_ESSENCE[mEssenceIndex].pc_label); - cdci_descriptor->setSignalStandard(SUPPORTED_ESSENCE[mEssenceIndex].signal_standard); + // DNxHR: do NOT write SignalStandard (golden and Resolve don't have it) + if (SUPPORTED_ESSENCE[mEssenceIndex].frame_size != VARI) + cdci_descriptor->setSignalStandard(SUPPORTED_ESSENCE[mEssenceIndex].signal_standard); cdci_descriptor->setFrameLayout(SUPPORTED_ESSENCE[mEssenceIndex].frame_layout); SetColorSitingMod(MXF_COLOR_SITING_REC601); cdci_descriptor->setComponentDepth(SUPPORTED_ESSENCE[mEssenceIndex].component_depth); - if (SUPPORTED_ESSENCE[mEssenceIndex].component_depth == 10) { + if (SUPPORTED_ESSENCE[mEssenceIndex].component_depth == 12) { + // DNxHR 12-bit + cdci_descriptor->setBlackRefLevel(64); + cdci_descriptor->setWhiteReflevel(940); + cdci_descriptor->setColorRange(897); + } else if (SUPPORTED_ESSENCE[mEssenceIndex].frame_size == VARI) { + // DNxHR 8-bit (LB/SQ/HQ) — Avid uses non-standard levels + cdci_descriptor->setBlackRefLevel(23); + cdci_descriptor->setWhiteReflevel(149); + cdci_descriptor->setColorRange(225); + } else if (SUPPORTED_ESSENCE[mEssenceIndex].component_depth == 10) { cdci_descriptor->setBlackRefLevel(64); cdci_descriptor->setWhiteReflevel(940); cdci_descriptor->setColorRange(897); @@ -235,11 +331,29 @@ void VC3MXFDescriptorHelper::UpdateFileDescriptor() cdci_descriptor->setWhiteReflevel(235); cdci_descriptor->setColorRange(225); } - SetCodingEquationsMod(ITUR_BT709_CODING_EQ); - cdci_descriptor->setStoredWidth(SUPPORTED_ESSENCE[mEssenceIndex].stored_width); - cdci_descriptor->setStoredHeight(SUPPORTED_ESSENCE[mEssenceIndex].stored_height); - cdci_descriptor->setDisplayWidth(SUPPORTED_ESSENCE[mEssenceIndex].display_width); - cdci_descriptor->setDisplayHeight(SUPPORTED_ESSENCE[mEssenceIndex].display_height); + if (SUPPORTED_ESSENCE[mEssenceIndex].frame_size == VARI) { + // DNxHR: write CodingEquations as straight UL, not AUID half-swapped + // (Avid DNxHR uses standard UL format, unlike DNxHD which uses AAF AUID) + GenericPictureEssenceDescriptor *pic_desc = dynamic_cast(mFileDescriptor); + pic_desc->setCodingEquations(ITUR_BT709_CODING_EQ); + // DNxHR: add TransferCharacteristic and ColorPrimaries (both working files have them) + pic_desc->setCaptureGamma(ITUR_BT709_TRANSFER_CH); + pic_desc->setColorPrimaries(ITU709_COLOR_PRIM); + } else { + SetCodingEquationsMod(ITUR_BT709_CODING_EQ); + } + if (SUPPORTED_ESSENCE[mEssenceIndex].stored_width == VARI) { + BMX_CHECK(BMX_OPT_PROP_IS_SET(mFrameWidth) && BMX_OPT_PROP_IS_SET(mFrameHeight)); + cdci_descriptor->setStoredWidth(mFrameWidth); + cdci_descriptor->setStoredHeight(mFrameHeight); + cdci_descriptor->setDisplayWidth(mFrameWidth); + cdci_descriptor->setDisplayHeight(mFrameHeight); + } else { + cdci_descriptor->setStoredWidth(SUPPORTED_ESSENCE[mEssenceIndex].stored_width); + cdci_descriptor->setStoredHeight(SUPPORTED_ESSENCE[mEssenceIndex].stored_height); + cdci_descriptor->setDisplayWidth(SUPPORTED_ESSENCE[mEssenceIndex].display_width); + cdci_descriptor->setDisplayHeight(SUPPORTED_ESSENCE[mEssenceIndex].display_height); + } cdci_descriptor->setSampledWidth(cdci_descriptor->getDisplayWidth()); cdci_descriptor->setSampledHeight(cdci_descriptor->getDisplayHeight()); if ((mFlavour & MXFDESC_AVID_FLAVOUR)) { @@ -251,13 +365,32 @@ void VC3MXFDescriptorHelper::UpdateFileDescriptor() cdci_descriptor->setVideoLineMap(SUPPORTED_ESSENCE[mEssenceIndex].video_line_map); cdci_descriptor->setHorizontalSubsampling(SUPPORTED_ESSENCE[mEssenceIndex].horiz_subsampling); cdci_descriptor->setVerticalSubsampling(1); - if ((mFlavour & MXFDESC_AVID_FLAVOUR)) - cdci_descriptor->setImageAlignmentOffset(8192); + if ((mFlavour & MXFDESC_AVID_FLAVOUR)) { + if (SUPPORTED_ESSENCE[mEssenceIndex].frame_size == VARI) { + cdci_descriptor->setImageAlignmentOffset(4096); // Avid aligns DNxHR to 4096 bytes + // DNxHR: set aspect ratio based on frame dimensions + if (BMX_OPT_PROP_IS_SET(mFrameWidth) && BMX_OPT_PROP_IS_SET(mFrameHeight)) { + mxfRational aspect_ratio; + aspect_ratio.numerator = 256; + aspect_ratio.denominator = 135; + cdci_descriptor->setAspectRatio(aspect_ratio); + } + } else { + cdci_descriptor->setImageAlignmentOffset(8192); + } + } } uint32_t VC3MXFDescriptorHelper::GetSampleSize() { - return SUPPORTED_ESSENCE[mEssenceIndex].frame_size; + uint32_t frame_size = SUPPORTED_ESSENCE[mEssenceIndex].frame_size; + + if (frame_size == VARI) { + BMX_CHECK(BMX_OPT_PROP_IS_SET(mFrameWidth) && BMX_OPT_PROP_IS_SET(mFrameHeight)); + frame_size = get_hr_frame_size(SUPPORTED_ESSENCE[mEssenceIndex].resolution_id, mFrameWidth, mFrameHeight); + } + + return frame_size; } mxfUL VC3MXFDescriptorHelper::ChooseEssenceContainerUL() const @@ -272,4 +405,3 @@ mxfUL VC3MXFDescriptorHelper::ChooseEssenceContainerUL() const return MXF_EC_L(VC3ClipWrapped); } } -