880 lines
30 KiB
C++
880 lines
30 KiB
C++
#include "hw/rk3588_defaults.h"
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "utils/logger.h"
|
|
|
|
#if defined(RK3588_ENABLE_MPP)
|
|
extern "C" {
|
|
#include <rockchip/mpp_buffer.h>
|
|
#include <rockchip/mpp_frame.h>
|
|
#include <rockchip/mpp_packet.h>
|
|
#include <rockchip/rk_mpi.h>
|
|
}
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#if defined(RK3588_ENABLE_FFMPEG)
|
|
extern "C" {
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libavutil/imgutils.h>
|
|
}
|
|
#endif
|
|
|
|
namespace rk3588 {
|
|
namespace {
|
|
|
|
std::string ToLower(std::string s) {
|
|
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) {
|
|
return static_cast<char>(std::tolower(c));
|
|
});
|
|
return s;
|
|
}
|
|
|
|
std::vector<uint8_t> ReadByteArray(const SimpleJson& config, const std::string& key) {
|
|
std::vector<uint8_t> out;
|
|
const SimpleJson* node = config.Find(key);
|
|
if (!node || !node->IsArray()) return out;
|
|
const auto& arr = node->AsArray();
|
|
out.reserve(arr.size());
|
|
for (const auto& v : arr) {
|
|
int iv = v.AsInt(-1);
|
|
if (iv < 0) continue;
|
|
if (iv > 255) iv = 255;
|
|
out.push_back(static_cast<uint8_t>(iv));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::string ReadBackend(const SimpleJson& config) {
|
|
std::string backend = ToLower(config.ValueOr<std::string>("backend", ""));
|
|
if (!backend.empty()) return backend;
|
|
if (config.ValueOr<bool>("use_mpp", false)) return "mpp";
|
|
if (config.ValueOr<bool>("use_ffmpeg", false)) return "ffmpeg";
|
|
return "";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#if defined(RK3588_ENABLE_MPP)
|
|
namespace {
|
|
|
|
inline int Align16(int v) { return (v + 15) & ~15; }
|
|
|
|
static bool IsAnnexB(const uint8_t* data, size_t size) {
|
|
if (!data || size < 4) return false;
|
|
if (data[0] == 0 && data[1] == 0 && data[2] == 1) return true;
|
|
if (data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1) return true;
|
|
return false;
|
|
}
|
|
|
|
class MppDecoder : public IDecoder {
|
|
public:
|
|
~MppDecoder() override { Close(); }
|
|
|
|
Status Open(const SimpleJson& config) override {
|
|
Close();
|
|
std::string codec = ToLower(config.ValueOr<std::string>("codec", ""));
|
|
if (codec.empty()) return FailStatus("codec not set");
|
|
|
|
MppCodingType coding = MPP_VIDEO_CodingAVC;
|
|
if (codec == "h264") {
|
|
coding = MPP_VIDEO_CodingAVC;
|
|
} else if (codec == "h265" || codec == "hevc") {
|
|
coding = MPP_VIDEO_CodingHEVC;
|
|
} else {
|
|
return FailStatus("unsupported codec");
|
|
}
|
|
|
|
if (mpp_create(&ctx_, &mpi_) != MPP_OK) {
|
|
return FailStatus("mpp_create failed");
|
|
}
|
|
if (mpp_init(ctx_, MPP_CTX_DEC, coding) != MPP_OK) {
|
|
return FailStatus("mpp_init failed");
|
|
}
|
|
|
|
MppDecCfg cfg = nullptr;
|
|
mpp_dec_cfg_init(&cfg);
|
|
if (mpi_->control(ctx_, MPP_DEC_GET_CFG, cfg) != MPP_OK) {
|
|
mpp_dec_cfg_deinit(cfg);
|
|
return FailStatus("MPP_DEC_GET_CFG failed");
|
|
}
|
|
if (mpp_dec_cfg_set_u32(cfg, "base:split_parse", 1) != MPP_OK) {
|
|
mpp_dec_cfg_deinit(cfg);
|
|
return FailStatus("MPP_DEC_SET_CFG failed");
|
|
}
|
|
mpi_->control(ctx_, MPP_DEC_SET_CFG, cfg);
|
|
mpp_dec_cfg_deinit(cfg);
|
|
|
|
auto extradata = ReadByteArray(config, "extradata");
|
|
if (!extradata.empty()) {
|
|
if (!IsAnnexB(extradata.data(), extradata.size())) {
|
|
LogWarn("[hw] mpp extradata not annexb");
|
|
}
|
|
SendExtraData(extradata.data(), extradata.size());
|
|
}
|
|
opened_ = true;
|
|
return OkStatus();
|
|
}
|
|
|
|
Status Send(const DecodePacket& packet) override {
|
|
if (!opened_ || !ctx_ || !mpi_) return FailStatus("decoder not opened");
|
|
if (!packet.data || packet.size == 0) return FailStatus("empty packet");
|
|
|
|
MppPacket src = nullptr;
|
|
MPP_RET ret = mpp_packet_init(&src, const_cast<uint8_t*>(packet.data), packet.size);
|
|
if (ret != MPP_OK || !src) return FailStatus("mpp_packet_init failed");
|
|
|
|
MppPacket mpp_pkt = nullptr;
|
|
ret = mpp_packet_copy_init(&mpp_pkt, src);
|
|
mpp_packet_deinit(&src);
|
|
if (ret != MPP_OK || !mpp_pkt) return FailStatus("mpp_packet_copy_init failed");
|
|
|
|
mpp_packet_set_pos(mpp_pkt, mpp_packet_get_data(mpp_pkt));
|
|
mpp_packet_set_length(mpp_pkt, packet.size);
|
|
mpp_packet_set_pts(mpp_pkt, static_cast<int64_t>(packet.pts));
|
|
mpp_packet_clr_eos(mpp_pkt);
|
|
|
|
bool pkt_done = false;
|
|
MPP_RET last_put = MPP_NOK;
|
|
int put_tries = 0;
|
|
while (!pkt_done && put_tries++ < 200) {
|
|
MPP_RET put_ret = mpi_->decode_put_packet(ctx_, mpp_pkt);
|
|
if (put_ret == MPP_OK) {
|
|
pkt_done = true;
|
|
break;
|
|
}
|
|
last_put = put_ret;
|
|
DrainFrames();
|
|
usleep(2000);
|
|
}
|
|
|
|
DrainFrames();
|
|
mpp_packet_deinit(&mpp_pkt);
|
|
|
|
if (!pkt_done && last_put != MPP_ERR_BUFFER_FULL) {
|
|
return FailStatus("decode_put_packet failed");
|
|
}
|
|
return OkStatus();
|
|
}
|
|
|
|
Result<std::shared_ptr<Frame>> Receive() override {
|
|
if (!frames_.empty()) {
|
|
auto frame = frames_.front();
|
|
frames_.pop_front();
|
|
return frame;
|
|
}
|
|
if (!opened_ || !ctx_ || !mpi_) return MakeError<std::shared_ptr<Frame>>("decoder not opened");
|
|
DrainFrames();
|
|
if (!frames_.empty()) {
|
|
auto frame = frames_.front();
|
|
frames_.pop_front();
|
|
return frame;
|
|
}
|
|
return MakeError<std::shared_ptr<Frame>>("no_frame");
|
|
}
|
|
|
|
void Close() override {
|
|
frames_.clear();
|
|
if (frame_group_) {
|
|
mpp_buffer_group_put(frame_group_);
|
|
frame_group_ = nullptr;
|
|
}
|
|
if (ctx_) {
|
|
mpp_destroy(ctx_);
|
|
ctx_ = nullptr;
|
|
mpi_ = nullptr;
|
|
}
|
|
opened_ = false;
|
|
}
|
|
|
|
private:
|
|
void DrainFrames() {
|
|
for (int n = 0; n < 8; ++n) {
|
|
MppFrame frm = nullptr;
|
|
MPP_RET ret = mpi_->decode_get_frame(ctx_, &frm);
|
|
if (ret == MPP_ERR_TIMEOUT) return;
|
|
if (ret != MPP_OK) return;
|
|
if (!frm) return;
|
|
if (mpp_frame_get_info_change(frm)) {
|
|
frame_ = frm;
|
|
HandleInfoChange();
|
|
frame_ = nullptr;
|
|
mpp_frame_deinit(&frm);
|
|
continue;
|
|
}
|
|
auto out = MakeFrameFromMpp(frm);
|
|
if (out) frames_.push_back(out);
|
|
mpp_frame_deinit(&frm);
|
|
}
|
|
}
|
|
|
|
void HandleInfoChange() {
|
|
if (!ctx_ || !mpi_ || !frame_) return;
|
|
MppBufferGroup group = frame_group_;
|
|
size_t buf_size = mpp_frame_get_buf_size(frame_);
|
|
if (!group) {
|
|
auto try_group = [&](MppBufferType t) -> bool {
|
|
return mpp_buffer_group_get(&group, t, MPP_BUFFER_INTERNAL, MODULE_TAG, __FUNCTION__) == MPP_OK;
|
|
};
|
|
if (!try_group(static_cast<MppBufferType>(MPP_BUFFER_TYPE_DMA_HEAP | MPP_BUFFER_FLAGS_CACHABLE | MPP_BUFFER_FLAGS_DMA32)) &&
|
|
!try_group(static_cast<MppBufferType>(MPP_BUFFER_TYPE_DMA_HEAP | MPP_BUFFER_FLAGS_DMA32)) &&
|
|
!try_group(static_cast<MppBufferType>(MPP_BUFFER_TYPE_DMA_HEAP | MPP_BUFFER_FLAGS_CACHABLE)) &&
|
|
!try_group(static_cast<MppBufferType>(MPP_BUFFER_TYPE_DMA_HEAP)) &&
|
|
!try_group(static_cast<MppBufferType>(MPP_BUFFER_TYPE_DRM | MPP_BUFFER_FLAGS_CACHABLE)) &&
|
|
!try_group(static_cast<MppBufferType>(MPP_BUFFER_TYPE_DRM)) &&
|
|
!try_group(static_cast<MppBufferType>(MPP_BUFFER_TYPE_ION | MPP_BUFFER_FLAGS_CACHABLE)) &&
|
|
!try_group(static_cast<MppBufferType>(MPP_BUFFER_TYPE_ION))) {
|
|
LogError("[hw] mpp info_change: failed to alloc frame buffer group");
|
|
return;
|
|
}
|
|
frame_group_ = group;
|
|
} else {
|
|
mpp_buffer_group_clear(group);
|
|
}
|
|
mpp_buffer_group_limit_config(group, buf_size, 24);
|
|
mpi_->control(ctx_, MPP_DEC_SET_EXT_BUF_GROUP, group);
|
|
mpi_->control(ctx_, MPP_DEC_SET_INFO_CHANGE_READY, nullptr);
|
|
}
|
|
|
|
std::shared_ptr<Frame> MakeFrameFromMpp(MppFrame frm) {
|
|
MppBuffer buf = mpp_frame_get_buffer(frm);
|
|
if (!buf) return nullptr;
|
|
mpp_buffer_inc_ref(buf);
|
|
auto owner = std::shared_ptr<void>(buf, [](void* p) { mpp_buffer_put(reinterpret_cast<MppBuffer>(p)); });
|
|
|
|
int width = mpp_frame_get_width(frm);
|
|
int height = mpp_frame_get_height(frm);
|
|
int hor_stride = mpp_frame_get_hor_stride(frm);
|
|
int ver_stride = mpp_frame_get_ver_stride(frm);
|
|
MppFrameFormat mpp_fmt = mpp_frame_get_fmt(frm);
|
|
|
|
PixelFormat fmt = PixelFormat::UNKNOWN;
|
|
int plane_count = 0;
|
|
if (mpp_fmt == MPP_FMT_YUV420SP
|
|
#ifdef MPP_FMT_YUV420SP_10BIT
|
|
|| mpp_fmt == MPP_FMT_YUV420SP_10BIT
|
|
#endif
|
|
) {
|
|
fmt = PixelFormat::NV12;
|
|
plane_count = 2;
|
|
} else if (mpp_fmt == MPP_FMT_YUV420P
|
|
#ifdef MPP_FMT_YUV420P10
|
|
|| mpp_fmt == MPP_FMT_YUV420P10
|
|
#endif
|
|
) {
|
|
fmt = PixelFormat::YUV420;
|
|
plane_count = 3;
|
|
}
|
|
|
|
if (plane_count == 0) return nullptr;
|
|
|
|
auto frame = std::make_shared<Frame>();
|
|
frame->width = width;
|
|
frame->height = height;
|
|
frame->format = fmt;
|
|
frame->stride = hor_stride;
|
|
frame->plane_count = plane_count;
|
|
frame->SetDmaFd(mpp_buffer_get_fd(buf));
|
|
frame->data = static_cast<uint8_t*>(mpp_buffer_get_ptr(buf));
|
|
frame->data_size = mpp_buffer_get_size(buf);
|
|
frame->SetOwner(owner);
|
|
frame->pts = static_cast<uint64_t>(mpp_frame_get_pts(frm));
|
|
|
|
if (fmt == PixelFormat::NV12 && plane_count == 2) {
|
|
int y_size = hor_stride * ver_stride;
|
|
frame->planes[0] = {frame->data, hor_stride, y_size, 0};
|
|
frame->planes[1] = {frame->data + y_size, hor_stride, y_size / 2, y_size};
|
|
} else if (fmt == PixelFormat::YUV420 && plane_count == 3) {
|
|
int y_size = hor_stride * ver_stride;
|
|
int uv_stride = hor_stride / 2;
|
|
int uv_h = ver_stride / 2;
|
|
int u_size = uv_stride * uv_h;
|
|
frame->planes[0] = {frame->data, hor_stride, y_size, 0};
|
|
frame->planes[1] = {frame->data + y_size, uv_stride, u_size, y_size};
|
|
frame->planes[2] = {frame->data + y_size + u_size, uv_stride, u_size, y_size + u_size};
|
|
}
|
|
|
|
frame->SyncBufferFromFrame();
|
|
|
|
return frame;
|
|
}
|
|
|
|
bool SendExtraData(const uint8_t* data, size_t size) {
|
|
if (!ctx_ || !mpi_ || !data || size == 0) return false;
|
|
|
|
MppPacket src = nullptr;
|
|
MPP_RET ret = mpp_packet_init(&src, const_cast<uint8_t*>(data), size);
|
|
if (ret != MPP_OK || !src) return false;
|
|
|
|
MppPacket pkt = nullptr;
|
|
ret = mpp_packet_copy_init(&pkt, src);
|
|
mpp_packet_deinit(&src);
|
|
if (ret != MPP_OK || !pkt) return false;
|
|
|
|
mpp_packet_set_pos(pkt, mpp_packet_get_data(pkt));
|
|
mpp_packet_set_length(pkt, size);
|
|
mpp_packet_set_pts(pkt, 0);
|
|
mpp_packet_set_extra_data(pkt);
|
|
|
|
int tries = 0;
|
|
while (tries++ < 200) {
|
|
MPP_RET put_ret = mpi_->decode_put_packet(ctx_, pkt);
|
|
if (put_ret == MPP_OK) {
|
|
mpp_packet_deinit(&pkt);
|
|
return true;
|
|
}
|
|
MppFrame tmp = nullptr;
|
|
if (mpi_->decode_get_frame(ctx_, &tmp) == MPP_OK && tmp) {
|
|
if (mpp_frame_get_info_change(tmp)) {
|
|
frame_ = tmp;
|
|
HandleInfoChange();
|
|
frame_ = nullptr;
|
|
}
|
|
mpp_frame_deinit(&tmp);
|
|
}
|
|
usleep(2000);
|
|
}
|
|
|
|
mpp_packet_deinit(&pkt);
|
|
return false;
|
|
}
|
|
|
|
MppCtx ctx_ = nullptr;
|
|
MppApi* mpi_ = nullptr;
|
|
MppBufferGroup frame_group_ = nullptr;
|
|
MppFrame frame_ = nullptr;
|
|
std::deque<std::shared_ptr<Frame>> frames_;
|
|
bool opened_ = false;
|
|
};
|
|
|
|
class MppEncoder : public IEncoder {
|
|
public:
|
|
~MppEncoder() override { Close(); }
|
|
|
|
Status Open(const SimpleJson& config) override {
|
|
Close();
|
|
width_ = config.ValueOr<int>("width", 0);
|
|
height_ = config.ValueOr<int>("height", 0);
|
|
fps_ = config.ValueOr<int>("fps", 25);
|
|
gop_ = config.ValueOr<int>("gop", std::max(1, fps_) * 2);
|
|
bitrate_bps_ = config.ValueOr<int>("bitrate_kbps", 4000) * 1000;
|
|
codec_ = ToLower(config.ValueOr<std::string>("codec", "h264"));
|
|
std::string fmt = ToLower(config.ValueOr<std::string>("pixel_format", "nv12"));
|
|
|
|
if (width_ <= 0 || height_ <= 0) return FailStatus("invalid size");
|
|
if (fmt == "nv12") {
|
|
mpp_fmt_ = MPP_FMT_YUV420SP;
|
|
} else if (fmt == "yuv420") {
|
|
mpp_fmt_ = MPP_FMT_YUV420P;
|
|
} else {
|
|
return FailStatus("unsupported pixel format");
|
|
}
|
|
|
|
if (codec_ == "h265" || codec_ == "hevc") {
|
|
coding_ = MPP_VIDEO_CodingHEVC;
|
|
} else {
|
|
coding_ = MPP_VIDEO_CodingAVC;
|
|
}
|
|
|
|
hor_stride_ = Align16(width_);
|
|
ver_stride_ = Align16(height_);
|
|
|
|
if (mpp_create(&ctx_, &mpi_) != MPP_OK) return FailStatus("mpp_create failed");
|
|
if (mpp_init(ctx_, MPP_CTX_ENC, coding_) != MPP_OK) return FailStatus("mpp_init failed");
|
|
|
|
if (mpp_enc_cfg_init(&cfg_) != MPP_OK) return FailStatus("mpp_enc_cfg_init failed");
|
|
if (mpi_->control(ctx_, MPP_ENC_GET_CFG, cfg_) != MPP_OK) return FailStatus("MPP_ENC_GET_CFG failed");
|
|
|
|
mpp_enc_cfg_set_s32(cfg_, "prep:width", width_);
|
|
mpp_enc_cfg_set_s32(cfg_, "prep:height", height_);
|
|
mpp_enc_cfg_set_s32(cfg_, "prep:hor_stride", hor_stride_);
|
|
mpp_enc_cfg_set_s32(cfg_, "prep:ver_stride", ver_stride_);
|
|
mpp_enc_cfg_set_s32(cfg_, "prep:format", mpp_fmt_);
|
|
|
|
mpp_enc_cfg_set_s32(cfg_, "rc:mode", MPP_ENC_RC_MODE_CBR);
|
|
mpp_enc_cfg_set_s32(cfg_, "rc:gop", gop_);
|
|
mpp_enc_cfg_set_s32(cfg_, "rc:fps_in_num", fps_);
|
|
mpp_enc_cfg_set_s32(cfg_, "rc:fps_in_denorm", 1);
|
|
mpp_enc_cfg_set_s32(cfg_, "rc:fps_out_num", fps_);
|
|
mpp_enc_cfg_set_s32(cfg_, "rc:fps_out_denorm", 1);
|
|
mpp_enc_cfg_set_s32(cfg_, "rc:bps_target", bitrate_bps_);
|
|
mpp_enc_cfg_set_s32(cfg_, "rc:bps_max", bitrate_bps_ * 3 / 2);
|
|
mpp_enc_cfg_set_s32(cfg_, "rc:bps_min", bitrate_bps_ / 2);
|
|
mpp_enc_cfg_set_s32(cfg_, "codec:type", coding_);
|
|
|
|
if (mpi_->control(ctx_, MPP_ENC_SET_CFG, cfg_) != MPP_OK) return FailStatus("MPP_ENC_SET_CFG failed");
|
|
|
|
if (coding_ == MPP_VIDEO_CodingAVC || coding_ == MPP_VIDEO_CodingHEVC) {
|
|
MppEncHeaderMode header_mode = MPP_ENC_HEADER_MODE_EACH_IDR;
|
|
mpi_->control(ctx_, MPP_ENC_SET_HEADER_MODE, &header_mode);
|
|
}
|
|
|
|
RK_S32 timeout = 2000;
|
|
mpi_->control(ctx_, MPP_SET_OUTPUT_TIMEOUT, &timeout);
|
|
|
|
MppPacket hdr = nullptr;
|
|
if (mpi_->control(ctx_, MPP_ENC_GET_HDR_SYNC, &hdr) == MPP_OK && hdr) {
|
|
auto* data = static_cast<uint8_t*>(mpp_packet_get_pos(hdr));
|
|
size_t len = mpp_packet_get_length(hdr);
|
|
header_.assign(data, data + len);
|
|
mpp_packet_deinit(&hdr);
|
|
}
|
|
|
|
if (mpp_buffer_group_get_internal(&frm_grp_, MPP_BUFFER_TYPE_DRM, NULL) != MPP_OK) {
|
|
if (mpp_buffer_group_get_internal(&frm_grp_, MPP_BUFFER_TYPE_NORMAL, NULL) != MPP_OK) {
|
|
return FailStatus("mpp_buffer_group_get_internal failed");
|
|
}
|
|
}
|
|
|
|
initialized_ = true;
|
|
return OkStatus();
|
|
}
|
|
|
|
Status Send(const std::shared_ptr<Frame>& frame) override {
|
|
if (!initialized_ || !frame) return FailStatus("encoder not ready");
|
|
|
|
MppBuffer buf = nullptr;
|
|
size_t frame_size = CalcFrameSize();
|
|
|
|
if (frame->DmaFd() >= 0 && frame->data_size > 0) {
|
|
MppBufferInfo info{};
|
|
info.type = MPP_BUFFER_TYPE_EXT_DMA;
|
|
info.size = frame->data_size;
|
|
info.fd = frame->DmaFd();
|
|
if (mpp_buffer_import(&buf, &info) != MPP_OK) {
|
|
buf = nullptr;
|
|
}
|
|
}
|
|
|
|
if (!buf) {
|
|
if (!frame->data) return FailStatus("frame has no data");
|
|
if (mpp_buffer_get(frm_grp_, &buf, frame_size) != MPP_OK) {
|
|
return FailStatus("mpp_buffer_get failed");
|
|
}
|
|
if (!CopyToBuffer(*frame, buf)) {
|
|
mpp_buffer_put(buf);
|
|
return FailStatus("copy to buffer failed");
|
|
}
|
|
}
|
|
|
|
MppFrame mpp_frame = nullptr;
|
|
mpp_frame_init(&mpp_frame);
|
|
mpp_frame_set_width(mpp_frame, width_);
|
|
mpp_frame_set_height(mpp_frame, height_);
|
|
mpp_frame_set_hor_stride(mpp_frame, hor_stride_);
|
|
mpp_frame_set_ver_stride(mpp_frame, ver_stride_);
|
|
mpp_frame_set_fmt(mpp_frame, mpp_fmt_);
|
|
mpp_frame_set_pts(mpp_frame, static_cast<int64_t>(frame->pts));
|
|
mpp_frame_set_buffer(mpp_frame, buf);
|
|
|
|
if (mpi_->encode_put_frame(ctx_, mpp_frame) != MPP_OK) {
|
|
mpp_frame_deinit(&mpp_frame);
|
|
mpp_buffer_put(buf);
|
|
return FailStatus("encode_put_frame failed");
|
|
}
|
|
mpp_frame_deinit(&mpp_frame);
|
|
|
|
while (true) {
|
|
MppPacket packet = nullptr;
|
|
MPP_RET ret = mpi_->encode_get_packet(ctx_, &packet);
|
|
if (ret == MPP_ERR_TIMEOUT) {
|
|
usleep(2000);
|
|
continue;
|
|
}
|
|
if (ret != MPP_OK || !packet) break;
|
|
|
|
EncodePacket out;
|
|
auto* pos = static_cast<uint8_t*>(mpp_packet_get_pos(packet));
|
|
size_t len = mpp_packet_get_length(packet);
|
|
out.data.assign(pos, pos + len);
|
|
RK_U32 flag = mpp_packet_get_flag(packet);
|
|
out.keyframe = (flag & 0x10) != 0; // MPP_PACKET_FLAG_INTRA
|
|
// Fallback: detect keyframe by checking for IDR (5) or SPS (7) NAL types
|
|
if (!out.keyframe && len >= 5) {
|
|
const uint8_t* d = out.data.data();
|
|
for (size_t i = 0; i + 4 < len && i < 1000; ++i) {
|
|
if (d[i]==0 && d[i+1]==0 && d[i+2]==0 && d[i+3]==1) {
|
|
uint8_t nal_type = d[i+4] & 0x1F;
|
|
if (nal_type == 5 || nal_type == 7) { // IDR or SPS
|
|
out.keyframe = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
int64_t mpp_pts = mpp_packet_get_pts(packet);
|
|
out.pts = mpp_pts > 0 ? static_cast<uint64_t>(mpp_pts) : frame->pts;
|
|
if (!header_sent_ && !header_.empty()) {
|
|
out.extra_data = header_;
|
|
header_sent_ = true;
|
|
}
|
|
packets_.push_back(std::move(out));
|
|
mpp_packet_deinit(&packet);
|
|
break;
|
|
}
|
|
|
|
mpp_buffer_put(buf);
|
|
return OkStatus();
|
|
}
|
|
|
|
Result<EncodePacket> Receive() override {
|
|
if (!packets_.empty()) {
|
|
auto pkt = std::move(packets_.front());
|
|
packets_.pop_front();
|
|
return pkt;
|
|
}
|
|
return MakeError<EncodePacket>("no_packet");
|
|
}
|
|
|
|
std::vector<uint8_t> ExtraData() const override {
|
|
return header_;
|
|
}
|
|
|
|
void Close() override {
|
|
packets_.clear();
|
|
header_.clear();
|
|
header_sent_ = false;
|
|
if (frm_grp_) {
|
|
mpp_buffer_group_put(frm_grp_);
|
|
frm_grp_ = nullptr;
|
|
}
|
|
if (cfg_) {
|
|
mpp_enc_cfg_deinit(cfg_);
|
|
cfg_ = nullptr;
|
|
}
|
|
if (ctx_) {
|
|
if (mpi_) mpi_->reset(ctx_);
|
|
mpp_destroy(ctx_);
|
|
ctx_ = nullptr;
|
|
mpi_ = nullptr;
|
|
}
|
|
initialized_ = false;
|
|
}
|
|
|
|
private:
|
|
size_t CalcFrameSize() const {
|
|
if (mpp_fmt_ == MPP_FMT_YUV420SP || mpp_fmt_ == MPP_FMT_YUV420P) {
|
|
return static_cast<size_t>(hor_stride_) * ver_stride_ * 3 / 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool CopyToBuffer(const Frame& frame, MppBuffer buf) const {
|
|
auto* dst = static_cast<uint8_t*>(mpp_buffer_get_ptr(buf));
|
|
size_t dst_size = mpp_buffer_get_size(buf);
|
|
size_t need = CalcFrameSize();
|
|
if (!dst || dst_size < need) return false;
|
|
std::memset(dst, 0, dst_size);
|
|
|
|
if (frame.plane_count < 2) return false;
|
|
const FramePlane& y = frame.planes[0];
|
|
const FramePlane& uv = frame.planes[1];
|
|
if (!y.data || !uv.data) return false;
|
|
|
|
for (int row = 0; row < height_; ++row) {
|
|
std::memcpy(dst + row * hor_stride_, y.data + row * y.stride,
|
|
std::min(hor_stride_, y.stride));
|
|
}
|
|
|
|
uint8_t* dst_uv = dst + hor_stride_ * ver_stride_;
|
|
int uv_rows = ver_stride_ / 2;
|
|
if (mpp_fmt_ == MPP_FMT_YUV420SP) {
|
|
for (int row = 0; row < uv_rows; ++row) {
|
|
std::memcpy(dst_uv + row * hor_stride_, uv.data + row * uv.stride,
|
|
std::min(hor_stride_, uv.stride));
|
|
}
|
|
} else if (frame.plane_count >= 3) {
|
|
const FramePlane& v = frame.planes[2];
|
|
uint8_t* dst_u = dst_uv;
|
|
uint8_t* dst_v = dst_uv + (hor_stride_ / 2) * uv_rows;
|
|
for (int row = 0; row < uv_rows; ++row) {
|
|
std::memcpy(dst_u + row * (hor_stride_ / 2), uv.data + row * uv.stride,
|
|
std::min(hor_stride_ / 2, uv.stride));
|
|
std::memcpy(dst_v + row * (hor_stride_ / 2), v.data + row * v.stride,
|
|
std::min(hor_stride_ / 2, v.stride));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
MppCtx ctx_ = nullptr;
|
|
MppApi* mpi_ = nullptr;
|
|
MppEncCfg cfg_ = nullptr;
|
|
MppBufferGroup frm_grp_ = nullptr;
|
|
MppCodingType coding_ = MPP_VIDEO_CodingAVC;
|
|
MppFrameFormat mpp_fmt_ = MPP_FMT_YUV420SP;
|
|
int width_ = 0;
|
|
int height_ = 0;
|
|
int hor_stride_ = 0;
|
|
int ver_stride_ = 0;
|
|
int fps_ = 25;
|
|
int gop_ = 50;
|
|
int bitrate_bps_ = 4000000;
|
|
std::string codec_;
|
|
std::vector<uint8_t> header_;
|
|
bool initialized_ = false;
|
|
bool header_sent_ = false;
|
|
std::deque<EncodePacket> packets_;
|
|
};
|
|
|
|
} // namespace
|
|
#endif
|
|
|
|
#if defined(RK3588_ENABLE_FFMPEG)
|
|
namespace {
|
|
|
|
AVCodecID CodecIdFromConfig(const SimpleJson& config) {
|
|
int codec_id = config.ValueOr<int>("codec_id", 0);
|
|
if (codec_id > 0) return static_cast<AVCodecID>(codec_id);
|
|
std::string codec = ToLower(config.ValueOr<std::string>("codec", ""));
|
|
if (codec == "h264") return AV_CODEC_ID_H264;
|
|
if (codec == "h265" || codec == "hevc") return AV_CODEC_ID_HEVC;
|
|
return AV_CODEC_ID_NONE;
|
|
}
|
|
|
|
class FfmpegDecoder : public IDecoder {
|
|
public:
|
|
~FfmpegDecoder() override { Close(); }
|
|
|
|
Status Open(const SimpleJson& config) override {
|
|
Close();
|
|
AVCodecID codec_id = CodecIdFromConfig(config);
|
|
if (codec_id == AV_CODEC_ID_NONE) return FailStatus("unsupported codec");
|
|
|
|
const AVCodec* codec = avcodec_find_decoder(codec_id);
|
|
if (!codec) return FailStatus("decoder not found");
|
|
|
|
codec_ctx_ = avcodec_alloc_context3(codec);
|
|
if (!codec_ctx_) return FailStatus("avcodec_alloc_context3 failed");
|
|
codec_ctx_->time_base = AVRational{1, 1000000};
|
|
codec_ctx_->pkt_timebase = AVRational{1, 1000000};
|
|
|
|
auto extradata = ReadByteArray(config, "extradata");
|
|
if (!extradata.empty()) {
|
|
codec_ctx_->extradata_size = static_cast<int>(extradata.size());
|
|
codec_ctx_->extradata = static_cast<uint8_t*>(av_mallocz(extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE));
|
|
std::memcpy(codec_ctx_->extradata, extradata.data(), extradata.size());
|
|
}
|
|
|
|
if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) return FailStatus("avcodec_open2 failed");
|
|
frame_ = av_frame_alloc();
|
|
if (!frame_) return FailStatus("av_frame_alloc failed");
|
|
opened_ = true;
|
|
return OkStatus();
|
|
}
|
|
|
|
Status Send(const DecodePacket& packet) override {
|
|
if (!opened_ || !codec_ctx_) return FailStatus("decoder not opened");
|
|
if (!packet.data || packet.size == 0) return FailStatus("empty packet");
|
|
|
|
AVPacket* pkt = av_packet_alloc();
|
|
if (!pkt) return FailStatus("av_packet_alloc failed");
|
|
if (av_new_packet(pkt, static_cast<int>(packet.size)) < 0) {
|
|
av_packet_free(&pkt);
|
|
return FailStatus("av_new_packet failed");
|
|
}
|
|
std::memcpy(pkt->data, packet.data, packet.size);
|
|
pkt->pts = static_cast<int64_t>(packet.pts);
|
|
pkt->dts = pkt->pts;
|
|
|
|
const int ret = avcodec_send_packet(codec_ctx_, pkt);
|
|
av_packet_free(&pkt);
|
|
if (ret < 0) return FailStatus("avcodec_send_packet failed");
|
|
|
|
DrainFrames();
|
|
return OkStatus();
|
|
}
|
|
|
|
Result<std::shared_ptr<Frame>> Receive() override {
|
|
if (!frames_.empty()) {
|
|
auto frame = frames_.front();
|
|
frames_.pop_front();
|
|
return frame;
|
|
}
|
|
if (!opened_) return MakeError<std::shared_ptr<Frame>>("decoder not opened");
|
|
DrainFrames();
|
|
if (!frames_.empty()) {
|
|
auto frame = frames_.front();
|
|
frames_.pop_front();
|
|
return frame;
|
|
}
|
|
return MakeError<std::shared_ptr<Frame>>("no_frame");
|
|
}
|
|
|
|
void Close() override {
|
|
frames_.clear();
|
|
if (codec_ctx_) {
|
|
avcodec_free_context(&codec_ctx_);
|
|
}
|
|
if (frame_) {
|
|
av_frame_free(&frame_);
|
|
}
|
|
opened_ = false;
|
|
}
|
|
|
|
private:
|
|
void DrainFrames() {
|
|
while (avcodec_receive_frame(codec_ctx_, frame_) == 0) {
|
|
auto out = MakeFrameFromAv(*frame_);
|
|
if (out) frames_.push_back(out);
|
|
av_frame_unref(frame_);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Frame> MakeFrameFromAv(const AVFrame& f) {
|
|
PixelFormat fmt = PixelFormat::UNKNOWN;
|
|
int plane_count = 0;
|
|
int width = f.width;
|
|
int height = f.height;
|
|
|
|
if (f.format == AV_PIX_FMT_NV12) {
|
|
fmt = PixelFormat::NV12;
|
|
plane_count = 2;
|
|
} else if (f.format == AV_PIX_FMT_YUV420P || f.format == AV_PIX_FMT_YUVJ420P) {
|
|
fmt = PixelFormat::YUV420;
|
|
plane_count = 3;
|
|
}
|
|
|
|
if (plane_count == 0) return nullptr;
|
|
|
|
int buf_size = av_image_get_buffer_size(static_cast<AVPixelFormat>(f.format), width, height, 1);
|
|
if (buf_size <= 0) return nullptr;
|
|
auto buffer = std::make_shared<std::vector<uint8_t>>(static_cast<size_t>(buf_size));
|
|
if (av_image_copy_to_buffer(buffer->data(), buffer->size(), f.data, f.linesize,
|
|
static_cast<AVPixelFormat>(f.format), width, height, 1) < 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto frame = std::make_shared<Frame>();
|
|
frame->width = width;
|
|
frame->height = height;
|
|
frame->format = fmt;
|
|
frame->stride = (fmt == PixelFormat::RGB || fmt == PixelFormat::BGR) ? width * 3 : width;
|
|
frame->data = buffer->data();
|
|
frame->data_size = buffer->size();
|
|
frame->plane_count = plane_count;
|
|
frame->SetOwner(buffer);
|
|
frame->pts = f.pts == AV_NOPTS_VALUE ? 0 : static_cast<uint64_t>(f.pts);
|
|
|
|
if (fmt == PixelFormat::NV12 && plane_count == 2) {
|
|
int y_size = width * height;
|
|
frame->planes[0] = {buffer->data(), width, y_size, 0};
|
|
frame->planes[1] = {buffer->data() + y_size, width, y_size / 2, y_size};
|
|
} else if (fmt == PixelFormat::YUV420 && plane_count == 3) {
|
|
int y_size = width * height;
|
|
int uv_stride = width / 2;
|
|
int uv_h = height / 2;
|
|
int u_size = uv_stride * uv_h;
|
|
frame->planes[0] = {buffer->data(), width, y_size, 0};
|
|
frame->planes[1] = {buffer->data() + y_size, uv_stride, u_size, y_size};
|
|
frame->planes[2] = {buffer->data() + y_size + u_size, uv_stride, u_size, y_size + u_size};
|
|
}
|
|
|
|
frame->SyncBufferFromFrame();
|
|
|
|
return frame;
|
|
}
|
|
|
|
AVCodecContext* codec_ctx_ = nullptr;
|
|
AVFrame* frame_ = nullptr;
|
|
std::deque<std::shared_ptr<Frame>> frames_;
|
|
bool opened_ = false;
|
|
};
|
|
|
|
class FfmpegEncoder : public IEncoder {
|
|
public:
|
|
Status Open(const SimpleJson& /*config*/) override { return FailStatus("ffmpeg encoder not implemented"); }
|
|
Status Send(const std::shared_ptr<Frame>& /*frame*/) override { return FailStatus("ffmpeg encoder not implemented"); }
|
|
Result<EncodePacket> Receive() override { return MakeError<EncodePacket>("ffmpeg encoder not implemented"); }
|
|
std::vector<uint8_t> ExtraData() const override { return {}; }
|
|
void Close() override {}
|
|
};
|
|
|
|
} // namespace
|
|
#endif
|
|
|
|
Status Rk3588Decoder::Open(const SimpleJson& config) {
|
|
Close();
|
|
std::string backend = ReadBackend(config);
|
|
if (backend == "mpp") {
|
|
#if defined(RK3588_ENABLE_MPP)
|
|
impl_ = std::make_unique<MppDecoder>();
|
|
#else
|
|
return FailStatus("mpp not enabled");
|
|
#endif
|
|
} else if (backend == "ffmpeg") {
|
|
#if defined(RK3588_ENABLE_FFMPEG)
|
|
impl_ = std::make_unique<FfmpegDecoder>();
|
|
#else
|
|
return FailStatus("ffmpeg not enabled");
|
|
#endif
|
|
} else {
|
|
return FailStatus("decoder backend not selected");
|
|
}
|
|
return impl_->Open(config);
|
|
}
|
|
|
|
Status Rk3588Decoder::Send(const DecodePacket& packet) {
|
|
if (!impl_) return FailStatus("decoder not initialized");
|
|
return impl_->Send(packet);
|
|
}
|
|
|
|
Result<std::shared_ptr<Frame>> Rk3588Decoder::Receive() {
|
|
if (!impl_) return MakeError<std::shared_ptr<Frame>>("decoder not initialized");
|
|
return impl_->Receive();
|
|
}
|
|
|
|
void Rk3588Decoder::Close() {
|
|
if (impl_) impl_->Close();
|
|
impl_.reset();
|
|
}
|
|
|
|
Status Rk3588Encoder::Open(const SimpleJson& config) {
|
|
Close();
|
|
std::string backend = ReadBackend(config);
|
|
if (backend == "mpp") {
|
|
#if defined(RK3588_ENABLE_MPP)
|
|
impl_ = std::make_unique<MppEncoder>();
|
|
#else
|
|
return FailStatus("mpp not enabled");
|
|
#endif
|
|
} else if (backend == "ffmpeg") {
|
|
#if defined(RK3588_ENABLE_FFMPEG)
|
|
impl_ = std::make_unique<FfmpegEncoder>();
|
|
#else
|
|
return FailStatus("ffmpeg not enabled");
|
|
#endif
|
|
} else {
|
|
return FailStatus("encoder backend not selected");
|
|
}
|
|
return impl_->Open(config);
|
|
}
|
|
|
|
Status Rk3588Encoder::Send(const std::shared_ptr<Frame>& frame) {
|
|
if (!impl_) return FailStatus("encoder not initialized");
|
|
return impl_->Send(frame);
|
|
}
|
|
|
|
Result<EncodePacket> Rk3588Encoder::Receive() {
|
|
if (!impl_) return MakeError<EncodePacket>("encoder not initialized");
|
|
return impl_->Receive();
|
|
}
|
|
|
|
std::vector<uint8_t> Rk3588Encoder::ExtraData() const {
|
|
if (!impl_) return {};
|
|
return impl_->ExtraData();
|
|
}
|
|
|
|
void Rk3588Encoder::Close() {
|
|
if (impl_) impl_->Close();
|
|
impl_.reset();
|
|
}
|
|
|
|
} // namespace rk3588
|