2

I am writing an application to decode MP3 frames. I am having difficulty finding the headers.

An MP3 header is 32 bits and begins with the signature: 11111111 111

In the inner loop below, I look for this signature. When this signature is found, I retrieve the next two bytes and then pass the latter three bytes of the header into a custom MpegFrame() class. The class verifies the integrity of the header and parses information from it. MpegFrame.isValid() returns a boolean indicating the validity/integrity of the frame's header. If the header is invalid, the outer loop is executed again, and the signature is looked for again.

When executing my program with a CBR MP3, only some of the frames are found. The application reports many invalid frames.

I believe the invalid frames may be a result of bits being skipped. The header is 4 bytes long. When the header is determined to be invalid, I skip all 4 bytes and start seeking the signature from the next four bytes. In a case like the following: 11111111 11101101 11111111 11101001, the header signature is found in the first two bytes, however the third byte contains an error which invalidates the header. If I skip all of the bytes because I've determined the header beginning with the first byte is invalid, I miss the pottential header starting with the third byte (as the third and fourth bytes contain a signature).

I cannot seek backwards in an InputStream, so my question is the following: When I determine a header starting with bytes 1 and 2 to be invalid, how do I run my signature finding loop starting with byte 2, rather than byte 5?

In the below code, b is the first byte of the possible header under consideration, b1 is the second byte, b2 is the third byte and b3 is the fourth byte.

int bytesRead = 0;

//10 bytes of Tagv2
int j = 0;

byte[] tagv2h = new byte[10];
j = fis.read(tagv2h);
bytesRead += j;

ByteBuffer bb = ByteBuffer.wrap(new byte[]{tagv2h[6], tagv2h[7],tagv2h[8], tagv2h[9]});
bb.order(ByteOrder.BIG_ENDIAN);
int tagSize = bb.getInt();

byte[] tagv2 = new byte[tagSize];
j = fis.read(tagv2);
bytesRead += j;

while (bytesRead < MPEG_FILE.length()) {

        boolean foundHeader = false;

        // Seek frame
        int b = 0;
        int b1 = 0;
        while ((b = fis.read()) > -1) {
            bytesRead++;
            if (b == 255) {
                b1 = fis.read();
                if (b1 > -1) {
                    bytesRead++;
                    if (((b1 >> 5) & 0x7) == 0x7) {
                        System.out.println("Found header.");
                        foundHeader = true;
                        break;
                    }
                }
            }
        }

        if (!foundHeader) {
            continue;
        }

        int b2 = fis.read();
        int b3 = fis.read();

        MpegFrame frame = new MpegFrame(b1, b2, b3, false);
        if (!frame.isValid()) {
            System.out.println("Invalid header @ " + (bytesRead-4));
            continue;
        }

}
noahnu
  • 3,479
  • 2
  • 18
  • 40

2 Answers2

0

You can wrap your input stream in a PushbackInputStream so that you can push back some bytes and re-parse them.

Denis Tulskiy
  • 19,012
  • 6
  • 50
  • 68
0

I ended up writing a function to shift the bytes of an invalid header so it can be re-parsed. I call the function in a loop where I essentially Seek for valid frames.

Seek() returns true when a valid frame is found (elsewhere the last frame found by calling Seek() is stored). CheckHeader() verifies the integrity of a header. SkipAudioData() reads all the audio data of a frame, placing the stream marker at the end of the frame.

private boolean Seek() throws IOException {
    while(!(CheckHeader() && SkipAudioData())){
        if(!ShiftHeader()){
            return false;
        }
    }
    return true;
}


private boolean ShiftHeader() {
    try {
        if (bytesRead >= MPEG_FILE.length()) {
            return false;
        }
    } catch (Exception ex) {
        ex.printStackTrace();
        return false;
    }

    header[0] = header[1];
    header[1] = header[2];
    header[2] = header[3];

    try {
        int b = fis.read();
        if (b > -1) {
            header[3] = b;
            return true;
        }
    } catch (IOException ex) {
        return false;
    } catch (Exception ex) {
        return false;
    }

    return false;
}
noahnu
  • 3,479
  • 2
  • 18
  • 40