解决MediaMuxer编码AAC文件时间戳计算

原创文章,转载请联系作者

西北望乡何处是,东南见月几回圆。
https://github.com/JadynAi/MediaLearn/blob/feature-0.9/mediakit
昨风一吹无人会,今夜清光似往年。

主题

音频是流式数据,并不像视频一样有P帧和B帧的概念。就像砌墙一样,咔咔往上摞就行了。一般来说,AAC编码中生成文件这一步,如果使用的是OutputStream流写入文件的话,就完全不需要计算时间。但在音视频同步或者使用Android自带的MediaMuxer来生成音频文件时,就需要计算音频帧的时间戳。

参考

本文所涉及到的计算方法和API,为在Android环境下。使用AudioRecord音频录制,MediaCodeC编码AAC格式音频,同时使用MediaMuxer封装AAC格式音频文件。

方法

AAC编码有两种计算时间戳的方式。第一种:使用PCM的数据量来计算;第二种:计算出AAC编码相应参数配置下,一帧的持续时间,再配合帧数来计算。

AAC编码、MediaMuxer生成文件伪代码

MediaCodeC的AAC编码流程不再赘述,这里用伪代码来代替。主要是为了体现在代码何处设置时间戳:

1
2
3
4
5
6
7
8
// MediaCodeC获得可用输入队列
index = codeC.dequeueInputBuffer(......)
// 当获取到可用输出队列时,我们将获取的PCM数据填入
inputBuffer = codec.getInputBuffer(index)
// 将PCM数据(ByteArray)填充到InputBuffer
inputBuffer.put(byteAarray——PCM数据)
codec.queueInputBuffer(index, 0, byteArray的size
, presentationTimeUs, 0)

在以上的伪代码中,presentationTimeUs就是需要我们设置时间戳的地方

填充PCM数据后,在得到MediaCodeC输出后,使用MedaMuxer写入数据,生成AAC文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
path = 输出路径。后缀aac、或者mp4
mediaMuxer= MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)

mediaMuxer.addTrack(音频轨)
mediaMuxer.start()

// codec拿到可用的输出数据。这些数据就是AAC格式的音频数据
id = codec.dequeueOutputBuffer(bufferInfo, 10000)
if(id >= 0){
outputBuffer = codec.getOutputBuffer(id)
mediaMuxer.writeSamplet(audioTrack, outputBuffer, bufferInfo)
}

需要注意的是:使用MediaMuxer生成AAC音频文件时,不需要添加AAC头信息,直接写入即可。
MediaMuxer写入文件时,BufferInfo这个参数就包含了这一帧数据的偏移、以及时间戳等信息。

更加完整的音频编码代码,请参考GitHub地址AudioEncoder

使用PCM的数据量来计算

PCM是没有经过压缩的纯音频数据,我之前写过一篇音频入门的文章初识音频,记录了一些PCM相关的常识问题,感兴趣的可以去看看。
PCM作为最原始的音频数据,可以根据大小来计算出时间,先给出公式:

1
presentationTimeUs = 1000000L * (totalBytes / 2) / sampleRate

这是配置为采样率sampletRate、采样位数为16bit、单声道的PCM文件时间戳计算方式

接下来我们来分析以上公式的计算由来:

假设有一段PCM文件,采样率为S,采样位数为n–(一般 采样位数的选择有4bit、8bit、16bit、32bit),声道为单声道。那么在1s内,这段PCM的大小为:

1
2
size = S * n * 1,单位为bit

众所周知,1 Byte = 8bit, 1 Short = 16bit。那么单位时间内,PCM的大小为:

1
2
3
以byte为单位 = S * n * 1 / 8
以short为单位 = S * n * 1 / 16

那么根据以上就可得到,配置参数为采样率sampleRate、16bit、声道为1的PCM文件,当传入编码器的总大小达到totalByte时,时间戳的计算方式:

1
2
3
currents (微妙) = totalByte / (sampleRate * 16 * 1 / 8)
= totalByte / 2 / sampleRate * 1000000L

当然如果选择以ShortArray来承载PCM数据的话,那么公式则变为:

1
2
currents (微妙) = totalShort / (sampleRate * 16 * 1 / 16)
= totalShort / sampletRate * 1000000L

使用AAC帧时间计算

当编码器每输出一次数据,即可视作输出一帧AAC数据。一帧AAC原始数据包括1024个sample,那么AAC音频文件1s内的帧数为:sampleRate / 1024 帧。从而得到一帧AAC的持续时间为:

1
2
perFrameTime (微妙) = 1000000L / sampleRate / 1024



已知每一帧的持续时间的话,那么只需要根据当前帧数,即可计算出当前的时间戳。

以上