音视频入门基础:AAC专题(5)——FFmpeg源码中,判断某文件是否为AAC裸流文件的实现

一、引言

通过FFmpeg命令:

cpp 复制代码
./ffmpeg -i XXX.aac

可以判断出某个文件是否为AAC裸流文件:

所以FFmpeg是怎样判断出某个文件是否为AAC裸流文件呢?它内部其实是通过adts_aac_probe函数来判断的。从《FFmpeg源码:av_probe_input_format3函数和AVInputFormat结构体分析(FFmpeg源码5.0.3版本)》和《7.0.1版本的FFmpeg源码中av_probe_input_format3函数和AVInputFormat结构体的改变》中我们可以知道:

FFmpeg源码中实现容器格式检测的函数是av_probe_input_format3函数,其内部通过循环while ((fmt1 = av_demuxer_iterate(&i))) 拿到所有容器格式对应的AVInputFormat结构,然后通过score = fmt1->read_probe(&lpd)语句执行不同容器格式对应的解析函数,根据是否能被解析,以及匹配程度,来判断出这是哪种容器格式。而AAC裸流文件对应的解析函数就是adts_aac_probe函数。

二、adts_aac_probe函数的定义

adts_aac_probe函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/aacdec.c中:

cpp 复制代码
static int adts_aac_probe(const AVProbeData *p)
{
    int max_frames = 0, first_frames = 0;
    int fsize, frames;
    const uint8_t *buf0 = p->buf;
    const uint8_t *buf2;
    const uint8_t *buf;
    const uint8_t *end = buf0 + p->buf_size - 7;

    buf = buf0;

    for (; buf < end; buf = buf2 + 1) {
        buf2 = buf;

        for (frames = 0; buf2 < end; frames++) {
            uint32_t header = AV_RB16(buf2);
            if ((header & 0xFFF6) != 0xFFF0) {
                if (buf != buf0) {
                    // Found something that isn't an ADTS header, starting
                    // from a position other than the start of the buffer.
                    // Discard the count we've accumulated so far since it
                    // probably was a false positive.
                    frames = 0;
                }
                break;
            }
            fsize = (AV_RB32(buf2 + 3) >> 13) & 0x1FFF;
            if (fsize < 7)
                break;
            fsize = FFMIN(fsize, end - buf2);
            buf2 += fsize;
        }
        max_frames = FFMAX(max_frames, frames);
        if (buf == buf0)
            first_frames = frames;
    }

    if (first_frames >= 3)
        return AVPROBE_SCORE_EXTENSION + 1;
    else if (max_frames > 100)
        return AVPROBE_SCORE_EXTENSION;
    else if (max_frames >= 3)
        return AVPROBE_SCORE_EXTENSION / 2;
    else if (first_frames >= 1)
        return 1;
    else
        return 0;
}

其作用就是检测某个文件是否为AAC裸流文件。由于通过FFmpeg命令(通过《音视频入门基础:AAC专题(2)------使用FFmpeg命令生成AAC裸流文件》)生成的AAC裸流文件都是ADTS格式的,所以adts_aac_probe函数只能用于检测某个文件是否为ADTS格式的AAC裸流,不能用于检测是否为AAC的ADIF格式。

形参pd:输入型参数,为AVProbeData类型的指针。

AVProbeData结构体声明在libavformat/avformat.h中:

cpp 复制代码
/**
 * This structure contains the data a format has to probe a file.
 */
typedef struct AVProbeData {
    const char *filename;
    unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
    int buf_size;       /**< Size of buf except extra allocated bytes */
    const char *mime_type; /**< mime_type, when known. */
} AVProbeData;

p->filename为:需要被推测格式的文件的路径。

p->buf:指向"存放从路径为p->filename的文件(AAC裸流文件)中读取出来的二进制数据"的缓冲区。

p->buf_size:缓冲区p->buf的大小,单位为字节。注:FFmpeg判断某个文件的格式时不会读取完整个文件,只会读取它前面的一部分,比如最开始的2048个字节。只要根据前面的这些字节就足够判断出它的格式了,所以p->buf_size的值一般就是2048。

p->mime_type:一般为NULL,可忽略。

返回值:返回一个类型为整形的分值。返回0表示该文件完全不符合AAC的ADTS格式。返回一个大于0的值表示该文件比较符合AAC的ADTS格式,但还需要在av_probe_input_format3函数中执行其它容器格式对应的解析函数来进行对比,最终通过最高分来确定到底是哪种容器格式。

三、adts_aac_probe函数的内部实现原理

adts_aac_probe函数内部,首先定义局部变量fsize来记录某个ADTS音频帧的长度;定义局部变量frames记录该AAC裸流文件前2048个字节(因为p->buf_size的值一般就是2048)中的有效音频帧的个数:

cpp 复制代码
int fsize, frames;

让指针buf2指向"AAC裸流文件二进制数据"的开头,也就是第一个ADTS音频帧的adts_fixed_header:

cpp 复制代码
    for (; buf < end; buf = buf2 + 1) {
        buf2 = buf;

按照大端模式读取第一个ADTS音频帧的前2个字节,赋值给变量header。关于AV_RB16宏定义的用法可以参考:《FFmpeg源码:AV_RB32、AV_RB16、AV_RB8宏定义分析》:

cpp 复制代码
uint32_t header = AV_RB16(buf2);

由《音视频入门基础:AAC专题(3)------AAC的ADTS格式简介》可以知道,ADTS音频帧的adts_fixed_header中的syncword属性占12位,每个位都必须被设置为1;layer属性占2位,必须被设置为0。所以通过下面代码块判断syncword和layer属性的值是否正确。如果表达式:header & 0xFFF6) != 0xFFF0为真,表示这两个属性的值不正确,即表示ADTS Header格式不正确,让变量frames的值归0,表示有效音频帧的个数归0:

cpp 复制代码
            if ((header & 0xFFF6) != 0xFFF0) {
                if (buf != buf0) {
                    // Found something that isn't an ADTS header, starting
                    // from a position other than the start of the buffer.
                    // Discard the count we've accumulated so far since it
                    // probably was a false positive.
                    frames = 0;
                }
                break;
            }

获取adts_variable_header中的aac_frame_length属性,即该ADTS音频帧的总长度(包含ADTS Header、错误校验和AAC原始数据块,单位为字节)。赋值给变量fsize:

cpp 复制代码
fsize = (AV_RB32(buf2 + 3) >> 13) & 0x1FFF;

由《音视频入门基础:AAC专题(3)------AAC的ADTS格式简介》可以知道,ADTS Header至少占7个字节(当存在CRC校验时,ADTS Header占9字节;不存在CRC校验时,ADTS Header占7字节),所以如果从上面得到的该ADTS音频帧的总长度小于7,表示ADTS Header格式不正确,通过break关键字跳出循环:

cpp 复制代码
            if (fsize < 7)
                break;

让指针buf2指向下一个ADTS音频帧的adts_fixed_header:

cpp 复制代码
buf2 += fsize;

如果该音频帧的ADTS Header格式正确,让frames的值(有效音频帧的个数)加1。执行for循环,继续判断下一个ADTS音频帧的Header的格式是否正确:

cpp 复制代码
for (frames = 0; buf2 < end; frames++) {

buf等于buf0,意味着读取到ADTS音频帧的Header的格式都是正确的,让first_frames的值等于frames:

cpp 复制代码
        max_frames = FFMAX(max_frames, frames);
        if (buf == buf0)
            first_frames = frames;

如果该AAC裸流文件前2048个字节中的有效音频帧的个数不小于3个,adts_aac_probe函数返回AVPROBE_SCORE_EXTENSION + 1(也就是返回51分),意味着该文件比较符合AAC的ADTS格式:

cpp 复制代码
    if (first_frames >= 3)
        return AVPROBE_SCORE_EXTENSION + 1;
相关推荐
cuijiecheng201810 小时前
音视频入门基础:AAC专题(4)——ADTS格式的AAC裸流实例分析
音视频·aac
wly47692308319 小时前
ChatGPT 有什么新奇的使用方式?
人工智能·gpt·chatgpt·ai作画·区块链·音视频
0点51 胜19 小时前
[ffmpeg] 音视频编码
ffmpeg·音视频
DisonTangor21 小时前
DepthCrafter:为开放世界视频生成一致的长深度序列
人工智能·计算机视觉·音视频
0点51 胜1 天前
[ffmpeg]音频格式转换
开发语言·c++·ffmpeg
中式代码美式咖啡1 天前
记录开发一个英语听力训练网站
java·spring boot·bootstrap·音视频·语音识别
神一样的老师1 天前
使用卷积神经网络进行人类活动识别的特征学习:惯性测量单元和音频数据的案例研究
学习·cnn·音视频
Kuekua-seu2 天前
文生视频算法
算法·音视频
PlumCarefree2 天前
基于鸿蒙API10的RTSP播放器(五:拖动底部视频滑轨实现跳转)
华为·ffmpeg·音视频
LuckyInn2 天前
从安装ffmpeg开始,把一个视频按照每秒30帧fps剪切为图片
ffmpeg·音视频