1 视频中的旋转信息以及为什么会有旋转信息

Android或者ios等手机上录制视频时,由于重力感应或者录制视频的摆放方式的问题会导致录制的视频拥有旋转信息。如果是横屏录制(手机逆时针旋转90度),则录制的视频时不带角度的。如果是竖屏录制(正常的拿手机的姿势),此时的录制的视频的旋转角度是90度。如果再旋转90度,此时一般音量键和关屏键朝下,此时的视频的旋转角度是180。以此类推。所以在手机上的视频一般会有4种角度的视频,播放时,要对视频资源进行旋转后再进行播放,不然视频就会出现各种反转、倾倒。

https://www.stubbornhuang.com/1855/ 已经分享了在python中如何读取视频旋转角度并根据视频旋转角度旋转视频帧的代码,有兴趣的可以查看。

我们可以使用ffmpge下的工具ffprobe查看视频的元数据信息:

1.1 不带旋转信息也就是旋转角度为0的视频元数据信息

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'a.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2021-07-17T14:12:45.000000Z
    location        : +28.7793+113.2697/
    location-eng    : +28.7793+113.2697/
    com.android.version: 8.1.0
  Duration: 00:00:06.13, start: 0.000000, bitrate: 18162 kb/s
    Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc, smpte170m), 1920x1080, 17008 kb/s, SAR 1:1 DAR 16:9, 29.86 fps, 30 tbr, 90k tbn, 180k tbc (default)
    Metadata:
      creation_time   : 2021-07-17T14:12:45.000000Z
      handler_name    : VideoHandle
    Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, mono, fltp, 96 kb/s (default)
    Metadata:
      creation_time   : 2021-07-17T14:12:45.000000Z
      handler_name    : SoundHandle

1.2 旋转90度的视频元数据信息

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'b.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2021-08-20T05:32:36.000000Z
    location        : +00.0000+000.0000/
    location-eng    : +00.0000+000.0000/
    com.android.version: 10
  Duration: 00:00:03.89, start: 0.000000, bitrate: 8777 kb/s
    Stream #0:0(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 127 kb/s (default)
    Metadata:
      creation_time   : 2021-08-20T05:32:36.000000Z
      handler_name    : SoundHandle
    Stream #0:1(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, unknown/bt470bg/unknown), 720x1280, 8724 kb/s, SAR 1:1 DAR 9:16, 30.09 fps, 30 tbr, 90k tbn, 180k tbc (default)
    Metadata:
      rotate          : 90
      creation_time   : 2021-08-20T05:32:36.000000Z
      handler_name    : VideoHandle
    Side data:
      displaymatrix: rotation of -90.00 degrees

1.3 旋转180度的视频元数据信息

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'c.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2021-07-18T01:43:43.000000Z
    com.android.version: 10
  Duration: 00:00:04.98, start: 0.000000, bitrate: 4997 kb/s
    Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080, 3502 kb/s, SAR 1:1 DAR 16:9, 29.12 fps, 29.50 tbr, 90k tbn, 180k tbc (default)
    Metadata:
      rotate          : 180
      creation_time   : 2021-07-18T01:43:43.000000Z
      handler_name    : VideoHandle
    Side data:
      displaymatrix: rotation of -180.00 degrees
    Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 193 kb/s (default)
    Metadata:
      creation_time   : 2021-07-18T01:43:43.000000Z
      handler_name    : SoundHandle

1.4 旋转270度的视频元数据信息

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'd.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2021-10-29T01:52:22.000000Z
    location        : +28.2386+113.0239/
    location-eng    : +28.2386+113.0239/
    com.android.version: 11
  Duration: 00:00:06.15, start: 0.000000, bitrate: 19144 kb/s
    Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1080x1920, 18490 kb/s, SAR 1:1 DAR 9:16, 30.08 fps, 90k tbr, 90k tbn, 180k tbc (default)
    Metadata:
      rotate          : 270
      creation_time   : 2021-10-29T01:52:22.000000Z
      handler_name    : VideoHandle
    Side data:
      displaymatrix: rotation of 90.00 degrees
    Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      creation_time   : 2021-10-29T01:52:22.000000Z
      handler_name    : SoundHandle

从上面各种视频的元数据信息中我们可以看到,如果该视频拥有旋转信息,那么在该视频的视频流的元数据信息的rotate字段会存储旋转角度,例如:

    Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1080x1920, 18490 kb/s, SAR 1:1 DAR 9:16, 30.08 fps, 90k tbr, 90k tbn, 180k tbc (default)
    Metadata:
      rotate          : 270
      creation_time   : 2021-10-29T01:52:22.000000Z
      handler_name    : VideoHandle

2 使用ffmpeg C++ sdk获取视频的视频流旋转信息

之前查看的视频旋转角度是使用ffprobe的这个工具,但是如何在代码中使用ffmpeg sdk获取视频角度信息呢?

通过上述发现,视频的旋转角度存储在视频流的元数据中,所以我们首先可以打开视频文件,然后找到视频中的视频流,最后获取视频流的元数据信息并得到旋转角度,具体的代码如下:

double GetVideoRotationMetadataInfo(const std::string& video_path)
{
    double rotationResult = 0.0;

    AVFormatContext* format_ctx = NULL;
    AVDictionaryEntry* tag = NULL;

    int ret;

    if ((ret = avformat_open_input(&format_ctx, video_path.c_str(), NULL, NULL)))
        return ret;

    // 查找文件视频流信息
    int video_stream_idx = -1;
    for (int i = 0; i < format_ctx->nb_streams; ++i)
    {
        if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            video_stream_idx = i;
        }
    }

    // 在视频流中查找元数据的旋转信息
    if (video_stream_idx != -1)
    {
        AVStream* pAVStream = format_ctx->streams[video_stream_idx];

        if (pAVStream != nullptr)
        {
            while ((tag = av_dict_get(pAVStream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
            {
                std::string key = tag->key;
                std::string value = tag->value;

                if (key.compare("rotate") == 0)
                {
                    rotationResult = std::atof(value.c_str());
                    break;
                }
            }
        }
    }



    avformat_close_input(&format_ctx);

    return rotationResult;
}

3 根据获取的旋转角度旋转视频中的视频帧

主要使用了opencv的仿射变换机制,具体的代码如下:

void RotateOpencvFrame(const cv::Mat& inputMat, cv::Mat& outMat, double rotation_angle)
{
    int src_width = inputMat.cols;
    int src_height = inputMat.rows;

    float src_center_x = src_width / 2;
    float src_center_y = src_height / 2;

    // 设置旋转矩阵
    cv::Mat rotateMat = cv::getRotationMatrix2D(cv::Point2f(src_center_x , src_center_y), -rotation_angle, 1);
    double cos = fabs(rotateMat.at<double>(0,0));
    double sin = fabs(rotateMat.at<double>(0,1));

    // 计算图像旋转后的新边界
    int dest_width = int((src_height * sin) + (src_width * cos));
    int dest_height = int((src_height * cos) + (src_width * sin));

    // 调整旋转矩阵的移动距离
    rotateMat.at<double>(0, 2) += (dest_width / 2) - src_center_x;
    rotateMat.at<double>(1, 2) += (dest_height / 2) - src_center_y;

    cv::warpAffine(inputMat, outMat, rotateMat, cv::Size(dest_width, dest_height));
}

4 完整代码

#include <iostream>
#include <string>

// opencv
#include "opencv/cv.h"
#include "opencv2/opencv.hpp"

// ffmpeg
/* 包含FFmpeg的头文件以及静态链接FFmpeg的lib库 */

#ifdef __cplusplus
extern "C"
#endif
{
#include <libavcodec\avcodec.h>
#include <libavformat\avformat.h>
#include <libavutil\channel_layout.h>
#include <libavutil\common.h>
#include <libavutil\imgutils.h>
#include <libswscale\swscale.h>
#include <libavutil\imgutils.h>
#include <libavutil\opt.h>
#include <libavutil\mathematics.h>
#include <libavutil\samplefmt.h>
#include <libswresample\swresample.h>
};
#pragma comment(lib, "avcodec.lib")  
#pragma comment(lib, "avformat.lib")  
#pragma comment(lib, "avdevice.lib")  
#pragma comment(lib, "avfilter.lib")  
#pragma comment(lib, "avutil.lib")  
#pragma comment(lib, "postproc.lib")  
#pragma comment(lib, "swresample.lib")  
#pragma comment(lib, "swscale.lib")  
/*--------------------------------------*/

double GetVideoRotationMetadataInfo(const std::string& video_path)
{
    double rotationResult = 0.0;

    AVFormatContext* format_ctx = NULL;
    AVDictionaryEntry* tag = NULL;

    int ret;

    if ((ret = avformat_open_input(&format_ctx, video_path.c_str(), NULL, NULL)))
        return ret;

    // 查找文件视频流信息
    int video_stream_idx = -1;
    for (int i = 0; i < format_ctx->nb_streams; ++i)
    {
        if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            video_stream_idx = i;
        }
    }

    // 在视频流中查找元数据的旋转信息
    if (video_stream_idx != -1)
    {
        AVStream* pAVStream = format_ctx->streams[video_stream_idx];

        if (pAVStream != nullptr)
        {
            while ((tag = av_dict_get(pAVStream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
            {
                std::string key = tag->key;
                std::string value = tag->value;

                if (key.compare("rotate") == 0)
                {
                    rotationResult = std::atof(value.c_str());
                    break;
                }
            }
        }
    }

    avformat_close_input(&format_ctx);

    return rotationResult;
}

void RotateOpencvFrame(const cv::Mat& inputMat, cv::Mat& outMat, double rotation_angle)
{
    int src_width = inputMat.cols;
    int src_height = inputMat.rows;

    float src_center_x = src_width / 2;
    float src_center_y = src_height / 2;

    // 设置旋转矩阵
    cv::Mat rotateMat = cv::getRotationMatrix2D(cv::Point2f(src_center_x , src_center_y), -rotation_angle, 1);
    double cos = fabs(rotateMat.at<double>(0,0));
    double sin = fabs(rotateMat.at<double>(0,1));

    // 计算图像旋转后的新边界
    int dest_width = int((src_height * sin) + (src_width * cos));
    int dest_height = int((src_height * cos) + (src_width * sin));

    // 调整旋转矩阵的移动距离
    rotateMat.at<double>(0, 2) += (dest_width / 2) - src_center_x;
    rotateMat.at<double>(1, 2) += (dest_height / 2) - src_center_y;

    cv::warpAffine(inputMat, outMat, rotateMat, cv::Size(dest_width, dest_height));
}


int main()
{
    std::string input_video_filepath = "a.mp4";

    cv::VideoCapture video_capture;
    video_capture.open(input_video_filepath);

    // 使用ffmpeg获取视频元数据的旋转角度信息
    double videoRotationAngle = GetVideoRotationMetadataInfo(input_video_filepath);

    while (video_capture.isOpened())
    {
        cv::Mat frame;
        video_capture.read(frame);

        if (frame.empty())
            break;

        cv::Mat frame_rotate;
        RotateOpencvFrame(frame, frame_rotate, videoRotationAngle);
        frame_rotate.copyTo(frame);

    }
    video_capture.release();

}

参考链接

  1. http://www.yanzuoguang.com/article/584.html

如果您觉得对您有帮助,可以请站长喝一杯咖啡哦!

记得在赞赏备注里写上您的昵称

您可在本站资助名单中查看你的打赏记录哦!

支付宝扫一扫

微信扫一扫

金额随意,礼轻义重

当前分类随机文章推荐

全站随机文章推荐

关于本站站长 StubbornHuang
C++ – 使用ffmpeg读取视频旋转角度并使用OpenCV根据旋转角度对视频进行旋转复原-StubbornHuang Blog纵使晴明无雨色,入云深处亦沾衣。