[FFmpeg-devel] [PATCH 1/5] avformat/matroskaenc: Support rotations
Andreas Rheinhardt
andreas.rheinhardt at outlook.com
Wed Aug 9 12:26:11 EEST 2023
Andreas Rheinhardt:
> Matroska supports orthogonal transformations (both pure rotations
> as well as reflections) via its 3D-projection elements, namely
> ProjectionPoseYaw (for a horizontal reflection) as well as
> ProjectionPoseRoll (for rotations). This commit adds support
> for this.
>
> Support for this in the demuxer has been added in
> 937bb6bbc1e8654633737e69e403e95a37113058 and
> the sample used in the matroska-dovi-write-config8 FATE-test
> includes a displaymatrix indicating a rotation which is now
> properly written and read, thereby providing coverage for
> the relevant code in the muxer as well as the demuxer.
>
> Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt at outlook.com>
> ---
> Honestly, I am not really sure how to handle the floating-point
> inaccuracies here (in atan2).
>
> libavformat/matroskaenc.c | 100 +++++++++++++++++----
> tests/ref/fate/matroska-dovi-write-config8 | 13 ++-
> 2 files changed, 94 insertions(+), 19 deletions(-)
>
> diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
> index 41e13b273d..c1f40b26e6 100644
> --- a/libavformat/matroskaenc.c
> +++ b/libavformat/matroskaenc.c
> @@ -1403,25 +1403,75 @@ static void mkv_write_video_color(EbmlWriter *writer, const AVStream *st,
> }
>
> #define MAX_VIDEO_PROJECTION_ELEMS 6
> -static void mkv_write_video_projection(AVFormatContext *s, EbmlWriter *writer,
> - const AVStream *st, uint8_t private[])
> +static void mkv_handle_rotation(void *logctx, const AVStream *st,
> + double *yaw, double *roll)
> +{
> + const int32_t *matrix =
> + (const int32_t*)av_stream_get_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, NULL);
> +
> + if (!matrix)
> + return;
> +
> + /* Check whether this is an affine transformation */
> + if (matrix[2] || matrix[5])
> + goto ignore;
> +
> + /* This together with the checks below test whether
> + * the upper-left 2x2 matrix is nonsingular. */
> + if (!matrix[0] && !matrix[1])
> + goto ignore;
> +
> + /* We ignore the translation part of the matrix (matrix[6] and matrix[7])
> + * as well as any scaling, i.e. we only look at the upper left 2x2 matrix.
> + * We only accept matrices that are an exact multiple of an orthogonal one.
> + * Apart from the multiple, every such matrix can be obtained by
> + * potentially flipping in the x-direction (corresponding to yaw = 180)
> + * followed by a rotation of (say) an angle phi in the counterclockwise
> + * direction. The upper-left 2x2 matrix then looks like this:
> + * | (+/-)cos(phi) (-/+)sin(phi) |
> + * scale * | |
> + * | sin(phi) cos(phi) |
> + * The first set of signs in the first row apply in case of no flipping,
> + * the second set applies in case of flipping. */
> +
> + /* The casts to int64_t are needed because -INT32_MIN doesn't fit
> + * in an int32_t. */
> + if (matrix[0] == matrix[4] && -(int64_t)matrix[1] == matrix[3]) {
> + /* No flipping case */
> + *yaw = 0;
> + } else if (-(int64_t)matrix[0] == matrix[4] && matrix[1] == matrix[3]) {
> + /* Horizontal flip */
> + *yaw = 180;
> + } else {
> +ignore:
> + av_log(logctx, AV_LOG_INFO, "Ignoring display matrix indicating "
> + "non-orthogonal transformation.\n");
> + return;
> + }
> + *roll = 180 / M_PI * atan2(matrix[3], matrix[4]);
> +
> + /* We do not write a ProjectionType element indicating "rectangular",
> + * because this is the default value. */
> +}
> +
> +static int mkv_handle_spherical(void *logctx, EbmlWriter *writer,
> + const AVStream *st, uint8_t private[],
> + double *yaw, double *pitch, double *roll)
> {
> const AVSphericalMapping *spherical =
> (const AVSphericalMapping *)av_stream_get_side_data(st, AV_PKT_DATA_SPHERICAL,
> NULL);
>
> if (!spherical)
> - return;
> + return 0;
>
> if (spherical->projection != AV_SPHERICAL_EQUIRECTANGULAR &&
> spherical->projection != AV_SPHERICAL_EQUIRECTANGULAR_TILE &&
> spherical->projection != AV_SPHERICAL_CUBEMAP) {
> - av_log(s, AV_LOG_WARNING, "Unknown projection type\n");
> - return;
> + av_log(logctx, AV_LOG_WARNING, "Unknown projection type\n");
> + return 0;
> }
>
> - ebml_writer_open_master(writer, MATROSKA_ID_VIDEOPROJECTION);
> -
> switch (spherical->projection) {
> case AV_SPHERICAL_EQUIRECTANGULAR:
> case AV_SPHERICAL_EQUIRECTANGULAR_TILE:
> @@ -1455,17 +1505,33 @@ static void mkv_write_video_projection(AVFormatContext *s, EbmlWriter *writer,
> av_assert0(0);
> }
>
> - if (spherical->yaw)
> - ebml_writer_add_float(writer, MATROSKA_ID_VIDEOPROJECTIONPOSEYAW,
> - (double) spherical->yaw / (1 << 16));
> - if (spherical->pitch)
> - ebml_writer_add_float(writer, MATROSKA_ID_VIDEOPROJECTIONPOSEPITCH,
> - (double) spherical->pitch / (1 << 16));
> - if (spherical->roll)
> - ebml_writer_add_float(writer, MATROSKA_ID_VIDEOPROJECTIONPOSEROLL,
> - (double) spherical->roll / (1 << 16));
> + *yaw = (double) spherical->yaw / (1 << 16);
> + *pitch = (double) spherical->pitch / (1 << 16);
> + *roll = (double) spherical->roll / (1 << 16);
>
> - ebml_writer_close_master(writer);
> + return 1; /* Projection included */
> +}
> +
> +static void mkv_write_video_projection(void *logctx, EbmlWriter *wr,
> + const AVStream *st, uint8_t private[])
> +{
> + double yaw = 0, pitch = 0, roll = 0;
> + int ret;
> +
> + ebml_writer_open_master(wr, MATROSKA_ID_VIDEOPROJECTION);
> +
> + ret = mkv_handle_spherical(logctx, wr, st, private, &yaw, &pitch, &roll);
> + if (!ret)
> + mkv_handle_rotation(logctx, st, &yaw, &roll);
> +
> + if (yaw)
> + ebml_writer_add_float(wr, MATROSKA_ID_VIDEOPROJECTIONPOSEYAW, yaw);
> + if (pitch)
> + ebml_writer_add_float(wr, MATROSKA_ID_VIDEOPROJECTIONPOSEPITCH, pitch);
> + if (roll)
> + ebml_writer_add_float(wr, MATROSKA_ID_VIDEOPROJECTIONPOSEROLL, roll);
> +
> + ebml_writer_close_or_discard_master(wr);
> }
>
> #define MAX_FIELD_ORDER_ELEMS 2
> diff --git a/tests/ref/fate/matroska-dovi-write-config8 b/tests/ref/fate/matroska-dovi-write-config8
> index bb22563eee..58eb454865 100644
> --- a/tests/ref/fate/matroska-dovi-write-config8
> +++ b/tests/ref/fate/matroska-dovi-write-config8
> @@ -1,5 +1,5 @@
> -09ff3c0a038eec0cdf4773929b24f41a *tests/data/fate/matroska-dovi-write-config8.matroska
> -3600606 tests/data/fate/matroska-dovi-write-config8.matroska
> +80d2b23a6f27ab28b02a907b37b9649c *tests/data/fate/matroska-dovi-write-config8.matroska
> +3600620 tests/data/fate/matroska-dovi-write-config8.matroska
> #extradata 0: 551, 0xa18acf66
> #extradata 1: 2, 0x00340022
> #tb 0: 1/1000
> @@ -46,6 +46,15 @@
> 1, 395, 395, 23, 439, 0x7d85e4c9
> [STREAM]
> [SIDE_DATA]
> +side_data_type=Display Matrix
> +displaymatrix=
> +00000000: 0 65536 0
> +00000001: -65536 0 0
> +00000002: 0 0 1073741824
> +
> +rotation=-90
> +[/SIDE_DATA]
> +[SIDE_DATA]
> side_data_type=DOVI configuration record
> dv_version_major=1
> dv_version_minor=0
Will apply patches 1-5 of this patchset tomorrow unless there are
objections. Notice that I slightly modified the test added in 3/5 to now
also set a creation_time metadata to test a previously untested codepath.
- Andreas
More information about the ffmpeg-devel
mailing list