CPU与OpenGL ES实现YUV数据左右留白边
2017-04-23 本文已影响285人
熊皮皮
以YUV420p为例实现视频左右留白边功能。因YUV颜色空间的UV通道在转换成RGB时需进行偏置,偏置前范围为[-0.5, 0.5],即[-128, 127]。相应的,白色作为255(0xFF),偏置前是127(0x7F)。参考实现代码如下:
/// Y
for (int row = 0; row < dstHeight; ++row) {
static uint8_t *tmpFrame = (uint8_t *)malloc(dstWidth);
static int kCopyStartAddress = (dstWidth - scaledWidth) / 2;
memset(tmpFrame, 0xFF, dstWidth);
memcpy(tmpFrame + kCopyStartAddress, dstFrame + row * dstWidth, scaledWidth);
memcpy(dstFrame + row * dstWidth, tmpFrame, dstWidth);
}
/// Cb
int kDstWidthU = dstWidth >> 1;
int kDstHeightU = dstHeight >> 1;
int scaledWidthU = scaledWidth / 2;
uint8_t *kDstFrameStartAddressU = dstFrame + dstWidth * dstHeight;
for (int row = 0; row < kDstHeightU; ++row) {
static uint8_t *tmpFrame = (uint8_t *)malloc(kDstWidthU);
static int kCopyStartAddressU = (kDstWidthU - scaledWidthU) / 2;
memset(tmpFrame, 0x7F, kDstWidthU);
memcpy(tmpFrame + kCopyStartAddressU, kDstFrameStartAddressU + row * kDstWidthU, scaledWidthU);
memcpy(kDstFrameStartAddressU + row * kDstWidthU, tmpFrame, kDstWidthU);
}
/// Cr
int kDstWidthV = dstWidth >> 1;
int kDstHeightV = dstHeight >> 1;
int scaledWidthV = scaledWidth / 2;
uint8_t *kDstFrameStartAddressV = dstFrame + dstWidth * dstHeight * 5 / 4;
for (int row = 0; row < kDstHeightV; ++row) {
static uint8_t *tmpFrame = (uint8_t *)malloc(kDstWidthV);
static int kCopyStartAddressV = (kDstWidthV - scaledWidthV) / 2;
memset(tmpFrame, 0x7F, kDstWidthV);
memcpy(tmpFrame + kCopyStartAddressV, kDstFrameStartAddressV + row * kDstWidthV, scaledWidthV);
memcpy(kDstFrameStartAddressV + row * kDstWidthV, tmpFrame, kDstWidthV);
}
如果用OpenGL (ES)实现,修改顶点坐标并设置glClearColor为白色即可,0.5应该是视频与glViewport中设置的宽度比例,示例如下:
GLfloat rect_vertices[] = {
-0.5, -1.0,
0.5, -1.0,
-0.5, 1.0,
0.5, 1.0,
};
另外,OpenGL (ES)实现剪裁图像,如16:9图像至1:1,通常变化的是纹理坐标,例如:
GLfloat texture_vertices[] = {
0.0f, 0.0f,
0.5f, 0.0f,
0.0f, 0.5f,
0.5f, 0.5f,
};
最终渲染出来的图像是符合预期的。然而,如果做多图像混合,比如在右下角叠加水印,为了适配不同分辨率的视频,通常会在片段着色器中计算纹理坐标。那么,上述修改会导致OpenGL插值时只处理指定的区域,多余的位置会被丢弃。按上面的坐标,当要计算textureCoordinate大于0.5的位置时,此时并没被插值出来,即得不到大于0.5的这些坐标。
影响片段着色器得到插值纹理坐标的顶点着色器,对于常规的数字图像处理,通常是这么实现的:
#version 300 es
in vec4 position;
in vec2 inputTextureCoordinate;
out vec2 textureCoordinate;
void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate;
}
为了得到完整的纹理坐标,客户端也只能上传完整坐标点,然后在片段着色器中判断,这样的实现在一次滤波时最差的情况将进行glViewport指定的width x height次判断,严重影响性能。