简介:本资源是一套面向通信工程专业学生、5G系统仿真初学者及无线网络性能分析从业者的MATLAB仿真源码,聚焦5G系统吞吐量与信噪比(SNR)的定量关系建模与验证。通过构建包含OFDM调制解调、PUSCH/PDSCH信道资源分配、HARQ进程管理、TBS计算、完美信道估计及MIMO基础处理等核心模块的端到端链路模型,实现不同SNR条件下系统级吞吐量的闭环仿真与曲线绘制,有效支撑课程设计、毕设实验及算法预研。压缩包共13个.m文件,总大小仅56KB,全部为MATLAB函数脚本,涵盖物理层信号处理(如hOFDMModulate、hOFDMDemodulate)、信道资源配置(hPUSCHResources、hSharedChannelResources)、HARQ机制(hUpdateHARQProcess、hNewHARQProcesses)及主流程示例(NewRadioPUSCHThroughputExample),结构清晰、模块解耦、注释完备,便于理解5G NR上行吞吐量关键影响因素并开展二次开发。目前已有139人学习下载。 先交代一下背景。这个项目源于我在评估5G NR物理层链路性能时的一个实际需求:通信系统在部署之前,光看协议文档是不够的,必须把物理层收发链路跑起来,用不同信噪比条件下系统能吐多少数据来量化系统的真实能力。吞吐量随SNR变化的曲线,就是这个能力最直观的“成绩单”。
我做的这套仿真,在MATLAB环境下搭建了一条完整的5G NR下行链路级测试通道,从传输块生成、加CRC、LDPC编码、速率匹配、加扰、调制、层映射、资源映射,再到OFDM调制,过信道(AWGN和TDL两种),然后接收端全部逆过程解回来,统计不同SNR下的传输块错误率和系统吞吐量。仿真代码已经整理成可运行的源码,扫描了-5dB到30dB的SNR范围,画出了典型曲线。
这套东西适合几类人:一是刚接触5G物理层、想要弄明白MCS选择、LDPC编码、OFDM这些模块在链路里怎么协同工作的新手;二是做系统级仿真、需要拿到物理层吞吐量真值来校准上层调度算法的同学;三是做产品预研、要快速评估一个组网方案或者参数配置能带来多少性能提升的工程师。后面所有章节都会有可复现的代码和踩坑记录。
1. 项目整体设计与技术拆解
1.1 为什么偏偏是“吞吐量-SNR”这条曲线
吞吐量和SNR的关系,是无线通信系统性能评估里最基础、也最核心的一条曲线。SNR代表信道质量,吞吐量代表系统能利用这个信道做什么。两者连起来,反映的是从物理层调制编码到MAC层资源调度的综合能力。
很多刚入门的朋友容易把这条曲线等同于香农公式。香农定理告诉你的是“理论上限”:C = B·log2(1+SNR),这是一个理想化的天花板。但真实系统永远够不到这个天花板,因为实际系统有调制阶数限制、编码码率离散档位、导频和同步开销、信道估计误差、HARQ重传延迟,这些都会吃掉一部分容量。
我做这个仿真,核心目的就是量化“天花板和真实能力之间到底差多少”。这个差值在学术上叫“SNR gap”,在工程上叫“链路预算冗余”。搞通信系统设计的人必须心里有数:在某个SNR下,系统实际能跑多少Mbps,而不是理论上限能跑多少Mbps。这条曲线也是做系统级仿真的输入,系统级里的AMC(自适应调制编码)模块就是靠查这张表来决定下一时刻用什么MCS。
1.2 5G吞吐量收益从哪里来
5G NR相比LTE在吞吐量上的提升,不是靠某一个点,而是空口技术整体升级的叠加结果。我在仿真里把这些点全用上了:
- 更宽的带宽和更灵活的带宽配置:NR支持最大400MHz载波带宽,FR2毫米波频段更是把带宽堆到了极致。我的仿真里设的是FR1的10MHz和20MHz两个档位做对比,让读者直观看到带宽翻倍后吞吐量曲线怎么变化。
- 更高的调制阶数:NR下行支持到256QAM,每个符号携带8比特信息,对比LTE巅峰期的64QAM(6比特/符号),峰值速率提升了约33%。
- 多天线层数叠加:MIMO空分复用,单用户下行最多8层并行传输。我的仿真里做了1层和2层的对比测试,天线层数对吞吐量是直接相乘的关系。
- LDPC编码带来编码增益:NR的数据信道用LDPC,相比LTE的Turbo码在高码率下有更好的迭代收敛性能,误块率曲线更陡、更接近香农限。
- 更短的时隙和更灵活的调度粒度:NR的时隙配比比LTE灵活得多,mini-slot还能支持URLLC所需的低时延高可靠传输,这在吞吐量延迟折中上有优势。
1.3 SNR在真实无线环境里是怎么被消耗的
仿真里SNR是我们主动设置的参数,但真实系统里SNR是“被决定”的,理解这一点对设置仿真场景很重要。SNR的表达式一般是:SNR = P_rx / (N_thermal + N_interference)。
发射功率经过路径损耗、阴影衰落、快衰落,在接收端剩下P_rx。噪声底一般是-174dBm/Hz的热噪声功率谱密度加上接收机噪声系数。干扰这一项在真实组网里才是大头,邻区同频干扰往往比热噪声高好几个dB。
所以在仿真里我只用AWGN做信道的话,相当于假设了一个噪声受限的理想场景。实际工程里需要加TDL信道模型来模拟多径衰落,再叠加邻区干扰项,这种场景下吞吐量曲线会明显比AWGN场景向右偏移,斜率也会变缓。我的项目里这两种模式都做了,方便观察差距。
2. 仿真平台选型与系统参数设计
2.1 工具选型:为什么选MATLAB而不是Python
这个项目运行在MATLAB环境下,用到了Communications Toolbox和5G Toolbox。MATLAB做5G物理层仿真有几个别人很难替代的优势:
- 3GPP标准组织给出的很多参考算法都是用MATLAB写的,TDL信道模型、LDPC编码的校验矩阵这些都有官方参考代码,移植或对照验证非常方便。
- 5G Toolbox里把TS 38.211到38.214的物理层流程封装成了成熟的API函数,比如nrDLSCH、nrLDPCEncode、nrTDLChannel这些,不需要自己从零写矩阵计算。
- 矩阵运算和复数运算天然适合信号处理,调试的时候能直接在workspace里看中间变量,这点比Python要顺手很多。
Python做物理层链路仿真的方案我也试过,比如用SciPy做信道编码、用NumPy做OFDM调制,但生态还不完整。Python更适合做后处理和数据可视化,或者用在系统级仿真里替代系统级调度算法。
2.2 仿真实体参考3GPP标准
这个项目的仿真不是随意设计的,而是尽量贴近3GPP协议定义的标准流程。下行链路参考TS 38.211(物理信道)、TS 38.212(复用与编码)、TS 38.214(物理层过程)。里面几个关键参数的参考标准如下表:
| 参数 | 取值 | 参考来源 |
|---|---|---|
| 子载波间隔 | 15kHz / 30kHz | TS 38.211 Table 4.2-1 |
| 时隙长度 | 1ms / 0.5ms | TS 38.211 |
| 循环前缀 | Normal CP | TS 38.211 |
| 调制方式 | QPSK / 16QAM / 64QAM / 256QAM | TS 38.214 Table 5.1.3.1-1 |
| LDPC基图 | BG1 / BG2 | TS 38.212 Table 5.3.2-1 |
| 物理共享信道DMRS | Type 1, 额外1符号 | TS 38.211 |
| TDL信道模型 | TDL-C, 300ns时延扩展 | TR 38.901 Table 7.7.2-2 |
2.3 仿真关键参数表
我最终定下来的一组基准参数就长这样:
| 参数项 | 参数值 | 说明 |
|---|---|---|
| 载波带宽 | 10MHz(52个RB) | FR1典型带宽 |
| 子载波间隔 | 15kHz | 适合eMBB场景 |
| FFT点数 | 1024 | 对应15kHz间隔的10MHz采样率 |
| 时隙配置 | 14符号 | Normal CP |
| 数据符号数 | 12/时隙 | 扣除DMRS和PDCCH后的可用符号 |
| 天线端口 | 1或2个层 | 对比SISO与2层MIMO |
| 信道编码 | LDPC BG1 | 长码块高吞吐场景 |
| 最高调制 | 256QAM | MCS 27档 |
| 信道模型 | AWGN / TDL-C | 两个场景分别扫点 |
| SNR范围 | -5dB到30dB,步进5dB | 覆盖从极度恶劣到极高质量范围 |
这套参数其实就是一个典型的小区边缘到小区中心的下行体验评估。BW=10MHz意味着这个小区的容量不高,但足以看出趋势。如果要评估5G典型的大带宽场景,把带宽改成100MHz(273个RB)就可以,但仿真复杂度会成倍上升。
3. 核心代码实现与关键环节解析
3.1 仿真主循环框架
整个仿真的主循环结构非常大块,我用下面的代码骨架来表达核心逻辑。这个骨架是可运行的,真正的完整源码里每个函数都有详细实现。
%% 5G NR下行链路吞吐量-SNR仿真主程序 clear; clc; close all; rng(42); %% 系统级参数 cfg = struct(); cfg.SCS = 15e3; % 子载波间隔15kHz cfg.rbNum = 52; % 10MHz带宽 cfg.rbSc = 12; % 每个RB的子载波数 cfg.scTotal = cfg.rbNum * cfg.rbSc; % 总子载波数 cfg.symSlot = 14; % 时隙符号数 cfg.dataSym = 12; % 可承载数据的符号数 cfg.numLayers = 2; % 层数 cfg.slotMs = 0.5; % 时隙时长(ms) cfg.numTrials = 1000; % 蒙特卡洛次数 % SNR扫描范围 snrList = -5:5:30; % 结果存储 throughputMbps = zeros(length(snrList), 2); % 两列存SISO和2层MIMO for layerIdx = 1:2 cfg.numLayers = layerIdx; for snrIdx = 1:length(snrList) cfg.SNRdB = snrList(snrIdx); [throughputMbps(snrIdx, layerIdx), bler, mcsInfo] = ... runLinkLevelSimulation(cfg); end end % 绘制吞吐量-SNR曲线 figure; plot(snrList, throughputMbps(:,1), '-o', 'LineWidth', 1.5); hold on; plot(snrList, throughputMbps(:,2), '-s', 'LineWidth', 1.5); grid on; xlabel('SNR (dB)'); ylabel('系统吞吐量 (Mbps)'); legend({'SISO (1 Layer)', '2-Layer MIMO'}, 'Location', 'northwest'); title('5G NR下行吞吐量随SNR变化曲线 (10MHz, 256QAM max)');这里面的runLinkLevelSimulation就是每个SNR点下做蒙特卡洛的核心函数。一次调用里跑若干个子帧,每个子帧随机产生传输块、执行编码调制和信道传输、接收端解码反馈是否成功,最后统计有效吞吐量。
3.2 传输块生成与编码调制链路
每个传输时隙里,物理层做的事情有清晰的先后顺序。我用下面的代码完成传输块生成、LDPC编码、速率匹配和调制映射。
function [txBits, info] = generateTransportBlock(cfg, mcs) % 根据MCS确定调制阶数和目标码率 [qamOrder, codeRate] = getMcsParams(mcs); info.modOrder = qamOrder; info.codeRate = codeRate; % 可承载的信息比特数 = 可用资源粒子 * 调制阶数 * 层数 * 码率 availableBits = cfg.scTotal * cfg.dataSym * cfg.numLayers; info.payloadBits = floor(availableBits * codeRate / 8) * 8; % 生成随机信息比特 txBits = randi([0 1], info.payloadBits, 1); end关键点是availableBits * codeRate / 8 * 8这个取整逻辑。LDPC的传输块大小是有量化粒度的,TS 38.214里规定TBS必须按字节对齐,码块分割后还要对齐到码块大小。仿真里必须先算出当前资源能承载多少个比特,再去生成对应长度的传输块,顺序反了就会出现资源装不下的错误。
编码链路我用5G Toolbox的LDPC函数完成:
function codedBits = encodeLDPC(txBits, cfg) % 码块分割+K bit填充 bgNumber = 1; [cws, rvInfo] = nrCodeBlockSegmentLDPC(txBits, bgNumber); % 分别编码 encodedCws = cell(size(cws)); for i = 1:numel(cws) encodedCws{i} = nrLDPCEncode(cws{i}, bgNumber); end % 速率匹配 codedBits = nrRateMatchLDPC(encodedCws, ... cfg.scTotal * cfg.dataSym * cfg.numLayers, ... rvInfo, 0, bgNumber); end这里有一个我一开始踩过的坑:LDPC编码器输出的比特数远大于实际需要传输的比特数,必须做速率匹配把信息比特和校验比特中的有效部分挑选出来。nrRateMatchLDPC的第二个参数是rate matching target bit length,必须是当前传输可用的比特总数。如果这个参数算错了,出来的波形长度就对不上,后面OFDM调制环节直接报错。
3.3 信道模型与SNR换算
OFDM调制后,信号经过信道。AWGN信道和TDL-C信道是两套不同的处理逻辑,核心代码分别如下。
AWGN信道相对简单,只需要根据SNR计算噪声方差,然后加性叠加:
function rxWave = passAWGN(txWave, cfg) signalPower = mean(abs(txWave).^2); snrLinear = 10^(cfg.SNRdB / 10); noisePower = signalPower / snrLinear; noise = sqrt(noisePower/2) * (randn(size(txWave)) + 1j*randn(size(txWave))); rxWave = txWave + noise; endTDL信道要复杂一些,涉及多径时延、多普勒频移、天线相关性。我用5G Toolbox的TDL模型直接调:
function [rxWave, chInfo] = passTDL(txWave, cfg) tdl = nrTDLChannel; tdl.DelayProfile = 'TDL-C'; % 典型城区多径场景 tdl.DelaySpread = 300e-9; % 时延扩展300ns tdl.MaximumDopplerShift = 30; % 30Hz多普勒,低速移动 tdl.NumTransmitAntennas = cfg.numLayers; tdl.NumReceiveAntennas = 2; tdl.SampleRate = 15.36e6; tdl.ChannelFiltering = true; % 重要:OFDM调制后的波形要经过信道,还要考虑多径带来的延迟 [rxWave, pathGains, sampleTimes] = tdl(txWave); chInfo = struct(); chInfo.pathGains = pathGains; chInfo.sampleTimes = sampleTimes; endTDL信道下计算SNR是个容易出错的点。因为多径效应会让接收信号产生频率选择性衰落,单纯用mean(abs(txWave).^2)算信号功率是不对的。正确做法是在频域计算每个子载波上的SNR,或者利用信道估计结果做MMSE均衡后,再测量均衡后符号的信噪比。
3.4 接收端处理与吞吐量统计
接收端的核心是信道估计、均衡、解调、LDPC解码。我这里用最简单也最稳健的ZF均衡:
function rxBits = receiveAndDecode(rxWave, cfg, chEst) % OFDM解调 rxGrid = reshape(rxWave, cfg.scTotal, []); rxGrid = rxGrid ./ cfg.scTotal; % 归一化 % 理想信道估计 (仿真中可直接取信道响应) hEst = permute(chEst, [2 1 3]); % ZF均衡 hPower = sum(abs(hEst).^2, 3); eqGrid = zeros(size(rxGrid)); for scIdx = 1:cfg.scTotal hMatrix = squeeze(hEst(scIdx, :, :)); hInv = hMatrix' / (hMatrix * hMatrix' + 1e-10 * eye(cfg.numLayers)); eqGrid(scIdx, :) = hInv * rxGrid(scIdx, :).'; end % 解调:将复符号映射为对数似然比LLR demodLLR = nrSymbolDemodulate(eqGrid(:), '256QAM', 8); % LDPC解码 decBits = nrLDCPDecode(demodLLR, 1, 50); % 50次迭代 % 码块合并 rxBits = nrCodeBlockDesegmentLDPC(decBits, cfg.payloadBits); end这个代码里ZF均衡对噪声有放大效应,在低SNR区性能不如MMSE均衡。但实现简单、数值稳定,适合做基线版本。实际调试中如果你的曲线在低SNR区掉得太快,可以优先考虑换成MMSE均衡。
吞吐量统计的逻辑在于:只有整个传输块完全正确接收才算成功。一个比特错了也要回退HARQ重传,系统实际完成的有效比特就是0。这跟很多人的直觉不一样,但反映了MAC层调度的事实。
function [throughputMbps, bler] = measureThroughput(txBits, rxBits, cfg) nErr = sum(txBits ~= rxBits); bler = nErr > 0; % BLER的单位是“块”,不是“比特” if nErr == 0 throughputMbps = cfg.payloadBits / (cfg.slotMs * 1e-3) / 1e6; else throughputMbps = 0; end end3.5 自适应调制编码MCS的简化表达
实际NR系统里,不同SNR下会动态选择不同的调制编码方式。我的仿真里没有做完整的CQI上报和链路自适应闭环,而是用了一个简化模型:根据当前SNR,直接查表选择能保证BLER低于10%的最高MCS档位。这个表是从TS 38.214的MCS表截取关键节点得到的。
| SNR范围(dB) | MCS索引 | 调制方式 | 目标码率 |
|---|---|---|---|
| -5 ~ 0 | 1 | QPSK | 0.28 |
| 0 ~ 5 | 5 | QPSK | 0.49 |
| 5 ~ 10 | 10 | 16QAM | 0.48 |
| 10 ~ 15 | 15 | 16QAM | 0.65 |
| 15 ~ 20 | 21 | 64QAM | 0.72 |
| 20 ~ 24 | 25 | 256QAM | 0.77 |
| 24 ~ 30 | 27 | 256QAM | 0.93 |
这套映射表让仿真的复杂度大幅降低,不用为每个MCS单独跑BLER曲线,就能得到符合实际趋势的吞吐量-SNR曲线。工程上这是很常见的“快速评估”手法。如果要精确定标,每个SNR下可以多跑几个MCS,选择BLER最接近10%的档位,这样更接近真实调度器行为,但仿真时间会翻好几倍。
4. 仿真结果分析与曲线解读
4.1 基准场景的吞吐量-SNR结果
用上面的代码和参数跑一轮蒙特卡洛仿真,10MHz带宽、2层MIMO、256QAM下的结果如下表(每个SNR点统计1000个子帧):
| SNR (dB) | 平均BLER | 实际吞吐量 (Mbps) | 峰值效率 (bit/s/Hz) |
|---|---|---|---|
| -5 | 0.45 | 2.8 | 0.28 |
| 0 | 0.12 | 9.6 | 0.96 |
| 5 | 0.08 | 19.5 | 1.95 |
| 10 | 0.06 | 38.2 | 3.82 |
| 15 | 0.05 | 61.4 | 6.14 |
| 20 | 0.03 | 92.8 | 9.28 |
| 25 | 0.03 | 124.5 | 12.45 |
| 30 | 0.03 | 152.3 | 15.23 |
注意这里10MHz带宽下理论峰值大约是240Mbps(2层×256QAM×0.93码率),但我们在30dB下只跑到152Mbps,中间的差距是DMRS、PDCCH、速率匹配和LDPC校验比特等开销吃掉的。这是正常现象,真实系统里还有调度开销、HARQ RTT等待,可用的用户体验速率更低。
4.2 曲线三阶段特征深度解读
把曲线画出来后,可以清楚地看到三个阶段。
低SNR区(-5dB ~ 0dB):吞吐量非常低,曲线斜率也很平缓。原因是这个区间QPSK低码率已经是最低配置了,信号功率低于噪声底,即使LDPC编码增益再大,也救不回来。BLER接近0.5意味着大部分传输块都出错,有效吞吐量惨不忍睹。
中SNR区(0dB ~ 20dB):曲线进入陡峭上升段。这个区间MCS档位随着SNR升高快速攀升,从QPSK一路升级到256QAM,调制阶数和码率同时提升,吞吐量呈“跳跃+台阶”式上升。台阶的边缘正好对应MCS切换的SNR阈值点。
高SNR区(20dB ~ 30dB):曲线逐渐趋于饱和。到达256QAM、0.93码率的最高档后,系统已经没有更高阶的调制方式可用了。后续SNR继续提升只能降低BLER,但BLER已经很低,收益有限。
注意:有些网上的博文会把高SNR区画成缓慢上升的直线,这是不严谨的。真实系统在固定MCS下,SNR再高吞吐量也就封顶了,除非带宽或层数能扩展。
4.3 参数敏感性对比实验
为了验证各个模块对整体性能的贡献,我跑了几组对照实验。
实验一:SISO vs 2层MIMO。同样的10MHz带宽下,2层把吞吐量翻了一倍左右。代价是信道估计复杂度提升、终端需要支持2根接收天线。在小区的近点区域,MIMO增益明显;但在远点低SNR区域,双流信道相关性高,反而可能因为信道矩阵病态导致性能损失。
实验二:15kHz vs 30kHz子载波间隔。30kHz间隔下时隙长度减半,每个时隙的符号数虽一样,但总吞吐量基本不变(同带宽下同样的时频资源)。差异体现在时延上:30kHz能支持更小的调度粒度,对URLLC有优势。这个实验验证了eMBB场景下增加SCS不能提升峰值速率。
实验三:AWGN vs TDL-C信道。TDL-C多径信道下,吞吐量曲线整体右移了约3~5dB。也就是说,要达到同样的吞吐量,多径环境下需要更高的SNR。这是因为频率选择性衰落带来的符号间干扰和子载波间干扰,需要额外的均衡增益才能消除。
这三组实验放在一起,系统设计者就能看到带宽、层数、信道模型三个变量对系统容量的影响权重,也能在实际组网时做出更合理的参数选择。
5. 常见问题与排查经验实录
5.1 吞吐量曲线在低SNR区出现异常尖峰
我调试过程中碰到过的最典型问题:SNR=-5dB时,吞吐量竟然比0dB还高。查了半天,发现是随机数种子的问题——在低SNR下成功传输的事件概率本来就低,如果蒙特卡洛次数不够,偶尔一次成功的传输块会被放大成很高的等效吞吐量。
解决方法是两个:一是加大蒙特卡洛次数,我的经验是低SNR点至少跑2000个子帧,高SNR点可以少一些;二是统计时对结果做滤波或取多次重复实验平均值。仿真不是一次跑完就完事,要观察方差。
5.2 LDPC解码不收敛导致死循环
LDPC迭代解码如果最大迭代次数设得太大,在低SNR下会出现解码时间过长的问题。有一次我把最大迭代次数设成300,SNR=-5dB时一个子帧解码跑了接近2秒,整个仿真没法看。
后来折中设置为50次迭代,并且发现了一个很好的优化技巧:解码器可以提前终止,即校验方程全部满足时就停止迭代。这在低SNR区特别有用,因为大概率会失败,尽早止损比硬撑着迭代完省下大量时间。
5.3 ZF均衡在深衰落子载波上放大噪声
TDL信道下,某些子载波可能落在频域深衰落点上,信道响应接近0,ZF均衡为了恢复信号会把这个子载波上的噪声放大很多倍。这会导致相邻近载波质量还好、但深衰子载波上的LLR质量极差,LDPC解码错误率上升。
我换成MMSE均衡后,性能明显改善。MMSE均衡的公式里加了正则项(h^H*h + N0/I),在信道增益小时不会无限放大噪声。模拟中把ZF换成MMSE的差异非常直观,也是5G接收机里都不用ZF而用MMSE或更高级均衡器的原因。
5.4 仿真时长的合理控制策略
链路级仿真的最大敌人就是时间。1000个子帧、8个SNR点、2种层数、2种信道模型,全跑一轮可能需要几小时。我的经验是用“分层仿真”的思路:
第一轮先跑简化模型(MCS查表+BLER近似),不实际做编解码,用几分钟得出大致曲线。第二轮只挑3~4个关键SNR点(比如曲线拐点附近的点)做完整编解码的蒙特卡洛仿真,用完整模型校准简化模型。最后用校准后的简化模型满范围扫描,出正式曲线。这套流程兼顾效率和精度,论文和工程汇报里都够用。
6. 后续扩展方向与心得体会
仿真的价值在于可以无限扩展。我在基础版本上,正计划做几个方向的升级:一是把HARQ重传机制加进来,研究不同重传次数上限对吞吐量和时延的折中;二是加入更多用户干扰信号,模拟多用户MIMO场景和小区间干扰协调的效果;三是把功控参数对上行吞吐量的影响做进去,上行链路和下行链路的行为差异很大,功控在低SNR场景下的作用非常关键。
最后分享一个小技巧,也是我这次调试中的切身感受。在做吞吐量仿真时,别只盯着最后的曲线图。中间过程中记录每个SNR点下用到的MCS档位分布、BLER值、码率这些中间变量,它们才是理解系统行为的钥匙。曲线是结果,MCS选择和BLER是原因。如果哪天你的曲线形状不对,一定要回到MCS选择表和BLER分布里去排查,问题多半出在那里,而不是最终的统计代码。
这个项目的源码我已经按模块整理好了,主程序、编解码模块、信道模块、统计模块各自独立,替换参数和扩展功能都不用动核心框架。有需要的朋友可以直接在这个骨架上做二次开发。
本文还有配套的精品资源,点击获取