OpenGL全景视频
全景视频其实在实现上和一般的视频播放基本差不多,解码可以用ffmpeg,只是对解码后的图片在绘制的时候要绘制在一个球上(我这里是球,好像有说有的格式要绘制在四面体上的,美做深入研究),而不是画在一个表面上。所以这里应该要用纹理。
1.计算球的顶点坐标和纹理坐标
球的顶点坐标和纹理坐标的计算可以说是全景的关键。这里参考android opengl播放全景视频
int cap_H = ;//必须大于0,且cap_H应等于cap_W
int cap_W = ;//绘制球体时,每次增加的角度 float* verticals;
float* UV_TEX_VERTEX; ........................ void getPointMatrix(GLfloat radius)
{
verticals = new float[( / cap_H) * ( / cap_W) * * ];
UV_TEX_VERTEX = new float[( / cap_H) * ( / cap_W) * * ]; float x = ;
float y = ;
float z = ; int index = ;
int index1 = ;
float r = radius;//球体半径
double d = cap_H * PI / ;//每次递增的弧度
for (int i = ; i < ; i += cap_H) {
double d1 = i * PI / ;
for (int j = ; j < ; j += cap_W) {
//获得球体上切分的超小片矩形的顶点坐标(两个三角形组成,所以有六点顶点)
double d2 = j * PI / ;
verticals[index++] = (float)(x + r * sin(d1 + d) * cos(d2 + d));
verticals[index++] = (float)(y + r * cos(d1 + d));
verticals[index++] = (float)(z + r * sin(d1 + d) * sin(d2 + d));
//获得球体上切分的超小片三角形的纹理坐标
UV_TEX_VERTEX[index1++] = (j + cap_W) * 1.0f / ;
UV_TEX_VERTEX[index1++] = (i + cap_H) * 1.0f / ; verticals[index++] = (float)(x + r * sin(d1) * cos(d2));
verticals[index++] = (float)(y + r * cos(d1));
verticals[index++] = (float)(z + r * sin(d1) * sin(d2)); UV_TEX_VERTEX[index1++] = j * 1.0f / ;
UV_TEX_VERTEX[index1++] = i * 1.0f / ; verticals[index++] = (float)(x + r * sin(d1) * cos(d2 + d));
verticals[index++] = (float)(y + r * cos(d1));
verticals[index++] = (float)(z + r * sin(d1) * sin(d2 + d)); UV_TEX_VERTEX[index1++] = (j + cap_W) * 1.0f / ;
UV_TEX_VERTEX[index1++] = i * 1.0f / ; verticals[index++] = (float)(x + r * sin(d1 + d) * cos(d2 + d));
verticals[index++] = (float)(y + r * cos(d1 + d));
verticals[index++] = (float)(z + r * sin(d1 + d) * sin(d2 + d)); UV_TEX_VERTEX[index1++] = (j + cap_W) * 1.0f / ;
UV_TEX_VERTEX[index1++] = (i + cap_H) * 1.0f / ; verticals[index++] = (float)(x + r * sin(d1 + d) * cos(d2));
verticals[index++] = (float)(y + r * cos(d1 + d));
verticals[index++] = (float)(z + r * sin(d1 + d) * sin(d2)); UV_TEX_VERTEX[index1++] = j * 1.0f / ;
UV_TEX_VERTEX[index1++] = (i + cap_H) * 1.0f / ; verticals[index++] = (float)(x + r * sin(d1) * cos(d2));
verticals[index++] = (float)(y + r * cos(d1));
verticals[index++] = (float)(z + r * sin(d1) * sin(d2)); UV_TEX_VERTEX[index1++] = j * 1.0f / ;
UV_TEX_VERTEX[index1++] = i * 1.0f / ;
}
}
}
2.文件解码
我这里用ffmpeg来做文件的解码,用了一个最简单的单线程循环来做,没有做过多复杂的考虑。解出来的图像数据放到一个循环队列中。
DWORD WINAPI ThreadFunc(LPVOID n)
{
AVFormatContext *pFormatCtx;
int i, videoindex;
AVCodec *pCodec;
AVCodecContext *pCodecCtx = NULL; char filepath[] = "H:\\F-5飞行.mp4"; av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != ){
printf("Couldn't open input stream.(无法打开输入流)\n");
return -;
} if (avformat_find_stream_info(pFormatCtx, NULL)<)
{
printf("Couldn't find stream information.(无法获取流信息)\n");
return -;
} videoindex = -;
for (i = ; i<pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoindex = i;
break;
}
if (videoindex == -)
{
printf("Didn't find a video stream.(没有找到视频流)\n");
return -;
}
pCodecCtx = pFormatCtx->streams[videoindex]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL)
{
printf("Codec not found.(没有找到解码器)\n");
return -;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL)<)
{
printf("Could not open codec.(无法打开解码器)\n");
return -;
} AVFrame *pFrame;
pFrame = av_frame_alloc();
int ret, got_picture;
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket)); AVFrame *pFrameBGR = NULL;
pFrameBGR = av_frame_alloc(); struct SwsContext *img_convert_ctx; int index = ;
while (av_read_frame(pFormatCtx, packet) >= )
{
if (packet->stream_index == videoindex)
{
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < )
{
printf("Decode Error.(解码错误)\n");
continue;
}
if (got_picture)
{
index++; flag_wait:
if (frame_queue.size >= MAXSIZE)
{
printf("size = %d I'm WAITING ... \n", frame_queue.size);
Sleep();
goto flag_wait;
} EnterCriticalSection(&frame_queue.cs); Vid_Frame *vp;
vp = &frame_queue.queue[frame_queue.rear]; vp->frame->pts = pFrame->pts; /* alloc or resize hardware picture buffer */
if (vp->buffer == NULL || vp->width != pFrame->width || vp->height != pFrame->height)
{
if (vp->buffer != NULL)
{
av_free(vp->buffer);
vp->buffer = NULL;
} int iSize = avpicture_get_size(AV_PIX_FMT_BGR24, pFrame->width, pFrame->height);
av_free(vp->buffer);
vp->buffer = (uint8_t *)av_mallocz(iSize); vp->width = pFrame->width;
vp->height = pFrame->height; } avpicture_fill((AVPicture *)vp->frame, vp->buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); if (vp->buffer)
{ img_convert_ctx = sws_getContext(vp->width, vp->height, (AVPixelFormat)pFrame->format, vp->width, vp->height,
AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, , vp->height, vp->frame->data, vp->frame->linesize);
sws_freeContext(img_convert_ctx); vp->pts = pFrame->pts;
} frame_queue.size++;
frame_queue.rear = (frame_queue.rear + ) % MAXSIZE; LeaveCriticalSection(&frame_queue.cs); //MySaveBmp("f5.bmp", vp->buffer, vp->width, vp->height); //int nHeight = vp->height;
//int nWidth = vp->width; //Mat tmp_mat = Mat::zeros(nHeight, nWidth, CV_32FC3); //int k = 0;
//for (int i = 0; i < nHeight; i++)
//{
// for (int j = 0; j < nWidth; j++)
// {
// tmp_mat.at<Vec3f>(i, j)[0] = vp->buffer[k++] / 255.0f;
// tmp_mat.at<Vec3f>(i, j)[1] = vp->buffer[k++] / 255.0f;
// tmp_mat.at<Vec3f>(i, j)[2] = vp->buffer[k++] / 255.0f;
// }
//} //imwrite("mat_Image.jpg", tmp_mat); //namedWindow("Marc_Antony");
//imshow("Marc_Antony", tmp_mat); //waitKey(0); }
}
av_free_packet(packet);
} avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx); return ;
}
其中frame_queue是一个循环队列,解码的时候入队,渲染的时候出队。虽然没有实际测,但我试用的几个视频文件都是4K的,所以解码时间估计有些长,解码这里如果能用硬解应该效果会更好。然后我这里没有考虑音频。
3.渲染
(1)初始化
void init(void)
{
initQueue(&frame_queue); glGenTextures(, &texturesArr); //创建纹理 glBindTexture(GL_TEXTURE_2D, texturesArr); //Mat image = imread("1.jpg");
//glTexImage2D(GL_TEXTURE_2D, 0, 3, image.cols, image.rows, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, image.data); //IplImage *image = cvLoadImage("4.png", 1);
//IplImage *image = cvLoadImage("5.png", 1);
//glTexImage2D(GL_TEXTURE_2D, 0, 3, image->width, image->height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, image->imageData);
//printf("nChannels is %d \n", image->nChannels);
//cvNamedWindow("1");
//cvShowImage("1", image);
//cvWaitKey(0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //线形滤波
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //线形滤波 glClearColor(0.0, 0.0, 0.0, 0.0);
glClearDepth();
glShadeModel(GL_SMOOTH);
//GLfloat _ambient[] = { 1.0, 1.0, 1.0, 1.0 };
//GLfloat _diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
//GLfloat _specular[] = { 1.0, 1.0, 1.0, 1.0 };
//GLfloat _position[] = { 255, 255, 255, 0 };
//glLightfv(GL_LIGHT0, GL_AMBIENT, _ambient);
//glLightfv(GL_LIGHT0, GL_DIFFUSE, _diffuse);
//glLightfv(GL_LIGHT0, GL_SPECULAR, _specular);
//glLightfv(GL_LIGHT0, GL_POSITION, _position);
//glEnable(GL_LIGHTING);
//glEnable(GL_LIGHT0); glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glDisable(GL_CULL_FACE); //禁用裁剪 getPointMatrix();
}
初始化中包含队列的初始化,创建纹理,计算球的顶点坐标和纹理坐标,各种参数设置。其中注意急用光源,否则图片各处的明暗会依据光源位置的设置而有不同;其次是禁用剪裁,否则无法进入到球体内部,因为全景视频是在球体内部看的。
(2)设置投影矩阵
void reshape(int w, int h)
{
glViewport(, , (GLsizei)w, (GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//glOrtho(-250.0, 250, -250.0, 250, -500, 500);
//glFrustum(-250.0, 250, -250.0, 250, -5, -500);
gluPerspective(, (GLfloat)w / h, 1.0f, 1000.0f); //设置投影矩阵
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
投影采用透视投影,这样可以进入球体内部。这里角度设置成45,可以自行设置,但不宜过大,过大效果不是很好。
(3)渲染
void display(void)
{
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); gluLookAt(, , distance, , , 500.0, , , );
printf("distance: %f \n", distance);
glRotatef(xangle, 1.0f, 0.0f, 0.0f); //绕X轴旋转
glRotatef(yangle, 0.0f, 1.0f, 0.0f); //绕Y轴旋转
glRotatef(zangle, 0.0f, 0.0f, 1.0f); //绕Z轴旋转 EnterCriticalSection(&frame_queue.cs); printf("display size = %d \n", frame_queue.size);
if (frame_queue.size > )
{
Vid_Frame *vp = &frame_queue.queue[frame_queue.front]; glBindTexture(GL_TEXTURE_2D, texturesArr);
glTexImage2D(GL_TEXTURE_2D, , , vp->width, vp->height, , GL_BGR_EXT, GL_UNSIGNED_BYTE, vp->buffer); frame_queue.size--;
frame_queue.front = (frame_queue.front + ) % MAXSIZE;
} LeaveCriticalSection(&frame_queue.cs); //glColor3f(1.0, 0.0, 0.0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(, GL_FLOAT, , verticals);
glTexCoordPointer(, GL_FLOAT, , UV_TEX_VERTEX);
glPushMatrix();
glDrawArrays(GL_TRIANGLES, , ( / cap_H) * ( / cap_W) * );
glPopMatrix();
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arrays glFlush(); av_usleep();
}
渲染时把解出来的数据从队列中取出生成新的纹理。渲染采用glDrawArrays函数,使用的GL_TRIANGLES参数,使用这个参数对于计算球的顶点坐标和纹理坐标来说不需要考虑很多,比较方便,就是点数过多的时候可能会影响渲染的效率。
(5)画面更新与重绘
void reDraw(int millisec)
{
glutTimerFunc(millisec, reDraw, millisec);
glutPostRedisplay();
}
这里用OpenGL的定时器来对画面做一个定时的更新,从而实现视频播放的效果。
4.一些控制操作
(1)键盘控制
void keyboard(unsigned char key, int x, int y)
{
switch (key)
{
case 'x': //当按下键盘上d时,以沿X轴旋转为主
xangle += 1.0f; //设置旋转增量
break;
case 'X':
xangle -= 1.0f; //设置旋转增量
break;
case 'y':
yangle += 1.0f;
break;
case 'Y':
yangle -= 1.0f;
break;
case 'z':
zangle += 1.0f;
break;
case 'Z':
zangle -= 1.0f;
break;
case 'a':
distance += 10.0f;
break;
case 'A':
distance -= 10.0f;
break;
default:
return;
}
glutPostRedisplay(); //重绘函数
}
用键盘来实现球体绕x,y,z轴的旋转,以及观察球体的距离。
(2)鼠标控制
//处理鼠标点击
void Mouse(int button, int state, int x, int y)
{
if (state == GLUT_DOWN) //第一次鼠标按下时,记录鼠标在窗口中的初始坐标
{
//记住鼠标点击后光标坐标
cx = x;
cy = y;
}
} //处理鼠标拖动
void onMouseMove(int x, int y)
{
float offset = 0.18;
//计算拖动后的偏移量,然后进行xy叠加减
yangle -= ((x - cx) * offset); if ( y > cy) {//往下拉
xangle += ((y - cy) * offset);
}
else if ( y < cy) {//往上拉
xangle += ((y - cy) * offset);
} glutPostRedisplay(); //保存好当前拖放后光标坐标点
cx = x;
cy = y;
}
5.主函数
int main(int argc, char* argv[])
{
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(, );
glutInitWindowPosition(, );
glutCreateWindow("OpenGL全景");
init();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutMouseFunc(Mouse);
glutMotionFunc(onMouseMove); glutTimerFunc(, reDraw, ); HANDLE hThrd = NULL;
DWORD threadId;
hThrd = CreateThread(NULL, , ThreadFunc, , , &threadId); glutMainLoop(); WaitForSingleObject(hThrd, INFINITE); if (hThrd)
{
CloseHandle(hThrd);
} return ;
}
glutMainLoop()函数真是个恶心的函数,都没找到正常退出他的方法,要退出貌似必须得把整个程序都退出去,在实际使用的时候大多数时候我们都只是希望退出循环就够了,不一定要退出整个程序。所以如果用win32来做,最好就不要用这个函数,用一个独立的线程来做渲染,各种消息通过win32来实现,这样是比较方便的。
运行截图:
工程源码:http://download.csdn.net/download/qq_33892166/9856939
VR视频推荐:http://dl.pconline.com.cn/vr/list0_1_2007_2018.html
OpenGL全景视频的更多相关文章
- 全景VR视频外包公司:长年承接VR全景视频外包(技术分享YouTube的360全景视频)
虽然比预期来得晚了些,但YouTube终于支持360度全景视频了,这应该会吸引不少VR(虚拟现实)爱好者.今年1月,Google就表示这一功能将在“接下来”的几周出现.现在YouTube上已经有了一些 ...
- 全景视频外包团队:技术分享Unity3D全景漫游
作者:未知 1.建模中使用的图片.文件.文件夹等以及模型中物体.材质等的名称都不能使用中文或者特殊符号,可以使用英文字母.数字.下划线等 2.调整Max的单位为米 3.烘培光影的设置 4.模型的中的植 ...
- H5全景视频VR视频
公司的有个专题页面涉及到全景视频展示这个技术点,找到一个相关的库. http://www.utovr.com/sdk/download 这里有个免费的SDK可以下载. 里面也有案例可以看,代码就照着 ...
- 《图说VR入门》——360全景视频
本文章由cartzhang编写,转载请注明出处. 所有权利保留. 文章链接:http://blog.csdn.net/cartzhang/article/details/53674647 作者:car ...
- 【Android开发VR实战】二.播放360°全景视频
转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53924006 本文出自[DylanAndroid的博客] [Android开发 ...
- UE4.16播放全景视频
全景视频有两种:一种是常见的一帧画面里面包含一张全景图,另外一种是一帧画面里面包含了左眼和右眼两张全景图. 根据种类的不同,选择不同的材质分别对应MAT_Single_Image和MAT_Stereo ...
- Linux下ffmpeg添加Facebook/transform代码块实现将全景视频的球模型转换成立方体模型
Facebook事实上已开始在平台中支持360度全景视频的流播,但公司对此并不满足.其工程师更是基于锥体几何学设计出了一套全新的视频编码,号称最高能将全景视频的文件大小减少80%.(VR最新突破:全景 ...
- OpenGL 实现视频编辑中的转场效果
转场介绍 转场效果是什么? 转场效果,简单来说就是两段视频之间的衔接过渡效果. 现在拍摄 vlog 的玩家越来越多,要是视频没有一两个炫酷的转场效果,都不好意思拿出来炫酷了. 那么如何在视频编辑软件中 ...
- 北京全景视频外包公司:长年承接VR全景视频外包
北京动点飞扬软件,从事外包业务五年,长年承接全景VR视频,全景普通视频外包. 以下是全景VR视频案例(可操作,人不动景物不动,人移动,景物跟随) 欢迎联系我们QQ:372900288 TEL:1391 ...
随机推荐
- MongoDB的安全校验
一.MongoDB安全校验的重要性 每个MongoDB实例中的数据库都可以有许多用户.如果没有开启安全校验,限制用户权限,则每个进到数据库的用户都能任意的对数据库数据进行读,写甚至是读写操作.这样的场 ...
- C++学习笔记--友元
C++控制对类对象私有部分的访问,在外部无法直接访问类的私有或保护成员.通常,公有类方法提供唯一的访问途径.有时这种限制太严格,不适合特定的编程问题.所以C++提供了友元这种形式,通过让函数或类成为类 ...
- Cout<<XXX<<<XXX<<<XXX,是从左到右计算的
int a=1,b=2,c=3; cout<<(c=a+b)<<' '<<(a=b+c)<<' '<<(b=a+c)<<e ...
- 微信小程序组件loading
操作反馈loading:官方文档 Demo Code: Page({ data: { hidden: true }, loadingChange: function () { console.log( ...
- [C#]浮点数除零无法捕获异常的解决办法
解决方法: //运算前先检查被除数是否为零,为零则手动抛出除零异常 if (numberB == 0.0) { throw new DivideByZeroException(); } Result ...
- firewall 防火墙相关
修改配置文件: /etc/sysconfig/network-scripts/ifcfg-ens33 文件 ONBOOT=no 改为yes 然后重启 service network restart ...
- Wex5各组件介绍
1.http://doc.wex5.com/comp-base/ 2.select 组件 http://doc.wex5.com/comps-select/ 3.页面交互以及传递参数 http:// ...
- 20145310 《Java程序设计》第8周学习总结
20145310 <Java程序设计>第8周学习总结 教材学习内容总结 本周主要进行第十四章和第十五章的学习. 第十四章 NIO使用频道(channel)来衔接数据节点,对数据区的标记提供 ...
- 20145314郑凯杰 《Java程序设计》实验一 Java开发环境的熟悉(Windows + Eclipse)实验报告
20145314郑凯杰 <Java程序设计>实验一 Java开发环境的熟悉(Windows + Eclipse)实验报告 实验要求 •使用JDK编译.运行简单的Java程序: •使用Ecl ...
- 20145329 《Java程序设计》第九周学习总结
教材学习内容总结 十六章 1.Metadata即"诠读数据的数据",数据库是用来存储数据的地方,然而数据库本身产品名称为何?数据库中有几个数据表格?表格名称为何?表格中有几个字段等 ...