add MP4Encoder class and test timelapse exporting

This commit is contained in:
2019-10-31 23:22:09 +01:00
parent 8e94d6b401
commit 9eecf60219
11 changed files with 283 additions and 47 deletions

192
src/mp4enc.cpp Normal file
View File

@@ -0,0 +1,192 @@
#include "pch.h"
#include "mp4enc.h"
#include <codec_api.h>
#include <libyuv.h>
#define MP4V2_NO_STDINT_DEFS
#include <mp4v2/mp4v2.h>
static void encoder_trace_callback(void* context, int level, const char* message)
{
LOG("ENCODER: %s", message);
}
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(&param);
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;
m_encoder->InitializeExt(&param);
int trace_level = WELS_LOG_ERROR;
m_encoder->SetOption(ENCODER_OPTION_TRACE_LEVEL, &trace_level);
m_encoder->SetOption(ENCODER_OPTION_TRACE_CALLBACK, encoder_trace_callback);
m_encoder->SetOption(ENCODER_OPTION_TRACE_CALLBACK_CONTEXT, this);
int videoFormat = videoFormatI420;
m_encoder->SetOption(ENCODER_OPTION_DATAFORMAT, &videoFormat);
}
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<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
{
uint8_t avc_profile = info.sLayerInfo[layer].pBsBuf[bs_size + 5];
uint8_t avc_profile_compat = info.sLayerInfo[layer].pBsBuf[bs_size + 6];
uint8_t 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_profile_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();
}