美文网首页音视频积累
FFmpeg音频 - AAC编码, 解码

FFmpeg音频 - AAC编码, 解码

作者: lieon | 来源:发表于2021-07-12 16:01 被阅读0次

    使用命令行进行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 <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");
    }
    

    相关文章

      网友评论

        本文标题:FFmpeg音频 - AAC编码, 解码

        本文链接:https://www.haomeiwen.com/subject/qjgorltx.html