Add resizer letterbox2
[jpcrr.git] / streamtools / playdump.cpp
bloba602e78230ccce1ab6c580d9801559e96f71edb5
1 /*
2 JPC-RR: A x86 PC Hardware Emulator
3 Release 1
5 Copyright (C) 2009-2010 H. Ilari Liusvaara
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as published by
9 the Free Software Foundation.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 Based on JPC x86 PC Hardware emulator,
21 A project from the Physics Dept, The University of Oxford
23 Details about original JPC can be found at:
25 www-jpc.physics.ox.ac.uk
29 #include "newpacket.hpp"
30 #include "resize.hpp"
31 #include "resampler.hpp"
32 #include "hardsubs.hpp"
33 #include <sstream>
34 #include "digital-filter.hpp"
35 #include "timecounter.hpp"
36 #include <iostream>
37 #include <cmath>
38 #include "misc.hpp"
39 #include "SDL.h"
40 #define MAXADVANCE 50
41 #define MINSAMPLES 512
42 #define BUFSAMPLES 2048
43 #define MAXSAMPLES 8192
45 std::vector<sample_number_t> audiobuffer;
46 uint32_t timebase;
47 uint32_t audio_clear = 0;
48 uint32_t audio_stamp = 0;
49 uint64_t audio_samples;
50 uint32_t audiorate = 44100;
51 uint32_t audio_lag = 0;
53 void audio_callback(void* x, Uint8* stream, int bytes)
55 unsigned samples = bytes / 4;
56 audio_samples += samples;
57 audio_stamp = SDL_GetTicks();
58 audio_clear = 1000 * audio_samples / audiorate;
60 //Remove the samples that have been missed.
61 if(audio_lag > 0) {
62 if(audio_lag > audiobuffer.size()) {
63 std::cerr << "Throwing away " << audiobuffer.size() << " samples as missed" << std::endl;
64 audio_lag -= audiobuffer.size();
65 audiobuffer.resize(0);
66 } else {
67 std::cerr << "Throwing away " << audio_lag << " samples as missed" << std::endl;
68 memmove((Uint8*)&audiobuffer[0], (Uint8*)&audiobuffer[audio_lag], 4 * (audiobuffer.size() - audio_lag));
69 audiobuffer.resize(audiobuffer.size() - audio_lag);
70 audio_lag = 0;
74 if(samples > audiobuffer.size()) {
75 std::cerr << "Audio underflow!" << std::endl;
76 memcpy(stream, (Uint8*)&audiobuffer[0], 4 * audiobuffer.size());
77 audio_lag += (bytes - 4 * audiobuffer.size()) / 4;
78 memset(stream + 4 * audiobuffer.size(), 0, bytes - 4 * audiobuffer.size());
79 samples = audiobuffer.size();
80 } else
81 memcpy(stream, (Uint8*)&audiobuffer[0], 4 * samples);
82 memmove((Uint8*)&audiobuffer[0], (Uint8*)&audiobuffer[samples], 4 * (audiobuffer.size() - samples));
83 audiobuffer.resize(audiobuffer.size() - samples);
86 int next_filename_index(int argc, char** argv, int currentindex)
88 bool split = false;
89 for(int i = 1; i < argc; i++) {
90 std::string arg = argv[i];
91 if(!split && arg == "--") {
92 split = true;
94 if(i <= currentindex)
95 continue;
96 if(split || !isstringprefix(argv[i], "--")) {
97 return i;
100 return -1;
103 int real_main(int argc, char** argv)
105 int filenameindex = -1;
106 uint64_t timecorrection = 0;
107 uint64_t last_timestamp = 0;
108 bool split = false;
109 unsigned long percentspeed = 100;
111 for(int i = 1; i < argc; i++)
112 try {
113 if(split || !isstringprefix(argv[i], "--")) {
114 if(filenameindex == -1)
115 filenameindex = i;
116 } else if(isstringprefix(argv[i], "--audio-rate=")) {
117 std::string val = settingvalue(argv[i]);
118 char* x;
119 unsigned long rate = strtoul(val.c_str(), &x, 10);
120 if(*x || !rate || rate > 1000000) {
121 std::cerr << "--audio-rate: Bad audio rate" << std::endl;
122 return 1;
124 audiorate = rate;
125 } else if(isstringprefix(argv[i], "--speed=")) {
126 std::string val = settingvalue(argv[i]);
127 char* x;
128 unsigned long rate = strtoul(val.c_str(), &x, 10);
129 if(*x || !rate || rate > 1000) {
130 std::cerr << "--speed: Bad speed" << std::endl;
131 return 1;
133 percentspeed = rate;
134 } else if(isstringprefix(argv[i], "--audio-mixer-")) {
135 //We process these later.
136 } else if(isstringprefix(argv[i], "--video-hardsub-")) {
137 //We process these later.
138 } else if(!strcmp(argv[i], "--"))
139 split = true;
140 else {
141 std::cerr << "Bad option: " << argv[i] << "." << std::endl;
142 return 1;
144 } catch(std::exception& e) {
145 std::cerr << "Error processing option: " << argv[i] << ":" << e.what() << std::endl;
146 return 1;
149 std::list<subtitle*> subtitles;
150 hardsub_settings stsettings;
151 //Initialize audio processing.
152 mixer& mix = *new mixer();
153 packet_demux ademux(mix, audiorate);
154 timecounter acounter(audiorate);
156 try {
157 process_audio_resampler_options(ademux, "--audio-mixer-", argc, argv);
158 subtitles = process_hardsubs_options(stsettings, "--video-hardsub-", argc, argv);
159 } catch(std::exception& e) {
160 std::cerr << "Error processing options: " << e.what() << std::endl;
161 return 1;
164 if(filenameindex < 0) {
165 std::cout << "usage: " << argv[0] << " [<options>] [--] <filename>..." << std::endl;
166 std::cout << "Show video contained in stream <filename> in window." << std::endl;
167 std::cout << "--speed=<speed>" << std::endl;
168 std::cout << "\tSet speed to <speed>%." << std::endl;
169 print_hardsubs_help("--video-hardsub-");
170 print_audio_resampler_help("--audio-mixer-");
171 return 1;
173 read_channel* in = new read_channel(argv[filenameindex]);
175 //Video stuff.
176 SDL_Surface* swsurf = NULL;
177 SDL_Surface* hwsurf = NULL;
178 struct packet* p = NULL;
179 unsigned prev_width = -1;
180 unsigned prev_height = -1;
182 if(SDL_Init(SDL_INIT_VIDEO) < 0) {
183 std::cerr << "Can't initialize SDL." << std::endl;
184 return 1;
187 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
188 uint32_t rmask = 0xFF000000;
189 uint32_t gmask = 0x00FF0000;
190 uint32_t bmask = 0x0000FF00;
191 #else
192 uint32_t rmask = 0x000000FF;
193 uint32_t gmask = 0x0000FF00;
194 uint32_t bmask = 0x00FF0000;
195 #endif
196 int enable_debug = 0;
197 if(getenv("PLAYDUMP_STATS"))
198 enable_debug = 1;
200 uint64_t lagged_frames = 0, total_frames = 0;
201 uint32_t last_realtime_second = 0;
202 timebase = SDL_GetTicks();
203 uint64_t audiosamples = 0;
204 std::list<image_frame_rgbx*> picture_buffer;
205 std::list<uint64_t> picture_stamp;
206 uint64_t first_stamp = 0;
208 SDL_AudioSpec aspec;
209 aspec.freq = audiorate * percentspeed / 100;
210 aspec.format = AUDIO_S16LSB;
211 aspec.channels = 2;
212 aspec.samples = BUFSAMPLES / 2;
213 aspec.callback = audio_callback;
214 if(SDL_OpenAudio(&aspec, NULL) < 0) {
215 std::cerr << "Can't initialize audio." << std::endl;
216 return 1;
217 } else
218 SDL_PauseAudio(0);
220 while(true) {
221 p = in->read();
222 //Correct the timestamp and update last seen time;
223 if(!p) {
224 //Exhausted current file, switch to next.
225 delete in;
226 timecorrection = last_timestamp;
227 std::cerr << "Time correction set to " << timecorrection << "." << std::endl;
228 filenameindex = next_filename_index(argc, argv, filenameindex);
229 if(filenameindex < 0)
230 break; //No more files.
231 in = new read_channel(argv[filenameindex]);
232 continue;
233 } else {
234 p->rp_timestamp += timecorrection;
235 last_timestamp = p->rp_timestamp;
237 //If we are too far ahead, slow down a bit.
238 if(audiobuffer.size() > MAXSAMPLES)
239 SDL_Delay(10);
240 while(p->rp_timestamp > acounter) {
241 acounter++;
242 //Extract audio sample.
243 sample_number_t s = ademux.nextsample();
244 SDL_LockAudio();
245 audiobuffer.push_back(s);
246 SDL_UnlockAudio();
247 audiosamples++;
249 if(p->rp_major == 5) {
250 //This is gameinfo packet.
251 subtitle_process_gameinfo(subtitles, *p);
252 continue;
253 } else if(p->rp_major != 0) {
254 //Process the audio packet.
255 ademux.sendpacket(*p);
256 delete p;
257 continue; //Not image.
259 uint32_t timenow = SDL_GetTicks();
260 uint32_t realtime = timenow - timebase;
261 total_frames++;
262 if(audiobuffer.size() < MINSAMPLES && p->rp_timestamp > 0) {
263 lagged_frames++;
264 delete p;
265 continue; //Behind deadline, try to catch up.
267 if(enable_debug && realtime / 1000 > last_realtime_second) {
268 last_realtime_second = realtime / 1000;
269 std::cout << "\e[1GTime " << last_realtime_second << "s: Frames: " << total_frames
270 << "(lagged:" << lagged_frames << "), Audio: " << audiosamples << "("
271 << audiobuffer.size() << ")";
272 std::cout << std::flush;
274 //Decode the frame.
275 picture_buffer.push_back(new image_frame_rgbx(*p));
276 if(picture_stamp.empty())
277 first_stamp = p->rp_timestamp / 1000000;
278 picture_stamp.push_back(p->rp_timestamp);
279 delete p;
280 while(true) {
281 if(picture_stamp.empty())
282 break;
283 uint32_t audiocorr = (SDL_GetTicks() - audio_stamp) * 100 / percentspeed;
284 if(first_stamp >= audio_clear)
285 if(first_stamp - audio_clear > audiocorr)
286 break;
287 uint64_t stamp = *picture_stamp.begin();
288 picture_stamp.pop_front();
289 if(!picture_stamp.empty())
290 first_stamp = *picture_stamp.begin() / 1000000;
292 image_frame_rgbx& frame = **picture_buffer.begin();
293 picture_buffer.pop_front();
295 if(prev_width != frame.get_width() || prev_height != frame.get_height()) {
296 hwsurf = SDL_SetVideoMode(frame.get_width(), frame.get_height(), 0, SDL_SWSURFACE |
297 SDL_DOUBLEBUF | SDL_ANYFORMAT);
298 swsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, frame.get_width(), frame.get_height(), 32,
299 rmask, gmask, bmask, 0);
301 prev_width = frame.get_width();
302 prev_height = frame.get_height();
303 for(std::list<subtitle*>::iterator j = subtitles.begin(); j != subtitles.end(); ++j)
304 if((*j)->timecode <= stamp && (*j)->timecode + (*j)->duration > stamp)
305 render_subtitle(frame, **j);
307 SDL_LockSurface(swsurf);
308 memcpy((unsigned char*)swsurf->pixels, frame.get_pixels(), 4 * prev_width * prev_height);
309 SDL_UnlockSurface(swsurf);
310 SDL_BlitSurface(swsurf, NULL, hwsurf, NULL);
311 SDL_Flip(hwsurf);
312 SDL_Event e;
313 if(SDL_PollEvent(&e) == 1 && e.type == SDL_QUIT)
314 goto quit; //Quit.
315 delete &frame;
318 quit:
319 SDL_Quit();
320 return 0;