Add option to skip the ROM patching screen
[lsnes.git] / avi / jmd.cpp
blobb6e0529354953991b3f739a2e1bb15d84828f2f7
1 #include "jmd.hpp"
2 #include <iostream>
3 #include <zlib.h>
4 #include <stdexcept>
5 #include <cstring>
7 namespace
9 void write32(char* x, uint64_t v)
11 x[0] = (v >> 24);
12 x[1] = (v >> 16);
13 x[2] = (v >> 8);
14 x[3] = v;
18 void jmd_dumper::video(uint64_t ts, uint32_t* memory, uint32_t width, uint32_t height)
20 frame_buffer f;
21 f.ts = ts;
22 size_t fsize = 0;
23 //We'll compress the frame here.
24 f.data = compress_frame(memory, width, height);
25 frames.push_back(f);
26 flush_buffers(false);
29 void jmd_dumper::audio(uint64_t ts, short l, short r)
31 sample_buffer s;
32 s.ts = ts;
33 s.l = l;
34 s.r = r;
35 samples.push_back(s);
36 flush_buffers(false);
39 jmd_dumper::jmd_dumper(const std::string& filename, unsigned level)
41 clevel = level;
42 jmd.open(filename.c_str(), std::ios::out | std::ios::binary);
43 if(!jmd)
44 throw std::runtime_error("Can't open output JMD file.");
45 last_written_ts = 0;
46 //Write the segment tables.
47 //Stream #0 is video.
48 //Stream #1 is PCM audio.
49 //Stream #2 is Gameinfo.
50 //Stream #3 is Dummy.
51 char header[] = {
52 /* Magic */
53 -1, -1, 0x4A, 0x50, 0x43, 0x52, 0x52, 0x4D, 0x55, 0x4C, 0x54, 0x49, 0x44, 0x55, 0x4D, 0x50,
54 /* Channel count. */
55 0x00, 0x04,
56 /* Video channel header. */
57 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 'v', 'i',
58 /* Audio channel header. */
59 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 'a', 'u',
60 /* Gameinfo channel header. */
61 0x00, 0x02, 0x00, 0x05, 0x00, 0x02, 'g', 'i',
62 /* Dummy channel header. */
63 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 'd', 'u'
65 jmd.write(header, sizeof(header));
66 if(!jmd)
67 throw std::runtime_error("Can't write JMD header and segment table");
70 jmd_dumper::~jmd_dumper()
72 try {
73 end(last_written_ts);
74 } catch(...) {
78 void jmd_dumper::end(uint64_t ts)
80 flush_buffers(true);
81 if(last_written_ts > ts) {
82 jmd.close();
83 return;
85 char dummypacket[8] = {0x00, 0x03};
86 write32(dummypacket + 2, ts - last_written_ts);
87 last_written_ts = ts;
88 jmd.write(dummypacket, sizeof(dummypacket));
89 if(!jmd)
90 throw std::runtime_error("Can't write JMD ending dummy packet");
91 jmd.close();
94 void jmd_dumper::gameinfo(const std::string& gamename, const std::string& authors, uint64_t gametime,
95 uint64_t rerecords)
97 //FIXME: Implement this.
100 void jmd_dumper::flush_buffers(bool force)
102 while(!frames.empty() || !samples.empty()) {
103 if(frames.empty() || samples.empty()) {
104 if(!force)
105 return;
106 else if(!frames.empty()) {
107 frame_buffer& f = frames.front();
108 flush_frame(f);
109 frames.pop_front();
110 } else if(!samples.empty()) {
111 sample_buffer& s = samples.front();
112 flush_sample(s);
113 samples.pop_front();
115 continue;
117 frame_buffer& f = frames.front();
118 sample_buffer& s = samples.front();
119 if(f.ts <= s.ts) {
120 flush_frame(f);
121 frames.pop_front();
122 } else {
123 flush_sample(s);
124 samples.pop_front();
129 void jmd_dumper::flush_frame(frame_buffer& f)
131 char videopacketh[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
132 write32(videopacketh + 2, f.ts - last_written_ts);
133 last_written_ts = f.ts;
134 unsigned lneed = 0;
135 if(f.data.size() >= (1ULL << 63))
136 videopacketh[7 + lneed++] = 0x80 | ((f.data.size() >> 63) & 0x7F);
137 if(f.data.size() >= (1ULL << 56))
138 videopacketh[7 + lneed++] = 0x80 | ((f.data.size() >> 56) & 0x7F);
139 if(f.data.size() >= (1ULL << 49))
140 videopacketh[7 + lneed++] = 0x80 | ((f.data.size() >> 49) & 0x7F);
141 if(f.data.size() >= (1ULL << 42))
142 videopacketh[7 + lneed++] = 0x80 | ((f.data.size() >> 42) & 0x7F);
143 if(f.data.size() >= (1ULL << 35))
144 videopacketh[7 + lneed++] = 0x80 | ((f.data.size() >> 35) & 0x7F);
145 if(f.data.size() >= (1ULL << 28))
146 videopacketh[7 + lneed++] = 0x80 | ((f.data.size() >> 28) & 0x7F);
147 if(f.data.size() >= (1ULL << 21))
148 videopacketh[7 + lneed++] = 0x80 | ((f.data.size() >> 21) & 0x7F);
149 if(f.data.size() >= (1ULL << 14))
150 videopacketh[7 + lneed++] = 0x80 | ((f.data.size() >> 14) & 0x7F);
151 if(f.data.size() >= (1ULL << 7))
152 videopacketh[7 + lneed++] = 0x80 | ((f.data.size() >> 7) & 0x7F);
153 videopacketh[7 + lneed++] = (f.data.size() & 0x7F);
155 jmd.write(videopacketh, 7 + lneed);
156 if(!jmd)
157 throw std::runtime_error("Can't write JMD video packet header");
158 if(f.data.size() > 0)
159 jmd.write(&f.data[0], f.data.size());
160 if(!jmd)
161 throw std::runtime_error("Can't write JMD video packet body");
164 void jmd_dumper::flush_sample(sample_buffer& s)
166 char soundpacket[12] = {0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04};
167 write32(soundpacket + 2, s.ts - last_written_ts);
168 last_written_ts = s.ts;
169 soundpacket[8] = (s.l >> 8) & 0xFF;
170 soundpacket[9] = s.l & 0xFF;
171 soundpacket[10] = (s.r >> 8) & 0xFF;
172 soundpacket[11] = s.r & 0xFF;
173 jmd.write(soundpacket, sizeof(soundpacket));
174 if(!jmd)
175 throw std::runtime_error("Can't write JMD sound packet");
178 #define INBUF_PIXELS 4096
179 #define OUTBUF_ADVANCE 4096
181 std::vector<char> jmd_dumper::compress_frame(uint32_t* memory, uint32_t width, uint32_t height)
183 std::vector<char> ret;
184 z_stream stream;
185 memset(&stream, 0, sizeof(stream));
186 if(deflateInit(&stream, clevel) != Z_OK)
187 throw std::runtime_error("Can't initialize zlib stream");
189 size_t usize = 4;
190 ret.resize(4);
191 ret[0] = (width >> 8);
192 ret[1] = width;
193 ret[2] = (height >> 8);
194 ret[3] = height;
195 uint8_t input_buffer[4 * INBUF_PIXELS];
196 size_t ptr = 0;
197 size_t pixels = static_cast<size_t>(width) * height;
198 bool input_clear = true;
199 bool flushed = false;
200 size_t bsize = 0;
201 uint32_t _magic = 0x18100800;
202 const uint8_t* magic = reinterpret_cast<const uint8_t*>(&magic);
203 while(1) {
204 if(input_clear) {
205 size_t pixel = ptr;
206 for(unsigned i = 0; i < INBUF_PIXELS && pixel < pixels; i++, pixel++) {
207 input_buffer[4 * i + 0] = memory[pixel];
208 input_buffer[4 * i + 1] = memory[pixel] >> 8;
209 input_buffer[4 * i + 2] = memory[pixel] >> 16;
210 input_buffer[4 * i + 3] = 0;
212 bsize = pixel - ptr;
213 ptr = pixel;
214 input_clear = false;
215 //Now the input data to compress is in input_buffer, bsize elements.
216 stream.next_in = reinterpret_cast<uint8_t*>(input_buffer);
217 stream.avail_in = 4 * bsize;
219 if(!stream.avail_out) {
220 if(flushed)
221 usize += (OUTBUF_ADVANCE - stream.avail_out);
222 flushed = true;
223 ret.resize(usize + OUTBUF_ADVANCE);
224 stream.next_out = reinterpret_cast<uint8_t*>(&ret[usize]);
225 stream.avail_out = OUTBUF_ADVANCE;
227 int r = deflate(&stream, (ptr == pixels) ? Z_FINISH : 0);
228 if(r == Z_STREAM_END)
229 break;
230 if(r != Z_OK)
231 throw std::runtime_error("Can't deflate data");
232 if(!stream.avail_in)
233 input_clear = true;
235 usize += (OUTBUF_ADVANCE - stream.avail_out);
236 deflateEnd(&stream);
238 ret.resize(usize);
239 return ret;