阅读 338

音视频 day14 PCM 编码压缩成 AAC

1. 如何查看 ffmpeg 中支持的 AAC 编解码器?

admindeMac-mini:$ ffmpeg -codecs | grep aac
DEAIL. aac                  AAC (Advanced Audio Coding) (decoders: aac aac_fixed aac_at libfdk_aac ) (encoders: aac aac_at libfdk_aac )
D.AIL. aac_latm             AAC LATM (Advanced Audio Coding LATM syntax)
复制代码

2. AVCodec 是一个重要的结构体,主要存储什么信息?

  • AVCodec是存储编解码器信息的结构体
  • 主要变量如下:
const char *name:编解码器的名字,比较短

const char *long_name:编解码器的名字,全称,比较长

enum AVMediaType type:指明了类型,是视频,音频,还是字幕

enum AVCodecID idID,不重复

const AVRational *supported_framerates:支持的帧率(仅视频)

const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)

const int *supported_samplerates:支持的采样率(仅音频)

const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)

const uint64_t *channel_layouts:支持的声道数(仅音频)

int priv_data_size:私有数据的大小
复制代码

3. AVFrame 是一个重要的结构体,主要存储什么信息?

  • AVFrame 用于存储编码前的数据(比如 PCM 等原始数据)
  • 主要变量如下:
uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)

int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。

int width, height:视频帧宽和高(1920x1080,1280x720...)

int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个

int format:解码后原始数据类型(YUV420,YUV422,RGB24...)

int key_frame:是否是关键帧

enum AVPictureType pict_type:帧类型(I,B,P...)

AVRational sample_aspect_ratio:宽高比(16:94:3...)

int64_t pts:显示时间戳

int coded_picture_number:编码帧序号

int display_picture_number:显示帧序号

int8_t *qscale_table:QP表

uint8_t *mbskip_table:跳过宏块表

int16_t (*motion_val[2])[2]:运动矢量表

uint32_t *mb_type:宏块类型表

short *dct_coeff:DCT系数,这个没有提取过

int8_t *ref_index[2]:运动估计参考帧列表(貌似H.264这种比较新的标准才会涉及到多参考帧)

int interlaced_frame:是否是隔行扫描

uint8_t motion_subsample_log2:一个宏块中的运动矢量采样个数,取log复制代码

4. AVPacket 是一个重要的结构体,主要存储什么信息?

  • AVPacket 是存储编码后的数据(比如 aac 等压缩后的数据)
  • 重要变量有如下几个
uint8_t *data:压缩编码的数据。

例如对于H.264来说。1个AVPacket的data通常对应一个NAL。

注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流

因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。

int   size:data的大小

int64_t pts:显示时间戳

int64_t dts:解码时间戳

int   stream_index:标识该AVPacket所属的视频/音频流。
复制代码

5. AVCodecContext 是一个重要的结构体,主要存储什么信息?

  • AVCodecContext 是包含变量较多的结构体,需要注意 AVCodecContext 中很多的参数是编码的时候使用的,而不是解码的时候使用的。
  • 重要变量有如下几个
enum AVMediaType codec_type:编解码器的类型(视频,音频...)

struct AVCodec  *codec:采用的解码器AVCodecH.264,MPEG2...)

int bit_rate:平均比特率

uint8_t *extradata; int extradata_size:针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)

AVRational time_base:根据该参数,可以把PTS转化为实际的时间(单位为秒s)

int width, height:如果是视频的话,代表宽和高

int refs:运动估计参考帧的个数(H.264的话会有多帧,MPEG2这类的一般就没有了)

int sample_rate:采样率(音频)

int channels:声道数(音频)

enum AVSampleFormat sample_fmt:采样格式

int profile:型(H.264里面就有,其他编码标准应该也有)

int level:级(和profile差不太多)
复制代码

6. 使用命令行将 PCM 编码成指定格式的 AAC,命令如下?然后用 ffproble out.acc 可以查看相关参数

ffmpeg -ar 44100 -ac 2 -f s16le -i 44100_s16le_2.pcm -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k out_terminal.aac

复制代码
admindeMac-mini:$ ffprobe out_terminal.aac
Input #0, aac, from 'out_terminal.aac':
Duration: 00:00:10.16, bitrate: 32 kb/s
 Stream #0:0: Audio: aac (HE-AACv2), 44100 Hz, stereo, fltp, 32 kb/s
复制代码

7. 请简述用代码实现 PCM 转 AAC 的关键步骤(5 个元素吧,重要)?

image.png

8. 使用代码实现和上面命令行一样的效果,主要代码在 ffmpegs.cpp 中,通过对比命令行和代码生成的 aac 文件大小,可以侧面印证代码的正确性。

#include "ffmpegs.h"
#include <QDebug>
#include <QFile>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}

#define ERROR_BUF(ret) \
 char errbuf[1024]; \
 av_strerror(ret, errbuf, sizeof (errbuf));

FFmpegs::FFmpegs()
{

}

// 检查采样格式
static int check_sample_fmt(const AVCodec *codec,
                         enum AVSampleFormat sample_fmt) {
 const enum AVSampleFormat *p = codec->sample_fmts;
 
 while (*p != AV_SAMPLE_FMT_NONE) {
//        qDebug() << "支持的采样格式 av_get_sample_fmt_name" << av_get_sample_fmt_name(*p);
     if (*p == sample_fmt) {
         return 1;
     }
     p++;
 }
 return 0;
}

// 音频编码
// 返回负数:中途出现了错误
// 返回 0:编码操作正常完成
static int encode(AVCodecContext *ctx,
               AVFrame *frame,
               AVPacket *pkt,
               QFile &outFile) {
 // 发送数据到编码器
 int ret = avcodec_send_frame(ctx, frame);
 if (ret < 0) {
     ERROR_BUF(ret);
     qDebug() << "avcodec_send_frame error" << errbuf;
     return ret;
 }
 
 // 不断从编码器中取出编码后的数据
 // while (ret > 0)
 while (true) {
     ret = avcodec_receive_packet(ctx, pkt);
     if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
         // 继续读取数据到 frame,然后送到编码器
         return 0;
     } else if (ret < 0) { // 其他错误
         return ret;
     }
     
     // 成功从编码器拿到编码后的数据
     // 将编码后的数据写入文件
     outFile.write((char *)pkt->data, pkt->size);
     
     // 释放 pkt 内部资源
     av_packet_unref(pkt);
 }
}



void FFmpegs::accEncode(AudioEncodeSpec &in, const char *outFilename) {
 // 文件
 QFile inFile(in.filename);
 QFile outFile(outFilename);
 
 // 返回结果
 int ret = 0;
 
 // 编码器
 AVCodec *codec = nullptr;
 
 // 编码上下文
 AVCodecContext *ctx = nullptr;
 
 // 存放编码前的数据(PCM)
 AVFrame *frame = nullptr;
 
 // 存放编码后的数据(AAC)
 AVPacket *pkt = nullptr;
 
 // 获取编码器
 codec = avcodec_find_encoder_by_name("libfdk_aac");
 if (!codec) {
     qDebug() << "encoder not found";
     return;
 }
 
 // libfdk_aac 对输入数据的要求:采样格式必须是 16 位整数
 // 检查输入数据的采样格式
 if (!check_sample_fmt(codec, in.sampleFmt)) {
     qDebug() << "unsupported sample format"
              << av_get_sample_fmt_name(in.sampleFmt);
     return;
 }
 
 // 创建编码上下文
 ctx = avcodec_alloc_context3(codec);
 if (!ctx) {
     qDebug() << "avcodec_alloc_context3 error" ;
     return;
 }
 
 // 设置 PCM 参数
 ctx->sample_rate = in.sampleRate;
 ctx->sample_fmt = in.sampleFmt;
 ctx->channel_layout = in.chLayout;
 
 // 比特率
 ctx->bit_rate = 32000;
 // 规格
 ctx->profile = FF_PROFILE_AAC_HE_V2;
 
 // 打开编码器
 ret = avcodec_open2(ctx, codec, nullptr);
 if (ret < 0) {
     ERROR_BUF(ret);
     qDebug() << "avcodec_open2 error" << errbuf;
     goto end;
 }
 
 // 创建 AVFrame
 frame = av_frame_alloc();
 if (!frame) {
     qDebug() << "av_frame_alloc error";
     goto end;
 }
 
 // frame 缓冲区中的样本帧数量(由 ctx->frame_size 决定)
 frame->nb_samples = ctx->frame_size;
 frame->format = ctx->sample_fmt;
 frame->channel_layout = ctx->channel_layout;
 
 // 利用 nb_samples、format、channel_layout 创建缓冲区
 ret = av_frame_get_buffer(frame, 0);
 if (ret) {
     ERROR_BUF(ret);
     qDebug() << "av_frame_get_buffer error" << errbuf;
     goto end;
 }
 
 // 创建 AVPacket
 pkt = av_packet_alloc();
 if (!pkt) {
     qDebug() << "av_packet_alloc error";
     goto end;
 }
 
 // 打开文件
 if (!inFile.open(QFile::ReadOnly)) {
     qDebug() << "file open error" << in.filename;
     goto end;
 }
 if (!outFile.open(QFile::WriteOnly)) {
     qDebug() << "file open error" << outFilename;
     goto end;
 }
 
 // 获取数据到 frame 中
 while ((ret = inFile.read((char *)frame->data[0],
                           frame->linesize[0])) > 0) {
     // 从文件中读取的数据,不足以填满 frame 缓冲区
     if (ret < frame->linesize[0]) {
         int bytes = av_get_bytes_per_sample((AVSampleFormat)frame->format);
         int ch = av_get_channel_layout_nb_channels(frame->channel_layout);
         // 设置真正有效的样本帧梳理
         // 防止编码器编码了一些冗余数据
         frame->nb_samples = ret/(bytes * ch);
     }
     
     // 进行编码
     if (encode(ctx, frame, pkt, outFile)) {
         goto end;
     }
     
 }
 
 // 刷新缓冲区
 encode(ctx, nullptr, pkt, outFile);
 
 
end:
 // 关闭文件
 inFile.close();
 outFile.close();

 // 释放资源
 av_frame_free(&frame);
 av_packet_free(&pkt);
 avcodec_free_context(&ctx);

 qDebug() << "线程正常结束";
}

复制代码
文章分类
iOS