2012년 1월 26일 목요일

Quick way to extract AMR-audio from Android 3GP files

출처 : http://android.amberfog.com/?p=181



Quick way to extract AMR-audio from Android 3GP files

If you need to record audio from microphone of your Android-device to a file, it is better to useMediaRecorder;. On the current Android platforms (we can be sure for 1.6, 1.5 and earlier versions) you can record only to 3GPP AMR-NB file, but not to the raw AMR. If you will try to use MediaRecorder.OutputFormat.RAW_AMR you will get native error that will not come to Java layer. We’re pretty sure that it will be fixed in the future, but what to do now if you need simple raw AMR-NB file without extra 3GP-container?
Simple Google search about how to extract raw AMR audio from 3GPP-file recorded by your Android return us a link to the isobox4j library that can convert between all kinds of 3gp, amr, mp4, etc. files. It is perfect, but it is 400Kb. Do you really need to have such huge library in your project only for converting one type of file to another?
I will show you the easier way that works fine only with Android 3GP-files. What you need to do – just remove 3GP-container and store AMR-data with appropriate header.
3GP-header fields are stored in 32-bits, big-endian format. So, you need correct functions to read 32-bits values from ByteArrayInputStream.
public long readUint32(ByteArrayInputStream bis) {
    long result = 0;
    result += ((long) readUInt16(bis)) << 16;
    result += readUInt16(bis);
    return result;
}
public int readUInt16(ByteArrayInputStream bis) {
    int result = 0;
    result += bis.read() << 8;
    result += bis.read();
    return result;
}
Now you need to skip 3gp-headers. First header is file header (‘ftyp’) and next is MediaData (‘mdat’). I’ve made it according to 3GP and Iso file structure specifications, using classes. Base Box contains two fields: size and type.
class Box {
    protected long size;
    protected long type;
 
    protected long boxSize = 0;
 
    public Box(ByteArrayInputStream bis) {
        size = readUint32(bis);
        boxSize += 4;
        type = readUint32(bis);
        boxSize += 4;
    }
}
Now classes for Filetype header and MediaData header:
class FileTypeBox extends Box {
    private static char[] HEADER_TYPE = {'f', 't', 'y', 'p'};
 
    protected long brand;
    protected long minorVersion;
    protected long[] compatibleBrands;
 
    public FileTypeBox(ByteArrayInputStream bis) {
        super(bis);
        brand = readUint32(bis);
        boxSize += 4;
        minorVersion = readUint32(bis);
        boxSize += 4;
        int remainSize = (int)(size - boxSize);
        if (remainSize > 0) {
            compatibleBrands = new long[remainSize / 4];
            for (int i = 0; i < compatibleBrands.length; i++) {
                compatibleBrands[i] = readUint32(bis);
            }
        }
    }
}
class MediaDataBox extends Box {
    private static char[] HEADER_TYPE = {'m', 'd', 'a', 't'};
 
    protected byte[] data;
 
    public MediaDataBox(ByteArrayInputStream bis) {
        super(bis);
        // you can read data[] here, but we need only size
    }
 
    public int getDataLength() {
        return (int)(size - boxSize);
    }
}
I add HEADER_TYPE to each Box-class. You can add checking that class field type has the appropriate HEADER_TYPE value just to be sure that you read and parse correct file.
So, we’re ready to convert 3GP to AMR. 3GP-structure is as follows:
1. FileType header
2. MediaDataHeader. Data inside it is raw AMR data.
3. MovieData box (‘moov’ type) that presents, but is empty and we don’t need to copy it to destination AMR-file.
Converting steps is as follows:
1. Skip FileType header
2. Skip MediaData Header size and type fields, get raw AMR data only.
3. Create new file
4. Add AMR-header magic number – “#!AMR\n” (according to RFC-3267)
5. Copy all raw AMR data to file.
That’s it!
// #!AMR\n
private static byte[] AMR_MAGIC_HEADER = {0x23, 0x21, 0x41, 0x4d, 0x52, 0x0a};
 
public byte[] convert3gpDataToAmr(byte[] data) {
    if (data == null) {
        return null;
    }
 
    ByteArrayInputStream bis = new ByteArrayInputStream(data);
    // read FileTypeHeader
    FileTypeBox ftypHeader = new FileTypeBox(bis);
    // You can check if it is correct here
    // read MediaDataHeader
    MediaDataBox mdatHeader = new MediaDataBox(bis);
    // You can check if it is correct here
    int rawAmrDataLength = mdatHeader.getDataLength();
    int fullAmrDataLength = AMR_MAGIC_HEADER.length + rawAmrDataLength;
    byte[] amrData = new byte[fullAmrDataLength];
    System.arraycopy(AMR_MAGIC_HEADER, 0, amrData, 0, AMR_MAGIC_HEADER.length);
    bis.read(amrData, AMR_MAGIC_HEADER.length, rawAmrDataLength);
    return amrData;
}
Enjoy!

댓글 없음:

댓글 쓰기