【FFmpeg实战】AAC编码, 解码

news2024/10/6 4:03:10

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/704670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

哪些辅助工具支持快递单号的批量查询

当我们批量发出大量快递时&#xff0c;有什么查询软件可以快速查询和跟踪批量每个单号的物流信息&#xff0c;及时跟踪物流状态&#xff1f;今天&#xff0c;我来安利一个软件&#xff1a;“固乔快递查询助手”&#xff0c;可以实现这个功能。我们一起来看看软件的使用和操作步…

C++类与对象(默认成员函数之拷贝构造函数)

接前几次的类与对象的默认函数的知识点&#xff0c;下来面是默认成员函数中的拷贝构造函数。是的&#xff0c;它的名字是拷贝构造函数&#xff0c;他其实也是一种构造函数&#xff0c;为什么呢&#xff1f;接下来你就知道了&#xff0c;我们直接看看代码&#xff0c;如下&#…

【AICG】【Midjourney】AI自动生成图片的初接触

背景 现在是AI 比较流行&#xff0c;公司也推荐学习和了解AI的相关东西。 公司的内部培训中涉及到了Midjourney的简单讲解。 效果 由于我的账号注册的时候过了free窗口期&#xff1a; 现在要我花钱订阅&#xff1a; 只能看看其他人的帖子过过AI生成的瘾。 如图所示&#x…

【TA100】4.2 SSAO算法 屏幕空间环境光遮蔽

1 SSAO介绍 1.1 环境光遮蔽&#xff08;AO&#xff09; 全称Ambient Occlusion,是计算机图形学中一种着色和渲染技术&#xff0c;模拟光线达到物体的能力的粗略的全局方法&#xff0c;描述光线到达物体表面的能力。 这个场景看起来没有表示颜色的贴图&#xff0c;但是场景的…

SC5103接口可pin对pin兼容TLK1501

SC5103 用于超高速双向点对点数据传输系统。SC5103 支持 0.6Gbps 至 1.5Gbps 的有效串行接口速度&#xff0c;提供高达 1.2Gbps 的有效数据带宽。可pin对pin兼容TLK1501。 SC5103 可替代并行传输数据结构从而减少传输路径数、连接端子数、发送/接收端子数。并行数据被发送链路转…

求职贴 | 多源融合定位方向24届硕士秋招求职

求职方向 多源融合定位 / LIO / VIO 基本情况 现就读于华中区某985&#xff0c;研究方向为多源融合定位&#xff0c;主要内容是LiDAR / IMU融合两篇期刊论文在投&#xff0c;论文内容即项目经历&#xff1b;具有丰富的电子设计类竞赛经历自我认为学习能力强&#xff0c;爱动…

JavaSE-15 【异常】

文章目录 JavaSE-15 【异常】第一章 什么是异常1.1 异常的概念1.2 异常的体系1.3 异常的分类1.4 异常产生的过程 第二章 异常的处理2.1 抛出异常throw2.2 声明异常throws2.3 捕获异常try...catch2.4 finally 代码块2.5 异常注意事项 第三章 自定义异常3.1 自定义异常概述3.2 自…

工业物联网解决方案:远程组态监控管理系统

如何高效的采集和集中工业设备PLC运行数据、工艺参数、产品质量等生产数据&#xff0c;通过数据分析反馈在制造工艺、生产流程、质量管理、设备维护和能耗管理的智能应用&#xff0c;这是工业远程组态监控管理系统可以解决的问题&#xff0c;也是众多工厂企业关注的重点。 工…

动态规划--输出路径06.25

https://www.cnblogs.com/jbelial/articles/2116074.html 博客参考 https://www.cnblogs.com/jbelial/articles/2116074.html 12. 背包问题求具体方案 - AcWing题库 由于需要求解最小字典序&#xff0c;尝试输入数据时逆序输入&#xff0c;其他不改变&#xff0c;状态含义不变…

奇安信浏览器调用JM9硬件解码操作教程

本文讲述如何通过奇安信浏览器调用景嘉微JM9系列显卡的硬件解码。 判断硬件解码是否打开方法 1.检查奇安信浏览器版本。dpkg -l | grep qaxbrowser&#xff0c;我这里是1.0.45209.2-1版本。要大于等于这个版本的奇安信浏览器才支持硬件解码。 2.打开奇安信浏览器&#xff…

海思平台OSD的实现

目录 1.海思平台OSD理论学习 1.1、OSD概述 1.2、海思OSD的4种类型 1.3、海思OSD的几个重要概念 1.4、海思平台OSD使用方法 1.5、海思平台OSD的API和关键数据结构 2.使用海思接口实现OSD---代码框架 2.1、函数调用层次 2.2、前\背景透明度、背景颜色 3.使用字库字符实现…

Jmeter远程服务模式运行时引用csv文件的路径配置

目录 前言&#xff1a; 问题 解决方法 前言&#xff1a; 在JMeter远程服务模式下运行时&#xff0c;你可以通过配置CSV文件的路径来引用该文件。CSV文件经常用于存储测试数据&#xff0c;可以在测试中进行参数化。 问题 在使用jmeter过程中&#xff0c;本机的内存等配置不…

掌握GDB调试工具,轻松排除bug!

一、什么是GDB gdb是GNU debugger的缩写&#xff0c;是编程调试工具。 GDB官网&#xff1a; https://www.gnu.org/software/gdb/GDB适用的编程语言&#xff1a; Ada / C / C / objective-c / Pascal 等。GDB的工作方式&#xff1a; 本地调试和远程调试。 目前release的最新版…

RRT* 算法研究(附 MATLAB 和 Python 实现)

RRT* 算法研究 参考 机器人路径规划、轨迹优化课程-第六讲-RRT*算法原理和代码讲解 路径规划 | 随机采样算法&#xff1a;PRM、RRT、RRT-Connect、RRT* 基于采样的运动规划算法-RRT(Rapidly-exploring Random Trees) 《改进RRT算法在移动机器人路径规划中的应用研究》 理论基础…

全网超全,pytest自动化测试框架pytest.ini配置文件详细(实战)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 pytest配置文件可…

Java 实现word、excel、ppt、txt等办公文件在线预览功能!

如何用 Java 实现word、excel、ppt、txt等办公文件在线预览功能&#xff1f;本文告诉你答案&#xff01; java 实现办公文件在线预览功能是一个大家在工作中也许会遇到的需求&#xff0c;网上些公司专门提供这样的服务&#xff0c;不过需要收费。 如果想要免费的&#xff0c;…

【ISO26262】汽车功能安全:以汽车安全完整性等级为导向和以安全为导向的分析

关于 ASIL剪裁的要求分解 表 A.1 以汽车安全完整性等级为导向和以安全为导向的分析的概览

Android Binder通信原理(五):Java 端的service 注册和获取

源码基于&#xff1a;Android R 0. 前言 在阐述相关的知识点&#xff0c;先整理个框架图&#xff0c;后面带着框架图来分析过程&#xff1a; Java 端对于binder 使用大致分为&#xff1a; Java client Java serviceJava client native service Java 端service 的注册使用 Se…

前端如何进行页面优化_如何优化前端页面

优化前端页面 1 .前期准备 1.1 首页命名为index.html / index.htm / index.php等。 1.2 需要制作404页面。 1.3 文件夹结构合理。 1.4 命名使用英文且有语义性的单词&#xff0c;并提供参考文档。 2 .结构 2.1 文件头部分 2.1.1 需要提供文档声明 2.1.2 设置utf-8的编…