流程:
第一步:下载fdk_aac开发包->共用->C开发库
第二步:编写脚本->编译iOS平台下fdk_aac开发包->编译.a静态库
#!/bin/sh
CONFIGURE_FLAGS="--enable-static --with-pic=yes --disable-shared"
ARCHS="arm64 x86_64 i386 armv7"
# directories
SOURCE="fdk-aac"
FAT="fdk-aac-ios"
SCRATCH="scratch"
# must be an absolute path
THIN=`pwd`/"thin-fdkaac"
COMPILE="y"
LIPO="y"
if [ "$*" ]
then
if [ "$*" = "lipo" ]
then
# skip compile
COMPILE=
else
ARCHS="$*"
if [ $# -eq 1 ]
then
# skip lipo
LIPO=
fi
fi
fi
if [ "$COMPILE" ]
then
CWD=`pwd`
for ARCH in $ARCHS
do
echo "building $ARCH..."
mkdir -p "$SCRATCH/$ARCH"
cd "$SCRATCH/$ARCH"
CFLAGS="-arch $ARCH"
if [ "$ARCH" = "i386" -o "$ARCH" = "x86_64" ]
then
PLATFORM="iPhoneSimulator"
CPU=
if [ "$ARCH" = "x86_64" ]
then
CFLAGS="$CFLAGS -mios-simulator-version-min=7.0"
HOST="--host=x86_64-apple-darwin"
else
CFLAGS="$CFLAGS -mios-simulator-version-min=7.0"
HOST="--host=i386-apple-darwin"
fi
else
PLATFORM="iPhoneOS"
if [ $ARCH = arm64 ]
then
HOST="--host=aarch64-apple-darwin"
else
HOST="--host=arm-apple-darwin"
fi
CFLAGS="$CFLAGS -fembed-bitcode"
fi
XCRUN_SDK=`echo $PLATFORM | tr '[:upper:]' '[:lower:]'`
CC="xcrun -sdk $XCRUN_SDK clang -Wno-error=unused-command-line-argument-hard-error-in-future"
AS="$CWD/$SOURCE/extras/gas-preprocessor.pl $CC"
CXXFLAGS="$CFLAGS"
LDFLAGS="$CFLAGS"
$CWD/$SOURCE/configure \
$CONFIGURE_FLAGS \
$HOST \
$CPU \
CC="$CC" \
CXX="$CC" \
CPP="$CC -E" \
AS="$AS" \
CFLAGS="$CFLAGS" \
LDFLAGS="$LDFLAGS" \
CPPFLAGS="$CFLAGS" \
--prefix="$THIN/$ARCH"
make -j3 install
cd $CWD
done
fi
if [ "$LIPO" ]
then
echo "building fat binaries..."
mkdir -p $FAT/lib
set - $ARCHS
CWD=`pwd`
cd $THIN/$1/lib
for LIB in *.a
do
cd $CWD
lipo -create `find $THIN -name $LIB` -output $FAT/lib/$LIB
done
cd $CWD
cp -rf $THIN/$1/include $FAT
fi
第三步:编译FFmpeg库->将FFmpeg和fdk_aac合并->编译.a静态库->依赖
#!/bin/bash
#1、首先:定义下载的库名称
source="ffmpeg-3.4"
#2、其次:定义".h/.m/.c"文件编译的结果目录
#目录作用:用于保存.h/.m/.c文件编译后的结果.o文件
cache="cache"
#3、定义".a"静态库保存目录
#pwd命令:表示获取当前目录
staticdir=`pwd`/"build-iOS-fdkaac-ffmpeg"
#4、添加FFmpeg配置选项->默认配置
#Toolchain options:工具链选项(指定我么需要编译平台CPU架构类型,例如:arm64、x86等等…)
#--enable-cross-compile: 交叉编译
#Developer options:开发者选项
#--disable-debug: 禁止使用调试模式
#Program options选项
#--disable-programs:禁用程序(不允许建立命令行程序)
#Documentation options:文档选项
#--disable-doc:不需要编译文档
#Toolchain options:工具链选项
#--enable-pic:允许建立与位置无关代码
configure_flags="--enable-cross-compile --disable-debug --enable-x86asm --disable-programs --disable-doc --enable-pic"
#核心库(编解码->最重要的库):avcodec
#5、定义默认CPU平台架构类型
#arm64 armv7->真机->CPU架构类型
#x86_64 i386->模拟器->CPU架构类型
archs="arm64 armv7 x86_64 i386"
#6、指定我们的这个库编译系统版本->iOS系统下的7.0以及以上版本使用这个静态库
targetversion="7.0"
#7、接受命令后输入参数
#我是动态接受命令行输入CPU平台架构类型(输入参数:编译指定的CPU库)
if [ "$*" ]
then
#存在输入参数,也就说:外部指定需要编译CPU架构类型
archs="$*"
fi
echo "循环编译"
#9、for循环编译FFmpeg静态库
currentdir=`pwd`
for arch in $archs
do
echo "开始编译"
#9.1、创建目录
#在编译结果目录下-创建对应的平台架构类型
mkdir -p "$cache/$arch"
#9.2、进入这个目录
cd "$cache/$arch"
#9.3、配置编译CPU架构类型->指定当前编译CPU架构类型
#错误三:"--arch $arch"
#正确三:"-arch $arch"
archflags="-arch $arch"
#9.4、判定一下你到底是编译的是模拟器.a静态库,还是真机.a静态库
if [ "$arch" = "i386" -o "$arch" = "x86_64" ]
then
#模拟器
platform="iPhoneSimulator"
#支持最小系统版本->iOS系统
archflags="$archflags -mios-simulator-version-min=$targetversion"
else
#真机(mac、iOS都支持)
platform="iPhoneOS"
#支持最小系统版本->iOS系统
archflags="$archflags -mios-version-min=$targetversion -fembed-bitcode"
#注意:优化处理(可有可无)
#如果架构类型是"arm64",那么
if [ "$arch" = "arm64" ]
then
#GNU汇编器(GNU Assembler),简称为GAS
#GASPP->汇编器预处理程序
#解决问题:分段错误
#通俗一点:就是程序运行时,变量访问越界一类的问题
EXPORT="GASPP_FIX_XCODE5=1"
fi
fi
#10、正式编译
#tr命令可以对来自标准输入的字符进行替换、压缩和删除
#'[:upper:]'->将小写转成大写
#'[:lower:]'->将大写转成小写
#将platform->转成大写或者小写
XCRUN_SDK=`echo $platform | tr '[:upper:]' '[:lower:]'`
#编译器->编译平台
CC="xcrun -sdk $XCRUN_SDK clang"
#架构类型->arm64
if [ "$arch" = "arm64" ]
then
#音视频默认一个编译命令
#preprocessor.pl帮助我们编译FFmpeg->arm64位静态库
AS="gas-preprocessor.pl -arch aarch64 -- $CC"
else
#默认编译平台
AS="$CC"
fi
echo "执行到了1"
#目录找到FFmepg编译源代码目录->设置编译配置->编译FFmpeg源码
#--target-os:目标系统->darwin(mac系统早起版本名字)
#darwin:是mac系统、iOS系统祖宗
#--arch:CPU平台架构类型
#--cc:指定编译器类型选项
#--as:汇编程序
#$configure_flags最初配置
#--extra-cflags
#--prefix:静态库输出目录
TMPDIR=${TMPDIR/%\/} $currentdir/$source/configure \
--target-os=darwin \
--arch=$arch \
--cc="$CC" \
--as="$AS" \
$configure_flags \
--disable-encoders \
--enable-libfdk-aac \
--enable-encoder=libfdk_aac \
--enable-decoder=libfdk_aac \
--extra-cflags="$archflags " \
--extra-ldflags="$archflags " \
--extra-cflags="-I/Users/yangshaohong/Desktop/ffmpeg-test/test/thin-fdkaac/arm64/include" \
--extra-ldflags="-L/Users/yangshaohong/Desktop/ffmpeg-test/test/thin-fdkaac/arm64/lib" \
--prefix="$staticdir/$arch" \
|| exit 1
echo "执行了"
#解决问题->分段错误问题
#安装->导出静态库(编译.a静态库)
#执行命令
#将-j设置为支持多核心/线程
make -j3 install $EXPORT || exit 1
#回到了我们的脚本文件目录
cd $currentdir
done
第四步:导入项目中
注意:FFmpeg需要导入进去,fdk_aac库也需要导入到项目中
第五步:进行开发
解码实现:
//导入音视频头文件库
//核心库
#include "libavcodec/avcodec.h"
//封装格式处理库
#include "libavformat/avformat.h"
//工具库
#include "libavutil/imgutils.h"
//视频像素数据格式库
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index) {
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1) {
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
ret = avcodec_encode_video2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame) {
ret = 0;
break;
}
NSLog(@"Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", enc_pkt.size);
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
@implementation FFmpegAudioDecodeTest
+(void)ffmpegAudioEncode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath{
//第一步:注册组件->音频编码器等等…
av_register_all();
//第二步:初始化封装格式上下文->视频编码->处理为音频压缩数据格式
AVFormatContext *avformat_context = avformat_alloc_context();
//注意事项:FFmepg程序推测输出文件类型->音频压缩数据格式类型->aac格式
const char *coutFilePath = [outFilePath UTF8String];
//得到音频压缩数据格式类型(aac、mp3等...)
AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);
//指定类型
avformat_context->oformat = avoutput_format;
//第三步:打开输出文件
//参数一:输出流
//参数二:输出文件
//参数三:权限->输出到文件中
if (avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE) < 0) {
NSLog(@"打开输出文件失败");
return;
}
//第四步:创建输出码流->创建了一块内存空间->并不知道他是什么类型流->希望他是视频流
AVStream *audio_st = avformat_new_stream(avformat_context, NULL);
//第五步:查找音频编码器
//1、获取编码器上下文
AVCodecContext *avcodec_context = audio_st->codec;
//2、设置编解码器上下文参数->必需设置->不可少
//目标:设置为是一个音频编码器上下文->指定的是音频编码器
//上下文种类:音频解码器、音频编码器
//2.1 设置音频编码器ID
avcodec_context->codec_id = avoutput_format->audio_codec;
//2.2 设置编码器类型->音频编码器
//视频编码器->AVMEDIA_TYPE_VIDEO
//音频编码器->AVMEDIA_TYPE_AUDIO
avcodec_context->codec_type = AVMEDIA_TYPE_AUDIO;
//2.3 设置读取音频采样数据格式->编码的是音频采样数据格式->音频采样数据格式->pcm格式
//注意:这个类型是根据你解码的时候指定的解码的音频采样数据格式类型
avcodec_context->sample_fmt = AV_SAMPLE_FMT_S16;
//设置采样率
avcodec_context->sample_rate = 44100;
//立体声
avcodec_context->channel_layout = AV_CH_LAYOUT_STEREO;
//声道数量
int channels = av_get_channel_layout_nb_channels(avcodec_context->channel_layout);
avcodec_context->channels = channels;
//设置码率
//基本的算法是:【码率】(kbps)=【视频大小 - 音频大小】(bit位) /【时间】(秒)
avcodec_context->bit_rate = 128000;
//第二点:查找音频编码器->aac
// AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);
AVCodec *avcodec = avcodec_find_encoder_by_name("libfdk_aac");
if (avcodec == NULL) {
NSLog(@"找不到音频编码器");
return;
}
//第六步:打开aac编码器
if (avcodec_open2(avcodec_context, avcodec, NULL) < 0) {
NSLog(@"打开音频编码器失败");
return;
}
//第七步:写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)
avformat_write_header(avformat_context, NULL);
//打开YUV文件
const char *c_inFilePath = [inFilePath UTF8String];
FILE *in_file = fopen(c_inFilePath, "rb");
if (in_file == NULL) {
NSLog(@"YUV文件打开失败");
return;
}
//第十步:初始化音频采样数据帧缓冲区
AVFrame *av_frame = av_frame_alloc();
av_frame->nb_samples = avcodec_context->frame_size;
av_frame->format = avcodec_context->sample_fmt;
//得到音频采样数据缓冲区大小
int buffer_size = av_samples_get_buffer_size(NULL,
avcodec_context->channels,
avcodec_context->frame_size,
avcodec_context->sample_fmt,
1);
//创建缓冲区->存储音频采样数据->一帧数据
uint8_t *out_buffer = (uint8_t *) av_malloc(buffer_size);
avcodec_fill_audio_frame(av_frame,
avcodec_context->channels,
avcodec_context->sample_fmt,
(const uint8_t *)out_buffer,
buffer_size,
1);
//第十二步:创建音频压缩数据->帧缓存空间
AVPacket *av_packet = (AVPacket *) av_malloc(buffer_size);
//第十三步:循环读取视频像素数据格式->编码压缩->视频压缩数据格式
int frame_current = 1;
int i = 0, ret = 0;
//第八步:循环编码每一帧视频
//即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)
while (true) {
//1、读取一帧音频采样数据
if (fread(out_buffer, 1, buffer_size, in_file) <= 0) {
NSLog(@"Failed to read raw data! \n");
break;
} else if (feof(in_file)) {
break;
}
//2、设置音频采样数据格式
//将outbuffer->av_frame格式
av_frame->data[0] = out_buffer;
av_frame->pts = i;
i++;
//3、编码一帧音频采样数据->得到音频压缩数据->aac
//采用新的API
//3.1 发送一帧音频采样数据
ret = avcodec_send_frame(avcodec_context, av_frame);
if (ret != 0) {
NSLog(@"Failed to send frame! \n");
return;
}
//3.2 编码一帧音频采样数据
ret = avcodec_receive_packet(avcodec_context, av_packet);
if (ret == 0) {
//第九步:将编码后的音频码流写入文件
NSLog(@"当前编码到了第%d帧", frame_current);
frame_current++;
av_packet->stream_index = audio_st->index;
ret = av_write_frame(avformat_context, av_packet);
if (ret < 0) {
NSLog(@"写入失败! \n");
return;
}
} else {
NSLog(@"Failed to encode! \n");
return;
}
}
//第十步:输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。
ret = flush_encoder(avformat_context, 0);
if (ret < 0) {
NSLog(@"Flushing encoder failed\n");
return;
}
//第十一步:写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)
av_write_trailer(avformat_context);
//第十二步:释放内存,关闭编码器
avcodec_close(avcodec_context);
av_free(av_frame);
av_free(out_buffer);
av_packet_free(&av_packet);
avio_close(avformat_context->pb);
avformat_free_context(avformat_context);
fclose(in_file);
}