前言:
不知你有没有注意到,每次用相机竖屏录制1080p的视频时,录制的mp4文件显示都是1920x1080,我们手机的屏幕都是横向小于竖向的,那么怎么播放这个1920x1080的视频呢?这就涉及到了rotation的概念。rotation表示视频的角度,当视频角度为90度的时候在显示时就是旋转90度播放的。具体是怎么实现的呢?本文将带你去了解。
一个疑问:
看到这里,我很好奇,为什么录制视频时非要1920x1080,rotation是90,不能录制1080x1920,然后rotation是0吗?后者还不需要播放时做处理。
一番研究才发现,确实不能这样。
录制视频的过程,首先我们需要用相机捕获画面,此时需要用到camera sensor,camera sensor是由很多个感光元件组成的,一些常用sensor的具体设置如下:
从上面的数据可以发现sensor的感光元件w一般都大于h,手机相机也是如此。
当我们设置尺寸为1920x1080时,sensor获取数据之后稍作裁剪再downscale即可,但是如果设置成1080x1920那么需要裁剪的数据较多,就会很大程度上损失fov。所以手机录制的视频都是w>h的。
对rotation的处理:
对于rotation不为0的视频,是什么时候做的旋转呢?解码前?decoder中解码后?还是surface中?
显然,不可能是解码前,因为码流数据很难处理。那么我们看看,ACodec对rotation的具体处理:
// frameworks/av/media/libstagefright/ACodec.cpp
status_t ACodec::configureCodec(
const char *mime, const sp<AMessage> &msg) {
.......
int32_t rotationDegrees;
if (msg->findInt32("rotation-degrees", &rotationDegrees)) { // 读取rotation
mRotationDegrees = rotationDegrees;
} else {
mRotationDegrees = 0;
}
.......
}
status_t ACodec::setupNativeWindowSizeFormatAndUsage(
ANativeWindow *nativeWindow /* nonnull */, int *finalUsage /* nonnull */,
bool reconnect) {
.......
return setNativeWindowSizeFormatAndUsage( // 将rotation设置给nativewindow
nativeWindow,
def.format.video.nFrameWidth,
def.format.video.nFrameHeight,
def.format.video.eColorFormat,
mRotationDegrees,
usage,
reconnect);
}
// frameworks/av/media/libstagefright/SurfaceUtils.cpp
status_t setNativeWindowSizeFormatAndUsage(
ANativeWindow *nativeWindow /* nonnull */,
int width, int height, int format, int rotation, int usage, bool reconnect) {
......
// 根据rotation判断transform
int transform = 0;
if ((rotation % 90) == 0) {
switch ((rotation / 90) & 3) {
case 1: transform = HAL_TRANSFORM_ROT_90; break;
case 2: transform = HAL_TRANSFORM_ROT_180; break;
case 3: transform = HAL_TRANSFORM_ROT_270; break;
default: transform = 0; break;
}
}
err = native_window_set_buffers_transform(nativeWindow, transform);
......
}
// frameworks/native/libs/nativewindow/include/system/window.h
static inline int native_window_set_buffers_transform(
struct ANativeWindow* window,
int transform)
{
return window->perform(window, NATIVE_WINDOW_SET_BUFFERS_TRANSFORM,
transform);
}
// frameworks/native/libs/gui/Surface.cpp
int Surface::perform(int operation, va_list args)
{
int res = NO_ERROR;
switch (operation) {
......
case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM:
res = dispatchSetBuffersTransform(args);
break;
......
}
int Surface::dispatchSetBuffersTransform(va_list args) {
uint32_t transform = va_arg(args, uint32_t);
return setBuffersTransform(transform);
}
具体过程如下:
step1: APP解析头文件,解析到video的rotation
step2: APP播放视频时调用mediacodec的configure函数,并把rotation传入
step3: MediaCodec ACodec对收到的消息进行处理,获取到rotation的信息。
step4: ACodec在创建nativewindow时将rotation信息传入。
step5: surface把rotation信息转换为transform的角度
step6: 播放视频时每解码完一帧就会把yuv数据送给surface去渲染,此时surface会根据设置的transform的角度旋转yuv数据并render。