Implementation for openh264 codec

This commit is contained in:
dijunkun
2023-11-03 02:31:57 -07:00
parent 6e19e53603
commit 5ae756bf7f
16 changed files with 653 additions and 63 deletions

View File

@@ -0,0 +1,271 @@
#include "openh264_encoder.h"
#include <chrono>
#include "log.h"
#ifdef __cplusplus
extern "C" {
#endif
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
};
#ifdef __cplusplus
};
#endif
#define YUV420P_BUFFER_SIZE 1280 * 720 * 3 / 2
unsigned char yuv420p_buffer[YUV420P_BUFFER_SIZE];
int NV12ToYUV420PFFmpeg(unsigned char *src_buffer, int width, int height,
unsigned char *dst_buffer) {
AVFrame *Input_pFrame = av_frame_alloc();
AVFrame *Output_pFrame = av_frame_alloc();
struct SwsContext *img_convert_ctx = sws_getContext(
width, height, AV_PIX_FMT_NV12, 1280, 720, AV_PIX_FMT_YUV420P,
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
av_image_fill_arrays(Input_pFrame->data, Input_pFrame->linesize, src_buffer,
AV_PIX_FMT_NV12, width, height, 1);
av_image_fill_arrays(Output_pFrame->data, Output_pFrame->linesize, dst_buffer,
AV_PIX_FMT_YUV420P, 1280, 720, 1);
sws_scale(img_convert_ctx, (uint8_t const **)Input_pFrame->data,
Input_pFrame->linesize, 0, height, Output_pFrame->data,
Output_pFrame->linesize);
if (Input_pFrame) av_free(Input_pFrame);
if (Output_pFrame) av_free(Output_pFrame);
if (img_convert_ctx) sws_freeContext(img_convert_ctx);
return 0;
}
OpenH264Encoder::OpenH264Encoder() { delete encoded_frame_; }
OpenH264Encoder::~OpenH264Encoder() { Release(); }
SEncParamExt OpenH264Encoder::CreateEncoderParams() const {
SEncParamExt encoder_params;
openh264_encoder_->GetDefaultParams(&encoder_params);
// if (codec_.mode == VideoCodecMode::kRealtimeVideo) { //
encoder_params.iUsageType = CAMERA_VIDEO_REAL_TIME;
// } else if (codec_.mode == VideoCodecMode::kScreensharing) {
// encoder_params.iUsageType = SCREEN_CONTENT_REAL_TIME;
// }
encoder_params.iPicWidth = frame_width_;
encoder_params.iPicHeight = frame_height_;
encoder_params.iTargetBitrate = target_bitrate_;
encoder_params.iMaxBitrate = max_bitrate_;
encoder_params.iRCMode = RC_BITRATE_MODE;
encoder_params.fMaxFrameRate = max_frame_rate_;
encoder_params.bEnableFrameSkip = false;
encoder_params.uiIntraPeriod = key_frame_interval_;
encoder_params.uiMaxNalSize = 0;
// Threading model: use auto.
// 0: auto (dynamic imp. internal encoder)
// 1: single thread (default value)
// >1: number of threads
encoder_params.iMultipleThreadIdc = 1;
// The base spatial layer 0 is the only one we use.
encoder_params.sSpatialLayers[0].iVideoWidth = encoder_params.iPicWidth;
encoder_params.sSpatialLayers[0].iVideoHeight = encoder_params.iPicHeight;
encoder_params.sSpatialLayers[0].fFrameRate = encoder_params.fMaxFrameRate;
encoder_params.sSpatialLayers[0].iSpatialBitrate =
encoder_params.iTargetBitrate;
encoder_params.sSpatialLayers[0].iMaxSpatialBitrate =
encoder_params.iMaxBitrate;
encoder_params.iTemporalLayerNum = 1;
if (encoder_params.iTemporalLayerNum > 1) {
encoder_params.iNumRefFrame = 1;
}
LOG_INFO("OpenH264 version is [{}.{}]", OPENH264_MAJOR, OPENH264_MINOR);
// SingleNalUnit
encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1;
encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceMode =
SM_SIZELIMITED_SLICE;
encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint =
static_cast<unsigned int>(max_payload_size_);
LOG_INFO("Encoder is configured with NALU constraint: {} bytes",
max_payload_size_);
return encoder_params;
}
int OpenH264Encoder::Init() {
// Create encoder.
if (WelsCreateSVCEncoder(&openh264_encoder_) != 0) {
LOG_ERROR("Failed to create OpenH264 encoder");
return -1;
}
encoded_frame_ = new uint8_t[YUV420P_BUFFER_SIZE];
int trace_level = WELS_LOG_WARNING;
openh264_encoder_->SetOption(ENCODER_OPTION_TRACE_LEVEL, &trace_level);
// Create encoder parameters based on the layer configuration.
SEncParamExt encoder_params = CreateEncoderParams();
if (openh264_encoder_->InitializeExt(&encoder_params) != 0) {
LOG_ERROR("Failed to initialize OpenH264 encoder");
// Release();
return -1;
}
int video_format = EVideoFormatType::videoFormatI420;
openh264_encoder_->SetOption(ENCODER_OPTION_DATAFORMAT, &video_format);
return 0;
}
int OpenH264Encoder::Encode(
const uint8_t *pData, int nSize,
std::function<int(char *encoded_packets, size_t size)> on_encoded_image) {
if (!openh264_encoder_) {
LOG_ERROR("Invalid openh264 encoder");
return -1;
}
if (0 == seq_++ % 300) {
ForceIdr();
}
NV12ToYUV420PFFmpeg((unsigned char *)pData, frame_width_, frame_height_,
(unsigned char *)yuv420p_buffer);
// memcpy(frame_->data[0], yuv420p_buffer, frame_width_ * frame_height_);
// memcpy(frame_->data[1], yuv420p_buffer + frame_width_ * frame_height_,
// frame_width_ * frame_height_ / 2);
// memcpy(frame_->data[2], yuv420p_buffer + frame_width_ * frame_height_ * 3 /
// 2,
// frame_width_ * frame_height_ / 2);
raw_frame_ = {0};
raw_frame_.iPicWidth = frame_width_;
raw_frame_.iPicHeight = frame_height_;
raw_frame_.iColorFormat = EVideoFormatType::videoFormatI420;
raw_frame_.uiTimeStamp =
std::chrono::high_resolution_clock::now().time_since_epoch().count();
raw_frame_.iStride[0] = frame_width_;
raw_frame_.iStride[1] = frame_width_ >> 1;
raw_frame_.iStride[2] = frame_width_ >> 1;
raw_frame_.pData[0] = (unsigned char *)yuv420p_buffer;
raw_frame_.pData[1] = raw_frame_.pData[0] + frame_width_ * frame_height_;
raw_frame_.pData[2] =
raw_frame_.pData[1] + (frame_width_ * frame_height_ >> 2);
SFrameBSInfo info;
memset(&info, 0, sizeof(SFrameBSInfo));
int enc_ret = openh264_encoder_->EncodeFrame(&raw_frame_, &info);
if (enc_ret != 0) {
LOG_ERROR("OpenH264 frame encoding failed, EncodeFrame returned {}",
enc_ret);
return -1;
}
#if 0
int encoded_frame_size = 0;
for (int layer = 0; layer < info.iLayerNum; ++layer) {
const SLayerBSInfo &layerInfo = info.sLayerInfo[layer];
size_t layer_len = 0;
memcpy(encoded_frame_ + encoded_frame_size, layerInfo.pBsBuf, layer_len);
encoded_frame_size += layer_len;
}
encoded_frame_size_ = encoded_frame_size;
if (on_encoded_image) {
on_encoded_image((char *)encoded_frame_, encoded_frame_size_);
} else {
OnEncodedImage((char *)encoded_frame_, encoded_frame_size_);
}
#else
if (info.eFrameType == videoFrameTypeInvalid) {
LOG_ERROR("videoFrameTypeInvalid");
return -1;
}
int temporal_id = 0;
int encoded_frame_size = 0;
if (info.eFrameType != videoFrameTypeSkip) {
int layer = 0;
while (layer < info.iLayerNum) {
SLayerBSInfo *pLayerBsInfo = &(info.sLayerInfo[layer]);
if (pLayerBsInfo != NULL) {
int layer_size = 0;
temporal_id = pLayerBsInfo->uiTemporalId;
int nal_index = pLayerBsInfo->iNalCount - 1;
do {
layer_size += pLayerBsInfo->pNalLengthInByte[nal_index];
--nal_index;
} while (nal_index >= 0);
memcpy(encoded_frame_ + encoded_frame_size, pLayerBsInfo->pBsBuf,
layer_size);
encoded_frame_size += layer_size;
}
++layer;
}
got_output = true;
} else {
is_keyframe = false;
}
if (encoded_frame_size > 0) {
encoded_frame_size_ = encoded_frame_size;
if (on_encoded_image) {
on_encoded_image((char *)encoded_frame_, encoded_frame_size_);
} else {
OnEncodedImage((char *)encoded_frame_, encoded_frame_size_);
}
EVideoFrameType ft_temp = info.eFrameType;
if (ft_temp == 1 || ft_temp == 2) {
is_keyframe = true;
} else if (ft_temp == 3) {
is_keyframe = false;
if (temporal_) {
if (temporal_id == 0 || temporal_id == 1) {
is_keyframe = true;
}
}
} else {
is_keyframe = false;
}
}
#endif
return 0;
}
int OpenH264Encoder::OnEncodedImage(char *encoded_packets, size_t size) {
LOG_INFO("OnEncodedImage not implemented");
return 0;
}
void OpenH264Encoder::ForceIdr() {
if (openh264_encoder_) {
openh264_encoder_->ForceIntraFrame(true);
}
}
int OpenH264Encoder::Release() {
if (openh264_encoder_) {
WelsDestroySVCEncoder(openh264_encoder_);
}
return 0;
}

View File

@@ -0,0 +1,62 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-11-03
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _OPENH264_ENCODER_H_
#define _OPENH264_ENCODER_H_
#include <wels/codec_api.h>
#include <wels/codec_app_def.h>
#include <wels/codec_def.h>
#include <wels/codec_ver.h>
#include <functional>
#include <vector>
#include "video_encoder.h"
class OpenH264Encoder : public VideoEncoder {
public:
OpenH264Encoder();
virtual ~OpenH264Encoder();
int Init();
int Encode(
const uint8_t* pData, int nSize,
std::function<int(char* encoded_packets, size_t size)> on_encoded_image);
virtual int OnEncodedImage(char* encoded_packets, size_t size);
void ForceIdr();
private:
SEncParamExt CreateEncoderParams() const;
int Release();
private:
int frame_width_ = 1280;
int frame_height_ = 720;
int key_frame_interval_ = 3000;
int target_bitrate_ = 1000;
int max_bitrate_ = 1000;
int max_payload_size_ = 3000;
int max_frame_rate_ = 30;
std::vector<std::vector<uint8_t>> encoded_packets_;
unsigned char* encoded_image_ = nullptr;
FILE* file_ = nullptr;
unsigned char* nv12_data_ = nullptr;
unsigned int seq_ = 0;
// openh264
ISVCEncoder* openh264_encoder_ = nullptr;
SSourcePicture raw_frame_;
uint8_t* encoded_frame_ = nullptr;
int encoded_frame_size_ = 0;
bool got_output = false;
bool is_keyframe = false;
int temporal_ = 1;
};
#endif