diff --git a/Include/enc-vfw.h b/Include/enc-vfw.h index 87d261f..74a55f9 100644 --- a/Include/enc-vfw.h +++ b/Include/enc-vfw.h @@ -28,6 +28,11 @@ namespace VFW { obs_encoder_info obsInfo; std::string FourCC, FourCC2; + std::vector stateInfo; + + int32_t defaultQuality; + int32_t defaultKeyframeRate; + bool hasConfigure, hasAbout; }; bool Initialize(); bool Finalize(); @@ -65,11 +70,28 @@ namespace VFW { private: VFW::Info* myInfo; HIC hIC; - std::vector vbiInput, vbiOutput; - BITMAPINFO *biInput, *biOutput; - std::vector inBuffer, outBuffer; COMPVARS cv; - uint32_t width, height, fpsNum, fpsDen; - uint32_t userKeyframeInterval, userBitrate, userQuality; + std::vector + m_bufferInput, + m_bufferInputBitmapInfo, + m_bufferPrevInput, + m_bufferPrevInputBitmapInfo, + m_bufferOutput, + m_bufferOutputBitmapInfo; + BITMAPINFO + *m_inputBitmapInfo, + *m_prevInputBitmapInfo, + *m_outputBitmapInfo; + + uint32_t + m_width, m_height, + m_fpsNum, m_fpsDen, + m_keyframeInterval, + m_bitrate, m_quality; + bool + m_useNormalCompress, + m_useTemporalFlag, + m_useBitrateFlag, + m_useQualityFlag; }; }; diff --git a/Include/plugin.h b/Include/plugin.h index c084f83..05bf070 100644 --- a/Include/plugin.h +++ b/Include/plugin.h @@ -36,6 +36,7 @@ #define PROP_KEYFRAME_INTERVAL "KeyframeInterval" #define PROP_MODE "Mode" #define PROP_MODE_NORMAL "Mode.Normal" +#define PROP_MODE_TEMPORAL "Mode.Temporal" #define PROP_MODE_SEQUENTIAL "Mode.Sequential" #define PROP_CONFIGURE "Configure" diff --git a/Source/enc-vfw.cpp b/Source/enc-vfw.cpp index 248606c..082f927 100644 --- a/Source/enc-vfw.cpp +++ b/Source/enc-vfw.cpp @@ -4,6 +4,7 @@ #include #include #include +#include std::map _IdToInfo; @@ -46,7 +47,7 @@ std::string FourCCFromInt32(DWORD& fccHandler) { return std::string(reinterpret_cast(&fccHandler), 4); } -std::string FormattedICCError(DWORD error) { +std::string FormattedICCError(LRESULT error) { switch (error) { case ICERR_OK: return "Ok"; @@ -89,7 +90,7 @@ bool VFW::Initialize() { icinfo.dwSize = sizeof(icinfo); DWORD fccType = 0; - for (size_t i = 0; ICInfo(fccType, i, &icinfo); i++) { + for (size_t i = 0; ICInfo(fccType, (DWORD)i, &icinfo); i++) { HIC hIC = ICOpen(icinfo.fccType, icinfo.fccHandler, ICMODE_QUERY); if (hIC) { ICINFO icinfo2; @@ -113,7 +114,7 @@ bool VFW::Initialize() { std::vector pathBuf(512); snprintf(pathBuf.data(), pathBuf.size(), "%.128ls", icinfo2.szDriver); info->Path = std::string(pathBuf.data()); - + // Register std::memset(&info->obsInfo, 0, sizeof(obs_encoder_info)); info->obsInfo.id = info->Id.data(); @@ -136,13 +137,20 @@ bool VFW::Initialize() { //info->obsInfo.get_sei_data = VFW::Encoder::get_sei_data; info->obsInfo.get_video_info = VFW::Encoder::get_video_info; - PLOG_INFO("Registering '%s' (Id: %s, FourCC1: %s, FourCC2: %s, Codec: %s, Driver: '%s')", + info->defaultQuality = ICGetDefaultQuality(hIC); + info->defaultKeyframeRate = ICGetDefaultKeyFrameRate(hIC); + info->hasConfigure = ICQueryConfigure(hIC); + info->hasAbout = ICQueryAbout(hIC); + + PLOG_INFO("Registering '%s' (Id: %s, FourCC1: %s, FourCC2: %s, Codec: %s, Driver: '%s', DefQual: %ld, DefKfR: %ld)", info->Name.c_str(), info->Id.c_str(), info->FourCC.c_str(), info->FourCC2.c_str(), info->obsInfo.codec, - info->Path.c_str()); + info->Path.c_str(), + info->defaultQuality, + info->defaultKeyframeRate); obs_register_encoder(&info->obsInfo); _IdToInfo.insert(std::make_pair(info->Id, info)); @@ -163,23 +171,34 @@ const char* VFW::Encoder::get_name(void* type_data) { } void VFW::Encoder::get_defaults(obs_data_t *settings) { - obs_data_set_default_int(settings, PROP_BITRATE, 2000); - obs_data_set_default_double(settings, PROP_QUALITY, 90.0); - obs_data_set_default_double(settings, PROP_KEYFRAME_INTERVAL, 2.0); + obs_data_set_default_int(settings, PROP_BITRATE, 0); + obs_data_set_default_double(settings, PROP_QUALITY, 100.0); + obs_data_set_default_double(settings, PROP_KEYFRAME_INTERVAL, 0.0); } obs_properties_t* VFW::Encoder::get_properties(void *data) { VFW::Info* info = static_cast(data); - + obs_properties_t* pr = obs_properties_create(); obs_properties_set_param(pr, data, nullptr); obs_property_t* p; - p = obs_properties_add_int(pr, PROP_BITRATE, "Bitrate", 1, 1000000, 1); + p = obs_properties_add_int_slider(pr, PROP_BITRATE, "Bitrate", 0, 300000, 1); + obs_property_set_visible(p, ((info->icInfo2.dwFlags & VIDCF_CRUNCH) != 0)); p = obs_properties_add_float_slider(pr, PROP_QUALITY, "Quality", 1, 100, 0.01); - p = obs_properties_add_float(pr, PROP_KEYFRAME_INTERVAL, "Keyframe Interval", 0.1, 30.00, 0.1); + obs_property_set_visible(p, ((info->icInfo2.dwFlags & VIDCF_QUALITY) != 0)); + p = obs_properties_add_float(pr, PROP_KEYFRAME_INTERVAL, "Keyframe Interval", 0.00, 30.00, 0.01); + + p = obs_properties_add_list(pr, PROP_MODE, "Mode", OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, "Normal", PROP_MODE_NORMAL); + if ((info->icInfo2.dwFlags & VIDCF_TEMPORAL) != 0) + obs_property_list_add_string(p, "Temporal", PROP_MODE_TEMPORAL); + obs_property_list_add_string(p, "Sequential", PROP_MODE_SEQUENTIAL); + p = obs_properties_add_button(pr, PROP_CONFIGURE, "Configure", cb_configure); + obs_property_set_visible(p, info->hasConfigure); p = obs_properties_add_button(pr, PROP_ABOUT, "About", cb_about); + obs_property_set_visible(p, info->hasAbout); return pr; } @@ -192,7 +211,25 @@ bool VFW::Encoder::cb_configure(obs_properties_t *pr, obs_property_t *p, void *d VFW::Info* info = static_cast(obs_properties_get_param(pr)); HIC hIC = ICOpen(info->icInfo.fccType, info->icInfo.fccHandler, ICMODE_FASTCOMPRESS); + if (info->stateInfo.size() > 0) { + LRESULT err = ICSetState(hIC, info->stateInfo.data(), info->stateInfo.size()); + if (err != ICERR_OK) { + PLOG_ERROR("Failed to set state before Configure: %s.", + FormattedICCError(err).c_str()); + } + } else { + ICSetState(hIC, NULL, 0); + } ICConfigure(hIC, GetDesktopWindow()); + DWORD size = ICGetStateSize(hIC); + if (size > 0) { + info->stateInfo.resize(size); + LRESULT err = ICGetState(hIC, info->stateInfo.data(), size); + if (err != ICERR_OK) { + PLOG_ERROR("Failed to retrieve state after Configure: %s.", + FormattedICCError(err).c_str()); + } + } ICClose(hIC); return false; @@ -204,6 +241,9 @@ bool VFW::Encoder::cb_about(obs_properties_t *pr, obs_property_t *p, void *data) UNREFERENCED_PARAMETER(data); VFW::Info* info = static_cast(obs_properties_get_param(pr)); + HIC hIC = ICOpen(info->icInfo.fccType, info->icInfo.fccHandler, ICMODE_FASTCOMPRESS); + ICAbout(hIC, GetDesktopWindow()); + ICClose(hIC); return false; } @@ -226,12 +266,12 @@ VFW::Encoder::Encoder(obs_data_t *settings, obs_encoder_t *encoder) { // Generic information. video_t* obsVideo = obs_encoder_video(encoder); const struct video_output_info *voi = video_output_get_info(obsVideo); - width = obs_encoder_get_width(encoder); height = obs_encoder_get_height(encoder); - fpsNum = voi->fps_num; fpsDen = voi->fps_den; - double_t factor = double_t(fpsNum) / double_t(fpsDen); - userKeyframeInterval = max(uint32_t(factor * obs_data_get_double(settings, PROP_KEYFRAME_INTERVAL)), 1); - userBitrate = obs_data_get_int(settings, PROP_BITRATE); - userQuality = uint32_t(obs_data_get_double(settings, PROP_QUALITY) * 100); + m_width = obs_encoder_get_width(encoder); m_height = obs_encoder_get_height(encoder); + m_fpsNum = voi->fps_num; m_fpsDen = voi->fps_den; + double_t factor = double_t(m_fpsNum) / double_t(m_fpsDen); + m_keyframeInterval = max(uint32_t(factor * obs_data_get_double(settings, PROP_KEYFRAME_INTERVAL)), 0); + m_bitrate = uint32_t(obs_data_get_int(settings, PROP_BITRATE)); + m_quality = uint32_t(obs_data_get_double(settings, PROP_QUALITY) * 100); hIC = ICOpen(myInfo->icInfo.fccType, myInfo->icInfo.fccHandler, ICMODE_FASTCOMPRESS); if (!hIC) { @@ -244,36 +284,68 @@ VFW::Encoder::Encoder(obs_data_t *settings, obs_encoder_t *encoder) { } PLOG_DEBUG("Initializing at %" PRIu32 "x%" PRIu32 " with %" PRIu32 "/%" PRIu32 " FPS", - width, height, fpsNum, fpsDen); + m_width, m_height, m_fpsNum, m_fpsDen); PLOG_DEBUG("Using Bitrate %" PRIu32 " kbit, Quality %" PRIu32 ".%02" PRIu32 "%%, Keyframe Interval %" PRIu32, - userBitrate, - userQuality / 100, userQuality % 100, - userKeyframeInterval); + m_bitrate, + m_quality / 100, m_quality % 100, + m_keyframeInterval); + + // Store temporary flags + m_useBitrateFlag = (myInfo->icInfo2.dwFlags & VIDCF_CRUNCH) != 0; + m_useQualityFlag = (myInfo->icInfo2.dwFlags & VIDCF_QUALITY) != 0; + + const char* emode = obs_data_get_string(settings, PROP_MODE); + if (strcmp(emode, PROP_MODE_NORMAL)) { + m_useTemporalFlag = false; + m_useNormalCompress = true; + } else if (strcmp(emode, PROP_MODE_TEMPORAL)) { + m_useTemporalFlag = true; + m_useNormalCompress = true; + } else { + m_useTemporalFlag = false; + m_useNormalCompress = false; + } + + // Load State from memory. + if (myInfo->stateInfo.size() > 0) { + LRESULT err = ICSetState(hIC, myInfo->stateInfo.data(), myInfo->stateInfo.size()); + if (err != ICERR_OK) { + PLOG_ERROR("Failed to set state before encoding: %s.", + FormattedICCError(err).c_str()); + } + } else { + ICSetState(hIC, NULL, 0); + } #pragma region Get Bitmap Information - vbiInput.resize(sizeof(BITMAPINFOHEADER)); - std::memset(vbiInput.data(), 0, vbiInput.size()); - biInput = reinterpret_cast(vbiInput.data()); - biInput->bmiHeader.biSize = vbiInput.size(); - biInput->bmiHeader.biWidth = width; - biInput->bmiHeader.biHeight = height; - biInput->bmiHeader.biPlanes = 1; - biInput->bmiHeader.biBitCount = 32; - biInput->bmiHeader.biCompression = BI_RGB; - biInput->bmiHeader.biSizeImage = width * height * (biInput->bmiHeader.biBitCount / 8) * biInput->bmiHeader.biPlanes; + m_bufferInputBitmapInfo.resize(sizeof(BITMAPINFOHEADER)); + std::memset(m_bufferInputBitmapInfo.data(), 0, m_bufferInputBitmapInfo.size()); + m_inputBitmapInfo = reinterpret_cast(m_bufferInputBitmapInfo.data()); + m_inputBitmapInfo->bmiHeader.biSize = (DWORD)m_bufferInputBitmapInfo.size(); + m_inputBitmapInfo->bmiHeader.biWidth = m_width; + m_inputBitmapInfo->bmiHeader.biHeight = m_height; + m_inputBitmapInfo->bmiHeader.biPlanes = 1; + m_inputBitmapInfo->bmiHeader.biBitCount = 32; + m_inputBitmapInfo->bmiHeader.biCompression = BI_RGB; + m_inputBitmapInfo->bmiHeader.biSizeImage = m_width * m_height * (m_inputBitmapInfo->bmiHeader.biBitCount / 8) * m_inputBitmapInfo->bmiHeader.biPlanes; - err = ICSendMessage(hIC, ICM_COMPRESS_GET_FORMAT, (DWORD_PTR)biInput, NULL); + err = ICSendMessage(hIC, ICM_COMPRESS_GET_FORMAT, (DWORD_PTR)m_inputBitmapInfo, NULL); if (err <= 0) { PLOG_ERROR("Unable to retrieve format information size: %s.", FormattedICCError(err).c_str()); throw std::exception(); } - vbiOutput.resize(err); - std::memset(vbiOutput.data(), 0, vbiOutput.size()); - biOutput = (BITMAPINFO*)vbiOutput.data(); - biOutput->bmiHeader.biSize = vbiOutput.size(); - err = ICSendMessage(hIC, ICM_COMPRESS_GET_FORMAT, (DWORD_PTR)biInput, (DWORD_PTR)biOutput); + if (m_useTemporalFlag) { + m_bufferPrevInputBitmapInfo.resize(m_bufferInputBitmapInfo.size()); + std::memcpy(m_bufferPrevInputBitmapInfo.data(), m_bufferInputBitmapInfo.data(), m_bufferInputBitmapInfo.size()); + } + + m_bufferOutputBitmapInfo.resize(err); + std::memset(m_bufferOutputBitmapInfo.data(), 0, m_bufferOutputBitmapInfo.size()); + m_outputBitmapInfo = (BITMAPINFO*)m_bufferOutputBitmapInfo.data(); + m_outputBitmapInfo->bmiHeader.biSize = (DWORD)m_bufferOutputBitmapInfo.size(); + err = ICSendMessage(hIC, ICM_COMPRESS_GET_FORMAT, (DWORD_PTR)m_inputBitmapInfo, (DWORD_PTR)m_outputBitmapInfo); if (err != ICERR_OK) { PLOG_ERROR("Unable to retrieve format information: %s.", FormattedICCError(err).c_str()); @@ -281,24 +353,40 @@ VFW::Encoder::Encoder(obs_data_t *settings, obs_encoder_t *encoder) { } #pragma endregion Get Bitmap Information - std::memset(&cv, 0, sizeof(COMPVARS)); - cv.cbSize = sizeof(COMPVARS); - cv.dwFlags = ICMF_COMPVARS_VALID; - cv.hic = hIC; - cv.fccType = myInfo->icInfo2.fccType; - cv.fccHandler = myInfo->icInfo2.fccHandler; - cv.lpbiOut = biOutput; - cv.lKey = userKeyframeInterval; - cv.lDataRate = userBitrate; - cv.lQ = userQuality; + // Prepare Input Buffers + size_t alignedWidth = (m_width / 16) * 16; + const size_t bufferSize = alignedWidth * m_height * 4; + m_bufferInput.resize(bufferSize); + m_bufferPrevInput.resize(bufferSize); - if (!ICSeqCompressFrameStart(&cv, biInput)) { - PLOG_ERROR("Unable to begin encoding."); - throw std::exception(); + // Begin Compression + if (m_useNormalCompress) { + LRESULT err = ICCompressBegin(hIC, m_inputBitmapInfo, m_outputBitmapInfo); + if (err != ICERR_OK) { + PLOG_ERROR("Unable to begin encoding: %s.", FormattedICCError(err).c_str()); + throw std::runtime_error(FormattedICCError(err)); + } + + DWORD size = ICCompressGetSize(hIC, m_inputBitmapInfo, m_outputBitmapInfo); + m_bufferOutput.resize(size); + } else { + std::memset(&cv, 0, sizeof(COMPVARS)); + cv.cbSize = sizeof(COMPVARS); + cv.dwFlags = ICMF_COMPVARS_VALID; + cv.hic = hIC; + cv.fccType = myInfo->icInfo2.fccType; + cv.fccHandler = myInfo->icInfo2.fccHandler; + cv.lpbiOut = m_outputBitmapInfo; + cv.lKey = m_keyframeInterval; + cv.lDataRate = m_bitrate; + cv.lQ = m_quality; + + if (!ICSeqCompressFrameStart(&cv, m_inputBitmapInfo)) { + PLOG_ERROR("Unable to begin encoding."); + throw std::exception(); + } } - inBuffer.resize(width * height * 4); - outBuffer.resize(width * height * 4); } void VFW::Encoder::destroy(void* data) { @@ -306,7 +394,13 @@ void VFW::Encoder::destroy(void* data) { } VFW::Encoder::~Encoder() { - ICSeqCompressFrameEnd(&cv); + if (m_useNormalCompress) { + ICCompressEnd(hIC); + } else { + ICSeqCompressFrameEnd(&cv); + //ICCompressorFree(&cv); + } + ICClose(hIC); } @@ -315,38 +409,110 @@ bool VFW::Encoder::encode(void *data, struct encoder_frame *frame, struct encode } bool VFW::Encoder::encode(struct encoder_frame *frame, struct encoder_packet *packet, bool *received_packet) { - DWORD dwCkID, cwCompFlags; - // Vertically invert Image for some reason. - const size_t ysize = height; - for (size_t y = 0; y < height; y++) { - uint8_t offset = frame->linesize[0] * y; - uint8_t target = width * 4 * (ysize - y - 1); - uint8_t* in = reinterpret_cast(frame->data[0]) + (frame->linesize[0] * y); - uint8_t* out = reinterpret_cast(inBuffer.data()) + (width * 4 * (ysize - y - 1)); - std::memcpy(out, in, width * 4); + size_t maxX = (m_width * 4) / 16; + if (maxX <= 0) + maxX = 1; + + // xF xE xD xC xB xA x9 x8 x7 x6 x5 x4 x3 x2 x1 x0 + // A B G R A B G R A B G R A B G R + + // Swizzle Mask (128-Bit swapping, could do higher but AMD decided to say fuck it) + /* + 0xF 0xE 0xD 0xC + 0xB 0xA 0x9 0x8 + 0x7 0x6 0x5 0x4 + 0x3 0x2 0x1 0x0 + */ + __m128i swizzle = _mm_set_epi8( + 0xF, 0xC, 0xD, 0xE, + 0xB, 0x8, 0x9, 0xA, + 0x7, 0x4, 0x5, 0x6, + 0x3, 0x0, 0x1, 0x2); + + const size_t lineSize = m_width * 4; + for (size_t y = 0; y < m_height; ++y) { + __m128i* pIn = reinterpret_cast<__m128i*>(frame->data[0] + (y * lineSize)); + __m128i* pOut = reinterpret_cast<__m128i*>(m_bufferInput.data() + ((m_height - 1 - y) * lineSize)); + + for (size_t x = 0; x < maxX; ++x) { + __m128i in = _mm_loadu_si128(pIn + x); + __m128i out = _mm_shuffle_epi8(in, swizzle); + _mm_storeu_si128(pOut + x, out); + } } - BOOL keyframe; LONG plSize = outBuffer.size(); - LPVOID fptr = ICSeqCompressFrame( - &cv, - 0, - reinterpret_cast(inBuffer.data()), - &keyframe, - &plSize); - if (fptr != NULL) { - *received_packet = true; - packet->keyframe = keyframe; - packet->dts = packet->pts = frame->pts; - packet->size = plSize; - packet->data = reinterpret_cast(fptr); - packet->type = OBS_ENCODER_VIDEO; - return true; + //for (size_t y = 0; y < m_height; ++y) { + + // uint8_t* lineOut = reinterpret_cast(m_bufferInput.data()) + (lineOutSize * (m_height - y - 1)); + + // for (size_t x = 0; x < m_width; ++x) { + // lineOut[x * 4] = lineIn[x * 4 + 2]; + // lineOut[x * 4 + 1] = lineIn[x * 4 + 1]; + // lineOut[x * 4 + 2] = lineIn[x * 4]; + // lineOut[x * 4 + 3] = lineIn[x * 4 + 3]; + // } + + //} + + bool isKeyframe = false; + + *received_packet = false; + if (m_useNormalCompress) { + DWORD dwFlags, cwCompFlags; + bool makeKeyframe = (m_keyframeInterval > 0) && ((frame->pts % m_keyframeInterval) == 0); + + LRESULT err = ICCompress(hIC, + makeKeyframe ? ICCOMPRESS_KEYFRAME : 0, + &(m_outputBitmapInfo->bmiHeader), m_bufferOutput.data(), + &(m_inputBitmapInfo->bmiHeader), m_bufferInput.data(), + &dwFlags, &cwCompFlags, + (LONG)frame->pts, + m_useBitrateFlag ? m_bitrate : 0, + m_useQualityFlag ? m_quality : 0, + !makeKeyframe && m_useTemporalFlag ? &(m_prevInputBitmapInfo->bmiHeader) : NULL, + !makeKeyframe && m_useTemporalFlag ? m_bufferPrevInput.data() : NULL); + if (err != ICERR_OK) { + PLOG_ERROR("Unable to encode: %s.", FormattedICCError(err).c_str()); + return false; + } + + // Swap Buffers + m_bufferPrevInput.swap(m_bufferInput); + + // Store some information we need right now. + packet->size = m_outputBitmapInfo->bmiHeader.biSizeImage; + isKeyframe = (cwCompFlags & AVIIF_KEYFRAME) != 0; } else { - PLOG_ERROR("Unable to encode."); - return false; + BOOL keyframe; LONG plSize = (LONG)m_bufferInput.size(); + LPVOID fptr = ICSeqCompressFrame( + &cv, + 0, + reinterpret_cast(m_bufferInput.data()), + &keyframe, + &plSize); + if (fptr == NULL) { + PLOG_ERROR("Unable to encode."); + return false; + } + + if (plSize > m_bufferOutput.size()) + m_bufferOutput.resize(plSize); + std::memcpy(m_bufferOutput.data(), fptr, plSize); + packet->size = plSize; + isKeyframe = keyframe != 0; + return true; } + + *received_packet = true; + packet->type = OBS_ENCODER_VIDEO; + packet->data = reinterpret_cast(m_bufferOutput.data()); + packet->keyframe = isKeyframe; + packet->pts = frame->pts; + packet->dts = frame->pts - 2; + + return true; } bool VFW::Encoder::update(void *data, obs_data_t *settings) { @@ -383,5 +549,7 @@ void VFW::Encoder::get_video_info(void *data, struct video_scale_info *info) { } void VFW::Encoder::get_video_info(struct video_scale_info *info) { - info->format = VIDEO_FORMAT_BGRX; + info->format = VIDEO_FORMAT_RGBA; + info->range = VIDEO_RANGE_FULL; + info->colorspace = VIDEO_CS_DEFAULT; }