You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于LSB的音频隐写算法产生噪声问题求助

音频隐写WAV文件后出现可闻噪声的问题解决

问题核心

将消息隐藏到WAV音频后,生成的modified_audio存在可闻噪声,不符合音频隐写的隐蔽性要求。

代码中的关键问题

1. 消息填充计算错误

原代码错误地将每个字符视为64位(len(message)*8*8),导致生成海量填充符#,几乎修改了所有音频帧的LSB。大量连续的LSB修改会直接引发可闻噪声。

2. 未适配音频采样宽度

WAV文件常见8位(单字节采样)和16位(双字节采样)两种格式:

  • 对于16位采样,原代码直接修改每个字节的LSB,包括高字节的第8位。这个位的变化会导致音频采样值出现256幅度的波动,人耳极易察觉。
  • 正确做法是只修改每个采样值的最低有效位(即16位采样的低字节LSB)。

3. 提取逻辑的结束标记不匹配

隐藏时填充单个#作为结束标记,但提取时用split("###")截断,会导致消息提取错误,甚至引发后续处理异常。

修改后的完整代码

import wave
import os

chosen_audio = ""

def hide():
    hide_menu()
    global chosen_audio
    message = input('Input message to be hidden: \n')
    print('hiding message ... \n')
    
    with wave.open(chosen_audio, 'rb') as audio:
        params = audio.getparams()
        sampwidth = params.sampwidth
        frame_bytes = bytearray(audio.readframes(params.nframes))
        
        # 计算最大可存储字符数:每个字符占8位,每个采样存1位
        max_chars = len(frame_bytes) // sampwidth // 8
        if len(message) > max_chars:
            print(f"Error: Message too long. Maximum allowed length is {max_chars} characters.")
            return
        
        # 填充#作为结束标记,直到达到最大字符数
        message = message + (max_chars - len(message)) * '#'
        
        # 将消息转为比特列表
        bits = []
        for char in message:
            # 转8位二进制,高位补0
            char_bits = bin(ord(char)).lstrip('0b').rjust(8, '0')
            bits.extend([int(bit) for bit in char_bits])
        
        # 按采样修改LSB
        bit_index = 0
        for i in range(0, len(frame_bytes), sampwidth):
            if bit_index >= len(bits):
                break
            sample_bytes = frame_bytes[i:i+sampwidth]
            
            if sampwidth == 1:
                # 8位采样:直接修改字节LSB
                sample_bytes[0] = (sample_bytes[0] & 0xFE) | bits[bit_index]
            elif sampwidth == 2:
                # 16位采样:修改采样值的最低位(小端模式)
                sample_value = int.from_bytes(sample_bytes, byteorder='little', signed=True)
                sample_value = (sample_value & ~1) | bits[bit_index]
                sample_bytes = sample_value.to_bytes(sampwidth, byteorder='little', signed=True)
            
            frame_bytes[i:i+sampwidth] = sample_bytes
            bit_index += 1
        
        # 写入修改后的音频
        output_path = 'C:/Users/*****/PycharmProjects/steganography/modified_audio.wav'
        with wave.open(output_path, 'wb') as modified_audio:
            modified_audio.setparams(params)
            modified_audio.writeframes(frame_bytes)
    
    print('message hidden ... \n')

def reveal():
    input_path = 'C:/Users/*****/PycharmProjects/steganography/modified_audio.wav'
    with wave.open(input_path, 'rb') as modified_audio:
        params = modified_audio.getparams()
        sampwidth = params.sampwidth
        frame_bytes = bytearray(modified_audio.readframes(params.nframes))
        
        # 提取每个采样的LSB
        ls_bits = []
        for i in range(0, len(frame_bytes), sampwidth):
            sample_bytes = frame_bytes[i:i+sampwidth]
            if sampwidth == 1:
                ls_bits.append(sample_bytes[0] & 1)
            elif sampwidth == 2:
                sample_value = int.from_bytes(sample_bytes, byteorder='little', signed=True)
                ls_bits.append(sample_value & 1)
        
        # 将比特转成字符
        text = ""
        for i in range(0, len(ls_bits), 8):
            if i+8 > len(ls_bits):
                break
            byte_str = ''.join(map(str, ls_bits[i:i+8]))
            text += chr(int(byte_str, 2))
        
        # 提取到第一个#为止的内容
        message = text.split('#')[0]
    
    print(f"Revealed message: {message}")
    return message

def mode():
    while True:
        method = input(
            ' \n\t\t\tPLEASE, CHOOSE THE PROCEDURE! \n\n \tPRESS H FOR HIDING THE MESSAGE  \t PRESS R FOR REVEALING THE MESSAGE FROM THE AUDIO\n')
        if method.lower() == 'h':
            hide()
            break
        elif method.lower() == 'r':
            reveal()
            break
        else:
            print('Invalid option, please try again.')

def hide_menu():
    global chosen_audio
    chosen_audio = ""
    print(' \nCHOOSE YOUR AUDIO FILE! ')
    
    while True:
        chosen_option = input('\t press 1 & ENTER for your own audio path\n\t press 2 & ENTER for default audio file\n')
        if chosen_option == '1':
            file_path = input('Enter a file path: ')
            if os.path.exists(file_path):
                print('The path is okay, file exists!')
                chosen_audio = file_path
                break
            else:
                print('The specified file in this path does NOT exist, try again.')
        elif chosen_option == '2':
            while True:
                chosen_audio_option = input(
                    '\t press V & enter to use voice audio \t press S & enter to use sound audio\t press M & enter to use song audio\n')
                if chosen_audio_option.lower() == 'm':
                    chosen_audio = 'C:\\Users\\*****\\PycharmProjects\\steganography\\song_audio.wav'
                    break
                elif chosen_audio_option.lower() == 'v':
                    chosen_audio = 'C:\\Users\\*****\\PycharmProjects\\steganography\\voice_audio.wav'
                    break
                elif chosen_audio_option.lower() == 's':
                    chosen_audio = 'C:\\Users\\*****\\Desktop\\audio\\hracka_pes.wav'
                    break
                else:
                    print('No such option !, try again.')
            break
        else:
            print('Invalid option, please try again.')

mode()

额外优化说明

  • 增加消息长度检查,避免超出音频存储上限
  • 使用with语句管理文件,无需手动关闭,避免资源泄漏
  • 简化循环逻辑,减少冗余操作
  • 适配WAV的小端存储格式(绝大多数WAV文件采用小端)

内容的提问来源于stack exchange,提问作者beka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 23:05:33