272 lines
9.9 KiB
C++
272 lines
9.9 KiB
C++
#include "pch.h"
|
|
#include "mp4enc.h"
|
|
#include "util.h"
|
|
|
|
#include <codec_api.h>
|
|
#include <libyuv.h>
|
|
|
|
#define MP4V2_NO_STDINT_DEFS
|
|
#include <mp4v2/mp4v2.h>
|
|
#include "shader.h"
|
|
|
|
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::ARGBScale
|
|
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
|
|
{
|
|
if (((uintptr_t)rgba.data() & 0xFF) != 0)
|
|
{
|
|
std::vector<uint8_t, AlignmentAllocator<uint8_t, 16>> aligned_buffer(rgba.data(), rgba.data() + (size_t)rgba.size());
|
|
libyuv::ABGRToI420(aligned_buffer.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);
|
|
}
|
|
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<uint8_t, 5> 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<Serializer::Integer>("width", m_width);
|
|
d.value<Serializer::Integer>("height", m_height);
|
|
d.value<Serializer::Integer>("bitrate", m_bitrate);
|
|
d.value<Serializer::Float>("framerate", m_framerate);
|
|
d.value<Serializer::RawData>("SPS", m_header.SPS_data);
|
|
d.value<Serializer::RawData>("PPS", m_header.PPS_data);
|
|
d.value<Serializer::Integer>("avc_profile", m_header.avc_profile);
|
|
d.value<Serializer::Integer>("avc_compat", m_header.avc_compat);
|
|
d.value<Serializer::Integer>("avc_level", m_header.avc_level);
|
|
auto frames = d.get<Serializer::List>("frames");
|
|
for (const auto& f : frames->items)
|
|
{
|
|
if (auto fd = std::dynamic_pointer_cast<Serializer::Descriptor>(f))
|
|
{
|
|
m_frames.emplace_back();
|
|
m_frames.back().type = (Frame::kType)fd->value<Serializer::Integer>("type");
|
|
m_frames.back().data = fd->value<Serializer::RawData>("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<Serializer::Integer>(m_width);
|
|
d.props["height"] = std::make_shared<Serializer::Integer>(m_height);
|
|
d.props["bitrate"] = std::make_shared<Serializer::Integer>(m_bitrate);
|
|
d.props["framerate"] = std::make_shared<Serializer::Float>(m_framerate);
|
|
d.props["SPS"] = std::make_shared<Serializer::RawData>(m_header.SPS_data);
|
|
d.props["PPS"] = std::make_shared<Serializer::RawData>(m_header.PPS_data);
|
|
d.props["avc_profile"] = std::make_shared<Serializer::Integer>(m_header.avc_profile);
|
|
d.props["avc_compat"] = std::make_shared<Serializer::Integer>(m_header.avc_compat);
|
|
d.props["avc_level"] = std::make_shared<Serializer::Integer>(m_header.avc_level);
|
|
auto frames = std::make_shared<Serializer::List>();
|
|
for (const auto& f : m_frames)
|
|
{
|
|
auto fd = frames->add<Serializer::Descriptor>();
|
|
fd->props["type"] = std::make_shared<Serializer::Integer>((int)f.type);
|
|
fd->props["data"] = std::make_shared<Serializer::RawData>(f.data);
|
|
}
|
|
d.props["frames"] = frames;
|
|
w << d;
|
|
}
|