使用命令行进行AAC编码
// PCM的三要素采样率,声道数, 采样格式
ffmpeg -ar 44100 -ac 2 -f s16le -i in.pcm out.aac
// -c:a codec:audio 指定的是音频编码
ffmpeg -ar 44100 -ac 2 -f s16le -i in.pcm -c:a libfdk_aac out.aac
ffmpeg -ar 44100 -ac 2 -f s16le -i 44100_s16le_2.pcm -c:a libfdk_aac out.aac
// wav格式的文件头已经有了pcm的三要素
ffmpeg -i in.wav -c:a libfdk_aac out.aac
// 设置输出比特率
ffmpeg -i in.wav -c:a libfdk_aac -b:a 96k out.aac
// 设置输出规格
ffmpeg -i in.wav -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k out.aac
// 开启VBR模式(可变比特率)
ffmpeg -i in.wav -c:a libfdk_aac -profile:a aac_he_v2 -vbr 1 out.aac
AAC编码步骤
- 获取编码器
avcodec_find_encoder_by_name
- 创建编码上下文
avcodec_alloc_context3
- 设置上下文PCM参数:采样格式,采样率,通道布局,比特率,规格
- 打开编码器
- 初始化输入AVFrame存放PCM
- 设置输入缓冲区参数:样本帧数量,格式,通道布局
- 利用nb_samples, format, channel_layout创建缓冲区
av_frame_get_buffer
- 创建输出缓冲区AVPacket
av_packet_alloc
- 读取PCM到创建好的AVFrame中
- 当文件中的PCM,不足以填满frame缓冲区时,应重新根据单位样本大小(
bytesPeSample * ch
)和剩余数据长度,计算出新的样本帧数量(nb_samples
),防止一些编码器编码一些冗余的数据
- 将填满AVFrame的frame进行AAC编码
- 发送数据到编码器
- 不断从编码器中取出编码后的数据, 将编码后的数据写入文件
- 释放输出缓冲区pkt内部的资源
av_packet_unref
// 包含static关键字的函数只在他所在的文件中是可见的,在其他文件中不可见,会导致找不到定义
static int checkSampleFmt(const AVCodec *codec, enum AVSampleFormat sampleFmt) {
const enum AVSampleFormat *p = codec->sample_fmts;
while (*p != AV_SAMPLE_FMT_NONE) {
if (*p == sampleFmt) {
return 1;
}
p++;
}
return 0;
}
static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, NSFileHandle *outFile) {
// 发送数据到编码器
int ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
ERROR_BUF(ret);
NSLog(@"avcodec_send_frame error: %s", errbuf);
return ret;
}
// 不断从编码器中取出编码后的数据
while (true) {
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// 继续读取数据到frame,然后送到编码器
return 0;
} else if (ret < 0) { // 其他错误
return ret;
}
// 成功从编码器拿到编码后的数据
// 将编码后的数据写入文件
NSData *data = [NSData dataWithBytes:pkt->data length:pkt->size];
[outFile writeData:data];
[outFile seekToEndOfFile];
// 释放pkt内部的资源
av_packet_unref(pkt);
}
return 0;
}
+ (void)aacEncodeWithSpec:(AudioEncodeSpec*)input outfile: (NSString*)outfileName {
NSFileHandle *infile = [NSFileHandle fileHandleForReadingAtPath:[NSString stringWithCString:input->filename encoding:NSUTF8StringEncoding]];
[[NSFileManager defaultManager]createFileAtPath: outfileName contents:[NSData new] attributes:nil];
NSFileHandle *outFile = [NSFileHandle fileHandleForWritingAtPath:outfileName];
[outFile seekToFileOffset:0];
int offset = 0;
int ret = 0;
// 编码器
AVCodec *codec = nullptr;
// 编码上下文
AVCodecContext *ctx = nullptr;
// 存放编码前的PCM
AVFrame *frame = nullptr;
// 存放编码后的数据 aac
AVPacket *pkt = nullptr;
NSData *inputDataBuffer = nullptr;
// 获取编码器
codec = avcodec_find_encoder_by_name("libfdk_aac");
if (!codec) {
NSLog(@"libfdk_acc encoder not found");
return;
}
// libfdk_aac对输入数据的要求:采样格式必须是16位整数
if(!checkSampleFmt(codec, input->sampleFmt)) {
NSLog(@"unsupported sample format: %s", av_get_sample_fmt_name(input->sampleFmt));
return;
}
// 创建编码上下文
ctx = avcodec_alloc_context3(codec);
if (!ctx) {
NSLog(@"avcodec_alloc_context3 error");
return;
}
// 设置PCM参数
ctx->sample_fmt = input->sampleFmt;
ctx->sample_rate = input->sampleRate;
ctx->channel_layout = input->chLayout;
// 比特率
ctx->bit_rate = 32000; // av_get_bytes_per_sample(input->sampleFmt) << 3;
//规格
ctx->profile = FF_PROFILE_AAC_HE_V2;
// 打开编码器
ret = avcodec_open2(ctx, codec, nullptr);
if (ret < 0) {
goto end;
}
frame = av_frame_alloc();
if (!frame) {
NSLog(@"av_frame_alloc error");
goto end;
}
// frame缓冲区中的样本帧数量
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 < 0) {
ERROR_BUF(ret);
NSLog(@"av_frame_get_buffer error: %s", errbuf);
goto end;
}
// 创建AVPacket
pkt = av_packet_alloc();
if (!pkt) {
NSLog(@"av_packet_alloc erro");
goto end;
}
// 读取数据到frame
[infile seekToFileOffset:offset];
inputDataBuffer = [infile readDataOfLength:frame->linesize[0]];
frame->data[0] = (uint8_t *)inputDataBuffer.bytes;
offset += frame->linesize[0];
NSLog(@"inputDataBuffer-length: %ld - frame->linesize[0]: %d - offset: %d", inputDataBuffer.length, frame->linesize[0], offset);
while (inputDataBuffer.length > 0) {
// 从文件中读取的数据,不足以填满farme缓冲区
if (inputDataBuffer.length < 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 = (int)inputDataBuffer.length / (bytes * ch);
NSLog(@"文件中读取的数据,不足以填满farme缓冲区: %d", frame->linesize[0]);
}
if (encode(ctx, frame, pkt, outFile)) {
goto end;
}
[infile seekToFileOffset:offset];
inputDataBuffer = [infile readDataOfLength:frame->linesize[0]];
frame->data[0] = (uint8_t *)inputDataBuffer.bytes;
offset += frame->linesize[0];
NSLog(@"inputDataBuffer-length: %ld - frame->linesize[0]: %d - offset: %d", inputDataBuffer.length, frame->linesize[0], offset);
}
encode(ctx, nullptr, pkt, outFile);
end:
[infile closeFile];
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&ctx);
NSLog(@"End");
}
AAC解码步骤
- 获取解码器
avcodec_find_decoder_by_name
- 初始化解码器上下文
av_parser_init
- 创建上下文
avcodec_alloc_context3
- 创建输入缓冲区AVPacket
av_packet_alloc
- 创建输出缓冲区AVFrame
av_frame_alloc
- 打开解码器
avcodec_open2
- 读取数据到输入缓冲区,将输入缓冲区的数据送入 解码解析器
- 读取数据的方式采用分段(IN_DATA_SIZE)读取,
- 当剩余数据小于REFILL_THRESH时,继续读取剩余的数据,
- 当读取到的数据长度为0时,直接跳出
- 将解析器的数据送入解码器进行解码
- 发送压缩数据到解码器
avcodec_send_packet
- 获取解码后的数据
avcodec_receive_frame
- 将解码后的数据写入文件
#import "AACDecode.h"
extern "C" {
#include
#include
}
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
// 输入缓冲区的大小
#define IN_DATA_SIZE 20480
// 需要再次读取输入文件数据的阈值
#define REFILL_THRESH 4096
@implementation AACDecode
static int decode(AVCodecContext *ctx, AVPacket *pkt, AVFrame *frame, NSFileHandle *outFile) {
int ret = avcodec_send_packet(ctx, pkt);
if (ret < 0) {
return ret;
}
while (true) {
ret = avcodec_receive_frame(ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
} else if (ret < 0) {
ERROR_BUF(ret);
NSLog(@"avcodec_receive_frame error: %s", errbuf);
return ret;
}
// 将编码后的数据写入文件
[outFile writeData:[NSData dataWithBytes:frame->data[0] length:frame->linesize[0]]];
[outFile seekToEndOfFile];
}
return 0;
}
+ (void)aacDecode:(NSString*)filename output:(AudioDecodeSpec*)output {
int ret = 0;
// 用来存放读取的输入文件数据
// 加上AV_INPUT_BUFFER_PADDING_SIZE是为了防止某些优化的reader一次读取过多导致越界
NSData *inDataArrayNS = nullptr;
char inDataArray[IN_DATA_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
char *inData = inDataArray;
// 每次输入文件中读取的长度(aac)
int inLen = 0;
// 是否读取到了输入文件的尾部
int inEnd = 0;
NSFileHandle *inFile = [NSFileHandle fileHandleForReadingAtPath:filename];
[[NSFileManager defaultManager]createFileAtPath:[NSString stringWithUTF8String:output->filename] contents:[NSData new] attributes:nil];
NSFileHandle *outFile = [NSFileHandle fileHandleForWritingAtPath:[NSString stringWithUTF8String:output->filename]];
// 解码器
AVCodec *codec = nullptr;
// 上下文
AVCodecContext *ctx = nullptr;
// 解析器上下文
AVCodecParserContext *parserCtx = nullptr;
// 存放解码前的数据
AVPacket *pkt = nullptr;
// 存放编码后的数据(PCM)
AVFrame *frame = nullptr;
// 获取解码器
codec = avcodec_find_decoder_by_name("libfdk_aac");
if (!codec) {
NSLog(@"decode not found");
return;
}
// 初始化解析器上下文
parserCtx = av_parser_init(codec->id);
if (!parserCtx) {
NSLog(@"av_parser_init error");
return;
}
// 创建上下文
ctx = avcodec_alloc_context3(codec);
if (!ctx) {
goto end;
}
// 创建AVPacket
pkt = av_packet_alloc();
if (!pkt) {
NSLog(@"av_packet_alloc error");
goto end;
}
frame = av_frame_alloc();
if (!frame) {
NSLog(@"av_frame_alloc error");
goto end;
}
// 打开编码器
ret = avcodec_open2(ctx, codec, nullptr);
if (ret < 0) {
ERROR_BUF(ret);
NSLog(@"avcodec_open2 error: %s", errbuf);
goto end;
}
inDataArrayNS = [inFile readDataOfLength:IN_DATA_SIZE];
inData = (char*)inDataArrayNS.bytes;
inLen = (int)inDataArrayNS.length;
while (inLen > 0) {
ret = av_parser_parse2(parserCtx,
ctx,
&pkt->data,
&pkt->size,
(uint8_t *)inData,
inLen,
AV_NOPTS_VALUE,
AV_NOPTS_VALUE, 0);
if (ret < 0) {
ERROR_BUF(ret);
NSLog(@"av_parser_parse2 error: %s", errbuf);
goto end;
}
// 跳过已经解析过的数据
inData += ret;
// 减去已经解析过的数据大小
inLen -= ret;
// 解码
if (pkt->size > 0 && decode(ctx, pkt, frame, outFile) < 0) {
goto end;
}
NSLog(@"inLen:%d", inLen);
// 检查是否需要读取新的文件数据
if (inLen < REFILL_THRESH && !inEnd) {
NSMutableData *data = [NSMutableData data];
[data appendData:[NSData dataWithBytes:inData length:inLen]];
NSData *padderData = [inFile readDataOfLength:IN_DATA_SIZE - inLen];
[data appendData:padderData];
inData = (char*)data.bytes;
int len = (int)padderData.length;
if (len > 0) {
inLen = (int)data.length;
} else {
inEnd = 1;
}
}
}
// 刷新缓冲区
decode(ctx, nullptr, frame, outFile);
// 赋值输出参数
output->sampleRate = ctx->sample_rate;
output->sampleFmt = ctx->sample_fmt;
output->chLayout = (int)ctx->channel_layout;
end:
[inFile closeFile];
[outFile closeFile];
av_packet_free(&pkt);
av_frame_free(&frame);
av_parser_close(parserCtx);
avcodec_free_context(&ctx);
NSLog(@"End");
}