【FFmpeg实战】AAC编码, 解码

使用命令行进行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

img

// 包含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
    • 将解码后的数据写入文件

img

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

#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");
}

作者:lieon
链接:https://www.jianshu.com/p/2436addbc118

  >>> 音视频开发 视频教程: https://ke.qq.com/course/3202131?flowToken=1031864 
  >>> 音视频开发学习资料、教学视频,免费分享有需要的可以自行添加学习交流群: 739729163  领取
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值