Add luma only
[yuv-player.git] / yuvPlayer.cpp
1 #include<iostream>
2 #include<fstream>
3 #include<thread>
4 #include<sstream>
5 #include<iomanip>
6 #include "SDL2/SDL.h"
7
8 using namespace std;
9
10 /*
11  * Convert MP4 to YUV
12  * ffmpeg -i big_buck_bunny_720p_10mb.mp4 -c:v rawvideo -pix_fmt yuv420p out.yuv
13  *
14  * Play YUV with ffplay
15  * ffplay -f rawvideo -pixel_format yuv420p -video_size 1280x720 -i ./out.yuv
16  *
17  * Usage:
18  *      1. press 'P' to pause or play as the given framerate
19  *      2. press 'q' to quit
20  *      3. press RIGHT to next frame, LEFT to previous frame
21  *      4. color space transform
22  */
23
24 class YuvPlayer 
25 {
26 public:
27     YuvPlayer(int width, int height, const string& title = "YUV Player");
28     void play(string fname, int fps = -1);
29     void toggleGrid();
30     void toggleYumaOnly();
31     ~YuvPlayer();
32
33 private:
34     bool readFrame(ifstream& in, uint8_t *raw, size_t size);
35     void updateRenderer(uint8_t *raw);
36     string file;
37     int width = 1280;
38     int height = 720;
39     SDL_Window *window;
40     SDL_Renderer *renderer;
41     SDL_Texture *texture;
42     unsigned int frameCnt = 0;
43     string title;
44     ifstream::pos_type last_pos;
45     bool isShowingGrid = false;
46     bool isLumOnly = false;
47     uint8_t *raw;
48 };
49
50
51 class ImageOperation {
52 public:
53     ImageOperation(uint8_t *r, int width, int height);
54
55
56 private:
57     int width;
58     int height;
59     uint8_t *raw;
60 };
61
62 YuvPlayer::YuvPlayer(int w, int h, const string& t)
63 : width(w)
64 , height(h)
65 , title(t)
66 {
67     SDL_Init(SDL_INIT_VIDEO);
68     window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN);
69     if (window == nullptr){
70         cout << "create error: " << SDL_GetError() << endl;
71     }
72     renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
73     texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, width, height);
74 }
75
76 YuvPlayer::~YuvPlayer()
77 {
78     SDL_DestroyTexture(texture);
79     SDL_DestroyRenderer(renderer);
80     SDL_DestroyWindow(window);
81     SDL_Quit();
82 }
83
84 // https://wiki.videolan.org/YUV/, YCrCb
85 // I420, YUV420p,  YYYY YYYY UU VV
86 static void lumaOnly(uint8_t *raw, int w, int h)
87 {
88     memset(raw + w * h, 128, w * h / 2); // UV 128
89     //memset(raw + w * h, 0, w * h / 2); // UV 128
90     //memset(raw + w * h, 0, w * h / 2); // UV 0, G only
91     //memset(raw + w * h, 255, w * h / 2); // UV 0, R only
92     //memset(raw + w * h, 128, w * h / 4); // U 128
93     //for (int i = 0; i < w * h; i++){
94     //    raw[i] = raw[i] / 2; // Y to 1/2
95     //}
96 }
97
98 void YuvPlayer::play(string file, int fps)
99 {
100     size_t frame_size = width * height * 3 / 2;
101     raw = (uint8_t *) malloc(sizeof(uint8_t) * frame_size);
102     ifstream in (file, ios::binary);
103     if (!in.is_open()) {
104         cout << "failed to open " << file << endl;
105     }
106
107
108     SDL_Event event;
109     bool quit = false;
110     bool isPlaying = true;
111     int interval = 1000 / fps;
112
113     while(!quit) {
114         interval = isPlaying ? 1000 / fps : -1;
115         SDL_WaitEventTimeout(&event, interval);
116         if(fps > 0 && isPlaying){
117             if(readFrame(in, raw, frame_size)){
118                 updateRenderer(raw);
119             }
120         }
121         switch(event.type){
122             case SDL_QUIT:
123                 quit = true;
124                 break;
125             case SDL_KEYDOWN:
126                 switch(event.key.keysym.sym){
127                     case SDLK_q:
128                         quit = true;
129                         break;
130                     case SDLK_RIGHT:
131                         if(readFrame(in, raw, frame_size)){
132                             updateRenderer(raw);
133                         }
134                         break;
135                     case SDLK_LEFT:
136                         frameCnt = frameCnt >= 2 ? frameCnt - 2 : 0;
137                         in.seekg(frameCnt * frame_size);
138                         if(readFrame(in, raw, frame_size)){
139                             updateRenderer(raw);
140                         }
141
142                         break;
143                     case SDLK_p:
144                         isPlaying = !isPlaying;
145                         break;
146                     case SDLK_g:
147                         toggleGrid();
148                         break;
149                     case SDLK_y:
150                         toggleYumaOnly();
151                     default:
152                         break;
153                 }
154             default:
155                 break;
156         }
157     }
158     free(raw);
159 }
160 void YuvPlayer::toggleYumaOnly()
161 {
162     isLumOnly = !isLumOnly;
163     updateRenderer(this->raw);
164 }
165
166 void YuvPlayer::toggleGrid()
167 {
168     isShowingGrid = !isShowingGrid;
169     updateRenderer(raw);
170 }
171
172
173 bool YuvPlayer::readFrame(ifstream& in, uint8_t *raw, size_t size)
174 {
175     last_pos = in.tellg();
176     bool ret = (bool) in.read(reinterpret_cast<char *>(raw), size);
177     if (ret){
178         frameCnt++;
179         stringstream ss;
180         ss << this->title << " [" << setw(4) << to_string(frameCnt) << "]";
181         SDL_SetWindowTitle(window, ss.str().c_str());
182     }else {
183         cout << "read error" << endl;
184     }
185     return ret;
186 }
187
188 void YuvPlayer::updateRenderer(uint8_t *raw)
189 {    
190     if (isLumOnly){
191         lumaOnly(raw, width, height);
192     }
193     SDL_UpdateTexture(texture, nullptr, raw, width * SDL_BYTESPERPIXEL(SDL_PIXELFORMAT_IYUV));
194     SDL_RenderClear(renderer);
195     SDL_RenderCopy(renderer, texture, nullptr, nullptr);
196
197     if(isShowingGrid){
198         SDL_SetRenderDrawColor( renderer, 0x00, 0x00, 0xFF, 0xFF );             
199         //SDL_RenderDrawLine( renderer, 0, height / 2, width, height / 2 );
200         SDL_RenderDrawLine( renderer, 0, height / 3, width, height / 3 );
201         SDL_RenderDrawLine( renderer, 0, height * 2 / 3, width, height * 2 / 3 );
202
203         SDL_RenderDrawLine( renderer, width / 3, 0, width / 3, height );
204         SDL_RenderDrawLine( renderer, width * 2 / 3, 0, width * 2 / 3, height );
205
206     }
207
208     SDL_RenderPresent(renderer);
209 }
210
211 int main(int argc, char* argv[])
212 {
213 //    string fname(argv[1] ? argv[1] : "out.yuv");
214 //    size_t width  = atoi(argv[2] ? argv[2] : "1280");
215 //    size_t height = atoi(argv[3] ? argv[3] : "720");
216 //
217 //    int fps = atoi(argv[4] ? argv[4] : "20");
218 //    cout << fname << "\n" << width << "\n" << height << "\n" << fps << "\n" << endl;
219 //
220     YuvPlayer player(1280, 720);
221     player.play("out.yuv", 25);
222     //player.play(fname);
223 }