写点什么

初学 ffmpeg 打卡 day2

用户头像
糖米唐爹
关注
发布于: 刚刚
初学 ffmpeg 打卡 day2

一,前言

本文分为三部分,第一部分介绍 aac 的基本概念, 第二部分对 ffmpeg 主要的数据结构进行概述,第三部分通过对 pcm 编码为 aac 的案例进行分析,尝试去理解 ffmpeg 音频编码的流程。


二, AAC 概述


1, AAC 的两种格式

AAC 全称 Advanced Audio Coding,有 ADIF 和 ADTS 两种。

ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。


ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于 mp3 数据流格式。简单说,ADTS 可以在任意帧解码,也就是说它每一帧都有头信息。


2, ADTS 解析



ADTS 头包含了 AAC 文件的采样率、通道数、帧数据长度等信息。ADTS 头分为固定头信息和可变头信息两个部分,固定头信息在每个帧中的是一样的,可变头信息在各个帧中并不是固定值。


2.1,固定头信息

  • syncword:帧同步标识一个帧的开始,固定为 0xFFF

  • ID:MPEG 标示符。0 表示 MPEG-4,1 表示 MPEG-2

  • layer:固定为'00'

  • protection_absent:标识是否进行误码校验。0 表示有 CRC 校验,1 表示没有 CRC 校验

  • profile:标识使用哪个级别的 AAC。

  • sampling_frequency_index:标识使用的采样率的下标。

  • private_bit:私有位,编码时设置为 0,解码时忽略

  • channel_configuration:标识声道数。

  • original_copy:编码时设置为 0,解码时忽略

  • home:编码时设置为 0,解码时忽略


有关 profile,sampling_frequency_index 和 channel_configuration 的详细信息见wiki


2.2,可变头信息

  • copyrighted_id_bit:编码时设置为 0,解码时忽略

  • copyrighted_id_start:编码时设置为 0,解码时忽略

  • aac_frame_length:ADTS 帧长度包括 ADTS 长度和 AAC 声音数据长度的和。即 aac_frame_length = (protection_absent == 0 ? 9 : 7) + audio_data_length

  • adts_buffer_fullness:固定为 0x7FF。表示是码率可变的码流

  • number_of_raw_data_blocks_in_frame:表示当前帧有 number_of_raw_data_blocks_in_frame + 1 个原始帧(一个 AAC 原始帧包含一段时间内 1024 个采样及相关数据)。


2.3,ADTS 解析


typedef struct {    unsigned int syncword : 12; // 0XFFF    unsigned int ID : 1; // MPEG version: 0 for MPEG-4, 1 for MPEG-2    unsigned int layer : 2; // always 00    unsigned int protection_absent : 1;    unsigned int profile : 2; // 0-main,1-LC, 2-SSR,3-reserved    /**     *  0: 96000 Hz     *  1: 88200 Hz     *  2: 64000 Hz     *  3: 48000 Hz     *  4: 44100 Hz     *  5: 32000 Hz     *  6: 24000 Hz     *  7: 22050 Hz     *  8: 16000 Hz     *  9: 12000 Hz     *  10: 11025 Hz     *  11: 8000 Hz     *  12: 7350 Hz     *  13: Reserved     *  14: Reserved     *  15: frequency is written explictly     */    unsigned int sampling_frequency_index : 4;    unsigned int private_bit : 1;    /**     *  0: Defined in AOT Specifc Config     *  1: 1 channel: front-center     *  2: 2 channels: front-left, front-right     *  3: 3 channels: front-center, front-left, front-right     *  4: 4 channels: front-center, front-left, front-right, back-center     *  5: 5 channels: front-center, front-left, front-right, back-left, back-right     *  6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel     *  7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel     *  8-15: Reserved     */    unsigned int channel_configuration : 3;    unsigned int original_copy : 1;    unsigned int home : 1;    unsigned int copyright_identification_bit : 1;    unsigned int copyright_identification_start : 1;    unsigned int aac_frame_length : 13;    unsigned int adts_buffer_fullness : 11;    unsigned int number_of_raw_data_blocks_in_frame : 2;} adts__fixed_head;

typedef struct { unsigned copyright_identification_bit : 1; unsigned copyright_identification_start : 1; unsigned aac_frame_length : 13; unsigned adts_buffer_fullness : 11; unsigned number_of_raw_data_blocks_in_frame : 2;} adts_variable_header;
int ParseAdts(){ FILE* fp = fopen(output_file.c_str(), "rb"); adts__fixed_head adtsHead; adts_variable_header var_header; unsigned char adts_buffer[7] = { 0 }; int nLen = fread(adts_buffer, 1, 7, fp); if (nLen < 7) return -1;
if (adts_buffer[0] == 0xFF && (adts_buffer[1]&0xf0)==0xf0) { // parse fixed head adtsHead.ID = (adts_buffer[1]&0x08) >> 3; adtsHead.layer = (adts_buffer[1] & 0x06) >> 1; adtsHead.protection_absent = (adts_buffer[1] & 0x1); adtsHead.profile = (adts_buffer[2] & 0xc0) >> 6; adtsHead.sampling_frequency_index = (adts_buffer[2] & 0x3c) >> 2; adtsHead.private_bit = (adts_buffer[2] & 0x02) >> 1; adtsHead.channel_configuration = (((adts_buffer[2] & 0x01)) << 2) | ((adts_buffer[3] & 0xc0) >> 6); adtsHead.original_copy = (adts_buffer[3] & 0x20) >> 5; adtsHead.home = (adts_buffer[3] & 0x10) >> 4;
// parase variable header var_header.copyright_identification_bit = (adts_buffer[3] & 0x08) >> 3; var_header.copyright_identification_start = (adts_buffer[3] & 0x04) >> 2; var_header.aac_frame_length = ((adts_buffer[3] & 0x03) << 11) | (adts_buffer[4] << 3) | (adts_buffer[5] & 0xe0 >> 5); var_header.adts_buffer_fullness = (adts_buffer[5] & 0x1f << 6) | (adts_buffer[6] & 0xfc >> 2); var_header.number_of_raw_data_blocks_in_frame = adts_buffer[6] & 0x03; } else { return -1; } fclose(fp); return 0;}
复制代码


三,数据结构


AVCodecContext:编解码器上下文的数据结构, 保存了视频(音频)编解码相关信息。

AVFrame: 存储原始数据,视频对应的 RGB/YUV 像素数据,音频对应的 PCM 数据

AVPacket: 存储压缩数据,视频对应的 H264 数据流、音频对应的 AAC/MP3 数据流

SwrContext: 重采样上下文,需要设置输入/输出参数。

AVFormatContext: AVFormatContext 主要存储视音频封装格式中包含的信息。

  • AVIOContext *pb:输入数据的缓存

  • unsigned int nb_streams:视音频流的个数

  • AVStream **streams:视音频流

  • char filename[1024]:文件名

  • int64_t duration:时长(单位:微秒 us,转换为秒需要除以 1000000)

  • int bit_rate:比特率(单位 bps,转换为 kbps 需要除以 1000)

  • AVDictionary *metadata:元数据


AVStream:一路媒体流的抽象。

四,AAC 编码示例


1,编码基本流程



2, 代码示例

下面的代码实现的功能有

  1. 读取 pcm 文件

  2. 对 pcm 数据重采样,

  3. 编码 pcm 数据

  4. 将编码后的数据写入 aac 文件

const std::string res_dir = "./x64/Debug/";const std::string input_file = res_dir + "input_8k16bits.pcm";const std::string output_file = res_dir + "test.aac";
int AudioEncoder::Run(){
int ret = 0; // 创建编码器 const AVCodec* codec; codec = avcodec_find_encoder(AV_CODEC_ID_AAC); if (!codec) { fprintf(stderr, "avcodec_find_encoder failed\n"); return -1; }
// 配置编码器上下文 AVCodecContext* ac = NULL; ac = avcodec_alloc_context3(codec); if (!ac) { fprintf(stderr, "avcodec_alloc_context3 failed\n"); return -1; }
// 设置采样率 ac->sample_rate = 64000; ac->channels = 2; ac->sample_fmt = AVSampleFormat::AV_SAMPLE_FMT_FLTP; ac->bit_rate = 64000; // 设置声道类型 ac->channel_layout = AV_CH_LAYOUT_STEREO;
if (!check_sample_fmt(codec, ac->sample_fmt)) { fprintf(stderr, "Encoder does not support sample format %s", av_get_sample_fmt_name(ac->sample_fmt)); return -1; } // 打开编码器 ret = avcodec_open2(ac, codec, NULL); if (ret != 0) { fprintf(stderr, "avcodec_open2 failed"); return -1; }
// 打开pcm 文件 FILE* fp = fopen(input_file.c_str(),"rb"); if (!fp) { fprintf(stderr, "fopen failed"); return -1; }
//创建接收pcm 数据的帧,并设置参数 AVFrame* frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "av_frame_alloc failed"); return -1; } frame->format = ac->sample_fmt; frame->channels = 2; frame->channel_layout = ac->channel_layout; frame->nb_samples = ac->frame_size;
//为frame 分配空间 ret = av_frame_get_buffer(frame, 0); if (ret < 0) { fprintf(stderr, "av_frame_get_buffer failed\n"); return -1; }
// 创建接收编码码数据的packet AVPacket* pkt = av_packet_alloc(); if (!pkt) { fprintf(stderr, "av_packet_alloc failed"); return -1; } int read_size = frame->nb_samples * 2; char* pcms = new char[read_size];
// 创建重采样上下文 SwrContext* swr_ctx = NULL; swr_ctx = swr_alloc(); if (!swr_ctx) { fprintf(stderr, "swr_alloc failed\n"); return -1; } // 设置重采样上下文输出参数 av_opt_set_int(swr_ctx, "out_channel_layout", ac->channel_layout, 0); av_opt_set_int(swr_ctx, "out_sample_rate", ac->sample_rate, 0); av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", ac->sample_fmt, 0);
// 设置重采样上下文输入参数 av_opt_set_int(swr_ctx, "in_channel_layout", AV_CH_LAYOUT_MONO, 0); av_opt_set_int(swr_ctx, "in_sample_rate", 8000, 0); av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
// 初始化重采样上下文 if ((ret = swr_init(swr_ctx)) < 0) { fprintf(stderr, "Failed to initialize the resampling context\n"); return -1; }
// 创建AVFormatContext,并设置参数 AVFormatContext* oc = NULL; //初始化输出码流的 AVFormatContext avformat_alloc_output_context2(&oc, NULL, NULL, output_file.c_str()); if (!oc) { fprintf(stderr, "avformat_alloc_output_context2 failed\n"); return -1; }
//创建AVStream,并与AVFormatContext 进行关联 AVStream* st = avformat_new_stream(oc, NULL); st->codecpar->codec_tag = 0; avcodec_parameters_from_context(st->codecpar, ac); av_dump_format(oc, 0, output_file.c_str(), 1); ret = avio_open(&oc->pb, output_file.c_str(), AVIO_FLAG_WRITE); if (ret < 0) { fprintf(stderr, "avio_open failed\n"); return -1; }
avformat_write_header(oc, NULL); for (;;) { size_t len = fread(pcms, 1, read_size, fp); if (len < 0) break;
// 重采样pcm 数据 const uint8_t* data[1]; data[0] = (uint8_t*)pcms; ret = swr_convert(swr_ctx, frame->data, frame->nb_samples, (const uint8_t**)data, frame->nb_samples); if (ret < 0) { fprintf(stderr, "swr_convert failed\n"); return -1; }
// 编码并写入文件 Encoder(ac, frame, pkt,oc); }
// 释放资源 delete[] pcms; av_write_trailer(oc); avio_close(oc->pb); avcodec_close(ac); avcodec_free_context(&ac); avformat_free_context(oc); return 0;}
bool AudioEncoder::check_sample_fmt(const AVCodec* codec, enum AVSampleFormat sample_fmt){ const enum AVSampleFormat* p = codec->sample_fmts; while (*p != AV_SAMPLE_FMT_NONE) { if (*p == sample_fmt) return 1; p++; } return 0;}
void AudioEncoder::Encoder(AVCodecContext* ctx, AVFrame* frame, AVPacket* pkt, AVFormatContext* oc){ int ret; // 开始编码 ret = avcodec_send_frame(ctx, frame); if (ret < 0) { fprintf(stderr, "Error sending the frame to the encoder\n"); exit(1); }
// 接收编码出的数据 while (ret >= 0) { ret = avcodec_receive_packet(ctx, pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) return; else if (ret < 0) { fprintf(stderr, "Error encoding audio frame\n"); exit(1); }
// 写入aac 文件 pkt->stream_index = 0; // 表示音频流 av_interleaved_write_frame(oc, pkt); av_packet_unref(pkt); }}
复制代码


五,引用

AAC的ADTS头解析

FFMPEG结构体分析:AVFormatContext

用户头像

糖米唐爹

关注

还未添加个人签名 2020.08.12 加入

还未添加个人简介

评论

发布
暂无评论
初学 ffmpeg 打卡 day2