基于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
相关产品推荐
相关产品推荐

