2 * Copyright (C) 2002-2013 The DOSBox Team
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 * D translation by Ketmar // Invisible Vector
19 module iv
.zmbv
/*is aliced*/;
22 // ////////////////////////////////////////////////////////////////////////// //
26 mixin(MyException
!"ZMBVError");
29 // ////////////////////////////////////////////////////////////////////////// //
51 // returns Format.None for unknown bpp
52 static Format
bpp2format(T
) (T bpp
) @safe pure nothrow @nogc if (__traits(isIntegral
, T
)) {
54 case 8: return Format
.bpp8
;
55 case 15: return Format
.bpp15
;
56 case 16: return Format
.bpp16
;
57 case 32: return Format
.bpp32
;
58 default: return Format
.None
;
63 // returns 0 for unknown format
64 static ubyte format2bpp (Format fmt
) @safe pure nothrow @nogc {
66 case Format
.bpp8
: return 8;
67 case Format
.bpp15
: return 15;
68 case Format
.bpp16
: return 16;
69 case Format
.bpp32
: return 32;
75 // returns 0 for unknown format
76 static ubyte format2pixelSize (Format fmt
) @safe pure nothrow @nogc {
78 case Format
.bpp8
: return 1;
79 case Format
.bpp15
: case Format
.bpp16
: return 2;
80 case Format
.bpp32
: return 4;
86 static bool isValidFormat (Format fmt
) @safe pure nothrow @nogc {
100 import etc
.c
.zlib
: z_stream
;
104 // ZMBV format version
120 align(1) struct KeyframeHeader
{
130 Compression mCompression
= Compression
.ZLib
;
132 ubyte* oldFrame
, newFrame
;
133 ubyte* buf1
, buf2
, work
;
141 ubyte[256*3] mPalette
;
143 uint mHeight
, mWidth
, mPitch
;
151 // used in encoder only, but moved here for freeBuffers()
156 this (uint width
, uint height
) @trusted {
157 if (width
< 1 || height
< 1 || width
> 32768 || height
> 32768) throw new ZMBVError("invalid ZMBV dimensions");
160 mPitch
= mWidth
+2*MaxVector
;
163 ~this () @safe nothrow /*@nogc*/ {
167 // release all allocated memory, finish zstream, etc
168 final void clear () @trusted nothrow /*@nogc*/ {
173 protected abstract void zlibDeinit () @trusted nothrow /*@nogc*/;
176 @property uint width () const @safe pure nothrow @nogc { return mWidth
; }
177 @property uint height () const @safe pure nothrow @nogc { return mHeight
; }
178 @property Format
format () const @safe pure nothrow @nogc { return mFormat
; }
181 void freeBuffers () @trusted nothrow @nogc {
182 import core
.stdc
.stdlib
: free
;
183 if (outbuf
!is null) free(outbuf
);
184 if (blocks
!is null) free(blocks
);
185 if (buf1
!is null) free(buf1
);
186 if (buf2
!is null) free(buf2
);
187 if (work
!is null) free(work
);
196 void setupBuffers (Format fmt
, uint blockWidth
, uint blockHeight
) @trusted {
197 import core
.stdc
.stdlib
: malloc
;
198 import std
.exception
: enforce
;
200 pixelSize
= format2pixelSize(fmt
);
201 if (pixelSize
== 0) throw new ZMBVError("invalid ZMBV format");
202 if (blockWidth
< 1 || blockHeight
< 1) throw new ZMBVError("invalid ZMBV block size");
203 palSize
= (fmt
== Format
.bpp8 ?
256 : 0);
204 bufSize
= (mHeight
+2*MaxVector
)*mPitch
*pixelSize
+2048;
205 buf1
= cast(ubyte*)malloc(bufSize
);
206 buf2
= cast(ubyte*)malloc(bufSize
);
207 work
= cast(ubyte*)malloc(bufSize
);
208 scope(failure
) freeBuffers();
209 enforce(buf1
!is null && buf2
!is null && work
!is null);
211 uint xblocks
= (mWidth
/blockWidth
);
212 uint xleft
= mWidth
%blockWidth
;
213 if (xleft
) ++xblocks
;
215 uint yblocks
= (mHeight
/blockHeight
);
216 uint yleft
= mHeight
%blockHeight
;
217 if (yleft
) ++yblocks
;
219 blockCount
= yblocks
*xblocks
;
220 blocks
= cast(FrameBlock
*)malloc(FrameBlock
.sizeof
*blockCount
);
221 enforce(blocks
!is null);
224 foreach (immutable y
; 0..yblocks
) {
225 foreach (immutable x
; 0..xblocks
) {
226 blocks
[i
].start
= ((y
*blockHeight
)+MaxVector
)*mPitch
+(x
*blockWidth
)+MaxVector
;
227 blocks
[i
].dx
= (xleft
&& x
== xblocks
-1 ? xleft
: blockWidth
);
228 blocks
[i
].dy
= (yleft
&& y
== yblocks
-1 ? yleft
: blockHeight
);
233 buf1
[0..bufSize
] = 0;
234 buf2
[0..bufSize
] = 0;
235 work
[0..bufSize
] = 0;
242 // swap old frame and new frame
243 void swapFrames () @safe nothrow @nogc {
244 auto copyFrame
= newFrame
;
246 oldFrame
= copyFrame
;
251 // ////////////////////////////////////////////////////////////////////////// //
252 class Encoder
: Codec
{
254 static struct CodecVector
{
257 CodecVector
[441] vectorTable
= void;
259 ubyte mCompressionLevel
;
270 this (uint width
, uint height
, Format fmt
=Format
.None
, int complevel
=-1, Compression ctype
=Compression
.ZLib
) @trusted {
271 super(width
, height
);
272 if (fmt
!= Format
.None
&& !isValidFormat(fmt
)) throw new ZMBVError("invalid ZMBV format");
273 if (ctype
!= Compression
.None
&& ctype
!= Compression
.ZLib
) throw new ZMBVError("invalid ZMBV compression");
274 if (ctype
== Compression
.ZLib
) {
275 if (complevel
< 0) complevel
= 4;
276 if (complevel
> 9) complevel
= 9;
280 mCompression
= ctype
;
281 mCompressionLevel
= cast(ubyte)complevel
;
285 if (mCompression
== Compression
.ZLib
) {
286 import etc
.c
.zlib
: deflateInit
, Z_OK
;
287 if (deflateInit(&zstream
, complevel
) != Z_OK
) throw new ZMBVError("can't initialize ZLib stream");
288 zstreamInited
= true;
290 // allocate buffer here if we know the format
291 if (fmt
!= Format
.None
) fixOutBuffer();
294 protected override void zlibDeinit () @trusted nothrow /*@nogc*/ {
296 import etc
.c
.zlib
: deflateEnd
;
297 try deflateEnd(&zstream
); catch (Exception
) {}
298 zstreamInited
= false;
300 frameCount
= 0; // do it here as we have no other hooks in clear()
303 // (re)allocate buffer for compressed data
304 private final fixOutBuffer () @trusted {
305 usize nbs
= workBufferSize();
306 if (nbs
== 0) throw new ZMBVError("internal error");
307 if (nbs
> outbufSize
) {
308 import core
.stdc
.stdlib
: realloc
;
309 // we need bigger buffer
310 void* nb
= realloc(outbuf
, nbs
);
311 if (nb
is null) throw new ZMBVError("out of memory for compression buffer");
313 outbuf
= cast(ubyte*)nb
;
318 @property Compression
compression () const @safe pure nothrow @nogc { return mCompression
; }
319 @property ubyte compressionLevel () const @safe pure nothrow @nogc { return mCompressionLevel
; }
320 @property ulong processedFrames () const @safe pure nothrow @nogc { return frameCount
; }
322 void prepareFrame (PrepareFlags flags
, const(ubyte)[] pal
=null, Format fmt
=Format
.None
) @trusted {
323 import std
.algorithm
: min
;
325 if (flags
!= PrepareFlags
.None
&& flags
!= PrepareFlags
.Keyframe
) throw new ZMBVError("invalid flags");
326 if (fmt
== Format
.None
) {
327 if (mFormat
== Format
.None
) throw new ZMBVError("invalid format");
330 if (!isValidFormat(fmt
)) throw new ZMBVError("invalid format");
332 if (fmt
!= mFormat || frameCount
== 0) {
334 setupBuffers(fmt
, 16, 16);
335 flags
= PrepareFlags
.Keyframe
; // force a keyframe
340 // (re)allocate buffer for compressed data
343 writeDone
= 1; // for firstByte
345 // set a pointer to the first byte which will contain info about this frame
346 ubyte* firstByte
= outbuf
;
347 *firstByte
= 0; // frame flags
349 // reset the work buffer
351 if (flags
== PrepareFlags
.Keyframe
) {
353 *firstByte |
= FrameMask
.Keyframe
;
354 auto header
= cast(KeyframeHeader
*)(outbuf
+writeDone
);
355 header
.versionHi
= Version
.High
;
356 header
.versionLo
= Version
.Low
;
357 header
.compression
= cast(byte)mCompression
;
358 header
.format
= cast(ubyte)mFormat
;
359 header
.blockWidth
= 16;
360 header
.blockHeight
= 16;
361 writeDone
+= KeyframeHeader
.sizeof
;
364 immutable usize end
= min(pal
.length
, mPalette
.length
);
365 mPalette
[0..end
] = pal
[0..end
];
366 if (end
< mPalette
.length
) mPalette
[end
..$] = 0;
370 // keyframes get the full palette
371 work
[0..palSize
*3] = mPalette
[];
372 workUsed
+= palSize
*3;
375 if (mCompression
== Compression
.ZLib
) {
376 import etc
.c
.zlib
: deflateReset
, Z_OK
;
377 if (deflateReset(&zstream
) != Z_OK
) throw new ZMBVError("can't restart deflate stream");
380 if (palSize
&& pal
.length
) {
381 immutable usize end
= min(pal
.length
, mPalette
.length
);
382 if (pal
!= mPalette
) {
383 *firstByte |
= FrameMask
.DeltaPalette
;
384 foreach (immutable i
; 0..end
) work
[workUsed
++] = mPalette
[i
]^pal
[i
];
385 mPalette
[0..end
] = pal
[0..end
];
391 // return false when frame is full (and line is not encoded)
392 void encodeLine (const(void)[] line
) @trusted {
393 if (linesDone
>= mHeight
) throw new ZMBVError("too many lines");
394 immutable uint lineWidth
= mWidth
*pixelSize
;
395 if (line
.length
< lineWidth
) throw new ZMBVError("line too short");
396 immutable usize ofs
= pixelSize
*(MaxVector
+(linesDone
+MaxVector
)*mPitch
);
397 newFrame
[ofs
..ofs
+lineWidth
] = (cast(const(ubyte*))line
.ptr
)[0..lineWidth
];
401 // return array with frame data
402 const(void)[] finishFrame () @trusted {
403 if (linesDone
!= mHeight
) throw new ZMBVError("can't finish incomplete frame");
404 ubyte firstByte
= *outbuf
;
405 if (firstByte
&FrameMask
.Keyframe
) {
406 // add the full frame data
407 usize ofs
= pixelSize
*(MaxVector
+MaxVector
*mPitch
);
408 immutable llen
= mWidth
*pixelSize
;
409 foreach (immutable i
; 0..mHeight
) {
410 work
[workUsed
..workUsed
+llen
] = newFrame
[ofs
..ofs
+llen
];
411 ofs
+= mPitch
*pixelSize
;
415 // add the delta frame data
417 case Format
.bpp8
: addXorFrame
!byte(); break;
418 case Format
.bpp15
: case Format
.bpp16
: addXorFrame
!short(); break;
419 case Format
.bpp32
: addXorFrame
!int(); break;
420 default: throw new ZMBVError("internal error");
423 if (mCompression
== Compression
.ZLib
) {
424 // create the actual frame with compression
425 import etc
.c
.zlib
: deflate
, Z_OK
, Z_SYNC_FLUSH
;
426 zstream
.next_in
= work
;
427 zstream
.avail_in
= cast(uint)workUsed
;
428 zstream
.total_in
= 0;
429 zstream
.next_out
= outbuf
+writeDone
;
430 zstream
.avail_out
= cast(uint)(outbufSize
-writeDone
);
431 zstream
.total_out
= 0;
432 if (deflate(&zstream
, Z_SYNC_FLUSH
) != Z_OK
) throw new ZMBVError("deflate error"); // the thing that should not be
434 return (cast(const(void)*)outbuf
)[0..writeDone
+zstream
.total_out
];
436 outbuf
[writeDone
..writeDone
+workUsed
] = work
[0..workUsed
];
438 return (cast(const(void)*)outbuf
)[0..workUsed
+writeDone
];
443 usize
workBufferSize () const @safe pure nothrow @nogc {
444 usize f
= format2pixelSize(mFormat
);
445 return (f ? f
*mWidth
*mHeight
+2*(1+(mWidth
/8))*(1+(mHeight
/8))+1024 : 0);
448 void createVectorTable () @safe pure nothrow @nogc {
449 import std
.math
: abs
;
450 vectorTable
[0].x
= vectorTable
[0].y
= 0;
451 usize vectorCount
= 1;
452 foreach (immutable s
; 1..11) {
453 foreach (immutable y
; 0-s
..0+s
+1) {
454 foreach (immutable x
; 0-s
..0+s
+1) {
455 if (abs(x
) == s ||
abs(y
) == s
) {
456 vectorTable
[vectorCount
].x
= x
;
457 vectorTable
[vectorCount
].y
= y
;
466 int possibleBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
468 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
469 auto pnew
= (cast(const(P
)*)newFrame
)+block
.start
;
470 for (usize y
= 0; y
< block
.dy
; y
+= 4) {
471 for (usize x
= 0; x
< block
.dx
; x
+= 4) {
472 int test = 0-((pold
[x
]-pnew
[x
])&0x00ffffff);
473 ret -= (test>>31); // 0 or -1
481 int compareBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
483 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
484 auto pnew
= (cast(const(P
)*)newFrame
)+block
.start
;
485 foreach (immutable y
; 0..block
.dy
) {
486 foreach (immutable x
; 0..block
.dx
) {
487 int test = 0-((pold
[x
]-pnew
[x
])&0x00ffffff);
488 ret -= (test>>31); // 0 or -1
496 void addXorBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
497 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
498 auto pnew
= (cast(const(P
)*)newFrame
)+block
.start
;
499 auto dest
= cast(P
*)(work
+workUsed
);
500 workUsed
+= P
.sizeof
*(block
.dx
*block
.dy
);
501 foreach (immutable y
; 0..block
.dy
) {
502 foreach (immutable x
; 0..block
.dx
) {
503 *dest
++ = pnew
[x
]^pold
[x
];
510 void addXorFrame(P
) () @trusted nothrow @nogc {
511 ubyte* vectors
= work
+workUsed
;
512 // align the following xor data on 4 byte boundary
513 workUsed
= (workUsed
+blockCount
*2+3)&~3;
514 const(FrameBlock
)* block
= blocks
;
515 foreach (immutable b
; 0..blockCount
) {
516 int bestvx
= 0, bestvy
= 0;
517 int bestchange
= compareBlock
!P(0, 0, block
);
518 if (bestchange
>= 4) {
520 foreach (ref vi
; vectorTable
) {
521 auto vx
= vi
.x
, vy
= vi
.y
;
522 if (possibleBlock
!P(vx
, vy
, block
) < 4) {
523 auto testchange
= compareBlock
!P(vx
, vy
, block
);
524 if (testchange
< bestchange
) {
525 bestchange
= testchange
;
528 if (bestchange
< 4) break;
530 if (--possibles
== 0) break;
534 vectors
[b
*2+0] = (bestvx
<<1)&0xff;
535 vectors
[b
*2+1] = (bestvy
<<1)&0xff;
538 addXorBlock
!P(bestvx
, bestvy
, block
);
546 // ////////////////////////////////////////////////////////////////////////// //
547 class Decoder
: Codec
{
550 bool mPaletteChanged
;
556 this (uint width
, uint height
) @trusted {
557 super(width
, height
);
558 mCompression
= Compression
.None
;
559 mOldFormat
= Format
.None
;
560 blockWidth
= blockHeight
= 0;
563 protected override void zlibDeinit () @trusted nothrow /*@nogc*/ {
565 import etc
.c
.zlib
: inflateEnd
;
566 try inflateEnd(&zstream
); catch (Exception
) {}
567 zstreamInited
= false;
572 @property const(ubyte)[] palette () @trusted nothrow @nogc { return mPalette
[0..768]; }
574 const(ubyte)[] line (usize idx
) const @trusted nothrow @nogc {
575 if (idx
>= mHeight
) return null;
576 usize stpos
= pixelSize
*(MaxVector
+MaxVector
*mPitch
)+mPitch
*pixelSize
*idx
;
577 return newFrame
[stpos
..stpos
+mWidth
*pixelSize
];
580 // was pallette changed on currently decoded frame?
581 bool paletteChanged () const @trusted nothrow @nogc { return mPaletteChanged
; }
583 void decodeFrame (const(void)[] frameData
) @trusted {
584 usize size
= frameData
.length
;
585 mPaletteChanged
= false;
586 if (frameData
is null || size
<= 1) throw new ZMBVError("invalid frame data");
587 auto data
= cast(const(ubyte)*)frameData
.ptr
;
590 if (tag
> 2) throw new ZMBVError("invalid frame data"); // for now we can have only 0, 1 or 2 in tag byte
591 if (tag
&FrameMask
.Keyframe
) {
592 auto header
= cast(const(KeyframeHeader
*))data
;
593 if (size
<= KeyframeHeader
.sizeof
) throw new ZMBVError("invalid frame data");
594 size
-= KeyframeHeader
.sizeof
;
595 data
+= KeyframeHeader
.sizeof
;
596 if (header
.versionHi
!= Version
.High || header
.versionLo
!= Version
.Low
) throw new ZMBVError("invalid frame data");
597 if (header
.compression
> Compression
.max
) throw new ZMBVError("invalid frame data"); // invalid compression mode
598 if (!isValidFormat(cast(Format
)header
.format
)) throw new ZMBVError("invalid frame data");
599 if (header
.blockWidth
< 1 || header
.blockHeight
< 1) throw new ZMBVError("invalid frame data");
600 if (mFormat
!= cast(Format
)header
.format || blockWidth
!= header
.blockWidth || blockHeight
!= header
.blockHeight
) {
601 // new format or block size
602 mFormat
= cast(Format
)header
.format
;
603 blockWidth
= header
.blockWidth
;
604 blockHeight
= header
.blockHeight
;
605 setupBuffers(mFormat
, blockWidth
, blockHeight
);
607 mCompression
= cast(Compression
)header
.compression
;
608 if (mCompression
== Compression
.ZLib
) {
609 import etc
.c
.zlib
: inflateInit
, inflateReset
, Z_OK
;
610 if (!zstreamInited
) {
611 if (inflateInit(&zstream
) != Z_OK
) throw new ZMBVError("can't initialize ZLib stream");
612 zstreamInited
= true;
614 if (inflateReset(&zstream
) != Z_OK
) throw new ZMBVError("can't reset inflate stream");
618 if (size
> bufSize
) throw new ZMBVError("frame too big");
619 if (mCompression
== Compression
.ZLib
) {
620 import etc
.c
.zlib
: inflate
, Z_OK
, Z_SYNC_FLUSH
;
621 zstream
.next_in
= cast(ubyte*)data
;
622 zstream
.avail_in
= cast(uint)size
;
623 zstream
.total_in
= 0;
624 zstream
.next_out
= work
;
625 zstream
.avail_out
= cast(uint)bufSize
;
626 zstream
.total_out
= 0;
627 if (inflate(&zstream
, Z_SYNC_FLUSH
/*Z_NO_FLUSH*/) != Z_OK
) throw new ZMBVError("can't read inflate stream"); // the thing that should not be
628 workUsed
= zstream
.total_out
;
630 if (size
> 0) work
[0..size
] = data
[0..size
];
634 if (tag
&FrameMask
.Keyframe
) {
636 if (workUsed
< palSize
*3) throw new ZMBVError("invalid frame data");
637 mPaletteChanged
= true;
639 mPalette
[0..workPos
] = work
[0..workPos
];
643 ubyte* writeframe
= newFrame
+pixelSize
*(MaxVector
+MaxVector
*mPitch
);
644 foreach (immutable _
; 0..mHeight
) {
645 if (workPos
+mWidth
*pixelSize
> workUsed
) throw new ZMBVError("invalid frame data");
646 writeframe
[0..mWidth
*pixelSize
] = work
[workPos
..workPos
+mWidth
*pixelSize
];
647 writeframe
+= mPitch
*pixelSize
;
648 workPos
+= mWidth
*pixelSize
;
652 if (tag
&FrameMask
.DeltaPalette
) {
653 if (workUsed
< palSize
*3) throw new ZMBVError("invalid frame data");
654 mPaletteChanged
= true;
655 foreach (immutable i
; 0..palSize
*3) mPalette
[i
] ^
= work
[workPos
++];
658 case Format
.bpp8
: unxorFrame
!ubyte(); break;
659 case Format
.bpp15
: case Format
.bpp16
: unxorFrame
!ushort(); break;
660 case Format
.bpp32
: unxorFrame
!uint(); break;
661 default: throw new ZMBVError("invalid format");
668 void unxorBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
669 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
670 auto pnew
= (cast(P
*)newFrame
)+block
.start
;
671 auto src
= cast(const(P
)*)(work
+workPos
);
672 workPos
+= P
.sizeof
*(block
.dx
*block
.dy
);
673 foreach (immutable y
; 0..block
.dy
) {
674 foreach (immutable x
; 0..block
.dx
) {
675 pnew
[x
] = pold
[x
]^
(*src
++);
682 void copyBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
683 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
684 auto pnew
= (cast(P
*)newFrame
)+block
.start
;
685 foreach (immutable y
; 0..block
.dy
) {
686 pnew
[0..block
.dx
] = pold
[0..block
.dx
];
692 void unxorFrame(P
) () @trusted nothrow @nogc {
693 auto vectors
= cast(byte*)work
+workPos
;
694 workPos
= (workPos
+blockCount
*2+3)&~3;
695 const(FrameBlock
)* block
= blocks
;
696 foreach (immutable b
; 0..blockCount
) {
697 int delta
= vectors
[b
*2+0]&1;
698 int vx
= vectors
[b
*2+0]>>1;
699 int vy
= vectors
[b
*2+1]>>1;
700 if (delta
) unxorBlock
!P(vx
, vy
, block
); else copyBlock
!P(vx
, vy
, block
);
707 // ////////////////////////////////////////////////////////////////////////// //
708 private enum AVI_HEADER_SIZE
= 500;
711 final class AviWriter
{
713 import std
.stdio
: File
;
720 uint indexsize
, indexused
;
724 uint audioused
; // always 0 for now
726 uint audiorate
; // 44100?
729 this (string fname
, uint width
, uint height
, uint fps
) @trusted {
730 import core
.stdc
.stdlib
: malloc
;
731 import std
.exception
: enforce
;
732 if (fps
< 1 || fps
> 255) throw new Exception("invalid fps");
733 ec
= new Encoder(width
, height
, Encoder
.Format
.None
, 9);
735 this.fl
= File(fname
, "w");
737 ubyte[AVI_HEADER_SIZE
] hdr
;
742 index
= cast(ubyte*)malloc(indexsize
);
743 enforce(index
!is null);
747 import core
.stdc
.stdlib
: free
;
748 if (index
!is null) free(index
);
753 void close () @trusted {
754 import core
.stdc
.stdlib
: free
;
757 if (index
!is null) free(index
);
762 void fixHeader () @trusted {
763 ubyte[AVI_HEADER_SIZE
] hdr
;
764 usize header_pos
= 0;
766 void AVIOUT4 (string s
) @trusted {
767 assert(s
.length
== 4);
768 hdr
[header_pos
..header_pos
+4] = cast(ubyte[])s
;
772 void AVIOUTw (ulong w
) @trusted {
773 if (w
> 0xffff) throw new Exception("invalid ushort");
774 hdr
[header_pos
++] = (w
&0xff);
775 hdr
[header_pos
++] = ((w
>>8)&0xff);
778 void AVIOUTd (ulong w
, int line
=__LINE__
) @trusted {
779 import std
.string
: format
;
780 if (w
> 0xffffffffu
) throw new Exception("invalid ulong: 0x%08x at line %s".format(w
, line
));
781 hdr
[header_pos
++] = (w
&0xff);
782 hdr
[header_pos
++] = ((w
>>8)&0xff);
783 hdr
[header_pos
++] = ((w
>>16)&0xff);
784 hdr
[header_pos
++] = ((w
>>24)&0xff);
789 AVIOUT4("RIFF"); // riff header
790 AVIOUTd(AVI_HEADER_SIZE
+written
-8+indexused
);
792 AVIOUT4("LIST"); // list header
793 main_list
= cast(uint)header_pos
;
794 AVIOUTd(0); // TODO size of list
798 AVIOUTd(56); // # of bytes to follow
799 AVIOUTd(1000000/fps
); // microseconds per frame
801 AVIOUTd(0); // PaddingGranularity (whatever that might be)
802 AVIOUTd(0x110); // Flags, 0x10 has index, 0x100 interleaved
803 AVIOUTd(frames
); // TotalFrames
804 AVIOUTd(0); // InitialFrames
805 AVIOUTd(audiowritten
> 0 ?
2 : 1); // Stream count
806 AVIOUTd(0); // SuggestedBufferSize
807 AVIOUTd(ec
.width
); // Width
808 AVIOUTd(ec
.height
); // Height
809 AVIOUTd(0); // TimeScale: Unit used to measure time
810 AVIOUTd(0); // DataRate: Data rate of playback
811 AVIOUTd(0); // StartTime: Starting time of AVI data
812 AVIOUTd(0); // DataLength: Size of AVI data chunk
816 AVIOUTd(4+8+56+8+40); // size of the list
818 // video stream header
820 AVIOUTd(56); // # of bytes to follow
821 AVIOUT4("vids"); // type
822 AVIOUT4("ZMBV"); // handler
824 AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
825 AVIOUTd(0); // InitialFrames
826 AVIOUTd(1000000); // Scale
827 AVIOUTd(1000000*fps
); // Rate: Rate/Scale == samples/second
829 AVIOUTd(frames
); // Length
830 AVIOUTd(0); // SuggestedBufferSize
831 AVIOUTd(0xffffffffu
); // Quality
832 AVIOUTd(0); // SampleSize
835 // the video stream format
837 AVIOUTd(40); // # of bytes to follow
839 AVIOUTd(ec
.width
); // Width
840 AVIOUTd(ec
.height
); // Height
841 //OUTSHRT(1); OUTSHRT(24); // Planes, Count
843 AVIOUT4("ZMBV"); // Compression
844 AVIOUTd(ec
.width
*ec
.height
*4); // SizeImage (in bytes?)
845 AVIOUTd(0); // XPelsPerMeter
846 AVIOUTd(0); // YPelsPerMeter
847 AVIOUTd(0); // ClrUsed: Number of colors used
848 AVIOUTd(0); // ClrImportant: Number of colors important
850 if (audiowritten
> 0) {
853 AVIOUTd(4+8+56+8+16); // Length of list in bytes
855 // the audio stream header
857 AVIOUTd(56); // # of bytes to follow
859 AVIOUTd(0); // Format (Optionally)
861 AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
862 AVIOUTd(0); // InitialFrames
864 AVIOUTd(audiorate
*4); // rate, actual rate is scale/rate
866 if (!audiorate
) audiorate
= 1;
867 AVIOUTd(audiowritten
/4); // Length
868 AVIOUTd(0); // SuggestedBufferSize
869 AVIOUTd(~0); // Quality
870 AVIOUTd(4); // SampleSize
873 // the audio stream format
875 AVIOUTd(16); // # of bytes to follow
876 AVIOUTw(1); // Format, WAVE_ZMBV_FORMAT_PCM
877 AVIOUTw(2); // Number of channels
878 AVIOUTd(audiorate
); // SamplesPerSec
879 AVIOUTd(audiorate
*4); // AvgBytesPerSec
880 AVIOUTw(4); // BlockAlign
881 AVIOUTw(16); // BitsPerSample
883 long nmain
= header_pos
-main_list
-4;
884 // finish stream list, i.e. put number of bytes in the list to proper pos
885 long njunk
= AVI_HEADER_SIZE
-8-12-header_pos
;
888 // fix the size of the main list
889 header_pos
= main_list
;
891 header_pos
= AVI_HEADER_SIZE
-12;
893 AVIOUTd(written
+4); // Length of list in bytes
895 // first add the index table to the end
901 uint d
= indexused
-8;
903 index
[5] = ((d
>>8)&0xff);
904 index
[6] = ((d
>>16)&0xff);
905 index
[7] = ((d
>>24)&0xff);
906 fl
.rawWrite(index
[0..indexused
]);
908 // now replace the header
913 void writeChunk (string tag
, const(void)[] data
, uint flags
, bool writeToIndex
=false) @trusted {
914 if (tag
.length
!= 4) throw new Exception("invalid tag name");
915 uint size
= cast(uint)data
.length
;
917 fl
.rawWrite((&size
)[0..1]);
920 // write the actual data
921 uint writesize
= (size
+1)&~1;
922 if (size
> 0) fl
.rawWrite(data
);
923 if (writesize
!= size
) {
925 assert(writesize
-size
== 1); // just in case
929 written
+= writesize
+8;
930 if (indexused
+16 >= indexsize
) {
931 import core
.stdc
.stdlib
: realloc
;
932 import std
.exception
: enforce
;
933 void* ni
= realloc(index
, indexsize
+16*4096);
934 enforce(ni
!is null);
935 index
= cast(ubyte*)ni
;
936 indexsize
+= 16*4096;
939 void putu32 (usize pos
, ulong n
) @trusted nothrow @nogc {
941 idx
[pos
] = ((n
>>8)&0xff);
942 idx
[pos
] = ((n
>>16)&0xff);
943 idx
[pos
] = ((n
>>24)&0xff);
947 idx
= index
+indexused
;
959 void writeChunkVideo (const(void)[] framedata
) @trusted {
960 if (framedata
.length
== 0) throw new Exception("can't write empty frame");
961 ubyte b
= (cast(const(ubyte)[])framedata
)[0];
962 writeChunk("00dc", framedata
, (b
&0x01 ?
0x10 : 0), ((b
&0x01) != 0));
967 void writeChunkAudio (const(void)[] data
) @trusted {
968 if (data
.length
== 0) return;
969 writeChunk("01wb", data
, 0);
970 audiowritten
= cast(uint)data
.length
;