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, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * D translation by Ketmar // Invisible Vector
20 module iv
.zmbv
/*is aliced*/;
23 // ////////////////////////////////////////////////////////////////////////// //
27 mixin(MyException
!"ZMBVError");
30 // ////////////////////////////////////////////////////////////////////////// //
52 // returns Format.None for unknown bpp
53 static Format
bpp2format(T
) (T bpp
) @safe pure nothrow @nogc if (__traits(isIntegral
, T
)) {
55 case 8: return Format
.bpp8
;
56 case 15: return Format
.bpp15
;
57 case 16: return Format
.bpp16
;
58 case 32: return Format
.bpp32
;
59 default: return Format
.None
;
64 // returns 0 for unknown format
65 static ubyte format2bpp (Format fmt
) @safe pure nothrow @nogc {
67 case Format
.bpp8
: return 8;
68 case Format
.bpp15
: return 15;
69 case Format
.bpp16
: return 16;
70 case Format
.bpp32
: return 32;
76 // returns 0 for unknown format
77 static ubyte format2pixelSize (Format fmt
) @safe pure nothrow @nogc {
79 case Format
.bpp8
: return 1;
80 case Format
.bpp15
: case Format
.bpp16
: return 2;
81 case Format
.bpp32
: return 4;
87 static bool isValidFormat (Format fmt
) @safe pure nothrow @nogc {
101 import etc
.c
.zlib
: z_stream
;
105 // ZMBV format version
121 align(1) struct KeyframeHeader
{
131 Compression mCompression
= Compression
.ZLib
;
133 ubyte* oldFrame
, newFrame
;
134 ubyte* buf1
, buf2
, work
;
142 ubyte[256*3] mPalette
;
144 uint mHeight
, mWidth
, mPitch
;
152 // used in encoder only, but moved here for freeBuffers()
157 this (uint width
, uint height
) @trusted {
158 if (width
< 1 || height
< 1 || width
> 32768 || height
> 32768) throw new ZMBVError("invalid ZMBV dimensions");
161 mPitch
= mWidth
+2*MaxVector
;
164 ~this () @safe nothrow /*@nogc*/ {
168 // release all allocated memory, finish zstream, etc
169 final void clear () @trusted nothrow /*@nogc*/ {
174 protected abstract void zlibDeinit () @trusted nothrow /*@nogc*/;
177 @property uint width () const @safe pure nothrow @nogc { return mWidth
; }
178 @property uint height () const @safe pure nothrow @nogc { return mHeight
; }
179 @property Format
format () const @safe pure nothrow @nogc { return mFormat
; }
182 void freeBuffers () @trusted nothrow @nogc {
183 import core
.stdc
.stdlib
: free
;
184 if (outbuf
!is null) free(outbuf
);
185 if (blocks
!is null) free(blocks
);
186 if (buf1
!is null) free(buf1
);
187 if (buf2
!is null) free(buf2
);
188 if (work
!is null) free(work
);
197 void setupBuffers (Format fmt
, uint blockWidth
, uint blockHeight
) @trusted {
198 import core
.stdc
.stdlib
: malloc
;
199 import std
.exception
: enforce
;
201 pixelSize
= format2pixelSize(fmt
);
202 if (pixelSize
== 0) throw new ZMBVError("invalid ZMBV format");
203 if (blockWidth
< 1 || blockHeight
< 1) throw new ZMBVError("invalid ZMBV block size");
204 palSize
= (fmt
== Format
.bpp8 ?
256 : 0);
205 bufSize
= (mHeight
+2*MaxVector
)*mPitch
*pixelSize
+2048;
206 buf1
= cast(ubyte*)malloc(bufSize
);
207 buf2
= cast(ubyte*)malloc(bufSize
);
208 work
= cast(ubyte*)malloc(bufSize
);
209 scope(failure
) freeBuffers();
210 enforce(buf1
!is null && buf2
!is null && work
!is null);
212 uint xblocks
= (mWidth
/blockWidth
);
213 uint xleft
= mWidth
%blockWidth
;
214 if (xleft
) ++xblocks
;
216 uint yblocks
= (mHeight
/blockHeight
);
217 uint yleft
= mHeight
%blockHeight
;
218 if (yleft
) ++yblocks
;
220 blockCount
= yblocks
*xblocks
;
221 blocks
= cast(FrameBlock
*)malloc(FrameBlock
.sizeof
*blockCount
);
222 enforce(blocks
!is null);
225 foreach (immutable y
; 0..yblocks
) {
226 foreach (immutable x
; 0..xblocks
) {
227 blocks
[i
].start
= ((y
*blockHeight
)+MaxVector
)*mPitch
+(x
*blockWidth
)+MaxVector
;
228 blocks
[i
].dx
= (xleft
&& x
== xblocks
-1 ? xleft
: blockWidth
);
229 blocks
[i
].dy
= (yleft
&& y
== yblocks
-1 ? yleft
: blockHeight
);
234 buf1
[0..bufSize
] = 0;
235 buf2
[0..bufSize
] = 0;
236 work
[0..bufSize
] = 0;
243 // swap old frame and new frame
244 void swapFrames () @safe nothrow @nogc {
245 auto copyFrame
= newFrame
;
247 oldFrame
= copyFrame
;
252 // ////////////////////////////////////////////////////////////////////////// //
253 class Encoder
: Codec
{
255 static struct CodecVector
{
258 CodecVector
[441] vectorTable
= void;
260 ubyte mCompressionLevel
;
271 this (uint width
, uint height
, Format fmt
=Format
.None
, int complevel
=-1, Compression ctype
=Compression
.ZLib
) @trusted {
272 super(width
, height
);
273 if (fmt
!= Format
.None
&& !isValidFormat(fmt
)) throw new ZMBVError("invalid ZMBV format");
274 if (ctype
!= Compression
.None
&& ctype
!= Compression
.ZLib
) throw new ZMBVError("invalid ZMBV compression");
275 if (ctype
== Compression
.ZLib
) {
276 if (complevel
< 0) complevel
= 4;
277 if (complevel
> 9) complevel
= 9;
281 mCompression
= ctype
;
282 mCompressionLevel
= cast(ubyte)complevel
;
286 if (mCompression
== Compression
.ZLib
) {
287 import etc
.c
.zlib
: deflateInit
, Z_OK
;
288 if (deflateInit(&zstream
, complevel
) != Z_OK
) throw new ZMBVError("can't initialize ZLib stream");
289 zstreamInited
= true;
291 // allocate buffer here if we know the format
292 if (fmt
!= Format
.None
) fixOutBuffer();
295 protected override void zlibDeinit () @trusted nothrow /*@nogc*/ {
297 import etc
.c
.zlib
: deflateEnd
;
298 try deflateEnd(&zstream
); catch (Exception
) {}
299 zstreamInited
= false;
301 frameCount
= 0; // do it here as we have no other hooks in clear()
304 // (re)allocate buffer for compressed data
305 private final fixOutBuffer () @trusted {
306 usize nbs
= workBufferSize();
307 if (nbs
== 0) throw new ZMBVError("internal error");
308 if (nbs
> outbufSize
) {
309 import core
.stdc
.stdlib
: realloc
;
310 // we need bigger buffer
311 void* nb
= realloc(outbuf
, nbs
);
312 if (nb
is null) throw new ZMBVError("out of memory for compression buffer");
314 outbuf
= cast(ubyte*)nb
;
319 @property Compression
compression () const @safe pure nothrow @nogc { return mCompression
; }
320 @property ubyte compressionLevel () const @safe pure nothrow @nogc { return mCompressionLevel
; }
321 @property ulong processedFrames () const @safe pure nothrow @nogc { return frameCount
; }
323 void prepareFrame (PrepareFlags flags
, const(ubyte)[] pal
=null, Format fmt
=Format
.None
) @trusted {
324 import std
.algorithm
: min
;
326 if (flags
!= PrepareFlags
.None
&& flags
!= PrepareFlags
.Keyframe
) throw new ZMBVError("invalid flags");
327 if (fmt
== Format
.None
) {
328 if (mFormat
== Format
.None
) throw new ZMBVError("invalid format");
331 if (!isValidFormat(fmt
)) throw new ZMBVError("invalid format");
333 if (fmt
!= mFormat || frameCount
== 0) {
335 setupBuffers(fmt
, 16, 16);
336 flags
= PrepareFlags
.Keyframe
; // force a keyframe
341 // (re)allocate buffer for compressed data
344 writeDone
= 1; // for firstByte
346 // set a pointer to the first byte which will contain info about this frame
347 ubyte* firstByte
= outbuf
;
348 *firstByte
= 0; // frame flags
350 // reset the work buffer
352 if (flags
== PrepareFlags
.Keyframe
) {
354 *firstByte |
= FrameMask
.Keyframe
;
355 auto header
= cast(KeyframeHeader
*)(outbuf
+writeDone
);
356 header
.versionHi
= Version
.High
;
357 header
.versionLo
= Version
.Low
;
358 header
.compression
= cast(byte)mCompression
;
359 header
.format
= cast(ubyte)mFormat
;
360 header
.blockWidth
= 16;
361 header
.blockHeight
= 16;
362 writeDone
+= KeyframeHeader
.sizeof
;
365 immutable usize end
= min(pal
.length
, mPalette
.length
);
366 mPalette
[0..end
] = pal
[0..end
];
367 if (end
< mPalette
.length
) mPalette
[end
..$] = 0;
371 // keyframes get the full palette
372 work
[0..palSize
*3] = mPalette
[];
373 workUsed
+= palSize
*3;
376 if (mCompression
== Compression
.ZLib
) {
377 import etc
.c
.zlib
: deflateReset
, Z_OK
;
378 if (deflateReset(&zstream
) != Z_OK
) throw new ZMBVError("can't restart deflate stream");
381 if (palSize
&& pal
.length
) {
382 immutable usize end
= min(pal
.length
, mPalette
.length
);
383 if (pal
!= mPalette
) {
384 *firstByte |
= FrameMask
.DeltaPalette
;
385 foreach (immutable i
; 0..end
) work
[workUsed
++] = mPalette
[i
]^pal
[i
];
386 mPalette
[0..end
] = pal
[0..end
];
392 // return false when frame is full (and line is not encoded)
393 void encodeLine (const(void)[] line
) @trusted {
394 if (linesDone
>= mHeight
) throw new ZMBVError("too many lines");
395 immutable uint lineWidth
= mWidth
*pixelSize
;
396 if (line
.length
< lineWidth
) throw new ZMBVError("line too short");
397 immutable usize ofs
= pixelSize
*(MaxVector
+(linesDone
+MaxVector
)*mPitch
);
398 newFrame
[ofs
..ofs
+lineWidth
] = (cast(const(ubyte*))line
.ptr
)[0..lineWidth
];
402 // return array with frame data
403 const(void)[] finishFrame () @trusted {
404 if (linesDone
!= mHeight
) throw new ZMBVError("can't finish incomplete frame");
405 ubyte firstByte
= *outbuf
;
406 if (firstByte
&FrameMask
.Keyframe
) {
407 // add the full frame data
408 usize ofs
= pixelSize
*(MaxVector
+MaxVector
*mPitch
);
409 immutable llen
= mWidth
*pixelSize
;
410 foreach (immutable i
; 0..mHeight
) {
411 work
[workUsed
..workUsed
+llen
] = newFrame
[ofs
..ofs
+llen
];
412 ofs
+= mPitch
*pixelSize
;
416 // add the delta frame data
418 case Format
.bpp8
: addXorFrame
!byte(); break;
419 case Format
.bpp15
: case Format
.bpp16
: addXorFrame
!short(); break;
420 case Format
.bpp32
: addXorFrame
!int(); break;
421 default: throw new ZMBVError("internal error");
424 if (mCompression
== Compression
.ZLib
) {
425 // create the actual frame with compression
426 import etc
.c
.zlib
: deflate
, Z_OK
, Z_SYNC_FLUSH
;
427 zstream
.next_in
= work
;
428 zstream
.avail_in
= cast(uint)workUsed
;
429 zstream
.total_in
= 0;
430 zstream
.next_out
= outbuf
+writeDone
;
431 zstream
.avail_out
= cast(uint)(outbufSize
-writeDone
);
432 zstream
.total_out
= 0;
433 if (deflate(&zstream
, Z_SYNC_FLUSH
) != Z_OK
) throw new ZMBVError("deflate error"); // the thing that should not be
435 return (cast(const(void)*)outbuf
)[0..writeDone
+zstream
.total_out
];
437 outbuf
[writeDone
..writeDone
+workUsed
] = work
[0..workUsed
];
439 return (cast(const(void)*)outbuf
)[0..workUsed
+writeDone
];
444 usize
workBufferSize () const @safe pure nothrow @nogc {
445 usize f
= format2pixelSize(mFormat
);
446 return (f ? f
*mWidth
*mHeight
+2*(1+(mWidth
/8))*(1+(mHeight
/8))+1024 : 0);
449 void createVectorTable () @safe pure nothrow @nogc {
450 import std
.math
: abs
;
451 vectorTable
[0].x
= vectorTable
[0].y
= 0;
452 usize vectorCount
= 1;
453 foreach (immutable s
; 1..11) {
454 foreach (immutable y
; 0-s
..0+s
+1) {
455 foreach (immutable x
; 0-s
..0+s
+1) {
456 if (abs(x
) == s ||
abs(y
) == s
) {
457 vectorTable
[vectorCount
].x
= x
;
458 vectorTable
[vectorCount
].y
= y
;
467 int possibleBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
469 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
470 auto pnew
= (cast(const(P
)*)newFrame
)+block
.start
;
471 for (usize y
= 0; y
< block
.dy
; y
+= 4) {
472 for (usize x
= 0; x
< block
.dx
; x
+= 4) {
473 int test = 0-((pold
[x
]-pnew
[x
])&0x00ffffff);
474 ret -= (test>>31); // 0 or -1
482 int compareBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
484 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
485 auto pnew
= (cast(const(P
)*)newFrame
)+block
.start
;
486 foreach (immutable y
; 0..block
.dy
) {
487 foreach (immutable x
; 0..block
.dx
) {
488 int test = 0-((pold
[x
]-pnew
[x
])&0x00ffffff);
489 ret -= (test>>31); // 0 or -1
497 void addXorBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
498 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
499 auto pnew
= (cast(const(P
)*)newFrame
)+block
.start
;
500 auto dest
= cast(P
*)(work
+workUsed
);
501 workUsed
+= P
.sizeof
*(block
.dx
*block
.dy
);
502 foreach (immutable y
; 0..block
.dy
) {
503 foreach (immutable x
; 0..block
.dx
) {
504 *dest
++ = pnew
[x
]^pold
[x
];
511 void addXorFrame(P
) () @trusted nothrow @nogc {
512 ubyte* vectors
= work
+workUsed
;
513 // align the following xor data on 4 byte boundary
514 workUsed
= (workUsed
+blockCount
*2+3)&~3;
515 const(FrameBlock
)* block
= blocks
;
516 foreach (immutable b
; 0..blockCount
) {
517 int bestvx
= 0, bestvy
= 0;
518 int bestchange
= compareBlock
!P(0, 0, block
);
519 if (bestchange
>= 4) {
521 foreach (ref vi
; vectorTable
) {
522 auto vx
= vi
.x
, vy
= vi
.y
;
523 if (possibleBlock
!P(vx
, vy
, block
) < 4) {
524 auto testchange
= compareBlock
!P(vx
, vy
, block
);
525 if (testchange
< bestchange
) {
526 bestchange
= testchange
;
529 if (bestchange
< 4) break;
531 if (--possibles
== 0) break;
535 vectors
[b
*2+0] = (bestvx
<<1)&0xff;
536 vectors
[b
*2+1] = (bestvy
<<1)&0xff;
539 addXorBlock
!P(bestvx
, bestvy
, block
);
547 // ////////////////////////////////////////////////////////////////////////// //
548 class Decoder
: Codec
{
551 bool mPaletteChanged
;
557 this (uint width
, uint height
) @trusted {
558 super(width
, height
);
559 mCompression
= Compression
.None
;
560 mOldFormat
= Format
.None
;
561 blockWidth
= blockHeight
= 0;
564 protected override void zlibDeinit () @trusted nothrow /*@nogc*/ {
566 import etc
.c
.zlib
: inflateEnd
;
567 try inflateEnd(&zstream
); catch (Exception
) {}
568 zstreamInited
= false;
573 @property const(ubyte)[] palette () @trusted nothrow @nogc { return mPalette
[0..768]; }
575 const(ubyte)[] line (usize idx
) const @trusted nothrow @nogc {
576 if (idx
>= mHeight
) return null;
577 usize stpos
= pixelSize
*(MaxVector
+MaxVector
*mPitch
)+mPitch
*pixelSize
*idx
;
578 return newFrame
[stpos
..stpos
+mWidth
*pixelSize
];
581 // was pallette changed on currently decoded frame?
582 bool paletteChanged () const @trusted nothrow @nogc { return mPaletteChanged
; }
584 void decodeFrame (const(void)[] frameData
) @trusted {
585 usize size
= frameData
.length
;
586 mPaletteChanged
= false;
587 if (frameData
is null || size
<= 1) throw new ZMBVError("invalid frame data");
588 auto data
= cast(const(ubyte)*)frameData
.ptr
;
591 if (tag
> 2) throw new ZMBVError("invalid frame data"); // for now we can have only 0, 1 or 2 in tag byte
592 if (tag
&FrameMask
.Keyframe
) {
593 auto header
= cast(const(KeyframeHeader
*))data
;
594 if (size
<= KeyframeHeader
.sizeof
) throw new ZMBVError("invalid frame data");
595 size
-= KeyframeHeader
.sizeof
;
596 data
+= KeyframeHeader
.sizeof
;
597 if (header
.versionHi
!= Version
.High || header
.versionLo
!= Version
.Low
) throw new ZMBVError("invalid frame data");
598 if (header
.compression
> Compression
.max
) throw new ZMBVError("invalid frame data"); // invalid compression mode
599 if (!isValidFormat(cast(Format
)header
.format
)) throw new ZMBVError("invalid frame data");
600 if (header
.blockWidth
< 1 || header
.blockHeight
< 1) throw new ZMBVError("invalid frame data");
601 if (mFormat
!= cast(Format
)header
.format || blockWidth
!= header
.blockWidth || blockHeight
!= header
.blockHeight
) {
602 // new format or block size
603 mFormat
= cast(Format
)header
.format
;
604 blockWidth
= header
.blockWidth
;
605 blockHeight
= header
.blockHeight
;
606 setupBuffers(mFormat
, blockWidth
, blockHeight
);
608 mCompression
= cast(Compression
)header
.compression
;
609 if (mCompression
== Compression
.ZLib
) {
610 import etc
.c
.zlib
: inflateInit
, inflateReset
, Z_OK
;
611 if (!zstreamInited
) {
612 if (inflateInit(&zstream
) != Z_OK
) throw new ZMBVError("can't initialize ZLib stream");
613 zstreamInited
= true;
615 if (inflateReset(&zstream
) != Z_OK
) throw new ZMBVError("can't reset inflate stream");
619 if (size
> bufSize
) throw new ZMBVError("frame too big");
620 if (mCompression
== Compression
.ZLib
) {
621 import etc
.c
.zlib
: inflate
, Z_OK
, Z_SYNC_FLUSH
;
622 zstream
.next_in
= cast(ubyte*)data
;
623 zstream
.avail_in
= cast(uint)size
;
624 zstream
.total_in
= 0;
625 zstream
.next_out
= work
;
626 zstream
.avail_out
= cast(uint)bufSize
;
627 zstream
.total_out
= 0;
628 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
629 workUsed
= zstream
.total_out
;
631 if (size
> 0) work
[0..size
] = data
[0..size
];
635 if (tag
&FrameMask
.Keyframe
) {
637 if (workUsed
< palSize
*3) throw new ZMBVError("invalid frame data");
638 mPaletteChanged
= true;
640 mPalette
[0..workPos
] = work
[0..workPos
];
644 ubyte* writeframe
= newFrame
+pixelSize
*(MaxVector
+MaxVector
*mPitch
);
645 foreach (immutable _
; 0..mHeight
) {
646 if (workPos
+mWidth
*pixelSize
> workUsed
) throw new ZMBVError("invalid frame data");
647 writeframe
[0..mWidth
*pixelSize
] = work
[workPos
..workPos
+mWidth
*pixelSize
];
648 writeframe
+= mPitch
*pixelSize
;
649 workPos
+= mWidth
*pixelSize
;
653 if (tag
&FrameMask
.DeltaPalette
) {
654 if (workUsed
< palSize
*3) throw new ZMBVError("invalid frame data");
655 mPaletteChanged
= true;
656 foreach (immutable i
; 0..palSize
*3) mPalette
[i
] ^
= work
[workPos
++];
659 case Format
.bpp8
: unxorFrame
!ubyte(); break;
660 case Format
.bpp15
: case Format
.bpp16
: unxorFrame
!ushort(); break;
661 case Format
.bpp32
: unxorFrame
!uint(); break;
662 default: throw new ZMBVError("invalid format");
669 void unxorBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
670 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
671 auto pnew
= (cast(P
*)newFrame
)+block
.start
;
672 auto src
= cast(const(P
)*)(work
+workPos
);
673 workPos
+= P
.sizeof
*(block
.dx
*block
.dy
);
674 foreach (immutable y
; 0..block
.dy
) {
675 foreach (immutable x
; 0..block
.dx
) {
676 pnew
[x
] = pold
[x
]^
(*src
++);
683 void copyBlock(P
) (int vx
, int vy
, const(FrameBlock
*) block
) @trusted nothrow @nogc {
684 auto pold
= (cast(const(P
)*)oldFrame
)+block
.start
+(vy
*mPitch
)+vx
;
685 auto pnew
= (cast(P
*)newFrame
)+block
.start
;
686 foreach (immutable y
; 0..block
.dy
) {
687 pnew
[0..block
.dx
] = pold
[0..block
.dx
];
693 void unxorFrame(P
) () @trusted nothrow @nogc {
694 auto vectors
= cast(byte*)work
+workPos
;
695 workPos
= (workPos
+blockCount
*2+3)&~3;
696 const(FrameBlock
)* block
= blocks
;
697 foreach (immutable b
; 0..blockCount
) {
698 int delta
= vectors
[b
*2+0]&1;
699 int vx
= vectors
[b
*2+0]>>1;
700 int vy
= vectors
[b
*2+1]>>1;
701 if (delta
) unxorBlock
!P(vx
, vy
, block
); else copyBlock
!P(vx
, vy
, block
);
708 // ////////////////////////////////////////////////////////////////////////// //
709 private enum AVI_HEADER_SIZE
= 500;
712 final class AviWriter
{
714 import std
.stdio
: File
;
721 uint indexsize
, indexused
;
725 uint audioused
; // always 0 for now
727 uint audiorate
; // 44100?
730 this (string fname
, uint width
, uint height
, uint fps
) @trusted {
731 import core
.stdc
.stdlib
: malloc
;
732 import std
.exception
: enforce
;
733 if (fps
< 1 || fps
> 255) throw new Exception("invalid fps");
734 ec
= new Encoder(width
, height
, Encoder
.Format
.None
, 9);
736 this.fl
= File(fname
, "w");
738 ubyte[AVI_HEADER_SIZE
] hdr
;
743 index
= cast(ubyte*)malloc(indexsize
);
744 enforce(index
!is null);
748 import core
.stdc
.stdlib
: free
;
749 if (index
!is null) free(index
);
754 void close () @trusted {
755 import core
.stdc
.stdlib
: free
;
758 if (index
!is null) free(index
);
763 void fixHeader () @trusted {
764 ubyte[AVI_HEADER_SIZE
] hdr
;
765 usize header_pos
= 0;
767 void AVIOUT4 (string s
) @trusted {
768 assert(s
.length
== 4);
769 hdr
[header_pos
..header_pos
+4] = cast(ubyte[])s
;
773 void AVIOUTw (ulong w
) @trusted {
774 if (w
> 0xffff) throw new Exception("invalid ushort");
775 hdr
[header_pos
++] = (w
&0xff);
776 hdr
[header_pos
++] = ((w
>>8)&0xff);
779 void AVIOUTd (ulong w
, int line
=__LINE__
) @trusted {
780 import std
.string
: format
;
781 if (w
> 0xffffffffu
) throw new Exception("invalid ulong: 0x%08x at line %s".format(w
, line
));
782 hdr
[header_pos
++] = (w
&0xff);
783 hdr
[header_pos
++] = ((w
>>8)&0xff);
784 hdr
[header_pos
++] = ((w
>>16)&0xff);
785 hdr
[header_pos
++] = ((w
>>24)&0xff);
790 AVIOUT4("RIFF"); // riff header
791 AVIOUTd(AVI_HEADER_SIZE
+written
-8+indexused
);
793 AVIOUT4("LIST"); // list header
794 main_list
= cast(uint)header_pos
;
795 AVIOUTd(0); // TODO size of list
799 AVIOUTd(56); // # of bytes to follow
800 AVIOUTd(1000000/fps
); // microseconds per frame
802 AVIOUTd(0); // PaddingGranularity (whatever that might be)
803 AVIOUTd(0x110); // Flags, 0x10 has index, 0x100 interleaved
804 AVIOUTd(frames
); // TotalFrames
805 AVIOUTd(0); // InitialFrames
806 AVIOUTd(audiowritten
> 0 ?
2 : 1); // Stream count
807 AVIOUTd(0); // SuggestedBufferSize
808 AVIOUTd(ec
.width
); // Width
809 AVIOUTd(ec
.height
); // Height
810 AVIOUTd(0); // TimeScale: Unit used to measure time
811 AVIOUTd(0); // DataRate: Data rate of playback
812 AVIOUTd(0); // StartTime: Starting time of AVI data
813 AVIOUTd(0); // DataLength: Size of AVI data chunk
817 AVIOUTd(4+8+56+8+40); // size of the list
819 // video stream header
821 AVIOUTd(56); // # of bytes to follow
822 AVIOUT4("vids"); // type
823 AVIOUT4("ZMBV"); // handler
825 AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
826 AVIOUTd(0); // InitialFrames
827 AVIOUTd(1000000); // Scale
828 AVIOUTd(1000000*fps
); // Rate: Rate/Scale == samples/second
830 AVIOUTd(frames
); // Length
831 AVIOUTd(0); // SuggestedBufferSize
832 AVIOUTd(0xffffffffu
); // Quality
833 AVIOUTd(0); // SampleSize
836 // the video stream format
838 AVIOUTd(40); // # of bytes to follow
840 AVIOUTd(ec
.width
); // Width
841 AVIOUTd(ec
.height
); // Height
842 //OUTSHRT(1); OUTSHRT(24); // Planes, Count
844 AVIOUT4("ZMBV"); // Compression
845 AVIOUTd(ec
.width
*ec
.height
*4); // SizeImage (in bytes?)
846 AVIOUTd(0); // XPelsPerMeter
847 AVIOUTd(0); // YPelsPerMeter
848 AVIOUTd(0); // ClrUsed: Number of colors used
849 AVIOUTd(0); // ClrImportant: Number of colors important
851 if (audiowritten
> 0) {
854 AVIOUTd(4+8+56+8+16); // Length of list in bytes
856 // the audio stream header
858 AVIOUTd(56); // # of bytes to follow
860 AVIOUTd(0); // Format (Optionally)
862 AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
863 AVIOUTd(0); // InitialFrames
865 AVIOUTd(audiorate
*4); // rate, actual rate is scale/rate
867 if (!audiorate
) audiorate
= 1;
868 AVIOUTd(audiowritten
/4); // Length
869 AVIOUTd(0); // SuggestedBufferSize
870 AVIOUTd(~0); // Quality
871 AVIOUTd(4); // SampleSize
874 // the audio stream format
876 AVIOUTd(16); // # of bytes to follow
877 AVIOUTw(1); // Format, WAVE_ZMBV_FORMAT_PCM
878 AVIOUTw(2); // Number of channels
879 AVIOUTd(audiorate
); // SamplesPerSec
880 AVIOUTd(audiorate
*4); // AvgBytesPerSec
881 AVIOUTw(4); // BlockAlign
882 AVIOUTw(16); // BitsPerSample
884 long nmain
= header_pos
-main_list
-4;
885 // finish stream list, i.e. put number of bytes in the list to proper pos
886 long njunk
= AVI_HEADER_SIZE
-8-12-header_pos
;
889 // fix the size of the main list
890 header_pos
= main_list
;
892 header_pos
= AVI_HEADER_SIZE
-12;
894 AVIOUTd(written
+4); // Length of list in bytes
896 // first add the index table to the end
902 uint d
= indexused
-8;
904 index
[5] = ((d
>>8)&0xff);
905 index
[6] = ((d
>>16)&0xff);
906 index
[7] = ((d
>>24)&0xff);
907 fl
.rawWrite(index
[0..indexused
]);
909 // now replace the header
914 void writeChunk (string tag
, const(void)[] data
, uint flags
, bool writeToIndex
=false) @trusted {
915 if (tag
.length
!= 4) throw new Exception("invalid tag name");
916 uint size
= cast(uint)data
.length
;
918 fl
.rawWrite((&size
)[0..1]);
921 // write the actual data
922 uint writesize
= (size
+1)&~1;
923 if (size
> 0) fl
.rawWrite(data
);
924 if (writesize
!= size
) {
926 assert(writesize
-size
== 1); // just in case
930 written
+= writesize
+8;
931 if (indexused
+16 >= indexsize
) {
932 import core
.stdc
.stdlib
: realloc
;
933 import std
.exception
: enforce
;
934 void* ni
= realloc(index
, indexsize
+16*4096);
935 enforce(ni
!is null);
936 index
= cast(ubyte*)ni
;
937 indexsize
+= 16*4096;
940 void putu32 (usize pos
, ulong n
) @trusted nothrow @nogc {
942 idx
[pos
] = ((n
>>8)&0xff);
943 idx
[pos
] = ((n
>>16)&0xff);
944 idx
[pos
] = ((n
>>24)&0xff);
948 idx
= index
+indexused
;
960 void writeChunkVideo (const(void)[] framedata
) @trusted {
961 if (framedata
.length
== 0) throw new Exception("can't write empty frame");
962 ubyte b
= (cast(const(ubyte)[])framedata
)[0];
963 writeChunk("00dc", framedata
, (b
&0x01 ?
0x10 : 0), ((b
&0x01) != 0));
968 void writeChunkAudio (const(void)[] data
) @trusted {
969 if (data
.length
== 0) return;
970 writeChunk("01wb", data
, 0);
971 audiowritten
= cast(uint)data
.length
;