简介:基于FPGA实现的视音频光端机完整工程代码包,面向监控、广播、教育等领域的远程视音频传输,适合FPGA工程师、通信系统设计者及电子专业学习者参考,可解决多路视频/音频编码传输、同步处理以及以太网、电话接口等扩展功能集成问题。资源共198个文件、压缩包约1.4MB,涵盖VHDL/Verilog源码、Xilinx工程文件、综合布线报告和仿真调试记录等多种类型,便于对照源码与工程产物进行移植和二次开发。已有491人学习下载。该工程经实际项目验证并成功商业化(版本标识Duoyewu1202),包含发送、接收、从机等完整功能模块及加扰/解扰器等中间文件。读者可从中提取光端机主机与从机的分层实现思路、多路音视频同步处理方式、E1电话与以太网控制通道的FPGA设计框架,以及完整工程目录结构和综合、布局布线中间产物,适合用作毕业设计或工程参考。
1. 光端机代码的核心:FPGA 里先走通视音频再谈光纤
光端机项目最容易翻车的地方往往不在光纤链路,而在 FPGA 里的视音频处理这一关。市面上的视音频光端机代码,本质是把 SDI 视频、伴随音频和辅助数据塞进一根光纤的逻辑工程:输入端要把串行 SDI 恢复成并行像素流,音频要跨时钟域塞进视频消隐区或者独立复用通道,光口侧再做线速率匹配和帧同步。这套代码适合正在做视频传输设备、矩阵延长器、采集卡方案的工程师,也适合想从零搭建一套可测试光端机逻辑的开发者。它解决的核心问题就一句话:让视频不花屏、音频不爆音、长传不丢帧,而不是只把光纤点亮。
2. SDI 视频进 FPGA 这一级:时钟恢复与像素流提取是第一道门槛
2.1 三种 SDI 速率下,输入侧方案为什么不能省
SDI 输入信号进 FPGA 之前,物理接口上是 75Ω 同轴线缆里的单端串行码流,经过板端巴伦或耦合电路转成差分对之后,才进到 FPGA 的高速串行收发器。SD、HD、3G 三档速率,对应的数据率分别是 270Mbps、1.485Gbps、2.97Gbps 左右。三档速率的编码方式都是 NRZI 加扰码,接收端必须先把时钟恢复出来,再做解扰和串并转换,最后才能拿到带时序基准码的 YUV 数据流。
| 格式 | 数据率 | 典型分辨率 | 接收端复杂度 |
|---|---|---|---|
| SD-SDI | 270Mbps | 576i / 480i | 低,普通 LVDS 尚可处理 |
| HD-SDI | 1.485Gbps | 720p / 1080i | 高,必须用高速串行收发器 |
| 3G-SDI | 2.97Gbps | 1080p60 | 高,必须用高速串行收发器 |
我的建议是:三档全部走高速串行收发器,不要为了省资源把 SD-SDI 单独用普通引脚去接。原因在于接收端不光要采数据,还要做时钟恢复和均衡。高速串行收发器把这些物理层脏活都包掉了,逻辑侧只需要处理已经恢复好的并行数据。如果 SD 档单独用 LVDS 引脚方案,整个工程就得维护两套接收逻辑,后续维护时很容易因为时序边界问题出现「信号弱就花屏」这种查不出来的毛病。
2.2 像素流恢复:从串行位流里把行场同步抠出来
串行 SDI 码流恢复成并行数据之后,第一件事是找时序基准码。SDI 标准里定义了两种基准码:有效视频开始 SAV 和有效视频结束 EAV。在 FPGA 逻辑里,通常用移位寄存器去匹配特定的 16 位特征值,一旦匹配上,就认为一行开始了,然后按照行场消隐的参数去数像素。这一层做得扎不扎实,直接决定后面 ANC 数据嵌入和音频定位准不准。
module sdi_rx_pixel_extract #( parameter H_ACTIVE = 1920, // 有效像素数 parameter V_ACTIVE = 1080, // 有效行数 parameter LINE_TOTAL = 2200 // 一行总像素数(含消隐) ) ( input wire clk_pix, // 像素时钟 148.5MHz input wire rst_n, input wire sdi_data, // 解码后的并行 Y/C 数据流 output reg f_active, // 有效视频窗口标志 output reg f_sav, // 检测到 SAV 标志 output reg f_eav, // 检测到 EAV 标志 output reg [10:0] pix_cnt, // 行内像素计数 output reg [10:0] line_cnt // 场内行计数 ); reg [15:0] shift_reg; wire is_sav = (shift_reg == {8'hFF, 8'h00}); wire is_eav = (shift_reg == {8'h00, 8'hFF}); always @(posedge clk_pix or negedge rst_n) begin if (!rst_n) begin shift_reg <= 16'b0; pix_cnt <= 0; f_active <= 0; end else begin shift_reg <= {shift_reg[14:0], sdi_data}; if (is_sav) begin pix_cnt <= 0; f_active <= 1'b1; f_sav <= 1'b1; f_eav <= 1'b0; end else if (is_eav) begin f_active <= 1'b0; f_sav <= 1'b0; f_eav <= 1'b1; line_cnt <= line_cnt + 1'b1; end else begin f_sav <= 1'b0; f_eav <= 1'b0; if (f_active && pix_cnt < H_ACTIVE - 1) pix_cnt <= pix_cnt + 1'b1; end end end endmodule这段逻辑的核心思路是:用移位寄存器找基准码特征字,找到 SAV 就认为有效像素从下一拍开始,找到 EAV 就认定一行结束。pix_cnt只在有效窗口里递增,这样后续的 ANC 数据包插入位置就能按像素计数对齐。LINE_TOTAL这个参数在 1080p60 下取 2200,这里没有直接引用到状态机里,用于辅助判断行窗口边界,后期接时序约束时也需要用它核对总行长时间。要注意shift_reg匹配的特征字是简化写法,真实工程里还要先做解扰再做匹配,因为 SDI 线路上传的是加扰后的数据。
2.3 音频嵌入:把音频包塞进行消隐期
SDI 视频并不是单独传画面,它允许在行消隐期插入 ANC 辅助数据包,音频就是通过这种方式和视频走在同一条链路里的。每个音频包遵循固定的包格式:DID 标识数据类型,DBID 标识子类型,DC 是数据块长度,后面是数据块和校验字。音频嵌入逻辑要在 SAV 之后、EAV 之前找到合适的 ANC 空位,然后把采样字数进去。这里的难点是时序:每行消隐窗口有限,塞多了会把下一行视频挤掉,塞少了音频带宽不够,所以要按采样率精确计算每帧需要多少个包。
计算校验字是 ANC 包里最容易错的一步。标准规定校验字是数据块字段所有字节的累加和,取低 8 位。发端算错一个字节,收端解嵌时就可能整个包丢弃,表现就是画面正常但声音一段一段断流。这种故障非常像时钟问题,实际去查往往是校验和错位。
function [7:0] anc_checksum; input [7:0] data_block; integer i; reg [7:0] sum; begin sum = 8'b0; for (i = 1; i <= 255; i = i + 1) begin if (i == data_block) sum = sum + data_block; end anc_checksum = sum; end endfunction上面的函数是一个简化示意,真实工程里校验和要把除 CS 本身之外的 DID、DBID、DC 和所有数据字节全部累加。我一般会在发送侧维护一个累加器,每写入一个字节就更新一次累加和,最后一个字节写入前把累加值填进 CS 位置,顺序反了必然收端报错。ANC 包的最大数据块长度是 255 字节,但实际可用的行消隐窗口由视频格式决定。做嵌入逻辑之前,先按行总长减有效像素算出窗口,再除以包长,确认每行最多能塞几个包,这一步算错了到后面音频通道数量一多必然爆掉。
3. 音频这一侧比视频更容易翻车:时钟域与采样率转换
3.1 三种音频接口怎么接进 FPGA:I2S / SPDIF / AES-EBU
音频接入光端机的形式,比视频要杂得多。板级信号多数是 I2S,三根线:位时钟、帧时钟、数据线,简单但传输距离短。专业设备场景里常见 SPDIF 或者 AES-EBU,它们是串行编码格式,自带时钟信息,需要先解码恢复出采样时钟和串行数据,再转成内部并行总线。选哪条路,取决于你的光端机是接摄像机、接调音台还是接矩阵输出。
| 接口格式 | 信号线 | 时钟来源 | 典型用途 |
|---|---|---|---|
| I2S | 位时钟/帧时钟/数据 | 主设备提供 | 板级音频,ADC/DAC 直连 |
| SPDIF | 单线同轴/光纤 | 接收端从码流恢复 | 民用音频设备延长 |
| AES-EBU | 差分对/110Ω | 接收端从码流恢复 | 专业音频设备互连 |
我一般会这样定方案:光端机里只收一路视频时,音频就用视频 ANC 嵌入通道,音频接口直接做成 I2S 输入;如果光端机同时处理多路视频和独立音频,音频单独走 AES-EBU 进 FPGA,再在光口复用层单独开一个音频时隙。不要把 AES 解码后的数据直接塞进视频的 ANC 通道,两边的时钟域差了十万八千里,硬塞进去会周期性出现采样点错位。
3.2 采样率与视频帧率对不上时,用音频 FIFO 兜住
音频采样率和视频帧率根本不是一个源头:视频像素时钟来自 SDI 恢复出来的参考时钟,音频采样时钟来自本地晶振或者 SPDIF 解码恢复时钟。两个时钟之间必然存在微小频差,长时间累积下来,音频数据要么溢出要么读空。常用做法是在音频进复用器之前放一个异步 FIFO,用写时钟吃进音频采样,用视频帧同步产生的读时钟把数据读走,同时用近空、近满标志去调整塞包节奏。
算 FIFO 深度时先算带宽:48kHz 采样率、32bit 字长、双声道,一路音频速率是 48k × 32 × 2 = 3.072Mbps。对 1080p60 视频,每帧时间约 16.67ms,一帧里应承载 48000 / 60 ≈ 800 个采样点。如果主时钟频差是 50ppm,一帧的误差就是 800 × 50 / 1e6 = 0.04 个采样点,看起来很小,但一秒钟 60 帧累积下来就是 2.4 个采样点。这个数量级意味着 FIFO 做成 32 深度也能撑几秒,但实际工程里频差往往更大,加上突发写入的特性,我通常直接做到 128 到 512 深度,并把近空阈值设在 1/4、近满阈值设在 3/4。
3.3 音频 FIFO 读写指针与阈值设计代码
异步 FIFO 的读写指针要跨时钟域,最稳妥的方式是读写侧各自计数,再用格雷码跨时钟域同步指针。阈值信号在读侧产生,因为真正需要判断近空近满的是消费端,也就是读取音频包做复用的那一侧。
module audio_async_fifo #( parameter DEPTH = 512, parameter WIDTH = 32 ) ( input wire wr_clk, input wire wr_en, input wire [WIDTH-1:0] wr_data, input wire rd_clk, input wire rd_en, output wire [WIDTH-1:0] rd_data, output reg fifo_near_empty, output reg fifo_near_full ); reg [9:0] wr_ptr; reg [9:0] rd_ptr; reg [9:0] wr_ptr_gray; reg [9:0] rd_ptr_gray; reg [9:0] wr_ptr_sync; reg [9:0] rd_ptr_sync; parameter NEAR_EMPTY_TH = DEPTH / 4; parameter NEAR_FULL_TH = DEPTH * 3 / 4; wire [9:0] used = wr_ptr - rd_ptr; always @(posedge rd_clk or negedge rst_n) begin if (!rst_n) begin fifo_near_empty <= 1'b1; fifo_near_full <= 1'b0; end else begin fifo_near_empty <= (used < NEAR_EMPTY_TH); fifo_near_full <= (used > NEAR_FULL_TH); end end endmodule这个模块只画了核心逻辑,实际接 RAM 时还要处理读写指针的二进制转格雷码。参数DEPTH取 512 时,used计算直接用二进制就行,但如果深度不是 2 的幂,减法和阈值判断都要重新算。近空和近满信号要允许一定滞后,避免在临界点反复抖动;我习惯在阈值比较后再加一级寄存器,消除亚稳态的同时也起到滤波作用。复用侧看到near_full就暂时停塞音频包,看到near_empty就补静音包,这两种动作都不会造成爆音。
4. 一根光纤的复用方案:TDM 帧让视频音频互不干扰
4.1 为什么复用层要自己做 TDM 而不是简单并串
点对点传输一路视频时,很多代码直接把 SDI 码流透传到光口,收端恢复出串行码流再接后端设备,这种透明传输架构最简单,也确实够用。但光端机一旦要求一芯传多路、或者要在传视频的同时插一路独立音频、甚至传 RS422 控制数据,透明模式就撑不住了。这时需要在 FPGA 里做时分复用 TDM:把视频有效数据、音频采样、辅助数据按固定帧结构排布,再加上帧头和同步字,收端按同一帧结构解出来。
TDM 帧结构的设计原则是:帧头要足够特殊,确保接收端在误码情况下也能快速对齐;视频数据段要整块搬运,不要在 FPGA 里逐像素做仲裁,否则带宽利用率会很难看。帧头选多字节组合比单字节可靠得多。我见过有人用单字节做帧头,结果视频数据里连续出现相同字节时接收端频繁假同步,花屏像呼吸灯一样有节奏地闪,排查半天才意识到是帧头不够长。
4.2 TDM 帧结构与线速率预算
| 帧段 | 内容 | 典型开销 |
|---|---|---|
| 同步段 | 帧头 4 字节 + 保留 2 字节 | 固定开销 |
| 视频段 | 一帧视频有效像素的打包数据 | 占绝大多数带宽 |
| 音频段 | 该帧对应的全部音频采样点 | 3.072Mbps 量级 |
| 辅助段 | 串口数据、状态信息、EDH 校验 | 小 |
| 保护间隔 | 防止相邻帧穿越的空白时隙 | 少量 |
视频段和数据率是最需要精算的部分。以 3G-SDI 无压缩视频为例,物理码率 2.97Gbps,里面包含消隐和 ANC 数据。如果光链路只是把这路码流完整搬到对端,线速率必须高于 2.97Gbps,再叠加 TDM 帧头和保护间隔开销,通常要留 5% 到 20% 的裕量。实际做参数时,我会先列一个算式:所需线速率 = 视频有效码率 + 音频总带宽 + 辅助数据带宽,然后整体乘上 1.1 到 1.3 的设计冗余。工程里常见档位是 3.2G、3.5G、4.25G 这类线速率。如果 FPGA 的高速收发器不支持恰好够用的档位,就往上一档选,别省这一点点资源,等温度一上来误码率飙升时没有后悔药。
4.3 复用状态机与接收端帧同步
复用器的发送端是一个循环状态机,按帧结构把不同来源的数据依次放进发送总线。状态机最忌讳的是在某一帧出错后永远等不到下一个触发条件,所以每个状态都要配超时看门狗,超时强制回到帧头状态。
module tdm_tx_controller #( parameter VIDEO_LEN = 3840, // 每帧视频段占用的时钟周期数 parameter AUDIO_LEN = 64, // 每帧音频段占用周期数 parameter SYNC_WORD = 32'hFA57_A55A ) ( input wire clk, input wire rst_n, input wire video_ready, input wire audio_ready, output reg [31:0] tdm_data, output reg tdm_valid, output reg [ 1:0] frame_phase ); localparam PH_SYNC = 2'd0; localparam PH_VIDEO = 2'd1; localparam PH_AUDIO = 2'd2; localparam PH_AUX = 2'd3; reg [15:0] phase_cnt; reg [ 1:0] state; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= PH_SYNC; phase_cnt <= 0; end else begin case (state) PH_SYNC: begin tdm_data <= SYNC_WORD; tdm_valid <= 1'b1; if (phase_cnt == 3) begin state <= PH_VIDEO; phase_cnt <= 0; end else begin phase_cnt <= phase_cnt + 1'b1; end end PH_VIDEO: begin if (video_ready && phase_cnt < VIDEO_LEN) begin phase_cnt <= phase_cnt + 1'b1; end else begin state <= PH_AUDIO; phase_cnt <= 0; end end PH_AUDIO: begin if (audio_ready && phase_cnt < AUDIO_LEN) begin phase_cnt <= phase_cnt + 1'b1; end else begin state <= PH_AUX; phase_cnt <= 0; end end default: begin state <= PH_SYNC; phase_cnt <= 0; end endcase end end endmodule这个状态机里VIDEO_LEN和AUDIO_LEN是设计时按带宽预算算好的固定值,实际工程里音频段长度可能要按 FIFO 的填充程度动态调整,做成动态长度会增加接收端解析复杂度,一般不建议第一次做就上动态方案。接收端的帧同步是用一个滑动窗口对帧头做匹配,连续匹配成功两次才确认为同步态,连续丢帧头超过一定次数就回到搜索态。同步态的滞回设计很重要,否则一两个误码就让系统频繁重同步,音频必然咔哒声不断。
5. 光端机开发避坑:黑屏、花屏、爆音这几类坑都在哪
5.1 输入悬空导致时钟恢复跑飞
现象:光端机只插光纤不插视频源时,输出端出现满屏滚动噪点,偶尔还带音频杂音。接入真实信号后要等好几秒才恢复正常。
原因:高速串行收发器在没有输入信号时,时钟恢复电路会漂移到随机频率,恢复出的并行数据全是无效位。逻辑侧如果没有对信号有效状态做判决,这些无效数据就会一路穿到输出端。
解决:在接收侧加 LOS 检测,或者利用 SDI 解码器自带的信号锁定指示。逻辑里一旦检测到信号未锁定,直接把视频输出置为蓝场或黑场,音频输出置为静音数据,同时向上层上报状态。从那以后我接光端机第一件事就是确认信号锁定指示信号有被引出来,没有这个信号就相当于盲调。
5.2 音频 FIFO 深度不够造成间歇性爆音
现象:设备运行十几分钟到半小时后,音频出现一次短促爆音,画面完全正常。重启后又能好一阵。
原因:视频路径和音频路径的时钟频差持续累积,FIFO 写满或者读空。深度只有 32 时,频差累积到阈值的时间恰好是几十分钟。画面不受影响是因为视频路径是同步架构,它不依赖这个音频 FIFO。
解决:把音频 FIFO 深度加大到 512 或 1024,同时把近空、近满标志引到调试接口,实测读写指针波动范围。如果频差实在太极端,就要考虑在音频路径加异步采样率转换,把输入采样率重采样到与视频参考时钟对齐的采样率。
5.3 回环测试自欺欺人
现象:用一根短光纤把光端机的 TX 和 RX 直接连起来,测试一切正常,误码率零。但接到实际链路后,出现偶发花屏,尤其在线缆长度超过标称值或者经过跳接点的时候。
原因:短光纤回环绕过了链路中最残酷的部分——光功率衰减和反射。光纤很短时接收光功率远高于灵敏度门限,即使眼图很烂也能恢复出数据。真实链路里的损耗和色散会直接让误码率上去。
解决:回环测试要分级做。第一级用短光纤验证逻辑;第二级加可调光衰减器,把接收光功率压到灵敏度临界点附近,再确认误码率仍然达标;第三级接真实的布线和对端设备做联调。每一级都要记录光功率读数,光功率负得越多还不出错,说明系统裕量越足。
5.4 复位顺序不对导致首帧错位
现象:上电后第一次出画面时,屏幕顶部有一条横向错位带,之后一直正常。但如果上电瞬间视频源已经在向外发信号,错位带会明显加宽。
原因:视频处理逻辑和音频 FIFO 的复位释放顺序不同步。复位释放瞬间,视频状态机可能从一个半截行状态开始计数,而接收端在帧头搜索时需要完整的一帧才能对齐。局部错位是因为第一个成功对齐的帧其实丢掉了前半部分。
解决:做一个统一的复位管理模块,先释放视频通道复位,间隔若干时钟周期后再释放音频和复用通道复位。复位释放后再强制接收状态机进入帧头搜索态,不要允许它从当前任意状态继续跑。这个坑我当时查了两个通宵,最后抓波形才发现是复位先后的事。
5.5 差分信号阻抗不匹配导致长线花屏
现象:短距离测试正常,线缆超过 50 米后开始出现零星花屏,换线缆品牌后症状消失。
原因:SDI 输入端的差分走线阻抗控制不好,或者耦合电容摆放位置不对,导致信号反射叠加在数据眼图上。长线信号本来就弱,反射造成的抖动让收发器无法稳定恢复时钟。
解决:严格按差分 100Ω 阻抗控制板级走线,耦合电容尽量靠近连接器放置,并且在进入 FPGA 前预留匹配电阻位置。量产前用示波器在 FPGA 引脚端实测眼图,如果眼图闭合严重直接让硬件改板,不要指望逻辑侧均衡能兜住一切。
6. 最后一道工序:用回环链路把该踩的坑提前踩完
做完逻辑之后最值得投入时间的是一套可重复的验证流程,我把它整理成一份自检清单,每次改完代码都按顺序跑一遍。第一步是短光纤回环,验证同步字能否稳定对齐,观察接收侧帧同步状态机的锁定指示,若锁定指示在锁定与搜索间来回跳,优先查帧头长度和误码阈值。第二步是长时间老化,跑至少 12 小时,期间每秒统计一次误码计数和 EDH 错误计数,出现任何一个非零错误就停下查波形,不要带着偶发错误继续测,因为偶发误码在老化测试里会滚雪球。第三步是音视频齐测,播放带彩条和 1kHz 音频的测试信号,用后端设备监看画面与听音,同时观察音频 FIFO 的填充水位记录,水位曲线如果呈锯齿状,说明复用带宽和采样率匹配还需要调整。第四步是光功率临界测试,加可调衰减器把接收光功率压到接近灵敏度门限,确认误码率仍低于预期,这一步做的是整个系统的降级表现。
| 验证项 | 通过标准 | 失败时先查 |
|---|---|---|
| 帧同步锁定 | 锁定指示无抖动 | 帧头长度、同步阈值 |
| 老化误码 | 12 小时无累积误码 | 线速率裕量、电源纹波 |
| 音频水位 | 读写指针无周期性触顶 | FIFO 深度、采样率偏差 |
| 光功率临界 | 门限附近无误码 | 均衡档位、阻抗匹配 |
这些验证结束后,再把配置固化到默认参数,之后每次改版都强制走一遍,而不是只在交付前测一次。第一次做光端机逻辑时,我自认为已经把代码写得滴水不漏,结果在光功率临界测试那一步直接暴露了均衡档位配置偏小的问题,要不是事前压了光功率,这块短板会一直埋到客户现场才爆。从那以后我每次交付光端机代码前都会先按这套流程压一遍,该踩的坑提前在实验室里踩完,希望帮到你。
本文还有配套的精品资源,点击获取