首页 > 其他分享 >AAC

AAC

时间:2024-07-07 20:34:30浏览次数:8  
标签:AAC inFormatCtx packet avformat ret av NULL

AAC 音频

AAC(Advanced Audio Coding),即高级音频编码,是一种专为 声音数据 设计的 文件压缩格式,具有以下优点:

  • 提升压缩率:以更小的文件大小获得更高的音质;
  • 支持多声道:可提供最多 48 个全音域声道;
  • 更高解析度:最高支持 96KHz 的采样频率;
  • 提升解码效率:解码播放所占的资源更少。

首先给出 ffmpeg 解封装命令,用于从音视频文件中提取 aac 音频流:

// 解封装, 提取 aac 音频流
ffmpeg -i ./水流众生.mp4 -vn -acodec aac ./水流众生.aac

// 使用 ffplay 播放
ffplay -hide_banner -autoexit ./水流众生.aac

然后给出代码实现:

#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include <stdio.h>

/**
 * step 1 : 打开媒体文件            avformat_open_input
 * step 2 : 获取码流信息            avformat_find_stream_info
 * step 3 : 获取音频流              av_find_best_stream
 * step 4 : 读取 packet 数据        av_read_frame
 * step 5 : 释放 packet 资源        av_packet_unref
 * step 6 : 关闭媒体文件            avformat_close_input
 */

int main(int argc, char* argv[])
{
    av_log_set_level(AV_LOG_DEBUG);

    if (argc != 3) {
        av_log(NULL, AV_LOG_ERROR, "Usage, %s infile outfile\n", argv[0]);
        exit(-1);
    }

    const char* infileName = argv[1];
    const char* outfileName = argv[2];

    AVFormatContext* inFormatCtx = NULL;

    int ret = avformat_open_input(&inFormatCtx, infileName, NULL, NULL);
    if (ret != 0) {
        av_log(NULL, AV_LOG_ERROR, "open input file format failed: %s\n", av_err2str(ret));
        exit(-1);
    }

    // 获取码流信息
    ret = avformat_find_stream_info(inFormatCtx, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "find stream info failed: %s\n", av_err2str(ret));
        avformat_close_input(&inFormatCtx);
        exit(-1);
    }

    // 获取音频流索引
    int audioIndex = av_find_best_stream(inFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if (audioIndex < 0) {
        av_log(NULL, AV_LOG_ERROR, "find best stream failed, index is %d\n", audioIndex);
        avformat_close_input(&inFormatCtx);
        exit(-1);
    }
    av_log(NULL, AV_LOG_INFO, "the audio index is %d\n", audioIndex);

    // 打开一个文件, 用于存储音频流数据
    FILE* dest_fp = fopen(outfileName, "wb");
    if (dest_fp == NULL) {
        av_log(NULL, AV_LOG_ERROR, "open %s file failed\n", outfileName);
        avformat_close_input(&inFormatCtx);
        exit(-1);
    }

    AVPacket packet;
    // 读取 packet 数据
    while (av_read_frame(inFormatCtx, &packet) == 0) {
        if (packet.stream_index == audioIndex) {
            ret = fwrite(packet.data, 1, packet.size, dest_fp);
            if (ret != packet.size) {
                av_log(NULL, AV_LOG_ERROR, "write file failed!\n");
                fclose(dest_fp);
                avformat_close_input(&inFormatCtx);
                exit(-1);
            }
        }
        // 释放 packet 数据
        av_packet_unref(&packet);
    }

    if (inFormatCtx) {
        avformat_close_input(&inFormatCtx);
    }

    if (dest_fp) {
        fclose(dest_fp);
    }

    return 0;
}

编译并运行上述代码,解封装测试文件(we_are_the_world.mp4):

使用 ffplay 播放:

提取出的 AAC 音频流无法正常播放。原因在于代码提取出的 AAC 没有包含用于解码的头部信息,下面将介绍 AAC 的两种格式。

AAC 编码存储格式

AAC 音频依据 流式传输 或者 存储 的需求存在多种格式,例如 ADTS(Audio Data Transport Stream)和 ASC(Audio Specific Config)。

1. ADTS 格式

ADTS(Audio Data Transport Stream),即音频数据传输流, 通常应用于 直播传输流,文件的每一帧前面都包含 ADTS 头信息。ADTS 格式一般用在裸 ADTS 封装中,或者在 MPEG2-TS 内用于流式传输。下图给出 ADTS 帧的构造方式:

下面通过代码构造 ADTS 头部信息:

static const int sampleFrequencyTable[] = {
    96000,
    88200,
    64000,
    48000,
    44100,
    32000,
    24000,
    22050,
    16000,
    12000,
    11025,
    8000,
    7350
};

static void setADTSHeader(char* adtsHeader, int packetSize, int profile, int sampleRate, int channels)
{
    int sampleFrequencyIndex = 3; // 默认使用 48000Hz
    int adtsLength = packetSize + 7;

    for (int i = 0; i < sizeof(sampleFrequencyTable) / sizeof(sampleFrequencyTable[0]); ++i) {
        if (sampleRate == sampleFrequencyTable[i]) {
            sampleFrequencyIndex = i;
            break;
        }
    }

    adtsHeader[0] = 0xff;           // syncword, 帧同步标识一个帧的开始. 固定为 0xfff, 使得解析器寻找头部非常方便
    adtsHeader[1] = 0xf0;           // syncword 同步头                                                 12 bits
    adtsHeader[1] |= (0 << 3);      // MPEG 标示符. 对于 MPEG-4 为 0, 对于 MPEG-2 为 1                   1 bit
    adtsHeader[1] |= (0 << 2);      // Layer: 固定为 '00'                                              2 bits
    
    // protection_absent: 表示是否进行CRC误码校验, 1 表示无CRC, 0 表示有CRC.   1 bit
    adtsHeader[1] |= 1;             

    // profile表示使用哪个级别的 AAC profile    2 bits
    // 1. AAC Main;  2. AAC LC(Low Complexity);  3. AAC SSR(Scalable Sample Rate);  4. AAC LTP(Long Term Prediction) 
    adtsHeader[2] = (profile << 6);

    // sampling frequency index, 标识使用的采样率的下标    4 bits
    adtsHeader[2] |= (sampleFrequencyIndex & 0x0f) << 2; 

    // 私有位, 编码时设置为 0, 解码时忽略     1 bit  
    adtsHeader[2] |= (0 << 1);

    // channel configuration, 标识声道数  3 bits
    adtsHeader[2] |= (channels & 0x04) >> 2;
    adtsHeader[3] = (channels & 0x03) << 6;         // channel configuration: channels

    // original_copy, 编码时设置为 0, 解码时忽略  1 bit
    adtsHeader[3] |= (0 << 5);

    // home, 编码时设置为 0, 解码时忽略  1 bit
    adtsHeader[3] |= (0 << 4);

    // copyright_identification_bit: 编码时设置为 0 , 解码时忽略   1 bit
    adtsHeader[3] |= (0 << 3);

    // copyright_identification_start: 编码时设置为 0, 解码时忽略  1 bit
    adtsHeader[3] |= (0 << 2);

    // aac_frame_lenght: ADTS 帧长度    13 bits
    // FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame)
    adtsHeader[3] |= ((adtsLength & 0x1800) >> 11);         // frame length
    adtsHeader[4] = (uint8_t)((adtsLength & 0x7f8) >> 3);   // frame length: value
    adtsHeader[5] = (uint8_t)((adtsLength & 0x7) << 5);     // frame length: value

    adtsHeader[5] |= 0x1f;                                  // buffer fullness:
    adtsHeader[6] = 0xfc;                                   // buffer fullness:
}

// ...
// 修改 main 函数中的 while 循环,将构造的 7 字节首部信息(不包含 CRC 校验)添加到 ADTS 裸流中。
while (av_read_frame(inFormatCtx, &packet) == 0) {
        if (packet.stream_index == audioIndex) {
            char adtsHeader[7] = {0};
            setADTSHeader(adtsHeader, packet.size, inFormatCtx->streams[audioIndex]->codecpar->profile,
                                inFormatCtx->streams[audioIndex]->codecpar->sample_rate,
                                inFormatCtx->streams[audioIndex]->codecpar->ch_layout.nb_channels);
            ret = fwrite(adtsHeader, 1, sizeof(adtsHeader), dest_fp);
            if (ret != sizeof(adtsHeader)) {
                av_log(NULL, AV_LOG_ERROR, "write file failed!\n");
                fclose(dest_fp);
                avformat_close_input(&inFormatCtx);
                exit(-1);
            }

            ret = fwrite(packet.data, 1, packet.size, dest_fp);
            if (ret != packet.size) {
                av_log(NULL, AV_LOG_ERROR, "write file failed!\n");
                fclose(dest_fp);
                avformat_close_input(&inFormatCtx);
                exit(-1);
            }
        }
        // 释放 packet 数据
        av_packet_unref(&packet);
}

编译并运行上述代码,解封装测试文件(we_are_the_world.mp4):

使用 ffplay 播放 AAC 文件:

AAC文件正常播放。

2. ASC 格式

ASC 通常 存储 在 MP4 格式中,在全局头部有一个配置,所以比 ADTS 更节省空间。ASC 格式通常用于 FLV、MOV/MP4。

参考资料

[1]. 《深入理解 FFmpeg》
[2]. 开课吧周李老师课程

标签:AAC,inFormatCtx,packet,avformat,ret,av,NULL
From: https://www.cnblogs.com/yizhikaohongshu/p/18287916

相关文章

  • VR-LLM-AAC
    VR-LLM-AAC方案测试测试一:汉字聚类hanzi_similar算法GithubKmeans算法hanzi_similar通过四角编码,汉字结构,偏旁部首,笔画数来判断两个汉字之间的相似度将权重调整为调高偏旁部首和汉字结构的权重根据任意两个汉字之间的相似度,通过Kmeans算法构建相似度矩阵,取得......
  • RTMP解析音频AAC
    我们知道AAC如果带ADTS头一般是FFF1或者FFF9,开头的,但是有些网络协议在传输AAC的时候是不带ADTS头的譬如:RTSP传输的媒体流中的AAC是被封装在RTP中的,此时的AAC是不带ADTS头的,而ADTS头里有音频参数信息;所以此时解析音频是依赖RTSP协议的SDP中的音频参数信息解码的;先介绍下AAC的AD......
  • 为何使用isaac gym做强化学习
    前言   本文仅对比Gazebo,Pybullet,IsaacGym三款仿真软件。详细对比可参考:Gazebo,Pybullet,IsaacGym用于强化学习训练对比-CSDN博客1仿真软件概述Gazebo:    Gazebo提供高保真的物理仿真,适合复杂的机器人模拟和实际应用中的验证。支持多种传感器和机器人模......
  • ffmpeg之视频(avc+aac)无损转mp4(批处理,拖放)
    很多能够无损转视频的工具都来自命令行的ffmpeg版本,本文将介绍如何简单的批处理方法(直接拖放到bat文件上)来实现无损转视频。工具/原料ffmpeg(默认的static版本)方法/步骤 1.桌面左下角开始菜单,点Windows附件→记事本。 2.复制本步骤以下全部内......
  • ffmpeg提取aac数据
    方法1:通过命令提取ffmpeg-iinput.mp4-vn-acodecaac../output.aac方法2:通过代码提取流程图  main.c#include"libavutil/log.h"#include"libavformat/avformat.h"#include"libavcodec/avcodec.h"intmain(intargc,char**argv){......
  • NVIDIA机器人仿真环境 —— NVIDIA Isaac Sim 的headless模式/无头模式 —— 非桌面模
    相关:https://developer.nvidia.com/isaac-sim可视化模式,也就是在桌面系统上直接安装软件,具体地址:https://developer.nvidia.com/isaac-sim无头模式则是使用docker安装,该种情况下不使用可视化界面,所有操作均在docker容器内,地址:https://catalog.ngc.nvidia.com/orgs/nvid......
  • NVIDIA的ROS项目 —— Isaac ROS
    文档地址:https://nvidia-isaac-ros.github.io/index.htmlGithub地址:https://github.com/NVIDIA-ISAAC-ROS......
  • NVIDIA的Isaac AMR产品介绍
    NVIDIA的IsaacAMR是仓库自动运货机器人项目,说直白些就是一个AGV的小车,不过和传统的AGV不同,NVIDIA推出的这个产品是智能化的。传统AGV小车的运行代码都是写死的,直接把运行命令写到了AGV里面,然后这个小车就只能按照预先的设定来进行运行,但是NVIDIA的这个产品是智能化的,是可以根据实......
  • 人形机器人 —— NVIDIA公司给出的操作算法(动态操作任务,dynamic manipulation tasks)(机
    原文:https://developer.nvidia.com/isaac/manipulator#foundation-modelsNVIDIA公司准备针对人形机器人的各部分操作分别推出一个AI框架,如:步态控制、3D感知、抓取操作、避障和规划,等等,本文介绍的就是NVIDIA计划推出的操作任务的算法的AI框架(manipulationtasks)。......
  • 记一次DataAccessException在代码中的处理
    有一天代码评审的时候发现我很多sql都会有一句抛异常DataAccessException。然后就这个异常跟我进行讨论,我觉得很多sql都会出现查不到数据的情况。一般来说表名不存在会抛这个异常。但是其实我们最开始就运行了建表语句,并且不是天表。所以并不会出现这种情况。因此特地查询了......