mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-22 10:40:05 +08:00
feat: VideoStack (#3373)
This commit is contained in:
parent
ff43fa5075
commit
437ae778c0
@ -532,3 +532,9 @@ endif ()
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/www" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf/config.ini" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/default.pem" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
|
||||
# 拷贝VideoStack 无视频流时默认填充的背景图片
|
||||
# Copy the default background image used by VideoStack when there is no video stream
|
||||
if (ENABLE_FFMPEG AND ENABLE_X264)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf/novideo.yuv" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
endif ()
|
1
conf/novideo.yuv
Normal file
1
conf/novideo.yuv
Normal file
File diff suppressed because one or more lines are too long
@ -1,11 +1,10 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "08c77fc3-7670-428c-bde4-80c8cc9f389f",
|
||||
"_postman_id": "8b3cdc62-3e18-4700-9ddd-dc9f58ebce83",
|
||||
"name": "ZLMediaKit",
|
||||
"description": "媒体服务器",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
"_exporter_id": "29185956",
|
||||
"_collection_link": "https://lively-station-598157.postman.co/workspace/%E6%B5%81%E5%AA%92%E4%BD%93%E6%9C%8D%E5%8A%A1~1e119172-45b0-4ed6-b1fc-8a15d0e2d5f8/collection/29185956-08c77fc3-7670-428c-bde4-80c8cc9f389f?action=share&source=collection_link&creator=29185956"
|
||||
"_exporter_id": "26338564"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
@ -34,6 +33,72 @@
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "关闭多屏拼接(stack/stop)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/getApiList?secret={{ZLMediaKit_secret}}&id=stack_test",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"getApiList"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "id",
|
||||
"value": "stack_test"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "添加多屏拼接(stack/start)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\r\n \"gapv\": 0.002,\r\n \"gaph\": 0.001,\r\n \"width\": 1920,\r\n \"url\": [\r\n [\r\n \"rtsp://kkem.me/live/test3\",\r\n \"rtsp://kkem.me/live/cy1\",\r\n \"rtsp://kkem.me/live/cy1\",\r\n \"rtsp://kkem.me/live/cy2\"\r\n ],\r\n [\r\n \"rtsp://kkem.me/live/cy1\",\r\n \"rtsp://kkem.me/live/cy5\",\r\n \"rtsp://kkem.me/live/cy3\",\r\n \"rtsp://kkem.me/live/cy4\"\r\n ],\r\n [\r\n \"rtsp://kkem.me/live/cy5\",\r\n \"rtsp://kkem.me/live/cy6\",\r\n \"rtsp://kkem.me/live/cy7\",\r\n \"rtsp://kkem.me/live/cy8\"\r\n ],\r\n [\r\n \"rtsp://kkem.me/live/cy9\",\r\n \"rtsp://kkem.me/live/cy10\",\r\n \"rtsp://kkem.me/live/cy11\",\r\n \"rtsp://kkem.me/live/cy12\"\r\n ]\r\n ],\r\n \"id\": \"89\",\r\n \"row\": 4,\r\n \"col\": 4,\r\n \"height\": 1080,\r\n \"span\": [\r\n [\r\n [\r\n 0,\r\n 0\r\n ],\r\n [\r\n 1,\r\n 1\r\n ]\r\n ],\r\n [\r\n [\r\n 3,\r\n 0\r\n ],\r\n [\r\n 3,\r\n 1\r\n ]\r\n ],\r\n [\r\n [\r\n 2,\r\n 3\r\n ],\r\n [\r\n 3,\r\n 3\r\n ]\r\n ]\r\n ]\r\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/stack/start?secret={{ZLMediaKit_secret}}",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"stack",
|
||||
"start"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "获取网络线程负载(getThreadsLoad)",
|
||||
"request": {
|
||||
|
590
server/VideoStack.cpp
Normal file
590
server/VideoStack.cpp
Normal file
@ -0,0 +1,590 @@
|
||||
#if defined(ENABLE_X264) && defined(ENABLE_FFMPEG)
|
||||
#include "VideoStack.h"
|
||||
#include "Codec/Transcode.h"
|
||||
#include "Common/Device.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/util.h"
|
||||
#include "json/value.h"
|
||||
#include <Thread/WorkThreadPool.h>
|
||||
#include <fstream>
|
||||
#include <libavutil/pixfmt.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
// ITU-R BT.601
|
||||
// #define RGB_TO_Y(R, G, B) ((( 66 * (R) + 129 * (G) + 25 * (B)+128) >> 8)+16)
|
||||
// #define RGB_TO_U(R, G, B) (((-38 * (R) - 74 * (G) + 112 * (B)+128) >> 8)+128)
|
||||
// #define RGB_TO_V(R, G, B) (((112 * (R) - 94 * (G) - 18 * (B)+128) >> 8)+128)
|
||||
|
||||
// ITU-R BT.709
|
||||
#define RGB_TO_Y(R, G, B) (((47 * (R) + 157 * (G) + 16 * (B) + 128) >> 8) + 16)
|
||||
#define RGB_TO_U(R, G, B) (((-26 * (R)-87 * (G) + 112 * (B) + 128) >> 8) + 128)
|
||||
#define RGB_TO_V(R, G, B) (((112 * (R)-102 * (G)-10 * (B) + 128) >> 8) + 128)
|
||||
|
||||
INSTANCE_IMP(VideoStackManager)
|
||||
|
||||
Param::~Param()
|
||||
{
|
||||
VideoStackManager::Instance().unrefChannel(
|
||||
id, width, height, pixfmt);
|
||||
}
|
||||
|
||||
Channel::Channel(const std::string& id, int width, int height, AVPixelFormat pixfmt)
|
||||
: _id(id)
|
||||
, _width(width)
|
||||
, _height(height)
|
||||
, _pixfmt(pixfmt)
|
||||
{
|
||||
_tmp = std::make_shared<mediakit::FFmpegFrame>();
|
||||
|
||||
_tmp->get()->width = _width;
|
||||
_tmp->get()->height = _height;
|
||||
_tmp->get()->format = _pixfmt;
|
||||
|
||||
av_frame_get_buffer(_tmp->get(), 32);
|
||||
|
||||
memset(_tmp->get()->data[0], 0, _tmp->get()->linesize[0] * _height);
|
||||
memset(_tmp->get()->data[1], 0, _tmp->get()->linesize[1] * _height / 2);
|
||||
memset(_tmp->get()->data[2], 0, _tmp->get()->linesize[2] * _height / 2);
|
||||
|
||||
auto frame = VideoStackManager::Instance().getBgImg();
|
||||
_sws = std::make_shared<mediakit::FFmpegSws>(_pixfmt, _width, _height);
|
||||
|
||||
_tmp = _sws->inputFrame(frame);
|
||||
}
|
||||
|
||||
void Channel::addParam(const std::weak_ptr<Param>& p)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
_params.push_back(p);
|
||||
}
|
||||
|
||||
void Channel::onFrame(const mediakit::FFmpegFrame::Ptr& frame)
|
||||
{
|
||||
std::weak_ptr<Channel> weakSelf = shared_from_this();
|
||||
// toolkit::WorkThreadPool::Instance().getFirstPoller()->async([weakSelf, frame]() {
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
self->_tmp = self->_sws->inputFrame(frame);
|
||||
|
||||
self->forEachParam([self](const Param::Ptr& p) { self->fillBuffer(p); });
|
||||
// });
|
||||
}
|
||||
|
||||
void Channel::forEachParam(const std::function<void(const Param::Ptr&)>& func)
|
||||
{
|
||||
for (auto& wp : _params) {
|
||||
if (auto sp = wp.lock()) {
|
||||
func(sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::fillBuffer(const Param::Ptr& p)
|
||||
{
|
||||
if (auto buf = p->weak_buf.lock()) {
|
||||
copyData(buf, p);
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::copyData(const mediakit::FFmpegFrame::Ptr& buf, const Param::Ptr& p)
|
||||
{
|
||||
|
||||
switch (p->pixfmt) {
|
||||
case AV_PIX_FMT_YUV420P: {
|
||||
for (int i = 0; i < p->height; i++) {
|
||||
memcpy(buf->get()->data[0] + buf->get()->linesize[0] * (i + p->posY) + p->posX,
|
||||
_tmp->get()->data[0] + _tmp->get()->linesize[0] * i,
|
||||
_tmp->get()->width);
|
||||
}
|
||||
//确保height为奇数时,也能正确的复制到最后一行uv数据
|
||||
for (int i = 0; i < (p->height + 1) / 2; i++) {
|
||||
// U平面
|
||||
memcpy(buf->get()->data[1] + buf->get()->linesize[1] * (i + p->posY / 2) + p->posX / 2,
|
||||
_tmp->get()->data[1] + _tmp->get()->linesize[1] * i,
|
||||
_tmp->get()->width / 2);
|
||||
|
||||
// V平面
|
||||
memcpy(buf->get()->data[2] + buf->get()->linesize[2] * (i + p->posY / 2) + p->posX / 2,
|
||||
_tmp->get()->data[2] + _tmp->get()->linesize[2] * i,
|
||||
_tmp->get()->width / 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_NV12: {
|
||||
//TODO: 待实现
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
WarnL << "No support pixformat: " << av_get_pix_fmt_name(p->pixfmt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
void StackPlayer::addChannel(const std::weak_ptr<Channel>& chn)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
_channels.push_back(chn);
|
||||
}
|
||||
|
||||
void StackPlayer::play()
|
||||
{
|
||||
|
||||
auto url = _url;
|
||||
//创建拉流 解码对象
|
||||
_player = std::make_shared<mediakit::MediaPlayer>();
|
||||
std::weak_ptr<mediakit::MediaPlayer> weakPlayer = _player;
|
||||
|
||||
std::weak_ptr<StackPlayer> weakSelf = shared_from_this();
|
||||
|
||||
(*_player)[mediakit::Client::kWaitTrackReady] = false;
|
||||
(*_player)[mediakit::Client::kRtpType] = mediakit::Rtsp::RTP_TCP;
|
||||
|
||||
_player->setOnPlayResult([weakPlayer, weakSelf, url](const toolkit::SockException& ex) mutable {
|
||||
TraceL << "StackPlayer: " << url << " OnPlayResult: " << ex.what();
|
||||
auto strongPlayer = weakPlayer.lock();
|
||||
if (!strongPlayer) {
|
||||
return;
|
||||
}
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ex) {
|
||||
// 取消定时器
|
||||
self->_timer.reset();
|
||||
self->_failedCount = 0;
|
||||
|
||||
} else {
|
||||
self->onDisconnect();
|
||||
self->rePlay(url);
|
||||
}
|
||||
|
||||
auto videoTrack = std::dynamic_pointer_cast<mediakit::VideoTrack>(strongPlayer->getTrack(mediakit::TrackVideo, false));
|
||||
//auto audioTrack = std::dynamic_pointer_cast<mediakit::AudioTrack>(strongPlayer->getTrack(mediakit::TrackAudio, false));
|
||||
|
||||
if (videoTrack) {
|
||||
//TODO:添加使用显卡还是cpu解码的判断逻辑
|
||||
//auto decoder = std::make_shared<FFmpegDecoder>(videoTrack, 1, std::vector<std::string>{ "hevc_cuvid", "h264_cuvid"});
|
||||
auto decoder = std::make_shared<mediakit::FFmpegDecoder>(videoTrack, 0, std::vector<std::string> { "h264", "hevc" });
|
||||
|
||||
decoder->setOnDecode([weakSelf](const mediakit::FFmpegFrame::Ptr& frame) mutable {
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->onFrame(frame);
|
||||
});
|
||||
|
||||
videoTrack->addDelegate((std::function<bool(const mediakit::Frame::Ptr&)>)[decoder](const mediakit::Frame::Ptr& frame) {
|
||||
return decoder->inputFrame(frame, false, true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_player->setOnShutdown([weakPlayer, url, weakSelf](const toolkit::SockException& ex) {
|
||||
TraceL << "StackPlayer: " << url << " OnShutdown: " << ex.what();
|
||||
auto strongPlayer = weakPlayer.lock();
|
||||
if (!strongPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->onDisconnect();
|
||||
|
||||
self->rePlay(url);
|
||||
});
|
||||
|
||||
_player->play(url);
|
||||
}
|
||||
|
||||
void StackPlayer::onFrame(const mediakit::FFmpegFrame::Ptr& frame)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
for (auto& weak_chn : _channels) {
|
||||
if (auto chn = weak_chn.lock()) {
|
||||
chn->onFrame(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StackPlayer::onDisconnect()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
for (auto& weak_chn : _channels) {
|
||||
if (auto chn = weak_chn.lock()) {
|
||||
auto frame = VideoStackManager::Instance().getBgImg();
|
||||
chn->onFrame(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StackPlayer::rePlay(const std::string& url)
|
||||
{
|
||||
_failedCount++;
|
||||
auto delay = MAX(2 * 1000, MIN(_failedCount * 3 * 1000, 60 * 1000)); //步进延迟 重试间隔
|
||||
std::weak_ptr<StackPlayer> weakSelf = shared_from_this();
|
||||
_timer = std::make_shared<toolkit::Timer>(
|
||||
delay / 1000.0f, [weakSelf, url]() {
|
||||
auto self = weakSelf.lock();
|
||||
if (!self) {
|
||||
}
|
||||
WarnL << "replay [" << self->_failedCount << "]:" << url;
|
||||
self->_player->play(url);
|
||||
return false;
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
|
||||
VideoStack::VideoStack(const std::string& id, int width, int height, AVPixelFormat pixfmt, float fps, int bitRate)
|
||||
: _id(id)
|
||||
, _width(width)
|
||||
, _height(height)
|
||||
, _pixfmt(pixfmt)
|
||||
, _fps(fps)
|
||||
, _bitRate(bitRate)
|
||||
{
|
||||
|
||||
_buffer = std::make_shared<mediakit::FFmpegFrame>();
|
||||
|
||||
_buffer->get()->width = _width;
|
||||
_buffer->get()->height = _height;
|
||||
_buffer->get()->format = _pixfmt;
|
||||
|
||||
av_frame_get_buffer(_buffer->get(), 32);
|
||||
|
||||
_dev = std::make_shared<mediakit::DevChannel>(mediakit::MediaTuple { DEFAULT_VHOST, "live", _id });
|
||||
|
||||
mediakit::VideoInfo info;
|
||||
info.codecId = mediakit::CodecH264;
|
||||
info.iWidth = _width;
|
||||
info.iHeight = _height;
|
||||
info.iFrameRate = _fps;
|
||||
info.iBitRate = _bitRate;
|
||||
|
||||
_dev->initVideo(info);
|
||||
//dev->initAudio(); //TODO:音频
|
||||
_dev->addTrackCompleted();
|
||||
|
||||
_isExit = false;
|
||||
}
|
||||
|
||||
VideoStack::~VideoStack()
|
||||
{
|
||||
_isExit = true;
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoStack::setParam(const Params& params)
|
||||
{
|
||||
if (_params) {
|
||||
for (auto& p : (*_params)) {
|
||||
if (!p)
|
||||
continue;
|
||||
p->weak_buf.reset();
|
||||
}
|
||||
}
|
||||
|
||||
initBgColor();
|
||||
for (auto& p : (*params)) {
|
||||
if (!p)
|
||||
continue;
|
||||
p->weak_buf = _buffer;
|
||||
if (auto chn = p->weak_chn.lock()) {
|
||||
chn->addParam(p);
|
||||
chn->fillBuffer(p);
|
||||
}
|
||||
}
|
||||
_params = params;
|
||||
}
|
||||
|
||||
void VideoStack::start()
|
||||
{
|
||||
_thread = std::thread([&]() {
|
||||
uint64_t pts = 0;
|
||||
int frameInterval = 1000 / _fps;
|
||||
auto lastEncTP = std::chrono::steady_clock::now();
|
||||
while (!_isExit) {
|
||||
if (std::chrono::steady_clock::now() - lastEncTP > std::chrono::milliseconds(frameInterval)) {
|
||||
lastEncTP = std::chrono::steady_clock::now();
|
||||
|
||||
_dev->inputYUV((char**)_buffer->get()->data, _buffer->get()->linesize, pts);
|
||||
pts += frameInterval;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void VideoStack::initBgColor()
|
||||
{
|
||||
//填充底色
|
||||
auto R = 20;
|
||||
auto G = 20;
|
||||
auto B = 20;
|
||||
|
||||
double Y = RGB_TO_Y(R, G, B);
|
||||
double U = RGB_TO_U(R, G, B);
|
||||
double V = RGB_TO_V(R, G, B);
|
||||
|
||||
memset(_buffer->get()->data[0], Y, _buffer->get()->linesize[0] * _height);
|
||||
memset(_buffer->get()->data[1], U, _buffer->get()->linesize[1] * _height / 2);
|
||||
memset(_buffer->get()->data[2], V, _buffer->get()->linesize[2] * _height / 2);
|
||||
}
|
||||
|
||||
Channel::Ptr VideoStackManager::getChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt)
|
||||
{
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto key = id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt);
|
||||
auto it = _channelMap.find(key);
|
||||
if (it != _channelMap.end()) {
|
||||
return it->second->acquire();
|
||||
}
|
||||
|
||||
return createChannel(id, width, height, pixfmt);
|
||||
}
|
||||
|
||||
void VideoStackManager::unrefChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt)
|
||||
{
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto key = id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt);
|
||||
auto chn_it = _channelMap.find(key);
|
||||
if (chn_it != _channelMap.end() && chn_it->second->dispose()) {
|
||||
_channelMap.erase(chn_it);
|
||||
|
||||
auto player_it = _playerMap.find(id);
|
||||
if (player_it != _playerMap.end() && player_it->second->dispose()) {
|
||||
_playerMap.erase(player_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int VideoStackManager::startVideoStack(const Json::Value& json)
|
||||
{
|
||||
|
||||
std::string id;
|
||||
int width, height;
|
||||
auto params = parseParams(json, id, width, height);
|
||||
|
||||
if (!params) {
|
||||
ErrorL << "Videostack parse params failed!";
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto stack = std::make_shared<VideoStack>(id, width, height);
|
||||
|
||||
for (auto& p : (*params)) {
|
||||
if (!p)
|
||||
continue;
|
||||
p->weak_chn = getChannel(p->id, p->width, p->height, p->pixfmt);
|
||||
}
|
||||
|
||||
stack->setParam(params);
|
||||
stack->start();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
_stackMap[id] = stack;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int VideoStackManager::resetVideoStack(const Json::Value& json)
|
||||
{
|
||||
std::string id;
|
||||
int width, height;
|
||||
auto params = parseParams(json, id, width, height);
|
||||
|
||||
if (!params) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
VideoStack::Ptr stack;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto it = _stackMap.find(id);
|
||||
if (it == _stackMap.end()) {
|
||||
return -2;
|
||||
}
|
||||
stack = it->second;
|
||||
}
|
||||
|
||||
for (auto& p : (*params)) {
|
||||
if (!p)
|
||||
continue;
|
||||
p->weak_chn = getChannel(p->id, p->width, p->height, p->pixfmt);
|
||||
}
|
||||
|
||||
stack->setParam(params);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int VideoStackManager::stopVideoStack(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto it = _stackMap.find(id);
|
||||
if (it != _stackMap.end()) {
|
||||
_stackMap.erase(it);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
mediakit::FFmpegFrame::Ptr VideoStackManager::getBgImg()
|
||||
{
|
||||
return _bgImg;
|
||||
}
|
||||
|
||||
Params VideoStackManager::parseParams(const Json::Value& json,
|
||||
std::string& id,
|
||||
int& width,
|
||||
int& height)
|
||||
{
|
||||
try {
|
||||
id = json["id"].asString();
|
||||
|
||||
width = json["width"].asInt();
|
||||
height = json["height"].asInt();
|
||||
|
||||
int rows = json["row"].asInt(); //堆叠行数
|
||||
int cols = json["col"].asInt(); //堆叠列数
|
||||
float gapv = json["gapv"].asFloat(); //垂直间距
|
||||
float gaph = json["gaph"].asFloat(); //水平间距
|
||||
|
||||
//单个间距
|
||||
int gaphPix = static_cast<int>(std::round(width * gaph));
|
||||
int gapvPix = static_cast<int>(std::round(height * gapv));
|
||||
|
||||
// 根据间距计算格子宽高
|
||||
int gridWidth = cols > 1 ? (width - gaphPix * (cols - 1)) / cols : width;
|
||||
int gridHeight = rows > 1 ? (height - gapvPix * (rows - 1)) / rows : height;
|
||||
|
||||
auto params = std::make_shared<std::vector<Param::Ptr>>(rows * cols);
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int col = 0; col < cols; col++) {
|
||||
std::string url = json["url"][row][col].asString();
|
||||
|
||||
auto param = std::make_shared<Param>();
|
||||
param->posX = gridWidth * col + col * gaphPix;
|
||||
param->posY = gridHeight * row + row * gapvPix;
|
||||
param->width = gridWidth;
|
||||
param->height = gridHeight;
|
||||
param->id = url;
|
||||
|
||||
(*params)[row * cols + col] = param;
|
||||
}
|
||||
}
|
||||
|
||||
//判断是否需要合并格子 (焦点屏)
|
||||
if (!json["span"].empty() && json.isMember("span")) {
|
||||
for (const auto& subArray : json["span"]) {
|
||||
if (!subArray.isArray() || subArray.size() != 2) {
|
||||
throw Json::LogicError("Incorrect 'span' sub-array format in JSON");
|
||||
}
|
||||
std::array<int, 4> mergePos;
|
||||
int index = 0;
|
||||
|
||||
for (const auto& innerArray : subArray) {
|
||||
if (!innerArray.isArray() || innerArray.size() != 2) {
|
||||
throw Json::LogicError("Incorrect 'span' inner-array format in JSON");
|
||||
}
|
||||
for (const auto& number : innerArray) {
|
||||
if (index < mergePos.size()) {
|
||||
mergePos[index++] = number.asInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = mergePos[0]; i <= mergePos[2]; i++) {
|
||||
for (int j = mergePos[1]; j <= mergePos[3]; j++) {
|
||||
if (i == mergePos[0] && j == mergePos[1]) {
|
||||
(*params)[i * cols + j]->width = (mergePos[3] - mergePos[1] + 1) * gridWidth + (mergePos[3] - mergePos[1]) * gapvPix;
|
||||
(*params)[i * cols + j]->height = (mergePos[2] - mergePos[0] + 1) * gridHeight + (mergePos[2] - mergePos[0]) * gaphPix;
|
||||
} else {
|
||||
(*params)[i * cols + j] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
} catch (const std::exception& e) {
|
||||
ErrorL << "Videostack parse params failed! " << e.what();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoStackManager::loadBgImg(const std::string& path)
|
||||
{
|
||||
_bgImg = std::make_shared<mediakit::FFmpegFrame>();
|
||||
|
||||
_bgImg->get()->width = 1280;
|
||||
_bgImg->get()->height = 720;
|
||||
_bgImg->get()->format = AV_PIX_FMT_YUV420P;
|
||||
|
||||
av_frame_get_buffer(_bgImg->get(), 32);
|
||||
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file.read((char*)_bgImg->get()->data[0], _bgImg->get()->linesize[0] * _bgImg->get()->height); // Y
|
||||
file.read((char*)_bgImg->get()->data[1], _bgImg->get()->linesize[1] * _bgImg->get()->height / 2); // U
|
||||
file.read((char*)_bgImg->get()->data[2], _bgImg->get()->linesize[2] * _bgImg->get()->height / 2); // V
|
||||
return true;
|
||||
}
|
||||
|
||||
Channel::Ptr VideoStackManager::createChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt)
|
||||
{
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
StackPlayer::Ptr player;
|
||||
auto it = _playerMap.find(id);
|
||||
if (it != _playerMap.end()) {
|
||||
player = it->second->acquire();
|
||||
} else {
|
||||
player = createPlayer(id);
|
||||
}
|
||||
|
||||
auto refChn = std::make_shared<RefWrapper<Channel::Ptr>>(std::make_shared<Channel>(id, width, height, pixfmt));
|
||||
auto chn = refChn->acquire();
|
||||
player->addChannel(chn);
|
||||
|
||||
_channelMap[id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt)] = refChn;
|
||||
return chn;
|
||||
}
|
||||
|
||||
StackPlayer::Ptr VideoStackManager::createPlayer(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(_mx);
|
||||
auto refPlayer = std::make_shared<RefWrapper<StackPlayer::Ptr>>(std::make_shared<StackPlayer>(id));
|
||||
_playerMap[id] = refPlayer;
|
||||
|
||||
auto player = refPlayer->acquire();
|
||||
if (!id.empty()) {
|
||||
player->play();
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
#endif
|
207
server/VideoStack.h
Normal file
207
server/VideoStack.h
Normal file
@ -0,0 +1,207 @@
|
||||
#pragma once
|
||||
#if defined(ENABLE_X264) && defined(ENABLE_FFMPEG)
|
||||
#include "Codec/Transcode.h"
|
||||
#include "Common/Device.h"
|
||||
#include "Player/MediaPlayer.h"
|
||||
#include "json/json.h"
|
||||
#include <mutex>
|
||||
template <typename T>
|
||||
class RefWrapper {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<RefWrapper<T>>;
|
||||
|
||||
template <typename... Args>
|
||||
explicit RefWrapper(Args&&... args)
|
||||
: _rc(0)
|
||||
, _entity(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
T acquire()
|
||||
{
|
||||
++_rc;
|
||||
return _entity;
|
||||
}
|
||||
|
||||
bool dispose() { return --_rc <= 0; }
|
||||
|
||||
private:
|
||||
T _entity;
|
||||
std::atomic<int> _rc;
|
||||
};
|
||||
|
||||
class Channel;
|
||||
|
||||
struct Param {
|
||||
using Ptr = std::shared_ptr<Param>;
|
||||
|
||||
int posX = 0;
|
||||
int posY = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
AVPixelFormat pixfmt = AV_PIX_FMT_YUV420P;
|
||||
std::string id {};
|
||||
|
||||
// runtime
|
||||
std::weak_ptr<Channel> weak_chn;
|
||||
std::weak_ptr<mediakit::FFmpegFrame> weak_buf;
|
||||
|
||||
~Param();
|
||||
};
|
||||
|
||||
using Params = std::shared_ptr<std::vector<Param::Ptr>>;
|
||||
|
||||
class Channel : public std::enable_shared_from_this<Channel> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<Channel>;
|
||||
|
||||
Channel(const std::string& id, int width, int height, AVPixelFormat pixfmt);
|
||||
|
||||
void addParam(const std::weak_ptr<Param>& p);
|
||||
|
||||
void onFrame(const mediakit::FFmpegFrame::Ptr& frame);
|
||||
|
||||
void fillBuffer(const Param::Ptr& p);
|
||||
|
||||
protected:
|
||||
void forEachParam(const std::function<void(const Param::Ptr&)>& func);
|
||||
|
||||
void copyData(const mediakit::FFmpegFrame::Ptr& buf, const Param::Ptr& p);
|
||||
|
||||
private:
|
||||
std::string _id;
|
||||
int _width;
|
||||
int _height;
|
||||
AVPixelFormat _pixfmt;
|
||||
|
||||
mediakit::FFmpegFrame::Ptr _tmp;
|
||||
|
||||
std::recursive_mutex _mx;
|
||||
std::vector<std::weak_ptr<Param>> _params;
|
||||
|
||||
mediakit::FFmpegSws::Ptr _sws;
|
||||
};
|
||||
|
||||
class StackPlayer : public std::enable_shared_from_this<StackPlayer> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<StackPlayer>;
|
||||
|
||||
StackPlayer(const std::string& url)
|
||||
: _url(url)
|
||||
{
|
||||
}
|
||||
|
||||
void addChannel(const std::weak_ptr<Channel>& chn);
|
||||
|
||||
void play();
|
||||
|
||||
void onFrame(const mediakit::FFmpegFrame::Ptr& frame);
|
||||
|
||||
void onDisconnect();
|
||||
|
||||
protected:
|
||||
void rePlay(const std::string& url);
|
||||
|
||||
private:
|
||||
std::string _url;
|
||||
mediakit::MediaPlayer::Ptr _player;
|
||||
|
||||
//用于断线重连
|
||||
toolkit::Timer::Ptr _timer;
|
||||
int _failedCount = 0;
|
||||
|
||||
std::recursive_mutex _mx;
|
||||
std::vector<std::weak_ptr<Channel>> _channels;
|
||||
};
|
||||
|
||||
class VideoStack {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<VideoStack>;
|
||||
|
||||
VideoStack(const std::string& url,
|
||||
int width = 1920,
|
||||
int height = 1080,
|
||||
AVPixelFormat pixfmt = AV_PIX_FMT_YUV420P,
|
||||
float fps = 25.0,
|
||||
int bitRate = 2 * 1024 * 1024);
|
||||
|
||||
~VideoStack();
|
||||
|
||||
void setParam(const Params& params);
|
||||
|
||||
void start();
|
||||
|
||||
protected:
|
||||
void initBgColor();
|
||||
|
||||
public:
|
||||
Params _params;
|
||||
|
||||
mediakit::FFmpegFrame::Ptr _buffer;
|
||||
|
||||
private:
|
||||
std::string _id;
|
||||
int _width;
|
||||
int _height;
|
||||
AVPixelFormat _pixfmt;
|
||||
float _fps;
|
||||
int _bitRate;
|
||||
|
||||
mediakit::DevChannel::Ptr _dev;
|
||||
|
||||
bool _isExit;
|
||||
|
||||
std::thread _thread;
|
||||
};
|
||||
|
||||
class VideoStackManager {
|
||||
public:
|
||||
static VideoStackManager& Instance();
|
||||
|
||||
Channel::Ptr getChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt);
|
||||
|
||||
void unrefChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt);
|
||||
|
||||
int startVideoStack(const Json::Value& json);
|
||||
|
||||
int resetVideoStack(const Json::Value& json);
|
||||
|
||||
int stopVideoStack(const std::string& id);
|
||||
|
||||
bool loadBgImg(const std::string& path);
|
||||
|
||||
mediakit::FFmpegFrame::Ptr getBgImg();
|
||||
|
||||
protected:
|
||||
Params parseParams(const Json::Value& json,
|
||||
std::string& id,
|
||||
int& width,
|
||||
int& height);
|
||||
|
||||
protected:
|
||||
Channel::Ptr createChannel(const std::string& id,
|
||||
int width,
|
||||
int height,
|
||||
AVPixelFormat pixfmt);
|
||||
|
||||
StackPlayer::Ptr createPlayer(const std::string& id);
|
||||
|
||||
private:
|
||||
mediakit::FFmpegFrame::Ptr _bgImg;
|
||||
|
||||
private:
|
||||
std::recursive_mutex _mx;
|
||||
|
||||
std::unordered_map<std::string, VideoStack::Ptr> _stackMap;
|
||||
|
||||
std::unordered_map<std::string, RefWrapper<Channel::Ptr>::Ptr> _channelMap;
|
||||
|
||||
std::unordered_map<std::string, RefWrapper<StackPlayer::Ptr>::Ptr> _playerMap;
|
||||
};
|
||||
#endif
|
@ -62,6 +62,10 @@
|
||||
#include "version.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_X264) && defined (ENABLE_FFMPEG)
|
||||
#include "VideoStack.h"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace Json;
|
||||
using namespace toolkit;
|
||||
@ -1925,6 +1929,36 @@ void installWebApi() {
|
||||
invoker(401, StrCaseMap {}, "None http access event listener");
|
||||
}
|
||||
});
|
||||
|
||||
#if defined(ENABLE_X264) && defined(ENABLE_FFMPEG)
|
||||
VideoStackManager::Instance().loadBgImg("novideo.yuv");
|
||||
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastStreamNoneReader, [](BroadcastStreamNoneReaderArgs) {
|
||||
auto id = sender.getMediaTuple().stream;
|
||||
VideoStackManager::Instance().stopVideoStack(id);
|
||||
InfoL << "VideoStack: " << id <<" stop";
|
||||
});
|
||||
|
||||
api_regist("/index/api/stack/start", [](API_ARGS_JSON_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
auto ret = VideoStackManager::Instance().startVideoStack(allArgs.getArgs());
|
||||
if (!ret) {
|
||||
invoker(200, headerOut, "success");
|
||||
} else {
|
||||
invoker(200, headerOut, "failed");
|
||||
}
|
||||
});
|
||||
|
||||
api_regist("/index/api/stack/stop", [](API_ARGS_MAP_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("id");
|
||||
auto ret = VideoStackManager::Instance().stopVideoStack(allArgs["id"]);
|
||||
if (!ret) {
|
||||
invoker(200, headerOut, "success");
|
||||
} else {
|
||||
invoker(200, headerOut, "failed");
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void unInstallWebApi(){
|
||||
|
Loading…
Reference in New Issue
Block a user