Combine 2 AAC files using Java
Asked Answered
F

2

8

I am trying to combine 2 AAC files into one, I found out that in AAC files - the header element is present till the FF8 byte, and then next 4 bytes contain the length of the data of the AAC. I tried to maintain 1 header array , add the size of the 2 AAC files, and then add the data buffer of the two files one after the other.

The resultant file played only the first AAC file. Here is the code snippet.

FileInputStream fs = new FileInputStream("./res/after.aac");

dis = new DataInputStream(fs);
headerData = new byte[0xFF8];
dis.read(headerData);


int lengthTotal = dis.readInt();
System.out.println("Length of After == "+lengthTotal);
dis.readInt();


data = new byte[dis.available()];

dis.readFully(data);
dis.close();
dis = null;
fs.close();
fs = null;


fs = new FileInputStream("./res/continue.aac");
dis = new DataInputStream(fs);

dis.skipBytes(0xFF8);

int length = dis.readInt();
System.out.println("Length of Ahead == "+length);
lengthTotal = lengthTotal + length -8;
System.out.println("Total Length== "+lengthTotal);
dis.readInt();
newData = new byte[dis.available()];
dis.read(newData);

FileOutputStream fos = new FileOutputStream("./res/combine.aac");
DataOutputStream dos = new DataOutputStream(fos);

dos.write(headerData);
dos.writeInt(lengthTotal);
dos.writeBytes("mdat");
dos.write(data);
dos.write(newData);

I know that there is information about time duration of the AAC file in the 56th byte, but I am not being able to figure it out. Can someone help me out here?

Fertilizer answered 4/10, 2012 at 2:42 Comment(1)
I am trying the same... Did u fix it? Any Solutions?Copeck
S
1

Well, I can't tell you what you're doing wrong. But I can tell you how to do what you want to do.

First create a General Helper Functions:

    public static class General {
    public static void CopyBytes(byte[] dst, int dstOffset, byte[] src) {
        Buffer.BlockCopy(src, 0, dst, dstOffset, src.Length);
    }
}

public static class BitHelper {
    public static int Read(ref ulong x, int length) {
        int r = (int)(x >> (64 - length));
        x <<= length;
        return r;
    }

    public static int Read(byte[] bytes, ref int offset, int length) {
        int startByte = offset / 8;
        int endByte = (offset + length - 1) / 8;
        int skipBits = offset % 8;
        ulong bits = 0;
        for (int i = 0; i <= Math.Min(endByte - startByte, 7); i++) {
            bits |= (ulong)bytes[startByte + i] << (56 - (i * 8));
        }
        if (skipBits != 0) Read(ref bits, skipBits);
        offset += length;
        return Read(ref bits, length);
    }

    public static void Write(ref ulong x, int length, int value) {
        ulong mask = 0xFFFFFFFFFFFFFFFF >> (64 - length);
        x = (x << length) | ((ulong)value & mask);
    }

    public static byte[] CopyBlock(byte[] bytes, int offset, int length) {
        int startByte = offset / 8;
        int endByte = (offset + length - 1) / 8;
        int shiftA = offset % 8;
        int shiftB = 8 - shiftA;
        byte[] dst = new byte[(length + 7) / 8];
        if (shiftA == 0) {
            Buffer.BlockCopy(bytes, startByte, dst, 0, dst.Length);
        }
        else {
            int i;
            for (i = 0; i < endByte - startByte; i++) {
                dst[i] = (byte)((bytes[startByte + i] << shiftA) | (bytes[startByte + i + 1] >> shiftB));
            }
            if (i < dst.Length) {
                dst[i] = (byte)(bytes[startByte + i] << shiftA);
            }
        }
        dst[dst.Length - 1] &= (byte)(0xFF << ((dst.Length * 8) - length));
        return dst;
    }
}

public static class BitConverterBE {
    public static ulong ToUInt64(byte[] value, int startIndex) {
        return
            ((ulong)value[startIndex    ] << 56) |
            ((ulong)value[startIndex + 1] << 48) |
            ((ulong)value[startIndex + 2] << 40) |
            ((ulong)value[startIndex + 3] << 32) |
            ((ulong)value[startIndex + 4] << 24) |
            ((ulong)value[startIndex + 5] << 16) |
            ((ulong)value[startIndex + 6] <<  8) |
            ((ulong)value[startIndex + 7]      );
    }

    public static uint ToUInt32(byte[] value, int startIndex) {
        return
            ((uint)value[startIndex    ] << 24) |
            ((uint)value[startIndex + 1] << 16) |
            ((uint)value[startIndex + 2] <<  8) |
            ((uint)value[startIndex + 3]      );
    }

    public static ushort ToUInt16(byte[] value, int startIndex) {
        return (ushort)(
            (value[startIndex    ] <<  8) |
            (value[startIndex + 1]      ));
    }

    public static byte[] GetBytes(ulong value) {
        byte[] buff = new byte[8];
        buff[0] = (byte)(value >> 56);
        buff[1] = (byte)(value >> 48);
        buff[2] = (byte)(value >> 40);
        buff[3] = (byte)(value >> 32);
        buff[4] = (byte)(value >> 24);
        buff[5] = (byte)(value >> 16);
        buff[6] = (byte)(value >>  8);
        buff[7] = (byte)(value      );
        return buff;
    }

    public static byte[] GetBytes(uint value) {
        byte[] buff = new byte[4];
        buff[0] = (byte)(value >> 24);
        buff[1] = (byte)(value >> 16);
        buff[2] = (byte)(value >>  8);
        buff[3] = (byte)(value      );
        return buff;
    }

    public static byte[] GetBytes(ushort value) {
        byte[] buff = new byte[2];
        buff[0] = (byte)(value >>  8);
        buff[1] = (byte)(value      );
        return buff;
    }
}

public static class BitConverterLE {
    public static byte[] GetBytes(ulong value) {
        byte[] buff = new byte[8];
        buff[0] = (byte)(value      );
        buff[1] = (byte)(value >>  8);
        buff[2] = (byte)(value >> 16);
        buff[3] = (byte)(value >> 24);
        buff[4] = (byte)(value >> 32);
        buff[5] = (byte)(value >> 40);
        buff[6] = (byte)(value >> 48);
        buff[7] = (byte)(value >> 56);
        return buff;
    }

    public static byte[] GetBytes(uint value) {
        byte[] buff = new byte[4];
        buff[0] = (byte)(value      );
        buff[1] = (byte)(value >>  8);
        buff[2] = (byte)(value >> 16);
        buff[3] = (byte)(value >> 24);
        return buff;
    }

    public static byte[] GetBytes(ushort value) {
        byte[] buff = new byte[2];
        buff[0] = (byte)(value      );
        buff[1] = (byte)(value >>  8);
        return buff;
    }
}

Now implement Audio Helper Class and Interface:

    interface IAudioWriter
    {
    void WriteChunk(byte[] chunk, uint timeStamp);
    void Finish();
    string Path { get; }
}

    class AACWriter : IAudioWriter
    {
    string _path;
    FileStream _fs;
    int _aacProfile;
    int _sampleRateIndex;
    int _channelConfig;

    public AACWriter(string path) {
        _path = path;
        _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
    }

    public void WriteChunk(byte[] chunk, uint timeStamp)
            {
        if (chunk.Length < 1) return;

        if (chunk[0] == 0) { // Header
            if (chunk.Length < 3) return;

            ulong bits = (ulong)BitConverterBE.ToUInt16(chunk, 1) << 48;

            _aacProfile = BitHelper.Read(ref bits, 5) - 1;
            _sampleRateIndex = BitHelper.Read(ref bits, 4);
            _channelConfig = BitHelper.Read(ref bits, 4);

            if ((_aacProfile < 0) || (_aacProfile > 3))
                throw new Exception("Unsupported AAC profile.");
            if (_sampleRateIndex > 12)
                throw new Exception("Invalid AAC sample rate index.");
            if (_channelConfig > 6)
                throw new Exception("Invalid AAC channel configuration.");
        }
        else { // Audio data
            int dataSize = chunk.Length - 1;
            ulong bits = 0;

            // Reference: WriteADTSHeader from FAAC's bitstream.c

            BitHelper.Write(ref bits, 12, 0xFFF);
            BitHelper.Write(ref bits,  1, 0);
            BitHelper.Write(ref bits,  2, 0);
            BitHelper.Write(ref bits,  1, 1);
            BitHelper.Write(ref bits,  2, _aacProfile);
            BitHelper.Write(ref bits,  4, _sampleRateIndex);
            BitHelper.Write(ref bits,  1, 0);
            BitHelper.Write(ref bits,  3, _channelConfig);
            BitHelper.Write(ref bits,  1, 0);
            BitHelper.Write(ref bits,  1, 0);
            BitHelper.Write(ref bits,  1, 0);
            BitHelper.Write(ref bits,  1, 0);
            BitHelper.Write(ref bits, 13, 7 + dataSize);
            BitHelper.Write(ref bits, 11, 0x7FF);
            BitHelper.Write(ref bits,  2, 0);

            _fs.Write(BitConverterBE.GetBytes(bits), 1, 7);
            _fs.Write(chunk, 1, dataSize);
        }
    }

    public void Finish() {
        _fs.Close();
    }

    public string Path {
        get {
            return _path;
        }
    }
}

Now what you need to do by yourself is, read chunks one by one from the first AAC file and write them, after then, read chunks one by one from the second AAC file and append them to the intermediate file.

Note, the above code is C#, so you'll have to use wrapping to simulate C#'s ref effect, just by replacing:

ref Type variable_name

with:

_<Type> variable_name
Snag answered 4/10, 2012 at 4:8 Comment(3)
Even that's still not going to be sufficient, since there's a bunch of MPEG4 metadata (like the stsz sample size table) that have to be combined intelligently.Yogini
What you are suggesting comes in the scope of MP4 container. ADTSHeader in every chunk provides enough information for the decoder to decode the audio correctly, which is AAC raw file. @duskwuffSnag
The reference to "mdat" in the sample code makes me think it's actually an MPEG4 with the wrong extension.Yogini
D
1

I would suggest you to take a look at how aac files are parsed in the jaad library, particularly interesting are ADTS handling code here and ADIF header parsing here.

Dionysus answered 4/10, 2012 at 4:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.