annotate src/common/nesvideos-piece.cpp @ 1:f9f4f1b99eed

importing src directory
author Robert McIntyre <rlm@mit.edu>
date Sat, 03 Mar 2012 10:31:27 -0600
parents
children
rev   line source
rlm@1 1 #include <cmath>
rlm@1 2 #include <cstdio>
rlm@1 3 #include <string>
rlm@1 4 #include <vector>
rlm@1 5
rlm@1 6 /* Note: This module assumes everyone uses RGB15 as display depth */
rlm@1 7
rlm@1 8 static std::string VIDEO_CMD =
rlm@1 9 "mencoder - -o test0.avi"
rlm@1 10 " -noskip -mc 0"
rlm@1 11 " -ovc lavc"
rlm@1 12 " -oac mp3lame"
rlm@1 13 " -lameopts preset=256:aq=2:mode=3"
rlm@1 14 " -lavcopts vcodec=ffv1:context=0:format=BGR32:coder=0:vstrict=-1"
rlm@1 15 " >& mencoder.log";
rlm@1 16
rlm@1 17 static void FlushWrite(FILE* fp, const unsigned char*buf, unsigned length);
rlm@1 18
rlm@1 19 #define BGR24 (0x42475218) // BGR24 fourcc
rlm@1 20 #define BGR16 (0x42475210) // BGR16 fourcc
rlm@1 21 #define BGR15 (0x4247520F) // BGR15 fourcc
rlm@1 22
rlm@1 23 static FILE* (*openFunc) (const char*, const char*) = NULL;
rlm@1 24 static int (*closeFunc) (FILE*) = NULL;
rlm@1 25
rlm@1 26 #if (defined(WIN32) || defined(win32)) // capital is standard, but check for either
rlm@1 27 #include <cstdlib>
rlm@1 28 #define popen _popen;
rlm@1 29 #define pclose _pclose;
rlm@1 30 #endif
rlm@1 31
rlm@1 32 #define u32(n) (n)&255,((n)>>8)&255,((n)>>16)&255,((n)>>24)&255
rlm@1 33 #define u16(n) (n)&255,((n)>>8)&255
rlm@1 34 #define s4(s) s[0],s[1],s[2],s[3]
rlm@1 35
rlm@1 36 static const unsigned FPS_SCALE = (0x1000000);
rlm@1 37
rlm@1 38 // general-purpose A/V sync debugging, ignored unless explicitly enabled with NESVideoEnableDebugging
rlm@1 39 static void (*debugVideoMessageFunc)(const char *msg) = NULL;
rlm@1 40 static void (*debugAudioMessageFunc)(const char *msg) = NULL;
rlm@1 41 // logo adds 1 "frame" to audio, so offset that (A/V frames shouldn't necessarily match up depending on the rates, but should at least make them start out matching in case they do)
rlm@1 42 static unsigned audioFramesWritten=0, videoFramesWritten=1;
rlm@1 43 static double audioSecondsWritten=0, videoSecondsWritten=0;
rlm@1 44
rlm@1 45
rlm@1 46 static class AVI
rlm@1 47 {
rlm@1 48 FILE* avifp;
rlm@1 49
rlm@1 50 bool KnowVideo;
rlm@1 51 unsigned width;
rlm@1 52 unsigned height;
rlm@1 53 unsigned fps_scaled;
rlm@1 54 std::vector<unsigned char> VideoBuffer;
rlm@1 55
rlm@1 56 bool KnowAudio;
rlm@1 57 unsigned rate;
rlm@1 58 unsigned chans;
rlm@1 59 unsigned bits;
rlm@1 60 std::vector<unsigned char> AudioBuffer;
rlm@1 61
rlm@1 62 public:
rlm@1 63 AVI() :
rlm@1 64 avifp(NULL),
rlm@1 65 KnowVideo(false),
rlm@1 66 KnowAudio(false)
rlm@1 67 {
rlm@1 68 }
rlm@1 69 ~AVI()
rlm@1 70 {
rlm@1 71 if(avifp) closeFunc(avifp);
rlm@1 72 }
rlm@1 73
rlm@1 74 void Audio(unsigned r,unsigned b,unsigned c,
rlm@1 75 const unsigned char*d, unsigned nsamples)
rlm@1 76 {
rlm@1 77 if(!KnowAudio)
rlm@1 78 {
rlm@1 79 rate = r;
rlm@1 80 chans = c;
rlm@1 81 bits = b;
rlm@1 82 KnowAudio = true;
rlm@1 83 CheckFlushing();
rlm@1 84 }
rlm@1 85 unsigned bytes = nsamples*chans*(bits/8);
rlm@1 86
rlm@1 87 if(debugAudioMessageFunc)
rlm@1 88 {
rlm@1 89 audioFramesWritten++;
rlm@1 90 audioSecondsWritten += (double)nsamples / (double)rate; // += bytes times seconds per byte
rlm@1 91 char temp [64];
rlm@1 92 sprintf(temp, "A: %.2lf s, %d f", audioSecondsWritten, audioFramesWritten);
rlm@1 93 debugAudioMessageFunc(temp);
rlm@1 94 }
rlm@1 95
rlm@1 96 if(KnowVideo)
rlm@1 97 SendAudioFrame(d, bytes);
rlm@1 98 else
rlm@1 99 {
rlm@1 100 AudioBuffer.insert(AudioBuffer.end(), d, d+bytes);
rlm@1 101 fprintf(stderr, "Buffering %u bytes of audio\n", bytes);
rlm@1 102 }
rlm@1 103 }
rlm@1 104 void Video(unsigned w,unsigned h,unsigned f, const unsigned char*d)
rlm@1 105 {
rlm@1 106 if(!KnowVideo)
rlm@1 107 {
rlm@1 108 width=w;
rlm@1 109 height=h;
rlm@1 110 fps_scaled=f;
rlm@1 111 KnowVideo = true;
rlm@1 112 CheckFlushing();
rlm@1 113 }
rlm@1 114
rlm@1 115 unsigned bytes = width*height*2;
rlm@1 116
rlm@1 117 //std::vector<unsigned char> tmp(bytes, 'k');
rlm@1 118 //d = &tmp[0];
rlm@1 119
rlm@1 120 if(debugVideoMessageFunc)
rlm@1 121 {
rlm@1 122 videoFramesWritten++;
rlm@1 123 videoSecondsWritten += (double)FPS_SCALE / (double)fps_scaled; // += seconds per frame
rlm@1 124 char temp [64];
rlm@1 125 sprintf(temp, "V: %.2lf s, %d f", videoSecondsWritten, videoFramesWritten);
rlm@1 126 debugVideoMessageFunc(temp);
rlm@1 127 }
rlm@1 128
rlm@1 129 if(KnowAudio)
rlm@1 130 SendVideoFrame(d, bytes);
rlm@1 131 else
rlm@1 132 {
rlm@1 133 VideoBuffer.insert(VideoBuffer.end(), d, d+bytes);
rlm@1 134 fprintf(stderr, "Buffering %u bytes of video\n", bytes);
rlm@1 135 }
rlm@1 136 }
rlm@1 137
rlm@1 138 private:
rlm@1 139 void CheckFlushing()
rlm@1 140 {
rlm@1 141 //AudioBuffer.clear();
rlm@1 142 //VideoBuffer.clear();
rlm@1 143
rlm@1 144 if(KnowAudio && KnowVideo)
rlm@1 145 {
rlm@1 146 unsigned last_offs;
rlm@1 147
rlm@1 148 // Flush Audio
rlm@1 149
rlm@1 150 last_offs = 0;
rlm@1 151 while(last_offs < AudioBuffer.size())
rlm@1 152 {
rlm@1 153 unsigned bytes = rate / (fps_scaled / FPS_SCALE);
rlm@1 154 bytes *= chans*(bits/8);
rlm@1 155
rlm@1 156 unsigned remain = AudioBuffer.size() - last_offs;
rlm@1 157 if(bytes > remain) bytes = remain;
rlm@1 158 if(!bytes) break;
rlm@1 159
rlm@1 160 unsigned begin = last_offs;
rlm@1 161 last_offs += bytes;
rlm@1 162 SendAudioFrame(&AudioBuffer[begin], bytes);
rlm@1 163 }
rlm@1 164 AudioBuffer.erase(AudioBuffer.begin(), AudioBuffer.begin()+last_offs);
rlm@1 165
rlm@1 166 // Flush Video
rlm@1 167
rlm@1 168 last_offs = 0;
rlm@1 169 while(last_offs < VideoBuffer.size())
rlm@1 170 {
rlm@1 171 unsigned bytes = width*height*2;
rlm@1 172 unsigned remain = VideoBuffer.size() - last_offs;
rlm@1 173 if(bytes > remain) bytes = remain;
rlm@1 174 if(!bytes)break;
rlm@1 175
rlm@1 176 unsigned begin = last_offs;
rlm@1 177 last_offs += bytes;
rlm@1 178 SendVideoFrame(&VideoBuffer[begin], bytes);
rlm@1 179 }
rlm@1 180 VideoBuffer.erase(VideoBuffer.begin(), VideoBuffer.begin()+last_offs);
rlm@1 181 }
rlm@1 182 }
rlm@1 183
rlm@1 184 void SendVideoFrame(const unsigned char* vidbuf, unsigned framesize)
rlm@1 185 {
rlm@1 186 CheckBegin();
rlm@1 187
rlm@1 188 //fprintf(stderr, "Writing 00dc of %u bytes\n", framesize);
rlm@1 189
rlm@1 190 const unsigned char header[] = { s4("00dc"), u32(framesize) };
rlm@1 191 FlushWrite(avifp, header, sizeof(header));
rlm@1 192 FlushWrite(avifp, vidbuf, framesize);
rlm@1 193 }
rlm@1 194
rlm@1 195 void SendAudioFrame(const unsigned char* audbuf, unsigned framesize)
rlm@1 196 {
rlm@1 197 CheckBegin();
rlm@1 198
rlm@1 199 //fprintf(stderr, "Writing 01wb of %u bytes\n", framesize);
rlm@1 200
rlm@1 201 const unsigned char header[] = { s4("01wb"), u32(framesize) };
rlm@1 202 FlushWrite(avifp, header, sizeof(header));
rlm@1 203 FlushWrite(avifp, audbuf, framesize);
rlm@1 204 }
rlm@1 205
rlm@1 206 void CheckBegin()
rlm@1 207 {
rlm@1 208 if(avifp) return;
rlm@1 209
rlm@1 210 if(!openFunc) openFunc = popen; // default
rlm@1 211 if(!closeFunc) closeFunc = pclose; // default
rlm@1 212
rlm@1 213 avifp = openFunc(VIDEO_CMD.c_str(), "wb");
rlm@1 214 if(!avifp) return;
rlm@1 215
rlm@1 216 const unsigned fourcc = BGR16;
rlm@1 217 const unsigned framesize = width*height*2;
rlm@1 218
rlm@1 219 const unsigned aud_rate = rate;
rlm@1 220 const unsigned aud_chans = chans;
rlm@1 221 const unsigned aud_bits = bits;
rlm@1 222
rlm@1 223 const unsigned nframes = 0; //unknown
rlm@1 224 const unsigned scale = FPS_SCALE;
rlm@1 225 const unsigned scaled_fps = fps_scaled;
rlm@1 226
rlm@1 227 const unsigned SIZE_strh_vids = 4 + 4*2 + 2*2 + 8*4 + 2*4;
rlm@1 228 const unsigned SIZE_strf_vids = 4*3 + 2*2 + 4*6;
rlm@1 229 const unsigned SIZE_strl_vids = 4+ 4+(4+SIZE_strh_vids) + 4+(4+SIZE_strf_vids);
rlm@1 230
rlm@1 231 const unsigned SIZE_strh_auds = 4 + 4*3 + 2*2 + 4*8 + 2*4;
rlm@1 232 const unsigned SIZE_strf_auds = 2*2 + 4*2 + 2*3;
rlm@1 233 const unsigned SIZE_strl_auds = 4+ 4+(4+SIZE_strh_auds) + 4+(4+SIZE_strf_auds);
rlm@1 234
rlm@1 235 const unsigned SIZE_avih = 4*12;
rlm@1 236 const unsigned SIZE_hdrl = 4+4+ (4+SIZE_avih) + 4 + (4+SIZE_strl_vids) + 4 + (4+SIZE_strl_auds);
rlm@1 237 const unsigned SIZE_movi = 4 + nframes*(4+4+framesize);
rlm@1 238 const unsigned SIZE_avi = 4+4+ (4+SIZE_hdrl) + 4 + (4+SIZE_movi);
rlm@1 239
rlm@1 240 const unsigned char AVIheader[] =
rlm@1 241 {
rlm@1 242 s4("RIFF"),
rlm@1 243 u32(SIZE_avi),
rlm@1 244 s4("AVI "),
rlm@1 245
rlm@1 246 // HEADER
rlm@1 247
rlm@1 248 s4("LIST"),
rlm@1 249 u32(SIZE_hdrl),
rlm@1 250 s4("hdrl"),
rlm@1 251
rlm@1 252 s4("avih"),
rlm@1 253 u32(SIZE_avih),
rlm@1 254 u32(0),
rlm@1 255 u32(0),
rlm@1 256 u32(0),
rlm@1 257 u32(0),
rlm@1 258 u32(nframes),
rlm@1 259 u32(0),
rlm@1 260 u32(2), // two streams
rlm@1 261 u32(0),
rlm@1 262 u32(0),
rlm@1 263 u32(0),
rlm@1 264 u32(0),
rlm@1 265 u32(0),
rlm@1 266
rlm@1 267 // VIDEO HEADER
rlm@1 268
rlm@1 269 s4("LIST"),
rlm@1 270 u32(SIZE_strl_vids),
rlm@1 271 s4("strl"),
rlm@1 272
rlm@1 273 s4("strh"),
rlm@1 274 u32(SIZE_strh_vids),
rlm@1 275 s4("vids"),
rlm@1 276 u32(0),
rlm@1 277 u32(0),
rlm@1 278 u16(0),
rlm@1 279 u16(0),
rlm@1 280 u32(0),
rlm@1 281 u32(scale),
rlm@1 282 u32(scaled_fps),
rlm@1 283 u32(0),
rlm@1 284 u32(0),
rlm@1 285 u32(0),
rlm@1 286 u32(0),
rlm@1 287 u32(0),
rlm@1 288 u16(0),
rlm@1 289 u16(0),
rlm@1 290 u16(0),
rlm@1 291 u16(0),
rlm@1 292
rlm@1 293 s4("strf"),
rlm@1 294 u32(SIZE_strf_vids),
rlm@1 295 u32(0),
rlm@1 296 u32(width),
rlm@1 297 u32(height),
rlm@1 298 u16(0),
rlm@1 299 u16(0),
rlm@1 300 u32(fourcc),
rlm@1 301 u32(0),
rlm@1 302 u32(0),
rlm@1 303 u32(0),
rlm@1 304 u32(0),
rlm@1 305 u32(0),
rlm@1 306
rlm@1 307 // AUDIO HEADER
rlm@1 308
rlm@1 309 s4("LIST"),
rlm@1 310 u32(SIZE_strl_auds),
rlm@1 311 s4("strl"),
rlm@1 312
rlm@1 313 s4("strh"),
rlm@1 314 u32(SIZE_strh_auds),
rlm@1 315 s4("auds"),
rlm@1 316 u32(0), //fourcc
rlm@1 317 u32(0), //handler
rlm@1 318 u32(0), //flags
rlm@1 319 u16(0), //prio
rlm@1 320 u16(0), //lang
rlm@1 321 u32(0), //init frames
rlm@1 322 u32(1), //scale
rlm@1 323 u32(aud_rate),
rlm@1 324 u32(0), //start
rlm@1 325 u32(0), //rate*length
rlm@1 326 u32(1048576), //suggested bufsize
rlm@1 327 u32(0), //quality
rlm@1 328 u32(aud_chans * (aud_bits / 8)), //sample size
rlm@1 329 u16(0), //frame size
rlm@1 330 u16(0),
rlm@1 331 u16(0),
rlm@1 332 u16(0),
rlm@1 333
rlm@1 334 s4("strf"),
rlm@1 335 u32(SIZE_strf_auds),
rlm@1 336 u16(1), // pcm format
rlm@1 337 u16(aud_chans),
rlm@1 338 u32(aud_rate),
rlm@1 339 u32(aud_rate * aud_chans * (aud_bits/8)), // samples per second
rlm@1 340 u16(aud_chans * (aud_bits/8)), //block align
rlm@1 341 u16(aud_bits), //bits
rlm@1 342 u16(0), //cbSize
rlm@1 343
rlm@1 344 // MOVIE
rlm@1 345
rlm@1 346 s4("LIST"),
rlm@1 347 u32(SIZE_movi),
rlm@1 348 s4("movi")
rlm@1 349 };
rlm@1 350
rlm@1 351 FlushWrite(avifp, AVIheader, sizeof(AVIheader));
rlm@1 352 }
rlm@1 353 } AVI;
rlm@1 354
rlm@1 355 extern "C"
rlm@1 356 {
rlm@1 357 int LoggingEnabled = 0; /* 0=no, 1=yes, 2=recording! */
rlm@1 358
rlm@1 359 const char* NESVideoGetVideoCmd()
rlm@1 360 {
rlm@1 361 return VIDEO_CMD.c_str();
rlm@1 362 }
rlm@1 363 void NESVideoSetVideoCmd(const char *cmd)
rlm@1 364 {
rlm@1 365 VIDEO_CMD = cmd;
rlm@1 366 }
rlm@1 367 void NESVideoEnableDebugging( void videoMessageFunc(const char *msg), void audioMessageFunc(const char *msg) )
rlm@1 368 {
rlm@1 369 debugVideoMessageFunc = videoMessageFunc;
rlm@1 370 debugAudioMessageFunc = audioMessageFunc;
rlm@1 371 }
rlm@1 372 void NESVideoSetFileFuncs( FILE* open(const char *,const char *), int close(FILE*) )
rlm@1 373 {
rlm@1 374 openFunc = open;
rlm@1 375 closeFunc = close;
rlm@1 376 }
rlm@1 377
rlm@1 378 void NESVideoLoggingVideo
rlm@1 379 (const void*data, unsigned width,unsigned height,
rlm@1 380 unsigned fps_scaled
rlm@1 381 )
rlm@1 382 {
rlm@1 383 if(LoggingEnabled < 2) return;
rlm@1 384
rlm@1 385 unsigned LogoFrames = fps_scaled >> 24;
rlm@1 386
rlm@1 387 static bool First = true;
rlm@1 388 if(First)
rlm@1 389 {
rlm@1 390 First=false;
rlm@1 391 /* Bisqwit's logo addition routine. */
rlm@1 392 /* If you don't have his files, this function does nothing
rlm@1 393 * and it does not matter at all.
rlm@1 394 */
rlm@1 395
rlm@1 396 const char *background =
rlm@1 397 width==320 ? "logo320_240"
rlm@1 398 : width==160 ? "logo160_144"
rlm@1 399 : width==240 ? "logo240_160"
rlm@1 400 : height>224 ? "logo256_240"
rlm@1 401 : "logo256_224";
rlm@1 402
rlm@1 403 /* Note: This should be 1 second long. */
rlm@1 404 for(unsigned frame = 0; frame < LogoFrames; ++frame)
rlm@1 405 {
rlm@1 406 char Buf[4096];
rlm@1 407 sprintf(Buf, "/shares/home/bisqwit/povray/nesvlogo/%s_f%u.tga",
rlm@1 408 background, frame);
rlm@1 409
rlm@1 410 FILE*fp = fopen(Buf, "rb");
rlm@1 411 if(!fp) // write blackness when missing frames to keep the intro 1 second long:
rlm@1 412 {
rlm@1 413 unsigned bytes = width*height*2;
rlm@1 414 unsigned char* buf = (unsigned char*)malloc(bytes);
rlm@1 415 if(buf)
rlm@1 416 {
rlm@1 417 memset(buf,0,bytes);
rlm@1 418 AVI.Video(width,height,fps_scaled, buf);
rlm@1 419 if(debugVideoMessageFunc) videoFramesWritten--;
rlm@1 420 free(buf);
rlm@1 421 }
rlm@1 422 }
rlm@1 423 else // write 1 frame of the logo:
rlm@1 424 {
rlm@1 425 int idlen = fgetc(fp);
rlm@1 426 /* Silently ignore all other header data.
rlm@1 427 * These files are assumed to be uncompressed BGR24 tga files with Y swapped.
rlm@1 428 * Even their geometry is assumed to match perfectly.
rlm@1 429 */
rlm@1 430 fseek(fp, 1+1+2+2+1+ /*org*/2+2+ /*geo*/2+2+ 1+1+idlen, SEEK_CUR);
rlm@1 431
rlm@1 432 bool yflip=true;
rlm@1 433 std::vector<unsigned char> data(width*height*3);
rlm@1 434 for(unsigned y=height; y-->0; )
rlm@1 435 fread(&data[y*width*3], 1, width*3, fp);
rlm@1 436 fclose(fp);
rlm@1 437
rlm@1 438 std::vector<unsigned short> result(width*height);
rlm@1 439 for(unsigned pos=0, max=result.size(); pos<max; ++pos)
rlm@1 440 {
rlm@1 441 unsigned usepos = pos;
rlm@1 442 if(yflip)
rlm@1 443 {
rlm@1 444 unsigned y = pos/width;
rlm@1 445 usepos = (usepos%width) + (height-y-1)*width;
rlm@1 446 }
rlm@1 447
rlm@1 448 unsigned B = data[usepos*3+0];
rlm@1 449 unsigned G = data[usepos*3+1];
rlm@1 450 unsigned R = data[usepos*3+2];
rlm@1 451 result[pos] = ((B*31/255)<<0)
rlm@1 452 | ((G*63/255)<<5)
rlm@1 453 | ((R*31/255)<<11);
rlm@1 454 }
rlm@1 455 AVI.Video(width,height,fps_scaled, (const unsigned char*)&result[0]);
rlm@1 456 if(debugVideoMessageFunc) videoFramesWritten--;
rlm@1 457 }
rlm@1 458 }
rlm@1 459 }
rlm@1 460 AVI.Video(width,height,fps_scaled, (const unsigned char*) data);
rlm@1 461 }
rlm@1 462
rlm@1 463 void NESVideoLoggingAudio
rlm@1 464 (const void*data,
rlm@1 465 unsigned rate, unsigned bits, unsigned chans,
rlm@1 466 unsigned nsamples)
rlm@1 467 {
rlm@1 468 if(LoggingEnabled < 2) return;
rlm@1 469
rlm@1 470 static bool First = true;
rlm@1 471 if(First)
rlm@1 472 {
rlm@1 473 First=false;
rlm@1 474
rlm@1 475 const unsigned n = rate; // assumes 1 second of logo to write silence for
rlm@1 476 if(n > 0)
rlm@1 477 {
rlm@1 478 unsigned bytes = n*chans*(bits/8);
rlm@1 479 unsigned char* buf = (unsigned char*)malloc(bytes);
rlm@1 480 if(buf)
rlm@1 481 {
rlm@1 482 memset(buf,0,bytes);
rlm@1 483 AVI.Audio(rate,bits,chans, buf, n);
rlm@1 484 free(buf);
rlm@1 485 }
rlm@1 486 }
rlm@1 487 }
rlm@1 488
rlm@1 489 AVI.Audio(rate,bits,chans, (const unsigned char*) data, nsamples);
rlm@1 490 }
rlm@1 491 } /* extern "C" */
rlm@1 492
rlm@1 493
rlm@1 494
rlm@1 495 static void FlushWrite(FILE* fp, const unsigned char*buf, unsigned length)
rlm@1 496 {
rlm@1 497 /// unsigned failures = 0;
rlm@1 498 /// const static int FAILURE_THRESH = 8092; // don't want to loop infinitely if we keep failing to make progress - actually maybe you would want this, so the checking is disabled
rlm@1 499 while(length > 0 /*&& failures < FAILURE_THRESH*/)
rlm@1 500 {
rlm@1 501 unsigned written = fwrite(buf, 1, length, fp);
rlm@1 502 /// if(written == 0)
rlm@1 503 /// failures++;
rlm@1 504 /// else
rlm@1 505 /// {
rlm@1 506 length -= written;
rlm@1 507 buf += written;
rlm@1 508 /// failures = 0;
rlm@1 509 /// }
rlm@1 510 }
rlm@1 511 /// if(failures >= FAILURE_THRESH)
rlm@1 512 /// {
rlm@1 513 /// fprintf(stderr, "FlushWrite() failed to write %d bytes %d times - giving up.", length, failures);
rlm@1 514 /// LoggingEnabled = 0;
rlm@1 515 /// }
rlm@1 516 }
rlm@1 517
rlm@1 518 // for the UB tech
rlm@1 519 #undef BGR24
rlm@1 520 #undef BGR16
rlm@1 521 #undef BGR15
rlm@1 522
rlm@1 523 #undef u32
rlm@1 524 #undef u16
rlm@1 525 #undef s4