dolby_vision-3.3.1/.cargo_vcs_info.json0000644000000001520000000000100135360ustar { "git": { "sha1": "83e1fdad6dcd5995556235946e7c5c0f9010d5a1" }, "path_in_vcs": "dolby_vision" }dolby_vision-3.3.1/.gitignore000064400000000000000000000001331046102023000143150ustar 00000000000000target **/*.rs.bk /*.mkv /*.hevc /*.txt /*.vpy /*.bin /*.ffindex /*.json /*.xml Cargo.lock dolby_vision-3.3.1/CHANGELOG.md000064400000000000000000000133261046102023000141460ustar 00000000000000## 3.3.1 - Added support for parsing `ext_mapping_idc` in `RpuDataHeader`. - `ext_mapping_idc_0_4` represents the 5 lowest bits, and `ext_mapping_idc_5_7` the 3 remaining bits. C API: - Added `dovi_parse_itu_t35_dovi_metadata_obu` function. ## 3.3.0 - Changed AV1 function signatures to take slices as input and return a `Vec`. - Added `write_av1_rpu_metadata_obu_t35_complete` function to encode RPUs in complete metadata OBU payloads. - XML parser: support decimals when parsing Level6 MaxCLL/MaxFALL values. - Added `DoviRpu::parse_itu_t35_dovi_metadata_obu` and deprecated `av1::parse_itu_t35_dovi_metadata_obu`. - Fixed encoding AV1 payloads with trailing bytes. They are now discarded. C API: - Added `dovi_write_av1_rpu_metadata_obu_t35_{payload,complete}` functions. ## 3.2.0 - Deprecated `RpuDataHeader.rpu_nal_prefix`. - Added `av1` module for handling AV1 Dolby Vision ITU-T T.35 metadata OBU payloads. - AV1 RPU bytes can now be encoded with `write_av1_rpu_metadata_obu_t35_payload`. - The payload is meant to be used for `itu_t_t35_payload_bytes`. ## 3.1.1 - Fixed RPU writing edge case that resulted in non conformant NALU bytes when using `write_hevc_unspec62_nalu`. ## 3.1.0 - Conversion mode 2 now defaults to remove luma and chroma mapping by default, only for profile 7 FEL. - Added `ConversionMode::To81MappingPreserved` for old mode 2 behaviour. ## 3.0.0 - Breaking changes from `RpuDataMapping` refactor. - Renamed `serde_feature` to simply `serde`. - Moved some fields from `RpuDataHeader` into `RpuDataMapping`. - `RpuDataNlq` is now part of `RpuDataMapping`. - The mapping now has one curve per component, which is a `DoviReshapingCurve`. - `DoviReshapingCurve` contains the pivots, mapping method and the respective curve params. - Component 0 describes the polynomial params in `DoviPolynomialCurve`. - Components 1 and 2 can be either polynomial or the MMR params in `DoviMMRCurve`. - Polynomial interpolation fields were removed as there are no existing samples. - `RpuDataNlq` was changed to contain only one set of params, as there is no significant pivot. - All `_minus_X` suffixed names were uniformized as `minusX`. The changes also affect the C API. C API: - Added `dovi_rpu_set_active_area_offsets` function to edit L5 metadata. - Added `dovi_rpu_remove_mapping` function. ## 2.1.0 - Made some parsing functions private, as they were always meant to be internal only. - Replaced `DoviRpu::trailing_bytes` by `trailing_zeroes`, which is only the count of the zero bytes. - Changed `DoviRpu::subprofile` to `&str`. ## 2.0.1 - Added `replace_levels_from_rpu` function to `DoviRpu`. - Added `l1_avg_pq_cm_version` to `GenerateConfig`. - Allows overriding the minimum L1 `avg_pq` CM version. - Example use case: Some grades are done in `CM v4.0` but distributed as `CM v2.9` RPU. ## 2.0.0 - Modified `extension_metadata::blocks` parsing functions to return a `Result`. ## 1.7.1 - Add `ExtMetadataBlockLevel1` constructor `new` and `from_stats_cm_version`. - Add `clamp_values_cm_version` function to `ExtMetadataBlockLevel1`. - Deprecated `ExtMetadataBlockLevel1` functions `from_stats` and `clamp_values`. ## 1.7.0 - Add `clamp_values` function to `ExtMetadataBlockLevel1`. - Add `fixup_l1` function to `GenerateConfig`. - Allow replacing `L254` extension metadata blocks. ## 1.6.7 - Add `rpu::utils` module, and `parse_rpu_file` helper function. - Made `bitvec_ser_bits` private as it shouldn't be exposed. - Added `dovi_parse_rpu_bin_file` to C API functions. - Fixed memory leaks from errors in the C API. ## 1.6.6 - Add `ConversionMode` enum to use with `DoviRpu::convert_with_mode`. - Added support to generate profile 5 RPUs. - Added long play mode RPU generation. - This sets `scene_refresh_flag` to `1` for every frame. - Deprecated `DoviRpu::convert_to_cmv4` as it can lead to playback issues. ## 1.6.5 - Breaking: Made `GenerateConfig::level6` optional. - Added `profile` to `GenerateConfig` to support profile 8.1 and 8.4. ## 1.6.4 - Add `DoviRpu::convert_to_cmv40` helper method. - Add `DoviRpu::subprofile` field, for profile 7 FEL or MEL. ## 1.6.3 - Add support for compressed RPU format. - Add `RpuDataHeader::nlq_pred_pivot_value` field, parsed for profile 4 and 7. ## 1.6.2 - Updated `bitvec` dependency to 1.0.0. - Allowed noop conversion when converting a profile 8 RPU with mode 2. - Removed `last_byte` field from `DoviRpu`, replaced by `trailing_bytes` Vec. - Fixes parsing when the NAL has multiple trailing 0 bytes. ## 1.6.1 - Add support for variable length blocks: L8, L9, L10. - Add L9 metadata by default when generating CM v4.0 RPUs. - Add support for L255 block in DM v1 payload. XML parser: - Improve specific version support up to XML version 5.1.0. - Add L10/L11 metadata parsing from XML. ## 1.6.0 - Fixed deserialize default value for `GenerateConfig`.`cm_version` field. - Added `default_metadata_blocks` to `GenerateConfig` struct. - Removed `target_nits` field from `GenerateConfig`. Use default blocks. ## 1.5.2 Changed DM data logic to write the number of blocks and align even if there are none. ## 1.5.1 Fix bug where metadata blocks were reordered after parsing, altering the final CRC32. ## 1.5.0 A bunch of breaking changes to add CMv4.0. Reworked extension metadata, different DM data payloads. C API: - Renamed `st2094_10_metadata` to `dm_data` in `DoviVdrDmData`. - Added more levels to `dm_data` struct. ## 1.4.0 Fixed L3/L4 metadata block sizes. Renamed `ExtMetadataBlock` functions: - `length` -> `length_bytes` - `bits` -> `length_bits` ## 1.3.1 Conditional initializations of `Vec`s in `RpuDataMapping` and `RpuDataNlq` structs. - Represents the actual parsed metadata better, instead of being defaulted to 0. Added `guessed_profile` field to C API `DoviRpuDataHeader` struct. dolby_vision-3.3.1/Cargo.toml0000644000000041370000000000100115430ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.79.0" name = "dolby_vision" version = "3.3.1" authors = ["quietvoid"] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Dolby Vision metadata parsing and writing" readme = "README.md" license = "MIT" repository = "https://github.com/quietvoid/dovi_tool/tree/main/dolby_vision" [package.metadata.capi.header] name = "rpu_parser" subdirectory = "libdovi" [package.metadata.capi.library] name = "dovi" rustflags = "-Cpanic=abort" [package.metadata.capi.pkg_config] filename = "dovi" name = "dovi" strip_include_path_components = 1 subdirectory = false [package.metadata.docs.rs] all-features = true [lib] name = "dolby_vision" path = "src/lib.rs" doctest = false [[bench]] name = "bench_main" path = "benches/bench_main.rs" harness = false [dependencies.anyhow] version = "1.0.86" [dependencies.bitvec] version = "1.0.1" [dependencies.bitvec_helpers] version = "3.1.5" features = ["bitstream-io"] default-features = false [dependencies.crc] version = "3.2.1" [dependencies.libc] version = "0.2" optional = true [dependencies.roxmltree] version = "0.20.0" optional = true [dependencies.serde] version = "1.0.203" features = ["derive"] optional = true [dependencies.serde_json] version = "1.0.117" features = ["preserve_order"] optional = true [dependencies.tinyvec] version = "1.6.0" features = ["rustc_1_55"] [dev-dependencies.criterion] version = "0.5.1" [features] capi = ["libc"] serde = [ "dep:serde", "dep:serde_json", "tinyvec/serde", ] xml = ["roxmltree"] [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = ["cfg(cargo_c)"] dolby_vision-3.3.1/Cargo.toml.orig000064400000000000000000000025211046102023000152170ustar 00000000000000[package] name = "dolby_vision" version = "3.3.1" authors = ["quietvoid"] edition = "2021" rust-version = "1.79.0" license = "MIT" description = "Dolby Vision metadata parsing and writing" repository = "https://github.com/quietvoid/dovi_tool/tree/main/dolby_vision" [dependencies] bitvec_helpers = { version = "3.1.5", default-features = false, features = ["bitstream-io"] } anyhow = "1.0.86" bitvec = "1.0.1" crc = "3.2.1" roxmltree = { version = "0.20.0", optional = true } serde = { version = "1.0.203", features = ["derive"], "optional" = true } serde_json = { version = "1.0.117", features = ["preserve_order"], "optional" = true } tinyvec = { version = "1.6.0", features = ["rustc_1_55"] } libc = { version = "0.2", optional = true } [dev-dependencies] criterion = "0.5.1" [features] xml = ["roxmltree"] serde = ["dep:serde", "dep:serde_json", "tinyvec/serde"] capi = ["libc"] [package.metadata.docs.rs] all-features = true [package.metadata.capi.header] subdirectory = "libdovi" name = "rpu_parser" [package.metadata.capi.pkg_config] strip_include_path_components = 1 subdirectory = false name = "dovi" filename = "dovi" [package.metadata.capi.library] rustflags = "-Cpanic=abort" name = "dovi" [lib] doctest = false [[bench]] name = "bench_main" harness = false [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(cargo_c)'] } dolby_vision-3.3.1/LICENSE000064400000000000000000000020521046102023000133340ustar 00000000000000MIT License Copyright (c) 2024 quietvoid 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. dolby_vision-3.3.1/README.md000064400000000000000000000012411046102023000136050ustar 00000000000000Library to read & write Dolby Vision metadata. Comes as a Rust crate and C compatible library. See [changelog](CHANGELOG.md) for API changes.   ### Toolchain The minimum Rust version to use `dolby_vision` is 1.79.0.   ### `libdovi`, C-API Packages - **Arch Linux**: available on the AUR, `libdovi` or `libdovi-git`.   #### Building the library `libdovi` comes as a C compatible library. To build and install it you can use [cargo-c](https://crates.io/crates/cargo-c): ```sh cargo install cargo-c cargo cinstall --release ``` #### Running the C-API example ```sh cd examples gcc capi_rpu_file.c -ldovi -o capi_example.o ./capi_example.o ``` dolby_vision-3.3.1/benches/bench_main.rs000064400000000000000000000002221046102023000163640ustar 00000000000000use criterion::criterion_main; mod benchmarks; criterion_main! { benchmarks::parsing::parse_rpus, benchmarks::rewriting::rewrite_rpus } dolby_vision-3.3.1/benches/benchmarks/mod.rs000064400000000000000000000000441046102023000171770ustar 00000000000000pub mod parsing; pub mod rewriting; dolby_vision-3.3.1/benches/benchmarks/parsing.rs000064400000000000000000000021501046102023000200630ustar 00000000000000use std::{ fs::File, io::Read, path::{Path, PathBuf}, }; use criterion::{criterion_group, Criterion}; use dolby_vision::rpu::dovi_rpu::DoviRpu; const RPU_FILES: &[&str] = &[ "profile5.bin", "profile8.bin", "fel_orig.bin", "mel_variable_l8_length13.bin", "cmv40_full_rpu.bin", "unordered_l8_blocks.bin", ]; fn get_bytes>(path: P) -> Vec { let mut buf = Vec::with_capacity(500); File::open(path).unwrap().read_to_end(&mut buf).unwrap(); buf } pub fn parse_single_unspec62_nalu(data: &[u8]) { DoviRpu::parse_unspec62_nalu(data).unwrap(); } fn parse_single_unspec62_nalu_benchmark(c: &mut Criterion) { let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let assets_path = lib_path.parent().unwrap().join("assets/tests"); let mut group = c.benchmark_group("parse_single_unspec62_nalu"); for file in RPU_FILES { let bytes = get_bytes(assets_path.join(file)); group.bench_function(*file, |b| b.iter(|| parse_single_unspec62_nalu(&bytes))); } } criterion_group!(parse_rpus, parse_single_unspec62_nalu_benchmark); dolby_vision-3.3.1/benches/benchmarks/rewriting.rs000064400000000000000000000021531046102023000204350ustar 00000000000000use std::{ fs::File, io::Read, path::{Path, PathBuf}, }; use criterion::{criterion_group, Criterion}; use dolby_vision::rpu::dovi_rpu::DoviRpu; const RPU_FILES: &[&str] = &["fel_orig.bin", "mel_variable_l8_length13.bin"]; fn get_bytes>(path: P) -> Vec { let mut buf = Vec::with_capacity(500); File::open(path).unwrap().read_to_end(&mut buf).unwrap(); buf } pub fn rewrite_single_unspec62_nalu(data: &[u8]) { let mut rpu = DoviRpu::parse_unspec62_nalu(data).unwrap(); rpu.convert_with_mode(2).unwrap(); rpu.write_hevc_unspec62_nalu().unwrap(); } fn rewrite_single_unspec62_nalu_benchmark(c: &mut Criterion) { let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let assets_path = lib_path.parent().unwrap().join("assets/tests"); let mut group = c.benchmark_group("rewrite_single_unspec62_nalu"); for file in RPU_FILES { let bytes = get_bytes(assets_path.join(file)); group.bench_function(*file, |b| b.iter(|| rewrite_single_unspec62_nalu(&bytes))); } } criterion_group!(rewrite_rpus, rewrite_single_unspec62_nalu_benchmark); dolby_vision-3.3.1/cbindgen.toml000064400000000000000000000005501046102023000147760ustar 00000000000000header = "// SPDX-License-Identifier: MIT" sys_includes = ["stddef.h", "stdint.h", "stdlib.h", "stdbool.h"] no_includes = true include_guard = "DOVI_H" tab_width = 4 style = "Type" language = "C" cpp_compat = true [parse] parse_deps = false [export] item_types = ["constants", "enums", "structs", "unions", "typedefs", "opaque", "functions"] prefix = "Dovi" dolby_vision-3.3.1/examples/capi_rpu_file.c000064400000000000000000000015111046102023000171110ustar 00000000000000#include #include #include #include "helpers.h" int main(void) { char *path = "../../assets/hevc_tests/regular_rpu_mel.bin"; int ret; const DoviRpuOpaqueList *rpus = dovi_parse_rpu_bin_file(path); if (rpus->error) { printf("%s\n", rpus->error); dovi_rpu_list_free(rpus); return 1; } printf("Parsed RPU file: %d frames\n", rpus->len); // All the RPUs are valid at this point DoviRpuOpaque *rpu = rpus->list[0]; const DoviRpuDataHeader *header = dovi_rpu_get_header(rpu); // Process the RPU.. ret = process_rpu_info(rpu, header); if (ret < 0) { const char *error = dovi_rpu_get_error(rpu); printf("%s\n", error); } // Free everything dovi_rpu_free_header(header); dovi_rpu_list_free(rpus); } dolby_vision-3.3.1/examples/capi_single_rpu.c000064400000000000000000000015351046102023000174610ustar 00000000000000#include #include #include #include "helpers.h" int main(void) { char *path = "../../assets/tests/cmv40_full_rpu.bin"; int ret; size_t length; const uint8_t *buf = read_rpu_file(path, &length); DoviRpuOpaque *rpu = dovi_parse_unspec62_nalu(buf, length); free((void *) buf); // The RPU header is always present const DoviRpuDataHeader *header = dovi_rpu_get_header(rpu); if (!header) { const char *error = dovi_rpu_get_error(rpu); printf("%s\n", error); dovi_rpu_free(rpu); return 1; } // Process the RPU.. ret = process_rpu_info(rpu, header); if (ret < 0) { const char *error = dovi_rpu_get_error(rpu); printf("%s\n", error); } // Free everything dovi_rpu_free_header(header); dovi_rpu_free(rpu); } dolby_vision-3.3.1/examples/helpers.h000064400000000000000000000252231046102023000157650ustar 00000000000000#include const uint8_t* read_rpu_file(char *path, size_t *len) { FILE *fileptr; uint8_t *buffer; fileptr = fopen(path, "rb"); fseek(fileptr, 0, SEEK_END); *len = (size_t) ftell(fileptr); rewind(fileptr); size_t size = *len * sizeof(uint8_t); buffer = (uint8_t *) malloc(size); fread(buffer, *len, 1, fileptr); fclose(fileptr); return buffer; } int process_rpu_data_mapping(DoviRpuOpaque *rpu, const DoviRpuDataHeader *header); int process_dm_metadata(DoviRpuOpaque *rpu, const DoviRpuDataHeader *header); int process_rpu_info(DoviRpuOpaque *rpu, const DoviRpuDataHeader *header) { const char *error; int ret; if (header->rpu_type != 2) return 0; printf("Guessed profile: %i\n", header->guessed_profile); if (header->el_type) { printf("Profile 7 EL type: %s\n", header->el_type); } // We have new rpu_data_mapping metadata if (!header->use_prev_vdr_rpu_flag) { ret = process_rpu_data_mapping(rpu, header); } // We have display management metadata if (header->vdr_dm_metadata_present_flag) { ret = process_dm_metadata(rpu, header); } if (header->guessed_profile == 7) { // Convert FEL to MEL ret = dovi_convert_rpu_with_mode(rpu, 1); if (ret < 0) { return -1; } } const DoviData *data = dovi_write_unspec62_nalu(rpu); if (!data) { return -1; } // Do something with the encoded RPU.. // Free the encoded data when we're done dovi_data_free(data); return 0; } int process_rpu_data_mapping(DoviRpuOpaque *rpu, const DoviRpuDataHeader *header) { const DoviRpuDataMapping *rpu_data_mapping = dovi_rpu_get_data_mapping(rpu); if (!rpu_data_mapping) return -1; printf("vdr_rpu_data_mapping()\n"); // Use the rpu_data_mapping metadata.. for (int cmp = 0; cmp < DoviNUM_COMPONENTS; cmp++) { printf(" cmp %d\n", cmp); const DoviReshapingCurve curve = rpu_data_mapping->curves[cmp]; // 1D buffer example printf(" num_pivots: %d\n", curve.num_pivots_minus2 + 2); printf(" values: [", cmp); const uint16_t *buf = curve.pivots.data; for (int i= 0; i < curve.pivots.len; i++) { printf(" %d", buf[i]); } printf(" ]\n"); if (curve.polynomial) { printf(" Polynomial reshaping curve\n"); const DoviPolynomialCurve *poly_curve = curve.polynomial; // 2D buffer example const DoviU64Data2D poly_coef = poly_curve->poly_coef; printf(" poly_coefs\n"); for (int i = 0; i < poly_coef.len; i++) { const DoviU64Data *dovi_data = poly_coef.list[i]; printf(" poly_coef[%d], len: %d, values: [", i, dovi_data->len); const uint64_t *buf = dovi_data->data; for (int j = 0; j < dovi_data->len; j++) { printf(" %d", buf[j]); } printf(" ]\n"); } } else if (curve.mmr) { printf(" MMR reshaping curve\n"); const DoviMMRCurve *mmr_curve = curve.mmr; // 3D buffer example const DoviU64Data3D mmr_coef = mmr_curve->mmr_coef; printf(" mmr_coefs, len: %d\n", mmr_coef.len); for (int i = 0; i < mmr_coef.len; i++) { const DoviU64Data2D *dovi_data_2d = mmr_coef.list[i]; printf(" mmr_coef[%d], len: %d, values: [\n", i, dovi_data_2d->len); for (int j = 0; j < dovi_data_2d->len; j++) { const DoviU64Data *dovi_data = dovi_data_2d->list[j]; printf(" mmr_coef[%d][%d], len: %d, values: [", i, j, dovi_data->len); const uint64_t *buf = dovi_data->data; for (int k = 0; k < dovi_data->len; k++) { printf(" %d", buf[k]); } printf(" ]\n"); } printf(" ]\n"); } } } // We have NLQ metadata (and therefore an enhancement layer) if (rpu_data_mapping->nlq) { const DoviRpuDataNlq *rpu_data_nlq = rpu_data_mapping->nlq; printf("Non linear quantization (NLQ)\n"); // Do something with the NLQ data.. printf(" nlq_offset: [%d, %d, %d]\n", rpu_data_nlq->nlq_offset[0], rpu_data_nlq->nlq_offset[1], rpu_data_nlq->nlq_offset[2]); printf(" vdr_in_max_int: [%d, %d, %d]\n", rpu_data_nlq->vdr_in_max_int[0], rpu_data_nlq->vdr_in_max_int[1], rpu_data_nlq->vdr_in_max_int[2]); printf(" vdr_in_max: [%d, %d, %d]\n", rpu_data_nlq->vdr_in_max[0], rpu_data_nlq->vdr_in_max[1], rpu_data_nlq->vdr_in_max[2]); } dovi_rpu_free_data_mapping(rpu_data_mapping); return 0; } int process_dm_metadata(DoviRpuOpaque *rpu, const DoviRpuDataHeader *header) { const DoviVdrDmData *vdr_dm_data = dovi_rpu_get_vdr_dm_data(rpu); if (!vdr_dm_data) return -1; printf("vdr_dm_data_payload()\n"); printf(" Num extension metadata blocks: %d\n", vdr_dm_data->dm_data.num_ext_blocks); // Do something with the DM metadata.. printf(" Mastering display PQ codes: min %.6f max %.6f\n", vdr_dm_data->source_min_pq / 4095.0, vdr_dm_data->source_max_pq / 4095.0); printf(" dm_data_payload(), CM v2.9 DM data\n"); // We have the frame stats if (vdr_dm_data->dm_data.level1) { const DoviExtMetadataBlockLevel1 *meta = vdr_dm_data->dm_data.level1; // Values are PQ encoded in 12 bit, from 0 to 4095 printf(" L1 Frame brightness: min %.6f, max %.6f, avg %.6f\n", meta->min_pq / 4095.0, meta->max_pq / 4095.0, meta->avg_pq / 4095.0); } // We have creative trims if (vdr_dm_data->dm_data.level2.len > 0) { const DoviLevel2BlockList blocks = vdr_dm_data->dm_data.level2; printf(" L2 Creative trims, targets: %d\n", vdr_dm_data->dm_data.level2.len); for (int i = 0; i < vdr_dm_data->dm_data.level2.len; i++) { const DoviExtMetadataBlockLevel2 *meta = blocks.list[i]; printf(" target display brightness PQ code: %.6f\n", meta->target_max_pq / 4095.0); // Trim values are from 0 to 4095 printf(" trim_slope: %d, trim_offset: %d, trim_power: %d\n", meta->trim_slope, meta->trim_offset, meta->trim_power); printf(" trim_chroma_weight: %d, trim_saturation_gain: %d, ms_weight: %d\n", meta->trim_chroma_weight, meta->trim_saturation_gain, meta->ms_weight); } } if (vdr_dm_data->dm_data.level4) { const DoviExtMetadataBlockLevel4 *meta = vdr_dm_data->dm_data.level4; printf(" L4 anchor_pq: %d, anchor_power: %d\n", meta->anchor_pq, meta->anchor_power); } // We have active area metadata if (vdr_dm_data->dm_data.level5) { const DoviExtMetadataBlockLevel5 *meta = vdr_dm_data->dm_data.level5; printf(" L5 Active area offsets: left %d, right %d, top %d, bottom %d\n", meta->active_area_left_offset, meta->active_area_right_offset, meta->active_area_top_offset, meta->active_area_bottom_offset); } // We have fallback HDR10 metadata if (vdr_dm_data->dm_data.level6) { const DoviExtMetadataBlockLevel6 *meta = vdr_dm_data->dm_data.level6; printf(" L6 Mastering display: min %.4f, max %d\n", meta->min_display_mastering_luminance / 10000.0, meta->max_display_mastering_luminance); printf(" MaxCLL %d, MaxFALL %d\n", meta->max_content_light_level, meta->max_frame_average_light_level); } // CM v4.0, DM data version 2 if (vdr_dm_data->dm_data.level254) { printf(" dm_data_payload2(), CM v4.0 DM data\n"); if (vdr_dm_data->dm_data.level3) { const DoviExtMetadataBlockLevel3 *meta = vdr_dm_data->dm_data.level3; printf(" L3 level 1 PQ offsets min: %d, max: %d, avg: %d\n", meta->min_pq_offset, meta->max_pq_offset, meta->avg_pq_offset); } // We have creative trims if (vdr_dm_data->dm_data.level8.len > 0) { const DoviLevel8BlockList blocks = vdr_dm_data->dm_data.level8; printf(" L8 Creative trims, targets: %d\n", vdr_dm_data->dm_data.level8.len); for (int i = 0; i < vdr_dm_data->dm_data.level8.len; i++) { const DoviExtMetadataBlockLevel8 *meta = blocks.list[i]; printf(" target display index: %d\n", meta->target_display_index); // Trim values are from 0 to 4095 printf(" trim_slope: %d, trim_offset: %d, trim_power: %d\n", meta->trim_slope, meta->trim_offset, meta->trim_power); printf(" trim_chroma_weight: %d, trim_saturation_gain: %d, ms_weight: %d\n", meta->trim_chroma_weight, meta->trim_saturation_gain, meta->ms_weight); } } if (vdr_dm_data->dm_data.level9) { const DoviExtMetadataBlockLevel9 *meta = vdr_dm_data->dm_data.level9; printf(" L9 Source primary index: %d\n", meta->source_primary_index); } // The L8 target definitions if (vdr_dm_data->dm_data.level10.len > 0) { const DoviLevel10BlockList blocks = vdr_dm_data->dm_data.level10; printf(" L10 Custom display targets: %d\n", vdr_dm_data->dm_data.level10.len); for (int i = 0; i < vdr_dm_data->dm_data.level10.len; i++) { const DoviExtMetadataBlockLevel10 *meta = blocks.list[i]; printf(" target display index: %d\n", meta->target_display_index); // Trim values are from 0 to 4095 printf(" target_max_pq: %d, target_min_pq: %d, target_primary_index: %d \n", meta->target_max_pq, meta->target_min_pq, meta->target_primary_index); } } if (vdr_dm_data->dm_data.level11) { const DoviExtMetadataBlockLevel11 *meta = vdr_dm_data->dm_data.level11; printf(" L11 Content type: %d, whitepoint: %d, reference_mode_flag: %d\n", meta->content_type, (meta->whitepoint * 375) + 6504, meta->reference_mode_flag); } if (vdr_dm_data->dm_data.level254) { const DoviExtMetadataBlockLevel254 *meta = vdr_dm_data->dm_data.level254; printf(" L254 dm_mode: %d, dm_version_index: %d\n", meta->dm_mode, meta->dm_version_index); } } dovi_rpu_free_vdr_dm_data(vdr_dm_data); return 0; } dolby_vision-3.3.1/examples/simple_edit.cpp000064400000000000000000000025561046102023000171600ustar 00000000000000#include #include #include #include #include extern "C" { #include "helpers.h" } int main(void) { std::ifstream input("../../assets/tests/fel_orig.bin", std::ios::binary); const std::vector buf( (std::istreambuf_iterator(input)), (std::istreambuf_iterator())); input.close(); auto start = std::chrono::high_resolution_clock::now(); DoviRpuOpaque *rpu = dovi_parse_unspec62_nalu(buf.data(), buf.size()); const DoviRpuDataHeader *header = dovi_rpu_get_header(rpu); // Only converts profile 7 as they are guaranteed to be HDR10 base if (header && header->guessed_profile == 7) { // Convert the base to 8.1 compatible // Also handles removing mapping for FEL int ret = dovi_convert_rpu_with_mode(rpu, 2); // Final video has letterboxing completely cropped ret = dovi_rpu_set_active_area_offsets(rpu, 0, 0, 0, 0); const DoviData *rpu_payload = dovi_write_unspec62_nalu(rpu); // Do something with the edited payload dovi_data_free(rpu_payload); } if (header) dovi_rpu_free_header(header); dovi_rpu_free(rpu); auto end = std::chrono::high_resolution_clock::now(); std::cout << std::chrono::duration_cast(end - start).count() << " μs"; } dolby_vision-3.3.1/src/av1/emdf.rs000064400000000000000000000057331046102023000150770ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; /// Parse the expected EMDF container with fixed values according to spec /// Returns `emdf_payload_size` pub(crate) fn parse_emdf_container(reader: &mut BsIoSliceReader) -> Result { let emdf_version = reader.get_n::(2)?; ensure!(emdf_version == 0); let key_id = reader.get_n::(3)?; ensure!(key_id == 6); let emdf_payload_id = reader.get_n::(5)?; ensure!(emdf_payload_id == 31); let emdf_payload_id_ext = parse_variable_bits(reader, 5)?; ensure!(emdf_payload_id_ext == 225); ensure!(!reader.get()?); // smploffste = 0 ensure!(!reader.get()?); // duratione = 0 ensure!(!reader.get()?); // groupide = 0 ensure!(!reader.get()?); // codecdatae = 0 ensure!(reader.get()?); // discard_unknown_payload = 1 let emdf_payload_size = parse_variable_bits(reader, 8)? as usize; Ok(emdf_payload_size) } /// Write the DOVI RPU EMDF container with payload pub(crate) fn write_emdf_container_with_dovi_rpu_payload( writer: &mut BitstreamIoWriter, payload: &[u8], ) -> Result<()> { let emdf_payload_size = payload.len() as u32; write_dovi_rpu_emdf_header(writer)?; write_variable_bits(writer, emdf_payload_size, 8)?; for b in payload { writer.write_n(b, 8)?; } // emdf_payload_id and emdf_protection writer.write_n(&0, 5)?; writer.write_n(&1, 2)?; writer.write_n(&0, 2)?; writer.write_n(&0, 8)?; Ok(()) } fn write_dovi_rpu_emdf_header(writer: &mut BitstreamIoWriter) -> Result<()> { writer.write_n(&0, 2)?; // emdf_version writer.write_n(&6, 3)?; // key_id writer.write_n(&31, 5)?; // emdf_payload_id write_variable_bits(writer, 225, 5)?; // emdf_payload_id_ext writer.write_n(&0, 4)?; // smploffste, duratione, groupide, codecdatae writer.write(true)?; // discard_unknown_payload Ok(()) } fn parse_variable_bits(reader: &mut BsIoSliceReader, n: u32) -> Result { let mut value: u32 = 0; loop { let tmp: u32 = reader.get_n(n)?; value += tmp; // read_more flag if !reader.get()? { break; } value <<= n; value += 1 << n; } Ok(value) } fn write_variable_bits(writer: &mut BitstreamIoWriter, value: u32, n: u32) -> Result<()> { let max = 1 << n; if value > max { let mut remaining = value; loop { let tmp = remaining >> n; let clipped = tmp << n; remaining -= clipped; let byte = (clipped - max) >> n; writer.write_n(&byte, n)?; writer.write(true)?; // read_more // Stop once the remaining can be written in N bits if remaining <= max { break; } } writer.write_n(&remaining, n)?; } else { writer.write_n(&value, n)?; } writer.write(false)?; Ok(()) } dolby_vision-3.3.1/src/av1/mod.rs000064400000000000000000000064141046102023000147400ustar 00000000000000use anyhow::{bail, ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; use crate::{ av1::emdf::{parse_emdf_container, write_emdf_container_with_dovi_rpu_payload}, rpu::dovi_rpu::{DoviRpu, FINAL_BYTE}, }; mod emdf; pub const ITU_T35_DOVI_RPU_PAYLOAD_HEADER: &[u8] = &[0x00, 0x3B, 0x00, 0x00, 0x08, 0x00, 0x37, 0xCD, 0x08]; const ITU_T35_DOVI_RPU_PAYLOAD_HEADER_LEN: usize = ITU_T35_DOVI_RPU_PAYLOAD_HEADER.len(); /// Parse AV1 ITU-T T.35 metadata OBU into a `DoviRpu` /// The payload is extracted out of the EMDF wrapper #[deprecated( since = "3.3.0", note = "Replaced by DoviRpu::parse_itu_t35_dovi_metadata_obu" )] pub fn parse_itu_t35_dovi_metadata_obu(data: &[u8]) -> Result { DoviRpu::parse_itu_t35_dovi_metadata_obu(data) } pub(crate) fn av1_validated_trimmed_data(data: &[u8]) -> Result<&[u8]> { if data.len() < 34 { bail!("Invalid RPU length: {}", data.len()); } let data = if data[0] == 0xB5 { // itu_t_t35_country_code - United States // Remove from buffer &data[1..] } else { data }; let trimmed_data = match &data[..ITU_T35_DOVI_RPU_PAYLOAD_HEADER_LEN] { ITU_T35_DOVI_RPU_PAYLOAD_HEADER => data, _ => bail!( "Invalid AV1 RPU payload header: {:?}", &data[..ITU_T35_DOVI_RPU_PAYLOAD_HEADER_LEN] ), }; Ok(trimmed_data) } /// Returns the EMDF payload bytes representing the RPU buffer pub(crate) fn convert_av1_rpu_payload_to_regular(data: &[u8]) -> Result> { let mut reader = BsIoSliceReader::from_slice(data); let itu_t_t35_terminal_provider_code = reader.get_n::(16)?; ensure!(itu_t_t35_terminal_provider_code == 0x3B); let itu_t_t35_terminal_provider_oriented_code = reader.get_n::(32)?; ensure!(itu_t_t35_terminal_provider_oriented_code == 0x800); let emdf_payload_size = parse_emdf_container(&mut reader)?; let mut converted_buf = Vec::with_capacity(emdf_payload_size + 1); converted_buf.push(0x19); for _ in 0..emdf_payload_size { converted_buf.push(reader.get_n(8)?); } Ok(converted_buf) } /// Wraps a regular RPU into EMDF container with ITU-T T.35 header /// Buffer must start with 0x19 prefix. /// /// Returns payload for AV1 ITU T-T.35 metadata OBU pub fn convert_regular_rpu_to_av1_payload(data: &[u8]) -> Result> { ensure!(data[0] == 0x19); // The EMDF payload must not include any trailing bytes after 0x80 terminator let trailing_zeroes = data.iter().rev().take_while(|b| **b == 0).count(); let rpu_end = data.len() - trailing_zeroes; let last_byte = data[rpu_end - 1]; if last_byte != FINAL_BYTE { bail!("Invalid RPU last byte: {}", last_byte); } // Exclude 0x19 prefix let data = &data[1..rpu_end]; let rpu_size = data.len(); let capacity = 16 + rpu_size; let mut writer = BitstreamIoWriter::with_capacity(capacity * 8); writer.write_n(&0x3B, 16)?; // itu_t_t35_terminal_provider_code writer.write_n(&0x800, 32)?; // itu_t_t35_terminal_provider_oriented_code write_emdf_container_with_dovi_rpu_payload(&mut writer, data)?; while !writer.is_aligned() { writer.write(true)?; } Ok(writer.into_inner()) } dolby_vision-3.3.1/src/c_structs/buffers.rs000064400000000000000000000225771046102023000171470ustar 00000000000000use std::ptr::null; use libc::size_t; use tinyvec::ArrayVec; pub trait Freeable { /// # Safety /// The pointers should all be valid. unsafe fn free(&self); } /// Struct representing a data buffer #[repr(C)] pub struct Data { /// Pointer to the data buffer pub data: *const u8, /// Data buffer size pub len: size_t, } /// Struct representing a data buffer #[repr(C)] pub struct U16Data { /// Pointer to the data buffer. Can be null if length is zero. pub data: *const u16, /// Data buffer size pub len: size_t, } /// Struct representing a data buffer #[repr(C)] pub struct U64Data { /// Pointer to the data buffer. Can be null if length is zero. pub data: *const u64, /// Data buffer size pub len: size_t, } /// Struct representing a data buffer #[repr(C)] pub struct I64Data { /// Pointer to the data buffer pub data: *const i64, /// Data buffer size pub len: size_t, } /// Struct representing a 2D data buffer #[repr(C)] pub struct Data2D { /// Pointer to the list of Data structs pub list: *const *const Data, /// List length pub len: size_t, } /// Struct representing a 2D data buffer #[repr(C)] pub struct U64Data2D { /// Pointer to the list of Data structs pub list: *const *const U64Data, /// List length pub len: size_t, } /// Struct representing a 2D data buffer #[repr(C)] pub struct I64Data2D { /// Pointer to the list of Data structs pub list: *const *const I64Data, /// List length pub len: size_t, } /// Struct representing a 3D data buffer #[repr(C)] pub struct U64Data3D { /// Pointer to the list of Data2D structs pub list: *const *const U64Data2D, /// List length pub len: size_t, } /// Struct representing a 3D data buffer #[repr(C)] pub struct I64Data3D { /// Pointer to the list of Data2D structs pub list: *const *const I64Data2D, /// List length pub len: size_t, } impl From> for Data { fn from(buf: Vec) -> Self { Data { len: buf.len(), data: Box::into_raw(buf.into_boxed_slice()) as *const u8, } } } impl From> for Data { fn from(buf: Vec) -> Self { let res: Vec = buf.into_iter().map(|e| e as u8).collect(); Data { len: res.len(), data: Box::into_raw(res.into_boxed_slice()) as *const u8, } } } impl From> for U16Data { fn from(buf: Vec) -> Self { U16Data { len: buf.len(), data: Box::into_raw(buf.into_boxed_slice()) as *const u16, } } } impl From<[bool; N]> for Data { fn from(array: [bool; N]) -> Self { let res: [u8; N] = array.map(|e| e as u8); Data { len: array.len(), data: Box::into_raw(Box::new(res)) as *const u8, } } } impl From> for U64Data { fn from(buf: Vec) -> Self { U64Data { len: buf.len(), data: Box::into_raw(buf.into_boxed_slice()) as *const u64, } } } impl From> for U64Data { fn from(buf: ArrayVec<[u64; N]>) -> Self { U64Data { len: buf.len(), data: Box::into_raw(buf.to_vec().into_boxed_slice()) as *const u64, } } } impl From> for I64Data { fn from(buf: Vec) -> Self { I64Data { len: buf.len(), data: Box::into_raw(buf.into_boxed_slice()) as *const i64, } } } impl From> for I64Data { fn from(buf: ArrayVec<[i64; N]>) -> Self { I64Data { len: buf.len(), data: Box::into_raw(buf.to_vec().into_boxed_slice()) as *const i64, } } } impl From<[u16; N]> for U16Data { fn from(array: [u16; N]) -> Self { U16Data { len: array.len(), data: Box::into_raw(Box::new(array)) as *const u16, } } } impl From<[u64; N]> for U64Data { fn from(array: [u64; N]) -> Self { U64Data { len: array.len(), data: Box::into_raw(Box::new(array)) as *const u64, } } } impl From>> for U64Data2D { fn from(buf_2d: Vec>) -> Self { let list: Vec<*const U64Data> = buf_2d .into_iter() .map(|buf| Box::into_raw(Box::new(U64Data::from(buf))) as *const U64Data) .collect(); U64Data2D { len: list.len(), list: Box::into_raw(list.into_boxed_slice()) as *const *const U64Data, } } } impl From; N]>> for U64Data2D { fn from(buf_2d: ArrayVec<[ArrayVec<[u64; N2]>; N]>) -> Self { let list: Vec<*const U64Data> = buf_2d .into_iter() .map(|buf| Box::into_raw(Box::new(U64Data::from(buf))) as *const U64Data) .collect(); U64Data2D { len: list.len(), list: Box::into_raw(list.into_boxed_slice()) as *const *const U64Data, } } } impl From>> for I64Data2D { fn from(buf_2d: Vec>) -> Self { let list: Vec<*const I64Data> = buf_2d .into_iter() .map(|buf| Box::into_raw(Box::new(I64Data::from(buf))) as *const I64Data) .collect(); I64Data2D { len: list.len(), list: Box::into_raw(list.into_boxed_slice()) as *const *const I64Data, } } } impl From; N]>> for I64Data2D { fn from(buf_2d: ArrayVec<[ArrayVec<[i64; N2]>; N]>) -> Self { let list: Vec<*const I64Data> = buf_2d .into_iter() .map(|buf| Box::into_raw(Box::new(I64Data::from(buf))) as *const I64Data) .collect(); I64Data2D { len: list.len(), list: Box::into_raw(list.into_boxed_slice()) as *const *const I64Data, } } } impl From; N]>>> for U64Data3D { fn from(buf_3d: Vec; N]>>) -> Self { let list: Vec<*const U64Data2D> = buf_3d .into_iter() .map(|buf| Box::into_raw(Box::new(U64Data2D::from(buf))) as *const U64Data2D) .collect(); U64Data3D { len: list.len(), list: Box::into_raw(list.into_boxed_slice()) as *const *const U64Data2D, } } } impl From; N]>>> for I64Data3D { fn from(buf_3d: Vec; N]>>) -> Self { let list: Vec<*const I64Data2D> = buf_3d .into_iter() .map(|buf| Box::into_raw(Box::new(I64Data2D::from(buf))) as *const I64Data2D) .collect(); I64Data3D { len: list.len(), list: Box::into_raw(list.into_boxed_slice()) as *const *const I64Data2D, } } } impl From> for U16Data { fn from(maybe_array: Option<[u16; N]>) -> Self { maybe_array.map_or(U16Data::empty(), U16Data::from) } } impl Freeable for Data { unsafe fn free(&self) { Vec::from_raw_parts(self.data as *mut u8, self.len, self.len); } } impl Freeable for U16Data { unsafe fn free(&self) { if !self.data.is_null() { Vec::from_raw_parts(self.data as *mut u16, self.len, self.len); } } } impl Freeable for U64Data { unsafe fn free(&self) { if !self.data.is_null() { Vec::from_raw_parts(self.data as *mut u64, self.len, self.len); } } } impl Freeable for I64Data { unsafe fn free(&self) { Vec::from_raw_parts(self.data as *mut i64, self.len, self.len); } } impl Freeable for Data2D { unsafe fn free(&self) { let list = Vec::from_raw_parts(self.list as *mut *const Data, self.len, self.len); for data_ptr in list { let data = Box::from_raw(data_ptr as *mut Data); data.free(); } } } impl Freeable for U64Data2D { unsafe fn free(&self) { let list = Vec::from_raw_parts(self.list as *mut *const U64Data, self.len, self.len); for data_ptr in list { let data = Box::from_raw(data_ptr as *mut U64Data); data.free(); } } } impl Freeable for I64Data2D { unsafe fn free(&self) { let list = Vec::from_raw_parts(self.list as *mut *const I64Data, self.len, self.len); for data_ptr in list { let data = Box::from_raw(data_ptr as *mut I64Data); data.free(); } } } impl Freeable for U64Data3D { unsafe fn free(&self) { let list = Vec::from_raw_parts(self.list as *mut *const U64Data2D, self.len, self.len); for data2d_ptr in list { let data2d = Box::from_raw(data2d_ptr as *mut U64Data2D); data2d.free(); } } } impl Freeable for I64Data3D { unsafe fn free(&self) { let list = Vec::from_raw_parts(self.list as *mut *const I64Data2D, self.len, self.len); for data2d_ptr in list { let data2d = Box::from_raw(data2d_ptr as *mut I64Data2D); data2d.free(); } } } impl U16Data { fn empty() -> Self { Self { len: 0, data: null(), } } } dolby_vision-3.3.1/src/c_structs/extension_metadata.rs000064400000000000000000000234461046102023000213630ustar 00000000000000use libc::size_t; use std::ptr::null; use crate::rpu::extension_metadata::blocks::*; use crate::rpu::extension_metadata::DmData as RuDmData; use crate::rpu::extension_metadata::WithExtMetadataBlocks; use crate::rpu::vdr_dm_data::CmVersion; /// C struct for the list of ext_metadata_block() #[repr(C)] pub struct DmData { /// Number of metadata blocks num_ext_blocks: u64, level1: *const ExtMetadataBlockLevel1, level2: Level2BlockList, level3: *const ExtMetadataBlockLevel3, level4: *const ExtMetadataBlockLevel4, level5: *const ExtMetadataBlockLevel5, level6: *const ExtMetadataBlockLevel6, level8: Level8BlockList, level9: *const ExtMetadataBlockLevel9, level10: Level10BlockList, level11: *const ExtMetadataBlockLevel11, level254: *const ExtMetadataBlockLevel254, level255: *const ExtMetadataBlockLevel255, } #[repr(C)] pub struct Level2BlockList { /// Pointer to the list of ExtMetadataBlockLevel2 structs pub list: *const *const ExtMetadataBlockLevel2, /// List length pub len: size_t, } #[repr(C)] pub struct Level8BlockList { /// Pointer to the list of ExtMetadataBlockLevel8 structs pub list: *const *const ExtMetadataBlockLevel8, /// List length pub len: size_t, } #[repr(C)] pub struct Level10BlockList { /// Pointer to the list of ExtMetadataBlockLevel10 structs pub list: *const *const ExtMetadataBlockLevel10, /// List length pub len: size_t, } impl DmData { pub fn combine_dm_data( cmv29_metadata: Option<&RuDmData>, cmv40_metadata: Option<&RuDmData>, ) -> Self { let mut dm_data = Self::default(); if let Some(RuDmData::V29(cmv29)) = cmv29_metadata { dm_data.num_ext_blocks += cmv29.num_ext_blocks(); dm_data.set_blocks(cmv29.blocks_ref(), CmVersion::V29); } if let Some(RuDmData::V40(cmv40)) = cmv40_metadata { dm_data.num_ext_blocks += cmv40.num_ext_blocks(); dm_data.set_blocks(cmv40.blocks_ref(), CmVersion::V40); } dm_data } fn set_blocks(&mut self, blocks: &[ExtMetadataBlock], cm_version: CmVersion) { for block in blocks { match block { ExtMetadataBlock::Level1(b) => { self.level1 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel1 } ExtMetadataBlock::Level2(_) => {} ExtMetadataBlock::Level3(b) => { self.level3 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel3 } ExtMetadataBlock::Level4(b) => { self.level4 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel4 } ExtMetadataBlock::Level5(b) => { self.level5 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel5 } ExtMetadataBlock::Level6(b) => { self.level6 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel6 } ExtMetadataBlock::Level8(_) => {} ExtMetadataBlock::Level9(b) => { self.level9 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel9 } ExtMetadataBlock::Level10(_) => {} ExtMetadataBlock::Level11(b) => { self.level11 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel11 } ExtMetadataBlock::Level254(b) => { self.level254 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel254 } ExtMetadataBlock::Level255(b) => { self.level255 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel255 } ExtMetadataBlock::Reserved(_) => {} }; } // Don't overwrite over previously set data match cm_version { CmVersion::V29 => self.level2 = Level2BlockList::from(blocks), CmVersion::V40 => { self.level8 = Level8BlockList::from(blocks); self.level10 = Level10BlockList::from(blocks); } } } /// # Safety pub unsafe fn free(&self) { drop(Box::from_raw(self.level1 as *mut ExtMetadataBlockLevel1)); self.level2.free(); drop(Box::from_raw(self.level3 as *mut ExtMetadataBlockLevel3)); drop(Box::from_raw(self.level4 as *mut ExtMetadataBlockLevel4)); drop(Box::from_raw(self.level5 as *mut ExtMetadataBlockLevel5)); drop(Box::from_raw(self.level6 as *mut ExtMetadataBlockLevel6)); self.level8.free(); drop(Box::from_raw(self.level9 as *mut ExtMetadataBlockLevel9)); self.level10.free(); drop(Box::from_raw(self.level11 as *mut ExtMetadataBlockLevel11)); drop(Box::from_raw( self.level254 as *mut ExtMetadataBlockLevel254, )); drop(Box::from_raw( self.level255 as *mut ExtMetadataBlockLevel255, )); } } impl Default for DmData { fn default() -> Self { Self { num_ext_blocks: Default::default(), level1: null(), level2: Default::default(), level3: null(), level4: null(), level5: null(), level6: null(), level8: Default::default(), level9: null(), level10: Default::default(), level11: null(), level254: null(), level255: null(), } } } impl Level2BlockList { /// # Safety pub unsafe fn free(&self) { let list = Vec::from_raw_parts( self.list as *mut *const ExtMetadataBlockLevel2, self.len, self.len, ); for data_ptr in list { drop(Box::from_raw(data_ptr as *mut ExtMetadataBlockLevel2)); } } } impl Level8BlockList { /// # Safety pub unsafe fn free(&self) { let list = Vec::from_raw_parts( self.list as *mut *const ExtMetadataBlockLevel8, self.len, self.len, ); for data_ptr in list { drop(Box::from_raw(data_ptr as *mut ExtMetadataBlockLevel8)); } } } impl Level10BlockList { /// # Safety pub unsafe fn free(&self) { let list = Vec::from_raw_parts( self.list as *mut *const ExtMetadataBlockLevel10, self.len, self.len, ); for data_ptr in list { drop(Box::from_raw(data_ptr as *mut ExtMetadataBlockLevel10)); } } } impl From<&[ExtMetadataBlock]> for Level2BlockList { fn from(blocks: &[ExtMetadataBlock]) -> Self { let level2_blocks: Vec<*const ExtMetadataBlockLevel2> = blocks .iter() .filter(|b| matches!(b, ExtMetadataBlock::Level2(_))) .map(|b| match b { ExtMetadataBlock::Level2(e) => { Box::into_raw(Box::new(e.clone())) as *const ExtMetadataBlockLevel2 } _ => null(), }) .collect(); Self { len: level2_blocks.len(), list: Box::into_raw(level2_blocks.into_boxed_slice()) as *const *const ExtMetadataBlockLevel2, } } } impl From<&[ExtMetadataBlock]> for Level8BlockList { fn from(blocks: &[ExtMetadataBlock]) -> Self { let level8_blocks: Vec<*const ExtMetadataBlockLevel8> = blocks .iter() .filter(|b| matches!(b, ExtMetadataBlock::Level8(_))) .map(|b| match b { ExtMetadataBlock::Level8(e) => { Box::into_raw(Box::new(e.clone())) as *const ExtMetadataBlockLevel8 } _ => null(), }) .collect(); Self { len: level8_blocks.len(), list: Box::into_raw(level8_blocks.into_boxed_slice()) as *const *const ExtMetadataBlockLevel8, } } } impl From<&[ExtMetadataBlock]> for Level10BlockList { fn from(blocks: &[ExtMetadataBlock]) -> Self { let level10_blocks: Vec<*const ExtMetadataBlockLevel10> = blocks .iter() .filter(|b| matches!(b, ExtMetadataBlock::Level10(_))) .map(|b| match b { ExtMetadataBlock::Level10(e) => { Box::into_raw(Box::new(e.clone())) as *const ExtMetadataBlockLevel10 } _ => null(), }) .collect(); Self { len: level10_blocks.len(), list: Box::into_raw(level10_blocks.into_boxed_slice()) as *const *const ExtMetadataBlockLevel10, } } } impl Default for Level2BlockList { fn default() -> Self { let len = 0; let list: Vec<*const ExtMetadataBlockLevel2> = Vec::new(); Self { list: Box::into_raw(list.into_boxed_slice()) as *const *const ExtMetadataBlockLevel2, len, } } } impl Default for Level8BlockList { fn default() -> Self { let len = 0; let list: Vec<*const ExtMetadataBlockLevel8> = Vec::new(); Self { list: Box::into_raw(list.into_boxed_slice()) as *const *const ExtMetadataBlockLevel8, len, } } } impl Default for Level10BlockList { fn default() -> Self { let len = 0; let list: Vec<*const ExtMetadataBlockLevel10> = Vec::new(); Self { list: Box::into_raw(list.into_boxed_slice()) as *const *const ExtMetadataBlockLevel10, len, } } } dolby_vision-3.3.1/src/c_structs/mod.rs000064400000000000000000000006231046102023000162560ustar 00000000000000use crate::rpu::NUM_COMPONENTS; mod buffers; mod extension_metadata; mod rpu; mod rpu_data_header; mod rpu_data_mapping; mod rpu_data_nlq; mod vdr_dm_data; pub use buffers::*; pub use extension_metadata::DmData; pub use rpu::{RpuOpaque, RpuOpaqueList}; pub use rpu_data_header::RpuDataHeader; pub use rpu_data_mapping::RpuDataMapping; pub use rpu_data_nlq::RpuDataNlq; pub use vdr_dm_data::VdrDmData; dolby_vision-3.3.1/src/c_structs/rpu.rs000064400000000000000000000026771046102023000163200ustar 00000000000000use std::ffi::CString; use libc::{c_char, size_t}; use crate::rpu::dovi_rpu::DoviRpu; use super::Freeable; /// Opaque Dolby Vision RPU. /// /// Use dovi_rpu_free to free. /// It should be freed regardless of whether or not an error occurred. pub struct RpuOpaque { /// Optional parsed RPU, present when parsing is successful. pub rpu: Option, /// Error String of the parsing, in cases of failure. pub error: Option, } /// Heap allocated list of valid RPU pointers #[repr(C)] pub struct RpuOpaqueList { pub list: *const *mut RpuOpaque, pub len: size_t, pub error: *const c_char, } impl RpuOpaque { pub(crate) fn new(rpu: Option, error: Option) -> Self { Self { rpu, error } } } impl From> for RpuOpaque { fn from(res: Result) -> Self { match res { Ok(rpu) => Self::new(Some(rpu), None), Err(e) => Self::new( None, Some(CString::new(format!("Failed parsing RPU: {e}")).unwrap()), ), } } } impl Freeable for RpuOpaqueList { unsafe fn free(&self) { let list = Vec::from_raw_parts(self.list as *mut *mut RpuOpaque, self.len, self.len); for ptr in list { drop(Box::from_raw(ptr)); } if !self.error.is_null() { drop(CString::from_raw(self.error as *mut c_char)); } } } dolby_vision-3.3.1/src/c_structs/rpu_data_header.rs000064400000000000000000000061611046102023000206110ustar 00000000000000use libc::c_char; use std::{ffi::CString, ptr::null}; use crate::rpu::rpu_data_header::RpuDataHeader as RuRpuDataHeader; /// C struct for rpu_data_header() #[repr(C)] pub struct RpuDataHeader { /// Profile guessed from the values in the header guessed_profile: u8, /// Enhancement layer type (FEL or MEL) if the RPU is profile 7 /// null pointer if not profile 7 pub(crate) el_type: *const c_char, /// Deprecated since 3.2.0 /// The field is not actually part of the RPU header #[deprecated( since = "3.2.0", note = "The field is not actually part of the RPU header" )] rpu_nal_prefix: u8, rpu_type: u8, rpu_format: u16, vdr_rpu_profile: u8, vdr_rpu_level: u8, vdr_seq_info_present_flag: bool, chroma_resampling_explicit_filter_flag: bool, coefficient_data_type: u8, coefficient_log2_denom: u64, vdr_rpu_normalized_idc: u8, bl_video_full_range_flag: bool, bl_bit_depth_minus8: u64, el_bit_depth_minus8: u64, vdr_bit_depth_minus8: u64, spatial_resampling_filter_flag: bool, reserved_zero_3bits: u8, el_spatial_resampling_filter_flag: bool, disable_residual_flag: bool, vdr_dm_metadata_present_flag: bool, use_prev_vdr_rpu_flag: bool, prev_vdr_rpu_id: u64, } impl RpuDataHeader { /// # Safety /// The buffer pointers should be valid. pub unsafe fn free(&self) { if !self.el_type.is_null() { drop(CString::from_raw(self.el_type as *mut c_char)); } } } impl From<&RuRpuDataHeader> for RpuDataHeader { fn from(header: &RuRpuDataHeader) -> Self { #[allow(deprecated)] Self { guessed_profile: header.get_dovi_profile(), el_type: null(), // FIXME: rpu_nal_prefix deprecation rpu_nal_prefix: header.rpu_nal_prefix, rpu_type: header.rpu_type, rpu_format: header.rpu_format, vdr_rpu_profile: header.vdr_rpu_profile, vdr_rpu_level: header.vdr_rpu_level, vdr_seq_info_present_flag: header.vdr_seq_info_present_flag, chroma_resampling_explicit_filter_flag: header.chroma_resampling_explicit_filter_flag, coefficient_data_type: header.coefficient_data_type, coefficient_log2_denom: header.coefficient_log2_denom, vdr_rpu_normalized_idc: header.vdr_rpu_normalized_idc, bl_video_full_range_flag: header.bl_video_full_range_flag, bl_bit_depth_minus8: header.bl_bit_depth_minus8, el_bit_depth_minus8: header.el_bit_depth_minus8, vdr_bit_depth_minus8: header.vdr_bit_depth_minus8, spatial_resampling_filter_flag: header.spatial_resampling_filter_flag, reserved_zero_3bits: header.reserved_zero_3bits, el_spatial_resampling_filter_flag: header.el_spatial_resampling_filter_flag, disable_residual_flag: header.disable_residual_flag, vdr_dm_metadata_present_flag: header.vdr_dm_metadata_present_flag, use_prev_vdr_rpu_flag: header.use_prev_vdr_rpu_flag, prev_vdr_rpu_id: header.prev_vdr_rpu_id, } } } dolby_vision-3.3.1/src/c_structs/rpu_data_mapping.rs000064400000000000000000000131051046102023000210100ustar 00000000000000use std::ptr::null_mut; use crate::rpu::rpu_data_mapping::{ DoviMMRCurve, DoviPolynomialCurve, DoviReshapingCurve, RpuDataMapping as RuRpuDataMapping, }; use super::{buffers::*, RpuDataNlq, NUM_COMPONENTS}; /// C struct for rpu_data_mapping() #[repr(C)] pub struct RpuDataMapping { vdr_rpu_id: u64, mapping_color_space: u64, mapping_chroma_format_idc: u64, num_x_partitions_minus1: u64, num_y_partitions_minus1: u64, curves: [ReshapingCurve; NUM_COMPONENTS], /// Set to -1 to represent Option::None nlq_method_idc: i32, /// Set to -1 to represent Option::None nlq_num_pivots_minus2: i32, /// Length of zero when not present. Only present in profile 4 and 7. nlq_pred_pivot_value: U16Data, /// Pointer to `RpuDataNlq` struct, null if not dual layer profile nlq: *const RpuDataNlq, } #[repr(C)] pub struct ReshapingCurve { /// [2, 9] pub num_pivots_minus2: u64, pub pivots: U16Data, /// Consistent for a component /// Luma (component 0): Polynomial = 0 /// Chroma (components 1 and 2): MMR = 1 pub mapping_idc: u8, /// mapping_idc = 0, null pointer otherwise pub polynomial: *const PolynomialCurve, /// mapping_idc = 1, null pointer otherwise pub mmr: *const MMRCurve, } #[repr(C)] pub struct PolynomialCurve { poly_order_minus1: U64Data, linear_interp_flag: Data, poly_coef_int: I64Data2D, poly_coef: U64Data2D, } #[repr(C)] pub struct MMRCurve { mmr_order_minus1: Data, mmr_constant_int: I64Data, mmr_constant: U64Data, mmr_coef_int: I64Data3D, mmr_coef: U64Data3D, } impl RpuDataMapping { /// # Safety /// The buffer pointers should be valid. pub unsafe fn free(&self) { self.curves.iter().for_each(|curve| curve.free()); self.nlq_pred_pivot_value.free(); if !self.nlq.is_null() { drop(Box::from_raw(self.nlq as *mut RpuDataNlq)) } } } impl From<&RuRpuDataMapping> for RpuDataMapping { fn from(mapping: &RuRpuDataMapping) -> Self { let curves = [ ReshapingCurve::from(&mapping.curves[0]), ReshapingCurve::from(&mapping.curves[1]), ReshapingCurve::from(&mapping.curves[2]), ]; Self { vdr_rpu_id: mapping.vdr_rpu_id, mapping_color_space: mapping.mapping_color_space, mapping_chroma_format_idc: mapping.mapping_chroma_format_idc, num_x_partitions_minus1: mapping.num_x_partitions_minus1, num_y_partitions_minus1: mapping.num_y_partitions_minus1, curves, nlq_method_idc: mapping .nlq_method_idc .as_ref() .map_or(-1, |e| (*e as u8) as i32), nlq_num_pivots_minus2: mapping.nlq_num_pivots_minus2.map_or(-1, |e| e as i32), nlq_pred_pivot_value: U16Data::from(mapping.nlq_pred_pivot_value), nlq: mapping.nlq.as_ref().map_or(null_mut(), |nlq| { Box::into_raw(Box::new(RpuDataNlq::from(nlq))) }), } } } impl ReshapingCurve { /// # Safety /// The buffer pointers should be valid. pub unsafe fn free(&self) { self.pivots.free(); if !self.polynomial.is_null() { let poly_curve = Box::from_raw(self.polynomial as *mut PolynomialCurve); poly_curve.free(); } else if !self.mmr.is_null() { let mmr_curve = Box::from_raw(self.mmr as *mut MMRCurve); mmr_curve.free(); } } } impl PolynomialCurve { /// # Safety /// The buffer pointers should be valid. pub unsafe fn free(&self) { self.poly_order_minus1.free(); self.linear_interp_flag.free(); self.poly_coef_int.free(); self.poly_coef.free(); } } impl MMRCurve { /// # Safety /// The buffer pointers should be valid. pub unsafe fn free(&self) { self.mmr_order_minus1.free(); self.mmr_constant_int.free(); self.mmr_constant.free(); self.mmr_coef_int.free(); self.mmr_coef.free(); } } impl From<&DoviReshapingCurve> for ReshapingCurve { fn from(curve: &DoviReshapingCurve) -> Self { Self { num_pivots_minus2: curve.num_pivots_minus2, pivots: U16Data::from(curve.pivots.clone()), mapping_idc: curve.mapping_idc as u8, polynomial: curve.polynomial.as_ref().map_or(null_mut(), |poly_curve| { Box::into_raw(Box::new(PolynomialCurve::from(poly_curve))) }), mmr: curve.mmr.as_ref().map_or(null_mut(), |mmr_curve| { Box::into_raw(Box::new(MMRCurve::from(mmr_curve))) }), } } } impl From<&DoviPolynomialCurve> for PolynomialCurve { fn from(poly_curve: &DoviPolynomialCurve) -> Self { PolynomialCurve { poly_order_minus1: U64Data::from(poly_curve.poly_order_minus1.clone()), linear_interp_flag: Data::from(poly_curve.linear_interp_flag.clone()), poly_coef_int: I64Data2D::from(poly_curve.poly_coef_int.clone()), poly_coef: U64Data2D::from(poly_curve.poly_coef.clone()), } } } impl From<&DoviMMRCurve> for MMRCurve { fn from(mmr_curve: &DoviMMRCurve) -> Self { MMRCurve { mmr_order_minus1: Data::from(mmr_curve.mmr_order_minus1.clone()), mmr_constant_int: I64Data::from(mmr_curve.mmr_constant_int.clone()), mmr_constant: U64Data::from(mmr_curve.mmr_constant.clone()), mmr_coef_int: I64Data3D::from(mmr_curve.mmr_coef_int.clone()), mmr_coef: U64Data3D::from(mmr_curve.mmr_coef.clone()), } } } dolby_vision-3.3.1/src/c_structs/rpu_data_nlq.rs000064400000000000000000000017711046102023000201550ustar 00000000000000use crate::rpu::{rpu_data_nlq::RpuDataNlq as RuRpuDataNlq, NUM_COMPONENTS}; /// C struct for rpu_data_nlq() #[repr(C)] pub struct RpuDataNlq { nlq_offset: [u16; NUM_COMPONENTS], vdr_in_max_int: [u64; NUM_COMPONENTS], vdr_in_max: [u64; NUM_COMPONENTS], linear_deadzone_slope_int: [u64; NUM_COMPONENTS], linear_deadzone_slope: [u64; NUM_COMPONENTS], linear_deadzone_threshold_int: [u64; NUM_COMPONENTS], linear_deadzone_threshold: [u64; NUM_COMPONENTS], } impl From<&RuRpuDataNlq> for RpuDataNlq { fn from(data: &RuRpuDataNlq) -> Self { Self { nlq_offset: data.nlq_offset, vdr_in_max_int: data.vdr_in_max_int, vdr_in_max: data.vdr_in_max, linear_deadzone_slope_int: data.linear_deadzone_slope_int, linear_deadzone_slope: data.linear_deadzone_slope, linear_deadzone_threshold_int: data.linear_deadzone_threshold_int, linear_deadzone_threshold: data.linear_deadzone_threshold, } } } dolby_vision-3.3.1/src/c_structs/vdr_dm_data.rs000064400000000000000000000066261046102023000177540ustar 00000000000000use crate::rpu::vdr_dm_data::VdrDmData as RuVdrDmData; use super::DmData; /// C struct for vdr_dm_data() #[repr(C)] pub struct VdrDmData { compressed: bool, affected_dm_metadata_id: u64, current_dm_metadata_id: u64, scene_refresh_flag: u64, ycc_to_rgb_coef0: i16, ycc_to_rgb_coef1: i16, ycc_to_rgb_coef2: i16, ycc_to_rgb_coef3: i16, ycc_to_rgb_coef4: i16, ycc_to_rgb_coef5: i16, ycc_to_rgb_coef6: i16, ycc_to_rgb_coef7: i16, ycc_to_rgb_coef8: i16, ycc_to_rgb_offset0: u32, ycc_to_rgb_offset1: u32, ycc_to_rgb_offset2: u32, rgb_to_lms_coef0: i16, rgb_to_lms_coef1: i16, rgb_to_lms_coef2: i16, rgb_to_lms_coef3: i16, rgb_to_lms_coef4: i16, rgb_to_lms_coef5: i16, rgb_to_lms_coef6: i16, rgb_to_lms_coef7: i16, rgb_to_lms_coef8: i16, signal_eotf: u16, signal_eotf_param0: u16, signal_eotf_param1: u16, signal_eotf_param2: u32, signal_bit_depth: u8, signal_color_space: u8, signal_chroma_format: u8, signal_full_range_flag: u8, source_min_pq: u16, source_max_pq: u16, source_diagonal: u16, dm_data: DmData, } impl VdrDmData { /// # Safety pub unsafe fn free(&self) { self.dm_data.free(); } } impl From<&RuVdrDmData> for VdrDmData { fn from(data: &RuVdrDmData) -> Self { Self { compressed: data.compressed, affected_dm_metadata_id: data.affected_dm_metadata_id, current_dm_metadata_id: data.current_dm_metadata_id, scene_refresh_flag: data.scene_refresh_flag, ycc_to_rgb_coef0: data.ycc_to_rgb_coef0, ycc_to_rgb_coef1: data.ycc_to_rgb_coef1, ycc_to_rgb_coef2: data.ycc_to_rgb_coef2, ycc_to_rgb_coef3: data.ycc_to_rgb_coef3, ycc_to_rgb_coef4: data.ycc_to_rgb_coef4, ycc_to_rgb_coef5: data.ycc_to_rgb_coef5, ycc_to_rgb_coef6: data.ycc_to_rgb_coef6, ycc_to_rgb_coef7: data.ycc_to_rgb_coef7, ycc_to_rgb_coef8: data.ycc_to_rgb_coef8, ycc_to_rgb_offset0: data.ycc_to_rgb_offset0, ycc_to_rgb_offset1: data.ycc_to_rgb_offset1, ycc_to_rgb_offset2: data.ycc_to_rgb_offset2, rgb_to_lms_coef0: data.rgb_to_lms_coef0, rgb_to_lms_coef1: data.rgb_to_lms_coef1, rgb_to_lms_coef2: data.rgb_to_lms_coef2, rgb_to_lms_coef3: data.rgb_to_lms_coef3, rgb_to_lms_coef4: data.rgb_to_lms_coef4, rgb_to_lms_coef5: data.rgb_to_lms_coef5, rgb_to_lms_coef6: data.rgb_to_lms_coef6, rgb_to_lms_coef7: data.rgb_to_lms_coef7, rgb_to_lms_coef8: data.rgb_to_lms_coef8, signal_eotf: data.signal_eotf, signal_eotf_param0: data.signal_eotf_param0, signal_eotf_param1: data.signal_eotf_param1, signal_eotf_param2: data.signal_eotf_param2, signal_bit_depth: data.signal_bit_depth, signal_color_space: data.signal_color_space, signal_chroma_format: data.signal_chroma_format, signal_full_range_flag: data.signal_full_range_flag, source_min_pq: data.source_min_pq, source_max_pq: data.source_max_pq, source_diagonal: data.source_diagonal, dm_data: DmData::combine_dm_data( data.cmv29_metadata.as_ref(), data.cmv40_metadata.as_ref(), ), } } } dolby_vision-3.3.1/src/capi.rs000064400000000000000000000320171046102023000144040ustar 00000000000000#![deny(missing_docs)] use libc::{c_char, size_t}; use std::{ ffi::{CStr, CString}, path::PathBuf, ptr::{null, null_mut}, slice, }; use crate::rpu::{dovi_rpu::DoviRpu, utils::parse_rpu_file, ConversionMode}; use super::c_structs::*; /// # Safety /// The pointer to the data must be valid. /// /// Parse a Dolby Vision RPU from unescaped byte buffer. /// Adds an error if the parsing fails. #[no_mangle] pub unsafe extern "C" fn dovi_parse_rpu(buf: *const u8, len: size_t) -> *mut RpuOpaque { assert!(!buf.is_null()); let data = slice::from_raw_parts(buf, len); let res = DoviRpu::parse_rpu(data); Box::into_raw(Box::new(RpuOpaque::from(res))) } /// # Safety /// The pointer to the data must be valid. /// /// Parse a Dolby Vision from a AV1 ITU-T T.35 metadata OBU byte buffer. /// Adds an error if the parsing fails. #[no_mangle] pub unsafe extern "C" fn dovi_parse_itu_t35_dovi_metadata_obu( buf: *const u8, len: size_t, ) -> *mut RpuOpaque { assert!(!buf.is_null()); let data = slice::from_raw_parts(buf, len); let res = DoviRpu::parse_itu_t35_dovi_metadata_obu(data); Box::into_raw(Box::new(RpuOpaque::from(res))) } /// # Safety /// The pointer to the data must be valid. /// /// Parse a Dolby Vision from a (possibly) escaped HEVC UNSPEC 62 NAL unit byte buffer. /// Adds an error if the parsing fails. #[no_mangle] pub unsafe extern "C" fn dovi_parse_unspec62_nalu(buf: *const u8, len: size_t) -> *mut RpuOpaque { assert!(!buf.is_null()); let data = slice::from_raw_parts(buf, len); let res = DoviRpu::parse_unspec62_nalu(data); Box::into_raw(Box::new(RpuOpaque::from(res))) } /// # Safety /// The pointer to the opaque struct must be valid. /// Avoid using on opaque pointers obtained through `dovi_parse_rpu_bin_file`. /// /// Free the RpuOpaque #[no_mangle] pub unsafe extern "C" fn dovi_rpu_free(ptr: *mut RpuOpaque) { if !ptr.is_null() { drop(Box::from_raw(ptr)); } } /// # Safety /// The pointer to the opaque struct must be valid. /// /// Get the last logged error for the RpuOpaque operations. /// /// On invalid parsing, an error is added. /// The user should manually verify if there is an error, as the parsing does not return an error code. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_get_error(ptr: *const RpuOpaque) -> *const c_char { if ptr.is_null() { return null(); } let opaque = &*ptr; match &opaque.error { Some(s) => s.as_ptr(), None => null(), } } /// # Safety /// The data pointer should exist, and be allocated by Rust. /// /// Free a Data buffer #[no_mangle] pub unsafe extern "C" fn dovi_data_free(data: *const Data) { if !data.is_null() { let data = Box::from_raw(data as *mut Data); data.free(); } } /// # Safety /// The struct pointer must be valid. /// /// Writes the encoded RPU as a byte buffer. /// If an error occurs in the writing, it is logged to RpuOpaque.error #[no_mangle] pub unsafe extern "C" fn dovi_write_rpu(ptr: *mut RpuOpaque) -> *const Data { if ptr.is_null() { return null_mut(); } let opaque = &mut *ptr; if let Some(rpu) = &opaque.rpu { match rpu.write_rpu() { Ok(buf) => Box::into_raw(Box::new(Data::from(buf))), Err(e) => { opaque.error = Some(CString::new(format!("Failed writing byte buffer: {e}")).unwrap()); null_mut() } } } else { null_mut() } } /// # Safety /// The struct pointer must be valid. /// /// Writes the encoded RPU, escapes the bytes for HEVC and prepends the buffer with 0x7C01. /// If an error occurs in the writing, it is logged to RpuOpaque.error #[no_mangle] pub unsafe extern "C" fn dovi_write_unspec62_nalu(ptr: *mut RpuOpaque) -> *const Data { if ptr.is_null() { return null_mut(); } let opaque = &mut *ptr; if let Some(rpu) = &opaque.rpu { match rpu.write_hevc_unspec62_nalu() { Ok(buf) => Box::into_raw(Box::new(Data::from(buf))), Err(e) => { opaque.error = Some(CString::new(format!("Failed writing byte buffer: {e}")).unwrap()); null_mut() } } } else { null_mut() } } /// # Safety /// The struct pointer must be valid. /// The mode must be between 0 and 4. /// /// Converts the RPU to be compatible with a different Dolby Vision profile. /// Possible modes: /// - 0: Don't modify the RPU /// - 1: Converts the RPU to be MEL compatible /// - 2: Converts the RPU to be profile 8.1 compatible. Both luma and chroma mapping curves are set to no-op. /// This mode handles source profiles 5, 7 and 8. /// - 3: Converts to static profile 8.4 /// - 4: Converts to profile 8.1 preserving luma and chroma mapping. Old mode 2 behaviour. /// /// If an error occurs, it is logged to RpuOpaque.error. /// Returns 0 if successful, -1 otherwise. #[no_mangle] pub unsafe extern "C" fn dovi_convert_rpu_with_mode(ptr: *mut RpuOpaque, mode: u8) -> i32 { if ptr.is_null() { return -1; } let opaque = &mut *ptr; if let Some(rpu) = &mut opaque.rpu { let mode = ConversionMode::from(mode); match rpu.convert_with_mode(mode) { Ok(_) => 0, Err(e) => { opaque.error = Some(CString::new(format!("Failed converting with mode {mode}: {e}")).unwrap()); -1 } } } else { -1 } } /// # Safety /// The pointer to the opaque struct must be valid. /// /// Get the DoVi RPU header struct. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_get_header(ptr: *const RpuOpaque) -> *const RpuDataHeader { if ptr.is_null() { return null_mut(); } let opaque = &*ptr; if let Some(rpu) = &opaque.rpu { let mut header = RpuDataHeader::from(&rpu.header); if let Some(el_type) = rpu.el_type.as_ref() { header.el_type = CString::new(el_type.as_str()).unwrap().into_raw(); } Box::into_raw(Box::new(header)) } else { null_mut() } } /// # Safety /// The pointer to the struct must be valid. /// /// Frees the memory used by the RPU header. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_free_header(ptr: *const RpuDataHeader) { if !ptr.is_null() { let header = Box::from_raw(ptr as *mut RpuDataHeader); header.free(); } } /// # Safety /// The pointer to the opaque struct must be valid. /// /// Get the DoVi RpuDataMapping struct. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_get_data_mapping(ptr: *const RpuOpaque) -> *const RpuDataMapping { if ptr.is_null() { return null_mut(); } let opaque = &*ptr; if let Some(rpu) = &opaque.rpu { if let Some(rpu_data_mapping) = &rpu.rpu_data_mapping { Box::into_raw(Box::new(RpuDataMapping::from(rpu_data_mapping))) } else { null_mut() } } else { null_mut() } } /// # Safety /// The pointer to the struct must be valid. /// /// Frees the memory used by the RpuDataMapping. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_free_data_mapping(ptr: *const RpuDataMapping) { if !ptr.is_null() { let rpu_data_mapping = Box::from_raw(ptr as *mut RpuDataMapping); rpu_data_mapping.free(); } } /// # Safety /// The pointer to the opaque struct must be valid. /// /// Get the DoVi VdrDmData struct. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_get_vdr_dm_data(ptr: *const RpuOpaque) -> *const VdrDmData { if ptr.is_null() { return null_mut(); } let opaque = &*ptr; if let Some(rpu) = &opaque.rpu { if let Some(vdr_dm_data) = &rpu.vdr_dm_data { Box::into_raw(Box::new(VdrDmData::from(vdr_dm_data))) } else { null_mut() } } else { null_mut() } } /// # Safety /// The pointer to the struct must be valid. /// /// Frees the memory used by the VdrDmData struct. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_free_vdr_dm_data(ptr: *const VdrDmData) { if !ptr.is_null() { let vdr_dm_data = Box::from_raw(ptr as *mut VdrDmData); vdr_dm_data.free(); } } /// # Safety /// The pointer to the file path must be valid. /// /// Parses an existing RPU binary file. /// /// Returns the heap allocated `DoviRpuList` as a pointer. /// The returned pointer may be null, or the list could be empty if an error occurred. #[no_mangle] pub unsafe extern "C" fn dovi_parse_rpu_bin_file(path: *const c_char) -> *const RpuOpaqueList { if !path.is_null() { let mut rpu_list = RpuOpaqueList { list: null(), len: 0, error: null(), }; let mut error = None; if let Ok(str) = CStr::from_ptr(path).to_str() { let path = PathBuf::from(str); if path.is_file() { match parse_rpu_file(path) { Ok(rpus) => { rpu_list.len = rpus.len(); let opaque_list: Vec<*mut RpuOpaque> = rpus .into_iter() .map(|rpu| Box::into_raw(Box::new(RpuOpaque::new(Some(rpu), None)))) .collect(); rpu_list.list = Box::into_raw(opaque_list.into_boxed_slice()) as *const *mut RpuOpaque; } Err(e) => { error = Some(format!("parse_rpu_bin_file: Errored while parsing: {e}")) } } } else { error = Some("parse_rpu_bin_file: Input file does not exist".to_string()); } } else { error = Some("parse_rpu_bin_file: Failed parsing the input path as a string".to_string()); } if let Some(err) = error { rpu_list.error = CString::new(err).unwrap().into_raw(); } return Box::into_raw(Box::new(rpu_list)); } null() } /// # Safety /// The pointer to the struct must be valid. /// /// Frees the memory used by the DoviRpuOpaqueList struct. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_list_free(ptr: *const RpuOpaqueList) { if !ptr.is_null() { let rpu_opaque_list = Box::from_raw(ptr as *mut RpuOpaqueList); rpu_opaque_list.free(); } } /// # Safety /// The struct pointer must be valid. /// /// Sets the L5 metadata active area offsets. /// If there is no L5 block present, it is created with the offsets. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_set_active_area_offsets( ptr: *mut RpuOpaque, left: u16, right: u16, top: u16, bottom: u16, ) -> i32 { if ptr.is_null() { return -1; } let opaque = &mut *ptr; if let Some(rpu) = &mut opaque.rpu { match rpu.set_active_area_offsets(left, right, top, bottom) { Ok(_) => 0, Err(e) => { opaque.error = Some(CString::new(format!("Failed editing active area offsets: {e}")).unwrap()); -1 } } } else { -1 } } /// # Safety /// The struct pointer must be valid. /// /// Converts the existing reshaping/mapping to become no-op. #[no_mangle] pub unsafe extern "C" fn dovi_rpu_remove_mapping(ptr: *mut RpuOpaque) -> i32 { if ptr.is_null() { return -1; } let opaque = &mut *ptr; if let Some(rpu) = &mut opaque.rpu { rpu.remove_mapping(); 0 } else { -1 } } /// # Safety /// The struct pointer must be valid. /// /// Writes the encoded RPU as `itu_t_t35_payload_bytes` for AV1 ITU-T T.35 metadata OBU /// If an error occurs in the writing, it is logged to RpuOpaque.error #[no_mangle] pub unsafe extern "C" fn dovi_write_av1_rpu_metadata_obu_t35_payload( ptr: *mut RpuOpaque, ) -> *const Data { if ptr.is_null() { return null_mut(); } let opaque = &mut *ptr; if let Some(rpu) = &opaque.rpu { match rpu.write_av1_rpu_metadata_obu_t35_payload() { Ok(buf) => Box::into_raw(Box::new(Data::from(buf))), Err(e) => { opaque.error = Some(CString::new(format!("Failed writing byte buffer: {e}")).unwrap()); null_mut() } } } else { null_mut() } } /// # Safety /// The struct pointer must be valid. /// /// Writes the encoded RPU a complete AV1 `metadata_itut_t35()` OBU /// If an error occurs in the writing, it is logged to RpuOpaque.error #[no_mangle] pub unsafe extern "C" fn dovi_write_av1_rpu_metadata_obu_t35_complete( ptr: *mut RpuOpaque, ) -> *const Data { if ptr.is_null() { return null_mut(); } let opaque = &mut *ptr; if let Some(rpu) = &opaque.rpu { match rpu.write_av1_rpu_metadata_obu_t35_complete() { Ok(buf) => Box::into_raw(Box::new(Data::from(buf))), Err(e) => { opaque.error = Some(CString::new(format!("Failed writing byte buffer: {e}")).unwrap()); null_mut() } } } else { null_mut() } } dolby_vision-3.3.1/src/lib.rs000064400000000000000000000007731046102023000142420ustar 00000000000000/// Dolby Vision RPU (as found in HEVC type 62 NALUs) module pub mod rpu; /// Dolby Vision RPU (as found in AV1 ITU T.35 metadata OBUs) pub mod av1; /// SMPTE ST2094-10 metadata module pub mod st2094_10; /// Various utils /// cbindgen:ignore pub mod utils; /// Dolby Vision XML metadata module #[cfg(feature = "xml")] pub mod xml; /// C API module #[cfg(any(cargo_c, feature = "capi"))] pub mod capi; /// Structs used and exposed in the C API #[cfg(any(cargo_c, feature = "capi"))] pub mod c_structs; dolby_vision-3.3.1/src/rpu/dovi_rpu.rs000064400000000000000000000432761046102023000161360ustar 00000000000000use anyhow::{anyhow, bail, ensure, Result}; use bitvec::prelude::{BitVec, Msb0}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::Serialize; use super::extension_metadata::blocks::{ExtMetadataBlock, ExtMetadataBlockLevel5}; use super::generate::GenerateConfig; use super::profiles::profile81::Profile81; use super::profiles::profile84::Profile84; use super::rpu_data_header::RpuDataHeader; use super::rpu_data_mapping::{DoviNlqMethod, RpuDataMapping}; use super::rpu_data_nlq::{DoviELType, RpuDataNlq}; use super::vdr_dm_data::{vdr_dm_data_payload, VdrDmData}; use super::{compute_crc32, ConversionMode}; use crate::av1::{ av1_validated_trimmed_data, convert_av1_rpu_payload_to_regular, convert_regular_rpu_to_av1_payload, }; use crate::utils::{ add_start_code_emulation_prevention_3_byte, clear_start_code_emulation_prevention_3_byte, }; pub(crate) const FINAL_BYTE: u8 = 0x80; const CRC32_TERMINATOR_BITS: u64 = 40; #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct DoviRpu { pub dovi_profile: u8, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub el_type: Option, pub header: RpuDataHeader, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub rpu_data_mapping: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub vdr_dm_data: Option, #[cfg_attr( feature = "serde", serde( serialize_with = "crate::utils::opt_bitvec_ser_bits", skip_serializing_if = "Option::is_none" ) )] pub remaining: Option>, pub rpu_data_crc32: u32, #[cfg_attr(feature = "serde", serde(skip_serializing))] pub modified: bool, #[cfg_attr(feature = "serde", serde(skip_serializing))] trailing_zeroes: usize, #[cfg_attr(feature = "serde", serde(skip_serializing))] original_payload_size: usize, } impl DoviRpu { pub fn validated_trimmed_data(data: &[u8]) -> Result<&[u8]> { if data.len() < 25 { bail!("Invalid RPU length: {}", &data.len()); } // Including 0x7C01 prepended let trimmed_data = match &data[..5] { [0, 0, 0, 1, 25] => &data[4..], [0, 0, 1, 25, 8] => &data[3..], [0, 1, 25, 8, 9] | [124, 1, 25, 8, 9] => &data[2..], [1, 25, 8, 9, _] => &data[1..], [25, 8, 9, _, _] => data, _ => bail!("Invalid RPU data start bytes\n{:?}", &data[..5]), }; Ok(trimmed_data) } /// HEVC UNSPEC62 NALU, clears start code emulation prevention 3 bytes pub fn parse_unspec62_nalu(data: &[u8]) -> Result { let trimmed_data = DoviRpu::validated_trimmed_data(data)?; // Clear start code emulation prevention 3 byte let bytes: Vec = clear_start_code_emulation_prevention_3_byte(trimmed_data); DoviRpu::parse(&bytes) } /// Parse AV1 ITU-T T.35 metadata OBU into a `DoviRpu` /// The payload is extracted out of the EMDF wrapper pub fn parse_itu_t35_dovi_metadata_obu(data: &[u8]) -> Result { let data = av1_validated_trimmed_data(data)?; let converted_buf = convert_av1_rpu_payload_to_regular(data)?; DoviRpu::parse(&converted_buf) } pub fn parse_rpu(data: &[u8]) -> Result { let trimmed_data = DoviRpu::validated_trimmed_data(data)?; DoviRpu::parse(trimmed_data) } #[inline(always)] pub(crate) fn parse(data: &[u8]) -> Result { let original_payload_size = data.len(); let trailing_zeroes = data.iter().rev().take_while(|b| **b == 0).count(); // Ignore trailing bytes let rpu_end = data.len() - trailing_zeroes; let last_byte = data[rpu_end - 1]; // Minus 4 bytes for the CRC32, 1 for the 0x80 ending byte let crc32_start = rpu_end - 5; // Ignoring the prefix byte let received_crc32 = compute_crc32(&data[1..crc32_start]); if last_byte != FINAL_BYTE { bail!("Invalid RPU last byte: {}", last_byte); } let mut dovi_rpu = DoviRpu::read_rpu_data(&data[..rpu_end])?; if received_crc32 != dovi_rpu.rpu_data_crc32 { bail!( "RPU CRC32 does not match the data. Received: {}, expected {}", received_crc32, dovi_rpu.rpu_data_crc32 ); } dovi_rpu.trailing_zeroes = trailing_zeroes; dovi_rpu.original_payload_size = original_payload_size; // Validate dovi_rpu.validate()?; Ok(dovi_rpu) } #[inline(always)] fn read_rpu_data(bytes: &[u8]) -> Result { let mut reader = BsIoSliceReader::from_slice(bytes); let rpu_prefix = reader.get_n(8)?; ensure!(rpu_prefix == 25, "rpu_nal_prefix should be 25"); let mut header = RpuDataHeader::parse(&mut reader)?; // FIXME: rpu_nal_prefix deprecation #[allow(deprecated)] { header.rpu_nal_prefix = rpu_prefix; } // Preliminary header validation let dovi_profile = header.get_dovi_profile(); header.validate(dovi_profile)?; let rpu_data_mapping = if !header.use_prev_vdr_rpu_flag { Some(RpuDataMapping::parse(&mut reader, &header)?) } else { None }; let el_type = rpu_data_mapping .as_ref() .map(|e| e.get_enhancement_layer_type()) .unwrap_or(None); let vdr_dm_data = if header.vdr_dm_metadata_present_flag { Some(vdr_dm_data_payload(&mut reader, &header)?) } else { None }; // rpu_alignment_zero_bit while !reader.is_aligned() { ensure!(!reader.get()?, "rpu_alignment_zero_bit != 0"); } // CRC32 is at the end, there can be more data in between let remaining = if reader.available()? > CRC32_TERMINATOR_BITS { let mut remaining: BitVec = BitVec::new(); while reader.available()? != CRC32_TERMINATOR_BITS { remaining.push(reader.get()?); } Some(remaining) } else { None }; let rpu_data_crc32 = reader.get_n(32)?; let last_byte: u8 = reader.get_n(8)?; ensure!(last_byte == FINAL_BYTE, "last byte should be 0x80"); Ok(DoviRpu { dovi_profile: header.get_dovi_profile(), el_type, header, rpu_data_mapping, vdr_dm_data, remaining, rpu_data_crc32, ..Default::default() }) } pub fn write_hevc_unspec62_nalu(&self) -> Result> { let mut out = self.write_rpu_data()?; add_start_code_emulation_prevention_3_byte(&mut out); // Put back NAL unit type out.insert(0, 0x01); out.insert(0, 0x7C); Ok(out) } pub fn write_rpu(&self) -> Result> { self.write_rpu_data() } /// `itu_t_t35_payload_bytes` pub fn write_av1_rpu_metadata_obu_t35_payload(&self) -> Result> { convert_regular_rpu_to_av1_payload(&self.write_rpu_data()?) } /// Complete `metadata_itut_t35()`, including `itu_t_t35_country_code` pub fn write_av1_rpu_metadata_obu_t35_complete(&self) -> Result> { let mut encoded_rpu = self.write_av1_rpu_metadata_obu_t35_payload()?; encoded_rpu.insert(0, 0xB5); Ok(encoded_rpu) } #[inline(always)] fn write_rpu_data(&self) -> Result> { // Capacity is in bits let mut writer = BitstreamIoWriter::with_capacity(self.original_payload_size * 8); self.validate()?; // RPU prefix writer.write_n(&0x19, 8)?; let header = &self.header; header.write_header(&mut writer)?; if header.rpu_type == 2 { if !header.use_prev_vdr_rpu_flag { if let Some(mapping) = &self.rpu_data_mapping { mapping.write(&mut writer, &self.header)?; } } if header.vdr_dm_metadata_present_flag { if let Some(vdr_dm_data) = &self.vdr_dm_data { vdr_dm_data.write(&mut writer)?; } } } if let Some(remaining) = &self.remaining { for b in remaining { writer.write(*b)?; } } writer.byte_align()?; let computed_crc32 = compute_crc32( writer .as_slice() .map(|s| &s[1..]) .ok_or_else(|| anyhow!("Unaligned bytes"))?, ); if !self.modified { // Validate the parsed crc32 is the same ensure!( self.rpu_data_crc32 == computed_crc32, "RPU CRC32 does not match computed value" ); } // Write crc32 writer.write_n(&computed_crc32, 32)?; writer.write_n(&FINAL_BYTE, 8)?; // Trailing bytes if self.trailing_zeroes > 0 { for _ in 0..self.trailing_zeroes { writer.write_n(&0_u8, 8)?; } } Ok(writer.into_inner()) } fn validate(&self) -> Result<()> { self.header.validate(self.dovi_profile)?; if let Some(mapping) = self.rpu_data_mapping.as_ref() { mapping.validate(self.dovi_profile)?; } if let Some(vdr_dm_data) = &self.vdr_dm_data { vdr_dm_data.validate()?; } Ok(()) } pub fn get_enhancement_layer_type(&self) -> Option { self.rpu_data_mapping .as_ref() .map(|e| e.get_enhancement_layer_type()) .unwrap_or(None) } /// Modes: /// - 0: Don't modify the RPU /// - 1: Converts the RPU to be MEL compatible /// - 2: Converts the RPU to be profile 8.1 compatible. /// Both luma and chroma mapping curves are set to no-op. /// This mode handles source profiles 5, 7 and 8. /// - 3: Converts to static profile 8.4 /// - 4: Converts to profile 8.1 preserving luma and chroma mapping. /// Old mode 2 behaviour. /// /// noop when profile 8 and mode 2 is used pub fn convert_with_mode>(&mut self, mode: T) -> Result<()> { let mode: ConversionMode = mode.into(); if mode != ConversionMode::Lossless { self.modified = true; } let valid_conversion = match mode { ConversionMode::Lossless => true, ConversionMode::ToMel => { if matches!(self.dovi_profile, 7 | 8) { self.convert_to_mel()?; true } else { false } } ConversionMode::To81 => match self.dovi_profile { 7 | 8 => { self.convert_to_p81_remove_mapping(); true } 5 => { self.p5_to_p81()?; true } _ => false, }, ConversionMode::To84 => { self.convert_to_p84(); true } ConversionMode::To81MappingPreserved => { if matches!(self.dovi_profile, 7 | 8) { self.convert_to_p81(); true } else { false } } }; if !valid_conversion { bail!("Invalid profile for mode {} conversion!", mode); } // Update profile value self.dovi_profile = self.header.get_dovi_profile(); self.el_type = self.get_enhancement_layer_type(); Ok(()) } fn convert_to_mel(&mut self) -> Result<()> { let header = &mut self.header; header.el_spatial_resampling_filter_flag = true; header.disable_residual_flag = false; if let Some(mapping) = self.rpu_data_mapping.as_mut() { mapping.nlq_method_idc = Some(DoviNlqMethod::LinearDeadzone); mapping.nlq_num_pivots_minus2 = Some(0); // BL is always 10 bit in current spec mapping.nlq_pred_pivot_value = Some([0, 1023]); if let Some(nlq) = mapping.nlq.as_mut() { nlq.convert_to_mel(); } else if self.dovi_profile == 8 { mapping.nlq = Some(RpuDataNlq::mel_default()); } else { bail!("Not profile 7 or 8, cannot convert to MEL!"); } } Ok(()) } fn convert_to_p81(&mut self) { self.modified = true; let header = &mut self.header; // Change to 8.1 header.el_spatial_resampling_filter_flag = false; header.disable_residual_flag = true; if let Some(mapping) = self.rpu_data_mapping.as_mut() { mapping.nlq_method_idc = None; mapping.nlq_num_pivots_minus2 = None; mapping.nlq_pred_pivot_value = None; mapping.num_x_partitions_minus1 = 0; mapping.num_y_partitions_minus1 = 0; mapping.nlq = None; } if let Some(vdr_dm_data) = self.vdr_dm_data.as_mut() { vdr_dm_data.set_p81_coeffs(); } } fn convert_to_p81_remove_mapping(&mut self) { self.modified = true; self.convert_to_p81(); if let Some(el_type) = self.el_type.as_ref() { if el_type == &DoviELType::FEL { self.remove_mapping(); } } } fn p5_to_p81(&mut self) -> Result<()> { self.modified = true; if self.dovi_profile == 5 { self.convert_to_p81(); self.dovi_profile = 8; self.header.vdr_rpu_profile = 1; self.header.bl_video_full_range_flag = false; self.remove_mapping(); if let Some(vdr_dm_data) = self.vdr_dm_data.as_mut() { vdr_dm_data.set_p81_coeffs(); } } else { bail!("Attempt to convert profile 5: RPU is not profile 5!"); } Ok(()) } pub fn profile5_config(config: &GenerateConfig) -> Result { Ok(DoviRpu { dovi_profile: 5, modified: true, header: RpuDataHeader::p5_default(), rpu_data_mapping: Some(Profile81::rpu_data_mapping()), vdr_dm_data: Some(VdrDmData::from_generate_config(config)?), ..Default::default() }) } pub fn profile81_config(config: &GenerateConfig) -> Result { Ok(DoviRpu { dovi_profile: 8, modified: true, header: RpuDataHeader::p8_default(), rpu_data_mapping: Some(Profile81::rpu_data_mapping()), vdr_dm_data: Some(VdrDmData::from_generate_config(config)?), ..Default::default() }) } /// Set existing L5 metadata to zero offsets /// If there is no L5 metadata, creates it with zero offsets pub fn crop(&mut self) -> Result<()> { self.modified = true; if let Some(vdr_dm_data) = self.vdr_dm_data.as_mut() { vdr_dm_data.replace_metadata_block(ExtMetadataBlock::Level5( ExtMetadataBlockLevel5::default(), ))?; } Ok(()) } pub fn set_active_area_offsets( &mut self, left: u16, right: u16, top: u16, bottom: u16, ) -> Result<()> { self.modified = true; if let Some(vdr_dm_data) = self.vdr_dm_data.as_mut() { vdr_dm_data.replace_metadata_block(ExtMetadataBlock::Level5( ExtMetadataBlockLevel5::from_offsets(left, right, top, bottom), ))?; } Ok(()) } pub fn remove_mapping(&mut self) { self.modified = true; if let Some(rpu_data_mapping) = self.rpu_data_mapping.as_mut() { rpu_data_mapping.set_empty_p81_mapping(); } } pub fn parse_list_of_unspec62_nalus(data: &[Vec]) -> Vec { data.iter() .map(|rpu| DoviRpu::parse_unspec62_nalu(rpu)) .filter_map(Result::ok) .collect() } pub fn profile84_config(config: &GenerateConfig) -> Result { Ok(DoviRpu { dovi_profile: 8, modified: true, header: RpuDataHeader::p8_default(), rpu_data_mapping: Some(Profile84::rpu_data_mapping()), vdr_dm_data: Some(VdrDmData::from_generate_config(config)?), ..Default::default() }) } fn convert_to_p84(&mut self) { self.convert_to_p81(); self.header = RpuDataHeader::p8_default(); self.rpu_data_mapping = Some(Profile84::rpu_data_mapping()); } pub fn remove_cmv40_extension_metadata(&mut self) -> Result<()> { if let Some(vdr_dm_data) = self.vdr_dm_data.as_mut() { if vdr_dm_data.cmv40_metadata.is_some() { self.modified = true; vdr_dm_data.cmv40_metadata = None; } } Ok(()) } pub fn replace_levels_from_rpu(&mut self, src_rpu: &Self, levels: &Vec) -> Result<()> { ensure!(!levels.is_empty(), "Must have levels to replace"); if let (Some(dst_vdr_dm_data), Some(src_vdr_dm_data)) = (self.vdr_dm_data.as_mut(), src_rpu.vdr_dm_data.as_ref()) { self.modified = true; for level in levels { dst_vdr_dm_data .replace_metadata_blocks(src_vdr_dm_data.level_blocks_iter(*level))?; } } Ok(()) } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level1.rs000064400000000000000000000056461046102023000226270ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::rpu::vdr_dm_data::CmVersion; use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; /// cbindgen:ignore pub const L1_MIN_PQ_MAX_VALUE: u16 = 12; /// cbindgen:ignore pub const L1_MAX_PQ_MIN_VALUE: u16 = 2081; /// cbindgen:ignore pub const L1_MAX_PQ_MAX_VALUE: u16 = 4095; /// cbindgen:ignore pub const L1_AVG_PQ_MIN_VALUE: u16 = 819; /// cbindgen:ignore pub const L1_AVG_PQ_MIN_VALUE_CMV40: u16 = 1229; /// Statistical analysis of the frame: min, max, avg brightness. #[repr(C)] #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ExtMetadataBlockLevel1 { pub min_pq: u16, pub max_pq: u16, pub avg_pq: u16, } impl ExtMetadataBlockLevel1 { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { Ok(ExtMetadataBlock::Level1(Self { min_pq: reader.get_n(12)?, max_pq: reader.get_n(12)?, avg_pq: reader.get_n(12)?, })) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; writer.write_n(&self.min_pq, 12)?; writer.write_n(&self.max_pq, 12)?; writer.write_n(&self.avg_pq, 12)?; Ok(()) } pub fn validate(&self) -> Result<()> { ensure!(self.min_pq <= L1_MAX_PQ_MAX_VALUE); ensure!(self.max_pq <= L1_MAX_PQ_MAX_VALUE); ensure!(self.avg_pq <= L1_MAX_PQ_MAX_VALUE); Ok(()) } pub fn new(min_pq: u16, max_pq: u16, avg_pq: u16) -> ExtMetadataBlockLevel1 { ExtMetadataBlockLevel1 { min_pq, max_pq, avg_pq, } } fn clamp_values_int(&mut self, cm_version: CmVersion) { let avg_min_value = match cm_version { CmVersion::V29 => L1_AVG_PQ_MIN_VALUE, CmVersion::V40 => L1_AVG_PQ_MIN_VALUE_CMV40, }; self.min_pq = self.min_pq.clamp(0, L1_MIN_PQ_MAX_VALUE); self.max_pq = self.max_pq.clamp(L1_MAX_PQ_MIN_VALUE, L1_MAX_PQ_MAX_VALUE); self.avg_pq = self.avg_pq.clamp(avg_min_value, self.max_pq - 1); } // Returns a L1 metadata block clamped to valid values pub fn from_stats_cm_version( min_pq: u16, max_pq: u16, avg_pq: u16, cm_version: CmVersion, ) -> ExtMetadataBlockLevel1 { let mut block = Self::new(min_pq, max_pq, avg_pq); block.clamp_values_int(cm_version); block } pub fn clamp_values_cm_version(&mut self, cm_version: CmVersion) { self.clamp_values_int(cm_version); } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel1 { fn level(&self) -> u8 { 1 } fn bytes_size(&self) -> u64 { 5 } fn required_bits(&self) -> u64 { 36 } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level10.rs000064400000000000000000000157651046102023000227120ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; use super::{level6::MAX_PQ_LUMINANCE, ColorPrimaries, ExtMetadataBlock, ExtMetadataBlockInfo}; pub const PRESET_TARGET_DISPLAYS: &[u8] = &[1, 16, 18, 21, 27, 28, 37, 38, 42, 48, 49]; /// Custom target display information /// /// This block can have varying byte lengths: 5 or 21 /// Depending on the length, the fields parsed default to zero and may not be set. /// Up to (including): /// - 5: target_primary_index /// - 21: target_primary_{red,green,blue,white}_{x,y} #[repr(C)] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ExtMetadataBlockLevel10 { pub length: u64, pub target_display_index: u8, pub target_max_pq: u16, pub target_min_pq: u16, pub target_primary_index: u8, pub target_primary_red_x: u16, pub target_primary_red_y: u16, pub target_primary_green_x: u16, pub target_primary_green_y: u16, pub target_primary_blue_x: u16, pub target_primary_blue_y: u16, pub target_primary_white_x: u16, pub target_primary_white_y: u16, } impl ExtMetadataBlockLevel10 { pub(crate) fn parse(reader: &mut BsIoSliceReader, length: u64) -> Result { let mut block = Self { length, target_display_index: reader.get_n(8)?, target_max_pq: reader.get_n(12)?, target_min_pq: reader.get_n(12)?, target_primary_index: reader.get_n(8)?, ..Default::default() }; if length > 5 { block.target_primary_red_x = reader.get_n(16)?; block.target_primary_red_y = reader.get_n(16)?; block.target_primary_green_x = reader.get_n(16)?; block.target_primary_green_y = reader.get_n(16)?; block.target_primary_blue_x = reader.get_n(16)?; block.target_primary_blue_y = reader.get_n(16)?; block.target_primary_white_x = reader.get_n(16)?; block.target_primary_white_y = reader.get_n(16)?; } Ok(ExtMetadataBlock::Level10(block)) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; writer.write_n(&self.target_display_index, 8)?; writer.write_n(&self.target_max_pq, 12)?; writer.write_n(&self.target_min_pq, 12)?; writer.write_n(&self.target_primary_index, 8)?; if self.length > 5 { writer.write_n(&self.target_primary_red_x, 16)?; writer.write_n(&self.target_primary_red_y, 16)?; writer.write_n(&self.target_primary_green_x, 16)?; writer.write_n(&self.target_primary_green_y, 16)?; writer.write_n(&self.target_primary_blue_x, 16)?; writer.write_n(&self.target_primary_blue_y, 16)?; writer.write_n(&self.target_primary_white_x, 16)?; writer.write_n(&self.target_primary_white_y, 16)?; } Ok(()) } pub fn validate(&self) -> Result<()> { ensure!(!PRESET_TARGET_DISPLAYS.contains(&self.target_display_index)); ensure!(self.target_max_pq <= MAX_PQ_LUMINANCE); ensure!(self.target_min_pq <= MAX_PQ_LUMINANCE); if self.length > 5 { ensure!(self.target_primary_index == 255); ensure!(self.target_primary_red_x > 0); ensure!(self.target_primary_red_y > 0); ensure!(self.target_primary_green_x > 0); ensure!(self.target_primary_green_y > 0); ensure!(self.target_primary_blue_x > 0); ensure!(self.target_primary_blue_y > 0); ensure!(self.target_primary_white_x > 0); ensure!(self.target_primary_white_y > 0); } else { ensure!(self.target_primary_index != 255); } Ok(()) } pub fn set_from_primaries(&mut self, primaries: &ColorPrimaries) { self.target_primary_red_x = primaries.red_x; self.target_primary_red_y = primaries.red_y; self.target_primary_green_x = primaries.green_x; self.target_primary_green_y = primaries.green_y; self.target_primary_blue_x = primaries.blue_x; self.target_primary_blue_y = primaries.blue_y; self.target_primary_white_x = primaries.white_x; self.target_primary_white_y = primaries.white_y; } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel10 { fn level(&self) -> u8 { 10 } fn bytes_size(&self) -> u64 { self.length } fn required_bits(&self) -> u64 { match self.length { 5 => 40, 21 => 168, _ => unreachable!(), } } fn sort_key(&self) -> (u8, u16) { (self.level(), self.target_display_index as u16) } } impl Default for ExtMetadataBlockLevel10 { fn default() -> Self { Self { length: 5, target_display_index: 20, target_max_pq: 2081, target_min_pq: 0, target_primary_index: 2, target_primary_red_x: 0, target_primary_red_y: 0, target_primary_green_x: 0, target_primary_green_y: 0, target_primary_blue_x: 0, target_primary_blue_y: 0, target_primary_white_x: 0, target_primary_white_y: 0, } } } #[cfg(feature = "serde")] impl Serialize for ExtMetadataBlockLevel10 { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let name = "ExtMetadataBlockLevel9"; let fields_count = match self.length { 5 => 5, 21 => 13, _ => unreachable!(), }; let mut state = serializer.serialize_struct(name, fields_count)?; state.serialize_field("length", &self.length)?; state.serialize_field("target_display_index", &self.target_display_index)?; state.serialize_field("target_max_pq", &self.target_max_pq)?; state.serialize_field("target_min_pq", &self.target_min_pq)?; state.serialize_field("target_primary_index", &self.target_primary_index)?; if self.length > 5 { state.serialize_field("target_primary_red_x", &self.target_primary_red_x)?; state.serialize_field("target_primary_red_y", &self.target_primary_red_y)?; state.serialize_field("target_primary_green_x", &self.target_primary_green_x)?; state.serialize_field("target_primary_green_y", &self.target_primary_green_y)?; state.serialize_field("target_primary_blue_x", &self.target_primary_blue_x)?; state.serialize_field("target_primary_blue_y", &self.target_primary_blue_y)?; state.serialize_field("target_primary_white_x", &self.target_primary_white_x)?; state.serialize_field("target_primary_white_y", &self.target_primary_white_y)?; } state.end() } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level11.rs000064400000000000000000000046331046102023000227030ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; const MAX_WHITEPOINT_VALUE: u8 = 15; /// Content type metadata level #[repr(C)] #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ExtMetadataBlockLevel11 { pub content_type: u8, pub whitepoint: u8, pub reference_mode_flag: bool, #[cfg_attr(feature = "serde", serde(default))] pub reserved_byte2: u8, #[cfg_attr(feature = "serde", serde(default))] pub reserved_byte3: u8, } impl ExtMetadataBlockLevel11 { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { let mut l11 = Self { content_type: reader.get_n(8)?, whitepoint: reader.get_n(8)?, reserved_byte2: reader.get_n(8)?, reserved_byte3: reader.get_n(8)?, ..Default::default() }; if l11.whitepoint > MAX_WHITEPOINT_VALUE { l11.reference_mode_flag = true; l11.whitepoint -= MAX_WHITEPOINT_VALUE + 1; } Ok(ExtMetadataBlock::Level11(l11)) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; let mut wp = self.whitepoint; if self.reference_mode_flag { wp += MAX_WHITEPOINT_VALUE + 1 } writer.write_n(&self.content_type, 8)?; writer.write_n(&wp, 8)?; writer.write_n(&self.reserved_byte2, 8)?; writer.write_n(&self.reserved_byte3, 8)?; Ok(()) } pub fn validate(&self) -> Result<()> { ensure!(self.content_type <= 15); ensure!(self.whitepoint <= 15); ensure!(self.reserved_byte2 == 0); ensure!(self.reserved_byte3 == 0); Ok(()) } /// Cinema, reference mode, D65 whitepoint pub fn default_reference_cinema() -> Self { Self { content_type: 1, whitepoint: 0, reference_mode_flag: true, reserved_byte2: 0, reserved_byte3: 0, } } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel11 { fn level(&self) -> u8 { 11 } fn bytes_size(&self) -> u64 { 4 } fn required_bits(&self) -> u64 { 32 } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level2.rs000064400000000000000000000063161046102023000226230ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::utils::nits_to_pq; use super::{ExtMetadataBlock, ExtMetadataBlockInfo, MAX_12_BIT_VALUE}; /// Creative intent trim passes per target display peak brightness #[repr(C)] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ExtMetadataBlockLevel2 { pub target_max_pq: u16, pub trim_slope: u16, pub trim_offset: u16, pub trim_power: u16, pub trim_chroma_weight: u16, pub trim_saturation_gain: u16, pub ms_weight: i16, } impl ExtMetadataBlockLevel2 { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { let mut level2 = Self { target_max_pq: reader.get_n(12)?, trim_slope: reader.get_n(12)?, trim_offset: reader.get_n(12)?, trim_power: reader.get_n(12)?, trim_chroma_weight: reader.get_n(12)?, trim_saturation_gain: reader.get_n(12)?, ms_weight: reader.get_n::(13)? as i16, }; if level2.ms_weight > MAX_12_BIT_VALUE as i16 { level2.ms_weight = level2.ms_weight.wrapping_sub(8192); } Ok(ExtMetadataBlock::Level2(level2)) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; writer.write_n(&self.target_max_pq, 12)?; writer.write_n(&self.trim_slope, 12)?; writer.write_n(&self.trim_offset, 12)?; writer.write_n(&self.trim_power, 12)?; writer.write_n(&self.trim_chroma_weight, 12)?; writer.write_n(&self.trim_saturation_gain, 12)?; writer.write_signed_n(&self.ms_weight, 13)?; Ok(()) } pub fn validate(&self) -> Result<()> { ensure!(self.target_max_pq <= MAX_12_BIT_VALUE); ensure!(self.trim_slope <= MAX_12_BIT_VALUE); ensure!(self.trim_offset <= MAX_12_BIT_VALUE); ensure!(self.trim_power <= MAX_12_BIT_VALUE); ensure!(self.trim_chroma_weight <= MAX_12_BIT_VALUE); ensure!(self.trim_saturation_gain <= MAX_12_BIT_VALUE); ensure!(self.ms_weight >= -1 && self.ms_weight <= (MAX_12_BIT_VALUE as i16)); Ok(()) } pub fn from_nits(target_nits: u16) -> ExtMetadataBlockLevel2 { ExtMetadataBlockLevel2 { target_max_pq: (nits_to_pq(target_nits.into()) * 4095.0).round() as u16, ..Default::default() } } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel2 { fn level(&self) -> u8 { 2 } fn bytes_size(&self) -> u64 { 11 } fn required_bits(&self) -> u64 { 85 } fn sort_key(&self) -> (u8, u16) { (self.level(), self.target_max_pq) } } impl Default for ExtMetadataBlockLevel2 { fn default() -> Self { Self { target_max_pq: 2081, trim_slope: 2048, trim_offset: 2048, trim_power: 2048, trim_chroma_weight: 2048, trim_saturation_gain: 2048, ms_weight: 2048, } } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level254.rs000064400000000000000000000025271046102023000227740ustar 00000000000000use anyhow::Result; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; /// Metadata level present in CM v4.0 #[repr(C)] #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ExtMetadataBlockLevel254 { pub dm_mode: u8, pub dm_version_index: u8, } impl ExtMetadataBlockLevel254 { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { Ok(ExtMetadataBlock::Level254(Self { dm_mode: reader.get_n(8)?, dm_version_index: reader.get_n(8)?, })) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { writer.write_n(&self.dm_mode, 8)?; writer.write_n(&self.dm_version_index, 8)?; Ok(()) } pub fn cmv402_default() -> ExtMetadataBlockLevel254 { ExtMetadataBlockLevel254 { dm_mode: 0, dm_version_index: 2, } } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel254 { fn level(&self) -> u8 { 254 } fn bytes_size(&self) -> u64 { 2 } fn required_bits(&self) -> u64 { 16 } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level255.rs000064400000000000000000000032711046102023000227720ustar 00000000000000use anyhow::Result; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; /// Metadata level optionally present in CM v2.9. /// Different display modes (calibration/verify/bypass), debugging #[repr(C)] #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ExtMetadataBlockLevel255 { pub dm_run_mode: u8, pub dm_run_version: u8, pub dm_debug0: u8, pub dm_debug1: u8, pub dm_debug2: u8, pub dm_debug3: u8, } impl ExtMetadataBlockLevel255 { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { Ok(ExtMetadataBlock::Level255(Self { dm_run_mode: reader.get_n(8)?, dm_run_version: reader.get_n(8)?, dm_debug0: reader.get_n(8)?, dm_debug1: reader.get_n(8)?, dm_debug2: reader.get_n(8)?, dm_debug3: reader.get_n(8)?, })) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { writer.write_n(&self.dm_run_mode, 8)?; writer.write_n(&self.dm_run_version, 8)?; writer.write_n(&self.dm_debug0, 8)?; writer.write_n(&self.dm_debug1, 8)?; writer.write_n(&self.dm_debug2, 8)?; writer.write_n(&self.dm_debug3, 8)?; Ok(()) } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel255 { fn level(&self) -> u8 { 255 } fn bytes_size(&self) -> u64 { 6 } fn required_bits(&self) -> u64 { 48 } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level3.rs000064400000000000000000000034051046102023000226200ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::{ExtMetadataBlock, ExtMetadataBlockInfo, MAX_12_BIT_VALUE}; /// Level 1 offsets. #[repr(C)] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ExtMetadataBlockLevel3 { pub min_pq_offset: u16, pub max_pq_offset: u16, pub avg_pq_offset: u16, } impl ExtMetadataBlockLevel3 { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { Ok(ExtMetadataBlock::Level3(Self { min_pq_offset: reader.get_n(12)?, max_pq_offset: reader.get_n(12)?, avg_pq_offset: reader.get_n(12)?, })) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; writer.write_n(&self.min_pq_offset, 12)?; writer.write_n(&self.max_pq_offset, 12)?; writer.write_n(&self.avg_pq_offset, 12)?; Ok(()) } pub fn validate(&self) -> Result<()> { ensure!(self.min_pq_offset <= MAX_12_BIT_VALUE); ensure!(self.max_pq_offset <= MAX_12_BIT_VALUE); ensure!(self.avg_pq_offset <= MAX_12_BIT_VALUE); Ok(()) } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel3 { fn level(&self) -> u8 { 3 } fn bytes_size(&self) -> u64 { 5 } fn required_bits(&self) -> u64 { 36 } } impl Default for ExtMetadataBlockLevel3 { fn default() -> Self { Self { min_pq_offset: 2048, max_pq_offset: 2048, avg_pq_offset: 2048, } } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level4.rs000064400000000000000000000025361046102023000226250ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::{ExtMetadataBlock, ExtMetadataBlockInfo, MAX_12_BIT_VALUE}; /// Something about temporal stability #[repr(C)] #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ExtMetadataBlockLevel4 { pub anchor_pq: u16, pub anchor_power: u16, } impl ExtMetadataBlockLevel4 { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { Ok(ExtMetadataBlock::Level4(Self { anchor_pq: reader.get_n(12)?, anchor_power: reader.get_n(12)?, })) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; writer.write_n(&self.anchor_pq, 12)?; writer.write_n(&self.anchor_power, 12)?; Ok(()) } pub fn validate(&self) -> Result<()> { ensure!(self.anchor_pq <= MAX_12_BIT_VALUE); ensure!(self.anchor_power <= MAX_12_BIT_VALUE); Ok(()) } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel4 { fn level(&self) -> u8 { 4 } fn bytes_size(&self) -> u64 { 3 } fn required_bits(&self) -> u64 { 24 } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level5.rs000064400000000000000000000063351046102023000226270ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; const MAX_RESOLUTION_13_BITS: u16 = 8191; /// Active area of the picture (letterbox, aspect ratio) #[repr(C)] #[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ExtMetadataBlockLevel5 { pub active_area_left_offset: u16, pub active_area_right_offset: u16, pub active_area_top_offset: u16, pub active_area_bottom_offset: u16, } impl ExtMetadataBlockLevel5 { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { Ok(ExtMetadataBlock::Level5(Self { active_area_left_offset: reader.get_n(13)?, active_area_right_offset: reader.get_n(13)?, active_area_top_offset: reader.get_n(13)?, active_area_bottom_offset: reader.get_n(13)?, })) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; writer.write_n(&self.active_area_left_offset, 13)?; writer.write_n(&self.active_area_right_offset, 13)?; writer.write_n(&self.active_area_top_offset, 13)?; writer.write_n(&self.active_area_bottom_offset, 13)?; Ok(()) } pub fn validate(&self) -> Result<()> { ensure!(self.active_area_left_offset <= MAX_RESOLUTION_13_BITS); ensure!(self.active_area_right_offset <= MAX_RESOLUTION_13_BITS); ensure!(self.active_area_top_offset <= MAX_RESOLUTION_13_BITS); ensure!(self.active_area_bottom_offset <= MAX_RESOLUTION_13_BITS); Ok(()) } pub fn get_offsets(&self) -> (u16, u16, u16, u16) { ( self.active_area_left_offset, self.active_area_right_offset, self.active_area_top_offset, self.active_area_bottom_offset, ) } pub fn get_offsets_vec(&self) -> Vec { vec![ self.active_area_left_offset, self.active_area_right_offset, self.active_area_top_offset, self.active_area_bottom_offset, ] } pub fn set_offsets(&mut self, left: u16, right: u16, top: u16, bottom: u16) { self.active_area_left_offset = left; self.active_area_right_offset = right; self.active_area_top_offset = top; self.active_area_bottom_offset = bottom; } pub fn crop(&mut self) { self.active_area_left_offset = 0; self.active_area_right_offset = 0; self.active_area_top_offset = 0; self.active_area_bottom_offset = 0; } pub fn from_offsets(left: u16, right: u16, top: u16, bottom: u16) -> Self { ExtMetadataBlockLevel5 { active_area_left_offset: left, active_area_right_offset: right, active_area_top_offset: top, active_area_bottom_offset: bottom, } } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel5 { fn level(&self) -> u8 { 5 } fn bytes_size(&self) -> u64 { 7 } fn required_bits(&self) -> u64 { 52 } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level6.rs000064400000000000000000000051001046102023000226150ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; /// cbindgen:ignore pub const MAX_PQ_LUMINANCE: u16 = 10_000; /// ST2086/HDR10 metadata fallback #[repr(C)] #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ExtMetadataBlockLevel6 { pub max_display_mastering_luminance: u16, pub min_display_mastering_luminance: u16, pub max_content_light_level: u16, pub max_frame_average_light_level: u16, } impl ExtMetadataBlockLevel6 { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { Ok(ExtMetadataBlock::Level6(Self { max_display_mastering_luminance: reader.get_n(16)?, min_display_mastering_luminance: reader.get_n(16)?, max_content_light_level: reader.get_n(16)?, max_frame_average_light_level: reader.get_n(16)?, })) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; writer.write_n(&self.max_display_mastering_luminance, 16)?; writer.write_n(&self.min_display_mastering_luminance, 16)?; writer.write_n(&self.max_content_light_level, 16)?; writer.write_n(&self.max_frame_average_light_level, 16)?; Ok(()) } pub fn validate(&self) -> Result<()> { ensure!(self.max_display_mastering_luminance <= MAX_PQ_LUMINANCE); ensure!(self.min_display_mastering_luminance <= MAX_PQ_LUMINANCE); ensure!(self.max_content_light_level <= MAX_PQ_LUMINANCE); ensure!(self.max_frame_average_light_level <= MAX_PQ_LUMINANCE); Ok(()) } pub fn source_meta_from_l6(&self) -> (u16, u16) { let mdl_min = self.min_display_mastering_luminance; let mdl_max = self.max_display_mastering_luminance; let source_min_pq = if mdl_min <= 10 { 7 } else if mdl_min == 50 { 62 } else { 0 }; let source_max_pq = match mdl_max { 1000 => 3079, 2000 => 3388, 4000 => 3696, 10000 => 4095, _ => 3079, }; (source_min_pq, source_max_pq) } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel6 { fn level(&self) -> u8 { 6 } fn bytes_size(&self) -> u64 { 8 } fn required_bits(&self) -> u64 { 64 } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level8.rs000064400000000000000000000217751046102023000226370ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; use super::{ExtMetadataBlock, ExtMetadataBlockInfo, MAX_12_BIT_VALUE}; /// Creative intent trim passes per target display peak brightness /// For CM v4.0, L8 metadata only is present and used to compute L2 /// /// This block can have varying byte lengths: 10, 12, 13, 19, 25 /// Depending on the length, the fields parsed default to zero and may not be set. /// Up to (including): /// - 10: ms_weight /// - 12: target_mid_contrast /// - 13: clip_trim /// - 19: saturation_vector_field[0-5] /// - 25: hue_vector_field[0-5] #[repr(C)] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ExtMetadataBlockLevel8 { pub length: u64, pub target_display_index: u8, pub trim_slope: u16, pub trim_offset: u16, pub trim_power: u16, pub trim_chroma_weight: u16, pub trim_saturation_gain: u16, pub ms_weight: u16, pub target_mid_contrast: u16, pub clip_trim: u16, pub saturation_vector_field0: u8, pub saturation_vector_field1: u8, pub saturation_vector_field2: u8, pub saturation_vector_field3: u8, pub saturation_vector_field4: u8, pub saturation_vector_field5: u8, pub hue_vector_field0: u8, pub hue_vector_field1: u8, pub hue_vector_field2: u8, pub hue_vector_field3: u8, pub hue_vector_field4: u8, pub hue_vector_field5: u8, } impl ExtMetadataBlockLevel8 { pub(crate) fn parse(reader: &mut BsIoSliceReader, length: u64) -> Result { let mut block = Self { length, target_display_index: reader.get_n(8)?, trim_slope: reader.get_n(12)?, trim_offset: reader.get_n(12)?, trim_power: reader.get_n(12)?, trim_chroma_weight: reader.get_n(12)?, trim_saturation_gain: reader.get_n(12)?, ms_weight: reader.get_n(12)?, ..Default::default() }; if length > 10 { block.target_mid_contrast = reader.get_n(12)?; } if length > 12 { block.clip_trim = reader.get_n(12)?; } if length > 13 { block.saturation_vector_field0 = reader.get_n(8)?; block.saturation_vector_field1 = reader.get_n(8)?; block.saturation_vector_field2 = reader.get_n(8)?; block.saturation_vector_field3 = reader.get_n(8)?; block.saturation_vector_field4 = reader.get_n(8)?; block.saturation_vector_field5 = reader.get_n(8)?; } if length > 19 { block.hue_vector_field0 = reader.get_n(8)?; block.hue_vector_field1 = reader.get_n(8)?; block.hue_vector_field2 = reader.get_n(8)?; block.hue_vector_field3 = reader.get_n(8)?; block.hue_vector_field4 = reader.get_n(8)?; block.hue_vector_field5 = reader.get_n(8)?; } Ok(ExtMetadataBlock::Level8(block)) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; writer.write_n(&self.target_display_index, 8)?; writer.write_n(&self.trim_slope, 12)?; writer.write_n(&self.trim_offset, 12)?; writer.write_n(&self.trim_power, 12)?; writer.write_n(&self.trim_chroma_weight, 12)?; writer.write_n(&self.trim_saturation_gain, 12)?; writer.write_n(&self.ms_weight, 12)?; // Write default values when the fields can not be omitted if self.length > 10 { writer.write_n(&self.target_mid_contrast, 12)?; } if self.length > 12 { writer.write_n(&self.clip_trim, 12)?; } if self.length > 13 { writer.write_n(&self.saturation_vector_field0, 8)?; writer.write_n(&self.saturation_vector_field1, 8)?; writer.write_n(&self.saturation_vector_field2, 8)?; writer.write_n(&self.saturation_vector_field3, 8)?; writer.write_n(&self.saturation_vector_field4, 8)?; writer.write_n(&self.saturation_vector_field5, 8)?; } if self.length > 19 { writer.write_n(&self.hue_vector_field0, 8)?; writer.write_n(&self.hue_vector_field1, 8)?; writer.write_n(&self.hue_vector_field2, 8)?; writer.write_n(&self.hue_vector_field3, 8)?; writer.write_n(&self.hue_vector_field4, 8)?; writer.write_n(&self.hue_vector_field5, 8)?; } Ok(()) } pub fn validate(&self) -> Result<()> { ensure!(self.trim_slope <= MAX_12_BIT_VALUE); ensure!(self.trim_offset <= MAX_12_BIT_VALUE); ensure!(self.trim_power <= MAX_12_BIT_VALUE); ensure!(self.trim_chroma_weight <= MAX_12_BIT_VALUE); ensure!(self.trim_saturation_gain <= MAX_12_BIT_VALUE); ensure!(self.ms_weight <= MAX_12_BIT_VALUE); ensure!(self.target_mid_contrast <= MAX_12_BIT_VALUE); ensure!(self.clip_trim <= MAX_12_BIT_VALUE); Ok(()) } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel8 { fn level(&self) -> u8 { 8 } fn bytes_size(&self) -> u64 { self.length } fn required_bits(&self) -> u64 { match self.length { 25 => 200, 19 => 152, 13 => 104, 12 => 92, 10 => 80, _ => unreachable!(), } } fn sort_key(&self) -> (u8, u16) { (self.level(), self.target_display_index as u16) } } impl Default for ExtMetadataBlockLevel8 { /// The default trim for a 100 nits target display fn default() -> Self { Self { length: 10, target_display_index: 1, trim_slope: 2048, trim_offset: 2048, trim_power: 2048, trim_chroma_weight: 2048, trim_saturation_gain: 2048, ms_weight: 2048, target_mid_contrast: 2048, clip_trim: 2048, saturation_vector_field0: 128, saturation_vector_field1: 128, saturation_vector_field2: 128, saturation_vector_field3: 128, saturation_vector_field4: 128, saturation_vector_field5: 128, hue_vector_field0: 128, hue_vector_field1: 128, hue_vector_field2: 128, hue_vector_field3: 128, hue_vector_field4: 128, hue_vector_field5: 128, } } } #[cfg(feature = "serde")] impl Serialize for ExtMetadataBlockLevel8 { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let name = "ExtMetadataBlockLevel8"; let fields_count = match self.length { 25 => 22, 19 => 16, 13 => 10, 12 => 9, 10 => 8, _ => unreachable!(), }; let mut state = serializer.serialize_struct(name, fields_count)?; state.serialize_field("length", &self.length)?; state.serialize_field("target_display_index", &self.target_display_index)?; state.serialize_field("trim_slope", &self.trim_slope)?; state.serialize_field("trim_offset", &self.trim_offset)?; state.serialize_field("trim_power", &self.trim_power)?; state.serialize_field("trim_chroma_weight", &self.trim_chroma_weight)?; state.serialize_field("trim_saturation_gain", &self.trim_saturation_gain)?; state.serialize_field("ms_weight", &self.ms_weight)?; if self.length > 10 { state.serialize_field("target_mid_contrast", &self.target_mid_contrast)?; } if self.length > 12 { state.serialize_field("clip_trim", &self.clip_trim)?; } if self.length > 13 { state.serialize_field("saturation_vector_field0", &self.saturation_vector_field0)?; state.serialize_field("saturation_vector_field1", &self.saturation_vector_field1)?; state.serialize_field("saturation_vector_field2", &self.saturation_vector_field2)?; state.serialize_field("saturation_vector_field3", &self.saturation_vector_field3)?; state.serialize_field("saturation_vector_field4", &self.saturation_vector_field4)?; state.serialize_field("saturation_vector_field5", &self.saturation_vector_field5)?; } if self.length > 19 { state.serialize_field("hue_vector_field0", &self.hue_vector_field0)?; state.serialize_field("hue_vector_field1", &self.hue_vector_field1)?; state.serialize_field("hue_vector_field2", &self.hue_vector_field2)?; state.serialize_field("hue_vector_field3", &self.hue_vector_field3)?; state.serialize_field("hue_vector_field4", &self.hue_vector_field4)?; state.serialize_field("hue_vector_field5", &self.hue_vector_field5)?; } state.end() } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/level9.rs000064400000000000000000000163621046102023000226340ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; use crate::rpu::extension_metadata::MasteringDisplayPrimaries; use super::{ColorPrimaries, ExtMetadataBlock, ExtMetadataBlockInfo}; pub const PREDEFINED_REALDEVICE_PRIMARIES: &[[f64; 8]] = &[ [0.693, 0.304, 0.208, 0.761, 0.1467, 0.0527, 0.3127, 0.329], [0.6867, 0.3085, 0.231, 0.69, 0.1489, 0.0638, 0.3127, 0.329], [0.6781, 0.3189, 0.2365, 0.7048, 0.141, 0.0489, 0.3127, 0.329], [0.68, 0.32, 0.265, 0.69, 0.15, 0.06, 0.3127, 0.329], [0.7042, 0.294, 0.2271, 0.725, 0.1416, 0.0516, 0.3127, 0.329], [0.6745, 0.310, 0.2212, 0.7109, 0.152, 0.0619, 0.3127, 0.329], [ 0.6805, 0.3191, 0.2522, 0.6702, 0.1397, 0.0554, 0.3127, 0.329, ], [ 0.6838, 0.3085, 0.2709, 0.6378, 0.1478, 0.0589, 0.3127, 0.329, ], [ 0.6753, 0.3193, 0.2636, 0.6835, 0.1521, 0.0627, 0.3127, 0.329, ], [ 0.6981, 0.2898, 0.1814, 0.7189, 0.1517, 0.0567, 0.3127, 0.329, ], ]; /// Source/mastering display color primaries /// /// This block can have varying byte lengths: 1 or 17 /// Depending on the length, the fields parsed default to zero and may not be set. /// Up to (including): /// - 1: source_primary_index /// - 17: source_primary_{red,green,blue,white}_{x,y} #[repr(C)] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ExtMetadataBlockLevel9 { pub length: u64, pub source_primary_index: u8, pub source_primary_red_x: u16, pub source_primary_red_y: u16, pub source_primary_green_x: u16, pub source_primary_green_y: u16, pub source_primary_blue_x: u16, pub source_primary_blue_y: u16, pub source_primary_white_x: u16, pub source_primary_white_y: u16, } impl ExtMetadataBlockLevel9 { pub(crate) fn parse(reader: &mut BsIoSliceReader, length: u64) -> Result { let mut block = Self { length, source_primary_index: reader.get_n(8)?, ..Default::default() }; if length > 1 { block.source_primary_red_x = reader.get_n(16)?; block.source_primary_red_y = reader.get_n(16)?; block.source_primary_green_x = reader.get_n(16)?; block.source_primary_green_y = reader.get_n(16)?; block.source_primary_blue_x = reader.get_n(16)?; block.source_primary_blue_y = reader.get_n(16)?; block.source_primary_white_x = reader.get_n(16)?; block.source_primary_white_y = reader.get_n(16)?; } Ok(ExtMetadataBlock::Level9(block)) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { self.validate()?; writer.write_n(&self.source_primary_index, 8)?; if self.length > 1 { writer.write_n(&self.source_primary_red_x, 16)?; writer.write_n(&self.source_primary_red_y, 16)?; writer.write_n(&self.source_primary_green_x, 16)?; writer.write_n(&self.source_primary_green_y, 16)?; writer.write_n(&self.source_primary_blue_x, 16)?; writer.write_n(&self.source_primary_blue_y, 16)?; writer.write_n(&self.source_primary_white_x, 16)?; writer.write_n(&self.source_primary_white_y, 16)?; } Ok(()) } pub fn validate(&self) -> Result<()> { if self.length > 1 { // Custom primaries required ensure!(self.source_primary_index == 255); ensure!(self.source_primary_red_x > 0); ensure!(self.source_primary_red_y > 0); ensure!(self.source_primary_green_x > 0); ensure!(self.source_primary_green_y > 0); ensure!(self.source_primary_blue_x > 0); ensure!(self.source_primary_blue_y > 0); ensure!(self.source_primary_white_x > 0); ensure!(self.source_primary_white_y > 0); } else { // Should be a preset primary between 0-8? // But not custom primaries ensure!(self.source_primary_index != 255); } Ok(()) } pub fn set_from_primaries(&mut self, primaries: &ColorPrimaries) { self.source_primary_red_x = primaries.red_x; self.source_primary_red_y = primaries.red_y; self.source_primary_green_x = primaries.green_x; self.source_primary_green_y = primaries.green_y; self.source_primary_blue_x = primaries.blue_x; self.source_primary_blue_y = primaries.blue_y; self.source_primary_white_x = primaries.white_x; self.source_primary_white_y = primaries.white_y; } pub fn default_dci_p3() -> ExtMetadataBlockLevel9 { Self { length: 1, source_primary_index: MasteringDisplayPrimaries::DCIP3D65 as u8, ..Default::default() } } } impl ExtMetadataBlockInfo for ExtMetadataBlockLevel9 { fn level(&self) -> u8 { 9 } fn bytes_size(&self) -> u64 { self.length } fn required_bits(&self) -> u64 { match self.length { 1 => 8, 17 => 136, _ => unreachable!(), } } fn sort_key(&self) -> (u8, u16) { (self.level(), self.source_primary_index as u16) } } impl Default for ExtMetadataBlockLevel9 { /// DCI-P3 D65 preset fn default() -> Self { Self { length: 1, source_primary_index: MasteringDisplayPrimaries::DCIP3D65 as u8, source_primary_red_x: 0, source_primary_red_y: 0, source_primary_green_x: 0, source_primary_green_y: 0, source_primary_blue_x: 0, source_primary_blue_y: 0, source_primary_white_x: 0, source_primary_white_y: 0, } } } #[cfg(feature = "serde")] impl Serialize for ExtMetadataBlockLevel9 { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let name = "ExtMetadataBlockLevel9"; let fields_count = match self.length { 1 => 2, 17 => 10, _ => unreachable!(), }; let mut state = serializer.serialize_struct(name, fields_count)?; state.serialize_field("length", &self.length)?; state.serialize_field("source_primary_index", &self.source_primary_index)?; if self.length > 1 { state.serialize_field("source_primary_red_x", &self.source_primary_red_x)?; state.serialize_field("source_primary_red_y", &self.source_primary_red_y)?; state.serialize_field("source_primary_green_x", &self.source_primary_green_x)?; state.serialize_field("source_primary_green_y", &self.source_primary_green_y)?; state.serialize_field("source_primary_blue_x", &self.source_primary_blue_x)?; state.serialize_field("source_primary_blue_y", &self.source_primary_blue_y)?; state.serialize_field("source_primary_white_x", &self.source_primary_white_x)?; state.serialize_field("source_primary_white_y", &self.source_primary_white_y)?; } state.end() } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/mod.rs000064400000000000000000000200361046102023000222040ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; pub mod level1; pub mod level10; pub mod level11; pub mod level2; pub mod level254; pub mod level255; pub mod level3; pub mod level4; pub mod level5; pub mod level6; pub mod level8; pub mod level9; pub mod reserved; pub use level1::ExtMetadataBlockLevel1; pub use level10::ExtMetadataBlockLevel10; pub use level11::ExtMetadataBlockLevel11; pub use level2::ExtMetadataBlockLevel2; pub use level254::ExtMetadataBlockLevel254; pub use level255::ExtMetadataBlockLevel255; pub use level3::ExtMetadataBlockLevel3; pub use level4::ExtMetadataBlockLevel4; pub use level5::ExtMetadataBlockLevel5; pub use level6::ExtMetadataBlockLevel6; pub use level8::ExtMetadataBlockLevel8; pub use level9::ExtMetadataBlockLevel9; pub use reserved::ReservedExtMetadataBlock; use super::{ColorPrimaries, WithExtMetadataBlocks}; /// cbindgen:ignore pub const MAX_12_BIT_VALUE: u16 = 4095; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum ExtMetadataBlock { Level1(ExtMetadataBlockLevel1), Level2(ExtMetadataBlockLevel2), Level3(ExtMetadataBlockLevel3), Level4(ExtMetadataBlockLevel4), Level5(ExtMetadataBlockLevel5), Level6(ExtMetadataBlockLevel6), Level8(ExtMetadataBlockLevel8), Level9(ExtMetadataBlockLevel9), Level10(ExtMetadataBlockLevel10), Level11(ExtMetadataBlockLevel11), Level254(ExtMetadataBlockLevel254), Level255(ExtMetadataBlockLevel255), Reserved(ReservedExtMetadataBlock), } pub trait ExtMetadataBlockInfo { fn level(&self) -> u8; fn bytes_size(&self) -> u64; fn required_bits(&self) -> u64; fn bits_size(&self) -> u64 { self.bytes_size() * 8 } fn sort_key(&self) -> (u8, u16) { (self.level(), 0) } } impl ExtMetadataBlock { pub fn length_bytes(&self) -> u64 { match self { ExtMetadataBlock::Level1(b) => b.bytes_size(), ExtMetadataBlock::Level2(b) => b.bytes_size(), ExtMetadataBlock::Level3(b) => b.bytes_size(), ExtMetadataBlock::Level4(b) => b.bytes_size(), ExtMetadataBlock::Level5(b) => b.bytes_size(), ExtMetadataBlock::Level6(b) => b.bytes_size(), ExtMetadataBlock::Level8(b) => b.bytes_size(), ExtMetadataBlock::Level9(b) => b.bytes_size(), ExtMetadataBlock::Level10(b) => b.bytes_size(), ExtMetadataBlock::Level11(b) => b.bytes_size(), ExtMetadataBlock::Level254(b) => b.bytes_size(), ExtMetadataBlock::Level255(b) => b.bytes_size(), ExtMetadataBlock::Reserved(b) => b.bytes_size(), } } pub fn length_bits(&self) -> u64 { match self { ExtMetadataBlock::Level1(b) => b.bits_size(), ExtMetadataBlock::Level2(b) => b.bits_size(), ExtMetadataBlock::Level3(b) => b.bits_size(), ExtMetadataBlock::Level4(b) => b.bits_size(), ExtMetadataBlock::Level5(b) => b.bits_size(), ExtMetadataBlock::Level6(b) => b.bits_size(), ExtMetadataBlock::Level8(b) => b.bits_size(), ExtMetadataBlock::Level9(b) => b.bits_size(), ExtMetadataBlock::Level10(b) => b.bits_size(), ExtMetadataBlock::Level11(b) => b.bits_size(), ExtMetadataBlock::Level254(b) => b.bits_size(), ExtMetadataBlock::Level255(b) => b.bits_size(), ExtMetadataBlock::Reserved(b) => b.bits_size(), } } pub fn required_bits(&self) -> u64 { match self { ExtMetadataBlock::Level1(b) => b.required_bits(), ExtMetadataBlock::Level2(b) => b.required_bits(), ExtMetadataBlock::Level3(b) => b.required_bits(), ExtMetadataBlock::Level4(b) => b.required_bits(), ExtMetadataBlock::Level5(b) => b.required_bits(), ExtMetadataBlock::Level6(b) => b.required_bits(), ExtMetadataBlock::Level8(b) => b.required_bits(), ExtMetadataBlock::Level9(b) => b.required_bits(), ExtMetadataBlock::Level10(b) => b.required_bits(), ExtMetadataBlock::Level11(b) => b.required_bits(), ExtMetadataBlock::Level254(b) => b.required_bits(), ExtMetadataBlock::Level255(b) => b.required_bits(), ExtMetadataBlock::Reserved(b) => b.required_bits(), } } pub fn level(&self) -> u8 { match self { ExtMetadataBlock::Level1(b) => b.level(), ExtMetadataBlock::Level2(b) => b.level(), ExtMetadataBlock::Level3(b) => b.level(), ExtMetadataBlock::Level4(b) => b.level(), ExtMetadataBlock::Level5(b) => b.level(), ExtMetadataBlock::Level6(b) => b.level(), ExtMetadataBlock::Level8(b) => b.level(), ExtMetadataBlock::Level9(b) => b.level(), ExtMetadataBlock::Level10(b) => b.level(), ExtMetadataBlock::Level11(b) => b.level(), ExtMetadataBlock::Level254(b) => b.level(), ExtMetadataBlock::Level255(b) => b.level(), ExtMetadataBlock::Reserved(b) => b.level(), } } pub fn sort_key(&self) -> (u8, u16) { match self { ExtMetadataBlock::Level1(b) => b.sort_key(), ExtMetadataBlock::Level2(b) => b.sort_key(), ExtMetadataBlock::Level3(b) => b.sort_key(), ExtMetadataBlock::Level4(b) => b.sort_key(), ExtMetadataBlock::Level5(b) => b.sort_key(), ExtMetadataBlock::Level6(b) => b.sort_key(), ExtMetadataBlock::Level8(b) => b.sort_key(), ExtMetadataBlock::Level9(b) => b.sort_key(), ExtMetadataBlock::Level10(b) => b.sort_key(), ExtMetadataBlock::Level11(b) => b.sort_key(), ExtMetadataBlock::Level254(b) => b.sort_key(), ExtMetadataBlock::Level255(b) => b.sort_key(), ExtMetadataBlock::Reserved(b) => b.sort_key(), } } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { match self { ExtMetadataBlock::Level1(b) => b.write(writer), ExtMetadataBlock::Level2(b) => b.write(writer), ExtMetadataBlock::Level3(b) => b.write(writer), ExtMetadataBlock::Level4(b) => b.write(writer), ExtMetadataBlock::Level5(b) => b.write(writer), ExtMetadataBlock::Level6(b) => b.write(writer), ExtMetadataBlock::Level8(b) => b.write(writer), ExtMetadataBlock::Level9(b) => b.write(writer), ExtMetadataBlock::Level10(b) => b.write(writer), ExtMetadataBlock::Level11(b) => b.write(writer), ExtMetadataBlock::Level254(b) => b.write(writer), ExtMetadataBlock::Level255(b) => b.write(writer), ExtMetadataBlock::Reserved(b) => b.write(writer), } } pub fn validate_correct_dm_data(&self) -> Result<()> { let level = self.level(); ensure!( T::ALLOWED_BLOCK_LEVELS.contains(&level), "Metadata block level {} is invalid for {}", &level, T::VERSION ); Ok(()) } pub(crate) fn validate_and_read_remaining( &self, reader: &mut BsIoSliceReader, block_length: u64, ) -> Result<()> { let level = self.level(); ensure!( block_length == self.length_bytes(), format!( "{}: Invalid metadata block. Block level {} should have length {}", T::VERSION, level, self.length_bytes() ) ); self.validate_correct_dm_data::()?; let ext_block_use_bits = self.length_bits() - self.required_bits(); for _ in 0..ext_block_use_bits { ensure!( !reader.get()?, format!("{}: ext_dm_alignment_zero_bit != 0", T::VERSION) ); } Ok(()) } } dolby_vision-3.3.1/src/rpu/extension_metadata/blocks/reserved.rs000064400000000000000000000032131046102023000232420ustar 00000000000000use anyhow::{bail, Result}; use bitvec::{order::Msb0, prelude::BitVec}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ReservedExtMetadataBlock { pub ext_block_length: u64, pub ext_block_level: u8, #[cfg_attr( feature = "serde", serde(serialize_with = "crate::utils::bitvec_ser_bits", skip_deserializing) )] pub data: BitVec, } impl ReservedExtMetadataBlock { pub(crate) fn parse( ext_block_length: u64, ext_block_level: u8, reader: &mut BsIoSliceReader, ) -> Result { let bits = 8 * ext_block_length; let mut data = BitVec::new(); for _ in 0..bits { data.push(reader.get()?); } Ok(ExtMetadataBlock::Reserved(Self { ext_block_length, ext_block_level, data, })) } pub fn write(&self, _writer: &mut BitstreamIoWriter) -> Result<()> { bail!("Cannot write reserved block"); // self.data.iter().for_each(|b| writer.write(*b))?; } } impl ExtMetadataBlockInfo for ReservedExtMetadataBlock { // TODO: Level 255 is actually definded for DM debugging purposes, we may add it. fn level(&self) -> u8 { 0 } fn bytes_size(&self) -> u64 { self.ext_block_length } fn required_bits(&self) -> u64 { self.data.len() as u64 } } dolby_vision-3.3.1/src/rpu/extension_metadata/cmv29.rs000064400000000000000000000126221046102023000211120ustar 00000000000000use anyhow::{bail, ensure, Result}; use bitvec_helpers::bitstream_io_reader::BsIoSliceReader; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::WithExtMetadataBlocks; use crate::rpu::extension_metadata::blocks::*; #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct CmV29DmData { num_ext_blocks: u64, ext_metadata_blocks: Vec, } impl WithExtMetadataBlocks for CmV29DmData { const VERSION: &'static str = "CM v2.9"; const ALLOWED_BLOCK_LEVELS: &'static [u8] = &[1, 2, 4, 5, 6, 255]; fn with_blocks_allocation(num_ext_blocks: u64) -> Self { Self { ext_metadata_blocks: Vec::with_capacity(num_ext_blocks as usize), ..Default::default() } } fn set_num_ext_blocks(&mut self, num_ext_blocks: u64) { self.num_ext_blocks = num_ext_blocks; } fn num_ext_blocks(&self) -> u64 { self.num_ext_blocks } fn blocks_ref(&self) -> &Vec { self.ext_metadata_blocks.as_ref() } fn blocks_mut(&mut self) -> &mut Vec { self.ext_metadata_blocks.as_mut() } fn parse_block(&mut self, reader: &mut BsIoSliceReader) -> Result<()> { let ext_block_length = reader.get_ue()?; let ext_block_level = reader.get_n(8)?; let ext_metadata_block = match ext_block_level { 1 => level1::ExtMetadataBlockLevel1::parse(reader)?, 2 => level2::ExtMetadataBlockLevel2::parse(reader)?, 4 => level4::ExtMetadataBlockLevel4::parse(reader)?, 5 => level5::ExtMetadataBlockLevel5::parse(reader)?, 6 => level6::ExtMetadataBlockLevel6::parse(reader)?, 255 => level255::ExtMetadataBlockLevel255::parse(reader)?, 3 | 8 | 9 | 10 | 11 | 254 => bail!( "Invalid block level {} for {} RPU", ext_block_level, Self::VERSION, ), _ => { ensure!( false, format!("{} - Unknown metadata block found: Level {}, length {}, please open an issue.", Self::VERSION, ext_block_level, ext_block_length) ); reserved::ReservedExtMetadataBlock::parse( ext_block_length, ext_block_level, reader, )? } }; ext_metadata_block.validate_and_read_remaining::(reader, ext_block_length)?; self.ext_metadata_blocks.push(ext_metadata_block); Ok(()) } } impl CmV29DmData { pub fn replace_level2_block(&mut self, block: &ExtMetadataBlockLevel2) { let blocks = self.blocks_mut(); let existing_idx = blocks.iter().position(|b| match b { ExtMetadataBlock::Level2(b) => b.target_max_pq == block.target_max_pq, _ => false, }); // Replace or add level 2 block if let Some(i) = existing_idx { blocks[i] = ExtMetadataBlock::Level2(block.clone()); } else { blocks.push(ExtMetadataBlock::Level2(block.clone())); } self.update_extension_block_info(); } /// Validates different level block counts. /// The specification requires one block of L1, L4, L5, L6 and L255. /// However they are not really required, so YMMV. pub fn validate(&self) -> Result<()> { let blocks = self.blocks_ref(); let invalid_blocks_count = blocks .iter() .filter(|b| !Self::ALLOWED_BLOCK_LEVELS.contains(&b.level())) .count(); let level1_count = blocks.iter().filter(|b| b.level() == 1).count(); let level2_count = blocks.iter().filter(|b| b.level() == 2).count(); let level255_count = blocks.iter().filter(|b| b.level() == 255).count(); let level4_count = blocks.iter().filter(|b| b.level() == 4).count(); let level5_count = blocks.iter().filter(|b| b.level() == 5).count(); let level6_count = blocks.iter().filter(|b| b.level() == 6).count(); ensure!( invalid_blocks_count == 0, format!( "{}: Only allowed blocks level 1, 2, 4, 5, 6, and 255", Self::VERSION ) ); ensure!( level1_count <= 1, format!( "{}: There must be at most one L1 metadata block", Self::VERSION ) ); ensure!( level2_count <= 8, format!( "{}: There must be at most 8 L2 metadata blocks", Self::VERSION ) ); ensure!( level255_count <= 1, format!( "{}: There must be at most one L255 metadata block", Self::VERSION ) ); ensure!( level4_count <= 1, format!( "{}: There must be at most one L4 metadata block", Self::VERSION ) ); ensure!( level5_count <= 1, format!( "{}: There must be at most one L5 metadata block", Self::VERSION ) ); ensure!( level6_count <= 1, format!( "{}: There must be at most one L6 metadata block", Self::VERSION ) ); Ok(()) } } dolby_vision-3.3.1/src/rpu/extension_metadata/cmv40.rs000064400000000000000000000146561046102023000211140ustar 00000000000000use anyhow::{bail, ensure, Result}; use bitvec_helpers::bitstream_io_reader::BsIoSliceReader; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::WithExtMetadataBlocks; use crate::rpu::extension_metadata::blocks::*; #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct CmV40DmData { num_ext_blocks: u64, ext_metadata_blocks: Vec, } impl WithExtMetadataBlocks for CmV40DmData { const VERSION: &'static str = "CM v4.0"; const ALLOWED_BLOCK_LEVELS: &'static [u8] = &[3, 8, 9, 10, 11, 254]; fn with_blocks_allocation(num_ext_blocks: u64) -> Self { Self { ext_metadata_blocks: Vec::with_capacity(num_ext_blocks as usize), ..Default::default() } } fn set_num_ext_blocks(&mut self, num_ext_blocks: u64) { self.num_ext_blocks = num_ext_blocks; } fn num_ext_blocks(&self) -> u64 { self.num_ext_blocks } fn blocks_ref(&self) -> &Vec { self.ext_metadata_blocks.as_ref() } fn blocks_mut(&mut self) -> &mut Vec { self.ext_metadata_blocks.as_mut() } fn parse_block(&mut self, reader: &mut BsIoSliceReader) -> Result<()> { let ext_block_length = reader.get_ue()?; let ext_block_level: u8 = reader.get_n(8)?; let ext_metadata_block = match ext_block_level { 3 => level3::ExtMetadataBlockLevel3::parse(reader)?, 8 => level8::ExtMetadataBlockLevel8::parse(reader, ext_block_length)?, 9 => level9::ExtMetadataBlockLevel9::parse(reader, ext_block_length)?, 10 => level10::ExtMetadataBlockLevel10::parse(reader, ext_block_length)?, 11 => level11::ExtMetadataBlockLevel11::parse(reader)?, 254 => level254::ExtMetadataBlockLevel254::parse(reader)?, 1 | 2 | 4 | 5 | 6 | 255 => bail!( "Invalid block level {} for {} RPU", ext_block_level, Self::VERSION, ), _ => { ensure!( false, format!("{} - Unknown metadata block found: Level {}, length {}, please open an issue.", Self::VERSION, ext_block_level, ext_block_length) ); reserved::ReservedExtMetadataBlock::parse( ext_block_length, ext_block_level, reader, )? } }; ext_metadata_block.validate_and_read_remaining::(reader, ext_block_length)?; self.ext_metadata_blocks.push(ext_metadata_block); Ok(()) } } impl CmV40DmData { pub fn replace_level8_block(&mut self, block: &ExtMetadataBlockLevel8) { let blocks = self.blocks_mut(); let existing_idx = blocks.iter().position(|b| match b { ExtMetadataBlock::Level8(b) => b.target_display_index == block.target_display_index, _ => false, }); // Replace or add level 8 block if let Some(i) = existing_idx { blocks[i] = ExtMetadataBlock::Level8(block.clone()); } else { blocks.push(ExtMetadataBlock::Level8(block.clone())); } self.update_extension_block_info(); } pub fn replace_level10_block(&mut self, block: &ExtMetadataBlockLevel10) { let blocks = self.blocks_mut(); let existing_idx = blocks.iter().position(|b| match b { ExtMetadataBlock::Level10(b) => b.target_display_index == block.target_display_index, _ => false, }); // Replace or add level 10 block if let Some(i) = existing_idx { blocks[i] = ExtMetadataBlock::Level10(block.clone()); } else { blocks.push(ExtMetadataBlock::Level10(block.clone())); } self.update_extension_block_info(); } /// Validates different level block counts. /// The specification requires one block of L254 metadata pub fn validate(&self) -> Result<()> { let blocks = self.blocks_ref(); let invalid_blocks_count = blocks .iter() .filter(|b| !Self::ALLOWED_BLOCK_LEVELS.contains(&b.level())) .count(); let level254_count = blocks.iter().filter(|b| b.level() == 254).count(); let level3_count = blocks.iter().filter(|b| b.level() == 3).count(); let level8_count = blocks.iter().filter(|b| b.level() == 8).count(); let level9_count = blocks.iter().filter(|b| b.level() == 9).count(); let level10_count = blocks.iter().filter(|b| b.level() == 10).count(); let level11_count = blocks.iter().filter(|b| b.level() == 11).count(); ensure!( invalid_blocks_count == 0, format!( "{}: Only allowed blocks level 3, 8, 9, 10, 11 and 254", Self::VERSION ) ); ensure!( level254_count == 1, format!("{}: There must be one L254 metadata block", Self::VERSION) ); ensure!( level3_count <= 1, format!( "{}: There must be at most one L3 metadata block", Self::VERSION ) ); ensure!( level8_count <= 5, format!( "{}: There must be at most 5 L8 metadata blocks", Self::VERSION ) ); ensure!( level9_count <= 1, format!( "{}: There must be at most one L9 metadata block", Self::VERSION ) ); ensure!( level10_count <= 4, format!( "{}: There must be at most 4 L10 metadata blocks", Self::VERSION ) ); ensure!( level11_count <= 1, format!( "{}: There must be at most one L11 metadata block", Self::VERSION ) ); Ok(()) } pub fn new_with_l254_402() -> Self { Self { num_ext_blocks: 1, ext_metadata_blocks: vec![ExtMetadataBlock::Level254( ExtMetadataBlockLevel254::cmv402_default(), )], } } pub fn new_with_custom_l254(level254: &ExtMetadataBlockLevel254) -> Self { Self { num_ext_blocks: 1, ext_metadata_blocks: vec![ExtMetadataBlock::Level254(level254.clone())], } } } dolby_vision-3.3.1/src/rpu/extension_metadata/mod.rs000064400000000000000000000074301046102023000207320ustar 00000000000000use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; pub mod blocks; pub mod cmv29; pub mod cmv40; pub mod primaries; pub use primaries::*; pub use cmv29::CmV29DmData; pub use cmv40::CmV40DmData; use blocks::ExtMetadataBlock; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] pub enum DmData { V29(CmV29DmData), V40(CmV40DmData), } pub trait ExtMetadata { fn parse(&mut self, reader: &mut BsIoSliceReader) -> Result<()>; fn write(&self, writer: &mut BitstreamIoWriter); } pub trait WithExtMetadataBlocks { const VERSION: &'static str; const ALLOWED_BLOCK_LEVELS: &'static [u8]; fn with_blocks_allocation(num_ext_blocks: u64) -> Self; fn set_num_ext_blocks(&mut self, num_ext_blocks: u64); fn num_ext_blocks(&self) -> u64; fn parse_block(&mut self, reader: &mut BsIoSliceReader) -> Result<()>; fn blocks_ref(&self) -> &Vec; fn blocks_mut(&mut self) -> &mut Vec; fn sort_blocks(&mut self) { let blocks = self.blocks_mut(); blocks.sort_by_key(|ext| ext.sort_key()); } fn update_extension_block_info(&mut self) { self.set_num_ext_blocks(self.blocks_ref().len() as u64); self.sort_blocks(); } fn add_block(&mut self, meta: ExtMetadataBlock) -> Result<()> { let level = meta.level(); ensure!( Self::ALLOWED_BLOCK_LEVELS.contains(&level), "Metadata block level {} is invalid for {}", &level, Self::VERSION ); let blocks = self.blocks_mut(); blocks.push(meta); self.update_extension_block_info(); Ok(()) } fn remove_level(&mut self, level: u8) { let blocks = self.blocks_mut(); blocks.retain(|b| b.level() != level); self.update_extension_block_info(); } fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { let num_ext_blocks = self.num_ext_blocks(); writer.write_ue(&num_ext_blocks)?; // dm_alignment_zero_bit writer.byte_align()?; let ext_metadata_blocks = self.blocks_ref(); for ext_metadata_block in ext_metadata_blocks { let remaining_bits = ext_metadata_block.length_bits() - ext_metadata_block.required_bits(); writer.write_ue(&ext_metadata_block.length_bytes())?; writer.write_n(&ext_metadata_block.level(), 8)?; ext_metadata_block.write(writer)?; // ext_dm_alignment_zero_bit for _ in 0..remaining_bits { writer.write(false)?; } } Ok(()) } } impl DmData { pub(crate) fn parse( reader: &mut BsIoSliceReader, ) -> Result> { let num_ext_blocks = reader.get_ue()?; let mut meta = T::with_blocks_allocation(num_ext_blocks); meta.set_num_ext_blocks(num_ext_blocks); while !reader.is_aligned() { ensure!( !reader.get()?, format!("{}: dm_alignment_zero_bit != 0", T::VERSION) ); } for _ in 0..num_ext_blocks { meta.parse_block(reader)?; } Ok(Some(meta)) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { match self { DmData::V29(m) => m.write(writer), DmData::V40(m) => m.write(writer), } } pub fn validate(&self) -> Result<()> { match self { DmData::V29(m) => m.validate(), DmData::V40(m) => m.validate(), } } } dolby_vision-3.3.1/src/rpu/extension_metadata/primaries.rs000064400000000000000000000056701046102023000221520ustar 00000000000000use std::convert::TryInto; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; pub const PREDEFINED_COLORSPACE_PRIMARIES: &[[f64; 8]] = &[ [0.68, 0.32, 0.265, 0.69, 0.15, 0.06, 0.3127, 0.329], // 0, DCI-P3 D65 [0.64, 0.33, 0.30, 0.60, 0.15, 0.06, 0.3127, 0.329], // 1, BT.709 [0.708, 0.292, 0.170, 0.797, 0.131, 0.046, 0.3127, 0.329], // 2, BT.2020 [0.63, 0.34, 0.31, 0.595, 0.155, 0.07, 0.3127, 0.329], // 3, BT.601 NTSC / SMPTE-C [0.64, 0.33, 0.29, 0.60, 0.15, 0.06, 0.3127, 0.329], // 4, BT.601 PAL / BT.470 BG [0.68, 0.32, 0.265, 0.69, 0.15, 0.06, 0.314, 0.351], // 5, DCI-P3 [0.7347, 0.2653, 0.0, 1.0, 0.0001, -0.077, 0.32168, 0.33767], // 6, ACES [0.73, 0.28, 0.14, 0.855, 0.10, -0.05, 0.3127, 0.329], // 7, S-Gamut [0.766, 0.275, 0.225, 0.80, 0.089, -0.087, 0.3127, 0.329], // 8, S-Gamut-3.Cine ]; #[derive(Debug, Default, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ColorPrimaries { pub red_x: u16, pub red_y: u16, pub green_x: u16, pub green_y: u16, pub blue_x: u16, pub blue_y: u16, pub white_x: u16, pub white_y: u16, } #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum MasteringDisplayPrimaries { #[cfg_attr(feature = "serde", serde(alias = "DCI-P3 D65"))] DCIP3D65 = 0, #[cfg_attr(feature = "serde", serde(alias = "BT.709"))] BT709, #[cfg_attr(feature = "serde", serde(alias = "BT.2020"))] BT2020, #[cfg_attr(feature = "serde", serde(alias = "SMPTE-C"))] SMPTEC, #[cfg_attr(feature = "serde", serde(alias = "BT.601"))] BT601, #[cfg_attr(feature = "serde", serde(alias = "DCI-P3"))] DCIP3, ACES, #[cfg_attr(feature = "serde", serde(alias = "S-Gamut"))] SGamut, #[cfg_attr(feature = "serde", serde(alias = "S-Gamut-3.Cine"))] SGamut3Cine, } impl ColorPrimaries { pub fn from_array_int(primaries: &[u16; 8]) -> ColorPrimaries { Self { red_x: primaries[0], red_y: primaries[1], green_x: primaries[2], green_y: primaries[3], blue_x: primaries[4], blue_y: primaries[5], white_x: primaries[6], white_y: primaries[7], } } pub fn from_array_float(primaries: &[f64; 8]) -> ColorPrimaries { // Float to integer primaries let primaries_int = f64_to_integer_primaries(primaries); Self::from_array_int(&primaries_int) } pub fn from_enum(primary: MasteringDisplayPrimaries) -> ColorPrimaries { Self::from_array_float(&PREDEFINED_COLORSPACE_PRIMARIES[primary as usize]) } } /// Assumes a list of size 8, otherwise panics pub fn f64_to_integer_primaries(primaries: &[f64]) -> [u16; 8] { const SCALE: f64 = 1.0 / 32767.0; primaries .iter() .map(|v| (v / SCALE).round() as u16) .collect::>() .try_into() .unwrap() } dolby_vision-3.3.1/src/rpu/generate.rs000064400000000000000000000561371046102023000161010ustar 00000000000000use std::{ fs::File, io::{BufWriter, Write}, path::Path, }; use anyhow::{ensure, Result}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::rpu::dovi_rpu::DoviRpu; use super::{extension_metadata::blocks, vdr_dm_data::CmVersion}; use blocks::*; const OUT_NAL_HEADER: &[u8] = &[0, 0, 0, 1]; /// Generic generation config struct. #[derive(Debug)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct GenerateConfig { /// Content mapping version /// Optional, defaults to v4.0 #[cfg_attr(feature = "serde", serde(default = "CmVersion::v40"))] pub cm_version: CmVersion, /// Profile to generate /// - 5: IPT base layer with no reshaping /// - 8.1: HDR10 base layer /// - 8.4: HLG base layer with static reshaping (iPhone 13 MMR) #[cfg_attr(feature = "serde", serde(default))] pub profile: GenerateProfile, /// Set scene cut flag for every frame. #[cfg_attr(feature = "serde", serde(default))] pub long_play_mode: bool, /// Number of RPU frames to generate. /// Required only when no shots are specified. #[cfg_attr(feature = "serde", serde(default))] pub length: usize, /// Mastering display min luminance, as 12 bit PQ code. #[cfg_attr(feature = "serde", serde(default))] pub source_min_pq: Option, /// Mastering display max luminance, as 12 bit PQ code. #[cfg_attr(feature = "serde", serde(default))] pub source_max_pq: Option, /// CM version to override the minimum L1 `avg_pq` #[cfg_attr(feature = "serde", serde(default))] pub l1_avg_pq_cm_version: Option, /// Active area offsets. /// Defaults to zero offsets, should be present in RPU #[cfg_attr(feature = "serde", serde(default))] pub level5: ExtMetadataBlockLevel5, /// ST2086/HDR10 fallback metadata. /// Required for deserialization. /// Defaults to 1000,0.0001 pub level6: Option, /// In the case of XML generation, the L254 metadata can vary. /// Not allowed to be deserialized because it's handled by the lib. #[cfg_attr(feature = "serde", serde(skip))] pub level254: Option, /// List of metadata blocks to use for every RPU generated. /// /// Per-shot or per-frame metadata replaces the default /// metadata blocks if there are conflicts. #[cfg_attr(feature = "serde", serde(default))] pub default_metadata_blocks: Vec, /// List of shots to generate. #[cfg_attr(feature = "serde", serde(default))] pub shots: Vec, } /// Supported profiles for generating RPU metadata #[derive(Debug, Default)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum GenerateProfile { #[cfg_attr(feature = "serde", serde(alias = "5"))] Profile5, #[default] #[cfg_attr(feature = "serde", serde(alias = "8.1"))] Profile81, #[cfg_attr(feature = "serde", serde(alias = "8.4"))] Profile84, } /// Struct defining a video shot. /// A shot is a group of frames that share the same metadata. #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct VideoShot { /// Optional (unused) ID of the shot. /// Only XML generation provides this. #[cfg_attr(feature = "serde", serde(default))] pub id: String, /// Frame start offset of the shot. /// Used as a sorting key for the shots. pub start: usize, /// Number of frames contained in the shot. pub duration: usize, /// List of metadata blocks. #[cfg_attr(feature = "serde", serde(default))] pub metadata_blocks: Vec, /// List of per-frame metadata edits. #[cfg_attr(feature = "serde", serde(default))] pub frame_edits: Vec, } /// Struct to represent a list of metadata edits for a specific frame. #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ShotFrameEdit { /// Frame offset within the parent shot. pub edit_offset: usize, /// List of metadata blocks to use. pub metadata_blocks: Vec, } impl GenerateConfig { pub fn generate_rpu_list(&self) -> Result> { let rpu = match self.profile { GenerateProfile::Profile5 => DoviRpu::profile5_config(self)?, GenerateProfile::Profile81 => DoviRpu::profile81_config(self)?, GenerateProfile::Profile84 => DoviRpu::profile84_config(self)?, }; let mut list = Vec::with_capacity(self.length); let shots_length: usize = self.shots.iter().map(|s| s.duration).sum(); ensure!( self.length == shots_length, format!( "Config length is not the same as shots total duration. Config: {}, Shots: {}", self.length, shots_length ) ); for shot in &self.shots { let end = shot.duration; for i in 0..end { let mut frame_rpu = rpu.clone(); if let Some(vdr_dm_data) = frame_rpu.vdr_dm_data.as_mut() { if i == 0 || self.long_play_mode { vdr_dm_data.set_scene_cut(true); } // Set metadata for this shot for block in &shot.metadata_blocks { vdr_dm_data.replace_metadata_block(block.clone())?; } let frame_edit = shot.frame_edits.iter().find(|e| e.edit_offset == i); // Set different metadata for this frame if let Some(edit) = frame_edit { for block in &edit.metadata_blocks { vdr_dm_data.replace_metadata_block(block.clone())?; } } } list.push(frame_rpu) } } Ok(list) } pub fn encode_option_rpus(rpus: &mut [Option]) -> Vec> { let encoded_rpus = rpus .iter_mut() .filter_map(|e| e.as_mut()) .map(|e| e.write_hevc_unspec62_nalu()) .filter_map(Result::ok) .collect(); encoded_rpus } pub fn encode_rpus(rpus: &mut [DoviRpu]) -> Vec> { let encoded_rpus = rpus .iter_mut() .map(|e| e.write_hevc_unspec62_nalu()) .filter_map(Result::ok) .collect(); encoded_rpus } pub fn write_rpus>(&self, path: P) -> Result<()> { let mut writer = BufWriter::with_capacity(100_000, File::create(path).expect("Can't create file")); let rpus = self.generate_rpu_list()?; for rpu in &rpus { let encoded_rpu = rpu.write_hevc_unspec62_nalu()?; writer.write_all(OUT_NAL_HEADER)?; // Remove 0x7C01 writer.write_all(&encoded_rpu[2..])?; } writer.flush()?; Ok(()) } pub fn fixup_l1(&mut self) { let clamp_l1 = |block: &mut ExtMetadataBlock| { if let ExtMetadataBlock::Level1(l1) = block { l1.clamp_values_cm_version(self.l1_avg_pq_cm_version.unwrap_or(self.cm_version)); } }; self.default_metadata_blocks.iter_mut().for_each(clamp_l1); self.shots.iter_mut().for_each(|shot| { shot.metadata_blocks.iter_mut().for_each(clamp_l1); shot.frame_edits .iter_mut() .for_each(|e| e.metadata_blocks.iter_mut().for_each(clamp_l1)); }); } } impl Default for GenerateConfig { fn default() -> Self { Self { cm_version: CmVersion::V40, profile: Default::default(), length: Default::default(), long_play_mode: Default::default(), source_min_pq: Default::default(), source_max_pq: Default::default(), l1_avg_pq_cm_version: Default::default(), default_metadata_blocks: Default::default(), level5: Default::default(), level6: Some(ExtMetadataBlockLevel6 { max_display_mastering_luminance: 1000, min_display_mastering_luminance: 1, max_content_light_level: 0, max_frame_average_light_level: 0, }), level254: Default::default(), shots: Default::default(), } } } impl VideoShot { pub fn copy_metadata_from_shot( &mut self, other_shot: &VideoShot, level_block_list: Option<&[u8]>, ) { // Add blocks to shot metadata let new_shot_blocks: Vec = if let Some(block_list) = level_block_list { other_shot .metadata_blocks .iter() .filter(|b| !block_list.contains(&b.level())) .cloned() .collect() } else { other_shot.metadata_blocks.clone() }; self.metadata_blocks.extend(new_shot_blocks); // Add blocks to existing frame edits for the same offsets for frame_edit in &mut self.frame_edits { let new_frame_edit = other_shot .frame_edits .iter() .find(|e| e.edit_offset == frame_edit.edit_offset); if let Some(other_edit) = new_frame_edit { let new_edit_blocks: Vec = if let Some(block_list) = level_block_list { other_edit .metadata_blocks .iter() .filter(|b| !block_list.contains(&b.level())) .cloned() .collect() } else { other_edit.metadata_blocks.clone() }; frame_edit.metadata_blocks.extend(new_edit_blocks); } } // Add extra frame edits but don't replace let existing_edit_offsets: Vec = self.frame_edits.iter().map(|e| e.edit_offset).collect(); // Filter out unwanted blocks and add new edits let added_frame_edits = other_shot .frame_edits .iter() .filter(|e| !existing_edit_offsets.contains(&e.edit_offset)) .cloned() .map(|mut frame_edit| { if let Some(block_list) = level_block_list { frame_edit .metadata_blocks .retain(|b| !block_list.contains(&b.level())); } frame_edit }); self.frame_edits.extend(added_frame_edits); } } #[cfg(all(test, feature = "xml"))] mod tests { use anyhow::Result; use std::path::PathBuf; use crate::{ rpu::{extension_metadata::blocks::ExtMetadataBlock, vdr_dm_data::CmVersion}, xml::{CmXmlParser, XmlParserOpts}, }; #[test] fn config_with_frame_edits() -> Result<()> { let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let assets_path = lib_path.parent().unwrap(); let opts = XmlParserOpts { canvas_width: Some(3840), canvas_height: Some(2160), }; let parser = CmXmlParser::parse_file(assets_path.join("assets/tests/cmv4_0_2.xml"), opts)?; let config = parser.config; assert_eq!(config.cm_version, CmVersion::V40); let rpus = config.generate_rpu_list()?; assert_eq!(rpus.len(), 259); // SHOT 1 let shot1_rpu = &rpus[0]; let shot1_vdr_dm_data = &shot1_rpu.vdr_dm_data.as_ref().unwrap(); assert_eq!(shot1_vdr_dm_data.scene_refresh_flag, 1); // L1, L5, L6 in CMv2.9 assert_eq!(shot1_vdr_dm_data.metadata_blocks(1).unwrap().len(), 3); // L3, L9, L11, L254 in CMv4.0 assert_eq!(shot1_vdr_dm_data.metadata_blocks(3).unwrap().len(), 4); if let ExtMetadataBlock::Level1(level1) = shot1_vdr_dm_data.get_block(1).unwrap() { assert_eq!(level1.min_pq, 0); assert_eq!(level1.max_pq, 2828); assert_eq!(level1.avg_pq, 1229); } if let ExtMetadataBlock::Level3(level3) = shot1_vdr_dm_data.get_block(3).unwrap() { assert_eq!(level3.min_pq_offset, 2048); assert_eq!(level3.max_pq_offset, 2048); assert_eq!(level3.avg_pq_offset, 2048); } if let ExtMetadataBlock::Level5(level5) = shot1_vdr_dm_data.get_block(5).unwrap() { assert_eq!(level5.get_offsets(), (240, 240, 0, 0)); } if let ExtMetadataBlock::Level6(level6) = shot1_vdr_dm_data.get_block(6).unwrap() { assert_eq!(level6.min_display_mastering_luminance, 1); assert_eq!(level6.max_display_mastering_luminance, 1000); assert_eq!(level6.max_content_light_level, 3948); assert_eq!(level6.max_frame_average_light_level, 120); } if let ExtMetadataBlock::Level9(level9) = shot1_vdr_dm_data.get_block(9).unwrap() { assert_eq!(level9.source_primary_index, 0); } if let ExtMetadataBlock::Level11(level11) = shot1_vdr_dm_data.get_block(11).unwrap() { assert_eq!(level11.content_type, 1); assert_eq!(level11.whitepoint, 0); assert!(level11.reference_mode_flag); } // SHOT 2 let shot2_rpu = &rpus[120]; let shot2_vdr_dm_data = &shot2_rpu.vdr_dm_data.as_ref().unwrap(); assert_eq!(shot2_vdr_dm_data.scene_refresh_flag, 1); // L1, 3*L2, L5, L6 in CMv2.9 assert_eq!(shot2_vdr_dm_data.metadata_blocks(1).unwrap().len(), 6); // L3, 2*L8, L9, L11, L254 in CMv4.0 assert_eq!(shot2_vdr_dm_data.metadata_blocks(3).unwrap().len(), 6); if let ExtMetadataBlock::Level1(level1) = shot2_vdr_dm_data.get_block(1).unwrap() { assert_eq!(level1.min_pq, 0); assert_eq!(level1.max_pq, 2081); assert_eq!(level1.avg_pq, 1229); } assert_eq!(shot2_vdr_dm_data.level_blocks_iter(2).count(), 3); let mut shot2_level2_iter = shot2_vdr_dm_data.level_blocks_iter(2); if let ExtMetadataBlock::Level2(shot2_l2) = shot2_level2_iter.next().unwrap() { assert_eq!(shot2_l2.target_max_pq, 2081); assert_eq!(shot2_l2.trim_slope, 2013); assert_eq!(shot2_l2.trim_offset, 2016); assert_eq!(shot2_l2.trim_power, 1339); assert_eq!(shot2_l2.trim_chroma_weight, 2048); assert_eq!(shot2_l2.trim_saturation_gain, 2048); assert_eq!(shot2_l2.ms_weight, 2048); } if let ExtMetadataBlock::Level2(shot2_l2) = shot2_level2_iter.next().unwrap() { assert_eq!(shot2_l2.target_max_pq, 2851); assert_eq!(shot2_l2.trim_slope, 2059); assert_eq!(shot2_l2.trim_offset, 2048); assert_eq!(shot2_l2.trim_power, 1955); assert_eq!(shot2_l2.trim_chroma_weight, 2048); assert_eq!(shot2_l2.trim_saturation_gain, 2048); assert_eq!(shot2_l2.ms_weight, 2048); } if let ExtMetadataBlock::Level2(shot2_l2) = shot2_level2_iter.next().unwrap() { assert_eq!(shot2_l2.target_max_pq, 3079); assert_eq!(shot2_l2.trim_slope, 2049); assert_eq!(shot2_l2.trim_offset, 2048); assert_eq!(shot2_l2.trim_power, 2047); assert_eq!(shot2_l2.trim_chroma_weight, 2048); assert_eq!(shot2_l2.trim_saturation_gain, 2048); assert_eq!(shot2_l2.ms_weight, 2048); } if let ExtMetadataBlock::Level5(level5) = shot2_vdr_dm_data.get_block(5).unwrap() { assert_eq!(level5.get_offsets(), (480, 480, 0, 0)); } if let ExtMetadataBlock::Level6(level6) = shot2_vdr_dm_data.get_block(6).unwrap() { assert_eq!(level6.min_display_mastering_luminance, 1); assert_eq!(level6.max_display_mastering_luminance, 1000); assert_eq!(level6.max_content_light_level, 3948); assert_eq!(level6.max_frame_average_light_level, 120); } assert_eq!(shot2_vdr_dm_data.level_blocks_iter(8).count(), 2); let mut shot2_level8_iter = shot2_vdr_dm_data.level_blocks_iter(8); if let ExtMetadataBlock::Level8(shot2_l8) = shot2_level8_iter.next().unwrap() { assert_eq!(shot2_l8.length, 13); assert_eq!(shot2_l8.target_display_index, 1); assert_eq!(shot2_l8.trim_slope, 2048); assert_eq!(shot2_l8.trim_offset, 2048); assert_eq!(shot2_l8.trim_power, 2048); assert_eq!(shot2_l8.trim_chroma_weight, 2048); assert_eq!(shot2_l8.trim_saturation_gain, 2048); assert_eq!(shot2_l8.ms_weight, 2048); assert_eq!(shot2_l8.target_mid_contrast, 2048); assert_eq!(shot2_l8.clip_trim, 2011); } if let ExtMetadataBlock::Level8(shot2_l8) = shot2_level8_iter.next().unwrap() { assert_eq!(shot2_l8.target_display_index, 48); assert_eq!(shot2_l8.trim_slope, 2048); assert_eq!(shot2_l8.trim_offset, 2048); assert_eq!(shot2_l8.trim_power, 2048); assert_eq!(shot2_l8.trim_chroma_weight, 2048); assert_eq!(shot2_l8.trim_saturation_gain, 2048); assert_eq!(shot2_l8.ms_weight, 2048); } if let ExtMetadataBlock::Level9(level9) = shot2_vdr_dm_data.get_block(9).unwrap() { assert_eq!(level9.source_primary_index, 0); } if let ExtMetadataBlock::Level11(level11) = shot2_vdr_dm_data.get_block(11).unwrap() { assert_eq!(level11.content_type, 1); assert_eq!(level11.whitepoint, 0); assert!(level11.reference_mode_flag); } // SHOT 3 let shot3_rpu = &rpus[219]; let shot3_vdr_dm_data = &shot3_rpu.vdr_dm_data.as_ref().unwrap(); assert_eq!(shot3_vdr_dm_data.scene_refresh_flag, 1); // L1, L5, L6 in CMv2.9 assert_eq!(shot3_vdr_dm_data.metadata_blocks(1).unwrap().len(), 3); // L3, L9, L11, L254 in CMv4.0 assert_eq!(shot3_vdr_dm_data.metadata_blocks(3).unwrap().len(), 4); if let ExtMetadataBlock::Level1(level1) = shot3_vdr_dm_data.get_block(1).unwrap() { assert_eq!(level1.min_pq, 0); assert_eq!(level1.max_pq, 2875); assert_eq!(level1.avg_pq, 1229); } if let ExtMetadataBlock::Level3(level3) = shot3_vdr_dm_data.get_block(3).unwrap() { assert_eq!(level3.min_pq_offset, 2048); assert_eq!(level3.max_pq_offset, 2048); assert_eq!(level3.avg_pq_offset, 1871); } if let ExtMetadataBlock::Level5(level5) = shot3_vdr_dm_data.get_block(5).unwrap() { assert_eq!(level5.get_offsets(), (480, 480, 0, 0)); } if let ExtMetadataBlock::Level6(level6) = shot3_vdr_dm_data.get_block(6).unwrap() { assert_eq!(level6.min_display_mastering_luminance, 1); assert_eq!(level6.max_display_mastering_luminance, 1000); assert_eq!(level6.max_content_light_level, 3948); assert_eq!(level6.max_frame_average_light_level, 120); } if let ExtMetadataBlock::Level9(level9) = shot3_vdr_dm_data.get_block(9).unwrap() { assert_eq!(level9.source_primary_index, 0); } if let ExtMetadataBlock::Level11(level11) = shot3_vdr_dm_data.get_block(11).unwrap() { assert_eq!(level11.content_type, 1); assert_eq!(level11.whitepoint, 0); assert!(level11.reference_mode_flag); } // Frame edit in shot 3, offset 10 = 229 let shot3_edit_rpu = &rpus[229]; let shot3_edit_vdr_dm_data = &shot3_edit_rpu.vdr_dm_data.as_ref().unwrap(); assert_eq!(shot3_edit_vdr_dm_data.scene_refresh_flag, 0); // L1, L2, L5, L6 in CMv2.9 assert_eq!(shot3_edit_vdr_dm_data.metadata_blocks(1).unwrap().len(), 4); // L3, L8, L9, L11, L254 in CMv4.0 assert_eq!(shot3_edit_vdr_dm_data.metadata_blocks(3).unwrap().len(), 5); if let ExtMetadataBlock::Level1(level1) = shot3_edit_vdr_dm_data.get_block(1).unwrap() { assert_eq!(level1.min_pq, 0); assert_eq!(level1.max_pq, 2081); assert_eq!(level1.avg_pq, 1229); } assert_eq!(shot3_edit_vdr_dm_data.level_blocks_iter(2).count(), 1); let mut shot3_edit_level2_iter = shot3_edit_vdr_dm_data.level_blocks_iter(2); if let ExtMetadataBlock::Level2(shot3_edit_l2) = shot3_edit_level2_iter.next().unwrap() { assert_eq!(shot3_edit_l2.target_max_pq, 2081); assert_eq!(shot3_edit_l2.trim_slope, 2013); assert_eq!(shot3_edit_l2.trim_offset, 2016); assert_eq!(shot3_edit_l2.trim_power, 1339); assert_eq!(shot3_edit_l2.trim_chroma_weight, 2048); assert_eq!(shot3_edit_l2.trim_saturation_gain, 2048); assert_eq!(shot3_edit_l2.ms_weight, 2048); } if let ExtMetadataBlock::Level3(level3) = shot3_edit_vdr_dm_data.get_block(3).unwrap() { assert_eq!(level3.min_pq_offset, 2048); assert_eq!(level3.max_pq_offset, 2048); assert_eq!(level3.avg_pq_offset, 1871); } if let ExtMetadataBlock::Level5(level5) = shot3_edit_vdr_dm_data.get_block(5).unwrap() { assert_eq!(level5.get_offsets(), (480, 480, 0, 0)); } if let ExtMetadataBlock::Level6(level6) = shot3_edit_vdr_dm_data.get_block(6).unwrap() { assert_eq!(level6.min_display_mastering_luminance, 1); assert_eq!(level6.max_display_mastering_luminance, 1000); assert_eq!(level6.max_content_light_level, 3948); assert_eq!(level6.max_frame_average_light_level, 120); } assert_eq!(shot3_edit_vdr_dm_data.level_blocks_iter(8).count(), 1); let mut shot3_edit_level8_iter = shot3_edit_vdr_dm_data.level_blocks_iter(8); if let ExtMetadataBlock::Level8(shot3_edit_l8) = shot3_edit_level8_iter.next().unwrap() { assert_eq!(shot3_edit_l8.target_display_index, 1); assert_eq!(shot3_edit_l8.trim_slope, 2068); assert_eq!(shot3_edit_l8.trim_offset, 2048); assert_eq!(shot3_edit_l8.trim_power, 2048); assert_eq!(shot3_edit_l8.trim_chroma_weight, 2048); assert_eq!(shot3_edit_l8.trim_saturation_gain, 2048); assert_eq!(shot3_edit_l8.ms_weight, 2048); } if let ExtMetadataBlock::Level9(level9) = shot3_edit_vdr_dm_data.get_block(9).unwrap() { assert_eq!(level9.source_primary_index, 0); } if let ExtMetadataBlock::Level11(level11) = shot3_edit_vdr_dm_data.get_block(11).unwrap() { assert_eq!(level11.content_type, 1); assert_eq!(level11.whitepoint, 0); assert!(level11.reference_mode_flag); } Ok(()) } } impl std::fmt::Display for GenerateProfile { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { GenerateProfile::Profile5 => write!(f, "Profile 5 (IPT)"), GenerateProfile::Profile81 => write!(f, "Profile 8.1 (HDR10)"), GenerateProfile::Profile84 => write!(f, "Profile 8.4 (HLG)"), } } } dolby_vision-3.3.1/src/rpu/mod.rs000064400000000000000000000031461046102023000150560ustar 00000000000000use crc::{Crc, Table, CRC_32_MPEG_2}; pub mod dovi_rpu; pub mod extension_metadata; pub mod generate; pub mod profiles; pub mod rpu_data_header; pub mod rpu_data_mapping; pub mod rpu_data_nlq; pub mod vdr_dm_data; pub mod utils; static CRC32_INSTANCE: Crc> = Crc::>::new(&CRC_32_MPEG_2); pub const NUM_COMPONENTS: usize = 3; pub(crate) const MMR_MAX_COEFFS: usize = 7; pub(crate) const NLQ_NUM_PIVOTS: usize = 2; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ConversionMode { Lossless = 0, ToMel, To81, To84, To81MappingPreserved, } #[inline(always)] fn compute_crc32(data: &[u8]) -> u32 { CRC32_INSTANCE.checksum(data) } impl From for ConversionMode { fn from(mode: u8) -> ConversionMode { match mode { 0 => ConversionMode::Lossless, 1 => ConversionMode::ToMel, 2 | 3 => ConversionMode::To81, 4 => ConversionMode::To84, _ => ConversionMode::Lossless, } } } impl std::fmt::Display for ConversionMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ConversionMode::Lossless => write!(f, "Lossless"), ConversionMode::ToMel => write!(f, "To MEL"), ConversionMode::To81 => write!(f, "To 8.1"), ConversionMode::To84 => write!(f, "To 8.4"), ConversionMode::To81MappingPreserved => { write!(f, "To 8.1, preserving the mapping metadata") } } } } impl Default for ConversionMode { fn default() -> Self { Self::Lossless } } dolby_vision-3.3.1/src/rpu/profiles/mod.rs000064400000000000000000000004351046102023000166770ustar 00000000000000use super::vdr_dm_data::VdrDmData; pub mod profile4; pub mod profile5; pub mod profile7; pub mod profile81; pub mod profile84; pub trait DoviProfile { fn dm_data() -> VdrDmData { VdrDmData::default_pq() } fn backwards_compatible() -> bool { true } } dolby_vision-3.3.1/src/rpu/profiles/profile4.rs000064400000000000000000000023071046102023000176440ustar 00000000000000use super::{DoviProfile, VdrDmData}; pub struct Profile4 {} impl DoviProfile for Profile4 { fn dm_data() -> VdrDmData { VdrDmData { ycc_to_rgb_coef0: 9575, ycc_to_rgb_coef1: 0, ycc_to_rgb_coef2: 14742, ycc_to_rgb_coef3: 9575, ycc_to_rgb_coef4: -1754, ycc_to_rgb_coef5: -4383, ycc_to_rgb_coef6: 9575, ycc_to_rgb_coef7: 17372, ycc_to_rgb_coef8: 0, ycc_to_rgb_offset0: 67108864, ycc_to_rgb_offset1: 536870912, ycc_to_rgb_offset2: 536870912, rgb_to_lms_coef0: 5845, rgb_to_lms_coef1: 9702, rgb_to_lms_coef2: 837, rgb_to_lms_coef3: 2568, rgb_to_lms_coef4: 12256, rgb_to_lms_coef5: 1561, rgb_to_lms_coef6: 0, rgb_to_lms_coef7: 679, rgb_to_lms_coef8: 15705, signal_eotf: 39322, signal_eotf_param0: 15867, signal_eotf_param1: 228, signal_eotf_param2: 1383604, signal_bit_depth: 14, signal_full_range_flag: 1, source_diagonal: 42, ..Default::default() } } } dolby_vision-3.3.1/src/rpu/profiles/profile5.rs000064400000000000000000000020551046102023000176450ustar 00000000000000use super::{DoviProfile, VdrDmData}; pub struct Profile5 {} impl DoviProfile for Profile5 { fn dm_data() -> VdrDmData { VdrDmData { ycc_to_rgb_coef0: 8192, ycc_to_rgb_coef1: 799, ycc_to_rgb_coef2: 1681, ycc_to_rgb_coef3: 8192, ycc_to_rgb_coef4: -933, ycc_to_rgb_coef5: 1091, ycc_to_rgb_coef6: 8192, ycc_to_rgb_coef7: 267, ycc_to_rgb_coef8: -5545, ycc_to_rgb_offset0: 0, ycc_to_rgb_offset1: 134217728, ycc_to_rgb_offset2: 134217728, rgb_to_lms_coef0: 17081, rgb_to_lms_coef1: -349, rgb_to_lms_coef2: -349, rgb_to_lms_coef3: -349, rgb_to_lms_coef4: 17081, rgb_to_lms_coef5: -349, rgb_to_lms_coef6: -349, rgb_to_lms_coef7: -349, rgb_to_lms_coef8: 17081, signal_color_space: 2, ..VdrDmData::default_pq() } } fn backwards_compatible() -> bool { false } } dolby_vision-3.3.1/src/rpu/profiles/profile7.rs000064400000000000000000000002711046102023000176450ustar 00000000000000use super::{profile81::Profile81, DoviProfile, VdrDmData}; pub struct Profile7 {} impl DoviProfile for Profile7 { fn dm_data() -> VdrDmData { Profile81::dm_data() } } dolby_vision-3.3.1/src/rpu/profiles/profile81.rs000064400000000000000000000040671046102023000177360ustar 00000000000000use crate::rpu::{ rpu_data_mapping::{ DoviMappingMethod, DoviPolynomialCurve, DoviReshapingCurve, RpuDataMapping, }, NUM_COMPONENTS, }; use super::{DoviProfile, VdrDmData}; pub struct Profile81 {} impl DoviProfile for Profile81 { fn dm_data() -> VdrDmData { VdrDmData { ycc_to_rgb_coef0: 9574, ycc_to_rgb_coef1: 0, ycc_to_rgb_coef2: 13802, ycc_to_rgb_coef3: 9574, ycc_to_rgb_coef4: -1540, ycc_to_rgb_coef5: -5348, ycc_to_rgb_coef6: 9574, ycc_to_rgb_coef7: 17610, ycc_to_rgb_coef8: 0, ycc_to_rgb_offset0: 16777216, ycc_to_rgb_offset1: 134217728, ycc_to_rgb_offset2: 134217728, rgb_to_lms_coef0: 7222, rgb_to_lms_coef1: 8771, rgb_to_lms_coef2: 390, rgb_to_lms_coef3: 2654, rgb_to_lms_coef4: 12430, rgb_to_lms_coef5: 1300, rgb_to_lms_coef6: 0, rgb_to_lms_coef7: 422, rgb_to_lms_coef8: 15962, ..VdrDmData::default_pq() } } } impl Profile81 { pub fn rpu_data_mapping() -> RpuDataMapping { let curves: [DoviReshapingCurve; NUM_COMPONENTS] = [ Self::dovi_reshaping_curve(), Self::dovi_reshaping_curve(), Self::dovi_reshaping_curve(), ]; RpuDataMapping { vdr_rpu_id: 0, mapping_color_space: 0, mapping_chroma_format_idc: 0, nlq_method_idc: None, nlq_num_pivots_minus2: None, nlq_pred_pivot_value: None, num_x_partitions_minus1: 0, num_y_partitions_minus1: 0, curves, nlq: None, } } pub fn dovi_reshaping_curve() -> DoviReshapingCurve { DoviReshapingCurve { num_pivots_minus2: 0, pivots: vec![0, 1023], mapping_idc: DoviMappingMethod::Polynomial, polynomial: Some(DoviPolynomialCurve::p81_default()), mmr: None, } } } dolby_vision-3.3.1/src/rpu/profiles/profile84.rs000064400000000000000000000107121046102023000177330ustar 00000000000000use tinyvec::array_vec; use crate::rpu::{ rpu_data_mapping::{ DoviMMRCurve, DoviMappingMethod, DoviPolynomialCurve, DoviReshapingCurve, RpuDataMapping, }, NUM_COMPONENTS, }; use super::{profile81::Profile81, DoviProfile, VdrDmData}; pub struct Profile84 {} impl DoviProfile for Profile84 { fn dm_data() -> VdrDmData { VdrDmData { source_min_pq: 62, source_max_pq: 3079, ..Profile81::dm_data() } } } // Based on iPhone 13 polynomials and MMR impl Profile84 { pub fn rpu_data_mapping() -> RpuDataMapping { // Luma component let poly_coef_int = vec![ array_vec!(-1, 1, -3), array_vec!(-1, 1, -2), array_vec!(0, 0, -1), array_vec!(0, 0, 0), array_vec!(0, -2, 1), array_vec!(6, -14, 8), array_vec!(13, -30, 16), array_vec!(28, -62, 34), ]; let poly_coef = vec![ array_vec!(7978928, 8332855, 4889184), array_vec!(8269552, 5186604, 3909327), array_vec!(1317527, 5338528, 7440486), array_vec!(2119979, 2065496, 2288524), array_vec!(7982780, 5409990, 1585336), array_vec!(3460436, 3197328, 615464), array_vec!(3921968, 6820672, 5546752), array_vec!(1947392, 1244640, 6094272), ]; let poly_curve = DoviPolynomialCurve { poly_order_minus1: vec![1; 8], linear_interp_flag: vec![], poly_coef_int, poly_coef, }; let luma_reshaping_curve = DoviReshapingCurve { num_pivots_minus2: 7, pivots: vec![63, 69, 230, 256, 256, 37, 16, 8, 7], mapping_idc: DoviMappingMethod::Polynomial, polynomial: Some(poly_curve), mmr: None, }; // Chroma component 1 let mmr_coef_int_cmp1 = vec![array_vec!( array_vec!(-1, -2, -5, 2, 5, 9, -12), array_vec!(-1, -1, 3, -1, -5, -12, 18), array_vec!(-1, 0, -2, 0, 2, 7, -19) )]; let mmr_coef_cmp1 = vec![array_vec!( array_vec!(87355, 6228986, 642500, 1023296, 6569512, 5128216, 4317296), array_vec!(8299905, 5819931, 2324124, 7273546, 1562484, 3679480, 6357360), array_vec!(8172981, 3261951, 5970055, 927142, 3525840, 5110348, 6236848) )]; let mmr_curve1 = DoviMMRCurve { mmr_order_minus1: vec![2], mmr_constant_int: vec![1], mmr_constant: vec![1150183], mmr_coef_int: mmr_coef_int_cmp1, mmr_coef: mmr_coef_cmp1, }; let chroma_reshaping_curve1 = DoviReshapingCurve { num_pivots_minus2: 0, pivots: vec![0, 1023], mapping_idc: DoviMappingMethod::MMR, polynomial: None, mmr: Some(mmr_curve1), }; // Chroma component 2 let mmr_coef_int_cmp2 = vec![array_vec!( array_vec!(4, 0, 5, -2, -8, -1, 1), array_vec!(-4, -1, -6, 1, 12, 0, -4), array_vec!(1, 0, 2, -1, -8, -1, 4) )]; let mmr_coef_cmp2 = vec![array_vec!( array_vec!(193104, 5369128, 2553116, 8009648, 2772020, 3122453, 2961581), array_vec!(6769788, 2565605, 7864496, 4777288, 649616, 7036536, 1666406), array_vec!(406265, 2901521, 2680224, 146340, 1008052, 4366810, 5080852) )]; let mmr_curve2 = DoviMMRCurve { mmr_order_minus1: vec![2], mmr_constant_int: vec![-2], mmr_constant: vec![6266112], mmr_coef_int: mmr_coef_int_cmp2, mmr_coef: mmr_coef_cmp2, }; let chroma_reshaping_curve2 = DoviReshapingCurve { num_pivots_minus2: 0, pivots: vec![0, 1023], mapping_idc: DoviMappingMethod::MMR, polynomial: None, mmr: Some(mmr_curve2), }; let curves: [DoviReshapingCurve; NUM_COMPONENTS] = [ luma_reshaping_curve, chroma_reshaping_curve1, chroma_reshaping_curve2, ]; RpuDataMapping { vdr_rpu_id: 0, mapping_color_space: 0, mapping_chroma_format_idc: 0, nlq_method_idc: None, nlq_num_pivots_minus2: None, nlq_pred_pivot_value: None, num_x_partitions_minus1: 0, num_y_partitions_minus1: 0, curves, nlq: None, } } } dolby_vision-3.3.1/src/rpu/rpu_data_header.rs000064400000000000000000000220641046102023000174060ustar 00000000000000use anyhow::{bail, ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::Serialize; #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct RpuDataHeader { /// Must be 25 #[deprecated( since = "3.2.0", note = "The field is not actually part of the RPU header" )] pub rpu_nal_prefix: u8, // Must be 2 pub rpu_type: u8, pub rpu_format: u16, pub vdr_rpu_profile: u8, pub vdr_rpu_level: u8, pub vdr_seq_info_present_flag: bool, pub chroma_resampling_explicit_filter_flag: bool, pub coefficient_data_type: u8, pub coefficient_log2_denom: u64, /// Calculated for `coefficient_data_type` pub coefficient_log2_denom_length: u32, pub vdr_rpu_normalized_idc: u8, pub bl_video_full_range_flag: bool, // [8, 16] pub bl_bit_depth_minus8: u64, pub el_bit_depth_minus8: u64, /// Extended base layer inverse mapping indicator pub ext_mapping_idc_0_4: u8, /// Reserved pub ext_mapping_idc_5_7: u8, // [8, 16] pub vdr_bit_depth_minus8: u64, pub spatial_resampling_filter_flag: bool, pub reserved_zero_3bits: u8, pub el_spatial_resampling_filter_flag: bool, pub disable_residual_flag: bool, pub vdr_dm_metadata_present_flag: bool, pub use_prev_vdr_rpu_flag: bool, // [0, 15] pub prev_vdr_rpu_id: u64, } impl RpuDataHeader { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { let rpu_type = reader.get_n(6)?; ensure!(rpu_type == 2); let rpu_format = reader.get_n(11)?; let vdr_rpu_profile = reader.get_n(4)?; let vdr_rpu_level = reader.get_n(4)?; let vdr_seq_info_present_flag = reader.get()?; let mut header = RpuDataHeader { rpu_type, rpu_format, vdr_rpu_profile, vdr_rpu_level, vdr_seq_info_present_flag, ..Default::default() }; if vdr_seq_info_present_flag { header.chroma_resampling_explicit_filter_flag = reader.get()?; header.coefficient_data_type = reader.get_n(2)?; if header.coefficient_data_type == 0 { header.coefficient_log2_denom = reader.get_ue()?; } header.vdr_rpu_normalized_idc = reader.get_n(2)?; header.bl_video_full_range_flag = reader.get()?; if header.rpu_format & 0x700 == 0 { header.bl_bit_depth_minus8 = reader.get_ue()?; let el_bit_depth_minus8 = reader.get_ue()?; // 8 lowest bits header.el_bit_depth_minus8 = el_bit_depth_minus8 & 0xFF; // Next 8 bits let ext_mapping_idc = (el_bit_depth_minus8 >> 8) as u8; // Lowest 5 bits header.ext_mapping_idc_0_4 = ext_mapping_idc & 0x1F; header.ext_mapping_idc_5_7 = ext_mapping_idc >> 5; header.vdr_bit_depth_minus8 = reader.get_ue()?; header.spatial_resampling_filter_flag = reader.get()?; header.reserved_zero_3bits = reader.get_n(3)?; header.el_spatial_resampling_filter_flag = reader.get()?; header.disable_residual_flag = reader.get()?; } header.coefficient_log2_denom_length = if header.coefficient_data_type == 0 { header.coefficient_log2_denom as u32 } else if header.coefficient_data_type == 1 { 32 } else { bail!( "Invalid coefficient_data_type value: {}", header.coefficient_data_type ); }; } header.vdr_dm_metadata_present_flag = reader.get()?; header.use_prev_vdr_rpu_flag = reader.get()?; if header.use_prev_vdr_rpu_flag { header.prev_vdr_rpu_id = reader.get_ue()?; } Ok(header) } pub fn validate(&self, profile: u8) -> Result<()> { match profile { 5 => { ensure!( self.vdr_rpu_profile == 0, "profile 5: vdr_rpu_profile should be 0" ); ensure!( self.bl_video_full_range_flag, "profile 5: bl_video_full_range_flag should be true" ); } 7 => { ensure!( self.vdr_rpu_profile == 1, "profile 7: vdr_rpu_profile should be 1" ); } 8 => { ensure!( self.vdr_rpu_profile == 1, "profile 8: vdr_rpu_profile should be 1" ); } _ => (), }; ensure!(self.vdr_rpu_level == 0, "vdr_rpu_level should be 0"); ensure!( self.bl_bit_depth_minus8 == 2, "bl_bit_depth_minus8 should be 2" ); ensure!( self.el_bit_depth_minus8 == 2, "el_bit_depth_minus8 should be 2" ); ensure!( self.vdr_bit_depth_minus8 <= 6, "vdr_bit_depth_minus8 should be <= 6" ); ensure!( self.coefficient_log2_denom <= 23, "coefficient_log2_denom should be <= 23" ); Ok(()) } pub fn get_dovi_profile(&self) -> u8 { match self.vdr_rpu_profile { 0 => { // Profile 5 is full range if self.bl_video_full_range_flag { 5 } else { 0 } } 1 => { // 4, 7 or 8 if self.el_spatial_resampling_filter_flag && !self.disable_residual_flag { if self.vdr_bit_depth_minus8 == 4 { 7 } else { 4 } } else { 8 } } _ => 0, } } pub fn write_header(&self, writer: &mut BitstreamIoWriter) -> Result<()> { writer.write_n(&self.rpu_type, 6)?; writer.write_n(&self.rpu_format, 11)?; writer.write_n(&self.vdr_rpu_profile, 4)?; writer.write_n(&self.vdr_rpu_level, 4)?; writer.write(self.vdr_seq_info_present_flag)?; if self.vdr_seq_info_present_flag { writer.write(self.chroma_resampling_explicit_filter_flag)?; writer.write_n(&self.coefficient_data_type, 2)?; if self.coefficient_data_type == 0 { writer.write_ue(&self.coefficient_log2_denom)?; } writer.write_n(&self.vdr_rpu_normalized_idc, 2)?; writer.write(self.bl_video_full_range_flag)?; if self.rpu_format & 0x700 == 0 { writer.write_ue(&self.bl_bit_depth_minus8)?; let ext_mapping_idc = ((self.ext_mapping_idc_5_7 << 5) | self.ext_mapping_idc_0_4) as u64; let el_bit_depth_minus8 = (ext_mapping_idc << 8) | self.el_bit_depth_minus8; writer.write_ue(&el_bit_depth_minus8)?; writer.write_ue(&self.vdr_bit_depth_minus8)?; writer.write(self.spatial_resampling_filter_flag)?; writer.write_n(&self.reserved_zero_3bits, 3)?; writer.write(self.el_spatial_resampling_filter_flag)?; writer.write(self.disable_residual_flag)?; } } writer.write(self.vdr_dm_metadata_present_flag)?; writer.write(self.use_prev_vdr_rpu_flag)?; if self.use_prev_vdr_rpu_flag { writer.write_ue(&self.prev_vdr_rpu_id)?; } Ok(()) } pub fn p5_default() -> RpuDataHeader { RpuDataHeader { vdr_rpu_profile: 0, bl_video_full_range_flag: true, ..RpuDataHeader::p8_default() } } pub fn p8_default() -> RpuDataHeader { let mut header = RpuDataHeader { rpu_type: 2, rpu_format: 18, vdr_rpu_profile: 1, vdr_rpu_level: 0, vdr_seq_info_present_flag: true, chroma_resampling_explicit_filter_flag: false, coefficient_data_type: 0, coefficient_log2_denom: 23, coefficient_log2_denom_length: 23, vdr_rpu_normalized_idc: 1, bl_video_full_range_flag: false, bl_bit_depth_minus8: 2, el_bit_depth_minus8: 2, vdr_bit_depth_minus8: 4, spatial_resampling_filter_flag: false, reserved_zero_3bits: 0, el_spatial_resampling_filter_flag: false, disable_residual_flag: true, vdr_dm_metadata_present_flag: true, use_prev_vdr_rpu_flag: false, prev_vdr_rpu_id: 0, ..Default::default() }; // FIXME: rpu_nal_prefix deprecation #[allow(deprecated)] { header.rpu_nal_prefix = 25; } header } } dolby_vision-3.3.1/src/rpu/rpu_data_mapping.rs000064400000000000000000000442051046102023000176120ustar 00000000000000use anyhow::{bail, ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::Serialize; use tinyvec::{array_vec, ArrayVec}; use crate::rpu::MMR_MAX_COEFFS; use super::rpu_data_header::RpuDataHeader; use super::rpu_data_nlq::{DoviELType, RpuDataNlq}; use super::{NLQ_NUM_PIVOTS, NUM_COMPONENTS}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize))] pub enum DoviMappingMethod { /// Not a valid value, placeholder for Default Invalid = 255, Polynomial = 0, MMR, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize))] pub enum DoviNlqMethod { LinearDeadzone = 0, } #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct RpuDataMapping { // [0, 15] pub vdr_rpu_id: u64, pub mapping_color_space: u64, pub mapping_chroma_format_idc: u64, pub num_x_partitions_minus1: u64, pub num_y_partitions_minus1: u64, pub curves: [DoviReshapingCurve; NUM_COMPONENTS], // NLQ params #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub nlq_method_idc: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub nlq_num_pivots_minus2: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub nlq_pred_pivot_value: Option<[u16; NLQ_NUM_PIVOTS]>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub nlq: Option, } #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct DoviReshapingCurve { // [2, 9] pub num_pivots_minus2: u64, pub pivots: Vec, // Consistent for a component // Luma (component 0): Polynomial // Chroma (components 1 and 2): MMR pub mapping_idc: DoviMappingMethod, /// DoviMappingMethod::Polynomial #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(feature = "serde", serde(flatten))] pub polynomial: Option, /// DoviMappingMethod::MMR #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(feature = "serde", serde(flatten))] pub mmr: Option, } #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct DoviPolynomialCurve { pub poly_order_minus1: Vec, pub linear_interp_flag: Vec, pub poly_coef_int: Vec>, pub poly_coef: Vec>, } #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct DoviMMRCurve { pub mmr_order_minus1: Vec, pub mmr_constant_int: Vec, pub mmr_constant: Vec, pub mmr_coef_int: Vec; 3]>>, pub mmr_coef: Vec; 3]>>, } impl RpuDataMapping { pub(crate) fn parse( reader: &mut BsIoSliceReader, header: &RpuDataHeader, ) -> Result { let mut mapping = RpuDataMapping { vdr_rpu_id: reader.get_ue()?, mapping_color_space: reader.get_ue()?, mapping_chroma_format_idc: reader.get_ue()?, ..Default::default() }; let bl_bit_depth = (header.bl_bit_depth_minus8 + 8) as u32; for cmp in 0..NUM_COMPONENTS { let curve = &mut mapping.curves[cmp]; curve.num_pivots_minus2 = reader.get_ue()?; let num_pivots = (curve.num_pivots_minus2 + 2) as usize; curve.pivots = vec![0; num_pivots]; for i in 0..num_pivots { curve.pivots[i] = reader.get_n(bl_bit_depth)?; } } // Profile 7 only if header.rpu_format & 0x700 == 0 && !header.disable_residual_flag { let nlq_method_idc = reader.get_n::(3)?; ensure!(nlq_method_idc == 0); mapping.nlq_method_idc = Some(DoviNlqMethod::from(nlq_method_idc)); mapping.nlq_num_pivots_minus2 = Some(0); let mut nlq_pred_pivot_value = [0; NLQ_NUM_PIVOTS]; for pv in &mut nlq_pred_pivot_value { *pv = reader.get_n(bl_bit_depth)?; } mapping.nlq_pred_pivot_value = Some(nlq_pred_pivot_value); } mapping.num_x_partitions_minus1 = reader.get_ue()?; mapping.num_y_partitions_minus1 = reader.get_ue()?; // rpu_data_mapping_param for cmp in 0..NUM_COMPONENTS { let curve = &mut mapping.curves[cmp]; let num_pieces = (curve.num_pivots_minus2 + 1) as usize; for _ in 0..num_pieces { let mapping_idc = DoviMappingMethod::from(reader.get_ue()?); curve.mapping_idc = mapping_idc; // MAPPING_POLYNOMIAL if mapping_idc == DoviMappingMethod::Polynomial { let poly_curve = curve .polynomial .get_or_insert_with(|| DoviPolynomialCurve::new(num_pieces)); poly_curve.parse(reader, header)?; } else if mapping_idc == DoviMappingMethod::MMR { let mmr_curve = curve .mmr .get_or_insert_with(|| DoviMMRCurve::new(num_pieces)); mmr_curve.parse(reader, header)?; } } } if mapping.nlq_method_idc.is_some() { mapping.nlq = Some(RpuDataNlq::parse(reader, header, &mapping)?); } Ok(mapping) } pub fn write(&self, writer: &mut BitstreamIoWriter, header: &RpuDataHeader) -> Result<()> { let coefficient_log2_denom_length = header.coefficient_log2_denom_length; let bl_bit_depth = (header.bl_bit_depth_minus8 + 8) as u32; writer.write_ue(&self.vdr_rpu_id)?; writer.write_ue(&self.mapping_color_space)?; writer.write_ue(&self.mapping_chroma_format_idc)?; for cmp in 0..NUM_COMPONENTS { let curve = &self.curves[cmp]; writer.write_ue(&curve.num_pivots_minus2)?; for p in &curve.pivots { writer.write_n(p, bl_bit_depth)?; } } if header.rpu_format & 0x700 == 0 && !header.disable_residual_flag { if let Some(nlq_method_idc) = self.nlq_method_idc { writer.write_n(&(nlq_method_idc as u8), 3)?; } if let Some(nlq_pred_pivot_value) = &self.nlq_pred_pivot_value { for pv in nlq_pred_pivot_value { writer.write_n(pv, bl_bit_depth)?; } } } writer.write_ue(&self.num_x_partitions_minus1)?; writer.write_ue(&self.num_y_partitions_minus1)?; for cmp in 0..NUM_COMPONENTS { let curve = &self.curves[cmp]; let num_pieces = (curve.num_pivots_minus2 + 1) as usize; for i in 0..num_pieces { writer.write_ue(&(curve.mapping_idc as u64))?; // MAPPING_POLYNOMIAL if let Some(poly_curve) = &curve.polynomial { writer.write_ue(&poly_curve.poly_order_minus1[i])?; let poly_order_minus1 = poly_curve.poly_order_minus1[i]; if poly_order_minus1 == 0 { writer.write(poly_curve.linear_interp_flag[i])?; } if poly_order_minus1 == 0 && poly_curve.linear_interp_flag[i] { unimplemented!("write: Polynomial interpolation: please open an issue"); /* if header.coefficient_data_type == 0 { writer.write_ue( self.pred_linear_interp_value_int[cmp_idx][pivot_idx], ); } writer.write_n( &self.pred_linear_interp_value[cmp_idx][pivot_idx].to_be_bytes(), coefficient_log2_denom_length, ); if pivot_idx as u64 == header.num_pivots_minus2[cmp_idx] { if header.coefficient_data_type == 0 { writer.write_ue( self.pred_linear_interp_value_int[cmp_idx][pivot_idx + 1], ); } writer.write_n( &self.pred_linear_interp_value[cmp_idx][pivot_idx + 1] .to_be_bytes(), coefficient_log2_denom_length, ); } */ } else { let poly_coef_count = poly_order_minus1 as usize + 1; for j in 0..=poly_coef_count { if header.coefficient_data_type == 0 { writer.write_se(&poly_curve.poly_coef_int[i][j])?; } writer.write_n( &poly_curve.poly_coef[i][j], coefficient_log2_denom_length, )?; } } } else if let Some(mmr_curve) = &curve.mmr { // MAPPING_MMR writer.write_n(&mmr_curve.mmr_order_minus1[i], 2)?; if header.coefficient_data_type == 0 { writer.write_se(&mmr_curve.mmr_constant_int[i])?; } writer.write_n(&mmr_curve.mmr_constant[i], coefficient_log2_denom_length)?; for j in 0..mmr_curve.mmr_order_minus1[i] as usize + 1 { for k in 0..MMR_MAX_COEFFS { if header.coefficient_data_type == 0 { writer.write_se(&mmr_curve.mmr_coef_int[i][j][k])?; } writer.write_n( &mmr_curve.mmr_coef[i][j][k], coefficient_log2_denom_length, )?; } } } else { bail!("Missing mapping method"); } } } if let Some(nlq) = self.nlq.as_ref() { nlq.write(writer, header, self)?; } Ok(()) } pub fn validate(&self, profile: u8) -> Result<()> { match profile { 5 => { ensure!( self.nlq_method_idc.is_none(), "profile 5: nlq_method_idc should be undefined" ); ensure!( self.nlq_num_pivots_minus2.is_none(), "profile 5: nlq_num_pivots_minus2 should be undefined" ); ensure!( self.nlq_pred_pivot_value.is_none(), "profile 5: nlq_pred_pivot_value should be undefined" ); } 7 => { ensure!( self.nlq_pred_pivot_value.is_some(), "profile 7: nlq_pred_pivot_value should be defined" ); if let Some(nlq_pred_pivot_value) = self.nlq_pred_pivot_value { ensure!( nlq_pred_pivot_value.iter().sum::() == 1023, "profile 7: nlq_pred_pivot_value elements should add up to the BL bit depth" ); } } 8 => { ensure!( self.nlq_method_idc.is_none(), "profile 8: nlq_method_idc should be undefined" ); ensure!( self.nlq_num_pivots_minus2.is_none(), "profile 8: nlq_num_pivots_minus2 should be undefined" ); ensure!( self.nlq_pred_pivot_value.is_none(), "profile 8: nlq_pred_pivot_value should be undefined" ); } _ => (), }; ensure!( self.mapping_color_space == 0, "mapping_color_space should be 0" ); ensure!( self.mapping_chroma_format_idc == 0, "mapping_chroma_format_idc should be 0" ); Ok(()) } pub fn set_empty_p81_mapping(&mut self) { self.curves.iter_mut().for_each(|curve| { curve.num_pivots_minus2 = 0; curve.pivots.clear(); curve.pivots.push(0); curve.pivots.push(1023); curve.mapping_idc = DoviMappingMethod::Polynomial; curve.mmr = None; if let Some(poly_curve) = curve.polynomial.as_mut() { poly_curve.set_p81_params(); } else { curve.polynomial = Some(DoviPolynomialCurve::p81_default()); } }); } pub fn get_enhancement_layer_type(&self) -> Option { self.nlq.as_ref().map(|nlq| nlq.el_type()) } } impl DoviPolynomialCurve { fn new(num_pieces: usize) -> Self { DoviPolynomialCurve { poly_order_minus1: Vec::with_capacity(num_pieces), linear_interp_flag: Vec::with_capacity(num_pieces), poly_coef_int: Vec::with_capacity(num_pieces), poly_coef: Vec::with_capacity(num_pieces), } } fn parse(&mut self, reader: &mut BsIoSliceReader, header: &RpuDataHeader) -> Result<()> { let coefficient_log2_denom_length = header.coefficient_log2_denom_length; let poly_order_minus1 = reader.get_ue()?; ensure!(poly_order_minus1 <= 1); self.poly_order_minus1.push(poly_order_minus1); let linear_interp_flag = if poly_order_minus1 == 0 { reader.get()? } else { false }; self.linear_interp_flag.push(linear_interp_flag); if poly_order_minus1 == 0 && linear_interp_flag { // Linear interpolation unimplemented!("parse: Polynomial interpolation: please open an issue"); /*if header.coefficient_data_type == 0 { self.pred_linear_interp_value_int[i] = reader.get_ue()?; } self.pred_linear_interp_value[i] = reader.get_n(coefficient_log2_denom_length)?; if pivot_idx as u64 == header.num_pivots_minus2[cmp] { if header.coefficient_data_type == 0 { self.pred_linear_interp_value_int[cmp][pivot_idx + 1] = reader.get_ue()?; } self.pred_linear_interp_value[cmp][pivot_idx + 1] = reader.get_n(coefficient_log2_denom_length)?; }*/ } else { let poly_coef_count = poly_order_minus1 as usize + 2; let mut poly_coef_int = array_vec!(); let mut poly_coef = array_vec!(); for _j in 0..poly_coef_count { if header.coefficient_data_type == 0 { poly_coef_int.push(reader.get_se()?); } poly_coef.push(reader.get_n(coefficient_log2_denom_length)?); } self.poly_coef_int.push(poly_coef_int); self.poly_coef.push(poly_coef); } Ok(()) } pub fn p81_default() -> Self { let mut poly_curve = Self::new(1); poly_curve.set_p81_params(); poly_curve } pub fn set_p81_params(&mut self) { self.poly_order_minus1.clear(); self.poly_order_minus1.push(0); self.linear_interp_flag.clear(); self.linear_interp_flag.push(false); self.poly_coef_int.clear(); self.poly_coef_int.push(array_vec!(0, 1)); self.poly_coef.clear(); self.poly_coef.push(array_vec!(0, 0)); } } impl DoviMMRCurve { fn new(num_pieces: usize) -> Self { DoviMMRCurve { mmr_order_minus1: Vec::with_capacity(num_pieces), mmr_constant_int: Vec::with_capacity(num_pieces), mmr_constant: Vec::with_capacity(num_pieces), mmr_coef_int: Vec::with_capacity(num_pieces), mmr_coef: Vec::with_capacity(num_pieces), } } fn parse(&mut self, reader: &mut BsIoSliceReader, header: &RpuDataHeader) -> Result<()> { let coefficient_log2_denom_length = header.coefficient_log2_denom_length; let mmr_order_minus1 = reader.get_n(2)?; ensure!(mmr_order_minus1 <= 2); self.mmr_order_minus1.push(mmr_order_minus1); let mmr_orders_count = mmr_order_minus1 as usize + 1; if header.coefficient_data_type == 0 { self.mmr_constant_int.push(reader.get_se()?); } self.mmr_constant .push(reader.get_n(coefficient_log2_denom_length)?); let mut mmr_coef_int = array_vec!(); let mut mmr_coef = array_vec!(); for _j in 0..mmr_orders_count { let mut mmr_coef_int2 = array_vec!(); let mut mmr_coef2 = array_vec!(); for _k in 0..MMR_MAX_COEFFS { if header.coefficient_data_type == 0 { mmr_coef_int2.push(reader.get_se()?); } mmr_coef2.push(reader.get_n(coefficient_log2_denom_length)?); } mmr_coef_int.push(mmr_coef_int2); mmr_coef.push(mmr_coef2); } self.mmr_coef_int.push(mmr_coef_int); self.mmr_coef.push(mmr_coef); Ok(()) } } impl Default for DoviMappingMethod { fn default() -> Self { Self::Invalid } } impl From for DoviMappingMethod { fn from(value: u64) -> Self { match value { 0 => Self::Polynomial, 1 => Self::MMR, _ => unreachable!(), } } } impl From for DoviNlqMethod { fn from(value: u8) -> Self { match value { 0 => Self::LinearDeadzone, _ => unreachable!(), } } } dolby_vision-3.3.1/src/rpu/rpu_data_nlq.rs000064400000000000000000000147311046102023000167520ustar 00000000000000use std::fmt::Display; use anyhow::{ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::Serialize; use super::rpu_data_header::RpuDataHeader; use super::rpu_data_mapping::{DoviNlqMethod, RpuDataMapping}; use super::NUM_COMPONENTS; const FEL_STR: &str = "FEL"; const MEL_STR: &str = "MEL"; #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize))] pub enum DoviELType { MEL, FEL, } #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct RpuDataNlq { // [0, 512] pub nlq_offset: [u16; NUM_COMPONENTS], pub vdr_in_max_int: [u64; NUM_COMPONENTS], pub vdr_in_max: [u64; NUM_COMPONENTS], pub linear_deadzone_slope_int: [u64; NUM_COMPONENTS], pub linear_deadzone_slope: [u64; NUM_COMPONENTS], pub linear_deadzone_threshold_int: [u64; NUM_COMPONENTS], pub linear_deadzone_threshold: [u64; NUM_COMPONENTS], } impl RpuDataNlq { pub(crate) fn parse( reader: &mut BsIoSliceReader, header: &RpuDataHeader, mapping: &RpuDataMapping, ) -> Result { ensure!( mapping.nlq_num_pivots_minus2.is_some(), "Shouldn't be in NLQ if not profile 7!" ); let num_pivots = mapping.nlq_num_pivots_minus2.unwrap() as usize + 1; ensure!(num_pivots == 1, "NLQ should only have 1 significant pivot"); let mut data = RpuDataNlq::default(); let coefficient_log2_denom_length = header.coefficient_log2_denom_length; for cmp in 0..NUM_COMPONENTS { // rpu_data_nlq_param data.nlq_offset[cmp] = reader.get_n((header.el_bit_depth_minus8 + 8) as u32)?; if header.coefficient_data_type == 0 { data.vdr_in_max_int[cmp] = reader.get_ue()?; } data.vdr_in_max[cmp] = reader.get_n(coefficient_log2_denom_length)?; // NLQ_LINEAR_DZ if let Some(nlq_method_idc) = mapping.nlq_method_idc { if nlq_method_idc == DoviNlqMethod::LinearDeadzone { if header.coefficient_data_type == 0 { data.linear_deadzone_slope_int[cmp] = reader.get_ue()?; } data.linear_deadzone_slope[cmp] = reader.get_n(coefficient_log2_denom_length)?; if header.coefficient_data_type == 0 { data.linear_deadzone_threshold_int[cmp] = reader.get_ue()?; } data.linear_deadzone_threshold[cmp] = reader.get_n(coefficient_log2_denom_length)?; } } } Ok(data) } pub fn convert_to_mel(&mut self) { // Set to 0 self.nlq_offset.fill(0); // Set to 1 self.vdr_in_max_int.fill(1); // Set to 0 self.vdr_in_max.fill(0); self.linear_deadzone_slope_int.fill(0); self.linear_deadzone_slope.fill(0); self.linear_deadzone_threshold_int.fill(0); self.linear_deadzone_threshold.fill(0); } pub fn write( &self, writer: &mut BitstreamIoWriter, header: &RpuDataHeader, mapping: &RpuDataMapping, ) -> Result<()> { let coefficient_log2_denom_length = header.coefficient_log2_denom_length; for cmp in 0..NUM_COMPONENTS { // rpu_data_nlq_param writer.write_n( &self.nlq_offset[cmp], (header.el_bit_depth_minus8 + 8) as u32, )?; if header.coefficient_data_type == 0 { writer.write_ue(&self.vdr_in_max_int[cmp])?; } writer.write_n(&self.vdr_in_max[cmp], coefficient_log2_denom_length)?; if let Some(nlq_method_idc) = mapping.nlq_method_idc { if nlq_method_idc == DoviNlqMethod::LinearDeadzone { // NLQ_LINEAR_DZ if header.coefficient_data_type == 0 { writer.write_ue(&self.linear_deadzone_slope_int[cmp])?; } writer.write_n( &self.linear_deadzone_slope[cmp], coefficient_log2_denom_length, )?; if header.coefficient_data_type == 0 { writer.write_ue(&self.linear_deadzone_slope_int[cmp])?; } writer.write_n( &self.linear_deadzone_threshold[cmp], coefficient_log2_denom_length, )?; } } } Ok(()) } pub fn mel_default() -> Self { let zeroed_cmps = [0_u64; NUM_COMPONENTS]; let vdr_in_max_int = [1; NUM_COMPONENTS]; Self { nlq_offset: [0_u16; NUM_COMPONENTS], vdr_in_max_int, vdr_in_max: zeroed_cmps, linear_deadzone_slope_int: zeroed_cmps, linear_deadzone_slope: zeroed_cmps, linear_deadzone_threshold_int: zeroed_cmps, linear_deadzone_threshold: zeroed_cmps, } } pub fn is_mel(&self) -> bool { let zero_nlq_offset = self.nlq_offset.iter().all(|e| *e == 0); let one_vdr_in_max_int = self.vdr_in_max_int.iter().all(|e| *e == 1); let one_vdr_in_max = self.vdr_in_max.iter().all(|e| *e == 0); let zero_dz_slope_int = self.linear_deadzone_slope_int.iter().all(|e| *e == 0); let zero_dz_slope = self.linear_deadzone_slope.iter().all(|e| *e == 0); let zero_dz_threshold_int = self.linear_deadzone_threshold_int.iter().all(|e| *e == 0); let zero_dz_threshold = self.linear_deadzone_threshold.iter().all(|e| *e == 0); zero_nlq_offset && one_vdr_in_max_int && one_vdr_in_max && zero_dz_slope_int && zero_dz_slope && zero_dz_threshold_int && zero_dz_threshold } pub fn el_type(&self) -> DoviELType { if self.is_mel() { DoviELType::MEL } else { DoviELType::FEL } } } impl DoviELType { pub const fn as_str(&self) -> &'static str { match self { DoviELType::MEL => MEL_STR, DoviELType::FEL => FEL_STR, } } } impl Display for DoviELType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) } } dolby_vision-3.3.1/src/rpu/utils.rs000064400000000000000000000075131046102023000154410ustar 00000000000000use std::{ fs::File, io::{BufReader, Read}, path::Path, }; use anyhow::{bail, Result}; use super::dovi_rpu::DoviRpu; pub fn parse_rpu_file>(input: P) -> Result> { let rpu_file = File::open(input)?; let metadata = rpu_file.metadata()?; let file_size_bytes = metadata.len() as usize; let mut reader = BufReader::new(rpu_file); let chunk_size = 100_000; let mut main_buf = vec![0; chunk_size]; let mut chunk = Vec::with_capacity(chunk_size); let mut end = Vec::with_capacity(chunk_size); let mut offsets_count = 0; // Estimate RPU count from file size let mut rpus: Vec = Vec::with_capacity(chunk_size / 400); let mut warning_error = None; while let Ok(n) = reader.read(&mut main_buf) { let read_bytes = n; if read_bytes == 0 && end.is_empty() && chunk.is_empty() { break; } if read_bytes < chunk_size { chunk.extend_from_slice(&main_buf[..read_bytes]); } else { chunk.extend_from_slice(&main_buf); } let mut offsets: Vec = chunk .windows(4) .enumerate() .filter_map(|(i, chunk)| { if matches!(chunk, &[0, 0, 0, 1]) { Some(i) } else { None } }) .collect(); if offsets.is_empty() { bail!("No NALU start codes found in chunk. Maybe not a valid RPU?"); } let last = if read_bytes < chunk_size { *offsets.last().unwrap() } else { let last = offsets.pop().unwrap(); end.clear(); end.extend_from_slice(&chunk[last..]); last }; let count = offsets.len(); let parsed_rpus_iter = offsets .iter() .enumerate() .map(|(index, offset)| { let size = if offset == &last { chunk.len() - offset } else { let size = if index == count - 1 { last - offset } else { offsets[index + 1] - offset }; match &chunk[offset + size - 1..offset + size + 3] { [0, 0, 0, 1] => size - 1, _ => size, } }; let start = *offset; let end = start + size; DoviRpu::parse_unspec62_nalu(&chunk[start..end]) }) .enumerate() .filter_map(|(i, res)| { if let Err(e) = &res { if warning_error.is_none() { warning_error = Some(format!("Found invalid RPU: Index {i}, error: {e}")) } } res.ok() }); rpus.extend(parsed_rpus_iter); if warning_error.is_some() { offsets_count += count; break; } else if rpus.is_empty() { bail!("No valid RPUs parsed for chunk, assuming invalid RPU file."); } if offsets_count == 0 && file_size_bytes > chunk_size { rpus.reserve((metadata.len() as usize - chunk_size) / 400); } offsets_count += count; chunk.clear(); if !end.is_empty() { chunk.extend_from_slice(&end); end.clear() } } if offsets_count > 0 && rpus.len() == offsets_count { Ok(rpus) } else if offsets_count == 0 { bail!("No RPU found"); } else if let Some(error) = warning_error { bail!("{}", error); } else { bail!( "Number of valid RPUs different from total: expected {} got {}", offsets_count, rpus.len() ); } } dolby_vision-3.3.1/src/rpu/vdr_dm_data.rs000064400000000000000000000466721046102023000165560ustar 00000000000000use anyhow::{bail, ensure, Result}; use bitvec_helpers::{ bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::extension_metadata::blocks::{ ExtMetadataBlock, ExtMetadataBlockLevel11, ExtMetadataBlockLevel9, }; use super::extension_metadata::*; use super::generate::{GenerateConfig, GenerateProfile}; use super::profiles::profile5::Profile5; use super::profiles::profile81::Profile81; use super::profiles::profile84::Profile84; use super::profiles::DoviProfile; use super::extension_metadata::WithExtMetadataBlocks; use super::rpu_data_header::RpuDataHeader; // 16 bits min for required level 254 + CRC32 + 0x80 const DM_DATA_PAYLOAD2_MIN_BITS: u64 = 56; #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct VdrDmData { pub compressed: bool, pub affected_dm_metadata_id: u64, pub current_dm_metadata_id: u64, pub scene_refresh_flag: u64, pub ycc_to_rgb_coef0: i16, pub ycc_to_rgb_coef1: i16, pub ycc_to_rgb_coef2: i16, pub ycc_to_rgb_coef3: i16, pub ycc_to_rgb_coef4: i16, pub ycc_to_rgb_coef5: i16, pub ycc_to_rgb_coef6: i16, pub ycc_to_rgb_coef7: i16, pub ycc_to_rgb_coef8: i16, pub ycc_to_rgb_offset0: u32, pub ycc_to_rgb_offset1: u32, pub ycc_to_rgb_offset2: u32, pub rgb_to_lms_coef0: i16, pub rgb_to_lms_coef1: i16, pub rgb_to_lms_coef2: i16, pub rgb_to_lms_coef3: i16, pub rgb_to_lms_coef4: i16, pub rgb_to_lms_coef5: i16, pub rgb_to_lms_coef6: i16, pub rgb_to_lms_coef7: i16, pub rgb_to_lms_coef8: i16, pub signal_eotf: u16, pub signal_eotf_param0: u16, pub signal_eotf_param1: u16, pub signal_eotf_param2: u32, pub signal_bit_depth: u8, pub signal_color_space: u8, pub signal_chroma_format: u8, pub signal_full_range_flag: u8, pub source_min_pq: u16, pub source_max_pq: u16, pub source_diagonal: u16, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub cmv29_metadata: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub cmv40_metadata: Option, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum CmVersion { V29, V40, } pub(crate) fn vdr_dm_data_payload( reader: &mut BsIoSliceReader, header: &RpuDataHeader, ) -> Result { let compressed_dm_data = header.reserved_zero_3bits == 1; let mut vdr_dm_data = if compressed_dm_data { VdrDmData { compressed: true, affected_dm_metadata_id: reader.get_ue()?, current_dm_metadata_id: reader.get_ue()?, scene_refresh_flag: reader.get_ue()?, ..Default::default() } } else { VdrDmData::parse(reader)? }; if let Some(cmv29_dm_data) = DmData::parse::(reader)? { vdr_dm_data.cmv29_metadata = Some(DmData::V29(cmv29_dm_data)); } if reader.available()? >= DM_DATA_PAYLOAD2_MIN_BITS { if let Some(cmv40_dm_data) = DmData::parse::(reader)? { vdr_dm_data.cmv40_metadata = Some(DmData::V40(cmv40_dm_data)); } } Ok(vdr_dm_data) } impl VdrDmData { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { let data = VdrDmData { affected_dm_metadata_id: reader.get_ue()?, current_dm_metadata_id: reader.get_ue()?, scene_refresh_flag: reader.get_ue()?, ycc_to_rgb_coef0: reader.get_n::(16)? as i16, ycc_to_rgb_coef1: reader.get_n::(16)? as i16, ycc_to_rgb_coef2: reader.get_n::(16)? as i16, ycc_to_rgb_coef3: reader.get_n::(16)? as i16, ycc_to_rgb_coef4: reader.get_n::(16)? as i16, ycc_to_rgb_coef5: reader.get_n::(16)? as i16, ycc_to_rgb_coef6: reader.get_n::(16)? as i16, ycc_to_rgb_coef7: reader.get_n::(16)? as i16, ycc_to_rgb_coef8: reader.get_n::(16)? as i16, ycc_to_rgb_offset0: reader.get_n(32)?, ycc_to_rgb_offset1: reader.get_n(32)?, ycc_to_rgb_offset2: reader.get_n(32)?, rgb_to_lms_coef0: reader.get_n::(16)? as i16, rgb_to_lms_coef1: reader.get_n::(16)? as i16, rgb_to_lms_coef2: reader.get_n::(16)? as i16, rgb_to_lms_coef3: reader.get_n::(16)? as i16, rgb_to_lms_coef4: reader.get_n::(16)? as i16, rgb_to_lms_coef5: reader.get_n::(16)? as i16, rgb_to_lms_coef6: reader.get_n::(16)? as i16, rgb_to_lms_coef7: reader.get_n::(16)? as i16, rgb_to_lms_coef8: reader.get_n::(16)? as i16, signal_eotf: reader.get_n(16)?, signal_eotf_param0: reader.get_n(16)?, signal_eotf_param1: reader.get_n(16)?, signal_eotf_param2: reader.get_n(32)?, signal_bit_depth: reader.get_n(5)?, signal_color_space: reader.get_n(2)?, signal_chroma_format: reader.get_n(2)?, signal_full_range_flag: reader.get_n(2)?, source_min_pq: reader.get_n(12)?, source_max_pq: reader.get_n(12)?, source_diagonal: reader.get_n(10)?, ..Default::default() }; Ok(data) } pub fn validate(&self) -> Result<()> { ensure!( self.affected_dm_metadata_id <= 15, "affected_dm_metadata_id should be <= 15" ); // FIXME: Compressed DM metadata, should be set from a state somehow if !self.compressed { ensure!( self.signal_bit_depth >= 8 && self.signal_bit_depth <= 16, "signal_bit_depth should be between 8 and 16" ); if self.signal_eotf_param0 == 0 && self.signal_eotf_param1 == 0 && self.signal_eotf_param2 == 0 { ensure!(self.signal_eotf == 65535, "signal_eotf should be 65535"); } } if let Some(cmv29) = &self.cmv29_metadata { cmv29.validate()?; } if let Some(cmv40) = &self.cmv40_metadata { cmv40.validate()?; } Ok(()) } pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { writer.write_ue(&self.affected_dm_metadata_id)?; writer.write_ue(&self.current_dm_metadata_id)?; writer.write_ue(&self.scene_refresh_flag)?; if !self.compressed { writer.write_signed_n(&self.ycc_to_rgb_coef0, 16)?; writer.write_signed_n(&self.ycc_to_rgb_coef1, 16)?; writer.write_signed_n(&self.ycc_to_rgb_coef2, 16)?; writer.write_signed_n(&self.ycc_to_rgb_coef3, 16)?; writer.write_signed_n(&self.ycc_to_rgb_coef4, 16)?; writer.write_signed_n(&self.ycc_to_rgb_coef5, 16)?; writer.write_signed_n(&self.ycc_to_rgb_coef6, 16)?; writer.write_signed_n(&self.ycc_to_rgb_coef7, 16)?; writer.write_signed_n(&self.ycc_to_rgb_coef8, 16)?; writer.write_n(&self.ycc_to_rgb_offset0, 32)?; writer.write_n(&self.ycc_to_rgb_offset1, 32)?; writer.write_n(&self.ycc_to_rgb_offset2, 32)?; writer.write_signed_n(&self.rgb_to_lms_coef0, 16)?; writer.write_signed_n(&self.rgb_to_lms_coef1, 16)?; writer.write_signed_n(&self.rgb_to_lms_coef2, 16)?; writer.write_signed_n(&self.rgb_to_lms_coef3, 16)?; writer.write_signed_n(&self.rgb_to_lms_coef4, 16)?; writer.write_signed_n(&self.rgb_to_lms_coef5, 16)?; writer.write_signed_n(&self.rgb_to_lms_coef6, 16)?; writer.write_signed_n(&self.rgb_to_lms_coef7, 16)?; writer.write_signed_n(&self.rgb_to_lms_coef8, 16)?; writer.write_n(&self.signal_eotf, 16)?; writer.write_n(&self.signal_eotf_param0, 16)?; writer.write_n(&self.signal_eotf_param1, 16)?; writer.write_n(&self.signal_eotf_param2, 32)?; writer.write_n(&self.signal_bit_depth, 5)?; writer.write_n(&self.signal_color_space, 2)?; writer.write_n(&self.signal_chroma_format, 2)?; writer.write_n(&self.signal_full_range_flag, 2)?; writer.write_n(&self.source_min_pq, 12)?; writer.write_n(&self.source_max_pq, 12)?; writer.write_n(&self.source_diagonal, 10)?; } if let Some(cmv29) = &self.cmv29_metadata { cmv29.write(writer)?; } if let Some(cmv40) = &self.cmv40_metadata { cmv40.write(writer)?; } Ok(()) } pub fn with_cmv29_dm_data(mut self) -> Self { self.cmv29_metadata = Some(DmData::V29(CmV29DmData::default())); self } pub fn extension_metadata_for_level(&self, level: u8) -> Option<&DmData> { if CmV29DmData::ALLOWED_BLOCK_LEVELS.contains(&level) { return self.cmv29_metadata.as_ref(); } else if CmV40DmData::ALLOWED_BLOCK_LEVELS.contains(&level) { return self.cmv40_metadata.as_ref(); } None } pub fn extension_metadata_for_level_mut(&mut self, level: u8) -> Option<&mut DmData> { if CmV29DmData::ALLOWED_BLOCK_LEVELS.contains(&level) { return self.cmv29_metadata.as_mut(); } else if CmV40DmData::ALLOWED_BLOCK_LEVELS.contains(&level) { return self.cmv40_metadata.as_mut(); } None } pub fn metadata_blocks(&self, level: u8) -> Option<&Vec> { self.extension_metadata_for_level(level) .map(|dm_data| match dm_data { DmData::V29(meta) => meta.blocks_ref(), DmData::V40(meta) => meta.blocks_ref(), }) } pub fn metadata_blocks_mut(&mut self, level: u8) -> Option<&mut Vec> { self.extension_metadata_for_level_mut(level) .map(|dm_data| match dm_data { DmData::V29(meta) => meta.blocks_mut(), DmData::V40(meta) => meta.blocks_mut(), }) } pub fn level_blocks_iter(&self, level: u8) -> impl Iterator { self.metadata_blocks(level) .into_iter() .flat_map(|e| e.iter()) .filter(move |e| e.level() == level) } pub fn level_blocks_iter_mut( &mut self, level: u8, ) -> impl Iterator { self.metadata_blocks_mut(level) .into_iter() .flat_map(|e| e.iter_mut()) .filter(move |e| e.level() == level) } pub fn get_block(&self, level: u8) -> Option<&ExtMetadataBlock> { self.level_blocks_iter(level).next() } pub fn get_block_mut(&mut self, level: u8) -> Option<&mut ExtMetadataBlock> { self.level_blocks_iter_mut(level).next() } pub fn add_metadata_block(&mut self, block: ExtMetadataBlock) -> Result<()> { let level = block.level(); if let Some(dm_data) = self.extension_metadata_for_level_mut(level) { match dm_data { DmData::V29(meta) => meta.add_block(block)?, DmData::V40(meta) => meta.add_block(block)?, } } Ok(()) } pub fn remove_metadata_level(&mut self, level: u8) { if let Some(dm_data) = self.extension_metadata_for_level_mut(level) { match dm_data { DmData::V29(meta) => meta.remove_level(level), DmData::V40(meta) => meta.remove_level(level), } } } pub fn replace_metadata_level(&mut self, block: ExtMetadataBlock) -> Result<()> { let level = block.level(); self.remove_metadata_level(level); self.add_metadata_block(block)?; Ok(()) } pub fn replace_metadata_block(&mut self, block: ExtMetadataBlock) -> Result<()> { let level = block.level(); match &block { ExtMetadataBlock::Level1(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level2(level2) => { if let Some(dm_data) = self.extension_metadata_for_level_mut(level) { match dm_data { DmData::V29(cmv29) => cmv29.replace_level2_block(level2), _ => unreachable!(), }; Ok(()) } else { bail!("Cannot replace L2 metadata, no CM v4.0 DM data") } } ExtMetadataBlock::Level3(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level4(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level5(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level6(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level8(level8) => { if let Some(dm_data) = self.extension_metadata_for_level_mut(level) { match dm_data { DmData::V40(cmv40) => cmv40.replace_level8_block(level8), _ => unreachable!(), }; Ok(()) } else { bail!("Cannot replace L8 metadata, no CM v4.0 DM data") } } ExtMetadataBlock::Level9(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level10(level10) => { if let Some(dm_data) = self.extension_metadata_for_level_mut(level) { match dm_data { DmData::V40(cmv40) => cmv40.replace_level10_block(level10), _ => unreachable!(), }; Ok(()) } else { bail!("Cannot replace L10 metadata, no CM v4.0 DM data") } } ExtMetadataBlock::Level11(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level254(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level255(_) => self.replace_metadata_level(block), ExtMetadataBlock::Reserved(_) => bail!("Cannot replace specific reserved block"), } } /// Clones every block to replace pub fn replace_metadata_blocks<'a, I>(&mut self, blocks: I) -> Result<()> where I: Iterator, { for block in blocks { self.replace_metadata_block(block.clone())?; } Ok(()) } pub fn set_p81_coeffs(&mut self) { self.ycc_to_rgb_coef0 = 9574; self.ycc_to_rgb_coef1 = 0; self.ycc_to_rgb_coef2 = 13802; self.ycc_to_rgb_coef3 = 9574; self.ycc_to_rgb_coef4 = -1540; self.ycc_to_rgb_coef5 = -5348; self.ycc_to_rgb_coef6 = 9574; self.ycc_to_rgb_coef7 = 17610; self.ycc_to_rgb_coef8 = 0; self.ycc_to_rgb_offset0 = 16777216; self.ycc_to_rgb_offset1 = 134217728; self.ycc_to_rgb_offset2 = 134217728; self.rgb_to_lms_coef0 = 7222; self.rgb_to_lms_coef1 = 8771; self.rgb_to_lms_coef2 = 390; self.rgb_to_lms_coef3 = 2654; self.rgb_to_lms_coef4 = 12430; self.rgb_to_lms_coef5 = 1300; self.rgb_to_lms_coef6 = 0; self.rgb_to_lms_coef7 = 422; self.rgb_to_lms_coef8 = 15962; self.signal_color_space = 0; } // Source PQ means the mastering display // MDL 1000,1-10 = 7,3079 // MDL 4000,50 = 62,3696 pub fn change_source_levels(&mut self, min_pq: Option, max_pq: Option) { if let Some(v) = min_pq { self.source_min_pq = v; } if let Some(v) = max_pq { self.source_max_pq = v; } if let Some(ExtMetadataBlock::Level6(level6_block)) = self.get_block(6) { let (derived_min_pq, derived_max_pq) = level6_block.source_meta_from_l6(); if min_pq.is_none() && self.source_min_pq == 0 { self.source_min_pq = derived_min_pq; } if max_pq.is_none() && self.source_max_pq == 0 { self.source_max_pq = derived_max_pq; } } } pub fn set_scene_cut(&mut self, is_scene_cut: bool) { self.scene_refresh_flag = is_scene_cut as u64; } pub fn default_pq() -> VdrDmData { VdrDmData { signal_eotf: 65535, signal_bit_depth: 12, signal_full_range_flag: 1, source_diagonal: 42, ..Default::default() } } /// Sets static metadata (L5/L6/L11) and source levels pub fn from_generate_config(config: &GenerateConfig) -> Result { let mut vdr_dm_data = match config.profile { GenerateProfile::Profile5 => Profile5::dm_data(), GenerateProfile::Profile81 => Profile81::dm_data(), GenerateProfile::Profile84 => Profile84::dm_data(), } .with_cmv29_dm_data(); if config.cm_version == CmVersion::V40 { vdr_dm_data.cmv40_metadata = if let Some(level254) = &config.level254 { Some(DmData::V40(CmV40DmData::new_with_custom_l254(level254))) } else { Some(DmData::V40(CmV40DmData::new_with_l254_402())) } } vdr_dm_data.set_static_metadata(config)?; vdr_dm_data.change_source_levels(config.source_min_pq, config.source_max_pq); Ok(vdr_dm_data) } pub fn set_static_metadata(&mut self, config: &GenerateConfig) -> Result<()> { self.replace_metadata_block(ExtMetadataBlock::Level5(config.level5.clone()))?; if let Some(level6) = &config.level6 { self.replace_metadata_block(ExtMetadataBlock::Level6(level6.clone()))?; } // Default to inserting both L9 (required) and L11 metadata self.replace_metadata_block(ExtMetadataBlock::Level9( ExtMetadataBlockLevel9::default_dci_p3(), ))?; self.replace_metadata_block(ExtMetadataBlock::Level11( ExtMetadataBlockLevel11::default_reference_cinema(), ))?; if !config.default_metadata_blocks.is_empty() { const LEVEL_BLOCK_LIST: &[u8] = &[5, 6]; let allowed_default_blocks = config .default_metadata_blocks .iter() .filter(|block| !LEVEL_BLOCK_LIST.contains(&block.level())); for block in allowed_default_blocks { self.replace_metadata_block(block.clone())?; } } Ok(()) } } impl CmVersion { pub fn v29() -> Self { CmVersion::V29 } pub fn v40() -> Self { CmVersion::V40 } } #[cfg(test)] mod tests { use crate::rpu::extension_metadata::blocks::{ExtMetadataBlock, ExtMetadataBlockLevel6}; use super::VdrDmData; #[test] fn change_source_levels_with_zero() { let mut vdr_dm_data = VdrDmData::default_pq().with_cmv29_dm_data(); vdr_dm_data .add_metadata_block(ExtMetadataBlock::Level6(ExtMetadataBlockLevel6 { max_display_mastering_luminance: 1000, min_display_mastering_luminance: 1, max_content_light_level: 1000, max_frame_average_light_level: 400, })) .unwrap(); vdr_dm_data.change_source_levels(Some(0), Some(1000)); assert_eq!(vdr_dm_data.source_min_pq, 0); assert_eq!(vdr_dm_data.source_max_pq, 1000); } } dolby_vision-3.3.1/src/st2094_10/itu_t35/cm_data.rs000064400000000000000000000154451046102023000176270ustar 00000000000000use anyhow::Result; use bitvec_helpers::bitstream_io_reader::BsIoSliceReader; use super::UserDataTypeStruct; use crate::rpu::NUM_COMPONENTS; #[derive(Default, Debug)] pub struct ST2094_10CmData { pub ccm_profile: u8, pub ccm_level: u8, pub coefficient_log2_denom: u64, pub bl_bit_depth_minus8: u64, pub el_bit_depth_minus8: u64, pub hdr_bit_depth_minus8: u64, pub disable_residual_flag: bool, pub num_pivots_minus2: [u64; NUM_COMPONENTS], pub pred_pivot_value: [Vec; NUM_COMPONENTS], pub mapping_idc: [Vec; NUM_COMPONENTS], pub poly_order_minus1: [Vec; NUM_COMPONENTS], pub poly_coef_int: [Vec>; NUM_COMPONENTS], pub poly_coef: [Vec>; NUM_COMPONENTS], pub mmr_order_minus1: [Vec; NUM_COMPONENTS], pub mmr_constant_int: [Vec; NUM_COMPONENTS], pub mmr_constant: [Vec; NUM_COMPONENTS], pub mmr_coef_int: [Vec>>; NUM_COMPONENTS], pub mmr_coef: [Vec>>; NUM_COMPONENTS], pub nlq_offset: [u64; NUM_COMPONENTS], pub hdr_in_max_int: [u64; NUM_COMPONENTS], pub hdr_in_max: [u64; NUM_COMPONENTS], pub linear_deadzone_slope_int: [u64; NUM_COMPONENTS], pub linear_deadzone_slope: [u64; NUM_COMPONENTS], pub linear_deadzone_threshold_int: [u64; NUM_COMPONENTS], pub linear_deadzone_threshold: [u64; NUM_COMPONENTS], } impl ST2094_10CmData { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { let mut meta = ST2094_10CmData { ccm_profile: reader.get_n(4)?, ccm_level: reader.get_n(4)?, coefficient_log2_denom: reader.get_ue()?, bl_bit_depth_minus8: reader.get_ue()?, el_bit_depth_minus8: reader.get_ue()?, hdr_bit_depth_minus8: reader.get_ue()?, disable_residual_flag: reader.get()?, ..Default::default() }; let coefficient_log2_denom_length = meta.coefficient_log2_denom as u32; for cmp in 0..NUM_COMPONENTS { meta.num_pivots_minus2[cmp] = reader.get_ue()?; meta.pred_pivot_value[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 2, Default::default); for pivot_idx in 0..(meta.num_pivots_minus2[cmp] as usize) + 2 { meta.pred_pivot_value[cmp][pivot_idx] = reader.get_n((meta.el_bit_depth_minus8 as u32) + 8)?; } } for cmp in 0..NUM_COMPONENTS { meta.mapping_idc[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 1, Default::default); meta.poly_order_minus1[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 1, Default::default); meta.poly_coef_int[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 1, Default::default); meta.poly_coef[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 1, Default::default); meta.mmr_order_minus1[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 1, Default::default); meta.mmr_constant_int[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 1, Default::default); meta.mmr_constant[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 1, Default::default); meta.mmr_coef_int[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 1, Default::default); meta.mmr_coef[cmp] .resize_with((meta.num_pivots_minus2[cmp] as usize) + 1, Default::default); for pivot_idx in 0..(meta.num_pivots_minus2[cmp] as usize) + 1 { meta.mapping_idc[cmp][pivot_idx] = reader.get_ue()?; // MAPPING_POLYNOMIAL if meta.mapping_idc[cmp][pivot_idx] == 0 { meta.poly_order_minus1[cmp][pivot_idx] = reader.get_ue()?; meta.poly_coef_int[cmp][pivot_idx].resize_with( (meta.poly_order_minus1[cmp][pivot_idx] as usize) + 2, Default::default, ); meta.poly_coef[cmp][pivot_idx].resize_with( (meta.poly_order_minus1[cmp][pivot_idx] as usize) + 2, Default::default, ); for i in 0..=(meta.poly_order_minus1[cmp][pivot_idx] as usize) + 1 { meta.poly_coef_int[cmp][pivot_idx][i] = reader.get_se()?; meta.poly_coef[cmp][pivot_idx][i] = reader.get_n(coefficient_log2_denom_length)?; } } else if meta.mapping_idc[cmp][pivot_idx] == 1 { // MAPPING_MMR meta.mmr_order_minus1[cmp][pivot_idx] = reader.get_n(2)?; meta.mmr_constant_int[cmp][pivot_idx] = reader.get_se()?; meta.mmr_constant[cmp][pivot_idx] = reader.get_n(coefficient_log2_denom_length)?; meta.mmr_coef_int[cmp][pivot_idx].resize_with( (meta.mmr_order_minus1[cmp][pivot_idx] as usize) + 2, Default::default, ); meta.mmr_coef[cmp][pivot_idx].resize_with( (meta.mmr_order_minus1[cmp][pivot_idx] as usize) + 2, Default::default, ); for i in 1..=(meta.mmr_order_minus1[cmp][pivot_idx] as usize) + 1 { meta.mmr_coef_int[cmp][pivot_idx][i].resize_with(8, Default::default); meta.mmr_coef[cmp][pivot_idx][i].resize_with(8, Default::default); for j in 0..7_usize { meta.mmr_coef_int[cmp][pivot_idx][i][j] = reader.get_se()?; meta.mmr_coef[cmp][pivot_idx][i][j] = reader.get_n(coefficient_log2_denom_length)?; } } } } } if !meta.disable_residual_flag { for cmp in 0..NUM_COMPONENTS { meta.nlq_offset[cmp] = reader.get_n((meta.el_bit_depth_minus8 as u32) + 8)?; meta.hdr_in_max_int[cmp] = reader.get_ue()?; meta.hdr_in_max[cmp] = reader.get_n(coefficient_log2_denom_length)?; meta.linear_deadzone_slope_int[cmp] = reader.get_ue()?; meta.linear_deadzone_slope[cmp] = reader.get_n(coefficient_log2_denom_length)?; meta.linear_deadzone_threshold_int[cmp] = reader.get_ue()?; meta.linear_deadzone_threshold[cmp] = reader.get_n(coefficient_log2_denom_length)?; } } Ok(UserDataTypeStruct::CMData(Box::new(meta))) } } dolby_vision-3.3.1/src/st2094_10/itu_t35/dm_data.rs000064400000000000000000000015371046102023000176250ustar 00000000000000use anyhow::Result; use bitvec_helpers::bitstream_io_reader::BsIoSliceReader; use crate::rpu::extension_metadata::{CmV29DmData, DmData}; use super::UserDataTypeStruct; #[derive(Default, Debug)] pub struct ST2094_10DmData { pub app_identifier: u64, pub app_version: u64, pub metadata_refresh_flag: bool, pub dm_data: Option, } impl ST2094_10DmData { pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { let mut meta = ST2094_10DmData { app_identifier: reader.get_ue()?, app_version: reader.get_ue()?, metadata_refresh_flag: reader.get()?, ..Default::default() }; if meta.metadata_refresh_flag { meta.dm_data = DmData::parse::(reader)?; } Ok(UserDataTypeStruct::DMData(meta)) } } dolby_vision-3.3.1/src/st2094_10/itu_t35/mod.rs000064400000000000000000000041211046102023000170030ustar 00000000000000use anyhow::{bail, ensure, Result}; use bitvec_helpers::bitstream_io_reader::BsIoSliceReader; use crate::utils::clear_start_code_emulation_prevention_3_byte; mod cm_data; mod dm_data; use cm_data::ST2094_10CmData; use dm_data::ST2094_10DmData; /// ITU T.35 SEI version of ST2094-10 metadata #[derive(Debug)] pub struct ST2094_10ItuT35 { pub user_data_type_struct: UserDataTypeStruct, } #[derive(Debug)] pub enum UserDataTypeStruct { DMData(ST2094_10DmData), CMData(Box), } impl ST2094_10ItuT35 { /// Implementation of https://dashif-documents.azurewebsites.net/DASH-IF-IOP/master/DASH-IF-IOP.html#codecs-dolbyvision pub fn parse_itu_t35_dashif(data: &[u8]) -> Result { let trimmed_data = Self::validated_trimmed_data(data)?; let bytes = clear_start_code_emulation_prevention_3_byte(trimmed_data); let mut reader = BsIoSliceReader::from_slice(&bytes); let itu_t_t35_country_code: u8 = reader.get_n(8)?; let itu_t_t35_provider_code: u16 = reader.get_n(16)?; ensure!(itu_t_t35_country_code == 0xB5); ensure!(itu_t_t35_provider_code == 0x31); let user_identifier: u32 = reader.get_n(32)?; ensure!( user_identifier == 0x47413934, "invalid user_identifier: {}", user_identifier ); let user_data_type_code: u8 = reader.get_n(8)?; let meta = match user_data_type_code { 0x08 => ST2094_10CmData::parse(&mut reader)?, 0x09 => ST2094_10DmData::parse(&mut reader)?, _ => bail!("Invalid user_data_type_code: {}", user_data_type_code), }; Ok(ST2094_10ItuT35 { user_data_type_struct: meta, }) } pub fn validated_trimmed_data(data: &[u8]) -> Result<&[u8]> { let trimmed_data = match &data[..7] { [0x4E, 0x01, 0x04, _, 0xB5, 0x00, 0x31] => &data[4..], [0xB5, 0x00, 0x31, 0x47, 0x41, 0x39, 0x34] => data, _ => bail!("Invalid St2094-10 T-T35 SEI start bytes\n{:?}", &data[..7]), }; Ok(trimmed_data) } } dolby_vision-3.3.1/src/st2094_10/mod.rs000064400000000000000000000000211046102023000155020ustar 00000000000000pub mod itu_t35; dolby_vision-3.3.1/src/utils.rs000064400000000000000000000051571046102023000146350ustar 00000000000000#[cfg(feature = "serde")] use { bitvec::prelude::*, serde::{ser::Serializer, Serialize}, }; pub const ST2084_Y_MAX: f64 = 10000.0; pub const ST2084_M1: f64 = 2610.0 / 16384.0; pub const ST2084_M2: f64 = (2523.0 / 4096.0) * 128.0; pub const ST2084_C1: f64 = 3424.0 / 4096.0; pub const ST2084_C2: f64 = (2413.0 / 4096.0) * 32.0; pub const ST2084_C3: f64 = (2392.0 / 4096.0) * 32.0; #[inline(always)] pub fn pq_to_nits(x: f64) -> f64 { if x > 0.0 { let xpow = x.powf(1.0 / ST2084_M2); let num = (xpow - ST2084_C1).max(0.0); let den = (ST2084_C2 - ST2084_C3 * xpow).max(f64::NEG_INFINITY); (num / den).powf(1.0 / ST2084_M1) * ST2084_Y_MAX } else { 0.0 } } /// Helper function to calculate PQ codes from nits (cd/m2) values #[inline(always)] pub fn nits_to_pq(nits: f64) -> f64 { let y = nits / ST2084_Y_MAX; ((ST2084_C1 + ST2084_C2 * y.powf(ST2084_M1)) / (1.0 + ST2084_C3 * y.powf(ST2084_M1))) .powf(ST2084_M2) } /// Copied from hevc_parser for convenience, and to avoid a dependency /// Unescapes a byte slice from annexb. /// Allocates a new Vec. pub fn clear_start_code_emulation_prevention_3_byte(data: &[u8]) -> Vec { let len = data.len(); if len > 2 { let mut unescaped_bytes: Vec = Vec::with_capacity(len); unescaped_bytes.push(data[0]); unescaped_bytes.push(data[1]); for i in 2..len { if !(data[i - 2] == 0 && data[i - 1] == 0 && data[i] == 3) { unescaped_bytes.push(data[i]); } } unescaped_bytes } else { data.to_owned() } } /// Escapes the vec to annexb to avoid emulating a start code by accident pub fn add_start_code_emulation_prevention_3_byte(data: &mut Vec) { let mut count = data.len(); let mut i = 0; while i < count { if i > 2 && data[i - 2] == 0 && data[i - 1] == 0 && data[i] <= 3 { data.insert(i, 3); count += 1; } i += 1; } } /// Serializing a bitvec as a vec of bits #[cfg(feature = "serde")] pub(crate) fn bitvec_ser_bits( bitvec: &BitVec, s: S, ) -> Result { let bits: Vec = bitvec.iter().map(|b| *b as u8).collect(); bits.serialize(s) } /// Serializing an optional bitvec as a vec of bits #[cfg(feature = "serde")] pub(crate) fn opt_bitvec_ser_bits( bitvec: &Option>, s: S, ) -> Result { let bits: Vec = if let Some(vec) = bitvec { vec.iter().map(|b| *b as u8).collect() } else { Vec::new() }; bits.serialize(s) } dolby_vision-3.3.1/src/xml/mod.rs000064400000000000000000000001541046102023000150440ustar 00000000000000/// XML metadata parser mod parser; #[cfg(test)] mod tests; pub use parser::{CmXmlParser, XmlParserOpts}; dolby_vision-3.3.1/src/xml/parser.rs000064400000000000000000000776061046102023000156010ustar 00000000000000use anyhow::{bail, ensure, Result}; use roxmltree::{Document, Node}; use std::cmp::min; use std::collections::HashMap; use std::convert::TryInto; use std::fs::File; use std::io::Read; use std::path::Path; use crate::rpu::extension_metadata::{blocks::*, primaries}; use crate::rpu::generate::{GenerateConfig, ShotFrameEdit, VideoShot}; use crate::rpu::vdr_dm_data::CmVersion; use crate::utils::nits_to_pq; use level10::PRESET_TARGET_DISPLAYS; use primaries::ColorPrimaries; #[derive(Default, Debug)] pub struct CmXmlParser { opts: XmlParserOpts, xml_version: u16, separator: char, pub target_displays: HashMap, pub config: GenerateConfig, } #[derive(Default, Debug)] pub struct XmlParserOpts { pub canvas_width: Option, pub canvas_height: Option, } #[derive(Default, Debug)] pub struct TargetDisplay { id: String, peak_nits: u16, min_nits: f64, primaries: [f64; 8], } impl CmXmlParser { pub fn parse_file>(file_path: P, opts: XmlParserOpts) -> Result { let mut s = String::new(); File::open(file_path)?.read_to_string(&mut s)?; Self::new(s, opts) } pub fn new(s: String, opts: XmlParserOpts) -> Result { let mut parser = CmXmlParser { opts, ..Default::default() }; let doc = roxmltree::Document::parse(&s).unwrap(); parser.xml_version = parser.parse_xml_version(&doc)?; parser.separator = if parser.is_cmv4() { ' ' } else { ',' }; // Override version if !parser.is_cmv4() { parser.config.cm_version = CmVersion::V29; } if let Some(output) = doc.descendants().find(|e| e.has_tag_name("Output")) { parser.parse_global_level5(&output)?; if let Some(video) = output.descendants().find(|e| e.has_tag_name("Video")) { let (max_frame_average_light_level, max_content_light_level) = parser.parse_level6(&video); let (min_display_mastering_luminance, max_display_mastering_luminance) = parser.parse_mastering_display_metadata(&video); let source_min_pq = (nits_to_pq(min_display_mastering_luminance as f64 / 10000.0) * 4095.0) .round() as u16; let source_max_pq = (nits_to_pq(max_display_mastering_luminance as f64) * 4095.0).round() as u16; parser.config.source_min_pq = Some(source_min_pq); parser.config.source_max_pq = Some(source_max_pq); parser.config.level6 = Some(ExtMetadataBlockLevel6 { max_display_mastering_luminance, min_display_mastering_luminance, max_content_light_level, max_frame_average_light_level, }); parser.config.level254 = parser.parse_level254(&video); parser.add_level11(&video)?; parser.target_displays = parser.parse_target_displays(&video)?; parser.config.shots = parser.parse_shots(&video)?; parser.config.shots.sort_by_key(|s| s.start); // Add default L10 blocks if parser.is_cmv4() { parser.parse_global_level10_targets()?; } parser.config.length = parser.config.shots.iter().map(|s| s.duration).sum(); } else { bail!("Could not find Video node"); } } else { bail!("Could not find Output node"); } Ok(parser) } fn parse_xml_version(&self, doc: &Document) -> Result { if let Some(node) = doc.descendants().find(|e| e.has_tag_name("DolbyLabsMDF")) { let version_attr = node.attribute("version"); let version_node = if let Some(version_node) = node.children().find(|e| e.has_tag_name("Version")) { version_node.text() } else { None }; let version_text = if let Some(v) = version_attr { v } else if let Some(v) = version_node { v } else { bail!("No XML version found!"); }; let version_split: Vec<&str> = version_text.split('.').collect(); let rev = version_split .iter() .rev() .enumerate() .fold(0, |rev, (i, v)| { rev + (v.parse::().unwrap() << (i * 4)) }); if rev >= 0x402 { match rev { 0x402 | 0x500 | 0x510 => {} 0x510.. => println!("Possibly unhandled new XML version {version_text} found! Please open an issue if you get anything wrong."), _ => bail!("invalid XML version {version_text} found!") }; } else { match rev { 0x205 => {} 0x1 | 0x20 | 0x201 | 0x204 => bail!( "Unhandled legacy XML version {version_text} found! Please open an issue." ), _ => bail!("invalid legacy XML version {version_text} found!"), }; } Ok(rev) } else { bail!("Could not find DolbyLabsMDF root node."); } } fn parse_level6(&self, video: &Node) -> (u16, u16) { if let Some(node) = video.descendants().find(|e| e.has_tag_name("Level6")) { let maxfall = if let Some(fall) = node.children().find(|e| e.has_tag_name("MaxFALL")) { fall.text() .map_or(0, |e| e.parse::().unwrap().round() as u16) } else { 0 }; let maxcll = if let Some(cll) = node.children().find(|e| e.has_tag_name("MaxCLL")) { cll.text() .map_or(0, |e| e.parse::().unwrap().round() as u16) } else { 0 }; (maxfall, maxcll) } else { (0, 0) } } fn parse_mastering_display_metadata(&self, video: &Node) -> (u16, u16) { if let Some(node) = video .descendants() .find(|e| e.has_tag_name("MasteringDisplay")) { let min = if let Some(min_brightness) = node .children() .find(|e| e.has_tag_name("MinimumBrightness")) { min_brightness.text().map_or(0, |e| { let v = e.parse::().unwrap(); (v * 10000.0) as u16 }) } else { 0 }; let max = if let Some(max_brightness) = node.children().find(|e| e.has_tag_name("PeakBrightness")) { max_brightness .text() .map_or(0, |e| e.parse::().unwrap()) } else { 0 }; (min, max) } else { (0, 0) } } fn parse_target_displays(&mut self, video: &Node) -> Result> { let mut targets = HashMap::new(); let target_display_nodes = video .descendants() .filter(|e| e.has_tag_name("TargetDisplay")); for target_node in target_display_nodes { let id = target_node .children() .find(|e| e.has_tag_name("ID")) .unwrap() .text() .unwrap() .to_string(); let peak_nits = target_node .children() .find(|e| e.has_tag_name("PeakBrightness")) .unwrap() .text() .unwrap() .parse::() .unwrap(); let min_nits = target_node .children() .find(|e| e.has_tag_name("MinimumBrightness")) .unwrap() .text() .unwrap() .parse::() .unwrap(); let primary_red = target_node .descendants() .find(|e| e.has_tag_name("Red")) .unwrap() .text() .unwrap(); let primary_green = target_node .descendants() .find(|e| e.has_tag_name("Green")) .unwrap() .text() .unwrap(); let primary_blue = target_node .descendants() .find(|e| e.has_tag_name("Blue")) .unwrap() .text() .unwrap(); let primary_white = target_node .children() .find(|e| e.has_tag_name("WhitePoint")) .unwrap() .text() .unwrap(); let primaries: Vec = [primary_red, primary_green, primary_blue, primary_white] .join(&self.separator.to_string()) .split(self.separator) .map(|v| v.parse::().unwrap()) .collect(); ensure!( primaries.len() == 8, "Primaries + WP should be a total of 8 values" ); let include_target = if self.xml_version >= 0x500 { let application_type = target_node .children() .find(|e| e.has_tag_name("ApplicationType")); ensure!( application_type.is_some(), format!("XML v5.0+: Missing ApplicationType for Target display ID {id}") ); let application_type = application_type.unwrap().text().unwrap().to_string(); // Only parse HOME targets application_type == "HOME" } else { true }; if include_target { targets.insert( id.clone(), TargetDisplay { id: id.clone(), peak_nits, min_nits, primaries: primaries.try_into().unwrap(), }, ); } } Ok(targets) } fn parse_level254(&self, video: &Node) -> Option { if let Some(node) = video.descendants().find(|e| e.has_tag_name("Level254")) { let dm_mode = if let Some(dmm) = node.children().find(|e| e.has_tag_name("DMMode")) { dmm.text().map_or(0, |e| e.parse::().unwrap()) } else { 0 }; let dm_version_index = if let Some(dmv) = node.children().find(|e| e.has_tag_name("DMVersion")) { dmv.text().map_or(2, |e| e.parse::().unwrap()) } else { 2 }; Some(ExtMetadataBlockLevel254 { dm_mode, dm_version_index, }) } else { // No L254 in the case of CM v2.9 None } } fn add_level11(&mut self, video: &Node) -> Result<()> { if let Some(node) = video.descendants().find(|e| e.has_tag_name("Level11")) { let content_type: Option = if let Some(content_type_node) = node.children().find(|e| e.has_tag_name("ContentType")) { content_type_node.text().map(|e| e.parse::().unwrap()) } else { None }; let whitepoint: Option = if let Some(wp_node) = node .children() .find(|e| e.has_tag_name("IntendedWhitePoint")) { wp_node.text().map(|e| e.parse::().unwrap()) } else { None }; if let (Some(content_type), Some(whitepoint)) = (content_type, whitepoint) { self.config .default_metadata_blocks .push(ExtMetadataBlock::Level11(ExtMetadataBlockLevel11 { content_type, whitepoint, ..Default::default() })) } } Ok(()) } fn parse_shots(&self, video: &Node) -> Result> { let shots = video .descendants() .filter(|e| e.has_tag_name("Shot")) .map(|n| { let mut shot = VideoShot { id: n .children() .find(|e| e.has_tag_name("UniqueID")) .unwrap() .text() .unwrap() .to_string(), ..Default::default() }; if let Some(record) = n.children().find(|e| e.has_tag_name("Record")) { shot.start = record .children() .find(|e| e.has_tag_name("In")) .unwrap() .text() .unwrap() .parse::() .unwrap(); shot.duration = record .children() .find(|e| e.has_tag_name("Duration")) .unwrap() .text() .unwrap() .parse::() .unwrap(); } shot.metadata_blocks = self.parse_shot_trims(&n)?; let frames = n.children().filter(|e| e.has_tag_name("Frame")); for frame in frames { let edit_offset = frame .children() .find(|e| e.has_tag_name("EditOffset")) .unwrap() .text() .unwrap() .parse::() .unwrap(); shot.frame_edits.push(ShotFrameEdit { edit_offset, metadata_blocks: self.parse_shot_trims(&frame)?, }); } Ok(shot) }) .collect(); shots } fn parse_shot_trims(&self, node: &Node) -> Result> { let mut metadata_blocks = Vec::new(); let dynamic_meta_tag = if self.is_cmv4() { "DVDynamicData" } else { "PluginNode" }; if let Some(defaults_node) = node .descendants() .find(|e| e.has_tag_name(dynamic_meta_tag)) { if self.is_cmv4() { let level_nodes = defaults_node .children() .filter(|e| e.has_attribute("level")); for level_node in level_nodes { let level = level_node.attribute("level").unwrap(); self.parse_trim_levels(&level_node, level, &mut metadata_blocks)?; } } else { let edr_nodes = defaults_node .children() .filter(|e| e.has_tag_name("DolbyEDR") && e.has_attribute("level")); for edr in edr_nodes { let level = edr.attribute("level").unwrap(); self.parse_trim_levels(&edr, level, &mut metadata_blocks)?; } }; } Ok(metadata_blocks) } fn parse_trim_levels( &self, node: &Node, level: &str, metadata_blocks: &mut Vec, ) -> Result<()> { if level == "1" { metadata_blocks.push(ExtMetadataBlock::Level1(self.parse_level1_trim(node)?)); } else if level == "2" { metadata_blocks.push(ExtMetadataBlock::Level2(self.parse_level2_trim(node)?)); } else if level == "3" { metadata_blocks.push(ExtMetadataBlock::Level3(self.parse_level3_trim(node)?)); } else if level == "5" { metadata_blocks.push(ExtMetadataBlock::Level5(self.parse_level5_trim(node)?)); } else if level == "8" { metadata_blocks.push(ExtMetadataBlock::Level8(self.parse_level8_trim(node)?)); } else if level == "9" { metadata_blocks.push(ExtMetadataBlock::Level9(self.parse_level9_trim(node)?)); } Ok(()) } pub fn parse_global_level5(&mut self, output: &Node) -> Result<()> { let canvas_ar = if let Some(canvas_ar) = output .children() .find(|e| e.has_tag_name("CanvasAspectRatio")) { canvas_ar.text().and_then(|v| v.parse::().ok()) } else { None }; let image_ar = if let Some(image_ar) = output .children() .find(|e| e.has_tag_name("ImageAspectRatio")) { image_ar.text().and_then(|v| v.parse::().ok()) } else { None }; if let (Some(c_ar), Some(i_ar)) = (canvas_ar, image_ar) { self.config.level5 = self .calculate_level5_metadata(c_ar, i_ar) .ok() .unwrap_or_default(); } Ok(()) } /// Parse every target display to create L10 metadata if they use custom primaries fn parse_global_level10_targets(&mut self) -> Result<()> { for (id, target) in &self.target_displays { let index = Self::find_primary_index(&target.primaries, false)?; let length = if index == 255 { 21 } else { 5 }; let mut block = ExtMetadataBlockLevel10 { length, target_display_index: target.id.parse::().unwrap(), target_max_pq: min( 4095, (nits_to_pq(target.peak_nits.into()) * 4095.0).round() as u16, ), target_min_pq: min(4095, (nits_to_pq(target.min_nits) * 4095.0).round() as u16), target_primary_index: index, ..Default::default() }; if index == 255 { let color_primaries = ColorPrimaries::from_array_float(&target.primaries); block.set_from_primaries(&color_primaries); } // Only allow custom L10 if !PRESET_TARGET_DISPLAYS.contains(&id.parse::().unwrap()) { self.config .default_metadata_blocks .push(ExtMetadataBlock::Level10(block)); } } Ok(()) } pub fn parse_level1_trim(&self, node: &Node) -> Result { let measurements = node .children() .find(|e| e.has_tag_name("ImageCharacter")) .unwrap() .text() .unwrap(); let measurements: Vec<&str> = measurements.split(self.separator).collect(); ensure!( measurements.len() == 3, "invalid L1 trim: should be 3 values" ); let min_pq = (measurements[0].parse::().unwrap() * 4095.0).round() as u16; let avg_pq = (measurements[1].parse::().unwrap() * 4095.0).round() as u16; let max_pq = (measurements[2].parse::().unwrap() * 4095.0).round() as u16; Ok(ExtMetadataBlockLevel1::from_stats_cm_version( min_pq, max_pq, avg_pq, self.config.cm_version, )) } pub fn parse_level2_trim(&self, node: &Node) -> Result { let target_id = node .children() .find(|e| e.has_tag_name("TID")) .unwrap() .text() .unwrap() .to_string(); let trim = node .children() .find(|e| e.has_tag_name("Trim")) .unwrap() .text() .unwrap(); let trim: Vec<&str> = trim.split(self.separator).collect(); let target_display = self .target_displays .get(&target_id) .expect("No target display found for L2 trim"); ensure!(trim.len() == 9, "invalid L2 trim: should be 9 values"); let trim_lift = trim[3].parse::().unwrap(); let trim_gain = trim[4].parse::().unwrap(); let trim_gamma = trim[5].parse::().unwrap().clamp(-1.0, 1.0); let trim_slope = min( 4095, ((((trim_gain + 2.0) * (1.0 - trim_lift / 2.0) - 2.0) * 2048.0) + 2048.0).round() as u16, ); let trim_offset = min( 4095, ((((trim_gain + 2.0) * (trim_lift / 2.0)) * 2048.0) + 2048.0).round() as u16, ); let trim_power = min( 4095, (((2.0 / (1.0 + trim_gamma / 2.0) - 2.0) * 2048.0) + 2048.0).round() as u16, ); let trim_chroma_weight = min( 4095, ((trim[6].parse::().unwrap() * 2048.0) + 2048.0).round() as u16, ); let trim_saturation_gain = min( 4095, ((trim[7].parse::().unwrap() * 2048.0) + 2048.0).round() as u16, ); let ms_weight = min( 4095, ((trim[8].parse::().unwrap() * 2048.0) + 2048.0).round() as i16, ); Ok(ExtMetadataBlockLevel2 { trim_slope, trim_offset, trim_power, trim_chroma_weight, trim_saturation_gain, ms_weight, ..ExtMetadataBlockLevel2::from_nits(target_display.peak_nits) }) } pub fn parse_level3_trim(&self, node: &Node) -> Result { let measurements = node .children() .find(|e| e.has_tag_name("L1Offset")) .unwrap() .text() .unwrap(); // [min, avg, max] let measurements: Vec<&str> = measurements.split(self.separator).collect(); ensure!( measurements.len() == 3, "invalid L3 trim: should be 3 values" ); Ok(ExtMetadataBlockLevel3 { min_pq_offset: ((measurements[0].parse::().unwrap() * 2048.0) + 2048.0).round() as u16, avg_pq_offset: ((measurements[1].parse::().unwrap() * 2048.0) + 2048.0).round() as u16, max_pq_offset: ((measurements[2].parse::().unwrap() * 2048.0) + 2048.0).round() as u16, }) } pub fn parse_level5_trim(&self, node: &Node) -> Result { let ratios = node .children() .find(|e| e.has_tag_name("AspectRatios")) .unwrap() .text() .unwrap(); let ratios: Vec<&str> = ratios.split(self.separator).collect(); ensure!(ratios.len() == 2, "invalid L5 trim: should be 2 values"); let canvas_ar = ratios[0].parse::().unwrap(); let image_ar = ratios[1].parse::().unwrap(); Ok(self .calculate_level5_metadata(canvas_ar, image_ar) .ok() .unwrap_or_default()) } pub fn parse_level8_trim(&self, node: &Node) -> Result { let target_id = node .children() .find(|e| e.has_tag_name("TID")) .unwrap() .text() .unwrap() .to_string(); let trim = node .children() .find(|e| e.has_tag_name("L8Trim")) .unwrap() .text() .unwrap(); let trim: Vec<&str> = trim.split(self.separator).collect(); let target_display = self .target_displays .get(&target_id) .expect("No target display found for L8 trim"); ensure!(trim.len() == 6, "Invalid L8 trim: should be 6 values"); let trim_lift = trim[0].parse::().unwrap(); let trim_gain = trim[1].parse::().unwrap(); let trim_gamma = trim[2].parse::().unwrap().clamp(-1.0, 1.0); let trim_slope = min( 4095, ((((trim_gain + 2.0) * (1.0 - trim_lift / 2.0) - 2.0) * 2048.0) + 2048.0).round() as u16, ); let trim_offset = min( 4095, ((((trim_gain + 2.0) * (trim_lift / 2.0)) * 2048.0) + 2048.0).round() as u16, ); let trim_power = min( 4095, (((2.0 / (1.0 + trim_gamma / 2.0) - 2.0) * 2048.0) + 2048.0).round() as u16, ); let trim_chroma_weight = min( 4095, ((trim[3].parse::().unwrap() * 2048.0) + 2048.0).round() as u16, ); let trim_saturation_gain = min( 4095, ((trim[4].parse::().unwrap() * 2048.0) + 2048.0).round() as u16, ); let ms_weight = min( 4095, ((trim[5].parse::().unwrap() * 2048.0) + 2048.0).round() as u16, ); let mid_contrast_bias_text = node .children() .find(|e| e.has_tag_name("MidContrastBias")) .unwrap() .text() .unwrap(); let highlight_clipping_text = node .children() .find(|e| e.has_tag_name("HighlightClipping")) .unwrap() .text() .unwrap(); let target_mid_contrast = min( 4095, ((mid_contrast_bias_text.parse::().unwrap() * 2048.0) + 2048.0).round() as u16, ); let clip_trim = min( 4095, ((highlight_clipping_text.parse::().unwrap() * 2048.0) + 2048.0).round() as u16, ); // L8 SaturationVectorField let satvec_text = node .children() .find(|e| e.has_tag_name("SaturationVectorField")) .unwrap() .text() .unwrap(); let satvec: Vec = satvec_text .split(self.separator) .map(|v| { min( 255, ((v.parse::().unwrap() * 128.0) + 128.0).round() as u8, ) }) .collect(); ensure!( satvec.len() == 6, "Invalid L8 SatVectorField: should be 6 values" ); // L8 HueVectorField let huevec_text = node .children() .find(|e| e.has_tag_name("HueVectorField")) .unwrap() .text() .unwrap(); let huevec: Vec = huevec_text .split(self.separator) .map(|v| { min( 255, ((v.parse::().unwrap() * 128.0) + 128.0).round() as u8, ) }) .collect(); ensure!( huevec.len() == 6, "Invalid L8 HueVectorField: should be 6 values" ); // Set variable length according to the metadata // Only write trims which were modified let length = if huevec.iter().any(|v| *v != 128) { 25 } else if satvec.iter().any(|v| *v != 128) { 19 } else if clip_trim != 2048 { 13 } else if target_mid_contrast != 2048 { 12 } else { 10 }; Ok(ExtMetadataBlockLevel8 { length, target_display_index: target_display.id.parse::()?, trim_slope, trim_offset, trim_power, trim_chroma_weight, trim_saturation_gain, ms_weight, target_mid_contrast, clip_trim, saturation_vector_field0: satvec[0], saturation_vector_field1: satvec[1], saturation_vector_field2: satvec[2], saturation_vector_field3: satvec[3], saturation_vector_field4: satvec[4], saturation_vector_field5: satvec[5], hue_vector_field0: huevec[0], hue_vector_field1: huevec[1], hue_vector_field2: huevec[2], hue_vector_field3: huevec[3], hue_vector_field4: huevec[4], hue_vector_field5: huevec[5], }) } fn find_primary_index(primaries: &[f64; 8], check_realdevice: bool) -> Result { // Check PREDEFINED_COLORSPACE_PRIMARIES anyway if check_realdevice { let primary_index = Self::find_primary_index(primaries, false)?; if primary_index < 255 { return Ok(primary_index); } }; let presets = if check_realdevice { level9::PREDEFINED_REALDEVICE_PRIMARIES } else { primaries::PREDEFINED_COLORSPACE_PRIMARIES }; let matching_primaries = presets.iter().enumerate().find(|(_, preset_primaries)| { primaries .iter() .zip(preset_primaries.iter()) .all(|(a, b)| (*a - *b).abs() < f64::EPSILON) }); // Exact match to preset primaries let primary_index = if let Some((primary_index, _)) = matching_primaries { if check_realdevice { primary_index + primaries::PREDEFINED_COLORSPACE_PRIMARIES.len() } else { primary_index } } else { 255 }; Ok(primary_index as u8) } /// Mastering display primaries fn parse_level9_trim(&self, node: &Node) -> Result { let source_color_primary = node .children() .find(|e| e.has_tag_name("SourceColorPrimary")) .unwrap() .text() .unwrap(); let primaries: Vec = source_color_primary .split(self.separator) .map(|v| v.parse::().unwrap()) .collect(); ensure!( primaries.len() == 8, "Invalid L9 SourceColorPrimary: should be 8 values" ); let primaries = primaries.try_into().unwrap(); let index = Self::find_primary_index(&primaries, true)?; let length = if index == 255 { 17 } else { 1 }; let mut block = ExtMetadataBlockLevel9 { length, source_primary_index: index, ..Default::default() }; if index == 255 { let color_primaries = ColorPrimaries::from_array_float(&primaries); block.set_from_primaries(&color_primaries); } Ok(block) } fn calculate_level5_metadata( &self, canvas_ar: f32, image_ar: f32, ) -> Result { ensure!( self.opts.canvas_width.is_some(), "Missing canvas width to calculate L5" ); ensure!( self.opts.canvas_height.is_some(), "Missing canvas height to calculate L5" ); let cw = self.opts.canvas_width.unwrap() as f32; let ch = self.opts.canvas_height.unwrap() as f32; let mut calculated_level5 = ExtMetadataBlockLevel5::default(); if (canvas_ar - image_ar).abs() < f32::EPSILON { // No AR difference, zero offsets } else if image_ar > canvas_ar { let image_h = (ch * (canvas_ar / image_ar)).round(); let diff = ch - image_h; let offset_top = (diff / 2.0).trunc(); let offset_bottom = diff - offset_top; calculated_level5.active_area_top_offset = offset_top as u16; calculated_level5.active_area_bottom_offset = offset_bottom as u16; } else { let image_w = (cw * (image_ar / canvas_ar)).round(); let diff = cw - image_w; let offset_left = (diff / 2.0).trunc(); let offset_right = diff - offset_left; calculated_level5.active_area_left_offset = offset_left as u16; calculated_level5.active_area_right_offset = offset_right as u16; } Ok(calculated_level5) } pub fn is_cmv4(&self) -> bool { self.xml_version >= 0x402 } } dolby_vision-3.3.1/src/xml/tests.rs000064400000000000000000000246731046102023000154430ustar 00000000000000use std::path::PathBuf; use crate::rpu::{extension_metadata::blocks::ExtMetadataBlock, vdr_dm_data::CmVersion}; use super::{CmXmlParser, XmlParserOpts}; use anyhow::Result; fn assert_num_blocks_for_level(blocks: &[ExtMetadataBlock], level: u8, count: usize) { let filtered = blocks.iter().filter(|b| b.level() == level).count(); assert_eq!(filtered, count); } #[test] fn parse_cmv2_9() -> Result<()> { let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let assets_path = lib_path.parent().unwrap(); let opts = XmlParserOpts { canvas_width: Some(3840), canvas_height: Some(2160), }; let parser = CmXmlParser::parse_file(assets_path.join("assets/tests/cmv2_9.xml"), opts)?; let config = parser.config; assert_eq!(config.cm_version, CmVersion::V29); assert_eq!(config.length, 108); assert_eq!(config.shots.len(), 2); // L5 assert_eq!(config.level5.get_offsets(), (0, 0, 276, 276)); // L6 let level6 = config.level6.as_ref().unwrap(); assert_eq!(level6.max_display_mastering_luminance, 1000); assert_eq!(level6.min_display_mastering_luminance, 1); assert_eq!(level6.max_content_light_level, 756); assert_eq!(level6.max_frame_average_light_level, 97); // No L254 assert!(config.level254.is_none()); let shot1 = &config.shots[0]; let shot1_blocks = &shot1.metadata_blocks; assert_eq!(shot1.duration, 12); assert_eq!(shot1_blocks.len(), 4); assert_num_blocks_for_level(shot1_blocks, 1, 1); assert_num_blocks_for_level(shot1_blocks, 2, 3); let shot2 = &config.shots[1]; let shot2_blocks = &shot2.metadata_blocks; assert_eq!(shot2.duration, 96); assert_eq!(shot2_blocks.len(), 4); assert_num_blocks_for_level(shot2_blocks, 1, 1); assert_num_blocks_for_level(shot2_blocks, 2, 3); Ok(()) } #[test] fn parse_cmv4_0_2() -> Result<()> { let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let assets_path = lib_path.parent().unwrap(); let opts = XmlParserOpts::default(); let parser = CmXmlParser::parse_file(assets_path.join("assets/tests/cmv4_0_2.xml"), opts)?; let config = parser.config; assert_eq!(config.cm_version, CmVersion::V40); assert_eq!(config.length, 259); assert_eq!(config.shots.len(), 3); // L5 assert_eq!(config.level5.get_offsets(), (0, 0, 0, 0)); // L6 let level6 = config.level6.as_ref().unwrap(); assert_eq!(level6.max_display_mastering_luminance, 1000); assert_eq!(level6.min_display_mastering_luminance, 1); assert_eq!(level6.max_content_light_level, 3948); assert_eq!(level6.max_frame_average_light_level, 120); // XML L254 assert!(config.level254.is_some()); let level254 = config.level254.as_ref().unwrap(); assert_eq!(level254.dm_mode, 0); assert_eq!(level254.dm_version_index, 2); let shot1 = &config.shots[0]; let shot1_blocks = &shot1.metadata_blocks; assert_eq!(shot1.duration, 120); assert_eq!(shot1_blocks.len(), 4); assert_num_blocks_for_level(shot1_blocks, 1, 1); assert_num_blocks_for_level(shot1_blocks, 3, 1); assert_num_blocks_for_level(shot1_blocks, 5, 1); assert_num_blocks_for_level(shot1_blocks, 9, 1); let shot2 = &config.shots[1]; let shot2_blocks = &shot2.metadata_blocks; assert_eq!(shot2.duration, 99); assert_eq!(shot2_blocks.len(), 8); assert_num_blocks_for_level(shot2_blocks, 1, 1); assert_num_blocks_for_level(shot2_blocks, 2, 3); assert_num_blocks_for_level(shot2_blocks, 3, 1); assert_num_blocks_for_level(shot2_blocks, 8, 2); assert_num_blocks_for_level(shot2_blocks, 9, 1); let shot3 = &config.shots[2]; let shot3_blocks = &shot3.metadata_blocks; assert_eq!(shot3.duration, 40); assert_eq!(shot3_blocks.len(), 3); let rpus = config.generate_rpu_list()?; let rpu = &rpus[0]; let vdr_dm_data = rpu.vdr_dm_data.as_ref().unwrap(); let level254 = vdr_dm_data.get_block(254).unwrap(); if let ExtMetadataBlock::Level254(block) = &level254 { assert_eq!(block.dm_mode, 0); assert_eq!(block.dm_version_index, 2); } Ok(()) } #[test] fn parse_cmv4_0_2_with_l5() -> Result<()> { let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let assets_path = lib_path.parent().unwrap(); let opts = XmlParserOpts { canvas_width: Some(3840), canvas_height: Some(2160), }; let parser = CmXmlParser::parse_file(assets_path.join("assets/tests/cmv4_0_2.xml"), opts)?; let config = parser.config; assert_eq!(config.cm_version, CmVersion::V40); // L5 assert_eq!(config.level5.get_offsets(), (480, 480, 0, 0)); Ok(()) } #[test] fn parse_cmv4_0_2_custom_displays() -> Result<()> { let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let assets_path = lib_path.parent().unwrap(); let opts = XmlParserOpts::default(); let parser = CmXmlParser::parse_file( assets_path.join("assets/tests/cmv4_0_2_custom_displays.xml"), opts, )?; let config = parser.config; assert_eq!(config.cm_version, CmVersion::V40); let rpus = config.generate_rpu_list()?; let rpu = &rpus[0]; let vdr_dm_data = rpu.vdr_dm_data.as_ref().unwrap(); // L1, L5, L6 in DMv1 assert_eq!(vdr_dm_data.metadata_blocks(1).unwrap().len(), 3); // L3, L8, L9, L10, L11, L254 assert_eq!(vdr_dm_data.metadata_blocks(3).unwrap().len(), 6); let level8 = vdr_dm_data.get_block(8).unwrap(); if let ExtMetadataBlock::Level8(block) = level8 { assert_eq!(block.length, 25); assert_eq!(block.target_display_index, 255); assert_eq!(block.trim_slope, 2068); assert_eq!(block.trim_offset, 2069); assert_eq!(block.trim_power, 1987); assert_eq!(block.trim_chroma_weight, 2130); assert_eq!(block.trim_saturation_gain, 2150); assert_eq!(block.ms_weight, 2171); assert_eq!(block.target_mid_contrast, 2089); assert_eq!(block.clip_trim, 2011); assert_eq!(block.saturation_vector_field0, 128); assert_eq!(block.saturation_vector_field1, 128); assert_eq!(block.saturation_vector_field2, 128); assert_eq!(block.saturation_vector_field3, 128); assert_eq!(block.saturation_vector_field4, 150); assert_eq!(block.saturation_vector_field5, 128); assert_eq!(block.hue_vector_field0, 128); assert_eq!(block.hue_vector_field1, 160); assert_eq!(block.hue_vector_field2, 128); assert_eq!(block.hue_vector_field3, 128); assert_eq!(block.hue_vector_field4, 128); assert_eq!(block.hue_vector_field5, 128); } else { panic!("No L8 block"); } let level9 = vdr_dm_data.get_block(9).unwrap(); if let ExtMetadataBlock::Level9(block) = level9 { assert_eq!(block.length, 17); assert_eq!(block.source_primary_index, 255); assert_eq!(block.source_primary_red_x, 22314); assert_eq!(block.source_primary_red_y, 10551); assert_eq!(block.source_primary_green_x, 8693); assert_eq!(block.source_primary_green_y, 22740); assert_eq!(block.source_primary_blue_x, 5079); assert_eq!(block.source_primary_blue_y, 2163); assert_eq!(block.source_primary_white_x, 10249); assert_eq!(block.source_primary_white_y, 10807); } else { panic!("No L9 block"); } let level10 = vdr_dm_data.get_block(10).unwrap(); if let ExtMetadataBlock::Level10(block) = level10 { assert_eq!(block.length, 21); assert_eq!(block.target_display_index, 255); assert_eq!(block.target_max_pq, 2081); assert_eq!(block.target_min_pq, 62); assert_eq!(block.target_primary_index, 255); assert_eq!(block.target_primary_red_x, 21004); assert_eq!(block.target_primary_red_y, 10879); assert_eq!(block.target_primary_green_x, 10813); assert_eq!(block.target_primary_green_y, 20971); assert_eq!(block.target_primary_blue_x, 5079); assert_eq!(block.target_primary_blue_y, 2163); assert_eq!(block.target_primary_white_x, 10249); assert_eq!(block.target_primary_white_y, 10807); } else { panic!("No L10 block"); } Ok(()) } #[test] fn parse_cmv4_2_xml_510() -> Result<()> { let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let assets_path = lib_path.parent().unwrap(); let opts = XmlParserOpts::default(); let parser = CmXmlParser::parse_file(assets_path.join("assets/tests/cmv4_2_xml_510.xml"), opts)?; // Only HOME targets assert_eq!(parser.target_displays.len(), 3); let config = parser.config; assert_eq!(config.cm_version, CmVersion::V40); let rpus = config.generate_rpu_list()?; let rpu = &rpus[0]; let vdr_dm_data = rpu.vdr_dm_data.as_ref().unwrap(); // L1, L5, L6 in DMv1 assert_eq!(vdr_dm_data.metadata_blocks(1).unwrap().len(), 3); // L3, L9, L11, L254 in DMv2 assert_eq!(vdr_dm_data.metadata_blocks(3).unwrap().len(), 4); // Level 9 block recognized as preset let level9 = vdr_dm_data.get_block(9).unwrap(); if let ExtMetadataBlock::Level9(block) = level9 { assert_eq!(block.length, 1); assert_eq!(block.source_primary_index, 0); } else { panic!("No L9 block"); } let level11 = vdr_dm_data.get_block(11).unwrap(); if let ExtMetadataBlock::Level11(block) = level11 { assert_eq!(block.content_type, 2); assert_eq!(block.whitepoint, 0); assert!(!block.reference_mode_flag); } else { panic!("No L11 block"); } Ok(()) } #[test] fn parse_level6_decimals() -> Result<()> { let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let assets_path = lib_path.parent().unwrap(); let opts = XmlParserOpts::default(); let parser = CmXmlParser::parse_file(assets_path.join("assets/tests/level6_decimals.xml"), opts)?; let rpus = parser.config.generate_rpu_list()?; let rpu = &rpus[0]; let vdr_dm_data = rpu.vdr_dm_data.as_ref().unwrap(); // Source mastering display assert_eq!(vdr_dm_data.source_min_pq, 7); assert_eq!(vdr_dm_data.source_max_pq, 3388); // L6 let level6 = vdr_dm_data.get_block(6).unwrap(); if let ExtMetadataBlock::Level6(block) = level6 { assert_eq!(block.max_display_mastering_luminance, 2000); assert_eq!(block.min_display_mastering_luminance, 1); assert_eq!(block.max_content_light_level, 788); assert_eq!(block.max_frame_average_light_level, 60); } else { panic!("No L6 block"); } Ok(()) }