AAC的实际时长和显示时长

Posted by RussXia on December 23, 2021

前因

使用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

参考