// I, Danny Milosavljevic, hereby release this code into the public domain. unit bitstreams; {$MODE OBJFPC} {$M+} interface uses classes; type TWordBitCount = 0..16; TBitstream = class private fSource : TStream; fValue : array[0..2] of Byte; fReadPosition : Byte; // in *bits* in the buffer. between 0 and 7 since the buffer is scrolling. fWritePosition : Byte; // in *bits* in the buffer. fBOwnsSource : Boolean; public destructor Destroy(); override; published constructor Create(aSource : TStream; aBOwnsSource : Boolean); function Read(out aValue : Word; aBitCount : TWordBitCount) : TWordBitCount; inline; procedure ReadBuffer(out aValue : Word; aBitCount : TWordBitCount); inline; // function Read(aBitCount : 17..32; out aValue : DWord) : TDWordBitCount; // function Read(aBitCount : 33..64; out aValue : UINT64) : TUInt64BitCount; property BOwnsSource : Boolean read fBOwnsSource write fBOwnsSource; end; implementation uses sysutils; constructor TBitstream.Create(aSource : TStream; aBOwnsSource : Boolean); begin fSource := aSource; end; destructor TBitstream.Destroy(); begin if fBOwnsSource then FreeAndNil(fSource); inherited Destroy(); end; function TBitstream.Read(out aValue : Word; aBitCount : TWordBitCount) : TWordBitCount; inline; var vAvailableBitCount : Cardinal; // how many bits are available in the buffer "fValue". vItem : Byte; // the data that was last read from the actual stream. vReadPositionBytes : Cardinal; // the read position inside "fValue", in bytes. vItemIndex : 0..7; // within the current Item of the buffer "fValue", where to read (in bits). vItemAvailableBitCount : 0..7; // how many bits are available in the Item. vItemUsedBitCount : 0..7; // how many bits have been used up from the Item. vOriginalBitCount : TWordBitCount; // (for debugging:) aBitCount from the beginning. begin Result := 0; aValue := 0; vOriginalBitCount := aBitCount; vAvailableBitCount := fWritePosition - fReadPosition; while vAvailableBitCount < aBitCount do begin if fSource.Read(vItem, 1) = 0 then begin // EOF. // here, there could still be *some* bits in the buffer. Make sure to return them before raising exception. if vAvailableBitCount > 0 then begin Break; end else raise EReadError.Create('read error.'); end; // not helpful: vItem := fStream.ReadByte(); assert((fWritePosition and 7) = 0); fValue[(fWritePosition shr 3)] := vItem; Inc(fWritePosition, 8); vAvailableBitCount := fWritePosition - fReadPosition; end; assert(vAvailableBitCount > 0); if vAvailableBitCount < aBitCount then aBitCount := vAvailableBitCount; //assert(vAvailableBitCount >= aBitCount); assert((fReadPosition shr 3) = 0); Result := 0; vReadPositionBytes := fReadPosition shr 3; vItemIndex := (fReadPosition and 7); while aBitCount > 0 do begin vItem := fValue[vReadPositionBytes] shr vItemIndex; vItemAvailableBitCount := 8 - vItemIndex; if aBitCount < vItemAvailableBitCount then begin vItem := vItem and ((1 shl aBitCount) - 1); vItemUsedBitCount := aBitCount; Inc(vItemIndex, vItemUsedBitCount); assert(vItemIndex < 8); end else begin vItemUsedBitCount := vItemAvailableBitCount; // evil optimization: If the byte has been read, scroll the remainder of the buffer so that the read byte position doesn't change. Move(fValue[1], fValue[0], Sizeof(fValue[1]) * (Length(fValue) - 1)); Dec(fWritePosition, 8); //Inc(vReadPositionBytes); //Dec(fReadPosition, 8); vItemIndex := 0; end; Dec(aBitCount, vItemUsedBitCount); aValue := aValue or (vItem shl Result); Inc(Result, vItemUsedBitCount); end; fReadPosition := (vReadPositionBytes shl 3) or (vItemIndex); assert((aValue shr vOriginalBitCount) = 0); end; procedure TBitstream.ReadBuffer(out aValue : Word; aBitCount : TWordBitCount); inline; begin if Self.Read(aValue, aBitCount) <> aBitCount then raise EReadError.Create('read error.'); end; initialization {$ASSERTIONS ON} assert(Sizeof(Word) = 2); end.