Android音视频处理之AAC编码

AAC是音频的一种编码格式,AAC通常压缩比为18:1,也有资料说为20:1,远胜mp3。

AAC音频格式有ADIF和ADTS:

ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。

ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。

简单说,ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。且这两种的header的格式也是不同的,目前一般编码后的和抽取出的都是ADTS格式的音频流。

ADTS是帧序列,本身具备流特征,在音频流的传输与处理方面更加合适。

下面我们对ADTS进行分析:

ADTS AAC
ADTS_header AAC ES ADTS_header AAC ES ... ADTS_header AAC ES

可以看到ADTS的每一帧都有头信息,即ADTS_header,ADTS头中相对有用的信息是采样率、声道数、帧长度。一般ADTS头信息都是7字节,如果有CRC则为9字节。

ADTS帧首部结构:
序号 长度(bits) 说明
1 Syncword 12 all bits must be 1
2 MPEG version 1 0 for MPEG-4, 1 for MPEG-2
3 Layer 2 always 0
4 Protection Absent 1 et to 1 if there is no CRC and 0 if there is CRC
5 Profile 2 the MPEG-4 Audio Object Type minus 1
6 MPEG-4 Sampling Frequency Index 4 MPEG-4 Sampling Frequency Index (15 is forbidden)
7 Private Stream 1 set to 0 when encoding, ignore when decoding
8 MPEG-4 Channel Configuration 3 MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE)
9 Originality 1 set to 0 when encoding, ignore when decoding
10 Home 1 set to 0 when encoding, ignore when decoding
11 Copyrighted Stream 1 set to 0 when encoding, ignore when decoding
12 Copyrighted Start 1 set to 0 when encoding, ignore when decoding
13 Frame Length 13 this value must include 7 or 9 bytes of header length: FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame)
14 Buffer Fullness 11 buffer fullness
15 Number of AAC Frames 2 number of AAC frames (RDBs) in ADTS frame minus 1, for maximum compatibility always use 1 AAC frame per ADTS frame
16 CRC 16 CRC if protection absent is 0

ADTS头部的生成:
/**
 * 添加ADTS头部
 *
 * @param packet    ADTS header 的 byte[],长度为7
 * @param packetLen 该帧的长度,包括header的长度
 */
private void addADTStoPacket(byte[] packet, int packetLen) {
    int profile = 2; // AAC LC
    int freqIdx = 3; // 48000Hz
    int chanCfg = 2; // 2 Channel

    packet[0] = (byte) 0xFF;
    packet[1] = (byte) 0xF9;
    packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
    packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
    packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
    packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
    packet[6] = (byte) 0xFC;
}

其中profile表示使用哪个级别的AAC,在MPEG-2 AAC中定义了3种:

AAC三种级别

freqIdx表示使用的采样率下标,通过这个下标在 Sampling Frequencies[ ]数组中查找得知采样率的值:

  • 0: 96000 Hz
  • 1: 88200 Hz
  • 2: 64000 Hz
  • 3: 48000 Hz
  • 4: 44100 Hz
  • 5: 32000 Hz
  • 6: 24000 Hz
  • 7: 22050 Hz
  • 8: 16000 Hz
  • 9: 12000 Hz
  • 10: 11025 Hz
  • 11: 8000 Hz
  • 12: 7350 Hz
  • 13: Reserved
  • 14: Reserved
  • 15: frequency is written explictly

chanCfg表示声道数:

  • 0: Defined in AOT Specifc Config
  • 1: 1 channel: front-center
  • 2: 2 channels: front-left, front-right
  • 3: 3 channels: front-center, front-left, front-right
  • 4: 4 channels: front-center, front-left, front-right, back-center
  • 5: 5 channels: front-center, front-left, front-right, back-left, back-right
  • 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
  • 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
  • 8-15: Reserved

AAC的解析:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

public class AACHelper {
    // 采样频率对照表
    private static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<>();

    static {
        samplingFrequencyIndexMap.put(96000, 0);
        samplingFrequencyIndexMap.put(88200, 1);
        samplingFrequencyIndexMap.put(64000, 2);
        samplingFrequencyIndexMap.put(48000, 3);
        samplingFrequencyIndexMap.put(44100, 4);
        samplingFrequencyIndexMap.put(32000, 5);
        samplingFrequencyIndexMap.put(24000, 6);
        samplingFrequencyIndexMap.put(22050, 7);
        samplingFrequencyIndexMap.put(16000, 8);
        samplingFrequencyIndexMap.put(12000, 9);
        samplingFrequencyIndexMap.put(11025, 10);
        samplingFrequencyIndexMap.put(8000, 11);
        samplingFrequencyIndexMap.put(0x0, 96000);
        samplingFrequencyIndexMap.put(0x1, 88200);
        samplingFrequencyIndexMap.put(0x2, 64000);
        samplingFrequencyIndexMap.put(0x3, 48000);
        samplingFrequencyIndexMap.put(0x4, 44100);
        samplingFrequencyIndexMap.put(0x5, 32000);
        samplingFrequencyIndexMap.put(0x6, 24000);
        samplingFrequencyIndexMap.put(0x7, 22050);
        samplingFrequencyIndexMap.put(0x8, 16000);
        samplingFrequencyIndexMap.put(0x9, 12000);
        samplingFrequencyIndexMap.put(0xa, 11025);
        samplingFrequencyIndexMap.put(0xb, 8000);
    }

    private AdtsHeader mAdtsHeader = new AdtsHeader();
    private BitReader mHeaderBitReader = new BitReader(new byte[7]);
    private byte[] mSkipTwoBytes = new byte[2];
    private FileInputStream mFileInputStream;
    private byte[] mBytes = new byte[1024];

    /**
     * 构造函数,通过传递进来的文件路径创建输入流
     *
     * @param aacFilePath AAC文件路径
     * @throws FileNotFoundException
     */
    public AACHelper(String aacFilePath) throws FileNotFoundException {
        mFileInputStream = new FileInputStream(aacFilePath);
    }

    /**
     * 获取下一Sample数据
     *
     * @param byteBuffer 存放Sample数据的ByteBuffer
     * @return 当前Sample的byte[]大小,如果为空返回-1
     * @throws IOException
     */
    public int getSample(ByteBuffer byteBuffer) throws IOException {
        if (readADTSHeader(mAdtsHeader, mFileInputStream)) {
            int length = mFileInputStream.read(mBytes, 0, mAdtsHeader.frameLength - mAdtsHeader.getSize());
            byteBuffer.clear();
            byteBuffer.put(mBytes, 0, length);
            byteBuffer.position(0);
            byteBuffer.limit(length);
            return length;
        }
        return -1;
    }

    /**
     * 从AAC文件流中读取ADTS头部
     *
     * @param adtsHeader      ADTS头部
     * @param fileInputStream AAC文件流
     * @return 是否读取成功
     * @throws IOException
     */
    private boolean readADTSHeader(AdtsHeader adtsHeader, FileInputStream fileInputStream) throws IOException {
        if (fileInputStream.read(mHeaderBitReader.buffer) < 7) {
            return false;
        }

        mHeaderBitReader.position = 0;

        int syncWord = mHeaderBitReader.readBits(12); // A
        if (syncWord != 0xfff) {
            throw new IOException("Expected Start Word 0xfff");
        }
        adtsHeader.mpegVersion = mHeaderBitReader.readBits(1); // B
        adtsHeader.layer = mHeaderBitReader.readBits(2); // C
        adtsHeader.protectionAbsent = mHeaderBitReader.readBits(1); // D
        adtsHeader.profile = mHeaderBitReader.readBits(2) + 1;  // E
        adtsHeader.sampleFrequencyIndex = mHeaderBitReader.readBits(4);
        adtsHeader.sampleRate = samplingFrequencyIndexMap.get(adtsHeader.sampleFrequencyIndex); // F
        mHeaderBitReader.readBits(1); // G
        adtsHeader.channelconfig = mHeaderBitReader.readBits(3); // H
        adtsHeader.original = mHeaderBitReader.readBits(1); // I
        adtsHeader.home = mHeaderBitReader.readBits(1); // J
        adtsHeader.copyrightedStream = mHeaderBitReader.readBits(1); // K
        adtsHeader.copyrightStart = mHeaderBitReader.readBits(1); // L
        adtsHeader.frameLength = mHeaderBitReader.readBits(13); // M
        adtsHeader.bufferFullness = mHeaderBitReader.readBits(11); // 54
        adtsHeader.numAacFramesPerAdtsFrame = mHeaderBitReader.readBits(2) + 1; // 56
        if (adtsHeader.numAacFramesPerAdtsFrame != 1) {
            throw new IOException("This muxer can only work with 1 AAC frame per ADTS frame");
        }
        if (adtsHeader.protectionAbsent == 0) {
            fileInputStream.read(mSkipTwoBytes);
        }
        return true;
    }

    /**
     * 释放资源
     *
     * @throws IOException
     */
    public void release() throws IOException {
        mFileInputStream.close();
    }

    /**
     * ADTS头部
     */
    private class AdtsHeader {
        int getSize() {
            return 7 + (protectionAbsent == 0 ? 2 : 0);
        }

        int sampleFrequencyIndex;

        int mpegVersion;
        int layer;
        int protectionAbsent;
        int profile;
        int sampleRate;

        int channelconfig;
        int original;
        int home;
        int copyrightedStream;
        int copyrightStart;
        int frameLength;
        int bufferFullness;
        int numAacFramesPerAdtsFrame;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容