6.音频编码流程

流程:

第一步:下载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);
}

你可能感兴趣的:(6.音频编码流程)