FFmpeg解码后需从AVStream metadata读取"rotate"字符串并atoi转为角度值(如90/180/270),再据此决定渲染或重编码时的变换逻辑;若CPU旋转则用sws_scale配合尺寸交换与坐标映射,编码时须同步更新输出流metadata中"rotate"及"com.apple.quicktime.rotation"键值以确保播放器正确识别。

FFmpeg 解码后如何获取并应用旋转元数据
很多手机拍的 MP4 视频自带 rotate 元数据(如 90、270),但 FFmpeg 默认解码出来的 AVFrame 数据是原始朝向的,不会自动旋转。你看到“画面横着”不是解码错了,而是没处理这个元数据。
关键步骤是:从 AVStream 的 metadata 里读 "rotate",再用它决定后续渲染或重编码时的变换逻辑。
-
av_dict_get(stream->metadata, "rotate", NULL, 0)取值,注意返回的是字符串,需atoi() - 常见值为
90、180、270,部分视频可能是-90(等价于270) - 该字段不一定存在——H.264 Annex B 流或某些封装(如 MKV)通常不带,MP4 / MOV 更常见
用 libswscale 手动旋转 AVFrame(CPU 方式)
如果目标是直接在 CPU 上把帧转成带旋转的图像(比如喂给 OpenGL 或 SDL 渲染),最可控的方式是用 sws_scale() 配合旋转后的输出尺寸和映射顺序。
本质是:申请一个「旋转后尺寸」的目标 AVFrame(例如原 1280×720 逆时针转 90° → 目标 720×1280),再用 sws_getCachedContext() 构建一个带翻转语义的转换上下文。
- 先调
sws_getRotatedContext()(FFmpeg 5.0+)或手动构造:srcW/srcH和dstW/dstH互换,再设SWS_BILINEAR+SWS_FLAG_ACCURATE_RND - 对 90°/270° 旋转,还需设置
srcSliceY和srcSliceH为整帧,并在sws_scale()前交换src_data[0]和src_linesize[0]的语义(即把宽高逻辑倒过来) - 更稳妥的做法是:用
sws_scale()先转成临时的AV_PIX_FMT_RGB24,再用 OpenCV 或手写循环做像素坐标映射(适合调试)
编码时写入新 rotate 元数据(避免重复旋转)
如果你在转码(比如缩放 + 旋转保存),别只改像素,还要把最终朝向写回输出流的元数据,否则播放器仍会按老 rotate 值再转一次,导致翻车。
- 在
avformat_write_header()前,调av_dict_set(&out_stream->metadata, "rotate", "0", 0)显式清空 - 若输出仍是竖屏,应设为
"90";若已转正,则设"0"—— 这步漏掉,iOS 系统播放器大概率又给你扭回去 - 注意:MP4 封装要求 rotate 存在
com.apple.quicktime.rotationkey 时才生效,仅"rotate"不够;可用av_dict_set()同时设两个 key
SDL/OpenGL 渲染时绕过 CPU 旋转(推荐路径)
真正在播放器里做实时旋转,硬解 + GPU 渲染才是低延迟方案。此时不该让 CPU 跑 sws_scale(),而应把旋转逻辑交给着色器或纹理矩阵。
以 SDL2 + OpenGL 为例:解码得到 YUV 数据后,上传为 3 个纹理,渲染时在顶点着色器里调整 texcoord 映射,或用 glRotatef() 配合 glViewport() 尺寸交换。
- 90° 顺时针旋转 =
texcoord.x和texcoord.y互换,且y = 1.0 - y - 务必同步调整
glViewport()宽高(如原 1280×720 →glViewport(0,0,720,1280)) - 硬解(
AV_HWDEVICE_TYPE_VAAPI/D3D11VA)输出的帧可能已是旋转后布局,此时再额外旋转就叠了——得先查AVFrame->sample_aspect_ratio和硬件文档
旋转不是单点问题,它横跨元数据解析、像素处理、GPU 渲染、封装写入四个环节。最容易被忽略的是:同一段视频,在不同设备上因元数据解析差异或硬解行为不同,表现可能完全相反。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











