/***********************************************************************************
 *	作者:韦访
 *	创建日期:2014.6.3
 *	说明:    本程序为基于opencv2.4.4的简易yuv420播放器,目的是完成老师安排的任务,拿到实验分
 *	使用方法:yuv格式自行去了解,值得注意的是要根据文件名改变帧大小	
 *				格式: Sub-QCIF   亮度分辨率: 128*96   每帧使用的位: 147456
 *				格式: QCIF       亮度分辨率: 176*144  每帧使用的位: 304128
 *				格式: CIF        亮度分辨率: 352*288  每帧使用的位: 1216512
 *				格式: 4CIF       亮度分辨率: 704*576  每帧使用的位: 4866048
 *			
 *          按ESC退出播放,S暂停播放,左键快退,右键快进,在暂停模式下,L显示前一帧,R显示后一帧,G继续播放,ESC退出
 *			可以拖动进度条播放指定位置
			在S暂停模式下,按B,出现一个新的滑动窗口,选择将剩下视频分割成n段,按回车键即可出现n个窗口,选择要播放的窗口号码,
			回车即可从该窗口开始播放
 *  注:本程序是基于opencv的,至于opencv如何安装自己上网查
 *
 ***********************************************************************************/

#include <opencv/highgui.h>
#include <stdio.h>
#include <cv.h>
#include<conio.h>

#define nWidth 352
#define nHeight 288
#define FrameSize  ( nWidth*nHeight*3/2)   //YUV一帧图像大小

using namespace cv;

int time_slider;  //进度条变量
int window_num;    //创建窗口数
int pos = 0;      //用于寻找下一帧位置
const int ratio_max = 6;  //最大分割窗口数
int frame_count;       //帧数
int pos_tmp[15];       
int pos_step;    //分割步长

const char *win_name[]= {"win0","win1","win2","win3","win4","win5","win6"};

//yuv420转rgb函数
void YUV420_2_RGB( unsigned char* pYUV, unsigned char* pRGB , int width, int height);

//进度条反馈函数
void time_trackbar( int, void* );
void ratio_trackbar( int, void* ); 


int main(int argc, char * argv[])
{
	int key;
	int save_win = 33;
	int win_tmp = -1;
	
	
	//打开文件
	 FILE *f ;
	if(!(f = fopen("mobile_cif.yuv","rb")))
	{
		 printf("file open error!");
	}

	//计算有多少帧图像
	 fseek(f, 0, SEEK_END);
     frame_count = (int) ((int)ftell(f)/((nWidth * nHeight * 3) / 2)); 
	  printf("tell1 = %d\n",ftell(f));
	 fseek(f, 0, SEEK_SET);//文件位置定位到文件头
	 printf("tell2 = %d\n",ftell(f));
	
	 //开辟缓冲区存放一帧图片信息
	 unsigned char *pBuf = new unsigned char[nWidth*nHeight*3/2];
	 unsigned char *pRGB =  new unsigned char[3*nWidth*nHeight];  
	 
	 //创建image,大小为nWidth*nHeight,数据类型为IPL_DEPTH_8U,3通道
	 IplImage * image    = cvCreateImageHeader(cvSize(nWidth, nHeight),IPL_DEPTH_8U,3);  
	 
	 //进度条名称定义
	  char TrackbarName[50];
	  char TrackbarName2[50];
  	  sprintf( TrackbarName, "进度%d", frame_count );
	  sprintf(TrackbarName2, "分割%d",  ratio_max );
	 
	  //创建窗口并命名
     cvNamedWindow("wf_yuv_player");

	
	//视频的播放,循环播放
	while(1)
   {
		//借助变量pos准确找到每帧图片的开头位置,并读入pBuf
		 fseek(f, pos, SEEK_SET);
		 fread(pBuf,1 , FrameSize, f );  

		 //yuv转rgb,然后将rgb转换进image
		 YUV420_2_RGB(pBuf,pRGB,nWidth,nHeight);  
		 cvSetData(image,pRGB,nWidth*3);

		 //显示图片
		 cvShowImage("wf_yuv_player", image);
		
		 //创建进度条
		 createTrackbar( TrackbarName, "wf_yuv_player", &time_slider, frame_count, time_trackbar );
	
		 //等待按键事件发生
		key=cvWaitKey(33);

		//以下操作位根据按键值做出相应的动作

		//ESC按键按下,退出程序
		  if(key == 27) 
		{	
			break;
		}
		  //左键按下,快退,步长为5帧
		 if ( key == 0x250000 && (time_slider >  5) )
		{	
			pos -= (5*FrameSize);		
			time_slider -= 5;			
		}
			
			// 右键按下,快进,步长为5帧
		if(key == 0x270000&&(time_slider < frame_count - 5))
		{
			pos += (5*FrameSize);			
			time_slider += 5;			
		}

		//如果按下s键则暂停
		if(key == 's' || key == 'S')
		{
			//因为要有单帧播放,所以进入死循环方便单帧操作
				while(1)
			  {
					key = cvWaitKey(33);
					
					//如果按下g则继续播放	
					 if(key == 'g'|| key == 'G')
					{
						break;
					} 

					  //ESC按键按下,退出程序
				     if(key == 27) 
				    {				
						goto end;
			        }

					 //按下l按键,显示上一帧
					 if((key =='l'|| key =='L') && (time_slider >  0))
				    {
						 pos -= FrameSize;							
						 time_slider--;							 
				    }
					 
					 //按下r按键,显示下一帧
					 if((key =='r' || key =='R') && (time_slider < frame_count ))
				    {
						 pos += FrameSize;							
						 time_slider++;					  
				    }
					
				
					 // 左键按下,快退
					 if( key == 0x250000 && (time_slider >  5) )
				    {
					     pos -= (5*FrameSize);							
						 time_slider -= 5 ;						
				    }
			
				// 右键按下,快进
					if( key == 0x270000 && (time_slider < frame_count - 5) )
				    {
						 pos += (5*FrameSize);					
						 time_slider +=5 ;						
				    }

				//如果按下B,出现一个滑动条,分割视频
					if( key == 'b'|| key == 'B' )
				   {	
					   save_win = -1;
						
					   if(win_tmp >= 0)
							cvDestroyWindow(win_name[win_tmp]);

						 pos_tmp[0] = pos;
					
						 createTrackbar( TrackbarName2, "wf_yuv_player", &window_num, ratio_max, ratio_trackbar );
						
						 //等待确认
						 while(key != 0xd)
						{
							key = cvWaitKey(10);
					    }
								 
						if(window_num < 1)
							window_num = 1;
							
						//创建window_num个窗口并显示
						for( int a = 0; a < window_num ; a++ )
						{
							cvNamedWindow(win_name[a]);
							if( a )
								pos_tmp[a] = pos_tmp[ a - 1] +  (pos_step * FrameSize);	

							fseek(f, pos_tmp[a], SEEK_SET);
							fread(pBuf,1 , FrameSize, f );
							
							YUV420_2_RGB(pBuf,pRGB,nWidth,nHeight);  

							cvSetData(image,pRGB,nWidth*3);
							cvShowImage(win_name[a], image);	
						}
						
						printf("请输入您选中的窗口号: \n");
							
						while(1)
						{
							save_win = cvWaitKey(10);
							if(save_win > 47 && save_win < 48 + window_num)
							{
								break;
							}
						}
						
						//printf("pos_step = %d\n", pos_step);
						for( int a = 0; a < window_num ; a++)
							{
								if(a != (save_win - 48))
										cvDestroyWindow(win_name[a]);
								else 
								{
									pos =  pos_tmp[a];
									if(pos > frame_count * FrameSize)	
										pos = frame_count * FrameSize;
									
									win_tmp = a;
									time_slider = pos / FrameSize;															
								}

							}	
					
				    }



				 //创建进度条
				 createTrackbar( TrackbarName, "wf_yuv_player", &time_slider, frame_count, time_trackbar );
				 fseek(f, pos, SEEK_SET);
				 fread(pBuf,1 , FrameSize, f );
				 YUV420_2_RGB(pBuf,pRGB,nWidth,nHeight);  
				
				 cvSetData(image,pRGB,nWidth*3);
				 cvShowImage("wf_yuv_player", image);		
			 }
		}
			
		//每播放一帧,time_slider自加
		time_slider++;
		pos += FrameSize;
		// printf("pos = %d\n", pos);	
		//播放完后,重新开始播放
		if(time_slider > frame_count - 1)
		{
			pos = 0;
			time_slider= 0;
		}
    }
end:
		//释放内存
		 cvDestroyWindow("yuv_player");
		 cvReleaseImage(&image);
		 delete pBuf;
		 delete pRGB;
	     fclose(f);
 return 0;
}

void YUV420_2_RGB( unsigned char* pYUV, unsigned char* pRGB , int width, int height)  
{  
	//找到Y、U、V在内存中的首地址
    unsigned char* pY = pYUV;   
    unsigned char* pU = pYUV+height*width;  
    unsigned char* pV = pU+(height*width/4);  
  
  
    unsigned char* pBGR = NULL;  
    unsigned char R = 0;  
    unsigned char G = 0;  
    unsigned char B = 0;  
    unsigned char Y = 0;  
    unsigned char U = 0;  
    unsigned char V = 0;  
    double temp = 0;  
    for ( int i = 0; i < height; i++ )  
    {  
        for ( int j = 0; j < width; j++ )  
        {  
            //找到相应的RGB首地址
			pBGR = pRGB+ i*width*3+j*3;  
  
			//取Y、U、V的数据值
            Y = *(pY+i*width+j);  
            U = *pU;  
            V = *pV;  
  
          //yuv转rgb公式
            //yuv转rgb公式
            temp = Y + ((1.773) * (U-128));  
            B = temp<0?0:(temp>255?255:(unsigned char)temp);
         
            temp = (Y - (0.344) * (U-128) - (0.714) * (V-128) )  ;  
            G= temp<0?0:(temp>255?255:(unsigned char)temp);

            temp =(Y + (1.403)*(V-128))  ;  
            R =temp<0?0:(temp>255?255:(unsigned char)temp);
           
			//将转化后的rgb保存在rgb内存中,注意放入的顺序b是最低位
            *pBGR     = B;              
            *(pBGR+1) = G;          
            *(pBGR+2) = R;  
            
			
            if (  j % 2 != 0)  
            {  
                *pU++;  
				*pV++; 
            }  
           
        }  
      if(i % 2 == 0)
	  {
			pU = pU -  width/2 ;
			pV = pV -  width/2 ;
	  }
    }  
}  


void time_trackbar( int, void* )
{	
	pos = time_slider * FrameSize;	
}


void ratio_trackbar( int, void* ) 
{	

	pos_step = (frame_count - time_slider) / ( window_num + 1);
	
}