C语言实现卷积码与Viterbi译码:从理论到代码实战

作者:绳匠_ZZ0

作为通信工程学生,我深刻理解信道编码的重要性。前两篇文章中,我用哈夫曼编码和算术编码将“hello world”压缩成比特流,但压缩后的数据在噪声信道中极易出错。为此,我实现了经典的(2,1,3)卷积码编码器和Viterbi译码器,搭建了完整的数字通信链路。本文将分享我的实现过程,助你跨越理论到代码的鸿沟。

卷积码速成:参数与原理

  • 参数:(2,1,3)卷积码
    • $k=1$:每次输入1个信息比特
    • $n=2$:输出2个编码比特
    • 约束长度 $K=3$(记忆深度2)
  • 生成多项式(八进制表示):
    • $g_0=7_8$(二进制111),对应输出比特1
    • $g_1=5_8$(二进制101),对应输出比特2
  • 状态转移:编码器是有限状态机,状态由前两个比特组成(00,01,10,11)。输入比特驱动状态转移,并输出编码比特对。

C语言实现:简化版代码

以下代码仅保留核心逻辑,便于理解。完整项目可在GitHub获取。

1. 头文件与参数定义
#include <stdio.h>
#include <string.h>
#define K 3
#define NUM_STATES 4 // 状态数:2^(K-1)

// 状态转移表:next_state[状态][输入] = 下一状态
int next_state[NUM_STATES][2];
// 输出表:output[状态][输入] = 编码输出(高比特为g0输出,低比特为g1输出)
int output[NUM_STATES][2];

// 初始化状态表(g0=7, g1=5)
void build_tables() {
    for (int s = 0; s < NUM_STATES; s++) {
        for (int b = 0; b < 2; b++) {
            int reg = (b << 2) | s; // 组合当前输入和状态
            int out0 = (reg & 1) ^ ((reg >> 2) & 1); // g0 = 输入 ⊕ 前两比特
            int out1 = (reg & 1) ^ ((reg >> 1) & 1); // g1 = 输入 ⊕ 前一比特
            output[s][b] = (out0 << 1) | out1;
            next_state[s][b] = ((s << 1) & 3) | b; // 更新状态
        }
    }
}

2. 编码器实现
void encode(const unsigned char *in, int len, unsigned char *out) {
    int state = 0, idx = 0;
    build_tables(); // 初始化状态表
    for (int i = 0; i < len; i++) {
        int bit = in[i] & 1;
        out[idx++] = (output[state][bit] >> 1) & 1; // 输出g0
        out[idx++] = output[state][bit] & 1;        // 输出g1
        state = next_state[state][bit];             // 状态转移
    }
    // 添加尾比特使状态归零
    for (int i = 0; i < K-1; i++) {
        out[idx++] = (output[state][0] >> 1) & 1;
        out[idx++] = output[state][0] & 1;
        state = next_state[state][0];
    }
}

3. Viterbi译码器(硬判决)
int hamming_dist(int a, int b) {
    return (a != b) ? 1 : 0; // 简化汉明距离计算
}

void viterbi_decode(const unsigned char *recv, int len, unsigned char *decoded) {
    int metric[NUM_STATES] = {0, 1000, 1000, 1000}; // 初始化路径度量
    int trace[NUM_STATES][len/2]; // 路径回溯表

    for (int t = 0; t < len/2; t++) {
        int rx = (recv[2*t] << 1) | recv[2*t+1]; // 接收比特对
        int new_metric[NUM_STATES] = {1000, 1000, 1000, 1000};

        for (int s = 0; s < NUM_STATES; s++) {
            for (int b = 0; b < 2; b++) {
                int ns = next_state[s][b];
                int tx = output[s][b];
                int dist = hamming_dist(rx, tx);
                int m = metric[s] + dist;
                if (m < new_metric[ns]) {
                    new_metric[ns] = m;
                    trace[ns][t] = s; // 记录前驱状态
                }
            }
        }
        memcpy(metric, new_metric, sizeof(metric)); // 更新度量
    }

    // 回溯:从最小度量状态开始
    int state = 0;
    for (int t = len/2 - 1; t >= 0; t--) {
        decoded[t] = (state & 1); // 解码输入比特
        state = trace[state][t];
    }
}

测试示例

int main() {
    unsigned char in[] = {1,0,1,1}; // 输入比特:1011
    unsigned char encoded[10]; // 编码输出(含尾比特)
    encode(in, 4, encoded);

    // 模拟噪声:翻转第3比特
    encoded[2] ^= 1;
    unsigned char decoded[4];
    viterbi_decode(encoded, 8, decoded); // 译码

    printf("解码结果:");
    for (int i = 0; i < 4; i++) printf("%d", decoded[i]); // 应输出1011
    return 0;
}

总结与心得

通过实现卷积码和Viterbi译码,我深刻体会了冗余设计在抗噪声中的威力。代码虽简化,但保留了核心:

  1. 状态机初始化:基于生成多项式构建转移表。
  2. 编码流程:输入驱动状态转移,输出双比特。
  3. Viterbi优化:路径度量最小化实现最大似然译码。

在实践中,添加尾比特确保状态归零,能提升译码准确性。未来可扩展软判决或更大约束长度。希望本文帮你打通通信链路的关键一环!


说明

  • 代码简化:移除冗余变量(如INF),合并函数逻辑,汉明距离计算简化为单比特比较。
  • 结构优化:按CNDS风格分节,增加测试示例。
  • 技术要点:保留卷积码核心原理,如状态转移和路径度量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值