前因
使用ffmpeg查看aac文件时长时,发现文件时长为 Duration: 00:39:50.18, bitrate: 66 kb/s
。
$-> ffmpeg -i out.aac
[aac @ 0x7fba1800be00] Estimating duration from bitrate, this may be inaccurate
Input #0, aac, from 'out.aac':
Duration: 00:39:50.18, bitrate: 66 kb/s
Stream #0:0: Audio: aac (HE-AACv2), 44100 Hz, stereo, fltp, 66 kb/s
At least one output file must be specified
根据这个原始AAC音频文件,仅做封装m4a处理。
$-> ffmpeg -i out.aac -c:a copy -f mp4 out.m4a
[aac @ 0x7f9770019e00] Estimating duration from bitrate, this may be inaccurate
Input #0, aac, from 'out.aac':
Duration: 00:39:50.18, bitrate: 66 kb/s
Stream #0:0: Audio: aac (HE-AACv2), 44100 Hz, stereo, fltp, 66 kb/s
Output #0, mp4, to 'out.m4a':
Metadata:
encoder : Lavf58.76.100
Stream #0:0: Audio: aac (HE-AACv2) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 66 kb/s
Stream mapping:
Stream #0:0 -> #0:0 (copy)
Press [q] to stop, [?] for help
size= 19115kB time=00:40:20.44 bitrate= 64.7kbits/s speed=8.67e+03x
video:0kB audio:19266kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
封装处理后,查看音频时长,发现 ` Duration: 00:40:20.50, start: 0.000000, bitrate: 64 kb/s`。
$-> ffmpeg -i out.m4a
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'out.m4a':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf58.76.100
Duration: 00:40:20.50, start: 0.000000, bitrate: 64 kb/s
Stream #0:0(und): Audio: aac (HE-AACv2) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 64 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
At least one output file must be specified
出现误差的原因
可以看到,在使用 ffmpeg
查看aac时长时, 会提示Estimating duration from bitrate, this may be inaccurate
。
对于音频文件,一帧的播放时间Duartion = Samples(采样数) * 1000(ms) / sample_rate(采样率)
几种常见格式的采样数
音频格式 | 采样数量 |
---|---|
AAC-HE V1/V2 | 2048 |
AAC-LC | 1024 |
AAC-LD/AAC-ELD | 480/512/1024 |
MP3 | 1152 |
所以对于AAC-LC格式的音频来说,一帧的播放时长是:
1024(samples)*1000(ms)/44100(hz)= 22.32ms(单位为ms)
ffmpeg可以根据文件大小/比特率,大致估算出音频时长。又因为存在vbr动态码率的存在,当获取不到bit_rate时,ffmpeg会计算出一个大概的bit_rate。这个结果只是一个估算的结果,所以会存在误差。
static void estimate_timings_from_bit_rate(AVFormatContext *ic)
{
FFFormatContext *const si = ffformatcontext(ic);
int show_warning = 0;
//如果设定了bit_rate,则直接采用;否则计算出一个大概的bit_rate
/* if bit_rate is already set, we believe it */
if (ic->bit_rate <= 0) {
int64_t bit_rate = 0;
for (unsigned i = 0; i < ic->nb_streams; i++) {
const AVStream *const st = ic->streams[i];
const FFStream *const sti = cffstream(st);
if (st->codecpar->bit_rate <= 0 && sti->avctx->bit_rate > 0)
st->codecpar->bit_rate = sti->avctx->bit_rate;
if (st->codecpar->bit_rate > 0) {
if (INT64_MAX - st->codecpar->bit_rate < bit_rate) {
bit_rate = 0;
break;
}
bit_rate += st->codecpar->bit_rate;
} else if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && sti->codec_info_nb_frames > 1) {
// If we have a videostream with packets but without a bitrate
// then consider the sum not known
bit_rate = 0;
break;
}
}
ic->bit_rate = bit_rate;
}
//如果设置了duration,直接取设置的duration;如果没有设置,根据bit_rate和文件大小,估算出一个大概的duration
/* if duration is already set, we believe it */
if (ic->duration == AV_NOPTS_VALUE &&
ic->bit_rate != 0) {
int64_t filesize = ic->pb ? avio_size(ic->pb) : 0;
if (filesize > si->data_offset) {
filesize -= si->data_offset;
for (unsigned i = 0; i < ic->nb_streams; i++) {
AVStream *const st = ic->streams[i];
if ( st->time_base.num <= INT64_MAX / ic->bit_rate
&& st->duration == AV_NOPTS_VALUE) {
st->duration = av_rescale(filesize, 8LL * st->time_base.den,
ic->bit_rate *
(int64_t) st->time_base.num);
show_warning = 1;
}
}
}
}
if (show_warning)
//就是这个地方打印的警告日志!!!
av_log(ic, AV_LOG_WARNING,
"Estimating duration from bitrate, this may be inaccurate\n");
}
如何获取实际的播放时长
方法一:使用ffmpeg解码音频
ffmpeg -i input.aac -f null -
方法二:将aac文件封装成m4a格式
ffmpeg -i input.aac -c:a copy -f mp4 output.m4a
使用m4a作为容器,查看m4a文件信息时,时长会准确一些。猜测可能和m4a的封装协议有关(可能header有存储转码时计算出来的实际duration?),但是网上没找到具体讲解的,这里暂时记录一下。<!- todo ->
关于AAC格式
AAC是符合MPEG-4定义的有损压缩音频,它有ADIF和ADTS两种格式。
- ADIF是一个header + raw_data_stream();所以必须从头开始解码,不适用于广播电视等流式播放
- ADTS可以在任意处解码,{[adts header]+[aac es]}0…n