// Lzma2Encoder.cpp #include "StdAfx.h" #include "../../../C/Alloc.h" #include "../../../C/fast-lzma2/fl2_errors.h" #include "../Common/CWrappers.h" #include "../Common/StreamUtils.h" #include "Lzma2Encoder.h" #pragma warning(disable : 4127) namespace NCompress { namespace NLzma { HRESULT SetLzmaProp(PROPID propID, const PROPVARIANT &prop, CLzmaEncProps &ep); } namespace NLzma2 { CEncoder::CEncoder() { _encoder = NULL; _encoder = Lzma2Enc_Create(&g_AlignedAlloc, &g_BigAlloc); if (!_encoder) throw 1; } CEncoder::~CEncoder() { if (_encoder) Lzma2Enc_Destroy(_encoder); } HRESULT SetLzma2Prop(PROPID propID, const PROPVARIANT &prop, CLzma2EncProps &lzma2Props) { switch (propID) { case NCoderPropID::kBlockSize: { if (prop.vt == VT_UI4) lzma2Props.blockSize = prop.ulVal; else if (prop.vt == VT_UI8) lzma2Props.blockSize = prop.uhVal.QuadPart; else return E_INVALIDARG; break; } case NCoderPropID::kNumThreads: if (prop.vt != VT_UI4) return E_INVALIDARG; lzma2Props.numTotalThreads = (int)(prop.ulVal); break; default: RINOK(NLzma::SetLzmaProp(propID, prop, lzma2Props.lzmaProps)); } return S_OK; } STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps) { CLzma2EncProps lzma2Props; Lzma2EncProps_Init(&lzma2Props); for (UInt32 i = 0; i < numProps; i++) { RINOK(SetLzma2Prop(propIDs[i], coderProps[i], lzma2Props)); } return SResToHRESULT(Lzma2Enc_SetProps(_encoder, &lzma2Props)); } STDMETHODIMP CEncoder::SetCoderPropertiesOpt(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps) { for (UInt32 i = 0; i < numProps; i++) { const PROPVARIANT &prop = coderProps[i]; PROPID propID = propIDs[i]; if (propID == NCoderPropID::kExpectedDataSize) if (prop.vt == VT_UI8) Lzma2Enc_SetDataSize(_encoder, prop.uhVal.QuadPart); } return S_OK; } STDMETHODIMP CEncoder::WriteCoderProperties(ISequentialOutStream *outStream) { Byte prop = Lzma2Enc_WriteProperties(_encoder); return WriteStream(outStream, &prop, 1); } #define RET_IF_WRAP_ERROR(wrapRes, sRes, sResErrorCode) \ if (wrapRes != S_OK /* && (sRes == SZ_OK || sRes == sResErrorCode) */) return wrapRes; STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) { CSeqInStreamWrap inWrap; CSeqOutStreamWrap outWrap; CCompressProgressWrap progressWrap; inWrap.Init(inStream); outWrap.Init(outStream); progressWrap.Init(progress); SRes res = Lzma2Enc_Encode2(_encoder, &outWrap.vt, NULL, NULL, &inWrap.vt, NULL, 0, progress ? &progressWrap.vt : NULL); RET_IF_WRAP_ERROR(inWrap.Res, res, SZ_ERROR_READ) RET_IF_WRAP_ERROR(outWrap.Res, res, SZ_ERROR_WRITE) RET_IF_WRAP_ERROR(progressWrap.Res, res, SZ_ERROR_PROGRESS) return SResToHRESULT(res); } // Fast LZMA2 encoder static HRESULT TranslateError(size_t res) { if (FL2_getErrorCode(res) == FL2_error_memory_allocation) return E_OUTOFMEMORY; return S_FALSE; } #define CHECK_S(f_) do { \ size_t r_ = f_; \ if (FL2_isError(r_)) \ return TranslateError(r_); \ } while (false) #define CHECK_H(f_) do { \ HRESULT r_ = f_; \ if (r_ != S_OK) \ return r_; \ } while (false) #define CHECK_P(f) if (FL2_isError(f)) return E_INVALIDARG; /* check and convert error code */ #define MIN_BLOCK_SIZE (1U << 20) #define MAX_BLOCK_SIZE (1U << 28) CFastEncoder::FastLzma2::FastLzma2() : fcs(NULL), dict_pos(0) { } CFastEncoder::FastLzma2::~FastLzma2() { FL2_freeCCtx(fcs); } HRESULT CFastEncoder::FastLzma2::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps) { CLzma2EncProps lzma2Props; Lzma2EncProps_Init(&lzma2Props); for (UInt32 i = 0; i < numProps; i++) { RINOK(SetLzma2Prop(propIDs[i], coderProps[i], lzma2Props)); } if (fcs == NULL) { fcs = FL2_createCStreamMt(lzma2Props.numTotalThreads, 1); if (fcs == NULL) return E_OUTOFMEMORY; } if (lzma2Props.lzmaProps.algo > 2) { if (lzma2Props.lzmaProps.algo > 3) return E_INVALIDARG; lzma2Props.lzmaProps.algo = 2; FL2_CCtx_setParameter(fcs, FL2_p_highCompression, 1); FL2_CCtx_setParameter(fcs, FL2_p_compressionLevel, lzma2Props.lzmaProps.level); } else { FL2_CCtx_setParameter(fcs, FL2_p_compressionLevel, lzma2Props.lzmaProps.level); } size_t dictSize = lzma2Props.lzmaProps.dictSize; if (!dictSize) { dictSize = (UInt32)FL2_CCtx_getParameter(fcs, FL2_p_dictionarySize); } UInt64 reduceSize = lzma2Props.lzmaProps.reduceSize; reduceSize += (reduceSize < (UInt64)-1); /* prevent extra buffer shift after read */ dictSize = (UInt32)min(dictSize, reduceSize); dictSize = max(dictSize, FL2_DICTSIZE_MIN); CHECK_P(FL2_CCtx_setParameter(fcs, FL2_p_dictionarySize, dictSize)); if (lzma2Props.lzmaProps.algo >= 0) { CHECK_P(FL2_CCtx_setParameter(fcs, FL2_p_strategy, (unsigned)lzma2Props.lzmaProps.algo)); } if (lzma2Props.lzmaProps.fb > 0) CHECK_P(FL2_CCtx_setParameter(fcs, FL2_p_fastLength, lzma2Props.lzmaProps.fb)); if (lzma2Props.lzmaProps.mc > 0) CHECK_P(FL2_CCtx_setParameter(fcs, FL2_p_hybridCycles, lzma2Props.lzmaProps.mc)); if (lzma2Props.lzmaProps.lc >= 0) CHECK_P(FL2_CCtx_setParameter(fcs, FL2_p_literalCtxBits, lzma2Props.lzmaProps.lc)); if (lzma2Props.lzmaProps.lp >= 0) CHECK_P(FL2_CCtx_setParameter(fcs, FL2_p_literalPosBits, lzma2Props.lzmaProps.lp)); if (lzma2Props.lzmaProps.pb >= 0) CHECK_P(FL2_CCtx_setParameter(fcs, FL2_p_posBits, lzma2Props.lzmaProps.pb)); if (lzma2Props.blockSize == 0) lzma2Props.blockSize = min(max(MIN_BLOCK_SIZE, dictSize * 4U), MAX_BLOCK_SIZE); else if (lzma2Props.blockSize == LZMA2_ENC_PROPS__BLOCK_SIZE__SOLID) lzma2Props.blockSize = 0; unsigned r = 0; if (lzma2Props.blockSize != 0) { r = 1; // Do not exceed the block size. TODO: the lib should support setting a value instead of a multiplier. while (r < FL2_RESET_INTERVAL_MAX && (r + 1) * (UInt64)dictSize <= lzma2Props.blockSize) ++r; } CHECK_P(FL2_CCtx_setParameter(fcs, FL2_p_resetInterval, r)); FL2_CCtx_setParameter(fcs, FL2_p_omitProperties, 1); FL2_setCStreamTimeout(fcs, 500); return S_OK; } size_t CFastEncoder::FastLzma2::GetDictSize() const { return FL2_CCtx_getParameter(fcs, FL2_p_dictionarySize); } HRESULT CFastEncoder::FastLzma2::Begin() { CHECK_S(FL2_initCStream(fcs, 0)); CHECK_S(FL2_getDictionaryBuffer(fcs, &dict)); dict_pos = 0; return S_OK; } BYTE* CFastEncoder::FastLzma2::GetAvailableBuffer(unsigned long& size) { size = static_cast(dict.size - dict_pos); return reinterpret_cast(dict.dst) + dict_pos; } HRESULT CFastEncoder::FastLzma2::WaitAndReport(size_t& res, ICompressProgressInfo *progress) { while (FL2_isTimedOut(res)) { if (!UpdateProgress(progress)) return S_FALSE; res = FL2_waitCStream(fcs); } CHECK_S(res); return S_OK; } HRESULT CFastEncoder::FastLzma2::AddByteCount(size_t count, ISequentialOutStream *outStream, ICompressProgressInfo *progress) { dict_pos += count; if (dict_pos == dict.size) { size_t res = FL2_updateDictionary(fcs, dict_pos); CHECK_H(WaitAndReport(res, progress)); if (res != 0) CHECK_H(WriteBuffers(outStream)); res = FL2_getDictionaryBuffer(fcs, &dict); while (FL2_isTimedOut(res)) { if (!UpdateProgress(progress)) return S_FALSE; res = FL2_getDictionaryBuffer(fcs, &dict); } CHECK_S(res); dict_pos = 0; } if (!UpdateProgress(progress)) return S_FALSE; return S_OK; } bool CFastEncoder::FastLzma2::UpdateProgress(ICompressProgressInfo *progress) { if (progress) { UInt64 outProcessed; UInt64 inProcessed = FL2_getCStreamProgress(fcs, &outProcessed); HRESULT err = progress->SetRatioInfo(&inProcessed, &outProcessed); if (err != S_OK) { FL2_cancelCStream(fcs); return false; } } return true; } HRESULT CFastEncoder::FastLzma2::WriteBuffers(ISequentialOutStream *outStream) { size_t csize; for (;;) { FL2_cBuffer cbuf; do { csize = FL2_getNextCompressedBuffer(fcs, &cbuf); } while (FL2_isTimedOut(csize)); CHECK_S(csize); if (csize == 0) break; HRESULT err = WriteStream(outStream, cbuf.src, cbuf.size); if (err != S_OK) return err; } return S_OK; } HRESULT CFastEncoder::FastLzma2::End(ISequentialOutStream *outStream, ICompressProgressInfo *progress) { if (dict_pos) { size_t res = FL2_updateDictionary(fcs, dict_pos); CHECK_H(WaitAndReport(res, progress)); } size_t res = FL2_endStream(fcs, nullptr); CHECK_H(WaitAndReport(res, progress)); while (res) { CHECK_H(WriteBuffers(outStream)); res = FL2_endStream(fcs, nullptr); CHECK_H(WaitAndReport(res, progress)); } return S_OK; } void CFastEncoder::FastLzma2::Cancel() { FL2_cancelCStream(fcs); } CFastEncoder::CFastEncoder() { } CFastEncoder::~CFastEncoder() { } STDMETHODIMP CFastEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps) { return _encoder.SetCoderProperties(propIDs, coderProps, numProps); } #define LZMA2_DIC_SIZE_FROM_PROP(p) (((UInt32)2 | ((p) & 1)) << ((p) / 2 + 11)) STDMETHODIMP CFastEncoder::WriteCoderProperties(ISequentialOutStream *outStream) { Byte prop; unsigned i; size_t dictSize = _encoder.GetDictSize(); for (i = 0; i < 40; i++) if (dictSize <= LZMA2_DIC_SIZE_FROM_PROP(i)) break; prop = (Byte)i; return WriteStream(outStream, &prop, 1); } STDMETHODIMP CFastEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) { CHECK_H(_encoder.Begin()); size_t inSize; unsigned long dSize; do { BYTE* dict = _encoder.GetAvailableBuffer(dSize); inSize = dSize; HRESULT err = ReadStream(inStream, dict, &inSize); if (err != S_OK) { _encoder.Cancel(); return err; } CHECK_H(_encoder.AddByteCount(inSize, outStream, progress)); } while (inSize == dSize); CHECK_H(_encoder.End(outStream, progress)); return S_OK; } }}