Skip to content

Commit 7c11d96

Browse files
authored
Merge pull request #3 from RolinShmily/feat/recording-pipeline-zero-copy
重构录制管线:画质修复 + GPU零拷贝硬编 + MSAA支持
2 parents 5626ac4 + 1bcb97b commit 7c11d96

15 files changed

Lines changed: 858 additions & 382 deletions

File tree

source/MulNXExtensions/MediaSystem/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_library(MediaSystem
88
VEncodeHelper/VEncodeHelper.cpp
99
VCD3D11Manager/VCD3D11Manager.cpp
1010
MediaParamManager/MediaParamManager.cpp
11+
MediaParamManager/RecordParams.cpp
1112
)
1213

1314
target_link_libraries(MediaSystem PUBLIC
Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
#include "MediaParamManager.hpp"
22

33
bool MediaParamManager::Init() {
4+
this->caps = DetectEncoderCaps();
5+
6+
this->LogSucc(std::format("编码器能力探测完成:硬件={} 软件={} D3D11VA={}",
7+
this->caps.hwEncoders.size(),
8+
this->caps.swEncoders.size(),
9+
this->caps.d3d11vaAvailable ? "" : ""));
10+
11+
if (!this->caps.hwEncoders.empty()) {
12+
std::string joined;
13+
for (const auto& n : this->caps.hwEncoders) joined += n + " ";
14+
this->LogInfo(std::format("可用硬件编码器: {}", joined));
15+
} else {
16+
this->LogWarning("未检测到硬件编码器,将使用软件编码");
17+
}
18+
if (!this->caps.swEncoders.empty()) {
19+
std::string joined;
20+
for (const auto& n : this->caps.swEncoders) joined += n + " ";
21+
this->LogInfo(std::format("可用软件编码器: {}", joined));
22+
}
423

524
return true;
6-
}
25+
}
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
#pragma once
22
#include <MulNXExtensions/MediaSystem/MediaModuleBase.hpp>
3+
#include <MulNXExtensions/MediaSystem/MediaParamManager/RecordParams.hpp>
34

45
class MediaParamManager :public MediaModuleBase{
6+
EncoderCaps caps;
7+
RecordParams params;
58
public:
69
bool Init()override;
7-
};
10+
11+
// 启动时探测到的编码器能力(只读)
12+
const EncoderCaps& Caps() const { return this->caps; }
13+
14+
// 当前生效的录制参数(可被 UI/消息修改)
15+
RecordParams& Params() { return this->params; }
16+
const RecordParams& Params() const { return this->params; }
17+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include "RecordParams.hpp"
2+
extern "C" {
3+
#include <libavcodec/codec.h>
4+
#include <libavutil/hwcontext.h>
5+
}
6+
7+
EncoderCaps DetectEncoderCaps() {
8+
EncoderCaps caps;
9+
const AVCodec* codec = nullptr;
10+
void* iter = nullptr;
11+
while ((codec = av_codec_iterate(&iter)) != nullptr) {
12+
if (!av_codec_is_encoder(codec) || codec->type != AVMEDIA_TYPE_VIDEO) continue;
13+
std::string name(codec->name ? codec->name : "");
14+
if (name == "h264_nvenc" || name == "hevc_nvenc" ||
15+
name == "h264_amf" || name == "hevc_amf" ||
16+
name == "h264_qsv" || name == "hevc_qsv")
17+
caps.hwEncoders.push_back(name);
18+
else
19+
caps.swEncoders.push_back(name);
20+
}
21+
AVHWDeviceType t = AV_HWDEVICE_TYPE_NONE;
22+
while ((t = av_hwdevice_iterate_types(t)) != AV_HWDEVICE_TYPE_NONE) {
23+
if (t == AV_HWDEVICE_TYPE_D3D11VA) { caps.d3d11vaAvailable = true; break; }
24+
}
25+
return caps;
26+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#pragma once
2+
#include <string>
3+
#include <vector>
4+
#include <cstdint>
5+
6+
enum class EncodeMode { Auto, H264, HEVC };
7+
enum class RateControl { CBR, VBR, CQ };
8+
9+
struct RecordParams {
10+
// ── 编码器 ──
11+
EncodeMode mode = EncodeMode::Auto;
12+
RateControl rc = RateControl::VBR;
13+
int bitrate = 20'000'000; // bps, CBR/VBR 时有效
14+
int cq = 23; // CQ 质量 0-51, 越小越好
15+
int maxBFrames = 0; // 0=无 B 帧
16+
int gopSize = 0; // 0=自动 = fps×2
17+
std::string preset = "p4"; // 编码器预设(nvenc: p1-p7, x264: ultrafast~placebo)
18+
std::string profile = "high"; // high / main / baseline
19+
20+
// ── 捕获 ──
21+
int width = 0; // 0=原生
22+
int height = 0; // 0=原生
23+
int captureFpsCap = 60; // 0=不限制
24+
int ringSlots = 6;
25+
};
26+
27+
struct EncoderCaps {
28+
std::vector<std::string> hwEncoders;
29+
std::vector<std::string> swEncoders;
30+
bool d3d11vaAvailable = false;
31+
};
32+
33+
EncoderCaps DetectEncoderCaps();

source/MulNXExtensions/MediaSystem/MediaRecorder/MediaRecorder.cpp

Lines changed: 83 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,85 +4,90 @@
44
#include <MulNXExtensions/MediaSystem/AudioCapturer/AudioCapturer.hpp>
55
#include <MulNXExtensions/MediaSystem/AEncodeHelper/AEncodeHelper.hpp>
66
#include <MulNXExtensions/MediaSystem/VEncodeHelper/VEncodeHelper.hpp>
7+
#include <MulNXExtensions/MediaSystem/VCD3D11Manager/VCD3D11Manager.hpp>
8+
#include <MulNXExtensions/MediaSystem/MediaParamManager/MediaParamManager.hpp>
79

810
void MediaRecorder::CaptureCallback(MulNX::UINode* node) {
9-
ImGui::GetBackgroundDrawList()->AddCallback([](const ImDrawList* parent_list, const ImDrawCmd* cmd) {
11+
ImGui::GetBackgroundDrawList()->AddCallback([](const ImDrawList*, const ImDrawCmd* cmd) {
1012
static_cast<MediaRecorder*>(cmd->UserCallbackData)->PublishSync("Hook/BeforePresent"_hash);
11-
}, this, 0);
13+
}, this, 0);
1214
}
1315

1416
bool MediaRecorder::Init() {
15-
this->pVideoCapturer = this->Core->ModuleManager()->FindModule<VideoCapturer>("VideoCapturer");
16-
this->pAudioCapturer = this->Core->ModuleManager()->FindModule<AudioCapturer>("AudioCapturer");
17-
this->pVEncodeHelper = this->Core->ModuleManager()->FindModule<VEncodeHelper>("VEncodeHelper");
18-
this->pAEncodeHelper = this->Core->ModuleManager()->FindModule<AEncodeHelper>("AEncodeHelper");
17+
this->pVideoCapturer = this->Core->ModuleManager()->FindModule<VideoCapturer>("VideoCapturer");
18+
this->pAudioCapturer = this->Core->ModuleManager()->FindModule<AudioCapturer>("AudioCapturer");
19+
this->pVEncodeHelper = this->Core->ModuleManager()->FindModule<VEncodeHelper>("VEncodeHelper");
20+
this->pAEncodeHelper = this->Core->ModuleManager()->FindModule<AEncodeHelper>("AEncodeHelper");
21+
this->pVCD3D11Manager = this->Core->ModuleManager()->FindModule<VCD3D11Manager>("VCD3D11Manager");
22+
this->pMediaParamManager= this->Core->ModuleManager()->FindModule<MediaParamManager>("MediaParamManager");
1923

2024
this->dirVedios = this->Path()->PathGetForShared("Vedios");
25+
(*this).SubscribeAsync("Media/Record/Start").SubscribeAsync("Media/Record/Stop");
2126

22-
(*this)
23-
.SubscribeAsync("Media/Record/Start")
24-
.SubscribeAsync("Media/Record/Stop");
25-
26-
this->SendTask("Main", "Media", [this]() {
27-
this->Main();
28-
return true;
29-
});
30-
31-
this->SendUIRoot("捕获以上所有根触发的UI渲染", [this](MulNX::UINode* node) {return this->CaptureCallback(node);});
32-
27+
this->SendTask("Main", "Media", [this]() { this->Main(); return true; });
28+
this->SendUIRoot("捕获以上所有根触发的UI渲染",
29+
[this](MulNX::UINode* node) { this->CaptureCallback(node); return true; });
3330
return true;
3431
}
3532

3633
void MediaRecorder::ProcessMsg(MulNX::Message& msg) {
3734
switch (msg.type) {
38-
case "Media/Record/Start"_hash: {
39-
auto path = msg.asp.get<MulNX::NetExt>()->str1;
40-
auto outFile = path + ".mp4";
41-
this->StartRecording(outFile, 1920, 1080);
35+
case "Media/Record/Start"_hash:
36+
this->StartRecording(msg.asp.get<MulNX::NetExt>()->str1);
4237
break;
43-
}
44-
case "Media/Record/Stop"_hash: {
38+
case "Media/Record/Stop"_hash:
4539
this->StopRecording();
4640
break;
4741
}
48-
}
4942
}
5043

51-
bool MediaRecorder::StartRecording(const std::string& filename, int w, int h) {
52-
if (this->runFlag1) {
53-
this->LogWarning("已在录制中,StartRecording 被忽略");
54-
return false;
44+
bool MediaRecorder::StartRecording(const std::string& pathNoExt) {
45+
if (this->runFlag1) { this->LogWarning("已在录制中"); return false; }
46+
if (!this->pVCD3D11Manager || !this->pVideoCapturer || !this->pVEncodeHelper) {
47+
this->LogError("模块缺失"); return false;
5548
}
5649

57-
if (!this->pAudioCapturer || !this->pVideoCapturer || !this->pAEncodeHelper || !this->pVEncodeHelper) {
58-
this->LogError("录制启动失败:缺少音视频模块");
59-
return false;
50+
RecordParams rp;
51+
if (this->pMediaParamManager) rp = this->pMediaParamManager->Params();
52+
53+
std::string outFile = pathNoExt + ".mp4";
54+
int srcW = this->pVCD3D11Manager->srcWidth;
55+
int srcH = this->pVCD3D11Manager->srcHeight;
56+
av::PixelFormat srcFmt = DXGIFormatToAvPixelFormat(this->pVCD3D11Manager->srcDxgiFormat);
57+
if (srcW <= 0 || srcH <= 0 || srcFmt == AV_PIX_FMT_NONE) {
58+
this->LogError("源纹理参数无效"); return false;
6059
}
6160

62-
// 清理旧缓存,保持录制起点对齐
61+
this->pVCD3D11Manager->SetCaptureFpsCap(rp.captureFpsCap);
6362
this->pAudioCapturer->ClearBuffer();
6463
this->pVideoCapturer->ClearBuffer();
6564
this->pVideoCapturer->Reset();
6665
this->pAEncodeHelper->Reset();
66+
this->pVEncodeHelper->Reset();
6767

6868
try {
69-
this->ofctx.openOutput(filename);
69+
this->ofctx.openOutput(outFile);
7070
this->recordStartTime = std::chrono::steady_clock::now();
71-
this->pVideoCapturer->StartCapture(this->recordStartTime);
71+
this->pVCD3D11Manager->SetRecordStart(this->recordStartTime);
7272

73-
this->pVEncodeHelper->SetOn(&this->ofctx, w, h, this->timeBase);
73+
this->pVEncodeHelper->SetOn(&this->ofctx, rp, srcW, srcH, srcFmt,
74+
this->pVCD3D11Manager->pDevice.Get(),
75+
rp.captureFpsCap > 0 ? rp.captureFpsCap : 0);
7476
this->pAEncodeHelper->SetOn(&this->ofctx, this->pAudioCapturer->GetSampleRate());
7577

76-
// 写文件头(即使只有视频流)
7778
this->ofctx.writeHeader();
78-
this->LogWarning(std::format("输出文件头已写入,流数量={}", this->ofctx.streamsCount()));
79-
79+
this->LogInfo(std::format("输出头已写入, 流数={}", this->ofctx.streamsCount()));
80+
81+
bool hwPath = this->pVEncodeHelper->IsHwAccel();
82+
this->pVideoCapturer->StartCapture(this->recordStartTime, hwPath);
83+
this->LogSucc(std::format("开始录制: {} ({}x{} {}{})", outFile,
84+
rp.width > 0 ? rp.width : srcW, rp.height > 0 ? rp.height : srcH,
85+
rp.captureFpsCap > 0 ? std::to_string(rp.captureFpsCap)+"fps " : "",
86+
hwPath ? "零拷贝" : "CPU读回"));
8087
this->runFlag1 = true;
81-
this->LogSucc("已开始录制: " + filename);
8288
return true;
83-
}
84-
catch (const std::exception& e) {
85-
this->LogError(std::string("录制启动失败: ") + e.what());
89+
} catch (const std::exception& e) {
90+
this->LogError(std::format("启动失败: {}", e.what()));
8691
this->ofctx.close();
8792
return false;
8893
}
@@ -91,95 +96,69 @@ bool MediaRecorder::StartRecording(const std::string& filename, int w, int h) {
9196
void MediaRecorder::Main() {
9297
this->Update();
9398
if (!this->runFlag1) return;
94-
9599
this->Encode();
96100
}
97101

98-
static int64_t TimestampInMicroseconds(const av::Timestamp &ts) {
99-
return ts.isValid() ? ts.timestamp({1, 1000000}) : std::numeric_limits<int64_t>::max();
102+
static int64_t PtsUs(const av::Timestamp& ts) {
103+
return ts.isValid() ? ts.timestamp({1,1000000}) : INT64_MAX;
100104
}
101105

102106
void MediaRecorder::Encode() {
103107
std::vector<av::Packet> packets;
104108

105-
// 视频编码
106-
while (auto opFrame = this->pVideoCapturer->TryPop()) {
107-
this->pVEncodeHelper->CheckRescaler(
108-
this->pVideoCapturer->stagingWidth, this->pVideoCapturer->stagingHeight,
109-
this->pVideoCapturer->srcPixelFormat);
109+
while (auto f = this->pVideoCapturer->TryPop())
110+
if (auto p = this->pVEncodeHelper->Encode(std::move(*f)))
111+
packets.push_back(std::move(*p));
110112

111-
if (auto pkt = this->pVEncodeHelper->Encode(*opFrame)) {
112-
packets.push_back(std::move(*pkt));
113-
}
113+
while (auto a = this->pAudioCapturer->TryPop()) {
114+
if (a->samplesCount() > 0)
115+
if (auto p = this->pAEncodeHelper->Encode(std::move(*a)))
116+
packets.push_back(std::move(*p));
114117
}
115118

116-
// 音频编码
117-
while (auto opAudio = this->pAudioCapturer->TryPop()) {
118-
if (opAudio->samplesCount() > 0) {
119-
if (auto pkt = this->pAEncodeHelper->Encode(std::move(*opAudio))) {
120-
packets.push_back(std::move(*pkt));
121-
}
122-
}
123-
}
119+
if (packets.empty()) return;
120+
std::sort(packets.begin(), packets.end(),
121+
[](const av::Packet& l, const av::Packet& r) { return PtsUs(l.dts()) < PtsUs(r.dts()); });
124122

125-
if (packets.empty()) {
126-
return;
127-
}
128-
129-
std::sort(packets.begin(), packets.end(), [](const av::Packet &left, const av::Packet &right) {
130-
return TimestampInMicroseconds(left.pts()) < TimestampInMicroseconds(right.pts());
131-
});
132-
133-
for (auto &pkt : packets) {
134-
try {
135-
this->ofctx.writePacket(pkt);
136-
}
123+
for (auto& pkt : packets) {
124+
try { this->ofctx.writePacket(pkt); }
137125
catch (const std::exception& e) {
138-
this->LogWarning(std::format("写入 packet 失败: {}, 跳过该包", e.what()));
126+
this->LogWarning(std::format("写包失败: {}", e.what()));
139127
}
140128
}
141129
}
142130

143131
bool MediaRecorder::StopRecording() {
144132
if (!this->runFlag1) return false;
145-
146133
this->runFlag1 = false;
147134
this->pVideoCapturer->StopCapture();
148135

149136
try {
150-
// Drain pending captured video frames before flushing the encoder.
151-
while (auto opFrame = this->pVideoCapturer->TryPop()) {
152-
if (auto pkt = this->pVEncodeHelper->Encode(*opFrame)) {
153-
this->ofctx.writePacket(*pkt);
154-
}
155-
}
156-
157-
while (auto pkt = this->pVEncodeHelper->TrySetOff()) {
158-
this->ofctx.writePacket(*pkt);
159-
}
160-
161-
while (auto opAudio = this->pAudioCapturer->TryPop()) {
162-
if (opAudio->samplesCount() > 0) {
163-
if (auto pkt = this->pAEncodeHelper->Encode(std::move(*opAudio))) {
164-
this->ofctx.writePacket(*pkt);
165-
}
166-
}
167-
}
168-
169-
while (auto pkt = this->pAEncodeHelper->TrySetOff()) {
170-
this->ofctx.writePacket(*pkt);
171-
}
172-
}
173-
catch (const std::exception& e) {
174-
this->LogWarning(std::string("停止录制时捕获到异常: ") + e.what());
137+
while (auto f = this->pVideoCapturer->TryPop())
138+
if (auto p = this->pVEncodeHelper->Encode(std::move(*f)))
139+
this->ofctx.writePacket(*p);
140+
while (auto p = this->pVEncodeHelper->TrySetOff())
141+
this->ofctx.writePacket(*p);
142+
while (auto a = this->pAudioCapturer->TryPop())
143+
if (a->samplesCount() > 0)
144+
if (auto p = this->pAEncodeHelper->Encode(std::move(*a)))
145+
this->ofctx.writePacket(*p);
146+
while (auto p = this->pAEncodeHelper->TrySetOff())
147+
this->ofctx.writePacket(*p);
148+
} catch (const std::exception& e) {
149+
this->LogWarning(std::format("停止时异常: {}", e.what()));
175150
}
176151

177152
this->ofctx.writeTrailer();
178-
153+
uint64_t dropped = this->pVCD3D11Manager->droppedFrames.load();
154+
if (dropped > 0)
155+
this->LogWarning(std::format("丢帧: {}", dropped));
156+
else
157+
this->LogSucc("无丢帧");
158+
this->pVCD3D11Manager->droppedFrames.store(0);
179159
this->pVideoCapturer->Reset();
180-
181160
this->pAEncodeHelper->Reset();
182-
161+
this->pVEncodeHelper->Reset();
183162
this->ofctx.close();
184163
return true;
185-
}
164+
}

0 commit comments

Comments
 (0)