#include "pch.h" #include "mp4enc.h" #include #include #define MP4V2_NO_STDINT_DEFS #include static void encoder_trace_callback(void* context, int level, const char* message) { LOG("ENCODER: %s", message); } MP4Encoder::~MP4Encoder() { destroy(); } bool MP4Encoder::init(int width, int height, int fps, int bitrate) noexcept { if (WelsCreateSVCEncoder(&m_encoder) != 0) return false; m_width = width; m_height = height; m_framerate = fps; m_bitrate = bitrate; //Encoder params SEncParamExt param; m_encoder->GetDefaultParams(¶m); param.iUsageType = CAMERA_VIDEO_REAL_TIME; param.fMaxFrameRate = m_framerate; param.iLtrMarkPeriod = 75; param.iPicWidth = m_width; param.iPicHeight = m_height; param.iTargetBitrate = m_bitrate; param.bEnableDenoise = false; param.iSpatialLayerNum = 1; param.bUseLoadBalancing = false; param.bEnableSceneChangeDetect = false; param.bEnableBackgroundDetection = false; param.bEnableAdaptiveQuant = false; param.bEnableFrameSkip = false; param.iMultipleThreadIdc = 0; //param.uiIntraPeriod = 10; for (int i = 0; i < param.iSpatialLayerNum; i++) { param.sSpatialLayers[i].iVideoWidth = m_width >> (param.iSpatialLayerNum - 1 - i); param.sSpatialLayers[i].iVideoHeight = m_height >> (param.iSpatialLayerNum - 1 - i); param.sSpatialLayers[i].fFrameRate = m_framerate; param.sSpatialLayers[i].iSpatialBitrate = m_bitrate; param.sSpatialLayers[i].uiProfileIdc = PRO_BASELINE; param.sSpatialLayers[i].uiLevelIdc = LEVEL_4_2; param.sSpatialLayers[i].iDLayerQp = 42; //SSliceArgument sliceArg; //sliceArg.uiSliceMode = SM_FIXEDSLCNUM_SLICE; //sliceArg.uiSliceNum = 0; //param.sSpatialLayers[i].sSliceArgument = sliceArg; } param.uiMaxNalSize = 1500; param.iTargetBitrate *= param.iSpatialLayerNum; if (m_encoder->InitializeExt(¶m) != 0) { LOG("Error initializing the encoder"); return false; } int trace_level = WELS_LOG_ERROR; m_encoder->SetOption(ENCODER_OPTION_TRACE_LEVEL, &trace_level); m_encoder->SetOption(ENCODER_OPTION_TRACE_CALLBACK, (void*)encoder_trace_callback); m_encoder->SetOption(ENCODER_OPTION_TRACE_CALLBACK_CONTEXT, this); int videoFormat = videoFormatI420; m_encoder->SetOption(ENCODER_OPTION_DATAFORMAT, &videoFormat); return true; } bool MP4Encoder::init() noexcept { return init(m_width, m_height, m_framerate, m_bitrate); } bool MP4Encoder::encode(const Image& rgba) noexcept { // lazy allocation of YUV buffer if (m_yuv_buffer.empty()) m_yuv_buffer.resize(m_width * m_height * 3 / 2); SFrameBSInfo info = { 0 }; SSourcePicture pic = { 0 }; pic.iPicWidth = m_width; pic.iPicHeight = m_height; pic.iColorFormat = videoFormatI420; pic.iStride[0] = pic.iPicWidth; pic.iStride[1] = pic.iStride[2] = pic.iPicWidth >> 1; pic.pData[0] = m_yuv_buffer.data(); pic.pData[1] = pic.pData[0] + ((size_t)m_width * m_height); pic.pData[2] = pic.pData[1] + ((size_t)m_width * m_height / 4); if (rgba.width != m_width || rgba.height != m_height) { Image resized = rgba.resize(m_width, m_height); libyuv::ABGRToI420(resized.data(), resized.width * 4, pic.pData[0], m_width, pic.pData[1], m_width / 2, pic.pData[2], m_width / 2, m_width, m_height); } else { libyuv::ABGRToI420(rgba.data(), rgba.width * 4, pic.pData[0], m_width, pic.pData[1], m_width / 2, pic.pData[2], m_width / 2, m_width, m_height); } if (m_encoder->EncodeFrame(&pic, &info)) { LOG("EcncodeFrame failed"); return false; } // No frames generated at this time if (info.eFrameType == videoFrameTypeSkip) return false; for (int layer = 0; layer < info.iLayerNum; layer++) { size_t bs_size = 0; for (int nal = 0; nal < info.sLayerInfo[layer].iNalCount; nal++) { std::array nalu_bytes; for (int i = 0; i < nalu_bytes.size(); i++) nalu_bytes[i] = info.sLayerInfo[layer].pBsBuf[bs_size + i]; if (nalu_bytes[4] == 0x67) // SPS { m_header.avc_profile = info.sLayerInfo[layer].pBsBuf[bs_size + 5]; m_header.avc_compat = info.sLayerInfo[layer].pBsBuf[bs_size + 6]; m_header.avc_level = info.sLayerInfo[layer].pBsBuf[bs_size + 7]; uint8_t* ptr = info.sLayerInfo[layer].pBsBuf + bs_size + 4; size_t sz = info.sLayerInfo[layer].pNalLengthInByte[nal] - 4ull; m_header.SPS_data = std::vector(ptr, ptr + sz); } else if (nalu_bytes[4] == 0x68) // PPS { uint8_t* ptr = info.sLayerInfo[layer].pBsBuf + bs_size + 4; size_t sz = info.sLayerInfo[layer].pNalLengthInByte[nal] - 4ull; m_header.PPS_data = std::vector(ptr, ptr + sz); } else { uint32_t nalu_sz = info.sLayerInfo[layer].pNalLengthInByte[nal]; uint8_t* data = info.sLayerInfo[layer].pBsBuf + bs_size; *(uint32_t*)data = BinaryStream::htonx(nalu_sz - 4); Frame::kType frame_type; if (nalu_bytes[4] == 0x65) // 0x65 I-frame frame_type = Frame::kType::IFrame; else if (nalu_bytes[4] == 0x61) // 0x61 P-frame frame_type = Frame::kType::PFrame; else // something else, don't care frame_type = Frame::kType::Unknown; m_frames.emplace_back(); m_frames.back().type = frame_type; m_frames.back().data = std::vector(data, data + nalu_sz); } //printf("nalu %x\n", nalu_bytes[4]); bs_size += info.sLayerInfo[layer].pNalLengthInByte[nal]; } } return true; } bool MP4Encoder::write_mp4(const std::string& filename) const noexcept { MP4FileHandle mp4 = MP4Create(filename.c_str()); MP4SetTimeScale(mp4, 90000); MP4SetVideoProfileLevel(mp4, 1); MP4TrackId mp4_track = MP4AddH264VideoTrack(mp4, 90000, 90000 / m_framerate, m_width, m_height, m_header.avc_profile, m_header.avc_compat, m_header.avc_level, 3); MP4AddH264SequenceParameterSet(mp4, mp4_track, m_header.SPS_data.data(), m_header.SPS_data.size()); MP4AddH264PictureParameterSet(mp4, mp4_track, m_header.PPS_data.data(), m_header.PPS_data.size()); for (const auto& f : m_frames) { if (!MP4WriteSample(mp4, mp4_track, f.data.data(), f.data.size(), MP4_INVALID_DURATION, 0, f.type == Frame::kType::IFrame)) { LOG("failed to write mp4 sample"); MP4Close(mp4); return false; } } MP4Close(mp4); return true; } void MP4Encoder::destroy() noexcept { if (m_encoder) { m_encoder->Uninitialize(); WelsDestroySVCEncoder(m_encoder); } m_encoder = nullptr; m_frames.clear(); m_header.PPS_data.clear(); m_header.SPS_data.clear(); } bool MP4Encoder::read(BinaryStreamReader& r) { Serializer::Descriptor d; r >> d; d.value("width", m_width); d.value("height", m_height); d.value("bitrate", m_bitrate); d.value("framerate", m_framerate); d.value("SPS", m_header.SPS_data); d.value("PPS", m_header.PPS_data); d.value("avc_profile", m_header.avc_profile); d.value("avc_compat", m_header.avc_compat); d.value("avc_level", m_header.avc_level); auto frames = d.get("frames"); for (const auto& f : frames->items) { if (auto fd = std::dynamic_pointer_cast(f)) { m_frames.emplace_back(); m_frames.back().type = (Frame::kType)fd->value("type"); m_frames.back().data = fd->value("data"); } } return true; } void MP4Encoder::write(BinaryStreamWriter& w) const { Serializer::Descriptor d; d.class_id = "mp4enc"; d.name = L"MP4 Encoder class"; d.props["width"] = std::make_shared(m_width); d.props["height"] = std::make_shared(m_height); d.props["bitrate"] = std::make_shared(m_bitrate); d.props["framerate"] = std::make_shared(m_framerate); d.props["SPS"] = std::make_shared(m_header.SPS_data); d.props["PPS"] = std::make_shared(m_header.PPS_data); d.props["avc_profile"] = std::make_shared(m_header.avc_profile); d.props["avc_compat"] = std::make_shared(m_header.avc_compat); d.props["avc_level"] = std::make_shared(m_header.avc_level); auto frames = std::make_shared(); for (const auto& f : m_frames) { auto fd = frames->add(); fd->props["type"] = std::make_shared((int)f.type); fd->props["data"] = std::make_shared(f.data); } d.props["frames"] = frames; w << d; }