当你改变一位时强烈改变WAV文件。隐写术

时间:2016-12-25 15:00:54

标签: java wav steganography

encode_text()函数实现 LSB 编码方法。首先记录消息长度,然后是消息本身。

numberOfBits 分配用于写入消息的位数。写入一个字节或两个或三个......

这种编码方法使用一位进行记录,声音变化不应该引起耳朵的注意。以及图像对眼睛的变化。但这种情况并非如此。出于某种原因,在声音开始时将声音更改为"噪音" 。那不应该。

读取()保存()好的,如果您读取数据文件然后刻录它们就没有变化。

问题是 encode_text()功能有什么问题。或者也许我做错了什么?

import java.io.*;
import java.util.Arrays;

public class wavIO
{

private String myPath;
private long myChunkSize;
private long mySubChunk1Size;
private int myFormat;
private long myChannels;
private long mySampleRate;
private long myByteRate;
private int myBlockAlign;
private int myBitsPerSample;
private long myDataSize;

public byte[] myData;

public String getPath()
{
    return myPath;
}
public void setPath(String newPath)
{
    myPath = newPath;
}

public wavIO()
    {
    myPath = "";
    }
public wavIO(String tmpPath)
    {
    myPath = tmpPath;
    }

// read a wav file into this class
public boolean read()
{
    DataInputStream inFile = null;
    myData = null;
    byte[] tmpLong = new byte[4];
    byte[] tmpInt = new byte[2];

    try
    {
        inFile = new DataInputStream(new FileInputStream(myPath));

        //System.out.println("Reading wav file...\n"); // for debugging only

        String chunkID = "" + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte();

        inFile.read(tmpLong); // read the ChunkSize
        myChunkSize = byteArrayToLong(tmpLong);

        String format = "" + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte();

        // print what we've read so far
        //System.out.println("chunkID:" + chunkID + " chunk1Size:" + myChunkSize + " format:" + format); // for debugging only



        String subChunk1ID = "" + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte();

        inFile.read(tmpLong); // read the SubChunk1Size
        mySubChunk1Size = byteArrayToLong(tmpLong);

        inFile.read(tmpInt); // read the audio format.  This should be 1 for PCM
        myFormat = byteArrayToInt(tmpInt);

        inFile.read(tmpInt); // read the # of channels (1 or 2)
        myChannels = byteArrayToInt(tmpInt);

        inFile.read(tmpLong); // read the samplerate
        mySampleRate = byteArrayToLong(tmpLong);

        inFile.read(tmpLong); // read the byterate
        myByteRate = byteArrayToLong(tmpLong);

        inFile.read(tmpInt); // read the blockalign
        myBlockAlign = byteArrayToInt(tmpInt);

        inFile.read(tmpInt); // read the bitspersample
        myBitsPerSample = byteArrayToInt(tmpInt);
        // print what we've read so far
        //System.out.println("SubChunk1ID:" + subChunk1ID + " SubChunk1Size:" + mySubChunk1Size + " AudioFormat:" + myFormat + " Channels:" + myChannels + " SampleRate:" + mySampleRate);


        // read the data chunk header - reading this IS necessary, because not all wav files will have the data chunk here - for now, we're just assuming that the data chunk is here
        String dataChunkID = "" + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte();

        inFile.read(tmpLong); // read the size of the data
        myDataSize = byteArrayToLong(tmpLong);


        // read the data chunk
        myData = new byte[(int)myDataSize];
        inFile.read(myData);

        // close the input stream
        inFile.close();

    }
    catch(Exception e)
    {
        return false;
    }

    return true; // this should probably be something more descriptive
}

// write out the wav file
public boolean save(String outputPath)
{
    try
    {
        //DataOutputStream outFile  = new DataOutputStream(new FileOutputStream(myPath));
        DataOutputStream outFile  = new DataOutputStream(new FileOutputStream(outputPath));

        // write the wav file per the wav file format
        outFile.writeBytes("RIFF");                 // 00 - RIFF
        outFile.write(intToByteArray((int)myChunkSize), 0, 4);      // 04 - how big is the rest of this file?
        outFile.writeBytes("WAVE");                 // 08 - WAVE
        outFile.writeBytes("fmt ");                 // 12 - fmt 
        outFile.write(intToByteArray((int)mySubChunk1Size), 0, 4);  // 16 - size of this chunk
        outFile.write(shortToByteArray((short)myFormat), 0, 2);     // 20 - what is the audio format? 1 for PCM = Pulse Code Modulation
        outFile.write(shortToByteArray((short)myChannels), 0, 2);   // 22 - mono or stereo? 1 or 2?  (or 5 or ???)
        outFile.write(intToByteArray((int)mySampleRate), 0, 4);     // 24 - samples per second (numbers per second)
        outFile.write(intToByteArray((int)myByteRate), 0, 4);       // 28 - bytes per second
        outFile.write(shortToByteArray((short)myBlockAlign), 0, 2); // 32 - # of bytes in one sample, for all channels
        outFile.write(shortToByteArray((short)myBitsPerSample), 0, 2);  // 34 - how many bits in a sample(number)?  usually 16 or 24
        outFile.writeBytes("data");                 // 36 - data
        outFile.write(intToByteArray((int)myDataSize), 0, 4);       // 40 - how big is this data chunk
        outFile.write(myData);                      // 44 - the actual data itself - just a long string of numbers
    }
    catch(Exception e)
    {
        System.out.println(e.getMessage());
        return false;
    }

    return true;
}

// return a printable summary of the wav file
public String getSummary()
{
    //String newline = System.getProperty("line.separator");
    String newline = "<br>";
    String summary = "<html>Format: " + myFormat + newline + "Channels: " + myChannels + newline + "SampleRate: " + mySampleRate + newline + "ByteRate: " + myByteRate + newline + "BlockAlign: " + myBlockAlign + newline + "BitsPerSample: " + myBitsPerSample + newline + "DataSize: " + myDataSize + "</html>";
    return summary;
}


// ===========================
// CONVERT BYTES TO JAVA TYPES
// ===========================

// these two routines convert a byte array to a unsigned short
public static int byteArrayToInt(byte[] b)
{
    int start = 0;
    int low = b[start] & 0xff;
    int high = b[start+1] & 0xff;
    return (int)( high << 8 | low );
}


// these two routines convert a byte array to an unsigned integer
public static long byteArrayToLong(byte[] b)
{
    int start = 0;
    int i = 0;
    int len = 4;
    int cnt = 0;
    byte[] tmp = new byte[len];
    for (i = start; i < (start + len); i++)
    {
        tmp[cnt] = b[i];
        cnt++;
    }
    long accum = 0;
    i = 0;
    for ( int shiftBy = 0; shiftBy < 32; shiftBy += 8 )
    {
        accum |= ( (long)( tmp[i] & 0xff ) ) << shiftBy;
        i++;
    }
    return accum;
}


// ===========================
// CONVERT JAVA TYPES TO BYTES
// ===========================
// returns a byte array of length 4
private static byte[] intToByteArray(int i)
{
    byte[] b = new byte[4];
    b[0] = (byte) (i & 0x00FF);
    b[1] = (byte) ((i >> 8) & 0x000000FF);
    b[2] = (byte) ((i >> 16) & 0x000000FF);
    b[3] = (byte) ((i >> 24) & 0x000000FF);
    return b;
}

// convert a short to a byte array
public static byte[] shortToByteArray(short data)
{
    return new byte[]{(byte)(data & 0xff),(byte)((data >>> 8) & 0xff)};
}


public void encode(String text, int numberOfBits)
{
    byte[] byteMessage = text.getBytes();
    byte[] messageLength = bit_conversion(byteMessage.length);
    encodeText(messageLength, myData, 0, numberOfBits);
    encodeText(byteMessage, myData, 32, numberOfBits);  
}

private void encodeText(byte[] addition, byte[] byteDataInputWav, int offset, int numberOfBits)
{
    if(addition.length + offset > byteDataInputWav.length)
    {
        System.out.println("File not long enough!");
    }
    else
    {
        for(int i=0; i<addition.length; ++i)
        {
            int add = addition[i];
            for(int bit=7; bit>=0; --bit, ++offset)
            {
                int b = (add >>> bit) & 1;
                byteDataInputWav[offset] = (byte)((byteDataInputWav[offset] & numberOfBits) | b );
            }
        }
    }
}  

private byte[] bit_conversion(int i)
{
    byte byte3 = (byte)((i & 0xFF000000) >>> 24);
    byte byte2 = (byte)((i & 0x00FF0000) >>> 16);
    byte byte1 = (byte)((i & 0x0000FF00) >>> 8 ); 
byte byte0 = (byte)((i & 0x000000FF)       );
return(new byte[]{byte3,byte2,byte1,byte0});
}

public String decode(String inputPath)
{
    byte[] byteDataOutputWav = myData;
    int length = 0;
int offset2 = 32;
for(int i=0; i<32; ++i)
{
    length = (length << 1) | (byteDataOutputWav[i] & 1);
}
byte[] result = new byte[length];
for(int b=0; b<result.length; ++b )
{
        for(int i=0; i<8; ++i, ++offset2)
        {
            result[b] = (byte)((result[b] << 1) | (byteDataOutputWav[offset2] & 1));
        }
}
    return new String(result);
}


}

1 个答案:

答案 0 :(得分:0)

您正在修改每个字节的最低有效位。因此,无论一个样本在声音文件中有多少位,您都会引入仅〜-48dB的满量程噪声。这在沉默中显然是可听的,我猜你的意思是“在声音的开头”。

您可能只想修改通常的16位的最低有效位,这样只会产生〜-96dB的满量程噪声。

如果仅修改了16位采样的LSB,那么噪声会更加柔和。但是请注意,在声音文件的静音部分,在大声播放音量的良好设备上,它仍然可以被正常的听觉感知。对于使用音频编辑器或类似工具查看声音文件的人来说,这也是相当明显的。对于有效的隐写术,您必须(至少)使您的编码适应与时间相关的声级。